[
  {
    "path": ".cherry_picker.toml",
    "content": "team = \"aio-libs\"\nrepo = \"aiohttp\"\ncheck_sha = \"f382b5ffc445e45a110734f5396728da7914aeb6\"\nfix_commit_msg = false\n"
  },
  {
    "path": ".codecov.yml",
    "content": "codecov:\n  branch: master\n  notify:\n    manual_trigger: true\n\ncomment:\n  require_head: false\n  require_base: false\n\ncoverage:\n  range: \"95..100\"\n\n  status:\n    project: no\n\ncomponent_management:\n  individual_components:\n    - component_id: project\n      paths:\n        - aiohttp/**\n    - component_id: tests\n      paths:\n        - tests/**\n\nflags:\n  library:\n    paths:\n    - aiohttp/\n  configs:\n    paths:\n    - requirements/\n    - \".git*\"\n    - \"*.toml\"\n    - \"*.yml\"\n  changelog:\n    paths:\n    - CHANGES/\n    - CHANGES.rst\n  docs:\n    paths:\n    - docs/\n    - \"*.md\"\n    - \"*.rst\"\n    - \"*.txt\"\n  tests:\n    paths:\n    - tests/\n  tools:\n    paths:\n    - tools/\n  third-party:\n    paths:\n    - vendor/\n"
  },
  {
    "path": ".coveragerc.toml",
    "content": "[run]\nbranch = true\n# NOTE: `ctrace` tracing method is needed because the `sysmon` tracer\n# NOTE: which is default on Python 3.14, causes unprecedented slow-down\n# NOTE: of the test runs.\n# Ref: https://github.com/coveragepy/coveragepy/issues/2099\ncore = 'ctrace'\nsource = [\n  'aiohttp',\n  'tests',\n]\nomit = [\n  'site-packages',\n]\n\n[report]\nexclude_also = [\n  'if TYPE_CHECKING',\n  'assert False',\n  ': \\.\\.\\.(\\s*#.*)?$',\n  '^ +\\.\\.\\.$',\n  'pytest.fail\\('\n]\n"
  },
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: http://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file\n[*]\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 4\ntrim_trailing_whitespace = true\ncharset = utf-8\n\n[Makefile]\nindent_style = tab\n\n[*.{yml,yaml}]\nindent_size = 2\n\n[*.rst]\nmax_line_length = 80\n"
  },
  {
    "path": ".git-blame-ignore-revs",
    "content": "# git hyper-blame master ignore list.\n#\n# This file contains a list of git hashes of revisions to be ignored by git\n# hyper-blame (in depot_tools). These revisions are considered \"unimportant\" in\n# that they are unlikely to be what you are interested in when blaming.\n#\n# Instructions:\n# - Only large (generally automated) reformatting or renaming CLs should be\n#   added to this list. Do not put things here just because you feel they are\n#   trivial or unimportant. If in doubt, do not put it on this list.\n# - Precede each revision with a comment containing the first line of its log.\n#   For bulk work over many commits, place all commits in a block with a single\n#   comment at the top describing the work done in those commits.\n# - Only put full 40-character hashes on this list (not short hashes or any\n#   other revision reference).\n# - Append to the bottom of the file (revisions should be in chronological order\n#   from oldest to newest).\n# - Because you must use a hash, you need to append to this list in a follow-up\n#   CL to the actual reformatting CL that you are trying to ignore.\n\n# Black\n6ab76b084bf5012b7185046162ed92bedcf073b5\n\n# Apply new hooks\n41c5467a62fb1041b77356ea22b81a74305941ef\n\n# Tune C source generation\n3f7d64798d46b8b166139811a377ba231b4f36bf\n\n# Apply pyupgrade\n32833c3fe081ff75c0b08ace9cc71e821a72fc5e\n"
  },
  {
    "path": ".gitattributes",
    "content": "tests/data.unknown_mime_type binary\ntests/sample.* binary\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @asvetlov\n/.github/* @webknjaz @asvetlov\n/.circleci/* @webknjaz @asvetlov\n/CHANGES/* @asvetlov\n/docs/* @asvetlov\n/examples/* @asvetlov\n/requirements/* @webknjaz @asvetlov\n/tests/* @asvetlov\n/tools/* @webknjaz @asvetlov\n/vendor/* @webknjaz @asvetlov\n*.ini @webknjaz @asvetlov\n*.md @webknjaz @asvetlov\n*.rst @webknjaz @asvetlov\n*.toml @webknjaz @asvetlov\n*.txt @webknjaz @asvetlov\n*.yml @webknjaz @asvetlov\n*.yaml @webknjaz @asvetlov\n.editorconfig @webknjaz @asvetlov\n.git* @webknjaz\nMakefile @webknjaz @asvetlov\nsetup.py @webknjaz @asvetlov\nsetup.cfg @webknjaz @asvetlov\ntox.ini @webknjaz\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "---\n\ngithub:\n- asvetlov\n- webknjaz\n- Dreamsorcerer\nopen_collective: aiohttp\ntidelift: pypi/aiohttp  # A single Tidelift platform-name/package-name e.g., npm/babel\ncustom:\n- https://opencollective.com/aio-libs\n\n...\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "---\nname: Bug Report\ndescription: Create a report to help us improve.\nlabels: [bug]\nassignees: aio-libs/triagers\nbody:\n- type: markdown\n  attributes:\n    value: |\n      **Thanks for taking a minute to file a bug report!**\n\n      ⚠\n      Verify first that your issue is not [already reported on\n      GitHub][issue search].\n\n      _Please fill out the form below with as many precise\n      details as possible._\n\n      [issue search]: ../search?q=is%3Aissue&type=issues\n\n- type: textarea\n  attributes:\n    label: Describe the bug\n    description: >-\n      A clear and concise description of what the bug is.\n  validations:\n    required: true\n\n- type: textarea\n  attributes:\n    label: To Reproduce\n    description: >-\n      Describe the steps to reproduce this bug.\n    placeholder: |\n      1. Implement the following server or a client '...'\n      2. Then run '...'\n      3. An error occurs.\n      The chances of someone looking at your issue are *vastly* improved if you provide\n      complete code that can be copy/pasted and executed directly in Python.\n  validations:\n    required: true\n\n- type: textarea\n  attributes:\n    label: Expected behavior\n    description: >-\n      A clear and concise description of what you expected to happen.\n  validations:\n    required: true\n\n- type: textarea\n  attributes:\n    label: Logs/tracebacks\n    description: |\n      If applicable, add logs/tracebacks to help explain your problem.\n      Paste the output of the steps above, including the commands\n      themselves and their output/traceback etc.\n    render: python-traceback\n  validations:\n    required: true\n\n- type: textarea\n  attributes:\n    label: Python Version\n    description: Attach your version of Python.\n    render: console\n    value: |\n      $ python --version\n  validations:\n    required: true\n- type: textarea\n  attributes:\n    label: aiohttp Version\n    description: Attach your version of aiohttp.\n    render: console\n    value: |\n      $ python -m pip show aiohttp\n  validations:\n    required: true\n- type: textarea\n  attributes:\n    label: multidict Version\n    description: Attach your version of multidict.\n    render: console\n    value: |\n      $ python -m pip show multidict\n  validations:\n    required: true\n- type: textarea\n  attributes:\n    label: propcache Version\n    description: Attach your version of propcache.\n    render: console\n    value: |\n      $ python -m pip show propcache\n  validations:\n    required: true\n- type: textarea\n  attributes:\n    label: yarl Version\n    description: Attach your version of yarl.\n    render: console\n    value: |\n      $ python -m pip show yarl\n  validations:\n    required: true\n\n- type: textarea\n  attributes:\n    label: OS\n    placeholder: >-\n      For example, Arch Linux, Windows, macOS, etc.\n  validations:\n    required: true\n\n- type: dropdown\n  attributes:\n    label: Related component\n    description: >-\n      aiohttp is both server framework and client library.\n      For getting rid of confusing make sure to select\n      'server', 'client' or both.\n    multiple: true\n    options:\n    - Server\n    - Client\n  validations:\n    required: true\n\n- type: textarea\n  attributes:\n    label: Additional context\n    description: |\n      Add any other context about the problem here.\n\n      Describe the environment you have that lead to your issue.\n      This includes proxy server and other bits that are related to your case.\n\n- type: checkboxes\n  attributes:\n    label: Code of Conduct\n    description: |\n      Read the [aio-libs Code of Conduct][CoC] first.\n\n      [CoC]: https://github.com/aio-libs/.github/blob/master/CODE_OF_CONDUCT.md\n    options:\n    - label: I agree to follow the aio-libs Code of Conduct\n      required: true\n...\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  # default: true\ncontact_links:\n- name: 🤷💻🤦 StackOverflow\n  url: https://stackoverflow.com/questions/tagged/aiohttp\n  about: Please ask typical Q&A here\n- name: 💬 Github Discussions\n  url: https://github.com/aio-libs/aiohttp/discussions\n  about: Please start usage discussions here\n- name: 💬 Gitter Chat\n  url: https://gitter.im/aio-libs/Lobby\n  about: Chat with devs and community\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "---\nname: 🚀 Feature request\ndescription: Suggest an idea for this project.\nlabels: enhancement\nbody:\n- type: markdown\n  attributes:\n    value: |\n      **Thanks for taking a minute to file a feature for aiohttp!**\n\n      ⚠\n      Verify first that your feature request is not [already reported on\n      GitHub][issue search].\n\n      _Please fill out the form below with as many precise\n      details as possible._\n\n      [issue search]: ../search?q=is%3Aissue&type=issues\n\n- type: textarea\n  attributes:\n    label: Is your feature request related to a problem?\n    description: >-\n       Please add a clear and concise description of what\n       the problem is. _Ex. I'm always frustrated when [...]_\n\n- type: textarea\n  attributes:\n    label: Describe the solution you'd like\n    description: >-\n      A clear and concise description of what you want to happen.\n  validations:\n    required: true\n\n- type: textarea\n  attributes:\n    label: Describe alternatives you've considered\n    description: >-\n      A clear and concise description of any alternative solutions\n      or features you've considered.\n  validations:\n    required: true\n\n- type: dropdown\n  attributes:\n    label: Related component\n    description: >-\n      aiohttp is both server framework and client library.\n      For getting rid of confusing make sure to select\n      'server', 'client' or both.\n    multiple: true\n    options:\n    - Server\n    - Client\n  validations:\n    required: true\n\n- type: textarea\n  attributes:\n    label: Additional context\n    description: >-\n      Add any other context or screenshots about\n      the feature request here.\n\n- type: checkboxes\n  attributes:\n    label: Code of Conduct\n    description: |\n      Read the [aio-libs Code of Conduct][CoC] first.\n\n      [CoC]: https://github.com/aio-libs/.github/blob/master/CODE_OF_CONDUCT.md\n    options:\n    - label: I agree to follow the aio-libs Code of Conduct\n      required: true\n...\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "## Long story short\n\n<!-- Please describe your problem and why the fix is important. -->\n\n## Expected behaviour\n\n<!-- What is the behaviour you expect? -->\n\n## Actual behaviour\n\n<!-- What's actually happening? -->\n\n## Steps to reproduce\n\n<!-- Please describe steps to reproduce the issue.\n     If you have a script that does that please include it here within\n     markdown code markup. The chances of someone looking at your issue\n     are *vastly* improved if you provide complete code that can be\n     copy/pasted and executed directly in Python. -->\n\n## Your environment\n\n<!-- Describe the environment you have that lead to your issue.\n     This includes aiohttp version, OS, proxy server and other bits that\n     are related to your case.\n\n     IMPORTANT: aiohttp is both server framework and client library.\n     For getting rid of confusing please put 'server', 'client' or 'both'\n     word here.\n     -->\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!-- Thank you for your contribution! -->\n\n## What do these changes do?\n\n<!-- Please give a short brief about these changes. -->\n\n## Are there changes in behavior for the user?\n\n<!-- Outline any notable behaviour for the end users. -->\n\n## Is it a substantial burden for the maintainers to support this?\n\n<!--\nStop right there! Pause. Just for a minute... Can you think of anything\nobvious that would complicate the ongoing development of this project?\n\nTry to consider if you'd be able to maintain it throughout the next\n5 years. Does it seem viable? Tell us your thoughts! We'd very much\nlove to hear what the consequences of merging this patch might be...\n\nThis will help us assess if your change is something we'd want to\nentertain early in the review process. Thank you in advance!\n-->\n\n## Related issue number\n\n<!-- Will this resolve any open issues? -->\n<!-- Remember to prefix with 'Fixes' if it closes an issue (e.g. 'Fixes #123'). -->\n\n## Checklist\n\n- [ ] I think the code is well written\n- [ ] Unit tests for the changes exist\n- [ ] Documentation reflects the changes\n- [ ] If you provide code modification, please add yourself to `CONTRIBUTORS.txt`\n  * The format is &lt;Name&gt; &lt;Surname&gt;.\n  * Please keep alphabetical order, the file is sorted by names.\n- [ ] Add a new news fragment into the `CHANGES/` folder\n  * name it `<issue_or_pr_num>.<type>.rst` (e.g. `588.bugfix.rst`)\n  * if you don't have an issue number, change it to the pull request\n    number after creating the PR\n    * `.bugfix`: A bug fix for something the maintainers deemed an\n      improper undesired behavior that got corrected to match\n      pre-agreed expectations.\n    * `.feature`: A new behavior, public APIs. That sort of stuff.\n    * `.deprecation`: A declaration of future API removals and breaking\n      changes in behavior.\n    * `.breaking`: When something public is removed in a breaking way.\n      Could be deprecated in an earlier release.\n    * `.doc`: Notable updates to the documentation structure or build\n      process.\n    * `.packaging`: Notes for downstreams about unobvious side effects\n      and tooling. Changes in the test invocation considerations and\n      runtime assumptions.\n    * `.contrib`: Stuff that affects the contributor experience. e.g.\n      Running tests, building the docs, setting up the development\n      environment.\n    * `.misc`: Changes that are hard to assign to any of the above\n      categories.\n  * Make sure to use full sentences with correct case and punctuation,\n    for example:\n    ```rst\n    Fixed issue with non-ascii contents in doctest text files\n    -- by :user:`contributor-gh-handle`.\n    ```\n\n    Use the past tense or the present tense a non-imperative mood,\n    referring to what's changed compared to the last released version\n    of this project.\n"
  },
  {
    "path": ".github/codeql.yml",
    "content": "query-filters:\n  - exclude:\n      id:\n        - py/ineffectual-statement\n        - py/unsafe-cyclic-import\n"
  },
  {
    "path": ".github/config.yml",
    "content": "chronographer:\n  exclude:\n    bots:\n    - dependabot-preview\n    - dependabot\n    - patchback\n    humans:\n    - pyup-bot\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n\n  # Maintain dependencies for GitHub Actions\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    labels:\n      - dependencies\n    schedule:\n      interval: \"daily\"\n\n  # Maintain dependencies for Python\n  - package-ecosystem: \"pip\"\n    directory: \"/\"\n    allow:\n      - dependency-type: \"all\"\n    labels:\n      - dependencies\n    schedule:\n      interval: \"daily\"\n    open-pull-requests-limit: 10\n\n  # Maintain dependencies for GitHub Actions aiohttp backport\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    labels:\n      - dependencies\n    target-branch: \"3.14\"\n    schedule:\n      interval: \"daily\"\n    open-pull-requests-limit: 10\n\n  # Maintain dependencies for Python aiohttp backport\n  - package-ecosystem: \"pip\"\n    directory: \"/\"\n    allow:\n      - dependency-type: \"all\"\n    labels:\n      - dependencies\n    target-branch: \"3.14\"\n    schedule:\n      interval: \"daily\"\n    open-pull-requests-limit: 10\n\n  - package-ecosystem: \"docker\"\n    directory: \"/tests/autobahn/\"\n    labels:\n      - dependencies\n    schedule:\n      interval: \"monthly\"\n\n  - package-ecosystem: \"docker\"\n    directory: \"/tests/autobahn/\"\n    labels:\n      - dependencies\n    target-branch: \"3.14\"\n    schedule:\n      interval: \"monthly\"\n"
  },
  {
    "path": ".github/lock.yml",
    "content": "# Configuration for Lock Threads - https://github.com/dessant/lock-threads\n# GitHub App - https://github.com/apps/lock\n\n---\n# Number of days of inactivity before a closed issue or pull request is locked\ndaysUntilLock: 365\n\n# Skip issues and pull requests created before a given timestamp. Timestamp must\n# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable\nskipCreatedBefore: false\n\n# Issues and pull requests with these labels will be ignored.\n# Set to `[]` to disable\nexemptLabels: []\n\n# Label to add before locking, such as `outdated`.\n# Set to `false` to disable\nlockLabel: outdated\n\n# Comment to post before locking. Set to `false` to disable\nlockComment: false\n\n# Assign `resolved` as the reason for locking. Set to `false` to disable\nsetLockReason: true\n\n# Limit to only `issues` or `pulls`\n# only: issues\n\n# Optionally, specify configuration settings just for `issues` or `pulls`\n# issues:\n#   exemptLabels:\n#     - help-wanted\n#   lockLabel: outdated\n\n# pulls:\n#   daysUntilLock: 30\n\n# Repository to extend settings from\n# _extends: repo\n"
  },
  {
    "path": ".github/workflows/auto-merge.yml",
    "content": "name: Dependabot auto-merge\non: pull_request_target\n\npermissions:\n  pull-requests: write\n  contents: write\n\njobs:\n  dependabot:\n    runs-on: ubuntu-latest\n    if: ${{ github.actor == 'dependabot[bot]' }}\n    steps:\n      - name: Dependabot metadata\n        id: metadata\n        uses: dependabot/fetch-metadata@v2\n        with:\n          github-token: \"${{ secrets.GITHUB_TOKEN }}\"\n      - name: Enable auto-merge for Dependabot PRs\n        run: gh pr merge --auto --squash \"$PR_URL\"\n        env:\n          PR_URL: ${{github.event.pull_request.html_url}}\n          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}\n"
  },
  {
    "path": ".github/workflows/ci-cd.yml",
    "content": "name: CI\n\non:\n  merge_group:\n  push:\n    branches:\n      - 'master'\n      - '[0-9].[0-9]+'  # matches to backport branches, e.g. 3.6\n    tags: [ 'v*' ]\n  pull_request:\n    branches:\n      - 'master'\n      - '[0-9].[0-9]+'\n  schedule:\n    - cron:  '0 6 * * *'  # Daily 6AM UTC build\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}\n  cancel-in-progress: true\n\nenv:\n  COLOR: yes\n  FORCE_COLOR: 1  # Request colored output from CLI tools supporting it\n  MYPY_FORCE_COLOR: 1\n  PY_COLORS: 1\n  UPSTREAM_REPOSITORY_ID: >-\n    13258039\n\npermissions: {}\n\njobs:\n\n  pre-setup:\n    name: Pre-Setup global build settings\n    runs-on: ubuntu-latest\n    outputs:\n      upstream-repository-id: ${{ env.UPSTREAM_REPOSITORY_ID }}\n      release-requested: >-\n        ${{\n          (\n            github.event_name == 'push'\n            && github.ref_type == 'tag'\n          )\n          && true\n          || false\n        }}\n    steps:\n      - name: Dummy\n        if: false\n        run: |\n          echo \"Pre-setup step\"\n\n  lint:\n    permissions:\n      contents: read # to fetch code (actions/checkout)\n\n    name: Linter\n    runs-on: ubuntu-latest\n    timeout-minutes: 5\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v6\n      with:\n        submodules: true\n    - name: >-\n        Verify that `requirements/runtime-deps.in`\n        is in sync with `pyproject.toml`\n      run: |\n        set -eEuo pipefail\n        make sync-direct-runtime-deps\n        git diff --exit-code -- requirements/runtime-deps.in\n    - name: Setup Python\n      uses: actions/setup-python@v6\n      with:\n        python-version: 3.11\n    - name: Cache PyPI\n      uses: actions/cache@v5.0.4\n      with:\n        key: pip-lint-${{ hashFiles('requirements/*.txt') }}\n        path: ~/.cache/pip\n        restore-keys: |\n            pip-lint-\n    - name: Update pip, wheel, setuptools, build, twine\n      run: |\n        python -m pip install -U pip wheel setuptools build twine\n    - name: Install dependencies\n      run: |\n        python -m pip install -r requirements/lint.in -c requirements/lint.txt\n    - name: Install self\n      run: |\n        python -m pip install . -c requirements/runtime-deps.txt\n      env:\n        AIOHTTP_NO_EXTENSIONS: 1\n    - name: Run mypy\n      run: |\n        make mypy\n    - name: Run slotscheck\n      run: |\n        # Some extra requirements are needed to ensure all modules\n        # can be scanned by slotscheck.\n        pip install -r requirements/base.in -c requirements/base.txt\n        slotscheck -v -m aiohttp\n    - name: Install spell checker\n      run: |\n        pip install -r requirements/doc-spelling.in -c requirements/doc-spelling.txt\n    - name: Run docs spelling\n      run: |\n        # towncrier --yes  # uncomment me after publishing a release\n        make doc-spelling\n    - name: Build package\n      run: |\n        python -m build\n      env:\n        AIOHTTP_NO_EXTENSIONS: 1\n    - name: Run twine checker\n      run: |\n        twine check --strict dist/*\n    - name: Making sure that CONTRIBUTORS.txt remains sorted\n      run: |\n        LC_ALL=C sort --check --ignore-case CONTRIBUTORS.txt\n\n  gen_llhttp:\n    permissions:\n      contents: read # to fetch code (actions/checkout)\n\n    name: Generate llhttp sources\n    runs-on: ubuntu-latest\n    timeout-minutes: 5\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v6\n      with:\n        submodules: true\n    - name: Cache llhttp generated files\n      uses: actions/cache@v5.0.4\n      id: cache\n      with:\n        key: llhttp-${{ hashFiles('vendor/llhttp/package*.json', 'vendor/llhttp/src/**/*') }}\n        path:  vendor/llhttp/build\n    - name: Setup NodeJS\n      if: steps.cache.outputs.cache-hit != 'true'\n      uses: actions/setup-node@v6\n      with:\n        node-version: 18\n    - name: Generate llhttp sources\n      if: steps.cache.outputs.cache-hit != 'true'\n      run: |\n        make generate-llhttp\n    - name: Upload llhttp generated files\n      uses: actions/upload-artifact@v6\n      with:\n        name: llhttp\n        path: vendor/llhttp/build\n        if-no-files-found: error\n\n  test:\n    permissions:\n      contents: read # to fetch code (actions/checkout)\n\n    name: Test\n    needs: gen_llhttp\n    strategy:\n      matrix:\n        pyver: ['3.10', '3.11', '3.12', '3.13', '3.14']\n        no-extensions: ['', 'Y']\n        os: [ubuntu, macos, windows]\n        experimental: [false]\n        exclude:\n          - os: macos\n            no-extensions: 'Y'\n          - os: windows\n            no-extensions: 'Y'\n        include:\n          - pyver: pypy-3.11\n            no-extensions: 'Y'\n            os: ubuntu\n            experimental: false\n          - os: ubuntu\n            pyver: \"3.14t\"\n            no-extensions: ''\n            experimental: false\n      fail-fast: true\n    runs-on: ${{ matrix.os }}-latest\n    continue-on-error: ${{ matrix.experimental }}\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v6\n      with:\n        submodules: true\n    - name: Setup Python ${{ matrix.pyver }}\n      id: python-install\n      uses: actions/setup-python@v6\n      with:\n        allow-prereleases: true\n        python-version: ${{ matrix.pyver }}\n    - name: Get pip cache dir\n      id: pip-cache\n      run: |\n        echo \"dir=$(pip cache dir)\" >> \"${GITHUB_OUTPUT}\"\n      shell: bash\n    - name: Cache PyPI\n      uses: actions/cache@v5.0.4\n      with:\n        key: pip-ci-${{ runner.os }}-${{ matrix.pyver }}-${{ matrix.no-extensions }}-${{ hashFiles('requirements/*.txt') }}\n        path: ${{ steps.pip-cache.outputs.dir }}\n        restore-keys: |\n            pip-ci-${{ runner.os }}-${{ matrix.pyver }}-${{ matrix.no-extensions }}-\n    - name: Update pip, wheel, setuptools, build, twine\n      run: |\n        python -m pip install -U pip wheel setuptools build twine\n    - name: Install dependencies\n      env:\n        DEPENDENCY_GROUP: test${{ endsWith(matrix.pyver, 't') && '-ft' || '' }}\n      run: |\n        python -Im pip install -r requirements/${{ env.DEPENDENCY_GROUP }}.in -c requirements/${{ env.DEPENDENCY_GROUP }}.txt\n    - name: Set PYTHON_GIL=0 for free-threading builds\n      if: ${{ endsWith(matrix.pyver, 't') }}\n      run: echo \"PYTHON_GIL=0\" >> $GITHUB_ENV\n    - name: Restore llhttp generated files\n      if: ${{ matrix.no-extensions == '' }}\n      uses: actions/download-artifact@v8\n      with:\n        name: llhttp\n        path: vendor/llhttp/build/\n    - name: Cythonize\n      if: ${{ matrix.no-extensions == '' }}\n      run: |\n        make cythonize\n    - name: Install self\n      env:\n        AIOHTTP_NO_EXTENSIONS: ${{ matrix.no-extensions }}\n      run: python -m pip install -e .\n    - name: Run unittests\n      env:\n        COLOR: yes\n        AIOHTTP_NO_EXTENSIONS: ${{ matrix.no-extensions }}\n        PIP_USER: 1\n      run: >-\n        PATH=\"${HOME}/Library/Python/3.11/bin:${HOME}/.local/bin:${PATH}\"\n        pytest --junitxml=junit.xml -m 'not dev_mode and not autobahn'\n      shell: bash\n    - name: Re-run the failing tests with maximum verbosity\n      if: failure()\n      env:\n        COLOR: yes\n        AIOHTTP_NO_EXTENSIONS: ${{ matrix.no-extensions }}\n      run: >-  # `exit 1` makes sure that the job remains red with flaky runs\n        pytest --no-cov --numprocesses=0 -vvvvv --lf && exit 1\n      shell: bash\n    - name: Run dev_mode tests\n      env:\n        COLOR: yes\n        AIOHTTP_NO_EXTENSIONS: ${{ matrix.no-extensions }}\n        PIP_USER: 1\n        PYTHONDEVMODE: 1\n      run: pytest -m dev_mode --cov-append --numprocesses=0\n      shell: bash\n    - name: Turn coverage into xml\n      env:\n        COLOR: 'yes'\n        PIP_USER: 1\n      run: |\n        python -m coverage xml\n    - name: Upload coverage\n      uses: codecov/codecov-action@v5\n      with:\n        files: ./coverage.xml\n        flags: >-\n          CI-GHA,OS-${{\n            runner.os\n          }},VM-${{\n            matrix.os\n          }},Py-${{\n            steps.python-install.outputs.python-version\n          }}\n        token: ${{ secrets.CODECOV_TOKEN }}\n    - name: Upload test results to Codecov\n      if: ${{ !cancelled() }}\n      uses: codecov/test-results-action@v1\n      with:\n        token: ${{ secrets.CODECOV_TOKEN }}\n\n  autobahn:\n    permissions:\n      contents: read # to fetch code (actions/checkout)\n\n    name: Autobahn testsuite\n    needs: gen_llhttp\n    strategy:\n      matrix:\n        pyver: ['3.14']\n        no-extensions: ['']\n        os: [ubuntu]\n      fail-fast: true\n    runs-on: ${{ matrix.os }}-latest\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v6\n      with:\n        submodules: true\n    - name: Setup Python ${{ matrix.pyver }}\n      id: python-install\n      uses: actions/setup-python@v6\n      with:\n        allow-prereleases: true\n        python-version: ${{ matrix.pyver }}\n    - name: Get pip cache dir\n      id: pip-cache\n      run: |\n        echo \"dir=$(pip cache dir)\" >> \"${GITHUB_OUTPUT}\"\n      shell: bash\n    - name: Cache PyPI\n      uses: actions/cache@v5.0.4\n      with:\n        key: pip-ci-${{ runner.os }}-${{ matrix.pyver }}-${{ matrix.no-extensions }}-${{ hashFiles('requirements/*.txt') }}\n        path: ${{ steps.pip-cache.outputs.dir }}\n        restore-keys: |\n            pip-ci-${{ runner.os }}-${{ matrix.pyver }}-${{ matrix.no-extensions }}-\n    - name: Update pip, wheel, setuptools, build, twine\n      run: |\n        python -m pip install -U pip wheel setuptools build twine\n    - name: Install dependencies\n      env:\n        DEPENDENCY_GROUP: test${{ endsWith(matrix.pyver, 't') && '-ft' || '' }}\n      run: |\n        python -Im pip install -r requirements/${{ env.DEPENDENCY_GROUP }}.in -c requirements/${{ env.DEPENDENCY_GROUP }}.txt\n    - name: Restore llhttp generated files\n      if: ${{ matrix.no-extensions == '' }}\n      uses: actions/download-artifact@v8\n      with:\n        name: llhttp\n        path: vendor/llhttp/build/\n    - name: Cythonize\n      if: ${{ matrix.no-extensions == '' }}\n      run: |\n        make cythonize\n    - name: Install self\n      env:\n        AIOHTTP_NO_EXTENSIONS: ${{ matrix.no-extensions }}\n      run: python -m pip install -e .\n    - name: Run unittests\n      env:\n        COLOR: yes\n        AIOHTTP_NO_EXTENSIONS: ${{ matrix.no-extensions }}\n        PIP_USER: 1\n      run: >-\n        PATH=\"${HOME}/Library/Python/3.11/bin:${HOME}/.local/bin:${PATH}\"\n        pytest --junitxml=junit.xml --numprocesses=0 -m autobahn\n      shell: bash\n    - name: Turn coverage into xml\n      env:\n        COLOR: 'yes'\n        PIP_USER: 1\n      run: |\n        python -m coverage xml\n    - name: Upload coverage\n      uses: codecov/codecov-action@v5\n      with:\n        files: ./coverage.xml\n        flags: >-\n          CI-GHA,OS-${{\n            runner.os\n          }},VM-${{\n            matrix.os\n          }},Py-${{\n            steps.python-install.outputs.python-version\n          }}\n        token: ${{ secrets.CODECOV_TOKEN }}\n    - name: Upload test results to Codecov\n      if: ${{ !cancelled() }}\n      uses: codecov/test-results-action@v1\n      with:\n        token: ${{ secrets.CODECOV_TOKEN }}\n\n  benchmark:\n    name: Benchmark\n    needs:\n    - gen_llhttp\n    - pre-setup  # transitive, for accessing settings\n    if: >-\n      needs.pre-setup.outputs.upstream-repository-id == github.repository_id\n    runs-on: ubuntu-latest\n    timeout-minutes: 12\n    steps:\n    - name: Checkout project\n      uses: actions/checkout@v6\n      with:\n        submodules: true\n    - name: Setup Python 3.13.2\n      id: python-install\n      uses: actions/setup-python@v6\n      with:\n        python-version: 3.13.2\n        cache: pip\n        cache-dependency-path: requirements/*.txt\n    - name: Update pip, wheel, setuptools, build, twine\n      run: |\n        python -m pip install -U pip wheel setuptools build twine\n    - name: Install dependencies\n      run: |\n        python -m pip install -r requirements/test.in -c requirements/test.txt\n    - name: Restore llhttp generated files\n      uses: actions/download-artifact@v8\n      with:\n        name: llhttp\n        path: vendor/llhttp/build/\n    - name: Cythonize\n      run: |\n        make cythonize\n    - name: Install self\n      run: python -m pip install -e .\n    - name: Run benchmarks\n      uses: CodSpeedHQ/action@v4\n      with:\n        mode: instrumentation\n        run: python -Im pytest --no-cov --numprocesses=0 -vvvvv --codspeed\n\n\n  check:  # This job does nothing and is only used for the branch protection\n    if: always()\n\n    needs:\n    - lint\n    - test\n    - autobahn\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Decide whether the needed jobs succeeded or failed\n      uses: re-actors/alls-green@release/v1\n      with:\n        jobs: ${{ toJSON(needs) }}\n    - name: Trigger codecov notification\n      uses: codecov/codecov-action@v5\n      with:\n        token: ${{ secrets.CODECOV_TOKEN }}\n        fail_ci_if_error: true\n        run_command: send-notifications\n\n  pre-deploy:\n    name: Pre-Deploy\n    runs-on: ubuntu-latest\n    needs:\n    - check\n    - pre-setup  # transitive, for accessing settings\n    if: fromJSON(needs.pre-setup.outputs.release-requested)\n    steps:\n      - name: Dummy\n        run: |\n            echo \"Predeploy step\"\n\n  build-tarball:\n    permissions:\n      contents: read # to fetch code (actions/checkout)\n\n    name: Tarball\n    runs-on: ubuntu-latest\n    needs: pre-deploy\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v6\n      with:\n        submodules: true\n    - name: Setup Python\n      uses: actions/setup-python@v6\n    - name: Update pip, wheel, setuptools, build, twine\n      run: |\n        python -m pip install -U pip wheel setuptools build twine\n    - name: Install cython\n      run: >-\n        python -m\n        pip install -r requirements/cython.in -c requirements/cython.txt\n    - name: Restore llhttp generated files\n      uses: actions/download-artifact@v8\n      with:\n        name: llhttp\n        path: vendor/llhttp/build/\n    - name: Cythonize\n      run: |\n        make cythonize\n    - name: Make sdist\n      run: |\n        python -m build --sdist\n    - name: Upload artifacts\n      uses: actions/upload-artifact@v6\n      with:\n        name: dist-sdist\n        path: dist\n\n  build-wheels:\n    permissions:\n      contents: read # to fetch code (actions/checkout)\n\n    name: Build wheels on ${{ matrix.os }} ${{ matrix.qemu }} ${{ matrix.musl }}\n    runs-on: ${{ matrix.os }}\n    needs: pre-deploy\n    strategy:\n      matrix:\n        os: [\"ubuntu-latest\", \"windows-latest\", \"windows-11-arm\", \"macos-latest\", \"ubuntu-24.04-arm\"]\n        qemu: ['']\n        musl: [\"\"]\n        include:\n          # Split ubuntu/musl jobs for the sake of speed-up\n        - os: ubuntu-latest\n          qemu: ppc64le\n          musl: \"\"\n        - os: ubuntu-latest\n          qemu: ppc64le\n          musl: musllinux\n        - os: ubuntu-latest\n          qemu: riscv64\n          musl: \"\"\n        - os: ubuntu-latest\n          qemu: riscv64\n          musl: musllinux\n        - os: ubuntu-latest\n          qemu: s390x\n          musl: \"\"\n        - os: ubuntu-latest\n          qemu: s390x\n          musl: musllinux\n        - os: ubuntu-latest\n          qemu: armv7l\n          musl: \"\"\n        - os: ubuntu-latest\n          qemu: armv7l\n          musl: musllinux\n        - os: ubuntu-latest\n          musl: musllinux\n        - os: ubuntu-24.04-arm\n          musl: musllinux\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v6\n      with:\n        submodules: true\n    - name: Set up QEMU\n      if: ${{ matrix.qemu }}\n      uses: docker/setup-qemu-action@v4\n      with:\n        platforms: all\n        # This should be temporary\n        # xref https://github.com/docker/setup-qemu-action/issues/188\n        # xref https://github.com/tonistiigi/binfmt/issues/215\n        image: tonistiigi/binfmt:qemu-v8.1.5\n      id: qemu\n    - name: Prepare emulation\n      run: |\n        if [[ -n \"${{ matrix.qemu }}\" ]]; then\n          # Build emulated architectures only if QEMU is set,\n          # use default \"auto\" otherwise\n          echo \"CIBW_ARCHS_LINUX=${{ matrix.qemu }}\" >> $GITHUB_ENV\n        fi\n      shell: bash\n    - name: Setup Python\n      uses: actions/setup-python@v6\n      with:\n        python-version: 3.x\n    - name: Update pip, wheel, setuptools, build, twine\n      run: |\n        python -m pip install -U pip wheel setuptools build twine\n    - name: Install cython\n      run: >-\n        python -m\n        pip install -r requirements/cython.in -c requirements/cython.txt\n    - name: Restore llhttp generated files\n      uses: actions/download-artifact@v8\n      with:\n        name: llhttp\n        path: vendor/llhttp/build/\n    - name: Cythonize\n      run: |\n        make cythonize\n    - name: Build wheels\n      uses: pypa/cibuildwheel@v3.4.0\n      env:\n        CIBW_SKIP: pp* ${{ matrix.musl == 'musllinux' && '*manylinux*' || '*musllinux*' }}\n        CIBW_ARCHS_MACOS: x86_64 arm64 universal2\n    - name: Upload wheels\n      uses: actions/upload-artifact@v6\n      with:\n        name: >-\n          dist-${{ matrix.os }}-${{ matrix.musl }}-${{\n            matrix.qemu\n            && matrix.qemu\n            || 'native'\n          }}\n        path: ./wheelhouse/*.whl\n\n  deploy:\n    name: Deploy\n    needs:\n    - build-tarball\n    - build-wheels\n    - pre-setup  # transitive, for accessing settings\n    runs-on: ubuntu-latest\n    if: >-\n      needs.pre-setup.outputs.upstream-repository-id == github.repository_id\n\n    permissions:\n      contents: write  # IMPORTANT: mandatory for making GitHub Releases\n      id-token: write  # IMPORTANT: mandatory for trusted publishing & sigstore\n\n    environment:\n      name: pypi\n      url: https://pypi.org/p/aiohttp\n\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v6\n      with:\n        submodules: true\n    - name: Login\n      run: |\n        echo \"${{ secrets.GITHUB_TOKEN }}\" | gh auth login --with-token\n    - name: Download distributions\n      uses: actions/download-artifact@v8\n      with:\n        path: dist\n        pattern: dist-*\n        merge-multiple: true\n    - name: Collected dists\n      run: |\n        tree dist\n    - name: Make Release\n      uses: aio-libs/create-release@v1.6.6\n      with:\n        changes_file: CHANGES.rst\n        name: aiohttp\n        version_file: aiohttp/__init__.py\n        github_token: ${{ secrets.GITHUB_TOKEN }}\n        dist_dir: dist\n        fix_issue_regex: >-\n          :issue:`(\\d+)`\n        fix_issue_repl: >-\n          #\\1\n\n    - name: >-\n        Publish 🐍📦 to PyPI\n      uses: pypa/gh-action-pypi-publish@release/v1\n\n    - name: Sign the dists with Sigstore\n      uses: sigstore/gh-action-sigstore-python@v3.2.0\n      with:\n        inputs: >-\n          ./dist/*.tar.gz\n          ./dist/*.whl\n\n    - name: Upload artifact signatures to GitHub Release\n      # Confusingly, this action also supports updating releases, not\n      # just creating them. This is what we want here, since we've manually\n      # created the release above.\n      uses: softprops/action-gh-release@v2\n      with:\n        # dist/ contains the built packages, which smoketest-artifacts/\n        # contains the signatures and certificates.\n        files: dist/**\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "name: \"CodeQL\"\n\non:\n  push:\n    branches:\n      - 'master'\n      - '[0-9].[0-9]+'  # matches to backport branches, e.g. 3.6\n  pull_request:\n    branches: [ \"master\" ]\n  schedule:\n    - cron: \"9 1 * * 4\"\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ python, javascript ]\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v4\n        with:\n          languages: ${{ matrix.language }}\n          config-file: ./.github/codeql.yml\n          queries: +security-and-quality\n\n      - name: Autobuild\n        uses: github/codeql-action/autobuild@v4\n        if: ${{ matrix.language == 'python' || matrix.language == 'javascript' }}\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v4\n        with:\n          category: \"/language:${{ matrix.language }}\"\n"
  },
  {
    "path": ".github/workflows/label-remove.yml",
    "content": "name: Clear needs-info/pr-unfinished on activity\non:\n  pull_request:\n    types: [synchronize, review_requested]\n  issue_comment:\n    types: [created]\n  pull_request_review_comment:\n    types: [created]\n\njobs:\n  clear-pr-unfinished:\n    runs-on: ubuntu-latest\n    permissions:\n      pull-requests: write\n\n    steps:\n      - name: Remove label\n        uses: actions-ecosystem/action-remove-labels@v1\n        with:\n          labels: |\n            needs-info\n            pr-unfinished\n"
  },
  {
    "path": ".github/workflows/labels.yml",
    "content": "name: Labels\non:\n  pull_request:\n    branches:\n      - 'master'\n    types: [labeled, opened, synchronize, reopened, unlabeled]\n\njobs:\n  backport:\n    runs-on: ubuntu-latest\n    name: Backport label added\n    if: ${{ github.event.pull_request.user.type != 'Bot' }}\n    steps:\n      - uses: actions/github-script@v8\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          script: |\n            const pr = await github.rest.pulls.get({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              pull_number: context.payload.pull_request.number\n            });\n            if (!pr.data.labels.find(l => l.name.startsWith(\"backport\")))\n              process.exit(1);\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: 'Close stale issues'\non:\n  schedule:\n    - cron: '50 5 * * *'\n\npermissions:\n  issues: write\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/stale@v10\n        with:\n          days-before-stale: 30\n          any-of-labels: needs-info\n"
  },
  {
    "path": ".gitignore",
    "content": "*.bak\n*.egg\n*.egg-info\n*.eggs\n*.md5\n*.pyc\n*.pyd\n*.pyo\n*.so\n*.swp\n*.tar.gz\n*~\n.DS_Store\n.Python\n.cache\n.codspeed\n.coverage\n.coverage.*\n.develop\n.direnv\n.envrc\n.flake\n.gitconfig\n.hash\n.idea\n.install-cython\n.install-deps\n.llhttp-gen\n.installed.cfg\n.mypy_cache\n.noseids\n.pytest_cache\n.python-version\n.test-results\n.tox\n.vimrc\n.vscode\naiohttp/_find_header.c\naiohttp/_headers.html\naiohttp/_headers.pxi\naiohttp/_http_parser.c\naiohttp/_http_parser.html\naiohttp/_http_writer.c\naiohttp/_http_writer.html\naiohttp/_websocket.c\naiohttp/_websocket.html\naiohttp/_websocket/mask.c\naiohttp/_websocket/reader_c.c\nbin\nbuild\ncoverage.xml\ndevelop-eggs\ndist\ndocs/_build/\neggs\nhtmlcov\ninclude/\nlib/\nman/\nnosetests.xml\nparts\npip-wheel-metadata\npyvenv\nsources\nvar/*\nvenv\nvirtualenv.py\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"vendor/llhttp\"]\n    path = vendor/llhttp\n    url = https://github.com/nodejs/llhttp.git\n    branch = main\n"
  },
  {
    "path": ".lgtm.yml",
    "content": "queries:\n- exclude: py/unsafe-cyclic-import\n"
  },
  {
    "path": ".mypy.ini",
    "content": "[mypy]\nfiles = aiohttp, docs/code, examples, tests\ncheck_untyped_defs = True\nfollow_imports_for_stubs = True\ndisallow_any_decorated = True\ndisallow_any_generics = True\ndisallow_any_unimported = True\ndisallow_incomplete_defs = True\ndisallow_subclassing_any = True\ndisallow_untyped_calls = True\ndisallow_untyped_decorators = True\ndisallow_untyped_defs = True\n# TODO(PY312): explicit-override\nenable_error_code = deprecated, exhaustive-match, ignore-without-code, possibly-undefined, redundant-expr, redundant-self, truthy-bool, truthy-iterable, unused-awaitable\nextra_checks = True\nfollow_untyped_imports = True\nimplicit_reexport = False\nno_implicit_optional = True\npretty = True\nshow_column_numbers = True\nshow_error_codes = True\nshow_error_code_links = True\nstrict_bytes = True\nstrict_equality = True\nwarn_incomplete_stub = True\nwarn_redundant_casts = True\nwarn_return_any = True\nwarn_unreachable = True\nwarn_unused_ignores = True\n\n[mypy-brotli]\nignore_missing_imports = True\n\n[mypy-brotlicffi]\nignore_missing_imports = True\n\n[mypy-gunicorn.*]\nignore_missing_imports = True\n"
  },
  {
    "path": ".pip-tools.toml",
    "content": "[pip-tools]\nallow-unsafe = true\nresolver = \"backtracking\"\nstrip-extras = true\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n- repo: local\n  hooks:\n  - id: changelogs-rst\n    name: changelog filenames\n    language: fail\n    entry: >-\n      Changelog files must be named\n      ####.(\n      bugfix\n      | feature\n      | deprecation\n      | breaking\n      | doc\n      | packaging\n      | contrib\n      | misc\n      )(.#)?(.rst)?\n    exclude: >-\n      (?x)\n      ^\n        CHANGES/(\n          \\.gitignore\n          |(\\d+|[0-9a-f]{8}|[0-9a-f]{7}|[0-9a-f]{40})\\.(\n            bugfix\n            |feature\n            |deprecation\n            |breaking\n            |doc\n            |packaging\n            |contrib\n            |misc\n          )(\\.\\d+)?(\\.rst)?\n          |README\\.rst\n          |\\.TEMPLATE\\.rst\n        )\n      $\n    files: ^CHANGES/\n  - id: changelogs-user-role\n    name: Changelog files should use a non-broken :user:`name` role\n    language: pygrep\n    entry: :user:([^`]+`?|`[^`]+[\\s,])\n    pass_filenames: true\n    types: [file, rst]\n  - id: check-changes\n    name: Check CHANGES\n    language: system\n    entry: ./tools/check_changes.py\n    pass_filenames: false\n- repo: https://github.com/pre-commit/pre-commit-hooks\n  rev: 'v6.0.0'\n  hooks:\n  - id: check-merge-conflict\n- repo: https://github.com/asottile/yesqa\n  rev: v1.5.0\n  hooks:\n  - id: yesqa\n    additional_dependencies:\n      - flake8-docstrings==1.6.0\n      - flake8-no-implicit-concat==0.3.4\n      - flake8-requirements==1.7.8\n- repo: https://github.com/PyCQA/isort\n  rev: '8.0.1'\n  hooks:\n  - id: isort\n- repo: https://github.com/psf/black-pre-commit-mirror\n  rev: '26.3.1'\n  hooks:\n    - id: black\n      language_version: python3 # Should be a command that runs python\n- repo: https://github.com/pre-commit/pre-commit-hooks\n  rev: 'v6.0.0'\n  hooks:\n  - id: end-of-file-fixer\n    exclude: >-\n      ^docs/[^/]*\\.svg$\n  - id: requirements-txt-fixer\n    files: requirements/.*\\.in$\n  - id: trailing-whitespace\n  - id: file-contents-sorter\n    args: ['--ignore-case']\n    files: |\n      CONTRIBUTORS.txt|\n      docs/spelling_wordlist.txt|\n      .gitignore|\n      .gitattributes\n  - id: check-case-conflict\n  - id: check-json\n  - id: check-xml\n  - id: check-executables-have-shebangs\n  - id: check-toml\n  - id: check-yaml\n  - id: debug-statements\n  - id: check-added-large-files\n  - id: check-symlinks\n  - id: fix-byte-order-marker\n  - id: detect-aws-credentials\n    args: ['--allow-missing-credentials']\n  - id: detect-private-key\n    exclude: ^examples/\n- repo: https://github.com/asottile/pyupgrade\n  rev: 'v3.21.2'\n  hooks:\n  - id: pyupgrade\n    args: ['--py37-plus']\n- repo: https://github.com/PyCQA/flake8\n  rev: '7.3.0'\n  hooks:\n  - id: flake8\n    additional_dependencies:\n      - flake8-docstrings==1.6.0\n      - flake8-no-implicit-concat==0.3.4\n      - flake8-requirements==1.7.8\n    exclude: \"^docs/\"\n- repo: https://github.com/Lucas-C/pre-commit-hooks-markup\n  rev: v1.0.1\n  hooks:\n  - id: rst-linter\n    files: >-\n      ^[^/]+[.]rst$\n    exclude: >-\n      ^CHANGES\\.rst$\n- repo: https://github.com/codespell-project/codespell\n  rev: v2.4.2\n  hooks:\n  - id: codespell\n    additional_dependencies:\n    - tomli\n"
  },
  {
    "path": ".readthedocs.yml",
    "content": "# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html\n# for details\n\n---\nversion: 2\n\nsphinx:\n  # Path to your Sphinx configuration file.\n  configuration: docs/conf.py\n\nsubmodules:\n  include: all\n  exclude: []\n  recursive: true\n\nbuild:\n  os: ubuntu-24.04\n  tools:\n    python: \"3.11\"\n  apt_packages:\n    - graphviz\n\n  jobs:\n    post_create_environment:\n    - >-\n      pip install\n      . -c requirements/runtime-deps.txt\n      -r requirements/doc.in -c requirements/doc.txt\n\n...\n"
  },
  {
    "path": "CHANGES/.TEMPLATE.rst",
    "content": "{# TOWNCRIER TEMPLATE #}\n{% for section, _ in sections.items() %}\n{% set underline = underlines[0] %}{% if section %}{{section}}\n{{ underline * section|length }}{% set underline = underlines[1] %}\n\n{% endif %}\n\n{% if sections[section] %}\n{% for category, val in definitions.items() if category in sections[section]%}\n{{ definitions[category]['name'] }}\n{{ underline * definitions[category]['name']|length }}\n\n{% if definitions[category]['showcontent'] %}\n{% for text, change_note_refs in sections[section][category].items() %}\n- {{ text + '\\n' }}\n\n  {#\n      NOTE: Replacing 'e' with 'f' is a hack that prevents Jinja's `int`\n      NOTE: filter internal implementation from treating the input as an\n      NOTE: infinite float when it looks like a scientific notation (with a\n      NOTE: single 'e' char in between digits), raising an `OverflowError`,\n      NOTE: subsequently. 'f' is still a hex letter so it won't affect the\n      NOTE: check for whether it's a (short or long) commit hash or not.\n      Ref: https://github.com/pallets/jinja/issues/1921\n  -#}\n  {%-\n    set pr_issue_numbers = change_note_refs\n    | map('lower')\n    | map('replace', 'e', 'f')\n    | map('int', default=None)\n    | select('integer')\n    | map('string')\n    | list\n  -%}\n  {%- set arbitrary_refs = [] -%}\n  {%- set commit_refs = [] -%}\n  {%- with -%}\n    {%- set commit_ref_candidates = change_note_refs | reject('in', pr_issue_numbers) -%}\n    {%- for cf in commit_ref_candidates -%}\n      {%- if cf | length in (7, 8, 40) and cf | int(default=None, base=16) is not none -%}\n        {%- set _ = commit_refs.append(cf) -%}\n      {%- else -%}\n        {%- set _ = arbitrary_refs.append(cf) -%}\n      {%- endif -%}\n    {%- endfor -%}\n  {%- endwith -%}\n\n  {% if pr_issue_numbers -%}\n  *Related issues and pull requests on GitHub:*\n  :issue:`{{ pr_issue_numbers | join('`, :issue:`') }}`.\n  {% endif %}\n\n  {% if commit_refs -%}\n  *Related commits on GitHub:*\n  :commit:`{{ commit_refs | join('`, :commit:`') }}`.\n  {% endif %}\n\n  {% if arbitrary_refs -%}\n  *Unlinked references:*\n  {{ arbitrary_refs | join(', ') }}`.\n  {% endif %}\n\n{% endfor %}\n{% else %}\n- {{ sections[section][category]['']|join(', ') }}\n\n{% endif %}\n{% if sections[section][category]|length == 0 %}\nNo significant changes.\n\n{% else %}\n{% endif %}\n\n{% endfor %}\n{% else %}\nNo significant changes.\n\n\n{% endif %}\n{% endfor %}\n----\n{{ '\\n' * 2 }}\n"
  },
  {
    "path": "CHANGES/.gitignore",
    "content": "*\n!.TEMPLATE.rst\n!.gitignore\n!README.rst\n!*.bugfix\n!*.bugfix.rst\n!*.bugfix.*.rst\n!*.breaking\n!*.breaking.rst\n!*.breaking.*.rst\n!*.contrib\n!*.contrib.rst\n!*.contrib.*.rst\n!*.deprecation\n!*.deprecation.rst\n!*.deprecation.*.rst\n!*.doc\n!*.doc.rst\n!*.doc.*.rst\n!*.feature\n!*.feature.rst\n!*.feature.*.rst\n!*.misc\n!*.misc.rst\n!*.misc.*.rst\n!*.packaging\n!*.packaging.rst\n!*.packaging.*.rst\n"
  },
  {
    "path": "CHANGES/10468.doc.rst",
    "content": "Added ``:canonical:`` directives to documentation reference pages, enabling\n``Intersphinx`` cross-referencing via fully-qualified module paths (e.g.\n``aiohttp.client.ClientSession``) -- by :user:`danielalanbates`.\n"
  },
  {
    "path": "CHANGES/10596.bugfix.rst",
    "content": "Fixed server hanging indefinitely when chunked transfer encoding chunk-size\ndoes not match actual data length. The server now raises\n``TransferEncodingError`` instead of waiting forever for data that will\nnever arrive -- by :user:`Fridayai700`.\n"
  },
  {
    "path": "CHANGES/10611.bugfix.rst",
    "content": "Reject HTTP requests with duplicate ``chunked`` Transfer-Encoding\n(e.g. ``Transfer-Encoding: chunked, chunked``) with a\n``BadHttpMessage`` error, per :rfc:`9112` section 7.1 -- by :user:`worksbyfriday`.\n"
  },
  {
    "path": "CHANGES/10665.feature.rst",
    "content": "Added :py:attr:`~aiohttp.web.TCPSite.port` accessor for dynamic port allocations in :class:`~aiohttp.web.TCPSite` -- by :user:`twhittock-disguise` and :user:`rodrigobnogueira`.\n"
  },
  {
    "path": "CHANGES/10683.bugfix.rst",
    "content": "Fixed misleading TLS-in-TLS warning being emitted when sending HTTPS requests through an HTTP proxy. The warning now only fires when the proxy itself uses HTTPS, which is the only case where TLS-in-TLS actually applies -- by :user:`wavebyrd`.\n"
  },
  {
    "path": "CHANGES/10753.bugfix.rst",
    "content": "Widened ``trace_request_ctx`` parameter type from ``Mapping[str, Any] | None`` to ``object`` to allow passing instances of user-defined classes as trace context -- by :user:`nightcityblade`.\n"
  },
  {
    "path": "CHANGES/10795.doc.rst",
    "content": "Replaced the deprecated ``ujson`` library with ``orjson`` in the\nclient quickstart documentation. ``ujson`` has been put into\nmaintenance-only mode; ``orjson`` is the recommended alternative.\n-- by :user:`indoor47`\n"
  },
  {
    "path": "CHANGES/11012.breaking.rst",
    "content": "Refactored ``ClientRequest`` class. This simplifies a lot of code and improves our type\nchecking accuracy. It also better aligns public/private attributes with what we expect\ndevelopers to access safely from a client middleware.\n\nIf code subclasses ``ClientRequest``, it is likely that the subclass will need tweaking\nto be compatible with the new version. Similarly, subclasses of ``ClientResponse`` may\nneed to adjust ``__init__`` parameters.\n\n-- by :user:`Dreamsorcerer`.\n"
  },
  {
    "path": "CHANGES/11268.feature.rst",
    "content": "Updated ``_TracingSignal`` to utilize a secondary generic variable for type hinting custom context variables\n-- by :user:`Vizonex`.\n"
  },
  {
    "path": "CHANGES/11283.bugfix.rst",
    "content": "Fixed access log timestamps ignoring daylight saving time (DST) changes. The\nprevious implementation used :py:data:`time.timezone` which is a constant and\ndoes not reflect DST transitions -- by :user:`nightcityblade`.\n"
  },
  {
    "path": "CHANGES/11601.breaking.rst",
    "content": "Dropped support for Python 3.9 -- by :user:`Dreamsorcerer`.\n"
  },
  {
    "path": "CHANGES/11681.feature.rst",
    "content": "Started accepting :term:`asynchronous context managers <asynchronous context manager>` for cleanup contexts.\nLegacy single-yield :term:`asynchronous generator` cleanup contexts continue to be\nsupported; async context managers are adapted internally so they are\nentered at startup and exited during cleanup.\n\n-- by :user:`MannXo`.\n"
  },
  {
    "path": "CHANGES/11737.contrib.rst",
    "content": "The benchmark CI job now runs only in the upstream repository -- by :user:`Cycloctane`.\n\nIt used to always fail in forks, which this change fixed.\n"
  },
  {
    "path": "CHANGES/11763.feature.rst",
    "content": "Added ``decode_text`` parameter to :meth:`~aiohttp.ClientSession.ws_connect` and :class:`~aiohttp.web.WebSocketResponse` to receive WebSocket TEXT messages as raw bytes instead of decoded strings, enabling direct use with high-performance JSON parsers like ``orjson`` -- by :user:`bdraco`.\n"
  },
  {
    "path": "CHANGES/11766.feature.rst",
    "content": "Added ``RequestKey`` and ``ResponseKey`` classes,\nwhich enable static type checking for request & response\ncontext storages in the same way that ``AppKey`` does for ``Application``\n-- by :user:`gsoldatov`.\n"
  },
  {
    "path": "CHANGES/11776.misc.rst",
    "content": "The warnings emitted when using ``str`` keys in ``web.Response``/``web.Request``\nhave been removed to avoid any performance concerns when frequently using these -- by :user:`Dreamsorcerer`.\n"
  },
  {
    "path": "CHANGES/11826.contrib.rst",
    "content": "The coverage tool is now configured using the new native\nauto-discovered :file:`.coveragerc.toml` file\n-- by :user:`webknjaz`.\n\nIt is also set up to use the ``ctrace`` core that works\naround the performance issues in the ``sysmon`` tracer\nwhich is default under Python 3.14.\n"
  },
  {
    "path": "CHANGES/11859.bugfix.rst",
    "content": "Removed support for ``ClientTimeout(total=0)`` to disable timeouts. Use ``None`` instead of ``0`` to disable the total timeout. Passing ``0`` now raises :exc:`ValueError` with a clear error message -- by :user:`veeceey`.\n"
  },
  {
    "path": "CHANGES/11876.misc.rst",
    "content": "Refactored tests to use ``create_autospec()`` for more robust mocking -- by :user:`soheil-star01`.\n"
  },
  {
    "path": "CHANGES/11898.bugfix.rst",
    "content": "Restored :py:meth:`~aiohttp.BodyPartReader.decode` as a synchronous method\nfor backward compatibility. The method was inadvertently changed to async\nin 3.13.3 as part of the decompression bomb security fix. A new\n:py:meth:`~aiohttp.BodyPartReader.decode_iter` method is now available\nfor non-blocking decompression of large payloads using an async generator.\nInternal aiohttp code uses the async variant to maintain security protections.\n\nChanged multipart processing chunk sizes from 64 KiB to 256KiB, to better\nmatch aiohttp internals\n-- by :user:`bdraco` and :user:`Dreamsorcerer`.\n"
  },
  {
    "path": "CHANGES/11937.misc.rst",
    "content": "Added win_arm64 to the wheels that gets pushed to PyPI\n-- by :user:`AraHaan`.\n"
  },
  {
    "path": "CHANGES/11955.feature.rst",
    "content": "Added ``max_headers`` parameter to limit the number of headers that should be read from a response -- by :user:`Dreamsorcerer`.\n"
  },
  {
    "path": "CHANGES/11972.bugfix.rst",
    "content": "Fixed false-positive :py:class:`DeprecationWarning` for passing ``enable_cleanup_closed=True`` to :py:class:`~aiohttp.TCPConnector` specifically on Python 3.12.7.\n-- by :user:`Robsdedude`.\n"
  },
  {
    "path": "CHANGES/11989.feature.rst",
    "content": "Added explicit APIs for bytes-returning JSON serializer:\n``JSONBytesEncoder`` type, ``JsonBytesPayload``,\n:func:`~aiohttp.web.json_bytes_response`,\n:meth:`~aiohttp.web.WebSocketResponse.send_json_bytes` and\n:meth:`~aiohttp.ClientWebSocketResponse.send_json_bytes` methods, and\n``json_serialize_bytes`` parameter for :class:`~aiohttp.ClientSession`\n-- by :user:`kevinpark1217`.\n"
  },
  {
    "path": "CHANGES/11992.contrib.rst",
    "content": "Fixed flaky performance tests by using appropriate fixed thresholds that account for CI variability -- by :user:`rodrigobnogueira`.\n"
  },
  {
    "path": "CHANGES/12027.misc.rst",
    "content": "Fixed ``test_invalid_idna`` to work with ``idna`` 3.11 by using an invalid character (``\\u0080``) that is rejected by ``yarl`` during URL construction -- by :user:`rodrigobnogueira`.\n"
  },
  {
    "path": "CHANGES/12030.bugfix.rst",
    "content": "Reset the WebSocket heartbeat timer on inbound data to avoid false ping/pong timeouts while receiving large frames\n-- by :user:`hoffmang9`.\n"
  },
  {
    "path": "CHANGES/12042.doc.rst",
    "content": "Documented :exc:`asyncio.TimeoutError` for ``WebSocketResponse.receive()``\nand related methods -- by :user:`veeceey`.\n"
  },
  {
    "path": "CHANGES/12069.packaging.rst",
    "content": "Upgraded llhttp to 3.9.1 -- by :user:`Dreamsorcerer`.\n"
  },
  {
    "path": "CHANGES/12088.bugfix.rst",
    "content": "Fixed tests to pass when run after 2027-05-31 -- by :user:`bmwiedemann`.\n"
  },
  {
    "path": "CHANGES/12091.bugfix.rst",
    "content": "Switched :py:meth:`~aiohttp.CookieJar.save` to use JSON format and\n:py:meth:`~aiohttp.CookieJar.load` to try JSON first with a fallback to\na restricted pickle unpickler that only allows cookie-related types\n(``SimpleCookie``, ``Morsel``, ``defaultdict``, etc.), preventing\narbitrary code execution via malicious pickle payloads\n(CWE-502) -- by :user:`YuvalElbar6`.\n"
  },
  {
    "path": "CHANGES/12096.bugfix.rst",
    "content": "Fixed _sendfile_fallback over-reading beyond requested count -- by :user:`bysiber`.\n"
  },
  {
    "path": "CHANGES/12097.bugfix.rst",
    "content": "Fixed digest auth dropping challenge fields with empty string values -- by :user:`bysiber`.\n"
  },
  {
    "path": "CHANGES/12106.feature.rst",
    "content": "Added a ``dns_cache_max_size`` parameter to ``TCPConnector`` to limit the size of the cache -- by :user:`Dreamsorcerer`.\n"
  },
  {
    "path": "CHANGES/12136.bugfix.rst",
    "content": "``ClientConnectorCertificateError.os_error`` no longer raises :exc:`AttributeError`\n-- by :user:`themylogin`.\n"
  },
  {
    "path": "CHANGES/12170.misc.rst",
    "content": "Fixed race condition in ``test_data_file`` on Python 3.14 free-threaded builds -- by :user:`rodrigobnogueira`.\n"
  },
  {
    "path": "CHANGES/12173.contrib.rst",
    "content": "Fixed and reworked ``autobahn`` tests -- by :user:`Dreamsorcerer`.\n"
  },
  {
    "path": "CHANGES/12195.bugfix.rst",
    "content": "Fixed redirects with consumed non-rewindable request bodies to raise\n:class:`aiohttp.ClientPayloadError` instead of silently sending an empty body.\n"
  },
  {
    "path": "CHANGES/12231.bugfix.rst",
    "content": "Adjusted pure-Python request header value validation to align with RFC 9110 control-character handling, while preserving lax response parser behavior, and added regression tests for Host/header control-character cases.\n-- by :user:`rodrigobnogueira`.\n"
  },
  {
    "path": "CHANGES/12240.bugfix.rst",
    "content": "Rejected duplicate singleton headers (``Host``, ``Content-Type``,\n``Content-Length``, etc.) in the C extension HTTP parser to match\nthe pure Python parser behavior, preventing potential host-based\naccess control bypasses via parser differentials\n-- by :user:`rodrigobnogueira`.\n"
  },
  {
    "path": "CHANGES/12249.bugfix.rst",
    "content": "Aligned the pure-Python HTTP request parser with the C parser by splitting\ncomma-separated and repeated ``Connection`` header values for keep-alive,\nclose, and upgrade handling -- by :user:`rodrigobnogueira`.\n"
  },
  {
    "path": "CHANGES/2174.bugfix",
    "content": "Raise 400 Bad Request on server-side `await request.json()` if incorrect content-type received.\n"
  },
  {
    "path": "CHANGES/2835.breaking.rst",
    "content": "Drop lowercased enum items of ``WSMsgType`` (text, binary, ...), use uppercased items instead (TEXT, BINARY, ...).\n"
  },
  {
    "path": "CHANGES/2977.breaking.rst",
    "content": "Drop aiodns<1.1 support.\n"
  },
  {
    "path": "CHANGES/3310.bugfix",
    "content": "Docs clarification that aiohttp client does not support HTTP Pipelining.\n"
  },
  {
    "path": "CHANGES/3462.feature",
    "content": "``web.HTTPException`` and derived classes are not inherited from ``web.Response`` anymore.\n"
  },
  {
    "path": "CHANGES/3463.breaking.rst",
    "content": "Make ``ClientSession`` slot-based class, convert debug-mode warning about a wild session modification into a strict error.\n"
  },
  {
    "path": "CHANGES/3482.bugfix",
    "content": "Do not return `None` on `await response.json()` when body is empty. Instead, raise `json.JSONDecodeError` as expected.\n"
  },
  {
    "path": "CHANGES/3538.breaking.rst",
    "content": "Drop ``@aiohttp.streamer`` decorator, use async generators instead.\n"
  },
  {
    "path": "CHANGES/3539.breaking.rst",
    "content": "Disallow creation of aiohttp objects (``ClientSession``, ``Connector`` etc.) without running event loop.\n"
  },
  {
    "path": "CHANGES/3540.feature",
    "content": "Make sanity check for web-handler return value working in release mode\n"
  },
  {
    "path": "CHANGES/3542.breaking.rst",
    "content": "Setting ``web.Application`` custom attributes is now forbidden\n"
  },
  {
    "path": "CHANGES/3545.feature",
    "content": "Drop custom router support\n"
  },
  {
    "path": "CHANGES/3547.breaking.rst",
    "content": "Remove deprecated resp.url_obj\n"
  },
  {
    "path": "CHANGES/3548.breaking.rst",
    "content": "Drop deprecated SSL client settings.\n"
  },
  {
    "path": "CHANGES/3559.doc",
    "content": "Clarified ``WebSocketResponse`` closure in the quick start example.\n"
  },
  {
    "path": "CHANGES/3562.bugfix",
    "content": "Raise ``web_exceptions.HTTPUnsupportedMediaType`` when invalid `Content-Type` encoding passed.\n"
  },
  {
    "path": "CHANGES/3569.feature",
    "content": "Make new style middleware default, deprecate the @middleware decorator and\nremove support for old-style middleware.\n"
  },
  {
    "path": "CHANGES/3580.breaking.rst",
    "content": "Drop explicit loop. Use ``asyncio.get_event_loop()`` instead if the loop instance is needed.\nAll aiohttp objects work with the currently running loop, a creation of aiohttp instances, e.g. ClientSession when the loop is not running is forbidden.\nAs a side effect of PR passing callables to ``aiohttp_server()`` and ``aiohttp_client()`` pytest fixtures are forbidden, please call these callables explicitly.\n"
  },
  {
    "path": "CHANGES/3612.bugfix",
    "content": "Fixed a grammatical error in documentation\n"
  },
  {
    "path": "CHANGES/3613.bugfix",
    "content": "Use sanitized URL as Location header in redirects\n"
  },
  {
    "path": "CHANGES/3642.doc",
    "content": "Modify documentation for Resolvers to make it clear that asynchronous resolver is not used by default when aiodns is installed.\n"
  },
  {
    "path": "CHANGES/3685.doc",
    "content": "Add documentation regarding creating and destroying persistent session.\n"
  },
  {
    "path": "CHANGES/3721.bugfix",
    "content": "Add the missing `TestClient.scheme` property.\n"
  },
  {
    "path": "CHANGES/3767.feature",
    "content": "Add ``AbstractAsyncAccessLogger`` to allow IO while logging.\n"
  },
  {
    "path": "CHANGES/3787.feature",
    "content": "Added ability to use contextvars in logger\n"
  },
  {
    "path": "CHANGES/3796.feature",
    "content": "Add a debug argument to `web.run_app()` for enabling debug mode on loop.\n"
  },
  {
    "path": "CHANGES/3890.breaking.rst",
    "content": "Drop deprecated `read_timeout` and `conn_timeout` in `ClientSession` constructor, please use `timeout` argument instead.\n"
  },
  {
    "path": "CHANGES/3901.breaking.rst",
    "content": "Drop sync context managers that raises ``TypeError`` already.\n"
  },
  {
    "path": "CHANGES/3929.breaking.rst",
    "content": "Drop processing sync web-handlers (deprecated since aiohttp 3.0)\n"
  },
  {
    "path": "CHANGES/3931.breaking.rst",
    "content": "Drop deprecated ``BaseRequest.message``, ``BaseRequest.loop``, ``BaseRequest.has_body``\n"
  },
  {
    "path": "CHANGES/3932.breaking.rst",
    "content": "Drop deprecated ``unused_port``, ``test_server``, ``raw_test_server`` and ``test_client`` pytest fixtures.\n"
  },
  {
    "path": "CHANGES/3933.breaking.rst",
    "content": "Forbid inheritance from ``ClientSession`` and ``Application``\n"
  },
  {
    "path": "CHANGES/3934.breaking.rst",
    "content": "Drop deprecated ``ClientResponseError.code`` attribute\n"
  },
  {
    "path": "CHANGES/3935.breaking.rst",
    "content": "Drop deprecated ``ClientSession.loop`` and ``Connection.loop``. Forbid changing ``ClientSession.requote_redirect_url``.\n"
  },
  {
    "path": "CHANGES/3939.breaking.rst",
    "content": "Drop deprecated ``Application.make_handler()``\n"
  },
  {
    "path": "CHANGES/3940.breaking.rst",
    "content": "Drop HTTP chunk size from client and server, remove deprecated `response.output_length`.\n"
  },
  {
    "path": "CHANGES/3942.breaking.rst",
    "content": "Make `web.BaseRequest`, `web.Request`, `web.StreamResponse`, `web.Response` and `web.WebSocketResponse` slot-based, prevent custom instance attributes.\n"
  },
  {
    "path": "CHANGES/3948.breaking.rst",
    "content": "Forbid changing frozen app properties.\n"
  },
  {
    "path": "CHANGES/3994.misc",
    "content": "correct the names of some functions in ``tests/test_client_functional.py``\n"
  },
  {
    "path": "CHANGES/4161.doc",
    "content": "Update contributing guide so new contributors can successfully install dependencies\n"
  },
  {
    "path": "CHANGES/4277.feature",
    "content": "Added ``set_cookie`` and ``del_cookie`` methods to ``HTTPException``\n"
  },
  {
    "path": "CHANGES/4283.bugfix",
    "content": "Fix incorrect code in example\n"
  },
  {
    "path": "CHANGES/4299.bugfix",
    "content": "Delete older code in example (:file:`examples/web_classview.py`)\n"
  },
  {
    "path": "CHANGES/4302.bugfix",
    "content": "Fixed the support of route handlers wrapped by :py:func:`functools.partial`\n"
  },
  {
    "path": "CHANGES/4368.bugfix",
    "content": "Make `web.BaseRequest`, `web.Request`, `web.StreamResponse`, `web.Response` and `web.WebSocketResponse` weak referenceable again.\n"
  },
  {
    "path": "CHANGES/4452.doc",
    "content": "Fixed a typo in the ``client_quickstart`` doc.\n"
  },
  {
    "path": "CHANGES/4504.doc",
    "content": "Updated the contribution guide to reflect the automatic thread locking policy.\n"
  },
  {
    "path": "CHANGES/4526.bugfix",
    "content": "Ignore protocol exceptions after it is closed.\n"
  },
  {
    "path": "CHANGES/4558.bugfix",
    "content": "Fixed body_size comparison to client_max_size for web request.\n"
  },
  {
    "path": "CHANGES/4656.bugfix",
    "content": "Propagate all warnings captured in coroutine test functions to pytest.\n"
  },
  {
    "path": "CHANGES/4695.doc",
    "content": "Added documentation on how to patch unittest cases with decorator for python < 3.8\n"
  },
  {
    "path": "CHANGES/4706.feature",
    "content": "Add a fixture ``aiohttp_client_cls`` that allows usage of ``aiohttp.test_utils.TestClient`` custom implementations in tests.\n"
  },
  {
    "path": "CHANGES/5075.feature",
    "content": "Multidict > 5 is now supported\n"
  },
  {
    "path": "CHANGES/5191.doc",
    "content": "Add pytest-aiohttp-client library to third party usage list\n"
  },
  {
    "path": "CHANGES/5258.bugfix",
    "content": "Fixed github workflow `update-pre-commit` on forks,\nsince this workflow should run only in the main repository and also because it was giving failed jobs on all the forks.\nNow it will show up as skipped workflow.\n"
  },
  {
    "path": "CHANGES/5278.breaking.rst",
    "content": "Drop Python 3.6 support\n"
  },
  {
    "path": "CHANGES/5284.breaking.rst",
    "content": "``attrs`` library was replaced with ``dataclasses``.  Replace ``attr.evolve()`` with ``dataclasses.replace()`` if needed.\n"
  },
  {
    "path": "CHANGES/5284.feature",
    "content": "Use ``dataclasses`` instead of ``attrs`` for ``ClientTimeout``, client signals, and other few internal structures.\n"
  },
  {
    "path": "CHANGES/5287.feature",
    "content": "Before ``sentinel`` was processed as either ``object`` or ``Any``, both variants are far from perfectness.\n\nNow ``sentinel`` has a dedicated type which is not equal to anything.\n"
  },
  {
    "path": "CHANGES/5516.misc",
    "content": "Removed @unittest_run_loop. This is now the default behaviour.\n"
  },
  {
    "path": "CHANGES/5533.misc",
    "content": "Add regression test for 0 timeouts.\n"
  },
  {
    "path": "CHANGES/5558.bugfix",
    "content": "Add parsing boundary from Content-Type header while making POST request\n"
  },
  {
    "path": "CHANGES/5634.feature",
    "content": "A warning was added, when a cookie's length exceeds the :rfc:`6265` minimum client support -- :user:`anesabml`.\n"
  },
  {
    "path": "CHANGES/5783.feature",
    "content": "Started keeping the ``Authorization`` header during HTTP -> HTTPS redirects when the host remains the same.\n"
  },
  {
    "path": "CHANGES/5806.misc",
    "content": "Remove last remnants of attrs library.\n"
  },
  {
    "path": "CHANGES/5829.misc",
    "content": "Disallow untyped defs on internal tests.\n"
  },
  {
    "path": "CHANGES/5870.misc",
    "content": "Simplify generator expression.\n"
  },
  {
    "path": "CHANGES/5894.bugfix",
    "content": "Fix JSON media type suffix matching with main types other than application.\n"
  },
  {
    "path": "CHANGES/6180.bugfix",
    "content": "Fixed matching the JSON media type to not accept arbitrary characters after ``application/json`` or the ``+json`` media type suffix.\n"
  },
  {
    "path": "CHANGES/6181.bugfix",
    "content": "Make JSON media type matching case insensitive per RFC 2045.\n"
  },
  {
    "path": "CHANGES/6193.feature",
    "content": "Bump async-timeout to >=4.0\n"
  },
  {
    "path": "CHANGES/6547.bugfix",
    "content": "Remove overlapping slots in ``RequestHandler``,\nfix broken slots inheritance in :py:class:`~aiohttp.web.StreamResponse`.\n"
  },
  {
    "path": "CHANGES/6721.misc",
    "content": "Remove unused argument `max_headers` of HeadersParser.\n"
  },
  {
    "path": "CHANGES/6979.doc",
    "content": "Improve grammar and brevity in communication in the Policy for Backward Incompatible Changes section of ``docs/index.rst`` -- :user:`Paarth`.\n"
  },
  {
    "path": "CHANGES/6998.doc",
    "content": "Added documentation on client authentication and updating headers. -- by :user:`faph`\n"
  },
  {
    "path": "CHANGES/7107.breaking.rst",
    "content": "Removed deprecated ``.loop``, ``.setUpAsync()``, ``.tearDownAsync()`` and ``.get_app()`` from ``AioHTTPTestCase``.\n"
  },
  {
    "path": "CHANGES/7265.breaking.rst",
    "content": "Deleted ``size`` arg from ``StreamReader.feed_data`` -- by :user:`DavidRomanovizc`.\n"
  },
  {
    "path": "CHANGES/7319.feature.rst",
    "content": "Changed ``WSMessage`` to a tagged union of ``NamedTuple`` -- by :user:`Dreamsorcerer`.\n\nThis change allows type checkers to know the precise type of ``data``\nafter checking the ``type`` attribute.\n\nIf accessing messages by tuple indexes, the order has now changed.\nCode such as:\n``typ, data, extra = ws_message``\nwill need to be changed to:\n``data, extra, typ = ws_message``\n\nNo changes are needed if accessing by attribute name.\n"
  },
  {
    "path": "CHANGES/7677.bugfix",
    "content": "Changed ``AppKey`` warning to ``web.NotAppKeyWarning`` and stop it being displayed by default. -- by :user:`Dreamsorcerer`\n"
  },
  {
    "path": "CHANGES/7772.bugfix",
    "content": "Fix CONNECT always being treated as having an empty body\n"
  },
  {
    "path": "CHANGES/7815.bugfix",
    "content": "Fixed an issue where the client could go into an infinite loop. -- by :user:`Dreamsorcerer`\n"
  },
  {
    "path": "CHANGES/8048.breaking.rst",
    "content": "Removed deprecated support for `ssl=None` -- by :user:`Dreamsorcerer`\n"
  },
  {
    "path": "CHANGES/8139.contrib.rst",
    "content": "Two definitions for \"test_invalid_route_name\" existed, only one was being run. Refactored them into a single parameterized test. Enabled lint rule to prevent regression. -- by :user:`alexmac`.\n"
  },
  {
    "path": "CHANGES/8197.doc",
    "content": "Fixed false behavior of base_url param for ClientSession in client documentation -- by :user:`alexis974`.\n"
  },
  {
    "path": "CHANGES/8303.breaking.rst",
    "content": "Removed ``content_transfer_encoding`` parameter in :py:meth:`FormData.add_field()\n<aiohttp.FormData.add_field>` and passing bytes no longer creates a file\nfield unless the ``filename`` parameter is used -- by :user:`Dreamsorcerer`.\n"
  },
  {
    "path": "CHANGES/8596.breaking.rst",
    "content": "Removed old async compatibility from ``ClientResponse.release()`` -- by :user:`Dreamsorcerer`.\n"
  },
  {
    "path": "CHANGES/8698.breaking.rst",
    "content": "Changed signature of ``content_disposition_header()`` so ``params`` is now passed as a dict, in order to reduce typing errors -- by :user:`Dreamsorcerer`.\n"
  },
  {
    "path": "CHANGES/8957.breaking.rst",
    "content": "Removed ``version`` parameter from ``.set_cookie()`` (this shouldn't exist in cookies today) -- by :user:`Dreamsorcerer`.\n"
  },
  {
    "path": "CHANGES/9109.breaking.rst",
    "content": "Changed default value to ``compress`` from ``None`` to ``False`` (``None`` is no longer an expected value) -- by :user:`Dreamsorcerer`.\n"
  },
  {
    "path": "CHANGES/9212.packaging.rst",
    "content": "Removed remaining `make_mocked_coro` in the test suite -- by :user:`polkapolka`.\n"
  },
  {
    "path": "CHANGES/9254.breaking.rst",
    "content": "Stopped allowing use of ``ClientResponse.text()``/``ClientResponse.json()`` after leaving ``async with`` context.\nThis now matches the behaviour of ``ClientResponse.read()`` -- by :user:`Dreamsorcerer`.\n"
  },
  {
    "path": "CHANGES/9292.breaking.rst",
    "content": "Started rejecting non string values in `FormData`, to avoid unexpected results -- by :user:`Dreamsorcerer`.\n"
  },
  {
    "path": "CHANGES/9413.misc.rst",
    "content": "Reduced memory required many small objects by adding ``__slots__`` to dataclasses -- by :user:`bdraco`.\n"
  },
  {
    "path": "CHANGES/README.rst",
    "content": ".. _Adding change notes with your PRs:\n\nAdding change notes with your PRs\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIt is very important to maintain a log for news of how\nupdating to the new version of the software will affect\nend-users. This is why we enforce collection of the change\nfragment files in pull requests as per `Towncrier philosophy`_.\n\nThe idea is that when somebody makes a change, they must record\nthe bits that would affect end-users, only including information\nthat would be useful to them. Then, when the maintainers publish\na new release, they'll automatically use these records to compose\na change log for the respective version. It is important to\nunderstand that including unnecessary low-level implementation\nrelated details generates noise that is not particularly useful\nto the end-users most of the time. And so such details should be\nrecorded in the Git history rather than a changelog.\n\nAlright! So how to add a news fragment?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``aiohttp`` uses `towncrier <https://pypi.org/project/towncrier/>`_\nfor changelog management.\nTo submit a change note about your PR, add a text file into the\n``CHANGES/`` folder. It should contain an\nexplanation of what applying this PR will change in the way\nend-users interact with the project. One sentence is usually\nenough but feel free to add as many details as you feel necessary\nfor the users to understand what it means.\n\n**Use the past tense** for the text in your fragment because,\ncombined with others, it will be a part of the \"news digest\"\ntelling the readers **what changed** in a specific version of\nthe library *since the previous version*. You should also use\n*reStructuredText* syntax for highlighting code (inline or block),\nlinking parts of the docs or external sites.\nHowever, you do not need to reference the issue or PR numbers here\nas *towncrier* will automatically add a reference to all of the\naffected issues when rendering the news file.\nIf you wish to sign your change, feel free to add ``-- by\n:user:`github-username``` at the end (replace ``github-username``\nwith your own!).\n\nFinally, name your file following the convention that Towncrier\nunderstands: it should start with the number of an issue or a\nPR followed by a dot, then add a patch type, like ``feature``,\n``doc``, ``contrib`` etc., and add ``.rst`` as a suffix. If you\nneed to add more than one fragment, you may add an optional\nsequence number (delimited with another period) between the type\nand the suffix.\n\nIn general the name will follow ``<pr_number>.<category>.rst`` pattern,\nwhere the categories are:\n\n- ``bugfix``: A bug fix for something we deemed an improper undesired\n  behavior that got corrected in the release to match pre-agreed\n  expectations.\n- ``feature``: A new behavior, public APIs. That sort of stuff.\n- ``deprecation``: A declaration of future API removals and breaking\n  changes in behavior.\n- ``breaking``: When something public gets removed in a breaking way.\n  Could be deprecated in an earlier release.\n- ``doc``: Notable updates to the documentation structure or build\n  process.\n- ``packaging``: Notes for downstreams about unobvious side effects\n  and tooling. Changes in the test invocation considerations and\n  runtime assumptions.\n- ``contrib``: Stuff that affects the contributor experience. e.g.\n  Running tests, building the docs, setting up the development\n  environment.\n- ``misc``: Changes that are hard to assign to any of the above\n  categories.\n\nA pull request may have more than one of these components, for example\na code change may introduce a new feature that deprecates an old\nfeature, in which case two fragments should be added. It is not\nnecessary to make a separate documentation fragment for documentation\nchanges accompanying the relevant code changes.\n\nExamples for adding changelog entries to your Pull Requests\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nFile :file:`CHANGES/6045.doc.1.rst`:\n\n.. code-block:: rst\n\n    Added a ``:user:`` role to Sphinx config -- by :user:`webknjaz`.\n\nFile :file:`CHANGES/8074.bugfix.rst`:\n\n.. code-block:: rst\n\n    Fixed an unhandled exception in the Python HTTP parser on header\n    lines starting with a colon -- by :user:`pajod`.\n\n    Invalid request lines with anything but a dot between the HTTP\n    major and minor version are now rejected. Invalid header field\n    names containing question mark or slash are now rejected. Such\n    requests are incompatible with :rfc:`9110#section-5.6.2` and are\n    not known to be of any legitimate use.\n\nFile :file:`CHANGES/4594.feature.rst`:\n\n.. code-block:: rst\n\n    Added support for ``ETag`` to :py:class:`~aiohttp.web.FileResponse`\n    -- by :user:`greshilov`, :user:`serhiy-storchaka` and\n    :user:`asvetlov`.\n\n.. tip::\n\n   See :file:`pyproject.toml` for all available categories\n   (``tool.towncrier.type``).\n\n.. _Towncrier philosophy:\n   https://towncrier.readthedocs.io/en/stable/#philosophy\n"
  },
  {
    "path": "CHANGES.rst",
    "content": "..\n    You should *NOT* be adding new change log entries to this file, this\n    file is managed by towncrier. You *may* edit previous change logs to\n    fix problems like typo corrections or such.\n    To add a new change log entry, please see\n    https://pip.pypa.io/en/latest/development/#adding-a-news-entry\n    we named the news folder \"changes\".\n\n    WARNING: Don't drop the next directive!\n\n.. towncrier release notes start\n\n3.13.3 (2026-01-03)\n===================\n\nThis release contains fixes for several vulnerabilities. It is advised to\nupgrade as soon as possible.\n\nBug fixes\n---------\n\n- Fixed proxy authorization headers not being passed when reusing a connection, which caused 407 (Proxy authentication required) errors\n  -- by :user:`GLeurquin`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`2596`.\n\n\n\n- Fixed multipart reading failing when encountering an empty body part -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11857`.\n\n\n\n- Fixed a case where the parser wasn't raising an exception for a websocket continuation frame when there was no initial frame in context.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11862`.\n\n\n\n\nRemovals and backward incompatible breaking changes\n---------------------------------------------------\n\n- ``Brotli`` and ``brotlicffi`` minimum version is now 1.2.\n  Decompression now has a default maximum output size of 32MiB per decompress call -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11898`.\n\n\n\n\nPackaging updates and notes for downstreams\n-------------------------------------------\n\n- Moved dependency metadata from :file:`setup.cfg` to :file:`pyproject.toml` per :pep:`621`\n  -- by :user:`cdce8p`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11643`.\n\n\n\n\nContributor-facing changes\n--------------------------\n\n- Removed unused ``update-pre-commit`` github action workflow -- by :user:`Cycloctane`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11689`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Optimized web server performance when access logging is disabled by reducing time syscalls -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10713`.\n\n\n\n- Added regression test for cached logging status -- by :user:`meehand`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11778`.\n\n\n\n\n----\n\n\n3.13.2 (2025-10-28)\n===================\n\nBug fixes\n---------\n\n- Fixed cookie parser to continue parsing subsequent cookies when encountering a malformed cookie that fails regex validation, such as Google's ``g_state`` cookie with unescaped quotes -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11632`.\n\n\n\n- Fixed loading netrc credentials from the default :file:`~/.netrc` (:file:`~/_netrc` on Windows) location when the :envvar:`NETRC` environment variable is not set -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11713`, :issue:`11714`.\n\n\n\n- Fixed WebSocket compressed sends to be cancellation safe. Tasks are now shielded during compression to prevent compressor state corruption. This ensures that the stateful compressor remains consistent even when send operations are cancelled -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11725`.\n\n\n\n\n----\n\n\n3.13.1 (2025-10-17)\n===================\n\nFeatures\n--------\n\n- Make configuration options in ``AppRunner`` also available in ``run_app()``\n  -- by :user:`Cycloctane`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11633`.\n\n\n\nBug fixes\n---------\n\n- Switched to `backports.zstd` for Python <3.14 and fixed zstd decompression for chunked zstd streams -- by :user:`ZhaoMJ`.\n\n  Note: Users who installed ``zstandard`` for support on Python <3.14 will now need to install\n  ``backports.zstd`` instead (installing ``aiohttp[speedups]`` will do this automatically).\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11623`.\n\n\n\n- Updated ``Content-Type`` header parsing to return ``application/octet-stream`` when header contains invalid syntax.\n  See :rfc:`9110#section-8.3-5`.\n\n  -- by :user:`sgaist`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10889`.\n\n\n\n- Fixed Python 3.14 support when built without ``zstd`` support -- by :user:`JacobHenner`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11603`.\n\n\n\n- Fixed blocking I/O in the event loop when using netrc authentication by moving netrc file lookup to an executor -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11634`.\n\n\n\n- Fixed routing to a sub-application added via ``.add_domain()`` not working\n  if the same path exists on the parent app. -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11673`.\n\n\n\n\nPackaging updates and notes for downstreams\n-------------------------------------------\n\n- Moved core packaging metadata from :file:`setup.cfg` to :file:`pyproject.toml` per :pep:`621`\n  -- by :user:`cdce8p`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9951`.\n\n\n\n\n----\n\n\n3.13.0 (2025-10-06)\n===================\n\nFeatures\n--------\n\n- Added support for Python 3.14.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10851`, :issue:`10872`.\n\n\n\n- Added support for free-threading in Python 3.14+ -- by :user:`kumaraditya303`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11466`, :issue:`11464`.\n\n\n\n- Added support for Zstandard (aka Zstd) compression\n  -- by :user:`KGuillaume-chaps`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11161`.\n\n\n\n- Added ``StreamReader.total_raw_bytes`` to check the number of bytes downloaded\n  -- by :user:`robpats`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11483`.\n\n\n\nBug fixes\n---------\n\n- Fixed pytest plugin to not use deprecated :py:mod:`asyncio` policy APIs.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10851`.\n\n\n\n- Updated `Content-Disposition` header parsing to handle trailing semicolons and empty parts\n  -- by :user:`PLPeeters`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11243`.\n\n\n\n- Fixed saved ``CookieJar`` failing to be loaded if cookies have ``partitioned`` flag when\n  ``http.cookie`` does not have partitioned cookies supports. -- by :user:`Cycloctane`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11523`.\n\n\n\n\nImproved documentation\n----------------------\n\n- Added ``Wireup`` to third-party libraries -- by :user:`maldoinc`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11233`.\n\n\n\n\nPackaging updates and notes for downstreams\n-------------------------------------------\n\n- The `blockbuster` test dependency is now optional; the corresponding test fixture is disabled when it is unavailable\n  -- by :user:`musicinybrain`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11363`.\n\n\n\n- Added ``riscv64`` build to releases -- by :user:`eshattow`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11425`.\n\n\n\n\nContributor-facing changes\n--------------------------\n\n\n\n- Fixed ``test_send_compress_text`` failing when alternative zlib implementation\n  is used. (``zlib-ng`` in python 3.14 windows build) -- by :user:`Cycloctane`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11546`.\n\n\n\n\n----\n\n\n3.12.15 (2025-07-28)\n====================\n\nBug fixes\n---------\n\n- Fixed :class:`~aiohttp.DigestAuthMiddleware` to preserve the algorithm case from the server's challenge in the authorization response. This improves compatibility with servers that perform case-sensitive algorithm matching (e.g., servers expecting ``algorithm=MD5-sess`` instead of ``algorithm=MD5-SESS``)\n  -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11352`.\n\n\n\n\nImproved documentation\n----------------------\n\n- Remove outdated contents of ``aiohttp-devtools`` and ``aiohttp-swagger``\n  from Web_advanced docs.\n  -- by :user:`Cycloctane`\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11347`.\n\n\n\n\nPackaging updates and notes for downstreams\n-------------------------------------------\n\n- Started including the ``llhttp`` :file:`LICENSE` file in wheels by adding ``vendor/llhttp/LICENSE`` to ``license-files`` in :file:`setup.cfg` -- by :user:`threexc`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11226`.\n\n\n\n\nContributor-facing changes\n--------------------------\n\n- Updated a regex in `test_aiohttp_request_coroutine` for Python 3.14.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11271`.\n\n\n\n\n----\n\n\n3.12.14 (2025-07-10)\n====================\n\nBug fixes\n---------\n\n- Fixed file uploads failing with HTTP 422 errors when encountering 307/308 redirects, and 301/302 redirects for non-POST methods, by preserving the request body when appropriate per :rfc:`9110#section-15.4.3-3.1` -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11270`.\n\n\n\n- Fixed :py:meth:`ClientSession.close() <aiohttp.ClientSession.close>` hanging indefinitely when using HTTPS requests through HTTP proxies -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11273`.\n\n\n\n- Bumped minimum version of aiosignal to 1.4+ to resolve typing issues -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11280`.\n\n\n\n\nFeatures\n--------\n\n- Added initial trailer parsing logic to Python HTTP parser -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11269`.\n\n\n\n\nImproved documentation\n----------------------\n\n- Clarified exceptions raised by ``WebSocketResponse.send_frame`` et al.\n  -- by :user:`DoctorJohn`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11234`.\n\n\n\n\n----\n\n\n3.12.13 (2025-06-14)\n====================\n\nBug fixes\n---------\n\n- Fixed auto-created :py:class:`~aiohttp.TCPConnector` not using the session's event loop when :py:class:`~aiohttp.ClientSession` is created without an explicit connector -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11147`.\n\n\n\n\n----\n\n\n3.12.12 (2025-06-09)\n====================\n\nBug fixes\n---------\n\n- Fixed cookie unquoting to properly handle octal escape sequences in cookie values (e.g., ``\\012`` for newline) by vendoring the correct ``_unquote`` implementation from Python's ``http.cookies`` module -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11173`.\n\n\n\n- Fixed ``Cookie`` header parsing to treat attribute names as regular cookies per :rfc:`6265#section-5.4` -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11178`.\n\n\n\n\n----\n\n\n3.12.11 (2025-06-07)\n====================\n\nFeatures\n--------\n\n- Improved SSL connection handling by changing the default ``ssl_shutdown_timeout``\n  from ``0.1`` to ``0`` seconds. SSL connections now use Python's default graceful\n  shutdown during normal operation but are aborted immediately when the connector\n  is closed, providing optimal behavior for both cases. Also added support for\n  ``ssl_shutdown_timeout=0`` on all Python versions. Previously, this value was\n  rejected on Python 3.11+ and ignored on earlier versions. Non-zero values on\n  Python < 3.11 now trigger a ``RuntimeWarning`` -- by :user:`bdraco`.\n\n  The ``ssl_shutdown_timeout`` parameter is now deprecated and will be removed in\n  aiohttp 4.0 as there is no clear use case for changing the default.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11148`.\n\n\n\n\nDeprecations (removal in next major release)\n--------------------------------------------\n\n- Improved SSL connection handling by changing the default ``ssl_shutdown_timeout``\n  from ``0.1`` to ``0`` seconds. SSL connections now use Python's default graceful\n  shutdown during normal operation but are aborted immediately when the connector\n  is closed, providing optimal behavior for both cases. Also added support for\n  ``ssl_shutdown_timeout=0`` on all Python versions. Previously, this value was\n  rejected on Python 3.11+ and ignored on earlier versions. Non-zero values on\n  Python < 3.11 now trigger a ``RuntimeWarning`` -- by :user:`bdraco`.\n\n  The ``ssl_shutdown_timeout`` parameter is now deprecated and will be removed in\n  aiohttp 4.0 as there is no clear use case for changing the default.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11148`.\n\n\n\n\n----\n\n\n3.12.10 (2025-06-07)\n====================\n\nBug fixes\n---------\n\n- Fixed leak of ``aiodns.DNSResolver`` when :py:class:`~aiohttp.TCPConnector` is closed and no resolver was passed when creating the connector -- by :user:`Tasssadar`.\n\n  This was a regression introduced in version 3.12.0 (:pr:`10897`).\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11150`.\n\n\n\n\n----\n\n\n3.12.9 (2025-06-04)\n===================\n\nBug fixes\n---------\n\n- Fixed ``IOBasePayload`` and ``TextIOPayload`` reading entire files into memory when streaming large files -- by :user:`bdraco`.\n\n  When using file-like objects with the aiohttp client, the entire file would be read into memory if the file size was provided in the ``Content-Length`` header. This could cause out-of-memory errors when uploading large files. The payload classes now correctly read data in chunks of ``READ_SIZE`` (64KB) regardless of the total content length.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11138`.\n\n\n\n\n----\n\n\n3.12.8 (2025-06-04)\n===================\n\nFeatures\n--------\n\n- Added preemptive digest authentication to :class:`~aiohttp.DigestAuthMiddleware` -- by :user:`bdraco`.\n\n  The middleware now reuses authentication credentials for subsequent requests to the same\n  protection space, improving efficiency by avoiding extra authentication round trips.\n  This behavior matches how web browsers handle digest authentication and follows\n  :rfc:`7616#section-3.6`.\n\n  Preemptive authentication is enabled by default but can be disabled by passing\n  ``preemptive=False`` to the middleware constructor.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11128`, :issue:`11129`.\n\n\n\n\n----\n\n\n3.12.7 (2025-06-02)\n===================\n\n.. warning::\n\n   This release fixes an issue where the ``quote_cookie`` parameter was not being properly\n   respected for shared cookies (domain=\"\", path=\"\"). If your server does not handle quoted\n   cookies correctly, you may need to disable cookie quoting by setting ``quote_cookie=False``\n   when creating your :class:`~aiohttp.ClientSession` or :class:`~aiohttp.CookieJar`.\n   See :ref:`aiohttp-client-cookie-quoting-routine` for details.\n\nBug fixes\n---------\n\n- Fixed cookie parsing to be more lenient when handling cookies with special characters\n  in names or values. Cookies with characters like ``{``, ``}``, and ``/`` in names are now\n  accepted instead of causing a :exc:`~http.cookies.CookieError` and 500 errors. Additionally,\n  cookies with mismatched quotes in values are now parsed correctly, and quoted cookie\n  values are now handled consistently whether or not they include special attributes\n  like ``Domain``. Also fixed :class:`~aiohttp.CookieJar` to ensure shared cookies (domain=\"\", path=\"\")\n  respect the ``quote_cookie`` parameter, making cookie quoting behavior consistent for\n  all cookies -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`2683`, :issue:`5397`, :issue:`7993`, :issue:`11112`.\n\n\n\n- Fixed an issue where cookies with duplicate names but different domains or paths\n  were lost when updating the cookie jar. The :class:`~aiohttp.ClientSession`\n  cookie jar now correctly stores all cookies even if they have the same name but\n  different domain or path, following the :rfc:`6265#section-5.3` storage model -- by :user:`bdraco`.\n\n  Note that :attr:`ClientResponse.cookies <aiohttp.ClientResponse.cookies>` returns\n  a :class:`~http.cookies.SimpleCookie` which uses the cookie name as a key, so\n  only the last cookie with each name is accessible via this interface. All cookies\n  can be accessed via :meth:`ClientResponse.headers.getall('Set-Cookie')\n  <multidict.MultiDictProxy.getall>` if needed.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`4486`, :issue:`11105`, :issue:`11106`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Avoided creating closed futures in ``ResponseHandler`` that will never be awaited -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11107`.\n\n\n\n- Downgraded the logging level for connector close errors from ERROR to DEBUG, as these are expected behavior with TLS 1.3 connections -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11114`.\n\n\n\n\n----\n\n\n3.12.6 (2025-05-31)\n===================\n\nBug fixes\n---------\n\n- Fixed spurious \"Future exception was never retrieved\" warnings for connection lost errors when the connector is not closed -- by :user:`bdraco`.\n\n  When connections are lost, the exception is now marked as retrieved since it is always propagated through other means, preventing unnecessary warnings in logs.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11100`.\n\n\n\n\n----\n\n\n3.12.5 (2025-05-30)\n===================\n\nFeatures\n--------\n\n- Added ``ssl_shutdown_timeout`` parameter to :py:class:`~aiohttp.ClientSession` and :py:class:`~aiohttp.TCPConnector` to control the grace period for SSL shutdown handshake on TLS connections. This helps prevent \"connection reset\" errors on the server side while avoiding excessive delays during connector cleanup. Note: This parameter only takes effect on Python 3.11+ -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11091`, :issue:`11094`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved performance of isinstance checks by using collections.abc types instead of typing module equivalents -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11085`, :issue:`11088`.\n\n\n\n\n----\n\n\n3.12.4 (2025-05-28)\n===================\n\nBug fixes\n---------\n\n- Fixed connector not waiting for connections to close before returning from :meth:`~aiohttp.BaseConnector.close` (partial backport of :pr:`3733`) -- by :user:`atemate` and :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`1925`, :issue:`11074`.\n\n\n\n\n----\n\n\n3.12.3 (2025-05-28)\n===================\n\nBug fixes\n---------\n\n- Fixed memory leak in :py:meth:`~aiohttp.CookieJar.filter_cookies` that caused unbounded memory growth\n  when making requests to different URL paths -- by :user:`bdraco` and :user:`Cycloctane`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11052`, :issue:`11054`.\n\n\n\n\n----\n\n\n3.12.2 (2025-05-26)\n===================\n\nBug fixes\n---------\n\n- Fixed ``Content-Length`` header not being set to ``0`` for non-GET requests with ``None`` body -- by :user:`bdraco`.\n\n  Non-GET requests (``POST``, ``PUT``, ``PATCH``, ``DELETE``) with ``None`` as the body now correctly set the ``Content-Length`` header to ``0``, matching the behavior of requests with empty bytes (``b\"\"``). This regression was introduced in aiohttp 3.12.1.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`11035`.\n\n\n\n\n----\n\n\n3.12.1 (2025-05-26)\n===================\n\nFeatures\n--------\n\n- Added support for reusable request bodies to enable retries, redirects, and digest authentication -- by :user:`bdraco` and :user:`GLGDLY`.\n\n  Most payloads can now be safely reused multiple times, fixing long-standing issues where POST requests with form data or file uploads would fail on redirects with errors like \"Form data has been processed already\" or \"I/O operation on closed file\". This also enables digest authentication to work with request bodies and allows retry mechanisms to resend requests without consuming the payload. Note that payloads derived from async iterables may still not be reusable in some cases.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`5530`, :issue:`5577`, :issue:`9201`, :issue:`11017`.\n\n\n\n\n----\n\n\n3.12.0 (2025-05-24)\n===================\n\nBug fixes\n---------\n\n- Fixed :py:attr:`~aiohttp.web.WebSocketResponse.prepared` property to correctly reflect the prepared state, especially during timeout scenarios -- by :user:`bdraco`\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`6009`, :issue:`10988`.\n\n\n\n- Response is now always True, instead of using MutableMapping behaviour (False when map is empty)\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10119`.\n\n\n\n- Fixed connection reuse for file-like data payloads by ensuring buffer\n  truncation respects content-length boundaries and preventing premature\n  connection closure race -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10325`, :issue:`10915`, :issue:`10941`, :issue:`10943`.\n\n\n\n- Fixed pytest plugin to not use deprecated :py:mod:`asyncio` policy APIs.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10851`.\n\n\n\n- Fixed :py:class:`~aiohttp.resolver.AsyncResolver` not using the ``loop`` argument in versions 3.x where it should still be supported -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10951`.\n\n\n\n\nFeatures\n--------\n\n- Added a comprehensive HTTP Digest Authentication client middleware (DigestAuthMiddleware)\n  that implements RFC 7616. The middleware supports all standard hash algorithms\n  (MD5, SHA, SHA-256, SHA-512) with session variants, handles both 'auth' and\n  'auth-int' quality of protection options, and automatically manages the\n  authentication flow by intercepting 401 responses and retrying with proper\n  credentials -- by :user:`feus4177`, :user:`TimMenninger`, and :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`2213`, :issue:`10725`.\n\n\n\n- Added client middleware support -- by :user:`bdraco` and :user:`Dreamsorcerer`.\n\n  This change allows users to add middleware to the client session and requests, enabling features like\n  authentication, logging, and request/response modification without modifying the core\n  request logic. Additionally, the ``session`` attribute was added to ``ClientRequest``,\n  allowing middleware to access the session for making additional requests.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9732`, :issue:`10902`, :issue:`10945`, :issue:`10952`, :issue:`10959`, :issue:`10968`.\n\n\n\n- Allow user setting zlib compression backend -- by :user:`TimMenninger`\n\n  This change allows the user to call :func:`aiohttp.set_zlib_backend()` with the\n  zlib compression module of their choice. Default behavior continues to use\n  the builtin ``zlib`` library.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9798`.\n\n\n\n- Added support for overriding the base URL with an absolute one in client sessions\n  -- by :user:`vivodi`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10074`.\n\n\n\n- Added ``host`` parameter to ``aiohttp_server`` fixture -- by :user:`christianwbrock`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10120`.\n\n\n\n- Detect blocking calls in coroutines using BlockBuster -- by :user:`cbornet`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10433`.\n\n\n\n- Added ``socket_factory`` to :py:class:`aiohttp.TCPConnector` to allow specifying custom socket options\n  -- by :user:`TimMenninger`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10474`, :issue:`10520`, :issue:`10961`, :issue:`10962`.\n\n\n\n- Started building armv7l manylinux wheels -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10797`.\n\n\n\n- Implemented shared DNS resolver management to fix excessive resolver object creation\n  when using multiple client sessions. The new ``_DNSResolverManager`` singleton ensures\n  only one ``DNSResolver`` object is created for default configurations, significantly\n  reducing resource usage and improving performance for applications using multiple\n  client sessions simultaneously -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10847`, :issue:`10923`, :issue:`10946`.\n\n\n\n- Upgraded to LLHTTP 9.3.0 -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10972`.\n\n\n\n- Optimized small HTTP requests/responses by coalescing headers and body into a single TCP packet -- by :user:`bdraco`.\n\n  This change enhances network efficiency by reducing the number of packets sent for small HTTP payloads, improving latency and reducing overhead. Most importantly, this fixes compatibility with memory-constrained IoT devices that can only perform a single read operation and expect HTTP requests in one packet. The optimization uses zero-copy ``writelines`` when coalescing data and works with both regular and chunked transfer encoding.\n\n  When ``aiohttp`` uses client middleware to communicate with an ``aiohttp`` server, connection reuse is more likely to occur since complete responses arrive in a single packet for small payloads.\n\n  This aligns ``aiohttp`` with other popular HTTP clients that already coalesce small requests.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10991`.\n\n\n\n\nImproved documentation\n----------------------\n\n- Improved documentation for middleware by adding warnings and examples about\n  request body stream consumption. The documentation now clearly explains that\n  request body streams can only be read once and provides best practices for\n  sharing parsed request data between middleware and handlers -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`2914`.\n\n\n\n\nPackaging updates and notes for downstreams\n-------------------------------------------\n\n- Removed non SPDX-license description from ``setup.cfg`` -- by :user:`devanshu-ziphq`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10662`.\n\n\n\n- Added support for building against system ``llhttp`` library -- by :user:`mgorny`.\n\n  This change adds support for :envvar:`AIOHTTP_USE_SYSTEM_DEPS` environment variable that\n  can be used to build aiohttp against the system install of the ``llhttp`` library rather\n  than the vendored one.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10759`.\n\n\n\n- ``aiodns`` is now installed on Windows with speedups extra -- by :user:`bdraco`.\n\n  As of ``aiodns`` 3.3.0, ``SelectorEventLoop`` is no longer required when using ``pycares`` 4.7.0 or later.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10823`.\n\n\n\n- Fixed compatibility issue with Cython 3.1.1 -- by :user:`bdraco`\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10877`.\n\n\n\n\nContributor-facing changes\n--------------------------\n\n- Sped up tests by disabling ``blockbuster`` fixture for ``test_static_file_huge`` and ``test_static_file_huge_cancel`` tests -- by :user:`dikos1337`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9705`, :issue:`10761`.\n\n\n\n- Updated tests to avoid using deprecated :py:mod:`asyncio` policy APIs and\n  make it compatible with Python 3.14.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10851`.\n\n\n\n- Added Winloop to test suite to support in the future -- by :user:`Vizonex`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10922`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Added support for the ``partitioned`` attribute in the ``set_cookie`` method.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9870`.\n\n\n\n- Setting :attr:`aiohttp.web.StreamResponse.last_modified` to an unsupported type will now raise :exc:`TypeError` instead of silently failing -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10146`.\n\n\n\n\n----\n\n\n3.11.18 (2025-04-20)\n====================\n\nBug fixes\n---------\n\n- Disabled TLS in TLS warning (when using HTTPS proxies) for uvloop and newer Python versions -- by :user:`lezgomatt`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`7686`.\n\n\n\n- Fixed reading fragmented WebSocket messages when the payload was masked -- by :user:`bdraco`.\n\n  The problem first appeared in 3.11.17\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10764`.\n\n\n\n\n----\n\n\n3.11.17 (2025-04-19)\n====================\n\nMiscellaneous internal changes\n------------------------------\n\n- Optimized web server performance when access logging is disabled by reducing time syscalls -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10713`.\n\n\n\n- Improved web server performance when connection can be reused -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10714`.\n\n\n\n- Improved performance of the WebSocket reader -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10740`.\n\n\n\n- Improved performance of the WebSocket reader with large messages -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10744`.\n\n\n\n\n----\n\n\n3.11.16 (2025-04-01)\n====================\n\nBug fixes\n---------\n\n- Replaced deprecated ``asyncio.iscoroutinefunction`` with its counterpart from ``inspect``\n  -- by :user:`layday`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10634`.\n\n\n\n- Fixed :class:`multidict.CIMultiDict` being mutated when passed to :class:`aiohttp.web.Response` -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10672`.\n\n\n\n\n----\n\n\n3.11.15 (2025-03-31)\n====================\n\nBug fixes\n---------\n\n- Reverted explicitly closing sockets if an exception is raised during ``create_connection`` -- by :user:`bdraco`.\n\n  This change originally appeared in aiohttp 3.11.13\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10464`, :issue:`10617`, :issue:`10656`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved performance of WebSocket buffer handling -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10601`.\n\n\n\n- Improved performance of serializing headers -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10625`.\n\n\n\n\n----\n\n\n3.11.14 (2025-03-16)\n====================\n\nBug fixes\n---------\n\n- Fixed an issue where dns queries were delayed indefinitely when an exception occurred in a ``trace.send_dns_cache_miss``\n  -- by :user:`logioniz`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10529`.\n\n\n\n- Fixed DNS resolution on platforms that don't support ``socket.AI_ADDRCONFIG`` -- by :user:`maxbachmann`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10542`.\n\n\n\n- The connector now raises :exc:`aiohttp.ClientConnectionError` instead of :exc:`OSError` when failing to explicitly close the socket after :py:meth:`asyncio.loop.create_connection` fails -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10551`.\n\n\n\n- Break cyclic references at connection close when there was a traceback -- by :user:`bdraco`.\n\n  Special thanks to :user:`availov` for reporting the issue.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10556`.\n\n\n\n- Break cyclic references when there is an exception handling a request -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10569`.\n\n\n\n\nFeatures\n--------\n\n- Improved logging on non-overlapping WebSocket client protocols to include the remote address -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10564`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved performance of parsing content types by adding a cache in the same manner currently done with mime types -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10552`.\n\n\n\n\n----\n\n\n3.11.13 (2025-02-24)\n====================\n\nBug fixes\n---------\n\n- Removed a break statement inside the finally block in :py:class:`~aiohttp.web.RequestHandler`\n  -- by :user:`Cycloctane`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10434`.\n\n\n\n- Changed connection creation to explicitly close sockets if an exception is raised in the event loop's ``create_connection`` method -- by :user:`top-oai`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10464`.\n\n\n\n\nPackaging updates and notes for downstreams\n-------------------------------------------\n\n- Fixed test ``test_write_large_payload_deflate_compression_data_in_eof_writelines`` failing with Python 3.12.9+ or 3.13.2+ -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10423`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Added human-readable error messages to the exceptions for WebSocket disconnects due to PONG not being received -- by :user:`bdraco`.\n\n  Previously, the error messages were empty strings, which made it hard to determine what went wrong.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10422`.\n\n\n\n\n----\n\n\n3.11.12 (2025-02-05)\n====================\n\nBug fixes\n---------\n\n- ``MultipartForm.decode()`` now follows RFC1341 7.2.1 with a ``CRLF`` after the boundary\n  -- by :user:`imnotjames`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10270`.\n\n\n\n- Restored the missing ``total_bytes`` attribute to ``EmptyStreamReader`` -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10387`.\n\n\n\n\nFeatures\n--------\n\n- Updated :py:func:`~aiohttp.request` to make it accept ``_RequestOptions`` kwargs.\n  -- by :user:`Cycloctane`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10300`.\n\n\n\n- Improved logging of HTTP protocol errors to include the remote address -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10332`.\n\n\n\n\nImproved documentation\n----------------------\n\n- Added ``aiohttp-openmetrics`` to list of third-party libraries -- by :user:`jelmer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10304`.\n\n\n\n\nPackaging updates and notes for downstreams\n-------------------------------------------\n\n- Added missing files to the source distribution to fix ``Makefile`` targets.\n  Added a ``cythonize-nodeps`` target to run Cython without invoking pip to install dependencies.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10366`.\n\n\n\n- Started building armv7l musllinux wheels -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10404`.\n\n\n\n\nContributor-facing changes\n--------------------------\n\n- The CI/CD workflow has been updated to use `upload-artifact` v4 and `download-artifact` v4 GitHub Actions -- by :user:`silamon`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10281`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Restored support for zero copy writes when using Python 3.12 versions 3.12.9 and later or Python 3.13.2+ -- by :user:`bdraco`.\n\n  Zero copy writes were previously disabled due to :cve:`2024-12254` which is resolved in these Python versions.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10137`.\n\n\n\n\n----\n\n\n3.11.11 (2024-12-18)\n====================\n\nBug fixes\n---------\n\n- Updated :py:meth:`~aiohttp.ClientSession.request` to reuse the ``quote_cookie`` setting from ``ClientSession._cookie_jar`` when processing cookies parameter.\n  -- by :user:`Cycloctane`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10093`.\n\n\n\n- Fixed type of ``SSLContext`` for some static type checkers (e.g. pyright).\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10099`.\n\n\n\n- Updated :meth:`aiohttp.web.StreamResponse.write` annotation to also allow :class:`bytearray` and :class:`memoryview` as inputs -- by :user:`cdce8p`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10154`.\n\n\n\n- Fixed a hang where a connection previously used for a streaming\n  download could be returned to the pool in a paused state.\n  -- by :user:`javitonino`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10169`.\n\n\n\n\nFeatures\n--------\n\n- Enabled ALPN on default SSL contexts. This improves compatibility with some\n  proxies which don't work without this extension.\n  -- by :user:`Cycloctane`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10156`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Fixed an infinite loop that can occur when using aiohttp in combination\n  with `async-solipsism`_ -- by :user:`bmerry`.\n\n  .. _async-solipsism: https://github.com/bmerry/async-solipsism\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10149`.\n\n\n\n\n----\n\n\n3.11.10 (2024-12-05)\n====================\n\nBug fixes\n---------\n\n- Fixed race condition in :class:`aiohttp.web.FileResponse` that could have resulted in an incorrect response if the file was replaced on the file system during ``prepare`` -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10101`, :issue:`10113`.\n\n\n\n- Replaced deprecated call to :func:`mimetypes.guess_type` with :func:`mimetypes.guess_file_type` when using Python 3.13+ -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10102`.\n\n\n\n- Disabled zero copy writes in the ``StreamWriter`` -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10125`.\n\n\n\n\n----\n\n\n3.11.9 (2024-12-01)\n===================\n\nBug fixes\n---------\n\n- Fixed invalid method logging unexpected being logged at exception level on subsequent connections -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10055`, :issue:`10076`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved performance of parsing headers when using the C parser -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10073`.\n\n\n\n\n----\n\n\n3.11.8 (2024-11-27)\n===================\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved performance of creating :class:`aiohttp.ClientResponse` objects when there are no cookies -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10029`.\n\n\n\n- Improved performance of creating :class:`aiohttp.ClientResponse` objects -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10030`.\n\n\n\n- Improved performances of creating objects during the HTTP request lifecycle -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10037`.\n\n\n\n- Improved performance of constructing :class:`aiohttp.web.Response` with headers -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10043`.\n\n\n\n- Improved performance of making requests when there are no auto headers to skip -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10049`.\n\n\n\n- Downgraded logging of invalid HTTP method exceptions on the first request to debug level -- by :user:`bdraco`.\n\n  HTTP requests starting with an invalid method are relatively common, especially when connected to the public internet, because browsers or other clients may try to speak SSL to a plain-text server or vice-versa. These exceptions can quickly fill the log with noise when nothing is wrong.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10055`.\n\n\n\n\n----\n\n\n3.11.7 (2024-11-21)\n===================\n\nBug fixes\n---------\n\n- Fixed the HTTP client not considering the connector's ``force_close`` value when setting the ``Connection`` header -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10003`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved performance of serializing HTTP headers -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`10014`.\n\n\n\n\n----\n\n\n3.11.6 (2024-11-19)\n===================\n\nBug fixes\n---------\n\n- Restored the ``force_close`` method to the ``ResponseHandler`` -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9997`.\n\n\n\n\n----\n\n\n3.11.5 (2024-11-19)\n===================\n\nBug fixes\n---------\n\n- Fixed the ``ANY`` method not appearing in :meth:`~aiohttp.web.UrlDispatcher.routes` -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9899`, :issue:`9987`.\n\n\n\n\n----\n\n\n3.11.4 (2024-11-18)\n===================\n\nBug fixes\n---------\n\n- Fixed ``StaticResource`` not allowing the ``OPTIONS`` method after calling ``set_options_route`` -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9972`, :issue:`9975`, :issue:`9976`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved performance of creating web responses when there are no cookies -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9895`.\n\n\n\n\n----\n\n\n3.11.3 (2024-11-18)\n===================\n\nBug fixes\n---------\n\n- Removed non-existing ``__author__`` from ``dir(aiohttp)`` -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9918`.\n\n\n\n- Restored the ``FlowControlDataQueue`` class -- by :user:`bdraco`.\n\n  This class is no longer used internally, and will be permanently removed in the next major version.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9963`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved performance of resolving resources when multiple methods are registered for the same route -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9899`.\n\n\n\n\n----\n\n\n3.11.2 (2024-11-14)\n===================\n\nBug fixes\n---------\n\n- Fixed improperly closed WebSocket connections generating an unhandled exception -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9883`.\n\n\n\n\n----\n\n\n3.11.1 (2024-11-14)\n===================\n\nBug fixes\n---------\n\n- Added a backward compatibility layer to :class:`aiohttp.RequestInfo` to allow creating these objects without a ``real_url`` -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9873`.\n\n\n\n\n----\n\n\n3.11.0 (2024-11-13)\n===================\n\nBug fixes\n---------\n\n- Raise :exc:`aiohttp.ServerFingerprintMismatch` exception on client-side if request through http proxy with mismatching server fingerprint digest: `aiohttp.ClientSession(headers=headers, connector=TCPConnector(ssl=aiohttp.Fingerprint(mismatch_digest), trust_env=True).request(...)` -- by :user:`gangj`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`6652`.\n\n\n\n- Modified websocket :meth:`aiohttp.ClientWebSocketResponse.receive_str`, :py:meth:`aiohttp.ClientWebSocketResponse.receive_bytes`, :py:meth:`aiohttp.web.WebSocketResponse.receive_str` & :py:meth:`aiohttp.web.WebSocketResponse.receive_bytes` methods to raise new :py:exc:`aiohttp.WSMessageTypeError` exception, instead of generic :py:exc:`TypeError`, when websocket messages of incorrect types are received -- by :user:`ara-25`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`6800`.\n\n\n\n- Made ``TestClient.app`` a ``Generic`` so type checkers will know the correct type (avoiding unneeded ``client.app is not None`` checks) -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8977`.\n\n\n\n- Fixed the keep-alive connection pool to be FIFO instead of LIFO -- by :user:`bdraco`.\n\n  Keep-alive connections are more likely to be reused before they disconnect.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9672`.\n\n\n\n\nFeatures\n--------\n\n- Added ``strategy`` parameter to :meth:`aiohttp.web.StreamResponse.enable_compression`\n  The value of this parameter is passed to the :func:`zlib.compressobj` function, allowing people\n  to use a more sufficient compression algorithm for their data served by :mod:`aiohttp.web`\n  -- by :user:`shootkin`\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`6257`.\n\n\n\n- Added ``server_hostname`` parameter to ``ws_connect``.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`7941`.\n\n\n\n- Exported :py:class:`~aiohttp.ClientWSTimeout` to top-level namespace -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8612`.\n\n\n\n- Added ``secure``/``httponly``/``samesite`` parameters to ``.del_cookie()`` -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8956`.\n\n\n\n- Updated :py:class:`~aiohttp.ClientSession`'s auth logic to include default auth only if the request URL's origin matches _base_url; otherwise, the auth will not be included -- by :user:`MaximZemskov`\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8966`, :issue:`9466`.\n\n\n\n- Added ``proxy`` and ``proxy_auth`` parameters to :py:class:`~aiohttp.ClientSession` -- by :user:`meshya`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9207`.\n\n\n\n- Added ``default_to_multipart`` parameter to ``FormData``.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9335`.\n\n\n\n- Added :py:meth:`~aiohttp.ClientWebSocketResponse.send_frame` and :py:meth:`~aiohttp.web.WebSocketResponse.send_frame` for WebSockets -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9348`.\n\n\n\n- Updated :py:class:`~aiohttp.ClientSession` to support paths in ``base_url`` parameter.\n  ``base_url`` paths must end with a ``/``  -- by :user:`Cycloctane`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9530`.\n\n\n\n- Improved performance of reading WebSocket messages with a Cython implementation -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9543`, :issue:`9554`, :issue:`9556`, :issue:`9558`, :issue:`9636`, :issue:`9649`, :issue:`9781`.\n\n\n\n- Added ``writer_limit`` to the :py:class:`~aiohttp.web.WebSocketResponse` to be able to adjust the limit before the writer forces the buffer to be drained -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9572`.\n\n\n\n- Added an :attr:`~aiohttp.abc.AbstractAccessLogger.enabled` property to :class:`aiohttp.abc.AbstractAccessLogger` to dynamically check if logging is enabled -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9822`.\n\n\n\n\nDeprecations (removal in next major release)\n--------------------------------------------\n\n- Deprecate obsolete `timeout: float` and `receive_timeout: Optional[float]` in :py:meth:`~aiohttp.ClientSession.ws_connect`. Change default websocket receive timeout from `None` to `10.0`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`3945`.\n\n\n\n\nRemovals and backward incompatible breaking changes\n---------------------------------------------------\n\n- Dropped support for Python 3.8 -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8797`.\n\n\n\n- Increased minimum yarl version to 1.17.0 -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8909`, :issue:`9079`, :issue:`9305`, :issue:`9574`.\n\n\n\n- Removed the ``is_ipv6_address`` and ``is_ip4_address`` helpers are they are no longer used -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9344`.\n\n\n\n- Changed ``ClientRequest.connection_key`` to be a `NamedTuple` to improve client performance -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9365`.\n\n\n\n- ``FlowControlDataQueue`` has been replaced with the ``WebSocketDataQueue`` -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9685`.\n\n\n\n- Changed ``ClientRequest.request_info`` to be a `NamedTuple` to improve client performance -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9692`.\n\n\n\n\nPackaging updates and notes for downstreams\n-------------------------------------------\n\n- Switched to using the :mod:`propcache <propcache.api>` package for property caching\n  -- by :user:`bdraco`.\n\n  The :mod:`propcache <propcache.api>` package is derived from the property caching\n  code in :mod:`yarl` and has been broken out to avoid maintaining it for multiple\n  projects.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9394`.\n\n\n\n- Separated ``aiohttp.http_websocket`` into multiple files to make it easier to maintain -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9542`, :issue:`9552`.\n\n\n\n\nContributor-facing changes\n--------------------------\n\n- Changed diagram images generator from ``blockdiag`` to ``GraphViz``.\n  Generating documentation now requires the GraphViz executable to be included in $PATH or sphinx build configuration.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9359`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Added flake8 settings to avoid some forms of implicit concatenation. -- by :user:`booniepepper`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`7731`.\n\n\n\n- Enabled keep-alive support on proxies (which was originally disabled several years ago) -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8920`.\n\n\n\n- Changed web entry point to not listen on TCP when only a Unix path is passed -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9033`.\n\n\n\n- Disabled automatic retries of failed requests in :class:`aiohttp.test_utils.TestClient`'s client session\n  (which could potentially hide errors in tests) -- by :user:`ShubhAgarwal-dev`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9141`.\n\n\n\n- Changed web ``keepalive_timeout`` default to around an hour in order to reduce race conditions on reverse proxies -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9285`.\n\n\n\n- Reduced memory required for stream objects created during the client request lifecycle -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9407`.\n\n\n\n- Improved performance of the internal ``DataQueue`` -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9659`.\n\n\n\n- Improved performance of calling ``receive`` for WebSockets for the most common message types -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9679`.\n\n\n\n- Replace internal helper methods ``method_must_be_empty_body`` and ``status_code_must_be_empty_body`` with simple `set` lookups -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9722`.\n\n\n\n- Improved performance of :py:class:`aiohttp.BaseConnector` when there is no ``limit_per_host`` -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9756`.\n\n\n\n- Improved performance of sending HTTP requests when there is no body -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9757`.\n\n\n\n- Improved performance of the ``WebsocketWriter`` when the protocol is not paused -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9796`.\n\n\n\n- Implemented zero copy writes for ``StreamWriter`` -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9839`.\n\n\n\n\n----\n\n\n3.10.11 (2024-11-13)\n====================\n\nBug fixes\n---------\n\n- Authentication provided by a redirect now takes precedence over provided ``auth`` when making requests with the client -- by :user:`PLPeeters`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9436`.\n\n\n\n- Fixed :py:meth:`WebSocketResponse.close() <aiohttp.web.WebSocketResponse.close>` to discard non-close messages within its timeout window after sending close -- by :user:`lenard-mosys`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9506`.\n\n\n\n- Fixed a deadlock that could occur while attempting to get a new connection slot after a timeout -- by :user:`bdraco`.\n\n  The connector was not cancellation-safe.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9670`, :issue:`9671`.\n\n\n\n- Fixed the WebSocket flow control calculation undercounting with multi-byte data -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9686`.\n\n\n\n- Fixed incorrect parsing of chunk extensions with the pure Python parser -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9851`.\n\n\n\n- Fixed system routes polluting the middleware cache -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9852`.\n\n\n\n\nRemovals and backward incompatible breaking changes\n---------------------------------------------------\n\n- Improved performance of the connector when a connection can be reused -- by :user:`bdraco`.\n\n  If ``BaseConnector.connect`` has been subclassed and replaced with custom logic, the ``ceil_timeout`` must be added.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9600`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved performance of the client request lifecycle when there are no cookies -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9470`.\n\n\n\n- Improved performance of sending client requests when the writer can finish synchronously -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9485`.\n\n\n\n- Improved performance of serializing HTTP headers -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9603`.\n\n\n\n- Passing ``enable_cleanup_closed`` to :py:class:`aiohttp.TCPConnector` is now ignored on Python 3.12.7+ and 3.13.1+ since the underlying bug that caused asyncio to leak SSL connections has been fixed upstream -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9726`, :issue:`9736`.\n\n\n\n----\n\n\n\n\n3.10.10 (2024-10-10)\n====================\n\nBug fixes\n---------\n\n- Fixed error messages from :py:class:`~aiohttp.resolver.AsyncResolver` being swallowed -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9451`, :issue:`9455`.\n\n\n\n\nFeatures\n--------\n\n- Added :exc:`aiohttp.ClientConnectorDNSError` for differentiating DNS resolution errors from other connector errors -- by :user:`mstojcevich`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8455`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Simplified DNS resolution throttling code to reduce chance of race conditions -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9454`.\n\n\n\n\n----\n\n\n3.10.9 (2024-10-04)\n===================\n\nBug fixes\n---------\n\n- Fixed proxy headers being used in the ``ConnectionKey`` hash when a proxy was not being used -- by :user:`bdraco`.\n\n  If default headers are used, they are also used for proxy headers. This could have led to creating connections that were not needed when one was already available.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9368`.\n\n\n\n- Widened the type of the ``trace_request_ctx`` parameter of\n  :meth:`ClientSession.request() <aiohttp.ClientSession.request>` and friends\n  -- by :user:`layday`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9397`.\n\n\n\n\nRemovals and backward incompatible breaking changes\n---------------------------------------------------\n\n- Fixed failure to try next host after single-host connection timeout -- by :user:`brettdh`.\n\n  The default client :class:`aiohttp.ClientTimeout` params has changed to include a ``sock_connect`` timeout of 30 seconds so that this correct behavior happens by default.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`7342`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved performance of resolving hosts with Python 3.12+ -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9342`.\n\n\n\n- Reduced memory required for timer objects created during the client request lifecycle -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9406`.\n\n\n\n\n----\n\n\n3.10.8 (2024-09-28)\n===================\n\nBug fixes\n---------\n\n- Fixed cancellation leaking upwards on timeout -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9326`.\n\n\n\n\n----\n\n\n3.10.7 (2024-09-27)\n===================\n\nBug fixes\n---------\n\n- Fixed assembling the :class:`~yarl.URL` for web requests when the host contains a non-default port or IPv6 address -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9309`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved performance of determining if a URL is absolute -- by :user:`bdraco`.\n\n  The property :attr:`~yarl.URL.absolute` is more performant than the method ``URL.is_absolute()`` and preferred when newer versions of yarl are used.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9171`.\n\n\n\n- Replaced code that can now be handled by ``yarl`` -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9301`.\n\n\n\n\n----\n\n\n3.10.6 (2024-09-24)\n===================\n\nBug fixes\n---------\n\n- Added :exc:`aiohttp.ClientConnectionResetError`. Client code that previously threw :exc:`ConnectionResetError`\n  will now throw this -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9137`.\n\n\n\n- Fixed an unclosed transport ``ResourceWarning`` on web handlers -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8875`.\n\n\n\n- Fixed resolve_host() 'Task was destroyed but is pending' errors -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8967`.\n\n\n\n- Fixed handling of some file-like objects (e.g. ``tarfile.extractfile()``) which raise ``AttributeError`` instead of ``OSError`` when ``fileno`` fails for streaming payload data -- by :user:`ReallyReivax`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`6732`.\n\n\n\n- Fixed web router not matching pre-encoded URLs (requires yarl 1.9.6+) -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8898`, :issue:`9267`.\n\n\n\n- Fixed an error when trying to add a route for multiple methods with a path containing a regex pattern -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8998`.\n\n\n\n- Fixed ``Response.text`` when body is a ``Payload`` -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`6485`.\n\n\n\n- Fixed compressed requests failing when no body was provided -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9108`.\n\n\n\n- Fixed client incorrectly reusing a connection when the previous message had not been fully sent -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8992`.\n\n\n\n- Fixed race condition that could cause server to close connection incorrectly at keepalive timeout -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9140`.\n\n\n\n- Fixed Python parser chunked handling with multiple Transfer-Encoding values -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8823`.\n\n\n\n- Fixed error handling after 100-continue so server sends 500 response instead of disconnecting -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8876`.\n\n\n\n- Stopped adding a default Content-Type header when response has no content -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8858`.\n\n\n\n- Added support for URL credentials with empty (zero-length) username, e.g. ``https://:password@host`` -- by :user:`shuckc`\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`6494`.\n\n\n\n- Stopped logging exceptions from ``web.run_app()`` that would be raised regardless -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`6807`.\n\n\n\n- Implemented binding to IPv6 addresses in the pytest server fixture.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`4650`.\n\n\n\n- Fixed the incorrect use of flags for ``getnameinfo()`` in the Resolver --by :user:`GitNMLee`\n\n  Link-Local IPv6 addresses can now be handled by the Resolver correctly.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9032`.\n\n\n\n- Fixed StreamResponse.prepared to return True after EOF is sent -- by :user:`arthurdarcet`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`5343`.\n\n\n\n- Changed ``make_mocked_request()`` to use empty payload by default -- by :user:`rahulnht`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`7167`.\n\n\n\n- Used more precise type for ``ClientResponseError.headers``, fixing some type errors when using them -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8768`.\n\n\n\n- Changed behavior when returning an invalid response to send a 500 response -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8845`.\n\n\n\n- Fixed response reading from closed session to throw an error immediately instead of timing out -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8878`.\n\n\n\n- Fixed ``CancelledError`` from one cleanup context stopping other contexts from completing -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8908`.\n\n\n\n- Fixed changing scheme/host in ``Response.clone()`` for absolute URLs -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8990`.\n\n\n\n- Fixed ``Site.name`` when host is an empty string -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8929`.\n\n\n\n- Updated Python parser to reject messages after a close message, matching C parser behaviour -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9018`.\n\n\n\n- Fixed creation of ``SSLContext`` inside of :py:class:`aiohttp.TCPConnector` with multiple event loops in different threads -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9029`.\n\n\n\n- Fixed (on Python 3.11+) some edge cases where a task cancellation may get incorrectly suppressed -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9030`.\n\n\n\n- Fixed exception information getting lost on ``HttpProcessingError`` -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9052`.\n\n\n\n- Fixed ``If-None-Match`` not using weak comparison -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9063`.\n\n\n\n- Fixed badly encoded charset crashing when getting response text instead of falling back to charset detector.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9160`.\n\n\n\n- Rejected `\\n` in `reason` values to avoid sending broken HTTP messages -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9167`.\n\n\n\n- Changed :py:meth:`ClientResponse.raise_for_status() <aiohttp.ClientResponse.raise_for_status>` to only release the connection when invoked outside an ``async with`` context -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9239`.\n\n\n\n\nFeatures\n--------\n\n- Improved type on ``params`` to match the underlying type allowed by ``yarl`` -- by :user:`lpetre`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8564`.\n\n\n\n- Declared Python 3.13 supported -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8748`.\n\n\n\n\nRemovals and backward incompatible breaking changes\n---------------------------------------------------\n\n- Improved middleware performance -- by :user:`bdraco`.\n\n  The ``set_current_app`` method was removed from ``UrlMappingMatchInfo`` because it is no longer used, and it was unlikely external caller would ever use it.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9200`.\n\n\n\n- Increased minimum yarl version to 1.12.0 -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9267`.\n\n\n\n\nImproved documentation\n----------------------\n\n- Clarified that ``GracefulExit`` needs to be handled in ``AppRunner`` and ``ServerRunner`` when using ``handle_signals=True``. -- by :user:`Daste745`\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`4414`.\n\n\n\n- Clarified that auth parameter in ClientSession will persist and be included with any request to any origin, even during redirects to different origins.  -- by :user:`MaximZemskov`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`6764`.\n\n\n\n- Clarified which timeout exceptions happen on which timeouts -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8968`.\n\n\n\n- Updated ``ClientSession`` parameters to match current code -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8991`.\n\n\n\n\nPackaging updates and notes for downstreams\n-------------------------------------------\n\n- Fixed ``test_client_session_timeout_zero`` to not require internet access -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9004`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved performance of making requests when there are no auto headers to skip -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8847`.\n\n\n\n- Exported ``aiohttp.TraceRequestHeadersSentParams`` -- by :user:`Hadock-is-ok`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8947`.\n\n\n\n- Avoided tracing overhead in the http writer when there are no active traces -- by user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9031`.\n\n\n\n- Improved performance of reify Cython implementation -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9054`.\n\n\n\n- Use :meth:`URL.extend_query() <yarl.URL.extend_query>` to extend query params (requires yarl 1.11.0+) -- by :user:`bdraco`.\n\n  If yarl is older than 1.11.0, the previous slower hand rolled version will be used.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9068`.\n\n\n\n- Improved performance of checking if a host is an IP Address -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9095`.\n\n\n\n- Significantly improved performance of middlewares -- by :user:`bdraco`.\n\n  The construction of the middleware wrappers is now cached and is built once per handler instead of on every request.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9158`, :issue:`9170`.\n\n\n\n- Improved performance of web requests -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9168`, :issue:`9169`, :issue:`9172`, :issue:`9174`, :issue:`9175`, :issue:`9241`.\n\n\n\n- Improved performance of starting web requests when there is no response prepare hook -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9173`.\n\n\n\n- Significantly improved performance of expiring cookies -- by :user:`bdraco`.\n\n  Expiring cookies has been redesigned to use :mod:`heapq` instead of a linear search, to better scale.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9203`.\n\n\n\n- Significantly sped up filtering cookies -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`9204`.\n\n\n\n\n----\n\n\n3.10.5 (2024-08-19)\n=========================\n\nBug fixes\n---------\n\n- Fixed :meth:`aiohttp.ClientResponse.json()` not setting ``status`` when :exc:`aiohttp.ContentTypeError` is raised -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8742`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved performance of the WebSocket reader -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8736`, :issue:`8747`.\n\n\n\n\n----\n\n\n3.10.4 (2024-08-17)\n===================\n\nBug fixes\n---------\n\n- Fixed decoding base64 chunk in BodyPartReader -- by :user:`hyzyla`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`3867`.\n\n\n\n- Fixed a race closing the server-side WebSocket where the close code would not reach the client -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8680`.\n\n\n\n- Fixed unconsumed exceptions raised by the WebSocket heartbeat -- by :user:`bdraco`.\n\n  If the heartbeat ping raised an exception, it would not be consumed and would be logged as an warning.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8685`.\n\n\n\n- Fixed an edge case in the Python parser when chunk separators happen to align with network chunks -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8720`.\n\n\n\n\nImproved documentation\n----------------------\n\n- Added ``aiohttp-apischema`` to supported libraries -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8700`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved performance of starting request handlers with Python 3.12+ -- by :user:`bdraco`.\n\n  This change is a followup to :issue:`8661` to make the same optimization for Python 3.12+ where the request is connected.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8681`.\n\n\n\n\n----\n\n\n3.10.3 (2024-08-10)\n========================\n\nBug fixes\n---------\n\n- Fixed multipart reading when stream buffer splits the boundary over several read() calls -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8653`.\n\n\n\n- Fixed :py:class:`aiohttp.TCPConnector` doing blocking I/O in the event loop to create the ``SSLContext`` -- by :user:`bdraco`.\n\n  The blocking I/O would only happen once per verify mode. However, it could cause the event loop to block for a long time if the ``SSLContext`` creation is slow, which is more likely during startup when the disk cache is not yet present.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8672`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved performance of :py:meth:`~aiohttp.ClientWebSocketResponse.receive` and :py:meth:`~aiohttp.web.WebSocketResponse.receive` when there is no timeout. -- by :user:`bdraco`.\n\n  The timeout context manager is now avoided when there is no timeout as it accounted for up to 50% of the time spent in the :py:meth:`~aiohttp.ClientWebSocketResponse.receive` and :py:meth:`~aiohttp.web.WebSocketResponse.receive` methods.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8660`.\n\n\n\n- Improved performance of starting request handlers with Python 3.12+ -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8661`.\n\n\n\n- Improved performance of HTTP keep-alive checks -- by :user:`bdraco`.\n\n  Previously, when processing a request for a keep-alive connection, the keep-alive check would happen every second; the check is now rescheduled if it fires too early instead.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8662`.\n\n\n\n- Improved performance of generating random WebSocket mask -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8667`.\n\n\n\n\n----\n\n\n3.10.2 (2024-08-08)\n===================\n\nBug fixes\n---------\n\n- Fixed server checks for circular symbolic links to be compatible with Python 3.13 -- by :user:`steverep`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8565`.\n\n\n\n- Fixed request body not being read when ignoring an Upgrade request -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8597`.\n\n\n\n- Fixed an edge case where shutdown would wait for timeout when the handler was already completed -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8611`.\n\n\n\n- Fixed connecting to ``npipe://``, ``tcp://``, and ``unix://`` urls -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8632`.\n\n\n\n- Fixed WebSocket ping tasks being prematurely garbage collected -- by :user:`bdraco`.\n\n  There was a small risk that WebSocket ping tasks would be prematurely garbage collected because the event loop only holds a weak reference to the task. The garbage collection risk has been fixed by holding a strong reference to the task. Additionally, the task is now scheduled eagerly with Python 3.12+ to increase the chance it can be completed immediately and avoid having to hold any references to the task.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8641`.\n\n\n\n- Fixed incorrectly following symlinks for compressed file variants -- by :user:`steverep`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8652`.\n\n\n\n\nRemovals and backward incompatible breaking changes\n---------------------------------------------------\n\n- Removed ``Request.wait_for_disconnection()``, which was mistakenly added briefly in 3.10.0 -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8636`.\n\n\n\n\nContributor-facing changes\n--------------------------\n\n- Fixed monkey patches for ``Path.stat()`` and ``Path.is_dir()`` for Python 3.13 compatibility -- by :user:`steverep`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8551`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved WebSocket performance when messages are sent or received frequently -- by :user:`bdraco`.\n\n  The WebSocket heartbeat scheduling algorithm was improved to reduce the ``asyncio`` scheduling overhead by decreasing the number of ``asyncio.TimerHandle`` creations and cancellations.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8608`.\n\n\n\n- Minor improvements to various type annotations -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8634`.\n\n\n\n\n----\n\n\n3.10.1 (2024-08-03)\n========================\n\nBug fixes\n---------\n\n- Fixed WebSocket server heartbeat timeout logic to terminate :py:meth:`~aiohttp.ClientWebSocketResponse.receive` and return :py:class:`~aiohttp.ServerTimeoutError` -- by :user:`arcivanov`.\n\n  When a WebSocket pong message was not received, the :py:meth:`~aiohttp.ClientWebSocketResponse.receive` operation did not terminate. This change causes ``_pong_not_received`` to feed the ``reader`` an error message, causing pending :py:meth:`~aiohttp.ClientWebSocketResponse.receive` to terminate and return the error message. The error message contains the exception :py:class:`~aiohttp.ServerTimeoutError`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8540`.\n\n\n\n- Fixed url dispatcher index not matching when a variable is preceded by a fixed string after a slash -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8566`.\n\n\n\n\nRemovals and backward incompatible breaking changes\n---------------------------------------------------\n\n- Creating :py:class:`aiohttp.TCPConnector`, :py:class:`aiohttp.ClientSession`, :py:class:`~aiohttp.resolver.ThreadedResolver` :py:class:`aiohttp.web.Server`, or :py:class:`aiohttp.CookieJar` instances without a running event loop now raises a :exc:`RuntimeError` -- by :user:`asvetlov`.\n\n  Creating these objects without a running event loop was deprecated in :issue:`3372` which was released in version 3.5.0.\n\n  This change first appeared in version 3.10.0 as :issue:`6378`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8555`, :issue:`8583`.\n\n\n\n\n----\n\n\n3.10.0 (2024-07-30)\n========================\n\nBug fixes\n---------\n\n- Fixed server response headers for ``Content-Type`` and ``Content-Encoding`` for\n  static compressed files -- by :user:`steverep`.\n\n  Server will now respond with a ``Content-Type`` appropriate for the compressed\n  file (e.g. ``\"application/gzip\"``), and omit the ``Content-Encoding`` header.\n  Users should expect that most clients will no longer decompress such responses\n  by default.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`4462`.\n\n\n\n- Fixed duplicate cookie expiration calls in the CookieJar implementation\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`7784`.\n\n\n\n- Adjusted ``FileResponse`` to check file existence and access when preparing the response -- by :user:`steverep`.\n\n  The :py:class:`~aiohttp.web.FileResponse` class was modified to respond with\n   403 Forbidden or 404 Not Found as appropriate.  Previously, it would cause a\n   server error if the path did not exist or could not be accessed.  Checks for\n   existence, non-regular files, and permissions were expected to be done in the\n   route handler.  For static routes, this now permits a compressed file to exist\n   without its uncompressed variant and still be served.  In addition, this\n   changes the response status for files without read permission to 403, and for\n   non-regular files from 404 to 403 for consistency.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8182`.\n\n\n\n- Fixed ``AsyncResolver`` to match ``ThreadedResolver`` behavior\n  -- by :user:`bdraco`.\n\n  On system with IPv6 support, the :py:class:`~aiohttp.resolver.AsyncResolver` would not fallback\n  to providing A records when AAAA records were not available.\n  Additionally, unlike the :py:class:`~aiohttp.resolver.ThreadedResolver`, the :py:class:`~aiohttp.resolver.AsyncResolver`\n  did not handle link-local addresses correctly.\n\n  This change makes the behavior consistent with the :py:class:`~aiohttp.resolver.ThreadedResolver`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8270`.\n\n\n\n- Fixed ``ws_connect`` not respecting `receive_timeout`` on WS(S) connection.\n  -- by :user:`arcivanov`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8444`.\n\n\n\n- Removed blocking I/O in the event loop for static resources and refactored\n  exception handling -- by :user:`steverep`.\n\n  File system calls when handling requests for static routes were moved to a\n  separate thread to potentially improve performance. Exception handling\n  was tightened in order to only return 403 Forbidden or 404 Not Found responses\n  for expected scenarios; 500 Internal Server Error would be returned for any\n  unknown errors.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8507`.\n\n\n\n\nFeatures\n--------\n\n- Added a Request.wait_for_disconnection() method, as means of allowing request handlers to be notified of premature client disconnections.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`2492`.\n\n\n\n- Added 5 new exceptions: :py:exc:`~aiohttp.InvalidUrlClientError`, :py:exc:`~aiohttp.RedirectClientError`,\n  :py:exc:`~aiohttp.NonHttpUrlClientError`, :py:exc:`~aiohttp.InvalidUrlRedirectClientError`,\n  :py:exc:`~aiohttp.NonHttpUrlRedirectClientError`\n\n  :py:exc:`~aiohttp.InvalidUrlRedirectClientError`, :py:exc:`~aiohttp.NonHttpUrlRedirectClientError`\n  are raised instead of :py:exc:`ValueError` or :py:exc:`~aiohttp.InvalidURL` when the redirect URL is invalid. Classes\n  :py:exc:`~aiohttp.InvalidUrlClientError`, :py:exc:`~aiohttp.RedirectClientError`,\n  :py:exc:`~aiohttp.NonHttpUrlClientError` are base for them.\n\n  The :py:exc:`~aiohttp.InvalidURL` now exposes a ``description`` property with the text explanation of the error details.\n\n  -- by :user:`setla`, :user:`AraHaan`, and :user:`bdraco`\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`2507`, :issue:`3315`, :issue:`6722`, :issue:`8481`, :issue:`8482`.\n\n\n\n- Added a feature to retry closed connections automatically for idempotent methods. -- by :user:`Dreamsorcerer`\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`7297`.\n\n\n\n- Implemented filter_cookies() with domain-matching and path-matching on the keys, instead of testing every single cookie.\n  This may break existing cookies that have been saved with `CookieJar.save()`. Cookies can be migrated with this script::\n\n      import pickle\n      with file_path.open(\"rb\") as f:\n          cookies = pickle.load(f)\n\n      morsels = [(name, m) for c in cookies.values() for name, m in c.items()]\n      cookies.clear()\n      for name, m in morsels:\n          cookies[(m[\"domain\"], m[\"path\"].rstrip(\"/\"))][name] = m\n\n      with file_path.open(\"wb\") as f:\n          pickle.dump(cookies, f, pickle.HIGHEST_PROTOCOL)\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`7583`, :issue:`8535`.\n\n\n\n- Separated connection and socket timeout errors, from ServerTimeoutError.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`7801`.\n\n\n\n- Implemented happy eyeballs\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`7954`.\n\n\n\n- Added server capability to check for static files with Brotli compression via a ``.br`` extension -- by :user:`steverep`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8062`.\n\n\n\n\nRemovals and backward incompatible breaking changes\n---------------------------------------------------\n\n- The shutdown logic in 3.9 waited on all tasks, which caused issues with some libraries.\n  In 3.10 we've changed this logic to only wait on request handlers. This means that it's\n  important for developers to correctly handle the lifecycle of background tasks using a\n  library such as ``aiojobs``. If an application is using ``handler_cancellation=True`` then\n  it is also a good idea to ensure that any :func:`asyncio.shield` calls are replaced with\n  :func:`aiojobs.aiohttp.shield`.\n\n  Please read the updated documentation on these points: \\\n  https://docs.aiohttp.org/en/stable/web_advanced.html#graceful-shutdown \\\n  https://docs.aiohttp.org/en/stable/web_advanced.html#web-handler-cancellation\n\n  -- by :user:`Dreamsorcerer`\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8495`.\n\n\n\n\nImproved documentation\n----------------------\n\n- Added documentation for ``aiohttp.web.FileResponse``.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`3958`.\n\n\n\n- Improved the docs for the `ssl` params.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8403`.\n\n\n\n\nContributor-facing changes\n--------------------------\n\n- Enabled HTTP parser tests originally intended for 3.9.2 release -- by :user:`pajod`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8088`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved URL handler resolution time by indexing resources in the UrlDispatcher.\n  For applications with a large number of handlers, this should increase performance significantly.\n  -- by :user:`bdraco`\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`7829`.\n\n\n\n- Added `nacl_middleware <https://github.com/CosmicDNA/nacl_middleware>`_ to the list of middlewares in the third party section of the documentation.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8346`.\n\n\n\n- Minor improvements to static typing -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8364`.\n\n\n\n- Added a 3.11-specific overloads to ``ClientSession``  -- by :user:`max-muoto`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8463`.\n\n\n\n- Simplified path checks for ``UrlDispatcher.add_static()`` method -- by :user:`steverep`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8491`.\n\n\n\n- Avoided creating a future on every websocket receive -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8498`.\n\n\n\n- Updated identity checks for all ``WSMsgType`` type compares -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8501`.\n\n\n\n- When using Python 3.12 or later, the writer is no longer scheduled on the event loop if it can finish synchronously. Avoiding event loop scheduling reduces latency and improves performance. -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8510`.\n\n\n\n- Restored :py:class:`~aiohttp.resolver.AsyncResolver` to be the default resolver. -- by :user:`bdraco`.\n\n  :py:class:`~aiohttp.resolver.AsyncResolver` was disabled by default because\n  of IPv6 compatibility issues. These issues have been resolved and\n  :py:class:`~aiohttp.resolver.AsyncResolver` is again now the default resolver.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8522`.\n\n\n\n\n----\n\n\n3.9.5 (2024-04-16)\n==================\n\nBug fixes\n---------\n\n- Fixed \"Unclosed client session\" when initialization of\n  :py:class:`~aiohttp.ClientSession` fails -- by :user:`NewGlad`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8253`.\n\n\n\n- Fixed regression (from :pr:`8280`) with adding ``Content-Disposition`` to the ``form-data``\n  part after appending to writer -- by :user:`Dreamsorcerer`/:user:`Olegt0rr`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8332`.\n\n\n\n- Added default ``Content-Disposition`` in ``multipart/form-data`` responses to avoid broken\n  form-data responses -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8335`.\n\n\n\n\n----\n\n\n3.9.4 (2024-04-11)\n==================\n\nBug fixes\n---------\n\n- The asynchronous internals now set the underlying causes\n  when assigning exceptions to the future objects\n  -- by :user:`webknjaz`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8089`.\n\n\n\n- Treated values of ``Accept-Encoding`` header as case-insensitive when checking\n  for gzip files -- by :user:`steverep`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8104`.\n\n\n\n- Improved the DNS resolution performance on cache hit -- by :user:`bdraco`.\n\n  This is achieved by avoiding an :mod:`asyncio` task creation in this case.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8163`.\n\n\n- Changed the type annotations to allow ``dict`` on :meth:`aiohttp.MultipartWriter.append`,\n  :meth:`aiohttp.MultipartWriter.append_json` and\n  :meth:`aiohttp.MultipartWriter.append_form` -- by :user:`cakemanny`\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`7741`.\n\n\n\n- Ensure websocket transport is closed when client does not close it\n  -- by :user:`bdraco`.\n\n  The transport could remain open if the client did not close it. This\n  change ensures the transport is closed when the client does not close\n  it.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8200`.\n\n\n\n- Leave websocket transport open if receive times out or is cancelled\n  -- by :user:`bdraco`.\n\n  This restores the behavior prior to the change in #7978.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8251`.\n\n\n\n- Fixed content not being read when an upgrade request was not supported with the pure Python implementation.\n  -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8252`.\n\n\n\n- Fixed a race condition with incoming connections during server shutdown -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8271`.\n\n\n\n- Fixed ``multipart/form-data`` compliance with :rfc:`7578` -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8280`.\n\n\n\n- Fixed blocking I/O in the event loop while processing files in a POST request\n  -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8283`.\n\n\n\n- Escaped filenames in static view -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8317`.\n\n\n\n- Fixed the pure python parser to mark a connection as closing when a\n  response has no length -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8320`.\n\n\n\n\nFeatures\n--------\n\n- Upgraded *llhttp* to 9.2.1, and started rejecting obsolete line folding\n  in Python parser to match -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8146`, :issue:`8292`.\n\n\n\n\nDeprecations (removal in next major release)\n--------------------------------------------\n\n- Deprecated ``content_transfer_encoding`` parameter in :py:meth:`FormData.add_field()\n  <aiohttp.FormData.add_field>` -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8280`.\n\n\n\n\nImproved documentation\n----------------------\n\n- Added a note about canceling tasks to avoid delaying server shutdown -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8267`.\n\n\n\n\nContributor-facing changes\n--------------------------\n\n- The pull request template is now asking the contributors to\n  answer a question about the long-term maintenance challenges\n  they envision as a result of merging their patches\n  -- by :user:`webknjaz`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8099`.\n\n\n\n- Updated CI and documentation to use NPM clean install and upgrade\n  node to version 18 -- by :user:`steverep`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8116`.\n\n\n\n- A pytest fixture ``hello_txt`` was introduced to aid\n  static file serving tests in\n  :file:`test_web_sendfile_functional.py`. It dynamically\n  provisions ``hello.txt`` file variants shared across the\n  tests in the module.\n\n  -- by :user:`steverep`\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8136`.\n\n\n\n\nPackaging updates and notes for downstreams\n-------------------------------------------\n\n- Added an ``internal`` pytest marker for tests which should be skipped\n  by packagers (use ``-m 'not internal'`` to disable them) -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8299`.\n\n\n\n\n----\n\n\n3.9.3 (2024-01-29)\n==================\n\nBug fixes\n---------\n\n- Fixed backwards compatibility breakage (in 3.9.2) of ``ssl`` parameter when set outside\n  of ``ClientSession`` (e.g. directly in ``TCPConnector``) -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8097`, :issue:`8098`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Improved test suite handling of paths and temp files to consistently use pathlib and pytest fixtures.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`3957`.\n\n\n\n\n----\n\n\n3.9.2 (2024-01-28)\n==================\n\nBug fixes\n---------\n\n- Fixed server-side websocket connection leak.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`7978`.\n\n\n\n- Fixed ``web.FileResponse`` doing blocking I/O in the event loop.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8012`.\n\n\n\n- Fixed double compress when compression enabled and compressed file exists in server file responses.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8014`.\n\n\n\n- Added runtime type check for ``ClientSession`` ``timeout`` parameter.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8021`.\n\n\n\n- Fixed an unhandled exception in the Python HTTP parser on header lines starting with a colon -- by :user:`pajod`.\n\n  Invalid request lines with anything but a dot between the HTTP major and minor version are now rejected.\n  Invalid header field names containing question mark or slash are now rejected.\n  Such requests are incompatible with :rfc:`9110#section-5.6.2` and are not known to be of any legitimate use.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8074`.\n\n\n\n- Improved validation of paths for static resources requests to the server -- by :user:`bdraco`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8079`.\n\n\n\n\nFeatures\n--------\n\n- Added support for passing :py:data:`True` to ``ssl`` parameter in ``ClientSession`` while\n  deprecating :py:data:`None` -- by :user:`xiangyan99`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`7698`.\n\n\n\nBreaking changes\n----------------\n\n- Fixed an unhandled exception in the Python HTTP parser on header lines starting with a colon -- by :user:`pajod`.\n\n  Invalid request lines with anything but a dot between the HTTP major and minor version are now rejected.\n  Invalid header field names containing question mark or slash are now rejected.\n  Such requests are incompatible with :rfc:`9110#section-5.6.2` and are not known to be of any legitimate use.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8074`.\n\n\n\n\nImproved documentation\n----------------------\n\n- Fixed examples of ``fallback_charset_resolver`` function in the :doc:`client_advanced` document. -- by :user:`henry0312`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`7995`.\n\n\n\n- The Sphinx setup was updated to avoid showing the empty\n  changelog draft section in the tagged release documentation\n  builds on Read The Docs -- by :user:`webknjaz`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8067`.\n\n\n\n\nPackaging updates and notes for downstreams\n-------------------------------------------\n\n- The changelog categorization was made clearer. The\n  contributors can now mark their fragment files more\n  accurately -- by :user:`webknjaz`.\n\n  The new category tags are:\n\n      * ``bugfix``\n\n      * ``feature``\n\n      * ``deprecation``\n\n      * ``breaking`` (previously, ``removal``)\n\n      * ``doc``\n\n      * ``packaging``\n\n      * ``contrib``\n\n      * ``misc``\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8066`.\n\n\n\n\nContributor-facing changes\n--------------------------\n\n- Updated :ref:`contributing/Tests coverage <aiohttp-contributing>` section to show how we use ``codecov`` -- by :user:`Dreamsorcerer`.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`7916`.\n\n\n\n- The changelog categorization was made clearer. The\n  contributors can now mark their fragment files more\n  accurately -- by :user:`webknjaz`.\n\n  The new category tags are:\n\n      * ``bugfix``\n\n      * ``feature``\n\n      * ``deprecation``\n\n      * ``breaking`` (previously, ``removal``)\n\n      * ``doc``\n\n      * ``packaging``\n\n      * ``contrib``\n\n      * ``misc``\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`8066`.\n\n\n\n\nMiscellaneous internal changes\n------------------------------\n\n- Replaced all ``tmpdir`` fixtures with ``tmp_path`` in test suite.\n\n\n  *Related issues and pull requests on GitHub:*\n  :issue:`3551`.\n\n\n\n\n----\n\n\n3.9.1 (2023-11-26)\n==================\n\nBugfixes\n--------\n\n- Fixed importing aiohttp under PyPy on Windows.\n\n  `#7848 <https://github.com/aio-libs/aiohttp/issues/7848>`_\n\n- Fixed async concurrency safety in websocket compressor.\n\n  `#7865 <https://github.com/aio-libs/aiohttp/issues/7865>`_\n\n- Fixed ``ClientResponse.close()`` releasing the connection instead of closing.\n\n  `#7869 <https://github.com/aio-libs/aiohttp/issues/7869>`_\n\n- Fixed a regression where connection may get closed during upgrade. -- by :user:`Dreamsorcerer`\n\n  `#7879 <https://github.com/aio-libs/aiohttp/issues/7879>`_\n\n- Fixed messages being reported as upgraded without an Upgrade header in Python parser. -- by :user:`Dreamsorcerer`\n\n  `#7895 <https://github.com/aio-libs/aiohttp/issues/7895>`_\n\n\n\n----\n\n\n3.9.0 (2023-11-18)\n==================\n\nFeatures\n--------\n\n- Introduced ``AppKey`` for static typing support of ``Application`` storage.\n  See https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config\n\n  `#5864 <https://github.com/aio-libs/aiohttp/issues/5864>`_\n\n- Added a graceful shutdown period which allows pending tasks to complete before the application's cleanup is called.\n  The period can be adjusted with the ``shutdown_timeout`` parameter. -- by :user:`Dreamsorcerer`.\n  See https://docs.aiohttp.org/en/latest/web_advanced.html#graceful-shutdown\n\n  `#7188 <https://github.com/aio-libs/aiohttp/issues/7188>`_\n\n- Added `handler_cancellation <https://docs.aiohttp.org/en/stable/web_advanced.html#web-handler-cancellation>`_ parameter to cancel web handler on client disconnection. -- by :user:`mosquito`\n  This (optionally) reintroduces a feature removed in a previous release.\n  Recommended for those looking for an extra level of protection against denial-of-service attacks.\n\n  `#7056 <https://github.com/aio-libs/aiohttp/issues/7056>`_\n\n- Added support for setting response header parameters ``max_line_size`` and ``max_field_size``.\n\n  `#2304 <https://github.com/aio-libs/aiohttp/issues/2304>`_\n\n- Added ``auto_decompress`` parameter to ``ClientSession.request`` to override ``ClientSession._auto_decompress``. -- by :user:`Daste745`\n\n  `#3751 <https://github.com/aio-libs/aiohttp/issues/3751>`_\n\n- Changed ``raise_for_status`` to allow a coroutine.\n\n  `#3892 <https://github.com/aio-libs/aiohttp/issues/3892>`_\n\n- Added client brotli compression support (optional with runtime check).\n\n  `#5219 <https://github.com/aio-libs/aiohttp/issues/5219>`_\n\n- Added ``client_max_size`` to ``BaseRequest.clone()`` to allow overriding the request body size. -- :user:`anesabml`.\n\n  `#5704 <https://github.com/aio-libs/aiohttp/issues/5704>`_\n\n- Added a middleware type alias ``aiohttp.typedefs.Middleware``.\n\n  `#5898 <https://github.com/aio-libs/aiohttp/issues/5898>`_\n\n- Exported ``HTTPMove`` which can be used to catch any redirection request\n  that has a location -- :user:`dreamsorcerer`.\n\n  `#6594 <https://github.com/aio-libs/aiohttp/issues/6594>`_\n\n- Changed the ``path`` parameter in ``web.run_app()`` to accept a ``pathlib.Path`` object.\n\n  `#6839 <https://github.com/aio-libs/aiohttp/issues/6839>`_\n\n- Performance: Skipped filtering ``CookieJar`` when the jar is empty or all cookies have expired.\n\n  `#7819 <https://github.com/aio-libs/aiohttp/issues/7819>`_\n\n- Performance: Only check origin if insecure scheme and there are origins to treat as secure, in ``CookieJar.filter_cookies()``.\n\n  `#7821 <https://github.com/aio-libs/aiohttp/issues/7821>`_\n\n- Performance: Used timestamp instead of ``datetime`` to achieve faster cookie expiration in ``CookieJar``.\n\n  `#7824 <https://github.com/aio-libs/aiohttp/issues/7824>`_\n\n- Added support for passing a custom server name parameter to HTTPS connection.\n\n  `#7114 <https://github.com/aio-libs/aiohttp/issues/7114>`_\n\n- Added support for using Basic Auth credentials from :file:`.netrc` file when making HTTP requests with the\n  :py:class:`~aiohttp.ClientSession` ``trust_env`` argument is set to ``True``. -- by :user:`yuvipanda`.\n\n  `#7131 <https://github.com/aio-libs/aiohttp/issues/7131>`_\n\n- Turned access log into no-op when the logger is disabled.\n\n  `#7240 <https://github.com/aio-libs/aiohttp/issues/7240>`_\n\n- Added typing information to ``RawResponseMessage``. -- by :user:`Gobot1234`\n\n  `#7365 <https://github.com/aio-libs/aiohttp/issues/7365>`_\n\n- Removed ``async-timeout`` for Python 3.11+ (replaced with ``asyncio.timeout()`` on newer releases).\n\n  `#7502 <https://github.com/aio-libs/aiohttp/issues/7502>`_\n\n- Added support for ``brotlicffi`` as an alternative to ``brotli`` (fixing Brotli support on PyPy).\n\n  `#7611 <https://github.com/aio-libs/aiohttp/issues/7611>`_\n\n- Added ``WebSocketResponse.get_extra_info()`` to access a protocol transport's extra info.\n\n  `#7078 <https://github.com/aio-libs/aiohttp/issues/7078>`_\n\n- Allow ``link`` argument to be set to None/empty in HTTP 451 exception.\n\n  `#7689 <https://github.com/aio-libs/aiohttp/issues/7689>`_\n\n\n\nBugfixes\n--------\n\n- Implemented stripping the trailing dots from fully-qualified domain names in ``Host`` headers and TLS context when acting as an HTTP client.\n  This allows the client to connect to URLs with FQDN host name like ``https://example.com./``.\n  -- by :user:`martin-sucha`.\n\n  `#3636 <https://github.com/aio-libs/aiohttp/issues/3636>`_\n\n- Fixed client timeout not working when incoming data is always available without waiting. -- by :user:`Dreamsorcerer`.\n\n  `#5854 <https://github.com/aio-libs/aiohttp/issues/5854>`_\n\n- Fixed ``readuntil`` to work with a delimiter of more than one character.\n\n  `#6701 <https://github.com/aio-libs/aiohttp/issues/6701>`_\n\n- Added ``__repr__`` to ``EmptyStreamReader`` to avoid ``AttributeError``.\n\n  `#6916 <https://github.com/aio-libs/aiohttp/issues/6916>`_\n\n- Fixed bug when using ``TCPConnector`` with ``ttl_dns_cache=0``.\n\n  `#7014 <https://github.com/aio-libs/aiohttp/issues/7014>`_\n\n- Fixed response returned from expect handler being thrown away. -- by :user:`Dreamsorcerer`\n\n  `#7025 <https://github.com/aio-libs/aiohttp/issues/7025>`_\n\n- Avoided raising ``UnicodeDecodeError`` in multipart and in HTTP headers parsing.\n\n  `#7044 <https://github.com/aio-libs/aiohttp/issues/7044>`_\n\n- Changed ``sock_read`` timeout to start after writing has finished, avoiding read timeouts caused by an unfinished write. -- by :user:`dtrifiro`\n\n  `#7149 <https://github.com/aio-libs/aiohttp/issues/7149>`_\n\n- Fixed missing query in tracing method URLs when using ``yarl`` 1.9+.\n\n  `#7259 <https://github.com/aio-libs/aiohttp/issues/7259>`_\n\n- Changed max 32-bit timestamp to an aware datetime object, for consistency with the non-32-bit one, and to avoid a ``DeprecationWarning`` on Python 3.12.\n\n  `#7302 <https://github.com/aio-libs/aiohttp/issues/7302>`_\n\n- Fixed ``EmptyStreamReader.iter_chunks()`` never ending. -- by :user:`mind1m`\n\n  `#7616 <https://github.com/aio-libs/aiohttp/issues/7616>`_\n\n- Fixed a rare ``RuntimeError: await wasn't used with future`` exception. -- by :user:`stalkerg`\n\n  `#7785 <https://github.com/aio-libs/aiohttp/issues/7785>`_\n\n- Fixed issue with insufficient HTTP method and version validation.\n\n  `#7700 <https://github.com/aio-libs/aiohttp/issues/7700>`_\n\n- Added check to validate that absolute URIs have schemes.\n\n  `#7712 <https://github.com/aio-libs/aiohttp/issues/7712>`_\n\n- Fixed unhandled exception when Python HTTP parser encounters unpaired Unicode surrogates.\n\n  `#7715 <https://github.com/aio-libs/aiohttp/issues/7715>`_\n\n- Updated parser to disallow invalid characters in header field names and stop accepting LF as a request line separator.\n\n  `#7719 <https://github.com/aio-libs/aiohttp/issues/7719>`_\n\n- Fixed Python HTTP parser not treating 204/304/1xx as an empty body.\n\n  `#7755 <https://github.com/aio-libs/aiohttp/issues/7755>`_\n\n- Ensure empty body response for 1xx/204/304 per RFC 9112 sec 6.3.\n\n  `#7756 <https://github.com/aio-libs/aiohttp/issues/7756>`_\n\n- Fixed an issue when a client request is closed before completing a chunked payload. -- by :user:`Dreamsorcerer`\n\n  `#7764 <https://github.com/aio-libs/aiohttp/issues/7764>`_\n\n- Edge Case Handling for ResponseParser for missing reason value.\n\n  `#7776 <https://github.com/aio-libs/aiohttp/issues/7776>`_\n\n- Fixed ``ClientWebSocketResponse.close_code`` being erroneously set to ``None`` when there are concurrent async tasks receiving data and closing the connection.\n\n  `#7306 <https://github.com/aio-libs/aiohttp/issues/7306>`_\n\n- Added HTTP method validation.\n\n  `#6533 <https://github.com/aio-libs/aiohttp/issues/6533>`_\n\n- Fixed arbitrary sequence types being allowed to inject values via version parameter. -- by :user:`Dreamsorcerer`\n\n  `#7835 <https://github.com/aio-libs/aiohttp/issues/7835>`_\n\n- Performance: Fixed increase in latency with small messages from websocket compression changes.\n\n  `#7797 <https://github.com/aio-libs/aiohttp/issues/7797>`_\n\n\n\nImproved Documentation\n----------------------\n\n- Fixed the `ClientResponse.release`'s type in the doc. Changed from `comethod` to `method`.\n\n  `#5836 <https://github.com/aio-libs/aiohttp/issues/5836>`_\n\n- Added information on behavior of base_url parameter in `ClientSession`.\n\n  `#6647 <https://github.com/aio-libs/aiohttp/issues/6647>`_\n\n- Fixed `ClientResponseError` docs.\n\n  `#6700 <https://github.com/aio-libs/aiohttp/issues/6700>`_\n\n- Updated Redis code examples to follow the latest API.\n\n  `#6907 <https://github.com/aio-libs/aiohttp/issues/6907>`_\n\n- Added a note about possibly needing to update headers when using ``on_response_prepare``. -- by :user:`Dreamsorcerer`\n\n  `#7283 <https://github.com/aio-libs/aiohttp/issues/7283>`_\n\n- Completed ``trust_env`` parameter description to honor ``wss_proxy``, ``ws_proxy`` or ``no_proxy`` env.\n\n  `#7325 <https://github.com/aio-libs/aiohttp/issues/7325>`_\n\n- Expanded SSL documentation with more examples (e.g. how to use certifi). -- by :user:`Dreamsorcerer`\n\n  `#7334 <https://github.com/aio-libs/aiohttp/issues/7334>`_\n\n- Fix, update, and improve client exceptions documentation.\n\n  `#7733 <https://github.com/aio-libs/aiohttp/issues/7733>`_\n\n\n\nDeprecations and Removals\n-------------------------\n\n- Added ``shutdown_timeout`` parameter to ``BaseRunner``, while\n  deprecating ``shutdown_timeout`` parameter from ``BaseSite``. -- by :user:`Dreamsorcerer`\n\n  `#7718 <https://github.com/aio-libs/aiohttp/issues/7718>`_\n\n- Dropped Python 3.6 support.\n\n  `#6378 <https://github.com/aio-libs/aiohttp/issues/6378>`_\n\n- Dropped Python 3.7 support. -- by :user:`Dreamsorcerer`\n\n  `#7336 <https://github.com/aio-libs/aiohttp/issues/7336>`_\n\n- Removed support for abandoned ``tokio`` event loop. -- by :user:`Dreamsorcerer`\n\n  `#7281 <https://github.com/aio-libs/aiohttp/issues/7281>`_\n\n\n\nMisc\n----\n\n- Made ``print`` argument in ``run_app()`` optional.\n\n  `#3690 <https://github.com/aio-libs/aiohttp/issues/3690>`_\n\n- Improved performance of ``ceil_timeout`` in some cases.\n\n  `#6316 <https://github.com/aio-libs/aiohttp/issues/6316>`_\n\n- Changed importing Gunicorn to happen on-demand, decreasing import time by ~53%. -- :user:`Dreamsorcerer`\n\n  `#6591 <https://github.com/aio-libs/aiohttp/issues/6591>`_\n\n- Improved import time by replacing ``http.server`` with ``http.HTTPStatus``.\n\n  `#6903 <https://github.com/aio-libs/aiohttp/issues/6903>`_\n\n- Fixed annotation of ``ssl`` parameter to disallow ``True``. -- by :user:`Dreamsorcerer`.\n\n  `#7335 <https://github.com/aio-libs/aiohttp/issues/7335>`_\n\n\n----\n\n\n3.8.6 (2023-10-07)\n==================\n\nSecurity bugfixes\n-----------------\n\n- Upgraded the vendored copy of llhttp_ to v9.1.3 -- by :user:`Dreamsorcerer`\n\n  Thanks to :user:`kenballus` for reporting this, see\n  https://github.com/aio-libs/aiohttp/security/advisories/GHSA-pjjw-qhg8-p2p9.\n\n  .. _llhttp: https://llhttp.org\n\n  `#7647 <https://github.com/aio-libs/aiohttp/issues/7647>`_\n\n- Updated Python parser to comply with RFCs 9110/9112 -- by :user:`Dreamorcerer`\n\n  Thanks to :user:`kenballus` for reporting this, see\n  https://github.com/aio-libs/aiohttp/security/advisories/GHSA-gfw2-4jvh-wgfg.\n\n  `#7663 <https://github.com/aio-libs/aiohttp/issues/7663>`_\n\n\nDeprecation\n-----------\n\n- Added ``fallback_charset_resolver`` parameter in ``ClientSession`` to allow a user-supplied\n  character set detection function.\n\n  Character set detection will no longer be included in 3.9 as a default. If this feature is needed,\n  please use `fallback_charset_resolver <https://docs.aiohttp.org/en/stable/client_advanced.html#character-set-detection>`_.\n\n  `#7561 <https://github.com/aio-libs/aiohttp/issues/7561>`_\n\n\nFeatures\n--------\n\n- Enabled lenient response parsing for more flexible parsing in the client\n  (this should resolve some regressions when dealing with badly formatted HTTP responses). -- by :user:`Dreamsorcerer`\n\n  `#7490 <https://github.com/aio-libs/aiohttp/issues/7490>`_\n\n\n\nBugfixes\n--------\n\n- Fixed ``PermissionError`` when ``.netrc`` is unreadable due to permissions.\n\n  `#7237 <https://github.com/aio-libs/aiohttp/issues/7237>`_\n\n- Fixed output of parsing errors pointing to a ``\\n``. -- by :user:`Dreamsorcerer`\n\n  `#7468 <https://github.com/aio-libs/aiohttp/issues/7468>`_\n\n- Fixed ``GunicornWebWorker`` max_requests_jitter not working.\n\n  `#7518 <https://github.com/aio-libs/aiohttp/issues/7518>`_\n\n- Fixed sorting in ``filter_cookies`` to use cookie with longest path. -- by :user:`marq24`.\n\n  `#7577 <https://github.com/aio-libs/aiohttp/issues/7577>`_\n\n- Fixed display of ``BadStatusLine`` messages from llhttp_. -- by :user:`Dreamsorcerer`\n\n  `#7651 <https://github.com/aio-libs/aiohttp/issues/7651>`_\n\n\n----\n\n\n3.8.5 (2023-07-19)\n==================\n\nSecurity bugfixes\n-----------------\n\n- Upgraded the vendored copy of llhttp_ to v8.1.1 -- by :user:`webknjaz`\n  and :user:`Dreamsorcerer`.\n\n  Thanks to :user:`sethmlarson` for reporting this and providing us with\n  comprehensive reproducer, workarounds and fixing details! For more\n  information, see\n  https://github.com/aio-libs/aiohttp/security/advisories/GHSA-45c4-8wx5-qw6w.\n\n  .. _llhttp: https://llhttp.org\n\n  `#7346 <https://github.com/aio-libs/aiohttp/issues/7346>`_\n\n\nFeatures\n--------\n\n- Added information to C parser exceptions to show which character caused the error. -- by :user:`Dreamsorcerer`\n\n  `#7366 <https://github.com/aio-libs/aiohttp/issues/7366>`_\n\n\nBugfixes\n--------\n\n- Fixed a transport is :data:`None` error -- by :user:`Dreamsorcerer`.\n\n  `#3355 <https://github.com/aio-libs/aiohttp/issues/3355>`_\n\n\n----\n\n\n3.8.4 (2023-02-12)\n==================\n\nBugfixes\n--------\n\n- Fixed incorrectly overwriting cookies with the same name and domain, but different path.\n  `#6638 <https://github.com/aio-libs/aiohttp/issues/6638>`_\n- Fixed ``ConnectionResetError`` not being raised after client disconnection in SSL environments.\n  `#7180 <https://github.com/aio-libs/aiohttp/issues/7180>`_\n\n\n----\n\n\n3.8.3 (2022-09-21)\n==================\n\n.. attention::\n\n   This is the last :doc:`aiohttp <index>` release tested under\n   Python 3.6. The 3.9 stream is dropping it from the CI and the\n   distribution package metadata.\n\nBugfixes\n--------\n\n- Increased the upper boundary of the :doc:`multidict:index` dependency\n  to allow for the version 6 -- by :user:`hugovk`.\n\n  It used to be limited below version 7 in :doc:`aiohttp <index>` v3.8.1 but\n  was lowered in v3.8.2 via :pr:`6550` and never brought back, causing\n  problems with dependency pins when upgrading. :doc:`aiohttp <index>` v3.8.3\n  fixes that by recovering the original boundary of ``< 7``.\n  `#6950 <https://github.com/aio-libs/aiohttp/issues/6950>`_\n\n\n----\n\n\n3.8.2 (2022-09-20, subsequently yanked on 2022-09-21)\n=====================================================\n\nBugfixes\n--------\n\n- Support registering OPTIONS HTTP method handlers via RouteTableDef.\n  `#4663 <https://github.com/aio-libs/aiohttp/issues/4663>`_\n- Started supporting ``authority-form`` and ``absolute-form`` URLs on the server-side.\n  `#6227 <https://github.com/aio-libs/aiohttp/issues/6227>`_\n- Fix Python 3.11 alpha incompatibilities by using Cython 0.29.25\n  `#6396 <https://github.com/aio-libs/aiohttp/issues/6396>`_\n- Remove a deprecated usage of pytest.warns(None)\n  `#6663 <https://github.com/aio-libs/aiohttp/issues/6663>`_\n- Fix regression where ``asyncio.CancelledError`` occurs on client disconnection.\n  `#6719 <https://github.com/aio-libs/aiohttp/issues/6719>`_\n- Export :py:class:`~aiohttp.web.PrefixedSubAppResource` under\n  :py:mod:`aiohttp.web` -- by :user:`Dreamsorcerer`.\n\n  This fixes a regression introduced by :pr:`3469`.\n  `#6889 <https://github.com/aio-libs/aiohttp/issues/6889>`_\n- Dropped the :class:`object` type possibility from\n  the :py:attr:`aiohttp.ClientSession.timeout`\n  property return type declaration.\n  `#6917 <https://github.com/aio-libs/aiohttp/issues/6917>`_,\n  `#6923 <https://github.com/aio-libs/aiohttp/issues/6923>`_\n\n\nImproved Documentation\n----------------------\n\n- Added clarification on configuring the app object with settings such as a db connection.\n  `#4137 <https://github.com/aio-libs/aiohttp/issues/4137>`_\n- Edited the web.run_app declaration.\n  `#6401 <https://github.com/aio-libs/aiohttp/issues/6401>`_\n- Dropped the :class:`object` type possibility from\n  the :py:attr:`aiohttp.ClientSession.timeout`\n  property return type declaration.\n  `#6917 <https://github.com/aio-libs/aiohttp/issues/6917>`_,\n  `#6923 <https://github.com/aio-libs/aiohttp/issues/6923>`_\n\n\nDeprecations and Removals\n-------------------------\n\n- Drop Python 3.5 support, aiohttp works on 3.6+ now.\n  `#4046 <https://github.com/aio-libs/aiohttp/issues/4046>`_\n\n\nMisc\n----\n\n- `#6369 <https://github.com/aio-libs/aiohttp/issues/6369>`_, `#6399 <https://github.com/aio-libs/aiohttp/issues/6399>`_, `#6550 <https://github.com/aio-libs/aiohttp/issues/6550>`_, `#6708 <https://github.com/aio-libs/aiohttp/issues/6708>`_, `#6757 <https://github.com/aio-libs/aiohttp/issues/6757>`_, `#6857 <https://github.com/aio-libs/aiohttp/issues/6857>`_, `#6872 <https://github.com/aio-libs/aiohttp/issues/6872>`_\n\n\n----\n\n\n3.8.1 (2021-11-14)\n==================\n\nBugfixes\n--------\n\n- Fix the error in handling the return value of `getaddrinfo`.\n  `getaddrinfo` will return an `(int, bytes)` tuple, if CPython could not handle the address family.\n  It will cause an index out of range error in aiohttp. For example, if user compile CPython with\n  `--disable-ipv6` option, but his system enable the ipv6.\n  `#5901 <https://github.com/aio-libs/aiohttp/issues/5901>`_\n- Do not install \"examples\" as a top-level package.\n  `#6189 <https://github.com/aio-libs/aiohttp/issues/6189>`_\n- Restored ability to connect IPv6-only host.\n  `#6195 <https://github.com/aio-libs/aiohttp/issues/6195>`_\n- Remove ``Signal`` from ``__all__``, replace ``aiohttp.Signal`` with ``aiosignal.Signal`` in docs\n  `#6201 <https://github.com/aio-libs/aiohttp/issues/6201>`_\n- Made chunked encoding HTTP header check stricter.\n  `#6305 <https://github.com/aio-libs/aiohttp/issues/6305>`_\n\n\nImproved Documentation\n----------------------\n\n- update quick starter demo codes.\n  `#6240 <https://github.com/aio-libs/aiohttp/issues/6240>`_\n- Added an explanation of how tiny timeouts affect performance to the client reference document.\n  `#6274 <https://github.com/aio-libs/aiohttp/issues/6274>`_\n- Add flake8-docstrings to flake8 configuration, enable subset of checks.\n  `#6276 <https://github.com/aio-libs/aiohttp/issues/6276>`_\n- Added information on running complex applications with additional tasks/processes -- :user:`Dreamsorcerer`.\n  `#6278 <https://github.com/aio-libs/aiohttp/issues/6278>`_\n\n\nMisc\n----\n\n- `#6205 <https://github.com/aio-libs/aiohttp/issues/6205>`_\n\n\n----\n\n\n3.8.0 (2021-10-31)\n==================\n\nFeatures\n--------\n\n- Added a ``GunicornWebWorker`` feature for extending the aiohttp server configuration by allowing the 'wsgi' coroutine to return ``web.AppRunner`` object.\n  `#2988 <https://github.com/aio-libs/aiohttp/issues/2988>`_\n- Switch from ``http-parser`` to ``llhttp``\n  `#3561 <https://github.com/aio-libs/aiohttp/issues/3561>`_\n- Use Brotli instead of brotlipy\n  `#3803 <https://github.com/aio-libs/aiohttp/issues/3803>`_\n- Disable implicit switch-back to pure python mode. The build fails loudly if aiohttp\n  cannot be compiled with C Accelerators.  Use AIOHTTP_NO_EXTENSIONS=1 to explicitly\n  disable C Extensions complication and switch to Pure-Python mode.  Note that Pure-Python\n  mode is significantly slower than compiled one.\n  `#3828 <https://github.com/aio-libs/aiohttp/issues/3828>`_\n- Make access log use local time with timezone\n  `#3853 <https://github.com/aio-libs/aiohttp/issues/3853>`_\n- Implemented ``readuntil`` in ``StreamResponse``\n  `#4054 <https://github.com/aio-libs/aiohttp/issues/4054>`_\n- FileResponse now supports ETag.\n  `#4594 <https://github.com/aio-libs/aiohttp/issues/4594>`_\n- Add a request handler type alias ``aiohttp.typedefs.Handler``.\n  `#4686 <https://github.com/aio-libs/aiohttp/issues/4686>`_\n- ``AioHTTPTestCase`` is more async friendly now.\n\n  For people who use unittest and are used to use :py:exc:`~unittest.TestCase`\n  it will be easier to write new test cases like the sync version of the :py:exc:`~unittest.TestCase` class,\n  without using the decorator `@unittest_run_loop`, just `async def test_*`.\n  The only difference is that for the people using python3.7 and below a new dependency is needed, it is ``asynctestcase``.\n  `#4700 <https://github.com/aio-libs/aiohttp/issues/4700>`_\n- Add validation of HTTP header keys and values to prevent header injection.\n  `#4818 <https://github.com/aio-libs/aiohttp/issues/4818>`_\n- Add predicate to ``AbstractCookieJar.clear``.\n  Add ``AbstractCookieJar.clear_domain`` to clean all domain and subdomains cookies only.\n  `#4942 <https://github.com/aio-libs/aiohttp/issues/4942>`_\n- Add keepalive_timeout parameter to web.run_app.\n  `#5094 <https://github.com/aio-libs/aiohttp/issues/5094>`_\n- Tracing for client sent headers\n  `#5105 <https://github.com/aio-libs/aiohttp/issues/5105>`_\n- Make type hints for http parser stricter\n  `#5267 <https://github.com/aio-libs/aiohttp/issues/5267>`_\n- Add final declarations for constants.\n  `#5275 <https://github.com/aio-libs/aiohttp/issues/5275>`_\n- Switch to external frozenlist and aiosignal libraries.\n  `#5293 <https://github.com/aio-libs/aiohttp/issues/5293>`_\n- Don't send secure cookies by insecure transports.\n\n  By default, the transport is secure if https or wss scheme is used.\n  Use `CookieJar(treat_as_secure_origin=\"http://127.0.0.1\")` to override the default security checker.\n  `#5571 <https://github.com/aio-libs/aiohttp/issues/5571>`_\n- Always create a new event loop in ``aiohttp.web.run_app()``.\n  This adds better compatibility with ``asyncio.run()`` or if trying to run multiple apps in sequence.\n  `#5572 <https://github.com/aio-libs/aiohttp/issues/5572>`_\n- Add ``aiohttp.pytest_plugin.AiohttpClient`` for static typing of pytest plugin.\n  `#5585 <https://github.com/aio-libs/aiohttp/issues/5585>`_\n- Added a ``socket_factory`` argument to ``BaseTestServer``.\n  `#5844 <https://github.com/aio-libs/aiohttp/issues/5844>`_\n- Add compression strategy parameter to enable_compression method.\n  `#5909 <https://github.com/aio-libs/aiohttp/issues/5909>`_\n- Added support for Python 3.10 to Github Actions CI/CD workflows and fix the related deprecation warnings -- :user:`Hanaasagi`.\n  `#5927 <https://github.com/aio-libs/aiohttp/issues/5927>`_\n- Switched ``chardet`` to ``charset-normalizer`` for guessing the HTTP payload body encoding -- :user:`Ousret`.\n  `#5930 <https://github.com/aio-libs/aiohttp/issues/5930>`_\n- Added optional auto_decompress argument for HttpRequestParser\n  `#5957 <https://github.com/aio-libs/aiohttp/issues/5957>`_\n- Added support for HTTPS proxies to the extent CPython's\n  :py:mod:`asyncio` supports it -- by :user:`bmbouter`,\n  :user:`jborean93` and :user:`webknjaz`.\n  `#5992 <https://github.com/aio-libs/aiohttp/issues/5992>`_\n- Added ``base_url`` parameter to the initializer of :class:`~aiohttp.ClientSession`.\n  `#6013 <https://github.com/aio-libs/aiohttp/issues/6013>`_\n- Add Trove classifier and create binary wheels for 3.10. -- :user:`hugovk`.\n  `#6079 <https://github.com/aio-libs/aiohttp/issues/6079>`_\n- Started shipping platform-specific wheels with the ``musl`` tag targeting typical Alpine Linux runtimes — :user:`asvetlov`.\n  `#6139 <https://github.com/aio-libs/aiohttp/issues/6139>`_\n- Started shipping platform-specific arm64 wheels for Apple Silicon — :user:`asvetlov`.\n  `#6139 <https://github.com/aio-libs/aiohttp/issues/6139>`_\n\n\nBugfixes\n--------\n\n- Modify _drain_helper() to handle concurrent `await resp.write(...)` or `ws.send_json(...)` calls without race-condition.\n  `#2934 <https://github.com/aio-libs/aiohttp/issues/2934>`_\n- Started using `MultiLoopChildWatcher` when it's available under POSIX while setting up the test I/O loop.\n  `#3450 <https://github.com/aio-libs/aiohttp/issues/3450>`_\n- Only encode content-disposition filename parameter using percent-encoding.\n  Other parameters are encoded to quoted-string or RFC2231 extended parameter\n  value.\n  `#4012 <https://github.com/aio-libs/aiohttp/issues/4012>`_\n- Fixed HTTP client requests to honor ``no_proxy`` environment variables.\n  `#4431 <https://github.com/aio-libs/aiohttp/issues/4431>`_\n- Fix supporting WebSockets proxies configured via environment variables.\n  `#4648 <https://github.com/aio-libs/aiohttp/issues/4648>`_\n- Change return type on URLDispatcher to UrlMappingMatchInfo to improve type annotations.\n  `#4748 <https://github.com/aio-libs/aiohttp/issues/4748>`_\n- Ensure a cleanup context is cleaned up even when an exception occurs during startup.\n  `#4799 <https://github.com/aio-libs/aiohttp/issues/4799>`_\n- Added a new exception type for Unix socket client errors which provides a more useful error message.\n  `#4984 <https://github.com/aio-libs/aiohttp/issues/4984>`_\n- Remove Transfer-Encoding and Content-Type headers for 204 in StreamResponse\n  `#5106 <https://github.com/aio-libs/aiohttp/issues/5106>`_\n- Only depend on typing_extensions for Python <3.8\n  `#5107 <https://github.com/aio-libs/aiohttp/issues/5107>`_\n- Add ABNORMAL_CLOSURE and BAD_GATEWAY to WSCloseCode\n  `#5192 <https://github.com/aio-libs/aiohttp/issues/5192>`_\n- Fix cookies disappearing from HTTPExceptions.\n  `#5233 <https://github.com/aio-libs/aiohttp/issues/5233>`_\n- StaticResource prefixes no longer match URLs with a non-folder prefix. For example ``routes.static('/foo', '/foo')`` no longer matches the URL ``/foobar``. Previously, this would attempt to load the file ``/foo/ar``.\n  `#5250 <https://github.com/aio-libs/aiohttp/issues/5250>`_\n- Acquire the connection before running traces to prevent race condition.\n  `#5259 <https://github.com/aio-libs/aiohttp/issues/5259>`_\n- Add missing slots to ```_RequestContextManager`` and ``_WSRequestContextManager``\n  `#5329 <https://github.com/aio-libs/aiohttp/issues/5329>`_\n- Ensure sending a zero byte file does not throw an exception (round 2)\n  `#5380 <https://github.com/aio-libs/aiohttp/issues/5380>`_\n- Set \"text/plain\" when data is an empty string in client requests.\n  `#5392 <https://github.com/aio-libs/aiohttp/issues/5392>`_\n- Stop automatically releasing the ``ClientResponse`` object on calls to the ``ok`` property for the failed requests.\n  `#5403 <https://github.com/aio-libs/aiohttp/issues/5403>`_\n- Include query parameters from `params` keyword argument in tracing `URL`.\n  `#5432 <https://github.com/aio-libs/aiohttp/issues/5432>`_\n- Fix annotations\n  `#5466 <https://github.com/aio-libs/aiohttp/issues/5466>`_\n- Fixed the multipart POST requests processing to always release file\n  descriptors for the ``tempfile.Temporaryfile``-created\n  ``_io.BufferedRandom`` instances of files sent within multipart request\n  bodies via HTTP POST requests -- by :user:`webknjaz`.\n  `#5494 <https://github.com/aio-libs/aiohttp/issues/5494>`_\n- Fix 0 being incorrectly treated as an immediate timeout.\n  `#5527 <https://github.com/aio-libs/aiohttp/issues/5527>`_\n- Fixes failing tests when an environment variable <scheme>_proxy is set.\n  `#5554 <https://github.com/aio-libs/aiohttp/issues/5554>`_\n- Replace deprecated app handler design in ``tests/autobahn/server.py`` with call to ``web.run_app``; replace deprecated ``aiohttp.ws_connect`` calls in ``tests/autobahn/client.py`` with ``aiohttp.ClienSession.ws_connect``.\n  `#5606 <https://github.com/aio-libs/aiohttp/issues/5606>`_\n- Fixed test for ``HTTPUnauthorized`` that access the ``text`` argument. This is not used in any part of the code, so it's removed now.\n  `#5657 <https://github.com/aio-libs/aiohttp/issues/5657>`_\n- Remove incorrect default from docs\n  `#5727 <https://github.com/aio-libs/aiohttp/issues/5727>`_\n- Remove external test dependency to http://httpbin.org\n  `#5840 <https://github.com/aio-libs/aiohttp/issues/5840>`_\n- Don't cancel current task when entering a cancelled timer.\n  `#5853 <https://github.com/aio-libs/aiohttp/issues/5853>`_\n- Added ``params`` keyword argument to ``ClientSession.ws_connect``. --  :user:`hoh`.\n  `#5868 <https://github.com/aio-libs/aiohttp/issues/5868>`_\n- Uses ``asyncio.ThreadedChildWatcher`` under POSIX to allow setting up test loop in non-main thread.\n  `#5877 <https://github.com/aio-libs/aiohttp/issues/5877>`_\n- Fix the error in handling the return value of `getaddrinfo`.\n  `getaddrinfo` will return an `(int, bytes)` tuple, if CPython could not handle the address family.\n  It will cause a index out of range error in aiohttp. For example, if user compile CPython with\n  `--disable-ipv6` option but his system enable the ipv6.\n  `#5901 <https://github.com/aio-libs/aiohttp/issues/5901>`_\n- Removed the deprecated ``loop`` argument from the ``asyncio.sleep``/``gather`` calls\n  `#5905 <https://github.com/aio-libs/aiohttp/issues/5905>`_\n- Return ``None`` from ``request.if_modified_since``, ``request.if_unmodified_since``, ``request.if_range`` and ``response.last_modified`` when corresponding http date headers are invalid.\n  `#5925 <https://github.com/aio-libs/aiohttp/issues/5925>`_\n- Fix resetting `SIGCHLD` signals in Gunicorn aiohttp Worker to fix `subprocesses` that capture output having an incorrect `returncode`.\n  `#6130 <https://github.com/aio-libs/aiohttp/issues/6130>`_\n- Raise ``400: Content-Length can't be present with Transfer-Encoding`` if both ``Content-Length`` and ``Transfer-Encoding`` are sent by peer by both C and Python implementations\n  `#6182 <https://github.com/aio-libs/aiohttp/issues/6182>`_\n\n\nImproved Documentation\n----------------------\n\n- Refactored OpenAPI/Swagger aiohttp addons, added ``aio-openapi``\n  `#5326 <https://github.com/aio-libs/aiohttp/issues/5326>`_\n- Fixed docs on request cookies type, so it matches what is actually used in the code (a\n  read-only dictionary-like object).\n  `#5725 <https://github.com/aio-libs/aiohttp/issues/5725>`_\n- Documented that the HTTP client ``Authorization`` header is removed\n  on redirects to a different host or protocol.\n  `#5850 <https://github.com/aio-libs/aiohttp/issues/5850>`_\n\n\nMisc\n----\n\n- `#3927 <https://github.com/aio-libs/aiohttp/issues/3927>`_, `#4247 <https://github.com/aio-libs/aiohttp/issues/4247>`_, `#4247 <https://github.com/aio-libs/aiohttp/issues/4247>`_, `#5389 <https://github.com/aio-libs/aiohttp/issues/5389>`_, `#5457 <https://github.com/aio-libs/aiohttp/issues/5457>`_, `#5486 <https://github.com/aio-libs/aiohttp/issues/5486>`_, `#5494 <https://github.com/aio-libs/aiohttp/issues/5494>`_, `#5515 <https://github.com/aio-libs/aiohttp/issues/5515>`_, `#5625 <https://github.com/aio-libs/aiohttp/issues/5625>`_, `#5635 <https://github.com/aio-libs/aiohttp/issues/5635>`_, `#5648 <https://github.com/aio-libs/aiohttp/issues/5648>`_, `#5657 <https://github.com/aio-libs/aiohttp/issues/5657>`_, `#5890 <https://github.com/aio-libs/aiohttp/issues/5890>`_, `#5914 <https://github.com/aio-libs/aiohttp/issues/5914>`_, `#5932 <https://github.com/aio-libs/aiohttp/issues/5932>`_, `#6002 <https://github.com/aio-libs/aiohttp/issues/6002>`_, `#6045 <https://github.com/aio-libs/aiohttp/issues/6045>`_, `#6131 <https://github.com/aio-libs/aiohttp/issues/6131>`_, `#6156 <https://github.com/aio-libs/aiohttp/issues/6156>`_, `#6165 <https://github.com/aio-libs/aiohttp/issues/6165>`_, `#6166 <https://github.com/aio-libs/aiohttp/issues/6166>`_\n\n\n----\n\n\n3.7.4.post0 (2021-03-06)\n========================\n\nMisc\n----\n\n- Bumped upper bound of the ``chardet`` runtime dependency\n  to allow their v4.0 version stream.\n  `#5366 <https://github.com/aio-libs/aiohttp/issues/5366>`_\n\n\n----\n\n\n3.7.4 (2021-02-25)\n==================\n\nBugfixes\n--------\n\n- **(SECURITY BUG)** Started preventing open redirects in the\n  ``aiohttp.web.normalize_path_middleware`` middleware. For\n  more details, see\n  https://github.com/aio-libs/aiohttp/security/advisories/GHSA-v6wp-4m6f-gcjg.\n\n  Thanks to `Beast Glatisant <https://github.com/g147>`__ for\n  finding the first instance of this issue and `Jelmer Vernooĳ\n  <https://jelmer.uk/>`__ for reporting and tracking it down\n  in aiohttp.\n  `#5497 <https://github.com/aio-libs/aiohttp/issues/5497>`_\n- Fix interpretation difference of the pure-Python and the Cython-based\n  HTTP parsers construct a ``yarl.URL`` object for HTTP request-target.\n\n  Before this fix, the Python parser would turn the URI's absolute-path\n  for ``//some-path`` into ``/`` while the Cython code preserved it as\n  ``//some-path``. Now, both do the latter.\n  `#5498 <https://github.com/aio-libs/aiohttp/issues/5498>`_\n\n\n----\n\n\n3.7.3 (2020-11-18)\n==================\n\nFeatures\n--------\n\n- Use Brotli instead of brotlipy\n  `#3803 <https://github.com/aio-libs/aiohttp/issues/3803>`_\n- Made exceptions pickleable. Also changed the repr of some exceptions.\n  `#4077 <https://github.com/aio-libs/aiohttp/issues/4077>`_\n\n\nBugfixes\n--------\n\n- Raise a ClientResponseError instead of an AssertionError for a blank\n  HTTP Reason Phrase.\n  `#3532 <https://github.com/aio-libs/aiohttp/issues/3532>`_\n- Fix ``web_middlewares.normalize_path_middleware`` behavior for patch without slash.\n  `#3669 <https://github.com/aio-libs/aiohttp/issues/3669>`_\n- Fix overshadowing of overlapped sub-applications prefixes.\n  `#3701 <https://github.com/aio-libs/aiohttp/issues/3701>`_\n- Make `BaseConnector.close()` a coroutine and wait until the client closes all connections. Drop deprecated \"with Connector():\" syntax.\n  `#3736 <https://github.com/aio-libs/aiohttp/issues/3736>`_\n- Reset the ``sock_read`` timeout each time data is received for a ``aiohttp.client`` response.\n  `#3808 <https://github.com/aio-libs/aiohttp/issues/3808>`_\n- Fixed type annotation for add_view method of UrlDispatcher to accept any subclass of View\n  `#3880 <https://github.com/aio-libs/aiohttp/issues/3880>`_\n- Fixed querying the address families from DNS that the current host supports.\n  `#5156 <https://github.com/aio-libs/aiohttp/issues/5156>`_\n- Change return type of MultipartReader.__aiter__() and BodyPartReader.__aiter__() to AsyncIterator.\n  `#5163 <https://github.com/aio-libs/aiohttp/issues/5163>`_\n- Provide x86 Windows wheels.\n  `#5230 <https://github.com/aio-libs/aiohttp/issues/5230>`_\n\n\nImproved Documentation\n----------------------\n\n- Add documentation for ``aiohttp.web.FileResponse``.\n  `#3958 <https://github.com/aio-libs/aiohttp/issues/3958>`_\n- Removed deprecation warning in tracing example docs\n  `#3964 <https://github.com/aio-libs/aiohttp/issues/3964>`_\n- Fixed wrong \"Usage\" docstring of ``aiohttp.client.request``.\n  `#4603 <https://github.com/aio-libs/aiohttp/issues/4603>`_\n- Add aiohttp-pydantic to third party libraries\n  `#5228 <https://github.com/aio-libs/aiohttp/issues/5228>`_\n\n\nMisc\n----\n\n- `#4102 <https://github.com/aio-libs/aiohttp/issues/4102>`_\n\n\n----\n\n\n3.7.2 (2020-10-27)\n==================\n\nBugfixes\n--------\n\n- Fixed static files handling for loops without ``.sendfile()`` support\n  `#5149 <https://github.com/aio-libs/aiohttp/issues/5149>`_\n\n\n----\n\n\n3.7.1 (2020-10-25)\n==================\n\nBugfixes\n--------\n\n- Fixed a type error caused by the conditional import of `Protocol`.\n  `#5111 <https://github.com/aio-libs/aiohttp/issues/5111>`_\n- Server doesn't send Content-Length for 1xx or 204\n  `#4901 <https://github.com/aio-libs/aiohttp/issues/4901>`_\n- Fix run_app typing\n  `#4957 <https://github.com/aio-libs/aiohttp/issues/4957>`_\n- Always require ``typing_extensions`` library.\n  `#5107 <https://github.com/aio-libs/aiohttp/issues/5107>`_\n- Fix a variable-shadowing bug causing `ThreadedResolver.resolve` to\n  return the resolved IP as the ``hostname`` in each record, which prevented\n  validation of HTTPS connections.\n  `#5110 <https://github.com/aio-libs/aiohttp/issues/5110>`_\n- Added annotations to all public attributes.\n  `#5115 <https://github.com/aio-libs/aiohttp/issues/5115>`_\n- Fix flaky test_when_timeout_smaller_second\n  `#5116 <https://github.com/aio-libs/aiohttp/issues/5116>`_\n- Ensure sending a zero byte file does not throw an exception\n  `#5124 <https://github.com/aio-libs/aiohttp/issues/5124>`_\n- Fix a bug in ``web.run_app()`` about Python version checking on Windows\n  `#5127 <https://github.com/aio-libs/aiohttp/issues/5127>`_\n\n\n----\n\n\n3.7.0 (2020-10-24)\n==================\n\nFeatures\n--------\n\n- Response headers are now prepared prior to running ``on_response_prepare`` hooks, directly before headers are sent to the client.\n  `#1958 <https://github.com/aio-libs/aiohttp/issues/1958>`_\n- Add a ``quote_cookie`` option to ``CookieJar``, a way to skip quotation wrapping of cookies containing special characters.\n  `#2571 <https://github.com/aio-libs/aiohttp/issues/2571>`_\n- Call ``AccessLogger.log`` with the current exception available from ``sys.exc_info()``.\n  `#3557 <https://github.com/aio-libs/aiohttp/issues/3557>`_\n- `web.UrlDispatcher.add_routes` and `web.Application.add_routes` return a list\n  of registered `AbstractRoute` instances. `AbstractRouteDef.register` (and all\n  subclasses) return a list of registered resources registered resource.\n  `#3866 <https://github.com/aio-libs/aiohttp/issues/3866>`_\n- Added properties of default ClientSession params to ClientSession class so it is available for introspection\n  `#3882 <https://github.com/aio-libs/aiohttp/issues/3882>`_\n- Don't cancel web handler on peer disconnection, raise `OSError` on reading/writing instead.\n  `#4080 <https://github.com/aio-libs/aiohttp/issues/4080>`_\n- Implement BaseRequest.get_extra_info() to access a protocol transports' extra info.\n  `#4189 <https://github.com/aio-libs/aiohttp/issues/4189>`_\n- Added `ClientSession.timeout` property.\n  `#4191 <https://github.com/aio-libs/aiohttp/issues/4191>`_\n- allow use of SameSite in cookies.\n  `#4224 <https://github.com/aio-libs/aiohttp/issues/4224>`_\n- Use ``loop.sendfile()`` instead of custom implementation if available.\n  `#4269 <https://github.com/aio-libs/aiohttp/issues/4269>`_\n- Apply SO_REUSEADDR to test server's socket.\n  `#4393 <https://github.com/aio-libs/aiohttp/issues/4393>`_\n- Use .raw_host instead of slower .host in client API\n  `#4402 <https://github.com/aio-libs/aiohttp/issues/4402>`_\n- Allow configuring the buffer size of input stream by passing ``read_bufsize`` argument.\n  `#4453 <https://github.com/aio-libs/aiohttp/issues/4453>`_\n- Pass tests on Python 3.8 for Windows.\n  `#4513 <https://github.com/aio-libs/aiohttp/issues/4513>`_\n- Add `method` and `url` attributes to `TraceRequestChunkSentParams` and `TraceResponseChunkReceivedParams`.\n  `#4674 <https://github.com/aio-libs/aiohttp/issues/4674>`_\n- Add ClientResponse.ok property for checking status code under 400.\n  `#4711 <https://github.com/aio-libs/aiohttp/issues/4711>`_\n- Don't ceil timeouts that are smaller than 5 seconds.\n  `#4850 <https://github.com/aio-libs/aiohttp/issues/4850>`_\n- TCPSite now listens by default on all interfaces instead of just IPv4 when `None` is passed in as the host.\n  `#4894 <https://github.com/aio-libs/aiohttp/issues/4894>`_\n- Bump ``http_parser`` to 2.9.4\n  `#5070 <https://github.com/aio-libs/aiohttp/issues/5070>`_\n\n\nBugfixes\n--------\n\n- Fix keepalive connections not being closed in time\n  `#3296 <https://github.com/aio-libs/aiohttp/issues/3296>`_\n- Fix failed websocket handshake leaving connection hanging.\n  `#3380 <https://github.com/aio-libs/aiohttp/issues/3380>`_\n- Fix tasks cancellation order on exit. The run_app task needs to be cancelled first for cleanup hooks to run with all tasks intact.\n  `#3805 <https://github.com/aio-libs/aiohttp/issues/3805>`_\n- Don't start heartbeat until _writer is set\n  `#4062 <https://github.com/aio-libs/aiohttp/issues/4062>`_\n- Fix handling of multipart file uploads without a content type.\n  `#4089 <https://github.com/aio-libs/aiohttp/issues/4089>`_\n- Preserve view handler function attributes across middlewares\n  `#4174 <https://github.com/aio-libs/aiohttp/issues/4174>`_\n- Fix the string representation of ``ServerDisconnectedError``.\n  `#4175 <https://github.com/aio-libs/aiohttp/issues/4175>`_\n- Raising RuntimeError when trying to get encoding from not read body\n  `#4214 <https://github.com/aio-libs/aiohttp/issues/4214>`_\n- Remove warning messages from noop.\n  `#4282 <https://github.com/aio-libs/aiohttp/issues/4282>`_\n- Raise ClientPayloadError if FormData re-processed.\n  `#4345 <https://github.com/aio-libs/aiohttp/issues/4345>`_\n- Fix a warning about unfinished task in ``web_protocol.py``\n  `#4408 <https://github.com/aio-libs/aiohttp/issues/4408>`_\n- Fixed 'deflate' compression. According to RFC 2616 now.\n  `#4506 <https://github.com/aio-libs/aiohttp/issues/4506>`_\n- Fixed OverflowError on platforms with 32-bit time_t\n  `#4515 <https://github.com/aio-libs/aiohttp/issues/4515>`_\n- Fixed request.body_exists returns wrong value for methods without body.\n  `#4528 <https://github.com/aio-libs/aiohttp/issues/4528>`_\n- Fix connecting to link-local IPv6 addresses.\n  `#4554 <https://github.com/aio-libs/aiohttp/issues/4554>`_\n- Fix a problem with connection waiters that are never awaited.\n  `#4562 <https://github.com/aio-libs/aiohttp/issues/4562>`_\n- Always make sure transport is not closing before reuse a connection.\n\n  Reuse a protocol based on keepalive in headers is unreliable.\n  For example, uWSGI will not support keepalive even it serves a\n  HTTP 1.1 request, except explicitly configure uWSGI with a\n  ``--http-keepalive`` option.\n\n  Servers designed like uWSGI could cause aiohttp intermittently\n  raise a ConnectionResetException when the protocol poll runs\n  out and some protocol is reused.\n  `#4587 <https://github.com/aio-libs/aiohttp/issues/4587>`_\n- Handle the last CRLF correctly even if it is received via separate TCP segment.\n  `#4630 <https://github.com/aio-libs/aiohttp/issues/4630>`_\n- Fix the register_resource function to validate route name before splitting it so that route name can include python keywords.\n  `#4691 <https://github.com/aio-libs/aiohttp/issues/4691>`_\n- Improve typing annotations for ``web.Request``, ``aiohttp.ClientResponse`` and\n  ``multipart`` module.\n  `#4736 <https://github.com/aio-libs/aiohttp/issues/4736>`_\n- Fix resolver task is not awaited when connector is cancelled\n  `#4795 <https://github.com/aio-libs/aiohttp/issues/4795>`_\n- Fix a bug \"Aiohttp doesn't return any error on invalid request methods\"\n  `#4798 <https://github.com/aio-libs/aiohttp/issues/4798>`_\n- Fix HEAD requests for static content.\n  `#4809 <https://github.com/aio-libs/aiohttp/issues/4809>`_\n- Fix incorrect size calculation for memoryview\n  `#4890 <https://github.com/aio-libs/aiohttp/issues/4890>`_\n- Add HTTPMove to _all__.\n  `#4897 <https://github.com/aio-libs/aiohttp/issues/4897>`_\n- Fixed the type annotations in the ``tracing`` module.\n  `#4912 <https://github.com/aio-libs/aiohttp/issues/4912>`_\n- Fix typing for multipart ``__aiter__``.\n  `#4931 <https://github.com/aio-libs/aiohttp/issues/4931>`_\n- Fix for race condition on connections in BaseConnector that leads to exceeding the connection limit.\n  `#4936 <https://github.com/aio-libs/aiohttp/issues/4936>`_\n- Add forced UTF-8 encoding for ``application/rdap+json`` responses.\n  `#4938 <https://github.com/aio-libs/aiohttp/issues/4938>`_\n- Fix inconsistency between Python and C http request parsers in parsing pct-encoded URL.\n  `#4972 <https://github.com/aio-libs/aiohttp/issues/4972>`_\n- Fix connection closing issue in HEAD request.\n  `#5012 <https://github.com/aio-libs/aiohttp/issues/5012>`_\n- Fix type hint on BaseRunner.addresses (from ``List[str]`` to ``List[Any]``)\n  `#5086 <https://github.com/aio-libs/aiohttp/issues/5086>`_\n- Make `web.run_app()` more responsive to Ctrl+C on Windows for Python < 3.8. It slightly\n  increases CPU load as a side effect.\n  `#5098 <https://github.com/aio-libs/aiohttp/issues/5098>`_\n\n\nImproved Documentation\n----------------------\n\n- Fix example code in client quick-start\n  `#3376 <https://github.com/aio-libs/aiohttp/issues/3376>`_\n- Updated the docs so there is no contradiction in ``ttl_dns_cache`` default value\n  `#3512 <https://github.com/aio-libs/aiohttp/issues/3512>`_\n- Add 'Deploy with SSL' to docs.\n  `#4201 <https://github.com/aio-libs/aiohttp/issues/4201>`_\n- Change typing of the secure argument on StreamResponse.set_cookie from ``Optional[str]`` to ``Optional[bool]``\n  `#4204 <https://github.com/aio-libs/aiohttp/issues/4204>`_\n- Changes ``ttl_dns_cache`` type from int to Optional[int].\n  `#4270 <https://github.com/aio-libs/aiohttp/issues/4270>`_\n- Simplify README hello word example and add a documentation page for people coming from requests.\n  `#4272 <https://github.com/aio-libs/aiohttp/issues/4272>`_\n- Improve some code examples in the documentation involving websockets and starting a simple HTTP site with an AppRunner.\n  `#4285 <https://github.com/aio-libs/aiohttp/issues/4285>`_\n- Fix typo in code example in Multipart docs\n  `#4312 <https://github.com/aio-libs/aiohttp/issues/4312>`_\n- Fix code example in Multipart section.\n  `#4314 <https://github.com/aio-libs/aiohttp/issues/4314>`_\n- Update contributing guide so new contributors read the most recent version of that guide. Update command used to create test coverage reporting.\n  `#4810 <https://github.com/aio-libs/aiohttp/issues/4810>`_\n- Spelling: Change \"canonize\" to \"canonicalize\".\n  `#4986 <https://github.com/aio-libs/aiohttp/issues/4986>`_\n- Add ``aiohttp-sse-client`` library to third party usage list.\n  `#5084 <https://github.com/aio-libs/aiohttp/issues/5084>`_\n\n\nMisc\n----\n\n- `#2856 <https://github.com/aio-libs/aiohttp/issues/2856>`_, `#4218 <https://github.com/aio-libs/aiohttp/issues/4218>`_, `#4250 <https://github.com/aio-libs/aiohttp/issues/4250>`_\n\n\n----\n\n\n3.6.3 (2020-10-12)\n==================\n\nBugfixes\n--------\n\n- Pin yarl to ``<1.6.0`` to avoid buggy behavior that will be fixed by the next aiohttp\n  release.\n\n3.6.2 (2019-10-09)\n==================\n\nFeatures\n--------\n\n- Made exceptions pickleable. Also changed the repr of some exceptions.\n  `#4077 <https://github.com/aio-libs/aiohttp/issues/4077>`_\n- Use ``Iterable`` type hint instead of ``Sequence`` for ``Application`` *middleware*\n  parameter.  `#4125 <https://github.com/aio-libs/aiohttp/issues/4125>`_\n\n\nBugfixes\n--------\n\n- Reset the ``sock_read`` timeout each time data is received for a\n  ``aiohttp.ClientResponse``.  `#3808\n  <https://github.com/aio-libs/aiohttp/issues/3808>`_\n- Fix handling of expired cookies so they are not stored in CookieJar.\n  `#4063 <https://github.com/aio-libs/aiohttp/issues/4063>`_\n- Fix misleading message in the string representation of ``ClientConnectorError``;\n  ``self.ssl == None`` means default SSL context, not SSL disabled `#4097\n  <https://github.com/aio-libs/aiohttp/issues/4097>`_\n- Don't clobber HTTP status when using FileResponse.\n  `#4106 <https://github.com/aio-libs/aiohttp/issues/4106>`_\n\n\nImproved Documentation\n----------------------\n\n- Added minimal required logging configuration to logging documentation.\n  `#2469 <https://github.com/aio-libs/aiohttp/issues/2469>`_\n- Update docs to reflect proxy support.\n  `#4100 <https://github.com/aio-libs/aiohttp/issues/4100>`_\n- Fix typo in code example in testing docs.\n  `#4108 <https://github.com/aio-libs/aiohttp/issues/4108>`_\n\n\nMisc\n----\n\n- `#4102 <https://github.com/aio-libs/aiohttp/issues/4102>`_\n\n\n----\n\n\n3.6.1 (2019-09-19)\n==================\n\nFeatures\n--------\n\n- Compatibility with Python 3.8.\n  `#4056 <https://github.com/aio-libs/aiohttp/issues/4056>`_\n\n\nBugfixes\n--------\n\n- correct some exception string format\n  `#4068 <https://github.com/aio-libs/aiohttp/issues/4068>`_\n- Emit a warning when ``ssl.OP_NO_COMPRESSION`` is\n  unavailable because the runtime is built against\n  an outdated OpenSSL.\n  `#4052 <https://github.com/aio-libs/aiohttp/issues/4052>`_\n- Update multidict requirement to >= 4.5\n  `#4057 <https://github.com/aio-libs/aiohttp/issues/4057>`_\n\n\nImproved Documentation\n----------------------\n\n- Provide pytest-aiohttp namespace for pytest fixtures in docs.\n  `#3723 <https://github.com/aio-libs/aiohttp/issues/3723>`_\n\n\n----\n\n\n3.6.0 (2019-09-06)\n==================\n\nFeatures\n--------\n\n- Add support for Named Pipes (Site and Connector) under Windows. This feature requires\n  Proactor event loop to work.  `#3629\n  <https://github.com/aio-libs/aiohttp/issues/3629>`_\n- Removed ``Transfer-Encoding: chunked`` header from websocket responses to be\n  compatible with more http proxy servers.  `#3798\n  <https://github.com/aio-libs/aiohttp/issues/3798>`_\n- Accept non-GET request for starting websocket handshake on server side.\n  `#3980 <https://github.com/aio-libs/aiohttp/issues/3980>`_\n\n\nBugfixes\n--------\n\n- Raise a ClientResponseError instead of an AssertionError for a blank\n  HTTP Reason Phrase.\n  `#3532 <https://github.com/aio-libs/aiohttp/issues/3532>`_\n- Fix an issue where cookies would sometimes not be set during a redirect.\n  `#3576 <https://github.com/aio-libs/aiohttp/issues/3576>`_\n- Change normalize_path_middleware to use '308 Permanent Redirect' instead of 301.\n\n  This behavior should prevent clients from being unable to use PUT/POST\n  methods on endpoints that are redirected because of a trailing slash.\n  `#3579 <https://github.com/aio-libs/aiohttp/issues/3579>`_\n- Drop the processed task from ``all_tasks()`` list early. It prevents logging about a\n  task with unhandled exception when the server is used in conjunction with\n  ``asyncio.run()``.  `#3587 <https://github.com/aio-libs/aiohttp/issues/3587>`_\n- ``Signal`` type annotation changed from ``Signal[Callable[['TraceConfig'],\n  Awaitable[None]]]`` to ``Signal[Callable[ClientSession, SimpleNamespace, ...]``.\n  `#3595 <https://github.com/aio-libs/aiohttp/issues/3595>`_\n- Use sanitized URL as Location header in redirects\n  `#3614 <https://github.com/aio-libs/aiohttp/issues/3614>`_\n- Improve typing annotations for multipart.py along with changes required\n  by mypy in files that references multipart.py.\n  `#3621 <https://github.com/aio-libs/aiohttp/issues/3621>`_\n- Close session created inside ``aiohttp.request`` when unhandled exception occurs\n  `#3628 <https://github.com/aio-libs/aiohttp/issues/3628>`_\n- Cleanup per-chunk data in generic data read. Memory leak fixed.\n  `#3631 <https://github.com/aio-libs/aiohttp/issues/3631>`_\n- Use correct type for add_view and family\n  `#3633 <https://github.com/aio-libs/aiohttp/issues/3633>`_\n- Fix _keepalive field in __slots__ of ``RequestHandler``.\n  `#3644 <https://github.com/aio-libs/aiohttp/issues/3644>`_\n- Properly handle ConnectionResetError, to silence the \"Cannot write to closing\n  transport\" exception when clients disconnect uncleanly.\n  `#3648 <https://github.com/aio-libs/aiohttp/issues/3648>`_\n- Suppress pytest warnings due to ``test_utils`` classes\n  `#3660 <https://github.com/aio-libs/aiohttp/issues/3660>`_\n- Fix overshadowing of overlapped sub-application prefixes.\n  `#3701 <https://github.com/aio-libs/aiohttp/issues/3701>`_\n- Fixed return type annotation for WSMessage.json()\n  `#3720 <https://github.com/aio-libs/aiohttp/issues/3720>`_\n- Properly expose TooManyRedirects publicly as documented.\n  `#3818 <https://github.com/aio-libs/aiohttp/issues/3818>`_\n- Fix missing brackets for IPv6 in proxy CONNECT request\n  `#3841 <https://github.com/aio-libs/aiohttp/issues/3841>`_\n- Make the signature of ``aiohttp.test_utils.TestClient.request`` match\n  ``asyncio.ClientSession.request`` according to the docs `#3852\n  <https://github.com/aio-libs/aiohttp/issues/3852>`_\n- Use correct style for re-exported imports, makes mypy ``--strict`` mode happy.\n  `#3868 <https://github.com/aio-libs/aiohttp/issues/3868>`_\n- Fixed type annotation for add_view method of UrlDispatcher to accept any subclass of\n  View `#3880 <https://github.com/aio-libs/aiohttp/issues/3880>`_\n- Made cython HTTP parser set Reason-Phrase of the response to an empty string if it is\n  missing.  `#3906 <https://github.com/aio-libs/aiohttp/issues/3906>`_\n- Add URL to the string representation of ClientResponseError.\n  `#3959 <https://github.com/aio-libs/aiohttp/issues/3959>`_\n- Accept ``istr`` keys in ``LooseHeaders`` type hints.\n  `#3976 <https://github.com/aio-libs/aiohttp/issues/3976>`_\n- Fixed race conditions in _resolve_host caching and throttling when tracing is enabled.\n  `#4013 <https://github.com/aio-libs/aiohttp/issues/4013>`_\n- For URLs like \"unix://localhost/...\" set Host HTTP header to \"localhost\" instead of\n  \"localhost:None\".  `#4039 <https://github.com/aio-libs/aiohttp/issues/4039>`_\n\n\nImproved Documentation\n----------------------\n\n- Modify documentation for Background Tasks to remove deprecated usage of event loop.\n  `#3526 <https://github.com/aio-libs/aiohttp/issues/3526>`_\n- use ``if __name__ == '__main__':`` in server examples.\n  `#3775 <https://github.com/aio-libs/aiohttp/issues/3775>`_\n- Update documentation reference to the default access logger.\n  `#3783 <https://github.com/aio-libs/aiohttp/issues/3783>`_\n- Improve documentation for ``web.BaseRequest.path`` and ``web.BaseRequest.raw_path``.\n  `#3791 <https://github.com/aio-libs/aiohttp/issues/3791>`_\n- Removed deprecation warning in tracing example docs\n  `#3964 <https://github.com/aio-libs/aiohttp/issues/3964>`_\n\n\n----\n\n\n3.5.4 (2019-01-12)\n==================\n\nBugfixes\n--------\n\n- Fix stream ``.read()`` / ``.readany()`` / ``.iter_any()`` which used to return a\n  partial content only in case of compressed content\n  `#3525 <https://github.com/aio-libs/aiohttp/issues/3525>`_\n\n\n3.5.3 (2019-01-10)\n==================\n\nBugfixes\n--------\n\n- Fix type stubs for ``aiohttp.web.run_app(access_log=True)`` and fix edge case of\n  ``access_log=True`` and the event loop being in debug mode.  `#3504\n  <https://github.com/aio-libs/aiohttp/issues/3504>`_\n- Fix ``aiohttp.ClientTimeout`` type annotations to accept ``None`` for fields\n  `#3511 <https://github.com/aio-libs/aiohttp/issues/3511>`_\n- Send custom per-request cookies even if session jar is empty\n  `#3515 <https://github.com/aio-libs/aiohttp/issues/3515>`_\n- Restore Linux binary wheels publishing on PyPI\n\n----\n\n\n3.5.2 (2019-01-08)\n==================\n\nFeatures\n--------\n\n- ``FileResponse`` from ``web_fileresponse.py`` uses a ``ThreadPoolExecutor`` to work\n  with files asynchronously.  I/O based payloads from ``payload.py`` uses a\n  ``ThreadPoolExecutor`` to work with I/O objects asynchronously.  `#3313\n  <https://github.com/aio-libs/aiohttp/issues/3313>`_\n- Internal Server Errors in plain text if the browser does not support HTML.\n  `#3483 <https://github.com/aio-libs/aiohttp/issues/3483>`_\n\n\nBugfixes\n--------\n\n- Preserve MultipartWriter parts headers on write.  Refactor the way how\n  ``Payload.headers`` are handled. Payload instances now always have headers and\n  Content-Type defined.  Fix Payload Content-Disposition header reset after initial\n  creation.  `#3035 <https://github.com/aio-libs/aiohttp/issues/3035>`_\n- Log suppressed exceptions in ``GunicornWebWorker``.\n  `#3464 <https://github.com/aio-libs/aiohttp/issues/3464>`_\n- Remove wildcard imports.\n  `#3468 <https://github.com/aio-libs/aiohttp/issues/3468>`_\n- Use the same task for app initialization and web server handling in gunicorn workers.\n  It allows to use Python3.7 context vars smoothly.\n  `#3471 <https://github.com/aio-libs/aiohttp/issues/3471>`_\n- Fix handling of chunked+gzipped response when first chunk does not give uncompressed\n  data `#3477 <https://github.com/aio-libs/aiohttp/issues/3477>`_\n- Replace ``collections.MutableMapping`` with ``collections.abc.MutableMapping`` to\n  avoid a deprecation warning.  `#3480\n  <https://github.com/aio-libs/aiohttp/issues/3480>`_\n- ``Payload.size`` type annotation changed from ``Optional[float]`` to\n  ``Optional[int]``.  `#3484 <https://github.com/aio-libs/aiohttp/issues/3484>`_\n- Ignore done tasks when cancels pending activities on ``web.run_app`` finalization.\n  `#3497 <https://github.com/aio-libs/aiohttp/issues/3497>`_\n\n\nImproved Documentation\n----------------------\n\n- Add documentation for ``aiohttp.web.HTTPException``.\n  `#3490 <https://github.com/aio-libs/aiohttp/issues/3490>`_\n\n\nMisc\n----\n\n- `#3487 <https://github.com/aio-libs/aiohttp/issues/3487>`_\n\n\n----\n\n\n3.5.1 (2018-12-24)\n====================\n\n- Fix a regression about ``ClientSession._requote_redirect_url`` modification in debug\n  mode.\n\n3.5.0 (2018-12-22)\n====================\n\nFeatures\n--------\n\n- The library type annotations are checked in strict mode now.\n- Add support for setting cookies for individual request (`#2387\n  <https://github.com/aio-libs/aiohttp/pull/2387>`_)\n- Application.add_domain implementation (`#2809\n  <https://github.com/aio-libs/aiohttp/pull/2809>`_)\n- The default ``app`` in the request returned by ``test_utils.make_mocked_request`` can\n  now have objects assigned to it and retrieved using the ``[]`` operator. (`#3174\n  <https://github.com/aio-libs/aiohttp/pull/3174>`_)\n- Make ``request.url`` accessible when transport is closed. (`#3177\n  <https://github.com/aio-libs/aiohttp/pull/3177>`_)\n- Add ``zlib_executor_size`` argument to ``Response`` constructor to allow compression\n  to run in a background executor to avoid blocking the main thread and potentially\n  triggering health check failures. (`#3205\n  <https://github.com/aio-libs/aiohttp/pull/3205>`_)\n- Enable users to set ``ClientTimeout`` in ``aiohttp.request`` (`#3213\n  <https://github.com/aio-libs/aiohttp/pull/3213>`_)\n- Don't raise a warning if ``NETRC`` environment variable is not set and ``~/.netrc``\n  file doesn't exist. (`#3267 <https://github.com/aio-libs/aiohttp/pull/3267>`_)\n- Add default logging handler to web.run_app If the ``Application.debug``` flag is set\n  and the default logger ``aiohttp.access`` is used, access logs will now be output\n  using a *stderr* ``StreamHandler`` if no handlers are attached. Furthermore, if the\n  default logger has no log level set, the log level will be set to ``DEBUG``. (`#3324\n  <https://github.com/aio-libs/aiohttp/pull/3324>`_)\n- Add method argument to ``session.ws_connect()``.  Sometimes server API requires a\n  different HTTP method for WebSocket connection establishment.  For example, ``Docker\n  exec`` needs POST. (`#3378 <https://github.com/aio-libs/aiohttp/pull/3378>`_)\n- Create a task per request handling. (`#3406\n  <https://github.com/aio-libs/aiohttp/pull/3406>`_)\n\n\nBugfixes\n--------\n\n- Enable passing ``access_log_class`` via ``handler_args`` (`#3158\n  <https://github.com/aio-libs/aiohttp/pull/3158>`_)\n- Return empty bytes with end-of-chunk marker in empty stream reader. (`#3186\n  <https://github.com/aio-libs/aiohttp/pull/3186>`_)\n- Accept ``CIMultiDictProxy`` instances for ``headers`` argument in ``web.Response``\n  constructor. (`#3207 <https://github.com/aio-libs/aiohttp/pull/3207>`_)\n- Don't uppercase HTTP method in parser (`#3233\n  <https://github.com/aio-libs/aiohttp/pull/3233>`_)\n- Make method match regexp RFC-7230 compliant (`#3235\n  <https://github.com/aio-libs/aiohttp/pull/3235>`_)\n- Add ``app.pre_frozen`` state to properly handle startup signals in\n  sub-applications. (`#3237 <https://github.com/aio-libs/aiohttp/pull/3237>`_)\n- Enhanced parsing and validation of helpers.BasicAuth.decode. (`#3239\n  <https://github.com/aio-libs/aiohttp/pull/3239>`_)\n- Change imports from collections module in preparation for 3.8. (`#3258\n  <https://github.com/aio-libs/aiohttp/pull/3258>`_)\n- Ensure Host header is added first to ClientRequest to better replicate browser (`#3265\n  <https://github.com/aio-libs/aiohttp/pull/3265>`_)\n- Fix forward compatibility with Python 3.8: importing ABCs directly from the\n  collections module will not be supported anymore. (`#3273\n  <https://github.com/aio-libs/aiohttp/pull/3273>`_)\n- Keep the query string by ``normalize_path_middleware``. (`#3278\n  <https://github.com/aio-libs/aiohttp/pull/3278>`_)\n- Fix missing parameter ``raise_for_status`` for aiohttp.request() (`#3290\n  <https://github.com/aio-libs/aiohttp/pull/3290>`_)\n- Bracket IPv6 addresses in the HOST header (`#3304\n  <https://github.com/aio-libs/aiohttp/pull/3304>`_)\n- Fix default message for server ping and pong frames. (`#3308\n  <https://github.com/aio-libs/aiohttp/pull/3308>`_)\n- Fix tests/test_connector.py typo and tests/autobahn/server.py duplicate loop\n  def. (`#3337 <https://github.com/aio-libs/aiohttp/pull/3337>`_)\n- Fix false-negative indicator end_of_HTTP_chunk in StreamReader.readchunk function\n  (`#3361 <https://github.com/aio-libs/aiohttp/pull/3361>`_)\n- Release HTTP response before raising status exception (`#3364\n  <https://github.com/aio-libs/aiohttp/pull/3364>`_)\n- Fix task cancellation when ``sendfile()`` syscall is used by static file\n  handling. (`#3383 <https://github.com/aio-libs/aiohttp/pull/3383>`_)\n- Fix stack trace for ``asyncio.TimeoutError`` which was not logged, when it is caught\n  in the handler. (`#3414 <https://github.com/aio-libs/aiohttp/pull/3414>`_)\n\n\nImproved Documentation\n----------------------\n\n- Improve documentation of ``Application.make_handler`` parameters. (`#3152\n  <https://github.com/aio-libs/aiohttp/pull/3152>`_)\n- Fix BaseRequest.raw_headers doc. (`#3215\n  <https://github.com/aio-libs/aiohttp/pull/3215>`_)\n- Fix typo in TypeError exception reason in ``web.Application._handle`` (`#3229\n  <https://github.com/aio-libs/aiohttp/pull/3229>`_)\n- Make server access log format placeholder %b documentation reflect\n  behavior and docstring. (`#3307 <https://github.com/aio-libs/aiohttp/pull/3307>`_)\n\n\nDeprecations and Removals\n-------------------------\n\n- Deprecate modification of ``session.requote_redirect_url`` (`#2278\n  <https://github.com/aio-libs/aiohttp/pull/2278>`_)\n- Deprecate ``stream.unread_data()`` (`#3260\n  <https://github.com/aio-libs/aiohttp/pull/3260>`_)\n- Deprecated use of boolean in ``resp.enable_compression()`` (`#3318\n  <https://github.com/aio-libs/aiohttp/pull/3318>`_)\n- Encourage creation of aiohttp public objects inside a coroutine (`#3331\n  <https://github.com/aio-libs/aiohttp/pull/3331>`_)\n- Drop dead ``Connection.detach()`` and ``Connection.writer``. Both methods were broken\n  for more than 2 years. (`#3358 <https://github.com/aio-libs/aiohttp/pull/3358>`_)\n- Deprecate ``app.loop``, ``request.loop``, ``client.loop`` and ``connector.loop``\n  properties. (`#3374 <https://github.com/aio-libs/aiohttp/pull/3374>`_)\n- Deprecate explicit debug argument. Use asyncio debug mode instead. (`#3381\n  <https://github.com/aio-libs/aiohttp/pull/3381>`_)\n- Deprecate body parameter in HTTPException (and derived classes) constructor. (`#3385\n  <https://github.com/aio-libs/aiohttp/pull/3385>`_)\n- Deprecate bare connector close, use ``async with connector:`` and ``await\n  connector.close()`` instead. (`#3417\n  <https://github.com/aio-libs/aiohttp/pull/3417>`_)\n- Deprecate obsolete ``read_timeout`` and ``conn_timeout`` in ``ClientSession``\n  constructor. (`#3438 <https://github.com/aio-libs/aiohttp/pull/3438>`_)\n\n\nMisc\n----\n\n- #3341, #3351\n\n\n\n\n----\n\n\n3.4.4 (2018-09-05)\n==================\n\n- Fix installation from sources when compiling toolkit is not available (`#3241 <https://github.com/aio-libs/aiohttp/pull/3241>`_)\n\n\n\n\n----\n\n\n3.4.3 (2018-09-04)\n==================\n\n- Add ``app.pre_frozen`` state to properly handle startup signals in sub-applications. (`#3237 <https://github.com/aio-libs/aiohttp/pull/3237>`_)\n\n\n\n\n----\n\n\n3.4.2 (2018-09-01)\n==================\n\n- Fix ``iter_chunks`` type annotation (`#3230 <https://github.com/aio-libs/aiohttp/pull/3230>`_)\n\n\n\n\n----\n\n\n3.4.1 (2018-08-28)\n==================\n\n- Fix empty header parsing regression. (`#3218 <https://github.com/aio-libs/aiohttp/pull/3218>`_)\n- Fix BaseRequest.raw_headers doc. (`#3215 <https://github.com/aio-libs/aiohttp/pull/3215>`_)\n- Fix documentation building on ReadTheDocs (`#3221 <https://github.com/aio-libs/aiohttp/pull/3221>`_)\n\n\n\n\n----\n\n\n3.4.0 (2018-08-25)\n==================\n\nFeatures\n--------\n\n- Add type hints (`#3049 <https://github.com/aio-libs/aiohttp/pull/3049>`_)\n- Add ``raise_for_status`` request parameter (`#3073 <https://github.com/aio-libs/aiohttp/pull/3073>`_)\n- Add type hints to HTTP client (`#3092 <https://github.com/aio-libs/aiohttp/pull/3092>`_)\n- Minor server optimizations (`#3095 <https://github.com/aio-libs/aiohttp/pull/3095>`_)\n- Preserve the cause when `HTTPException` is raised from another exception. (`#3096 <https://github.com/aio-libs/aiohttp/pull/3096>`_)\n- Add `close_boundary` option in `MultipartWriter.write` method. Support streaming (`#3104 <https://github.com/aio-libs/aiohttp/pull/3104>`_)\n- Added a ``remove_slash`` option to the ``normalize_path_middleware`` factory. (`#3173 <https://github.com/aio-libs/aiohttp/pull/3173>`_)\n- The class `AbstractRouteDef` is importable from `aiohttp.web`. (`#3183 <https://github.com/aio-libs/aiohttp/pull/3183>`_)\n\n\nBugfixes\n--------\n\n- Prevent double closing when client connection is released before the\n  last ``data_received()`` callback. (`#3031 <https://github.com/aio-libs/aiohttp/pull/3031>`_)\n- Make redirect with `normalize_path_middleware` work when using url encoded paths. (`#3051 <https://github.com/aio-libs/aiohttp/pull/3051>`_)\n- Postpone web task creation to connection establishment. (`#3052 <https://github.com/aio-libs/aiohttp/pull/3052>`_)\n- Fix ``sock_read`` timeout. (`#3053 <https://github.com/aio-libs/aiohttp/pull/3053>`_)\n- When using a server-request body as the `data=` argument of a client request, iterate over the content with `readany` instead of `readline` to avoid `Line too long` errors. (`#3054 <https://github.com/aio-libs/aiohttp/pull/3054>`_)\n- fix `UrlDispatcher` has no attribute `add_options`, add `web.options` (`#3062 <https://github.com/aio-libs/aiohttp/pull/3062>`_)\n- correct filename in content-disposition with multipart body (`#3064 <https://github.com/aio-libs/aiohttp/pull/3064>`_)\n- Many HTTP proxies has buggy keepalive support.\n  Let's not reuse connection but close it after processing every response. (`#3070 <https://github.com/aio-libs/aiohttp/pull/3070>`_)\n- raise 413 \"Payload Too Large\" rather than raising ValueError in request.post()\n  Add helpful debug message to 413 responses (`#3087 <https://github.com/aio-libs/aiohttp/pull/3087>`_)\n- Fix `StreamResponse` equality, now that they are `MutableMapping` objects. (`#3100 <https://github.com/aio-libs/aiohttp/pull/3100>`_)\n- Fix server request objects comparison (`#3116 <https://github.com/aio-libs/aiohttp/pull/3116>`_)\n- Do not hang on `206 Partial Content` response with `Content-Encoding: gzip` (`#3123 <https://github.com/aio-libs/aiohttp/pull/3123>`_)\n- Fix timeout precondition checkers (`#3145 <https://github.com/aio-libs/aiohttp/pull/3145>`_)\n\n\nImproved Documentation\n----------------------\n\n- Add a new FAQ entry that clarifies that you should not reuse response\n  objects in middleware functions. (`#3020 <https://github.com/aio-libs/aiohttp/pull/3020>`_)\n- Add FAQ section \"Why is creating a ClientSession outside of an event loop dangerous?\" (`#3072 <https://github.com/aio-libs/aiohttp/pull/3072>`_)\n- Fix link to Rambler (`#3115 <https://github.com/aio-libs/aiohttp/pull/3115>`_)\n- Fix TCPSite documentation on the Server Reference page. (`#3146 <https://github.com/aio-libs/aiohttp/pull/3146>`_)\n- Fix documentation build configuration file for Windows. (`#3147 <https://github.com/aio-libs/aiohttp/pull/3147>`_)\n- Remove no longer existing lingering_timeout parameter of Application.make_handler from documentation. (`#3151 <https://github.com/aio-libs/aiohttp/pull/3151>`_)\n- Mention that ``app.make_handler`` is deprecated, recommend to use runners\n  API instead. (`#3157 <https://github.com/aio-libs/aiohttp/pull/3157>`_)\n\n\nDeprecations and Removals\n-------------------------\n\n- Drop ``loop.current_task()`` from ``helpers.current_task()`` (`#2826 <https://github.com/aio-libs/aiohttp/pull/2826>`_)\n- Drop ``reader`` parameter from ``request.multipart()``. (`#3090 <https://github.com/aio-libs/aiohttp/pull/3090>`_)\n\n\n\n\n----\n\n\n3.3.2 (2018-06-12)\n==================\n\n- Many HTTP proxies has buggy keepalive support. Let's not reuse connection but\n  close it after processing every response. (`#3070 <https://github.com/aio-libs/aiohttp/pull/3070>`_)\n\n- Provide vendor source files in tarball (`#3076 <https://github.com/aio-libs/aiohttp/pull/3076>`_)\n\n\n\n\n----\n\n\n3.3.1 (2018-06-05)\n==================\n\n- Fix ``sock_read`` timeout. (`#3053 <https://github.com/aio-libs/aiohttp/pull/3053>`_)\n- When using a server-request body as the ``data=`` argument of a client request,\n  iterate over the content with ``readany`` instead of ``readline`` to avoid ``Line\n  too long`` errors. (`#3054 <https://github.com/aio-libs/aiohttp/pull/3054>`_)\n\n\n\n\n----\n\n\n3.3.0 (2018-06-01)\n==================\n\nFeatures\n--------\n\n- Raise ``ConnectionResetError`` instead of ``CancelledError`` on trying to\n  write to a closed stream. (`#2499 <https://github.com/aio-libs/aiohttp/pull/2499>`_)\n- Implement ``ClientTimeout`` class and support socket read timeout. (`#2768 <https://github.com/aio-libs/aiohttp/pull/2768>`_)\n- Enable logging when ``aiohttp.web`` is used as a program (`#2956 <https://github.com/aio-libs/aiohttp/pull/2956>`_)\n- Add canonical property to resources (`#2968 <https://github.com/aio-libs/aiohttp/pull/2968>`_)\n- Forbid reading response BODY after release (`#2983 <https://github.com/aio-libs/aiohttp/pull/2983>`_)\n- Implement base protocol class to avoid a dependency from internal\n  ``asyncio.streams.FlowControlMixin`` (`#2986 <https://github.com/aio-libs/aiohttp/pull/2986>`_)\n- Cythonize ``@helpers.reify``, 5% boost on macro benchmark (`#2995 <https://github.com/aio-libs/aiohttp/pull/2995>`_)\n- Optimize HTTP parser (`#3015 <https://github.com/aio-libs/aiohttp/pull/3015>`_)\n- Implement ``runner.addresses`` property. (`#3036 <https://github.com/aio-libs/aiohttp/pull/3036>`_)\n- Use ``bytearray`` instead of a list of ``bytes`` in websocket reader. It\n  improves websocket message reading a little. (`#3039 <https://github.com/aio-libs/aiohttp/pull/3039>`_)\n- Remove heartbeat on closing connection on keepalive timeout. The used hack\n  violates HTTP protocol. (`#3041 <https://github.com/aio-libs/aiohttp/pull/3041>`_)\n- Limit websocket message size on reading to 4 MB by default. (`#3045 <https://github.com/aio-libs/aiohttp/pull/3045>`_)\n\n\nBugfixes\n--------\n\n- Don't reuse a connection with the same URL but different proxy/TLS settings\n  (`#2981 <https://github.com/aio-libs/aiohttp/pull/2981>`_)\n- When parsing the Forwarded header, the optional port number is now preserved.\n  (`#3009 <https://github.com/aio-libs/aiohttp/pull/3009>`_)\n\n\nImproved Documentation\n----------------------\n\n- Make Change Log more visible in docs (`#3029 <https://github.com/aio-libs/aiohttp/pull/3029>`_)\n- Make style and grammar improvements on the FAQ page. (`#3030 <https://github.com/aio-libs/aiohttp/pull/3030>`_)\n- Document that signal handlers should be async functions since aiohttp 3.0\n  (`#3032 <https://github.com/aio-libs/aiohttp/pull/3032>`_)\n\n\nDeprecations and Removals\n-------------------------\n\n- Deprecate custom application's router. (`#3021 <https://github.com/aio-libs/aiohttp/pull/3021>`_)\n\n\nMisc\n----\n\n- #3008, #3011\n\n\n\n\n----\n\n\n3.2.1 (2018-05-10)\n==================\n\n- Don't reuse a connection with the same URL but different proxy/TLS settings\n  (`#2981 <https://github.com/aio-libs/aiohttp/pull/2981>`_)\n\n\n\n\n----\n\n\n3.2.0 (2018-05-06)\n==================\n\nFeatures\n--------\n\n- Raise ``TooManyRedirects`` exception when client gets redirected too many\n  times instead of returning last response. (`#2631 <https://github.com/aio-libs/aiohttp/pull/2631>`_)\n- Extract route definitions into separate ``web_routedef.py`` file (`#2876 <https://github.com/aio-libs/aiohttp/pull/2876>`_)\n- Raise an exception on request body reading after sending response. (`#2895 <https://github.com/aio-libs/aiohttp/pull/2895>`_)\n- ClientResponse and RequestInfo now have real_url property, which is request\n  url without fragment part being stripped (`#2925 <https://github.com/aio-libs/aiohttp/pull/2925>`_)\n- Speed up connector limiting (`#2937 <https://github.com/aio-libs/aiohttp/pull/2937>`_)\n- Added and links property for ClientResponse object (`#2948 <https://github.com/aio-libs/aiohttp/pull/2948>`_)\n- Add ``request.config_dict`` for exposing nested applications data. (`#2949 <https://github.com/aio-libs/aiohttp/pull/2949>`_)\n- Speed up HTTP headers serialization, server micro-benchmark runs 5% faster\n  now. (`#2957 <https://github.com/aio-libs/aiohttp/pull/2957>`_)\n- Apply assertions in debug mode only (`#2966 <https://github.com/aio-libs/aiohttp/pull/2966>`_)\n\n\nBugfixes\n--------\n\n- expose property `app` for TestClient (`#2891 <https://github.com/aio-libs/aiohttp/pull/2891>`_)\n- Call on_chunk_sent when write_eof takes as a param the last chunk (`#2909 <https://github.com/aio-libs/aiohttp/pull/2909>`_)\n- A closing bracket was added to `__repr__` of resources (`#2935 <https://github.com/aio-libs/aiohttp/pull/2935>`_)\n- Fix compression of FileResponse (`#2942 <https://github.com/aio-libs/aiohttp/pull/2942>`_)\n- Fixes some bugs in the limit connection feature (`#2964 <https://github.com/aio-libs/aiohttp/pull/2964>`_)\n\n\nImproved Documentation\n----------------------\n\n- Drop ``async_timeout`` usage from documentation for client API in favor of\n  ``timeout`` parameter. (`#2865 <https://github.com/aio-libs/aiohttp/pull/2865>`_)\n- Improve Gunicorn logging documentation (`#2921 <https://github.com/aio-libs/aiohttp/pull/2921>`_)\n- Replace multipart writer `.serialize()` method with `.write()` in\n  documentation. (`#2965 <https://github.com/aio-libs/aiohttp/pull/2965>`_)\n\n\nDeprecations and Removals\n-------------------------\n\n- Deprecate Application.make_handler() (`#2938 <https://github.com/aio-libs/aiohttp/pull/2938>`_)\n\n\nMisc\n----\n\n- #2958\n\n\n\n\n----\n\n\n3.1.3 (2018-04-12)\n==================\n\n- Fix cancellation broadcast during DNS resolve (`#2910 <https://github.com/aio-libs/aiohttp/pull/2910>`_)\n\n\n\n\n----\n\n\n3.1.2 (2018-04-05)\n==================\n\n- Make ``LineTooLong`` exception more detailed about actual data size (`#2863 <https://github.com/aio-libs/aiohttp/pull/2863>`_)\n\n- Call ``on_chunk_sent`` when write_eof takes as a param the last chunk (`#2909 <https://github.com/aio-libs/aiohttp/pull/2909>`_)\n\n\n\n\n----\n\n\n3.1.1 (2018-03-27)\n==================\n\n- Support *asynchronous iterators* (and *asynchronous generators* as\n  well) in both client and server API as request / response BODY\n  payloads. (`#2802 <https://github.com/aio-libs/aiohttp/pull/2802>`_)\n\n\n\n\n----\n\n\n3.1.0 (2018-03-21)\n==================\n\nWelcome to aiohttp 3.1 release.\n\nThis is an *incremental* release, fully backward compatible with *aiohttp 3.0*.\n\nBut we have added several new features.\n\nThe most visible one is ``app.add_routes()`` (an alias for existing\n``app.router.add_routes()``. The addition is very important because\nall *aiohttp* docs now uses ``app.add_routes()`` call in code\nsnippets. All your existing code still do register routes / resource\nwithout any warning but you've got the idea for a favorite way: noisy\n``app.router.add_get()`` is replaced by ``app.add_routes()``.\n\nThe library does not make a preference between decorators::\n\n   routes = web.RouteTableDef()\n\n   @routes.get('/')\n   async def hello(request):\n       return web.Response(text=\"Hello, world\")\n\n   app.add_routes(routes)\n\nand route tables as a list::\n\n   async def hello(request):\n       return web.Response(text=\"Hello, world\")\n\n   app.add_routes([web.get('/', hello)])\n\nBoth ways are equal, user may decide basing on own code taste.\n\nAlso we have a lot of minor features, bug fixes and documentation\nupdates, see below.\n\nFeatures\n--------\n\n- Relax JSON content-type checking in the ``ClientResponse.json()`` to allow\n  \"application/xxx+json\" instead of strict \"application/json\". (`#2206 <https://github.com/aio-libs/aiohttp/pull/2206>`_)\n- Bump C HTTP parser to version 2.8 (`#2730 <https://github.com/aio-libs/aiohttp/pull/2730>`_)\n- Accept a coroutine as an application factory in ``web.run_app`` and gunicorn\n  worker. (`#2739 <https://github.com/aio-libs/aiohttp/pull/2739>`_)\n- Implement application cleanup context (``app.cleanup_ctx`` property). (`#2747 <https://github.com/aio-libs/aiohttp/pull/2747>`_)\n- Make ``writer.write_headers`` a coroutine. (`#2762 <https://github.com/aio-libs/aiohttp/pull/2762>`_)\n- Add tracking signals for getting request/response bodies. (`#2767 <https://github.com/aio-libs/aiohttp/pull/2767>`_)\n- Deprecate ClientResponseError.code in favor of .status to keep similarity\n  with response classes. (`#2781 <https://github.com/aio-libs/aiohttp/pull/2781>`_)\n- Implement ``app.add_routes()`` method. (`#2787 <https://github.com/aio-libs/aiohttp/pull/2787>`_)\n- Implement ``web.static()`` and ``RouteTableDef.static()`` API. (`#2795 <https://github.com/aio-libs/aiohttp/pull/2795>`_)\n- Install a test event loop as default by ``asyncio.set_event_loop()``. The\n  change affects aiohttp test utils but backward compatibility is not broken\n  for 99.99% of use cases. (`#2804 <https://github.com/aio-libs/aiohttp/pull/2804>`_)\n- Refactor ``ClientResponse`` constructor: make logically required constructor\n  arguments mandatory, drop ``_post_init()`` method. (`#2820 <https://github.com/aio-libs/aiohttp/pull/2820>`_)\n- Use ``app.add_routes()`` in server docs everywhere (`#2830 <https://github.com/aio-libs/aiohttp/pull/2830>`_)\n- Websockets refactoring, all websocket writer methods are converted into\n  coroutines. (`#2836 <https://github.com/aio-libs/aiohttp/pull/2836>`_)\n- Provide ``Content-Range`` header for ``Range`` requests (`#2844 <https://github.com/aio-libs/aiohttp/pull/2844>`_)\n\n\nBugfixes\n--------\n\n- Fix websocket client return EofStream. (`#2784 <https://github.com/aio-libs/aiohttp/pull/2784>`_)\n- Fix websocket demo. (`#2789 <https://github.com/aio-libs/aiohttp/pull/2789>`_)\n- Property ``BaseRequest.http_range`` now returns a python-like slice when\n  requesting the tail of the range. It's now indicated by a negative value in\n  ``range.start`` rather then in ``range.stop`` (`#2805 <https://github.com/aio-libs/aiohttp/pull/2805>`_)\n- Close a connection if an unexpected exception occurs while sending a request\n  (`#2827 <https://github.com/aio-libs/aiohttp/pull/2827>`_)\n- Fix firing DNS tracing events. (`#2841 <https://github.com/aio-libs/aiohttp/pull/2841>`_)\n\n\nImproved Documentation\n----------------------\n\n- Document behavior when cchardet detects encodings that are unknown to Python.\n  (`#2732 <https://github.com/aio-libs/aiohttp/pull/2732>`_)\n- Add diagrams for tracing request life style. (`#2748 <https://github.com/aio-libs/aiohttp/pull/2748>`_)\n- Drop removed functionality for passing ``StreamReader`` as data at client\n  side. (`#2793 <https://github.com/aio-libs/aiohttp/pull/2793>`_)\n\n\n\n----\n\n3.0.9 (2018-03-14)\n==================\n\n- Close a connection if an unexpected exception occurs while sending a request\n  (`#2827 <https://github.com/aio-libs/aiohttp/pull/2827>`_)\n\n\n\n----\n\n\n3.0.8 (2018-03-12)\n==================\n\n- Use ``asyncio.current_task()`` on Python 3.7 (`#2825 <https://github.com/aio-libs/aiohttp/pull/2825>`_)\n\n\n\n----\n\n3.0.7 (2018-03-08)\n==================\n\n- Fix SSL proxy support by client. (`#2810 <https://github.com/aio-libs/aiohttp/pull/2810>`_)\n- Restore an imperative check in ``setup.py`` for python version. The check\n  works in parallel to environment marker. As effect an error about unsupported\n  Python versions is raised even on outdated systems with very old\n  ``setuptools`` version installed. (`#2813 <https://github.com/aio-libs/aiohttp/pull/2813>`_)\n\n\n\n----\n\n\n3.0.6 (2018-03-05)\n==================\n\n- Add ``_reuse_address`` and ``_reuse_port`` to\n  ``web_runner.TCPSite.__slots__``. (`#2792 <https://github.com/aio-libs/aiohttp/pull/2792>`_)\n\n\n\n----\n\n3.0.5 (2018-02-27)\n==================\n\n- Fix ``InvalidStateError`` on processing a sequence of two\n  ``RequestHandler.data_received`` calls on web server. (`#2773 <https://github.com/aio-libs/aiohttp/pull/2773>`_)\n\n\n\n----\n\n3.0.4 (2018-02-26)\n==================\n\n- Fix ``IndexError`` in HTTP request handling by server. (`#2752 <https://github.com/aio-libs/aiohttp/pull/2752>`_)\n- Fix MultipartWriter.append* no longer returning part/payload. (`#2759 <https://github.com/aio-libs/aiohttp/pull/2759>`_)\n\n\n\n----\n\n\n3.0.3 (2018-02-25)\n==================\n\n- Relax ``attrs`` dependency to minimal actually supported version\n  17.0.3 The change allows to avoid version conflicts with currently\n  existing test tools.\n\n\n\n----\n\n3.0.2 (2018-02-23)\n==================\n\nSecurity Fix\n------------\n\n- Prevent Windows absolute URLs in static files.  Paths like\n  ``/static/D:\\path`` and ``/static/\\\\hostname\\drive\\path`` are\n  forbidden.\n\n\n\n----\n\n3.0.1\n=====\n\n- Technical release for fixing distribution problems.\n\n\n\n----\n\n3.0.0 (2018-02-12)\n==================\n\nFeatures\n--------\n\n- Speed up the `PayloadWriter.write` method for large request bodies. (`#2126 <https://github.com/aio-libs/aiohttp/pull/2126>`_)\n- StreamResponse and Response are now MutableMappings. (`#2246 <https://github.com/aio-libs/aiohttp/pull/2246>`_)\n- ClientSession publishes a set of signals to track the HTTP request execution.\n  (`#2313 <https://github.com/aio-libs/aiohttp/pull/2313>`_)\n- Content-Disposition fast access in ClientResponse (`#2455 <https://github.com/aio-libs/aiohttp/pull/2455>`_)\n- Added support to Flask-style decorators with class-based Views. (`#2472 <https://github.com/aio-libs/aiohttp/pull/2472>`_)\n- Signal handlers (registered callbacks) should be coroutines. (`#2480 <https://github.com/aio-libs/aiohttp/pull/2480>`_)\n- Support ``async with test_client.ws_connect(...)`` (`#2525 <https://github.com/aio-libs/aiohttp/pull/2525>`_)\n- Introduce *site* and *application runner* as underlying API for `web.run_app`\n  implementation. (`#2530 <https://github.com/aio-libs/aiohttp/pull/2530>`_)\n- Only quote multipart boundary when necessary and sanitize input (`#2544 <https://github.com/aio-libs/aiohttp/pull/2544>`_)\n- Make the `aiohttp.ClientResponse.get_encoding` method public with the\n  processing of invalid charset while detecting content encoding. (`#2549 <https://github.com/aio-libs/aiohttp/pull/2549>`_)\n- Add optional configurable per message compression for\n  `ClientWebSocketResponse` and `WebSocketResponse`. (`#2551 <https://github.com/aio-libs/aiohttp/pull/2551>`_)\n- Add hysteresis to `StreamReader` to prevent flipping between paused and\n  resumed states too often. (`#2555 <https://github.com/aio-libs/aiohttp/pull/2555>`_)\n- Support `.netrc` by `trust_env` (`#2581 <https://github.com/aio-libs/aiohttp/pull/2581>`_)\n- Avoid to create a new resource when adding a route with the same name and\n  path of the last added resource (`#2586 <https://github.com/aio-libs/aiohttp/pull/2586>`_)\n- `MultipartWriter.boundary` is `str` now. (`#2589 <https://github.com/aio-libs/aiohttp/pull/2589>`_)\n- Allow a custom port to be used by `TestServer` (and associated pytest\n  fixtures) (`#2613 <https://github.com/aio-libs/aiohttp/pull/2613>`_)\n- Add param access_log_class to web.run_app function (`#2615 <https://github.com/aio-libs/aiohttp/pull/2615>`_)\n- Add ``ssl`` parameter to client API (`#2626 <https://github.com/aio-libs/aiohttp/pull/2626>`_)\n- Fixes performance issue introduced by #2577. When there are no middlewares\n  installed by the user, no additional and useless code is executed. (`#2629 <https://github.com/aio-libs/aiohttp/pull/2629>`_)\n- Rename PayloadWriter to StreamWriter (`#2654 <https://github.com/aio-libs/aiohttp/pull/2654>`_)\n- New options *reuse_port*, *reuse_address* are added to `run_app` and\n  `TCPSite`. (`#2679 <https://github.com/aio-libs/aiohttp/pull/2679>`_)\n- Use custom classes to pass client signals parameters (`#2686 <https://github.com/aio-libs/aiohttp/pull/2686>`_)\n- Use ``attrs`` library for data classes, replace `namedtuple`. (`#2690 <https://github.com/aio-libs/aiohttp/pull/2690>`_)\n- Pytest fixtures renaming, add ``aiohttp_`` prefix (`#2578 <https://github.com/aio-libs/aiohttp/pull/2578>`_)\n- Add ``aiohttp-`` prefix for ``pytest-aiohttp`` command line\n  parameters (`#2578 <https://github.com/aio-libs/aiohttp/pull/2578>`_)\n\nBugfixes\n--------\n\n- Correctly process upgrade request from server to HTTP2. ``aiohttp`` does not\n  support HTTP2 yet, the protocol is not upgraded but response is handled\n  correctly. (`#2277 <https://github.com/aio-libs/aiohttp/pull/2277>`_)\n- Fix ClientConnectorSSLError and ClientProxyConnectionError for proxy\n  connector (`#2408 <https://github.com/aio-libs/aiohttp/pull/2408>`_)\n- Fix connector convert OSError to ClientConnectorError (`#2423 <https://github.com/aio-libs/aiohttp/pull/2423>`_)\n- Fix connection attempts for multiple dns hosts (`#2424 <https://github.com/aio-libs/aiohttp/pull/2424>`_)\n- Fix writing to closed transport by raising `asyncio.CancelledError` (`#2499 <https://github.com/aio-libs/aiohttp/pull/2499>`_)\n- Fix warning in `ClientSession.__del__` by stopping to try to close it.\n  (`#2523 <https://github.com/aio-libs/aiohttp/pull/2523>`_)\n- Fixed race-condition for iterating addresses from the DNSCache. (`#2620 <https://github.com/aio-libs/aiohttp/pull/2620>`_)\n- Fix default value of `access_log_format` argument in `web.run_app` (`#2649 <https://github.com/aio-libs/aiohttp/pull/2649>`_)\n- Freeze sub-application on adding to parent app (`#2656 <https://github.com/aio-libs/aiohttp/pull/2656>`_)\n- Do percent encoding for `.url_for()` parameters (`#2668 <https://github.com/aio-libs/aiohttp/pull/2668>`_)\n- Correctly process request start time and multiple request/response\n  headers in access log extra (`#2641 <https://github.com/aio-libs/aiohttp/pull/2641>`_)\n\nImproved Documentation\n----------------------\n\n- Improve tutorial docs, using `literalinclude` to link to the actual files.\n  (`#2396 <https://github.com/aio-libs/aiohttp/pull/2396>`_)\n- Small improvement docs: better example for file uploads. (`#2401 <https://github.com/aio-libs/aiohttp/pull/2401>`_)\n- Rename `from_env` to `trust_env` in client reference. (`#2451 <https://github.com/aio-libs/aiohttp/pull/2451>`_)\n- ﻿Fixed mistype in `Proxy Support` section where `trust_env` parameter was\n  used in `session.get(\"http://python.org\", trust_env=True)` method instead of\n  aiohttp.ClientSession constructor as follows:\n  `aiohttp.ClientSession(trust_env=True)`. (`#2688 <https://github.com/aio-libs/aiohttp/pull/2688>`_)\n- Fix issue with unittest example not compiling in testing docs. (`#2717 <https://github.com/aio-libs/aiohttp/pull/2717>`_)\n\nDeprecations and Removals\n-------------------------\n\n- Simplify HTTP pipelining implementation (`#2109 <https://github.com/aio-libs/aiohttp/pull/2109>`_)\n- Drop `StreamReaderPayload` and `DataQueuePayload`. (`#2257 <https://github.com/aio-libs/aiohttp/pull/2257>`_)\n- Drop `md5` and `sha1` finger-prints (`#2267 <https://github.com/aio-libs/aiohttp/pull/2267>`_)\n- Drop WSMessage.tp (`#2321 <https://github.com/aio-libs/aiohttp/pull/2321>`_)\n- Drop Python 3.4 and Python 3.5.0, 3.5.1, 3.5.2. Minimal supported Python\n  versions are 3.5.3 and 3.6.0. `yield from` is gone, use `async/await` syntax.\n  (`#2343 <https://github.com/aio-libs/aiohttp/pull/2343>`_)\n- Drop `aiohttp.Timeout` and use `async_timeout.timeout` instead. (`#2348 <https://github.com/aio-libs/aiohttp/pull/2348>`_)\n- Drop `resolve` param from TCPConnector. (`#2377 <https://github.com/aio-libs/aiohttp/pull/2377>`_)\n- Add DeprecationWarning for returning HTTPException (`#2415 <https://github.com/aio-libs/aiohttp/pull/2415>`_)\n- `send_str()`, `send_bytes()`, `send_json()`, `ping()` and `pong()` are\n  genuine async functions now. (`#2475 <https://github.com/aio-libs/aiohttp/pull/2475>`_)\n- Drop undocumented `app.on_pre_signal` and `app.on_post_signal`. Signal\n  handlers should be coroutines, support for regular functions is dropped.\n  (`#2480 <https://github.com/aio-libs/aiohttp/pull/2480>`_)\n- `StreamResponse.drain()` is not a part of public API anymore, just use `await\n  StreamResponse.write()`. `StreamResponse.write` is converted to async\n  function. (`#2483 <https://github.com/aio-libs/aiohttp/pull/2483>`_)\n- Drop deprecated `slow_request_timeout` param and `**kwargs`` from\n  `RequestHandler`. (`#2500 <https://github.com/aio-libs/aiohttp/pull/2500>`_)\n- Drop deprecated `resource.url()`. (`#2501 <https://github.com/aio-libs/aiohttp/pull/2501>`_)\n- Remove `%u` and `%l` format specifiers from access log format. (`#2506 <https://github.com/aio-libs/aiohttp/pull/2506>`_)\n- Drop deprecated `request.GET` property. (`#2547 <https://github.com/aio-libs/aiohttp/pull/2547>`_)\n- Simplify stream classes: drop `ChunksQueue` and `FlowControlChunksQueue`,\n  merge `FlowControlStreamReader` functionality into `StreamReader`, drop\n  `FlowControlStreamReader` name. (`#2555 <https://github.com/aio-libs/aiohttp/pull/2555>`_)\n- Do not create a new resource on `router.add_get(..., allow_head=True)`\n  (`#2585 <https://github.com/aio-libs/aiohttp/pull/2585>`_)\n- Drop access to TCP tuning options from PayloadWriter and Response classes\n  (`#2604 <https://github.com/aio-libs/aiohttp/pull/2604>`_)\n- Drop deprecated `encoding` parameter from client API (`#2606 <https://github.com/aio-libs/aiohttp/pull/2606>`_)\n- Deprecate ``verify_ssl``, ``ssl_context`` and ``fingerprint`` parameters in\n  client API (`#2626 <https://github.com/aio-libs/aiohttp/pull/2626>`_)\n- Get rid of the legacy class StreamWriter. (`#2651 <https://github.com/aio-libs/aiohttp/pull/2651>`_)\n- Forbid non-strings in `resource.url_for()` parameters. (`#2668 <https://github.com/aio-libs/aiohttp/pull/2668>`_)\n- Deprecate inheritance from ``ClientSession`` and ``web.Application`` and\n  custom user attributes for ``ClientSession``, ``web.Request`` and\n  ``web.Application`` (`#2691 <https://github.com/aio-libs/aiohttp/pull/2691>`_)\n- Drop `resp = await aiohttp.request(...)` syntax for sake of `async with\n  aiohttp.request(...) as resp:`. (`#2540 <https://github.com/aio-libs/aiohttp/pull/2540>`_)\n- Forbid synchronous context managers for `ClientSession` and test\n  server/client. (`#2362 <https://github.com/aio-libs/aiohttp/pull/2362>`_)\n\n\nMisc\n----\n\n- #2552\n\n\n\n----\n\n\n2.3.10 (2018-02-02)\n===================\n\n- Fix 100% CPU usage on HTTP GET and websocket connection just after it (`#1955 <https://github.com/aio-libs/aiohttp/pull/1955>`_)\n- Patch broken `ssl.match_hostname()` on Python<3.7 (`#2674 <https://github.com/aio-libs/aiohttp/pull/2674>`_)\n\n\n\n----\n\n2.3.9 (2018-01-16)\n==================\n\n- Fix colon handing in path for dynamic resources (`#2670 <https://github.com/aio-libs/aiohttp/pull/2670>`_)\n\n\n\n----\n\n2.3.8 (2018-01-15)\n==================\n\n- Do not use `yarl.unquote` internal function in aiohttp.  Fix\n  incorrectly unquoted path part in URL dispatcher (`#2662 <https://github.com/aio-libs/aiohttp/pull/2662>`_)\n- Fix compatibility with `yarl==1.0.0` (`#2662 <https://github.com/aio-libs/aiohttp/pull/2662>`_)\n\n\n\n----\n\n2.3.7 (2017-12-27)\n==================\n\n- Fixed race-condition for iterating addresses from the DNSCache. (`#2620 <https://github.com/aio-libs/aiohttp/pull/2620>`_)\n- Fix docstring for request.host (`#2591 <https://github.com/aio-libs/aiohttp/pull/2591>`_)\n- Fix docstring for request.remote (`#2592 <https://github.com/aio-libs/aiohttp/pull/2592>`_)\n\n\n\n----\n\n\n2.3.6 (2017-12-04)\n==================\n\n- Correct `request.app` context (for handlers not just middlewares). (`#2577 <https://github.com/aio-libs/aiohttp/pull/2577>`_)\n\n\n\n----\n\n\n2.3.5 (2017-11-30)\n==================\n\n- Fix compatibility with `pytest` 3.3+ (`#2565 <https://github.com/aio-libs/aiohttp/pull/2565>`_)\n\n\n\n----\n\n\n2.3.4 (2017-11-29)\n==================\n\n- Make `request.app` point to proper application instance when using nested\n  applications (with middlewares). (`#2550 <https://github.com/aio-libs/aiohttp/pull/2550>`_)\n- Change base class of ClientConnectorSSLError to ClientSSLError from\n  ClientConnectorError. (`#2563 <https://github.com/aio-libs/aiohttp/pull/2563>`_)\n- Return client connection back to free pool on error in `connector.connect()`.\n  (`#2567 <https://github.com/aio-libs/aiohttp/pull/2567>`_)\n\n\n\n----\n\n\n2.3.3 (2017-11-17)\n==================\n\n- Having a `;` in Response content type does not assume it contains a charset\n  anymore. (`#2197 <https://github.com/aio-libs/aiohttp/pull/2197>`_)\n- Use `getattr(asyncio, 'async')` for keeping compatibility with Python 3.7.\n  (`#2476 <https://github.com/aio-libs/aiohttp/pull/2476>`_)\n- Ignore `NotImplementedError` raised by `set_child_watcher` from `uvloop`.\n  (`#2491 <https://github.com/aio-libs/aiohttp/pull/2491>`_)\n- Fix warning in `ClientSession.__del__` by stopping to try to close it.\n  (`#2523 <https://github.com/aio-libs/aiohttp/pull/2523>`_)\n- Fixed typo's in Third-party libraries page. And added async-v20 to the list\n  (`#2510 <https://github.com/aio-libs/aiohttp/pull/2510>`_)\n\n\n\n----\n\n\n2.3.2 (2017-11-01)\n==================\n\n- Fix passing client max size on cloning request obj. (`#2385 <https://github.com/aio-libs/aiohttp/pull/2385>`_)\n- Fix ClientConnectorSSLError and ClientProxyConnectionError for proxy\n  connector. (`#2408 <https://github.com/aio-libs/aiohttp/pull/2408>`_)\n- Drop generated `_http_parser` shared object from tarball distribution. (`#2414 <https://github.com/aio-libs/aiohttp/pull/2414>`_)\n- Fix connector convert OSError to ClientConnectorError. (`#2423 <https://github.com/aio-libs/aiohttp/pull/2423>`_)\n- Fix connection attempts for multiple dns hosts. (`#2424 <https://github.com/aio-libs/aiohttp/pull/2424>`_)\n- Fix ValueError for AF_INET6 sockets if a preexisting INET6 socket to the\n  `aiohttp.web.run_app` function. (`#2431 <https://github.com/aio-libs/aiohttp/pull/2431>`_)\n- `_SessionRequestContextManager` closes the session properly now. (`#2441 <https://github.com/aio-libs/aiohttp/pull/2441>`_)\n- Rename `from_env` to `trust_env` in client reference. (`#2451 <https://github.com/aio-libs/aiohttp/pull/2451>`_)\n\n\n\n----\n\n\n2.3.1 (2017-10-18)\n==================\n\n- Relax attribute lookup in warning about old-styled middleware (`#2340 <https://github.com/aio-libs/aiohttp/pull/2340>`_)\n\n\n\n----\n\n\n2.3.0 (2017-10-18)\n==================\n\nFeatures\n--------\n\n- Add SSL related params to `ClientSession.request` (`#1128 <https://github.com/aio-libs/aiohttp/pull/1128>`_)\n- Make enable_compression work on HTTP/1.0 (`#1828 <https://github.com/aio-libs/aiohttp/pull/1828>`_)\n- Deprecate registering synchronous web handlers (`#1993 <https://github.com/aio-libs/aiohttp/pull/1993>`_)\n- Switch to `multidict 3.0`. All HTTP headers preserve casing now but compared\n  in case-insensitive way. (`#1994 <https://github.com/aio-libs/aiohttp/pull/1994>`_)\n- Improvement for `normalize_path_middleware`. Added possibility to handle URLs\n  with query string. (`#1995 <https://github.com/aio-libs/aiohttp/pull/1995>`_)\n- Use towncrier for CHANGES.txt build (`#1997 <https://github.com/aio-libs/aiohttp/pull/1997>`_)\n- Implement `trust_env=True` param in `ClientSession`. (`#1998 <https://github.com/aio-libs/aiohttp/pull/1998>`_)\n- Added variable to customize proxy headers (`#2001 <https://github.com/aio-libs/aiohttp/pull/2001>`_)\n- Implement `router.add_routes` and router decorators. (`#2004 <https://github.com/aio-libs/aiohttp/pull/2004>`_)\n- Deprecated `BaseRequest.has_body` in favor of\n  `BaseRequest.can_read_body` Added `BaseRequest.body_exists`\n  attribute that stays static for the lifetime of the request (`#2005 <https://github.com/aio-libs/aiohttp/pull/2005>`_)\n- Provide `BaseRequest.loop` attribute (`#2024 <https://github.com/aio-libs/aiohttp/pull/2024>`_)\n- Make `_CoroGuard` awaitable and fix `ClientSession.close` warning message\n  (`#2026 <https://github.com/aio-libs/aiohttp/pull/2026>`_)\n- Responses to redirects without Location header are returned instead of\n  raising a RuntimeError (`#2030 <https://github.com/aio-libs/aiohttp/pull/2030>`_)\n- Added `get_client`, `get_server`, `setUpAsync` and `tearDownAsync` methods to\n  AioHTTPTestCase (`#2032 <https://github.com/aio-libs/aiohttp/pull/2032>`_)\n- Add automatically a SafeChildWatcher to the test loop (`#2058 <https://github.com/aio-libs/aiohttp/pull/2058>`_)\n- add ability to disable automatic response decompression (`#2110 <https://github.com/aio-libs/aiohttp/pull/2110>`_)\n- Add support for throttling DNS request, avoiding the requests saturation when\n  there is a miss in the DNS cache and many requests getting into the connector\n  at the same time. (`#2111 <https://github.com/aio-libs/aiohttp/pull/2111>`_)\n- Use request for getting access log information instead of message/transport\n  pair. Add `RequestBase.remote` property for accessing to IP of client\n  initiated HTTP request. (`#2123 <https://github.com/aio-libs/aiohttp/pull/2123>`_)\n- json() raises a ContentTypeError exception if the content-type does not meet\n  the requirements instead of raising a generic ClientResponseError. (`#2136 <https://github.com/aio-libs/aiohttp/pull/2136>`_)\n- Make the HTTP client able to return HTTP chunks when chunked transfer\n  encoding is used. (`#2150 <https://github.com/aio-libs/aiohttp/pull/2150>`_)\n- add `append_version` arg into `StaticResource.url` and\n  `StaticResource.url_for` methods for getting an url with hash (version) of\n  the file. (`#2157 <https://github.com/aio-libs/aiohttp/pull/2157>`_)\n- Fix parsing the Forwarded header. * commas and semicolons are allowed inside\n  quoted-strings; * empty forwarded-pairs (as in for=_1;;by=_2) are allowed; *\n  non-standard parameters are allowed (although this alone could be easily done\n  in the previous parser). (`#2173 <https://github.com/aio-libs/aiohttp/pull/2173>`_)\n- Don't require ssl module to run. aiohttp does not require SSL to function.\n  The code paths involved with SSL will only be hit upon SSL usage. Raise\n  `RuntimeError` if HTTPS protocol is required but ssl module is not present.\n  (`#2221 <https://github.com/aio-libs/aiohttp/pull/2221>`_)\n- Accept coroutine fixtures in pytest plugin (`#2223 <https://github.com/aio-libs/aiohttp/pull/2223>`_)\n- Call `shutdown_asyncgens` before event loop closing on Python 3.6. (`#2227 <https://github.com/aio-libs/aiohttp/pull/2227>`_)\n- Speed up Signals when there are no receivers (`#2229 <https://github.com/aio-libs/aiohttp/pull/2229>`_)\n- Raise `InvalidURL` instead of `ValueError` on fetches with invalid URL.\n  (`#2241 <https://github.com/aio-libs/aiohttp/pull/2241>`_)\n- Move `DummyCookieJar` into `cookiejar.py` (`#2242 <https://github.com/aio-libs/aiohttp/pull/2242>`_)\n- `run_app`: Make `print=None` disable printing (`#2260 <https://github.com/aio-libs/aiohttp/pull/2260>`_)\n- Support `brotli` encoding (generic-purpose lossless compression algorithm)\n  (`#2270 <https://github.com/aio-libs/aiohttp/pull/2270>`_)\n- Add server support for WebSockets Per-Message Deflate. Add client option to\n  add deflate compress header in WebSockets request header. If calling\n  ClientSession.ws_connect() with `compress=15` the client will support deflate\n  compress negotiation. (`#2273 <https://github.com/aio-libs/aiohttp/pull/2273>`_)\n- Support `verify_ssl`, `fingerprint`, `ssl_context` and `proxy_headers` by\n  `client.ws_connect`. (`#2292 <https://github.com/aio-libs/aiohttp/pull/2292>`_)\n- Added `aiohttp.ClientConnectorSSLError` when connection fails due\n  `ssl.SSLError` (`#2294 <https://github.com/aio-libs/aiohttp/pull/2294>`_)\n- `aiohttp.web.Application.make_handler` support `access_log_class` (`#2315 <https://github.com/aio-libs/aiohttp/pull/2315>`_)\n- Build HTTP parser extension in non-strict mode by default. (`#2332 <https://github.com/aio-libs/aiohttp/pull/2332>`_)\n\n\nBugfixes\n--------\n\n- Clear auth information on redirecting to other domain (`#1699 <https://github.com/aio-libs/aiohttp/pull/1699>`_)\n- Fix missing app.loop on startup hooks during tests (`#2060 <https://github.com/aio-libs/aiohttp/pull/2060>`_)\n- Fix issue with synchronous session closing when using `ClientSession` as an\n  asynchronous context manager. (`#2063 <https://github.com/aio-libs/aiohttp/pull/2063>`_)\n- Fix issue with `CookieJar` incorrectly expiring cookies in some edge cases.\n  (`#2084 <https://github.com/aio-libs/aiohttp/pull/2084>`_)\n- Force use of IPv4 during test, this will make tests run in a Docker container\n  (`#2104 <https://github.com/aio-libs/aiohttp/pull/2104>`_)\n- Warnings about unawaited coroutines now correctly point to the user's code.\n  (`#2106 <https://github.com/aio-libs/aiohttp/pull/2106>`_)\n- Fix issue with `IndexError` being raised by the `StreamReader.iter_chunks()`\n  generator. (`#2112 <https://github.com/aio-libs/aiohttp/pull/2112>`_)\n- Support HTTP 308 Permanent redirect in client class. (`#2114 <https://github.com/aio-libs/aiohttp/pull/2114>`_)\n- Fix `FileResponse` sending empty chunked body on 304. (`#2143 <https://github.com/aio-libs/aiohttp/pull/2143>`_)\n- Do not add `Content-Length: 0` to GET/HEAD/TRACE/OPTIONS requests by default.\n  (`#2167 <https://github.com/aio-libs/aiohttp/pull/2167>`_)\n- Fix parsing the Forwarded header according to RFC 7239. (`#2170 <https://github.com/aio-libs/aiohttp/pull/2170>`_)\n- Securely determining remote/scheme/host #2171 (`#2171 <https://github.com/aio-libs/aiohttp/pull/2171>`_)\n- Fix header name parsing, if name is split into multiple lines (`#2183 <https://github.com/aio-libs/aiohttp/pull/2183>`_)\n- Handle session close during connection, `KeyError:\n  <aiohttp.connector._TransportPlaceholder>` (`#2193 <https://github.com/aio-libs/aiohttp/pull/2193>`_)\n- Fixes uncaught `TypeError` in `helpers.guess_filename` if `name` is not a\n  string (`#2201 <https://github.com/aio-libs/aiohttp/pull/2201>`_)\n- Raise OSError on async DNS lookup if resolved domain is an alias for another\n  one, which does not have an A or CNAME record. (`#2231 <https://github.com/aio-libs/aiohttp/pull/2231>`_)\n- Fix incorrect warning in `StreamReader`. (`#2251 <https://github.com/aio-libs/aiohttp/pull/2251>`_)\n- Properly clone state of web request (`#2284 <https://github.com/aio-libs/aiohttp/pull/2284>`_)\n- Fix C HTTP parser for cases when status line is split into different TCP\n  packets. (`#2311 <https://github.com/aio-libs/aiohttp/pull/2311>`_)\n- Fix `web.FileResponse` overriding user supplied Content-Type (`#2317 <https://github.com/aio-libs/aiohttp/pull/2317>`_)\n\n\nImproved Documentation\n----------------------\n\n- Add a note about possible performance degradation in `await resp.text()` if\n  charset was not provided by `Content-Type` HTTP header. Pass explicit\n  encoding to solve it. (`#1811 <https://github.com/aio-libs/aiohttp/pull/1811>`_)\n- Drop `disqus` widget from documentation pages. (`#2018 <https://github.com/aio-libs/aiohttp/pull/2018>`_)\n- Add a graceful shutdown section to the client usage documentation. (`#2039 <https://github.com/aio-libs/aiohttp/pull/2039>`_)\n- Document `connector_owner` parameter. (`#2072 <https://github.com/aio-libs/aiohttp/pull/2072>`_)\n- Update the doc of web.Application (`#2081 <https://github.com/aio-libs/aiohttp/pull/2081>`_)\n- Fix mistake about access log disabling. (`#2085 <https://github.com/aio-libs/aiohttp/pull/2085>`_)\n- Add example usage of on_startup and on_shutdown signals by creating and\n  disposing an aiopg connection engine. (`#2131 <https://github.com/aio-libs/aiohttp/pull/2131>`_)\n- Document `encoded=True` for `yarl.URL`, it disables all yarl transformations.\n  (`#2198 <https://github.com/aio-libs/aiohttp/pull/2198>`_)\n- Document that all app's middleware factories are run for every request.\n  (`#2225 <https://github.com/aio-libs/aiohttp/pull/2225>`_)\n- Reflect the fact that default resolver is threaded one starting from aiohttp\n  1.1 (`#2228 <https://github.com/aio-libs/aiohttp/pull/2228>`_)\n\n\nDeprecations and Removals\n-------------------------\n\n- Drop deprecated `Server.finish_connections` (`#2006 <https://github.com/aio-libs/aiohttp/pull/2006>`_)\n- Drop %O format from logging, use %b instead. Drop %e format from logging,\n  environment variables are not supported anymore. (`#2123 <https://github.com/aio-libs/aiohttp/pull/2123>`_)\n- Drop deprecated secure_proxy_ssl_header support (`#2171 <https://github.com/aio-libs/aiohttp/pull/2171>`_)\n- Removed TimeService in favor of simple caching. TimeService also had a bug\n  where it lost about 0.5 seconds per second. (`#2176 <https://github.com/aio-libs/aiohttp/pull/2176>`_)\n- Drop unused response_factory from static files API (`#2290 <https://github.com/aio-libs/aiohttp/pull/2290>`_)\n\n\nMisc\n----\n\n- #2013, #2014, #2048, #2094, #2149, #2187, #2214, #2225, #2243, #2248\n\n\n\n----\n\n\n2.2.5 (2017-08-03)\n==================\n\n- Don't raise deprecation warning on\n  `loop.run_until_complete(client.close())` (`#2065 <https://github.com/aio-libs/aiohttp/pull/2065>`_)\n\n\n\n----\n\n2.2.4 (2017-08-02)\n==================\n\n- Fix issue with synchronous session closing when using ClientSession\n  as an asynchronous context manager.  (`#2063 <https://github.com/aio-libs/aiohttp/pull/2063>`_)\n\n\n\n----\n\n2.2.3 (2017-07-04)\n==================\n\n- Fix `_CoroGuard` for python 3.4\n\n\n\n----\n\n2.2.2 (2017-07-03)\n==================\n\n- Allow `await session.close()` along with `yield from session.close()`\n\n\n\n----\n\n\n2.2.1 (2017-07-02)\n==================\n\n- Relax `yarl` requirement to 0.11+\n- Backport #2026: `session.close` *is* a coroutine (`#2029 <https://github.com/aio-libs/aiohttp/pull/2029>`_)\n\n\n\n----\n\n\n2.2.0 (2017-06-20)\n==================\n\n- Add doc for add_head, update doc for add_get. (`#1944 <https://github.com/aio-libs/aiohttp/pull/1944>`_)\n- Fixed consecutive calls for `Response.write_eof`.\n- Retain method attributes (e.g. :code:`__doc__`) when registering synchronous\n  handlers for resources. (`#1953 <https://github.com/aio-libs/aiohttp/pull/1953>`_)\n- Added signal TERM handling in `run_app` to gracefully exit (`#1932 <https://github.com/aio-libs/aiohttp/pull/1932>`_)\n- Fix websocket issues caused by frame fragmentation. (`#1962 <https://github.com/aio-libs/aiohttp/pull/1962>`_)\n- Raise RuntimeError is you try to set the Content Length and enable\n  chunked encoding at the same time (`#1941 <https://github.com/aio-libs/aiohttp/pull/1941>`_)\n- Small update for `unittest_run_loop`\n- Use CIMultiDict for ClientRequest.skip_auto_headers (`#1970 <https://github.com/aio-libs/aiohttp/pull/1970>`_)\n- Fix wrong startup sequence: test server and `run_app()` are not raise\n  `DeprecationWarning` now (`#1947 <https://github.com/aio-libs/aiohttp/pull/1947>`_)\n- Make sure cleanup signal is sent if startup signal has been sent (`#1959 <https://github.com/aio-libs/aiohttp/pull/1959>`_)\n- Fixed server keep-alive handler, could cause 100% cpu utilization (`#1955 <https://github.com/aio-libs/aiohttp/pull/1955>`_)\n- Connection can be destroyed before response get processed if\n  `await aiohttp.request(..)` is used (`#1981 <https://github.com/aio-libs/aiohttp/pull/1981>`_)\n- MultipartReader does not work with -OO (`#1969 <https://github.com/aio-libs/aiohttp/pull/1969>`_)\n- Fixed `ClientPayloadError` with blank `Content-Encoding` header (`#1931 <https://github.com/aio-libs/aiohttp/pull/1931>`_)\n- Support `deflate` encoding implemented in `httpbin.org/deflate` (`#1918 <https://github.com/aio-libs/aiohttp/pull/1918>`_)\n- Fix BadStatusLine caused by extra `CRLF` after `POST` data (`#1792 <https://github.com/aio-libs/aiohttp/pull/1792>`_)\n- Keep a reference to `ClientSession` in response object (`#1985 <https://github.com/aio-libs/aiohttp/pull/1985>`_)\n- Deprecate undocumented `app.on_loop_available` signal (`#1978 <https://github.com/aio-libs/aiohttp/pull/1978>`_)\n\n\n\n----\n\n\n\n2.1.0 (2017-05-26)\n==================\n\n- Added support for experimental `async-tokio` event loop written in Rust\n  https://github.com/PyO3/tokio\n- Write to transport ``\\r\\n`` before closing after keepalive timeout,\n  otherwise client can not detect socket disconnection. (`#1883 <https://github.com/aio-libs/aiohttp/pull/1883>`_)\n- Only call `loop.close` in `run_app` if the user did *not* supply a loop.\n  Useful for allowing clients to specify their own cleanup before closing the\n  asyncio loop if they wish to tightly control loop behavior\n- Content disposition with semicolon in filename (`#917 <https://github.com/aio-libs/aiohttp/pull/917>`_)\n- Added `request_info` to response object and `ClientResponseError`. (`#1733 <https://github.com/aio-libs/aiohttp/pull/1733>`_)\n- Added `history` to `ClientResponseError`. (`#1741 <https://github.com/aio-libs/aiohttp/pull/1741>`_)\n- Allow to disable redirect url re-quoting (`#1474 <https://github.com/aio-libs/aiohttp/pull/1474>`_)\n- Handle RuntimeError from transport (`#1790 <https://github.com/aio-libs/aiohttp/pull/1790>`_)\n- Dropped \"%O\" in access logger (`#1673 <https://github.com/aio-libs/aiohttp/pull/1673>`_)\n- Added `args` and `kwargs` to `unittest_run_loop`. Useful with other\n  decorators, for example `@patch`. (`#1803 <https://github.com/aio-libs/aiohttp/pull/1803>`_)\n- Added `iter_chunks` to response.content object. (`#1805 <https://github.com/aio-libs/aiohttp/pull/1805>`_)\n- Avoid creating TimerContext when there is no timeout to allow\n  compatibility with Tornado. (`#1817 <https://github.com/aio-libs/aiohttp/pull/1817>`_) (`#1180 <https://github.com/aio-libs/aiohttp/pull/1180>`_)\n- Add `proxy_from_env` to `ClientRequest` to read from environment\n  variables. (`#1791 <https://github.com/aio-libs/aiohttp/pull/1791>`_)\n- Add DummyCookieJar helper. (`#1830 <https://github.com/aio-libs/aiohttp/pull/1830>`_)\n- Fix assertion errors in Python 3.4 from noop helper. (`#1847 <https://github.com/aio-libs/aiohttp/pull/1847>`_)\n- Do not unquote `+` in match_info values (`#1816 <https://github.com/aio-libs/aiohttp/pull/1816>`_)\n- Use Forwarded, X-Forwarded-Scheme and X-Forwarded-Host for better scheme and\n  host resolution. (`#1134 <https://github.com/aio-libs/aiohttp/pull/1134>`_)\n- Fix sub-application middlewares resolution order (`#1853 <https://github.com/aio-libs/aiohttp/pull/1853>`_)\n- Fix applications comparison (`#1866 <https://github.com/aio-libs/aiohttp/pull/1866>`_)\n- Fix static location in index when prefix is used (`#1662 <https://github.com/aio-libs/aiohttp/pull/1662>`_)\n- Make test server more reliable (`#1896 <https://github.com/aio-libs/aiohttp/pull/1896>`_)\n- Extend list of web exceptions, add HTTPUnprocessableEntity,\n  HTTPFailedDependency, HTTPInsufficientStorage status codes (`#1920 <https://github.com/aio-libs/aiohttp/pull/1920>`_)\n\n\n\n----\n\n\n2.0.7 (2017-04-12)\n==================\n\n- Fix *pypi* distribution\n- Fix exception description (`#1807 <https://github.com/aio-libs/aiohttp/pull/1807>`_)\n- Handle socket error in FileResponse (`#1773 <https://github.com/aio-libs/aiohttp/pull/1773>`_)\n- Cancel websocket heartbeat on close (`#1793 <https://github.com/aio-libs/aiohttp/pull/1793>`_)\n\n\n\n----\n\n\n2.0.6 (2017-04-04)\n==================\n\n- Keeping blank values for `request.post()` and `multipart.form()` (`#1765 <https://github.com/aio-libs/aiohttp/pull/1765>`_)\n- TypeError in data_received of ResponseHandler (`#1770 <https://github.com/aio-libs/aiohttp/pull/1770>`_)\n- Fix ``web.run_app`` not to bind to default host-port pair if only socket is\n  passed (`#1786 <https://github.com/aio-libs/aiohttp/pull/1786>`_)\n\n\n\n----\n\n\n2.0.5 (2017-03-29)\n==================\n\n- Memory leak with aiohttp.request (`#1756 <https://github.com/aio-libs/aiohttp/pull/1756>`_)\n- Disable cleanup closed ssl transports by default.\n- Exception in request handling if the server responds before the body\n  is sent (`#1761 <https://github.com/aio-libs/aiohttp/pull/1761>`_)\n\n\n\n----\n\n\n2.0.4 (2017-03-27)\n==================\n\n- Memory leak with aiohttp.request (`#1756 <https://github.com/aio-libs/aiohttp/pull/1756>`_)\n- Encoding is always UTF-8 in POST data (`#1750 <https://github.com/aio-libs/aiohttp/pull/1750>`_)\n- Do not add \"Content-Disposition\" header by default (`#1755 <https://github.com/aio-libs/aiohttp/pull/1755>`_)\n\n\n\n----\n\n\n2.0.3 (2017-03-24)\n==================\n\n- Call https website through proxy will cause error (`#1745 <https://github.com/aio-libs/aiohttp/pull/1745>`_)\n- Fix exception on multipart/form-data post if content-type is not set (`#1743 <https://github.com/aio-libs/aiohttp/pull/1743>`_)\n\n\n\n----\n\n\n2.0.2 (2017-03-21)\n==================\n\n- Fixed Application.on_loop_available signal (`#1739 <https://github.com/aio-libs/aiohttp/pull/1739>`_)\n- Remove debug code\n\n\n\n----\n\n\n2.0.1 (2017-03-21)\n==================\n\n- Fix allow-head to include name on route (`#1737 <https://github.com/aio-libs/aiohttp/pull/1737>`_)\n- Fixed AttributeError in WebSocketResponse.can_prepare (`#1736 <https://github.com/aio-libs/aiohttp/pull/1736>`_)\n\n\n\n----\n\n\n2.0.0 (2017-03-20)\n==================\n\n- Added `json` to `ClientSession.request()` method (`#1726 <https://github.com/aio-libs/aiohttp/pull/1726>`_)\n- Added session's `raise_for_status` parameter, automatically calls\n  raise_for_status() on any request. (`#1724 <https://github.com/aio-libs/aiohttp/pull/1724>`_)\n- `response.json()` raises `ClientResponseError` exception if response's\n  content type does not match (`#1723 <https://github.com/aio-libs/aiohttp/pull/1723>`_)\n  - Cleanup timer and loop handle on any client exception.\n- Deprecate `loop` parameter for Application's constructor\n- Properly handle payload errors (`#1710 <https://github.com/aio-libs/aiohttp/pull/1710>`_)\n- Added `ClientWebSocketResponse.get_extra_info()` (`#1717 <https://github.com/aio-libs/aiohttp/pull/1717>`_)\n- It is not possible to combine Transfer-Encoding and chunked parameter,\n  same for compress and Content-Encoding (`#1655 <https://github.com/aio-libs/aiohttp/pull/1655>`_)\n- Connector's `limit` parameter indicates total concurrent connections.\n  New `limit_per_host` added, indicates total connections per endpoint. (`#1601 <https://github.com/aio-libs/aiohttp/pull/1601>`_)\n- Use url's `raw_host` for name resolution (`#1685 <https://github.com/aio-libs/aiohttp/pull/1685>`_)\n- Change `ClientResponse.url` to `yarl.URL` instance (`#1654 <https://github.com/aio-libs/aiohttp/pull/1654>`_)\n- Add max_size parameter to web.Request reading methods (`#1133 <https://github.com/aio-libs/aiohttp/pull/1133>`_)\n- Web Request.post() stores data in temp files (`#1469 <https://github.com/aio-libs/aiohttp/pull/1469>`_)\n- Add the `allow_head=True` keyword argument for `add_get` (`#1618 <https://github.com/aio-libs/aiohttp/pull/1618>`_)\n- `run_app` and the Command Line Interface now support serving over\n  Unix domain sockets for faster inter-process communication.\n- `run_app` now supports passing a preexisting socket object. This can be useful\n  e.g. for socket-based activated applications, when binding of a socket is\n  done by the parent process.\n- Implementation for Trailer headers parser is broken (`#1619 <https://github.com/aio-libs/aiohttp/pull/1619>`_)\n- Fix FileResponse to not fall on bad request (range out of file size)\n- Fix FileResponse to correct stream video to Chromes\n- Deprecate public low-level api (`#1657 <https://github.com/aio-libs/aiohttp/pull/1657>`_)\n- Deprecate `encoding` parameter for ClientSession.request() method\n- Dropped aiohttp.wsgi (`#1108 <https://github.com/aio-libs/aiohttp/pull/1108>`_)\n- Dropped `version` from ClientSession.request() method\n- Dropped websocket version 76 support (`#1160 <https://github.com/aio-libs/aiohttp/pull/1160>`_)\n- Dropped: `aiohttp.protocol.HttpPrefixParser`  (`#1590 <https://github.com/aio-libs/aiohttp/pull/1590>`_)\n- Dropped: Servers response's `.started`, `.start()` and\n  `.can_start()` method (`#1591 <https://github.com/aio-libs/aiohttp/pull/1591>`_)\n- Dropped:  Adding `sub app` via `app.router.add_subapp()` is deprecated\n  use `app.add_subapp()` instead (`#1592 <https://github.com/aio-libs/aiohttp/pull/1592>`_)\n- Dropped: `Application.finish()` and `Application.register_on_finish()` (`#1602 <https://github.com/aio-libs/aiohttp/pull/1602>`_)\n- Dropped: `web.Request.GET` and `web.Request.POST`\n- Dropped: aiohttp.get(), aiohttp.options(), aiohttp.head(),\n  aiohttp.post(), aiohttp.put(), aiohttp.patch(), aiohttp.delete(), and\n  aiohttp.ws_connect() (`#1593 <https://github.com/aio-libs/aiohttp/pull/1593>`_)\n- Dropped: `aiohttp.web.WebSocketResponse.receive_msg()` (`#1605 <https://github.com/aio-libs/aiohttp/pull/1605>`_)\n- Dropped: `ServerHttpProtocol.keep_alive_timeout` attribute and\n  `keep-alive`, `keep_alive_on`, `timeout`, `log` constructor parameters (`#1606 <https://github.com/aio-libs/aiohttp/pull/1606>`_)\n- Dropped: `TCPConnector's`` `.resolve`, `.resolved_hosts`,\n  `.clear_resolved_hosts()` attributes and `resolve` constructor\n  parameter (`#1607 <https://github.com/aio-libs/aiohttp/pull/1607>`_)\n- Dropped `ProxyConnector` (`#1609 <https://github.com/aio-libs/aiohttp/pull/1609>`_)\n\n\n\n----\n\n\n1.3.5 (2017-03-16)\n==================\n\n- Fixed None timeout support (`#1720 <https://github.com/aio-libs/aiohttp/pull/1720>`_)\n\n\n\n----\n\n\n1.3.4 (2017-03-14)\n==================\n\n- Revert timeout handling in client request\n- Fix StreamResponse representation after eof\n- Fix file_sender to not fall on bad request (range out of file size)\n- Fix file_sender to correct stream video to Chromes\n- Fix NotImplementedError server exception (`#1703 <https://github.com/aio-libs/aiohttp/pull/1703>`_)\n- Clearer error message for URL without a host name. (`#1691 <https://github.com/aio-libs/aiohttp/pull/1691>`_)\n- Silence deprecation warning in __repr__ (`#1690 <https://github.com/aio-libs/aiohttp/pull/1690>`_)\n- IDN + HTTPS = `ssl.CertificateError` (`#1685 <https://github.com/aio-libs/aiohttp/pull/1685>`_)\n\n\n\n----\n\n\n1.3.3 (2017-02-19)\n==================\n\n- Fixed memory leak in time service (`#1656 <https://github.com/aio-libs/aiohttp/pull/1656>`_)\n\n\n\n----\n\n\n1.3.2 (2017-02-16)\n==================\n\n- Awaiting on WebSocketResponse.send_* does not work (`#1645 <https://github.com/aio-libs/aiohttp/pull/1645>`_)\n- Fix multiple calls to client ws_connect when using a shared header\n  dict (`#1643 <https://github.com/aio-libs/aiohttp/pull/1643>`_)\n- Make CookieJar.filter_cookies() accept plain string parameter. (`#1636 <https://github.com/aio-libs/aiohttp/pull/1636>`_)\n\n\n\n----\n\n\n1.3.1 (2017-02-09)\n==================\n\n- Handle CLOSING in WebSocketResponse.__anext__\n- Fixed AttributeError 'drain' for server websocket handler (`#1613 <https://github.com/aio-libs/aiohttp/pull/1613>`_)\n\n\n\n----\n\n\n1.3.0 (2017-02-08)\n==================\n\n- Multipart writer validates the data on append instead of on a\n  request send (`#920 <https://github.com/aio-libs/aiohttp/pull/920>`_)\n- Multipart reader accepts multipart messages with or without their epilogue\n  to consistently handle valid and legacy behaviors (`#1526 <https://github.com/aio-libs/aiohttp/pull/1526>`_) (`#1581 <https://github.com/aio-libs/aiohttp/pull/1581>`_)\n- Separate read + connect + request timeouts # 1523\n- Do not swallow Upgrade header (`#1587 <https://github.com/aio-libs/aiohttp/pull/1587>`_)\n- Fix polls demo run application (`#1487 <https://github.com/aio-libs/aiohttp/pull/1487>`_)\n- Ignore unknown 1XX status codes in client (`#1353 <https://github.com/aio-libs/aiohttp/pull/1353>`_)\n- Fix sub-Multipart messages missing their headers on serialization (`#1525 <https://github.com/aio-libs/aiohttp/pull/1525>`_)\n- Do not use readline when reading the content of a part\n  in the multipart reader (`#1535 <https://github.com/aio-libs/aiohttp/pull/1535>`_)\n- Add optional flag for quoting `FormData` fields (`#916 <https://github.com/aio-libs/aiohttp/pull/916>`_)\n- 416 Range Not Satisfiable if requested range end > file size (`#1588 <https://github.com/aio-libs/aiohttp/pull/1588>`_)\n- Having a `:` or `@` in a route does not work (`#1552 <https://github.com/aio-libs/aiohttp/pull/1552>`_)\n- Added `receive_timeout` timeout for websocket to receive complete\n  message. (`#1325 <https://github.com/aio-libs/aiohttp/pull/1325>`_)\n- Added `heartbeat` parameter for websocket to automatically send\n  `ping` message. (`#1024 <https://github.com/aio-libs/aiohttp/pull/1024>`_) (`#777 <https://github.com/aio-libs/aiohttp/pull/777>`_)\n- Remove `web.Application` dependency from `web.UrlDispatcher` (`#1510 <https://github.com/aio-libs/aiohttp/pull/1510>`_)\n- Accepting back-pressure from slow websocket clients (`#1367 <https://github.com/aio-libs/aiohttp/pull/1367>`_)\n- Do not pause transport during set_parser stage (`#1211 <https://github.com/aio-libs/aiohttp/pull/1211>`_)\n- Lingering close does not terminate before timeout (`#1559 <https://github.com/aio-libs/aiohttp/pull/1559>`_)\n- `setsockopt` may raise `OSError` exception if socket is closed already (`#1595 <https://github.com/aio-libs/aiohttp/pull/1595>`_)\n- Lots of CancelledError when requests are interrupted (`#1565 <https://github.com/aio-libs/aiohttp/pull/1565>`_)\n- Allow users to specify what should happen to decoding errors\n  when calling a responses `text()` method (`#1542 <https://github.com/aio-libs/aiohttp/pull/1542>`_)\n- Back port std module `http.cookies` for python3.4.2 (`#1566 <https://github.com/aio-libs/aiohttp/pull/1566>`_)\n- Maintain url's fragment in client response (`#1314 <https://github.com/aio-libs/aiohttp/pull/1314>`_)\n- Allow concurrently close WebSocket connection (`#754 <https://github.com/aio-libs/aiohttp/pull/754>`_)\n- Gzipped responses with empty body raises ContentEncodingError (`#609 <https://github.com/aio-libs/aiohttp/pull/609>`_)\n- Return 504 if request handle raises TimeoutError.\n- Refactor how we use keep-alive and close lingering timeouts.\n- Close response connection if we can not consume whole http\n  message during client response release\n- Abort closed ssl client transports, broken servers can keep socket\n  open un-limit time (`#1568 <https://github.com/aio-libs/aiohttp/pull/1568>`_)\n- Log warning instead of `RuntimeError` is websocket connection is closed.\n- Deprecated: `aiohttp.protocol.HttpPrefixParser`\n  will be removed in 1.4 (`#1590 <https://github.com/aio-libs/aiohttp/pull/1590>`_)\n- Deprecated: Servers response's `.started`, `.start()` and\n  `.can_start()` method will be removed in 1.4 (`#1591 <https://github.com/aio-libs/aiohttp/pull/1591>`_)\n- Deprecated: Adding `sub app` via `app.router.add_subapp()` is deprecated\n  use `app.add_subapp()` instead, will be removed in 1.4 (`#1592 <https://github.com/aio-libs/aiohttp/pull/1592>`_)\n- Deprecated: aiohttp.get(), aiohttp.options(), aiohttp.head(), aiohttp.post(),\n  aiohttp.put(), aiohttp.patch(), aiohttp.delete(), and aiohttp.ws_connect()\n  will be removed in 1.4 (`#1593 <https://github.com/aio-libs/aiohttp/pull/1593>`_)\n- Deprecated: `Application.finish()` and `Application.register_on_finish()`\n  will be removed in 1.4 (`#1602 <https://github.com/aio-libs/aiohttp/pull/1602>`_)\n\n\n\n----\n\n\n1.2.0 (2016-12-17)\n==================\n\n- Extract `BaseRequest` from `web.Request`, introduce `web.Server`\n  (former `RequestHandlerFactory`), introduce new low-level web server\n  which is not coupled with `web.Application` and routing (`#1362 <https://github.com/aio-libs/aiohttp/pull/1362>`_)\n- Make `TestServer.make_url` compatible with `yarl.URL` (`#1389 <https://github.com/aio-libs/aiohttp/pull/1389>`_)\n- Implement range requests for static files (`#1382 <https://github.com/aio-libs/aiohttp/pull/1382>`_)\n- Support task attribute for StreamResponse (`#1410 <https://github.com/aio-libs/aiohttp/pull/1410>`_)\n- Drop `TestClient.app` property, use `TestClient.server.app` instead\n  (BACKWARD INCOMPATIBLE)\n- Drop `TestClient.handler` property, use `TestClient.server.handler` instead\n  (BACKWARD INCOMPATIBLE)\n- `TestClient.server` property returns a test server instance, was\n  `asyncio.AbstractServer` (BACKWARD INCOMPATIBLE)\n- Follow gunicorn's signal semantics in `Gunicorn[UVLoop]WebWorker` (`#1201 <https://github.com/aio-libs/aiohttp/pull/1201>`_)\n- Call worker_int and worker_abort callbacks in\n  `Gunicorn[UVLoop]WebWorker` (`#1202 <https://github.com/aio-libs/aiohttp/pull/1202>`_)\n- Has functional tests for client proxy (`#1218 <https://github.com/aio-libs/aiohttp/pull/1218>`_)\n- Fix bugs with client proxy target path and proxy host with port (`#1413 <https://github.com/aio-libs/aiohttp/pull/1413>`_)\n- Fix bugs related to the use of unicode hostnames (`#1444 <https://github.com/aio-libs/aiohttp/pull/1444>`_)\n- Preserve cookie quoting/escaping (`#1453 <https://github.com/aio-libs/aiohttp/pull/1453>`_)\n- FileSender will send gzipped response if gzip version available (`#1426 <https://github.com/aio-libs/aiohttp/pull/1426>`_)\n- Don't override `Content-Length` header in `web.Response` if no body\n  was set (`#1400 <https://github.com/aio-libs/aiohttp/pull/1400>`_)\n- Introduce `router.post_init()` for solving (`#1373 <https://github.com/aio-libs/aiohttp/pull/1373>`_)\n- Fix raise error in case of multiple calls of `TimeServive.stop()`\n- Allow to raise web exceptions on router resolving stage (`#1460 <https://github.com/aio-libs/aiohttp/pull/1460>`_)\n- Add a warning for session creation outside of coroutine (`#1468 <https://github.com/aio-libs/aiohttp/pull/1468>`_)\n- Avoid a race when application might start accepting incoming requests\n  but startup signals are not processed yet e98e8c6\n- Raise a `RuntimeError` when trying to change the status of the HTTP response\n  after the headers have been sent (`#1480 <https://github.com/aio-libs/aiohttp/pull/1480>`_)\n- Fix bug with https proxy acquired cleanup (`#1340 <https://github.com/aio-libs/aiohttp/pull/1340>`_)\n- Use UTF-8 as the default encoding for multipart text parts (`#1484 <https://github.com/aio-libs/aiohttp/pull/1484>`_)\n\n\n\n----\n\n\n1.1.6 (2016-11-28)\n==================\n\n- Fix `BodyPartReader.read_chunk` bug about returns zero bytes before\n  `EOF` (`#1428 <https://github.com/aio-libs/aiohttp/pull/1428>`_)\n\n\n\n----\n\n1.1.5 (2016-11-16)\n==================\n\n- Fix static file serving in fallback mode (`#1401 <https://github.com/aio-libs/aiohttp/pull/1401>`_)\n\n\n\n----\n\n1.1.4 (2016-11-14)\n==================\n\n- Make `TestServer.make_url` compatible with `yarl.URL` (`#1389 <https://github.com/aio-libs/aiohttp/pull/1389>`_)\n- Generate informative exception on redirects from server which\n  does not provide redirection headers (`#1396 <https://github.com/aio-libs/aiohttp/pull/1396>`_)\n\n\n\n----\n\n\n1.1.3 (2016-11-10)\n==================\n\n- Support *root* resources for sub-applications (`#1379 <https://github.com/aio-libs/aiohttp/pull/1379>`_)\n\n\n\n----\n\n\n1.1.2 (2016-11-08)\n==================\n\n- Allow starting variables with an underscore (`#1379 <https://github.com/aio-libs/aiohttp/pull/1379>`_)\n- Properly process UNIX sockets by gunicorn worker (`#1375 <https://github.com/aio-libs/aiohttp/pull/1375>`_)\n- Fix ordering for `FrozenList`\n- Don't propagate pre and post signals to sub-application (`#1377 <https://github.com/aio-libs/aiohttp/pull/1377>`_)\n\n\n\n----\n\n1.1.1 (2016-11-04)\n==================\n\n- Fix documentation generation (`#1120 <https://github.com/aio-libs/aiohttp/pull/1120>`_)\n\n\n\n----\n\n1.1.0 (2016-11-03)\n==================\n\n- Drop deprecated `WSClientDisconnectedError` (BACKWARD INCOMPATIBLE)\n- Use `yarl.URL` in client API. The change is 99% backward compatible\n  but `ClientResponse.url` is an `yarl.URL` instance now. (`#1217 <https://github.com/aio-libs/aiohttp/pull/1217>`_)\n- Close idle keep-alive connections on shutdown (`#1222 <https://github.com/aio-libs/aiohttp/pull/1222>`_)\n- Modify regex in AccessLogger to accept underscore and numbers (`#1225 <https://github.com/aio-libs/aiohttp/pull/1225>`_)\n- Use `yarl.URL` in web server API. `web.Request.rel_url` and `web.Request.url` are added. URLs and templates are\n  percent-encoded now. (`#1224 <https://github.com/aio-libs/aiohttp/pull/1224>`_)\n- Accept `yarl.URL` by server redirections (`#1278 <https://github.com/aio-libs/aiohttp/pull/1278>`_)\n- Return `yarl.URL` by `.make_url()` testing utility (`#1279 <https://github.com/aio-libs/aiohttp/pull/1279>`_)\n- Properly format IPv6 addresses by `aiohttp.web.run_app` (`#1139 <https://github.com/aio-libs/aiohttp/pull/1139>`_)\n- Use `yarl.URL` by server API (`#1288 <https://github.com/aio-libs/aiohttp/pull/1288>`_)\n\n  * Introduce `resource.url_for()`, deprecate `resource.url()`.\n  * Implement `StaticResource`.\n  * Inherit `SystemRoute` from `AbstractRoute`\n  * Drop old-style routes: `Route`, `PlainRoute`, `DynamicRoute`,\n    `StaticRoute`, `ResourceAdapter`.\n- Revert `resp.url` back to `str`, introduce `resp.url_obj` (`#1292 <https://github.com/aio-libs/aiohttp/pull/1292>`_)\n- Raise ValueError if BasicAuth login has a \":\" character (`#1307 <https://github.com/aio-libs/aiohttp/pull/1307>`_)\n- Fix bug when ClientRequest send payload file with opened as\n  open('filename', 'r+b') (`#1306 <https://github.com/aio-libs/aiohttp/pull/1306>`_)\n- Enhancement to AccessLogger (pass *extra* dict) (`#1303 <https://github.com/aio-libs/aiohttp/pull/1303>`_)\n- Show more verbose message on import errors (`#1319 <https://github.com/aio-libs/aiohttp/pull/1319>`_)\n- Added save and load functionality for `CookieJar` (`#1219 <https://github.com/aio-libs/aiohttp/pull/1219>`_)\n- Added option on `StaticRoute` to follow symlinks (`#1299 <https://github.com/aio-libs/aiohttp/pull/1299>`_)\n- Force encoding of `application/json` content type to utf-8 (`#1339 <https://github.com/aio-libs/aiohttp/pull/1339>`_)\n- Fix invalid invocations of `errors.LineTooLong` (`#1335 <https://github.com/aio-libs/aiohttp/pull/1335>`_)\n- Websockets: Stop `async for` iteration when connection is closed (`#1144 <https://github.com/aio-libs/aiohttp/pull/1144>`_)\n- Ensure TestClient HTTP methods return a context manager (`#1318 <https://github.com/aio-libs/aiohttp/pull/1318>`_)\n- Raise `ClientDisconnectedError` to `FlowControlStreamReader` read function\n  if `ClientSession` object is closed by client when reading data. (`#1323 <https://github.com/aio-libs/aiohttp/pull/1323>`_)\n- Document deployment without `Gunicorn` (`#1120 <https://github.com/aio-libs/aiohttp/pull/1120>`_)\n- Add deprecation warning for MD5 and SHA1 digests when used for fingerprint\n  of site certs in TCPConnector. (`#1186 <https://github.com/aio-libs/aiohttp/pull/1186>`_)\n- Implement sub-applications (`#1301 <https://github.com/aio-libs/aiohttp/pull/1301>`_)\n- Don't inherit `web.Request` from `dict` but implement\n  `MutableMapping` protocol.\n- Implement frozen signals\n- Don't inherit `web.Application` from `dict` but implement\n  `MutableMapping` protocol.\n- Support freezing for web applications\n- Accept access_log parameter in `web.run_app`, use `None` to disable logging\n- Don't flap `tcp_cork` and `tcp_nodelay` in regular request handling.\n  `tcp_nodelay` is still enabled by default.\n- Improve performance of web server by removing premature computing of\n  Content-Type if the value was set by `web.Response` constructor.\n\n  While the patch boosts speed of trivial `web.Response(text='OK',\n  content_type='text/plain)` very well please don't expect significant\n  boost of your application -- a couple DB requests and business logic\n  is still the main bottleneck.\n- Boost performance by adding a custom time service (`#1350 <https://github.com/aio-libs/aiohttp/pull/1350>`_)\n- Extend `ClientResponse` with `content_type` and `charset`\n  properties like in `web.Request`. (`#1349 <https://github.com/aio-libs/aiohttp/pull/1349>`_)\n- Disable aiodns by default (`#559 <https://github.com/aio-libs/aiohttp/pull/559>`_)\n- Don't flap `tcp_cork` in client code, use TCP_NODELAY mode by default.\n- Implement `web.Request.clone()` (`#1361 <https://github.com/aio-libs/aiohttp/pull/1361>`_)\n\n\n\n----\n\n1.0.5 (2016-10-11)\n==================\n\n- Fix StreamReader._read_nowait to return all available\n  data up to the requested amount (`#1297 <https://github.com/aio-libs/aiohttp/pull/1297>`_)\n\n\n\n----\n\n\n1.0.4 (2016-09-22)\n==================\n\n- Fix FlowControlStreamReader.read_nowait so that it checks\n  whether the transport is paused (`#1206 <https://github.com/aio-libs/aiohttp/pull/1206>`_)\n\n\n\n----\n\n\n1.0.2 (2016-09-22)\n==================\n\n- Make CookieJar compatible with 32-bit systems (`#1188 <https://github.com/aio-libs/aiohttp/pull/1188>`_)\n- Add missing `WSMsgType` to `web_ws.__all__`, see (`#1200 <https://github.com/aio-libs/aiohttp/pull/1200>`_)\n- Fix `CookieJar` ctor when called with `loop=None` (`#1203 <https://github.com/aio-libs/aiohttp/pull/1203>`_)\n- Fix broken upper-casing in wsgi support (`#1197 <https://github.com/aio-libs/aiohttp/pull/1197>`_)\n\n\n\n----\n\n\n1.0.1 (2016-09-16)\n==================\n\n- Restore `aiohttp.web.MsgType` alias for `aiohttp.WSMsgType` for sake\n  of backward compatibility (`#1178 <https://github.com/aio-libs/aiohttp/pull/1178>`_)\n- Tune alabaster schema.\n- Use `text/html` content type for displaying index pages by static\n  file handler.\n- Fix `AssertionError` in static file handling (`#1177 <https://github.com/aio-libs/aiohttp/pull/1177>`_)\n- Fix access log formats `%O` and `%b` for static file handling\n- Remove `debug` setting of GunicornWorker, use `app.debug`\n  to control its debug-mode instead\n\n\n\n----\n\n\n1.0.0 (2016-09-16)\n==================\n\n- Change default size for client session's connection pool from\n  unlimited to 20 (`#977 <https://github.com/aio-libs/aiohttp/pull/977>`_)\n- Add IE support for cookie deletion. (`#994 <https://github.com/aio-libs/aiohttp/pull/994>`_)\n- Remove deprecated `WebSocketResponse.wait_closed` method (BACKWARD\n  INCOMPATIBLE)\n- Remove deprecated `force` parameter for `ClientResponse.close`\n  method (BACKWARD INCOMPATIBLE)\n- Avoid using of mutable CIMultiDict kw param in make_mocked_request\n  (`#997 <https://github.com/aio-libs/aiohttp/pull/997>`_)\n- Make WebSocketResponse.close a little bit faster by avoiding new\n  task creating just for timeout measurement\n- Add `proxy` and `proxy_auth` params to `client.get()` and family,\n  deprecate `ProxyConnector` (`#998 <https://github.com/aio-libs/aiohttp/pull/998>`_)\n- Add support for websocket send_json and receive_json, synchronize\n  server and client API for websockets (`#984 <https://github.com/aio-libs/aiohttp/pull/984>`_)\n- Implement router shourtcuts for most useful HTTP methods, use\n  `app.router.add_get()`, `app.router.add_post()` etc. instead of\n  `app.router.add_route()` (`#986 <https://github.com/aio-libs/aiohttp/pull/986>`_)\n- Support SSL connections for gunicorn worker (`#1003 <https://github.com/aio-libs/aiohttp/pull/1003>`_)\n- Move obsolete examples to legacy folder\n- Switch to multidict 2.0 and title-cased strings (`#1015 <https://github.com/aio-libs/aiohttp/pull/1015>`_)\n- `{FOO}e` logger format is case-sensitive now\n- Fix logger report for unix socket 8e8469b\n- Rename aiohttp.websocket to aiohttp._ws_impl\n- Rename ``aiohttp.MsgType`` to ``aiohttp.WSMsgType``\n- Introduce ``aiohttp.WSMessage`` officially\n- Rename Message -> WSMessage\n- Remove deprecated decode param from resp.read(decode=True)\n- Use 5min default client timeout (`#1028 <https://github.com/aio-libs/aiohttp/pull/1028>`_)\n- Relax HTTP method validation in UrlDispatcher (`#1037 <https://github.com/aio-libs/aiohttp/pull/1037>`_)\n- Pin minimal supported asyncio version to 3.4.2+ (`loop.is_close()`\n  should be present)\n- Remove aiohttp.websocket module (BACKWARD INCOMPATIBLE)\n  Please use high-level client and server approaches\n- Link header for 451 status code is mandatory\n- Fix test_client fixture to allow multiple clients per test (`#1072 <https://github.com/aio-libs/aiohttp/pull/1072>`_)\n- make_mocked_request now accepts dict as headers (`#1073 <https://github.com/aio-libs/aiohttp/pull/1073>`_)\n- Add Python 3.5.2/3.6+ compatibility patch for async generator\n  protocol change (`#1082 <https://github.com/aio-libs/aiohttp/pull/1082>`_)\n- Improvement test_client can accept instance object (`#1083 <https://github.com/aio-libs/aiohttp/pull/1083>`_)\n- Simplify ServerHttpProtocol implementation (`#1060 <https://github.com/aio-libs/aiohttp/pull/1060>`_)\n- Add a flag for optional showing directory index for static file\n  handling (`#921 <https://github.com/aio-libs/aiohttp/pull/921>`_)\n- Define `web.Application.on_startup()` signal handler (`#1103 <https://github.com/aio-libs/aiohttp/pull/1103>`_)\n- Drop ChunkedParser and LinesParser (`#1111 <https://github.com/aio-libs/aiohttp/pull/1111>`_)\n- Call `Application.startup` in GunicornWebWorker (`#1105 <https://github.com/aio-libs/aiohttp/pull/1105>`_)\n- Fix client handling hostnames with 63 bytes when a port is given in\n  the url (`#1044 <https://github.com/aio-libs/aiohttp/pull/1044>`_)\n- Implement proxy support for ClientSession.ws_connect (`#1025 <https://github.com/aio-libs/aiohttp/pull/1025>`_)\n- Return named tuple from WebSocketResponse.can_prepare (`#1016 <https://github.com/aio-libs/aiohttp/pull/1016>`_)\n- Fix access_log_format in `GunicornWebWorker` (`#1117 <https://github.com/aio-libs/aiohttp/pull/1117>`_)\n- Setup Content-Type to application/octet-stream by default (`#1124 <https://github.com/aio-libs/aiohttp/pull/1124>`_)\n- Deprecate debug parameter from app.make_handler(), use\n  `Application(debug=True)` instead (`#1121 <https://github.com/aio-libs/aiohttp/pull/1121>`_)\n- Remove fragment string in request path (`#846 <https://github.com/aio-libs/aiohttp/pull/846>`_)\n- Use aiodns.DNSResolver.gethostbyname() if available (`#1136 <https://github.com/aio-libs/aiohttp/pull/1136>`_)\n- Fix static file sending on uvloop when sendfile is available (`#1093 <https://github.com/aio-libs/aiohttp/pull/1093>`_)\n- Make prettier urls if query is empty dict (`#1143 <https://github.com/aio-libs/aiohttp/pull/1143>`_)\n- Fix redirects for HEAD requests (`#1147 <https://github.com/aio-libs/aiohttp/pull/1147>`_)\n- Default value for `StreamReader.read_nowait` is -1 from now (`#1150 <https://github.com/aio-libs/aiohttp/pull/1150>`_)\n- `aiohttp.StreamReader` is not inherited from `asyncio.StreamReader` from now\n  (BACKWARD INCOMPATIBLE) (`#1150 <https://github.com/aio-libs/aiohttp/pull/1150>`_)\n- Streams documentation added (`#1150 <https://github.com/aio-libs/aiohttp/pull/1150>`_)\n- Add `multipart` coroutine method for web Request object (`#1067 <https://github.com/aio-libs/aiohttp/pull/1067>`_)\n- Publish ClientSession.loop property (`#1149 <https://github.com/aio-libs/aiohttp/pull/1149>`_)\n- Fix static file with spaces (`#1140 <https://github.com/aio-libs/aiohttp/pull/1140>`_)\n- Fix piling up asyncio loop by cookie expiration callbacks (`#1061 <https://github.com/aio-libs/aiohttp/pull/1061>`_)\n- Drop `Timeout` class for sake of `async_timeout` external library.\n  `aiohttp.Timeout` is an alias for `async_timeout.timeout`\n- `use_dns_cache` parameter of `aiohttp.TCPConnector` is `True` by\n  default (BACKWARD INCOMPATIBLE) (`#1152 <https://github.com/aio-libs/aiohttp/pull/1152>`_)\n- `aiohttp.TCPConnector` uses asynchronous DNS resolver if available by\n  default (BACKWARD INCOMPATIBLE) (`#1152 <https://github.com/aio-libs/aiohttp/pull/1152>`_)\n- Conform to RFC3986 - do not include url fragments in client requests (`#1174 <https://github.com/aio-libs/aiohttp/pull/1174>`_)\n- Drop `ClientSession.cookies` (BACKWARD INCOMPATIBLE) (`#1173 <https://github.com/aio-libs/aiohttp/pull/1173>`_)\n- Refactor `AbstractCookieJar` public API (BACKWARD INCOMPATIBLE) (`#1173 <https://github.com/aio-libs/aiohttp/pull/1173>`_)\n- Fix clashing cookies with have the same name but belong to different\n  domains (BACKWARD INCOMPATIBLE) (`#1125 <https://github.com/aio-libs/aiohttp/pull/1125>`_)\n- Support binary Content-Transfer-Encoding (`#1169 <https://github.com/aio-libs/aiohttp/pull/1169>`_)\n\n\n\n----\n\n\n0.22.5 (08-02-2016)\n===================\n\n- Pin miltidict version to >=1.2.2\n\n\n\n----\n\n0.22.3 (07-26-2016)\n===================\n\n- Do not filter cookies if unsafe flag provided (`#1005 <https://github.com/aio-libs/aiohttp/pull/1005>`_)\n\n\n\n----\n\n\n0.22.2 (07-23-2016)\n===================\n\n- Suppress CancelledError when Timeout raises TimeoutError (`#970 <https://github.com/aio-libs/aiohttp/pull/970>`_)\n- Don't expose `aiohttp.__version__`\n- Add unsafe parameter to CookieJar (`#968 <https://github.com/aio-libs/aiohttp/pull/968>`_)\n- Use unsafe cookie jar in test client tools\n- Expose aiohttp.CookieJar name\n\n\n\n----\n\n\n0.22.1 (07-16-2016)\n===================\n\n- Large cookie expiration/max-age does not break an event loop from now\n  (fixes (`#967 <https://github.com/aio-libs/aiohttp/pull/967>`_))\n\n\n\n----\n\n\n0.22.0 (07-15-2016)\n===================\n\n- Fix bug in serving static directory (`#803 <https://github.com/aio-libs/aiohttp/pull/803>`_)\n- Fix command line arg parsing (`#797 <https://github.com/aio-libs/aiohttp/pull/797>`_)\n- Fix a documentation chapter about cookie usage (`#790 <https://github.com/aio-libs/aiohttp/pull/790>`_)\n- Handle empty body with gzipped encoding (`#758 <https://github.com/aio-libs/aiohttp/pull/758>`_)\n- Support 451 Unavailable For Legal Reasons http status  (`#697 <https://github.com/aio-libs/aiohttp/pull/697>`_)\n- Fix Cookie share example and few small typos in docs (`#817 <https://github.com/aio-libs/aiohttp/pull/817>`_)\n- UrlDispatcher.add_route with partial coroutine handler (`#814 <https://github.com/aio-libs/aiohttp/pull/814>`_)\n- Optional support for aiodns (`#728 <https://github.com/aio-libs/aiohttp/pull/728>`_)\n- Add ServiceRestart and TryAgainLater websocket close codes (`#828 <https://github.com/aio-libs/aiohttp/pull/828>`_)\n- Fix prompt message for `web.run_app` (`#832 <https://github.com/aio-libs/aiohttp/pull/832>`_)\n- Allow to pass None as a timeout value to disable timeout logic (`#834 <https://github.com/aio-libs/aiohttp/pull/834>`_)\n- Fix leak of connection slot during connection error (`#835 <https://github.com/aio-libs/aiohttp/pull/835>`_)\n- Gunicorn worker with uvloop support\n  `aiohttp.worker.GunicornUVLoopWebWorker` (`#878 <https://github.com/aio-libs/aiohttp/pull/878>`_)\n- Don't send body in response to HEAD request (`#838 <https://github.com/aio-libs/aiohttp/pull/838>`_)\n- Skip the preamble in MultipartReader (`#881 <https://github.com/aio-libs/aiohttp/pull/881>`_)\n- Implement BasicAuth decode classmethod. (`#744 <https://github.com/aio-libs/aiohttp/pull/744>`_)\n- Don't crash logger when transport is None (`#889 <https://github.com/aio-libs/aiohttp/pull/889>`_)\n- Use a create_future compatibility wrapper instead of creating\n  Futures directly (`#896 <https://github.com/aio-libs/aiohttp/pull/896>`_)\n- Add test utilities to aiohttp (`#902 <https://github.com/aio-libs/aiohttp/pull/902>`_)\n- Improve Request.__repr__ (`#875 <https://github.com/aio-libs/aiohttp/pull/875>`_)\n- Skip DNS resolving if provided host is already an ip address (`#874 <https://github.com/aio-libs/aiohttp/pull/874>`_)\n- Add headers to ClientSession.ws_connect (`#785 <https://github.com/aio-libs/aiohttp/pull/785>`_)\n- Document that server can send pre-compressed data (`#906 <https://github.com/aio-libs/aiohttp/pull/906>`_)\n- Don't add Content-Encoding and Transfer-Encoding if no body (`#891 <https://github.com/aio-libs/aiohttp/pull/891>`_)\n- Add json() convenience methods to websocket message objects (`#897 <https://github.com/aio-libs/aiohttp/pull/897>`_)\n- Add client_resp.raise_for_status() (`#908 <https://github.com/aio-libs/aiohttp/pull/908>`_)\n- Implement cookie filter (`#799 <https://github.com/aio-libs/aiohttp/pull/799>`_)\n- Include an example of middleware to handle error pages (`#909 <https://github.com/aio-libs/aiohttp/pull/909>`_)\n- Fix error handling in StaticFileMixin (`#856 <https://github.com/aio-libs/aiohttp/pull/856>`_)\n- Add mocked request helper (`#900 <https://github.com/aio-libs/aiohttp/pull/900>`_)\n- Fix empty ALLOW Response header for cls based View (`#929 <https://github.com/aio-libs/aiohttp/pull/929>`_)\n- Respect CONNECT method to implement a proxy server (`#847 <https://github.com/aio-libs/aiohttp/pull/847>`_)\n- Add pytest_plugin (`#914 <https://github.com/aio-libs/aiohttp/pull/914>`_)\n- Add tutorial\n- Add backlog option to support more than 128 (default value in\n  \"create_server\" function) concurrent connections (`#892 <https://github.com/aio-libs/aiohttp/pull/892>`_)\n- Allow configuration of header size limits (`#912 <https://github.com/aio-libs/aiohttp/pull/912>`_)\n- Separate sending file logic from StaticRoute dispatcher (`#901 <https://github.com/aio-libs/aiohttp/pull/901>`_)\n- Drop deprecated share_cookies connector option (BACKWARD INCOMPATIBLE)\n- Drop deprecated support for tuple as auth parameter.\n  Use aiohttp.BasicAuth instead (BACKWARD INCOMPATIBLE)\n- Remove deprecated `request.payload` property, use `content` instead.\n  (BACKWARD INCOMPATIBLE)\n- Drop all mentions about api changes in documentation for versions\n  older than 0.16\n- Allow to override default cookie jar (`#963 <https://github.com/aio-libs/aiohttp/pull/963>`_)\n- Add manylinux wheel builds\n- Dup a socket for sendfile usage (`#964 <https://github.com/aio-libs/aiohttp/pull/964>`_)\n\n\n\n----\n\n0.21.6 (05-05-2016)\n===================\n\n- Drop initial query parameters on redirects (`#853 <https://github.com/aio-libs/aiohttp/pull/853>`_)\n\n\n\n----\n\n\n0.21.5 (03-22-2016)\n===================\n\n- Fix command line arg parsing (`#797 <https://github.com/aio-libs/aiohttp/pull/797>`_)\n\n\n\n----\n\n0.21.4 (03-12-2016)\n===================\n\n- Fix ResourceAdapter: don't add method to allowed if resource is not\n  match (`#826 <https://github.com/aio-libs/aiohttp/pull/826>`_)\n- Fix Resource: append found method to returned allowed methods\n\n\n\n----\n\n0.21.2 (02-16-2016)\n===================\n\n- Fix a regression: support for handling ~/path in static file routes was\n  broken (`#782 <https://github.com/aio-libs/aiohttp/pull/782>`_)\n\n\n\n----\n\n0.21.1 (02-10-2016)\n===================\n\n- Make new resources classes public (`#767 <https://github.com/aio-libs/aiohttp/pull/767>`_)\n- Add `router.resources()` view\n- Fix cmd-line parameter names in doc\n\n\n\n----\n\n0.21.0 (02-04-2016)\n===================\n\n- Introduce on_shutdown signal (`#722 <https://github.com/aio-libs/aiohttp/pull/722>`_)\n- Implement raw input headers (`#726 <https://github.com/aio-libs/aiohttp/pull/726>`_)\n- Implement web.run_app utility function (`#734 <https://github.com/aio-libs/aiohttp/pull/734>`_)\n- Introduce on_cleanup signal\n- Deprecate Application.finish() / Application.register_on_finish() in favor of on_cleanup.\n- Get rid of bare aiohttp.request(), aiohttp.get() and family in docs (`#729 <https://github.com/aio-libs/aiohttp/pull/729>`_)\n- Deprecate bare aiohttp.request(), aiohttp.get() and family (`#729 <https://github.com/aio-libs/aiohttp/pull/729>`_)\n- Refactor keep-alive support (`#737 <https://github.com/aio-libs/aiohttp/pull/737>`_)\n\n  - Enable keepalive for HTTP 1.0 by default\n  - Disable it for HTTP 0.9 (who cares about 0.9, BTW?)\n  - For keepalived connections\n\n      - Send `Connection: keep-alive` for HTTP 1.0 only\n      - don't send `Connection` header for HTTP 1.1\n  - For non-keepalived connections\n\n      - Send `Connection: close` for HTTP 1.1 only\n      - don't send `Connection` header for HTTP 1.0\n- Add version parameter to ClientSession constructor,\n  deprecate it for session.request() and family (`#736 <https://github.com/aio-libs/aiohttp/pull/736>`_)\n- Enable access log by default (`#735 <https://github.com/aio-libs/aiohttp/pull/735>`_)\n- Deprecate app.router.register_route() (the method was not documented intentionally BTW).\n- Deprecate app.router.named_routes() in favor of app.router.named_resources()\n- route.add_static accepts pathlib.Path now (`#743 <https://github.com/aio-libs/aiohttp/pull/743>`_)\n- Add command line support: `$ python -m aiohttp.web package.main` (`#740 <https://github.com/aio-libs/aiohttp/pull/740>`_)\n- FAQ section was added to docs. Enjoy and fill free to contribute new topics\n- Add async context manager support to ClientSession\n- Document ClientResponse's host, method, url properties\n- Use CORK/NODELAY in client API (`#748 <https://github.com/aio-libs/aiohttp/pull/748>`_)\n- ClientSession.close and Connector.close are coroutines now\n- Close client connection on exception in ClientResponse.release()\n- Allow to read multipart parts without content-length specified (`#750 <https://github.com/aio-libs/aiohttp/pull/750>`_)\n- Add support for unix domain sockets to gunicorn worker (`#470 <https://github.com/aio-libs/aiohttp/pull/470>`_)\n- Add test for default Expect handler (`#601 <https://github.com/aio-libs/aiohttp/pull/601>`_)\n- Add the first demo project\n- Rename `loader` keyword argument in `web.Request.json` method. (`#646 <https://github.com/aio-libs/aiohttp/pull/646>`_)\n- Add local socket binding for TCPConnector (`#678 <https://github.com/aio-libs/aiohttp/pull/678>`_)\n\n\n\n----\n\n0.20.2 (01-07-2016)\n===================\n\n- Enable use of `await` for a class based view (`#717 <https://github.com/aio-libs/aiohttp/pull/717>`_)\n- Check address family to fill wsgi env properly (`#718 <https://github.com/aio-libs/aiohttp/pull/718>`_)\n- Fix memory leak in headers processing (thanks to Marco Paolini) (`#723 <https://github.com/aio-libs/aiohttp/pull/723>`_\n\n\n\n----)\n\n0.20.1 (12-30-2015)\n===================\n\n- Raise RuntimeError is Timeout context manager was used outside of\n  task context.\n- Add number of bytes to stream.read_nowait (`#700 <https://github.com/aio-libs/aiohttp/pull/700>`_)\n- Use X-FORWARDED-PROTO for wsgi.url_scheme when available\n\n\n\n----\n\n\n0.20.0 (12-28-2015)\n===================\n\n- Extend list of web exceptions, add HTTPMisdirectedRequest,\n  HTTPUpgradeRequired, HTTPPreconditionRequired, HTTPTooManyRequests,\n  HTTPRequestHeaderFieldsTooLarge, HTTPVariantAlsoNegotiates,\n  HTTPNotExtended, HTTPNetworkAuthenticationRequired status codes (`#644 <https://github.com/aio-libs/aiohttp/pull/644>`_)\n- Do not remove AUTHORIZATION header by WSGI handler (`#649 <https://github.com/aio-libs/aiohttp/pull/649>`_)\n- Fix broken support for https proxies with authentication (`#617 <https://github.com/aio-libs/aiohttp/pull/617>`_)\n- Get REMOTE_* and SEVER_* http vars from headers when listening on\n  unix socket (`#654 <https://github.com/aio-libs/aiohttp/pull/654>`_)\n- Add HTTP 308 support (`#663 <https://github.com/aio-libs/aiohttp/pull/663>`_)\n- Add Tf format (time to serve request in seconds, %06f format) to\n  access log (`#669 <https://github.com/aio-libs/aiohttp/pull/669>`_)\n- Remove one and a half years long deprecated\n  ClientResponse.read_and_close() method\n- Optimize chunked encoding: use a single syscall instead of 3 calls\n  on sending chunked encoded data\n- Use TCP_CORK and TCP_NODELAY to optimize network latency and\n  throughput (`#680 <https://github.com/aio-libs/aiohttp/pull/680>`_)\n- Websocket XOR performance improved (`#687 <https://github.com/aio-libs/aiohttp/pull/687>`_)\n- Avoid sending cookie attributes in Cookie header (`#613 <https://github.com/aio-libs/aiohttp/pull/613>`_)\n- Round server timeouts to seconds for grouping pending calls.  That\n  leads to less amount of poller syscalls e.g. epoll.poll(). (`#702 <https://github.com/aio-libs/aiohttp/pull/702>`_)\n- Close connection on websocket handshake error (`#703 <https://github.com/aio-libs/aiohttp/pull/703>`_)\n- Implement class based views (`#684 <https://github.com/aio-libs/aiohttp/pull/684>`_)\n- Add *headers* parameter to ws_connect() (`#709 <https://github.com/aio-libs/aiohttp/pull/709>`_)\n- Drop unused function `parse_remote_addr()` (`#708 <https://github.com/aio-libs/aiohttp/pull/708>`_)\n- Close session on exception (`#707 <https://github.com/aio-libs/aiohttp/pull/707>`_)\n- Store http code and headers in WSServerHandshakeError (`#706 <https://github.com/aio-libs/aiohttp/pull/706>`_)\n- Make some low-level message properties readonly (`#710 <https://github.com/aio-libs/aiohttp/pull/710>`_)\n\n\n\n----\n\n\n0.19.0 (11-25-2015)\n===================\n\n- Memory leak in ParserBuffer (`#579 <https://github.com/aio-libs/aiohttp/pull/579>`_)\n- Support gunicorn's `max_requests` settings in gunicorn worker\n- Fix wsgi environment building (`#573 <https://github.com/aio-libs/aiohttp/pull/573>`_)\n- Improve access logging (`#572 <https://github.com/aio-libs/aiohttp/pull/572>`_)\n- Drop unused host and port from low-level server (`#586 <https://github.com/aio-libs/aiohttp/pull/586>`_)\n- Add Python 3.5 `async for` implementation to server websocket (`#543 <https://github.com/aio-libs/aiohttp/pull/543>`_)\n- Add Python 3.5 `async for` implementation to client websocket\n- Add Python 3.5 `async with` implementation to client websocket\n- Add charset parameter to web.Response constructor (`#593 <https://github.com/aio-libs/aiohttp/pull/593>`_)\n- Forbid passing both Content-Type header and content_type or charset\n  params into web.Response constructor\n- Forbid duplicating of web.Application and web.Request (`#602 <https://github.com/aio-libs/aiohttp/pull/602>`_)\n- Add an option to pass Origin header in ws_connect (`#607 <https://github.com/aio-libs/aiohttp/pull/607>`_)\n- Add json_response function (`#592 <https://github.com/aio-libs/aiohttp/pull/592>`_)\n- Make concurrent connections respect limits (`#581 <https://github.com/aio-libs/aiohttp/pull/581>`_)\n- Collect history of responses if redirects occur (`#614 <https://github.com/aio-libs/aiohttp/pull/614>`_)\n- Enable passing pre-compressed data in requests (`#621 <https://github.com/aio-libs/aiohttp/pull/621>`_)\n- Expose named routes via UrlDispatcher.named_routes() (`#622 <https://github.com/aio-libs/aiohttp/pull/622>`_)\n- Allow disabling sendfile by environment variable AIOHTTP_NOSENDFILE (`#629 <https://github.com/aio-libs/aiohttp/pull/629>`_)\n- Use ensure_future if available\n- Always quote params for Content-Disposition (`#641 <https://github.com/aio-libs/aiohttp/pull/641>`_)\n- Support async for in multipart reader (`#640 <https://github.com/aio-libs/aiohttp/pull/640>`_)\n- Add Timeout context manager (`#611 <https://github.com/aio-libs/aiohttp/pull/611>`_)\n\n\n\n----\n\n0.18.4 (13-11-2015)\n===================\n\n- Relax rule for router names again by adding dash to allowed\n  characters: they may contain identifiers, dashes, dots and columns\n\n\n\n----\n\n0.18.3 (25-10-2015)\n===================\n\n- Fix formatting for _RequestContextManager helper (`#590 <https://github.com/aio-libs/aiohttp/pull/590>`_)\n\n\n\n----\n\n0.18.2 (22-10-2015)\n===================\n\n- Fix regression for OpenSSL < 1.0.0 (`#583 <https://github.com/aio-libs/aiohttp/pull/583>`_)\n\n\n\n----\n\n0.18.1 (20-10-2015)\n===================\n\n- Relax rule for router names: they may contain dots and columns\n  starting from now\n\n\n\n----\n\n0.18.0 (19-10-2015)\n===================\n\n- Use errors.HttpProcessingError.message as HTTP error reason and\n  message (`#459 <https://github.com/aio-libs/aiohttp/pull/459>`_)\n- Optimize cythonized multidict a bit\n- Change repr's of multidicts and multidict views\n- default headers in ClientSession are now case-insensitive\n- Make '=' char and 'wss://' schema safe in urls (`#477 <https://github.com/aio-libs/aiohttp/pull/477>`_)\n- `ClientResponse.close()` forces connection closing by default from now (`#479 <https://github.com/aio-libs/aiohttp/pull/479>`_)\n\n  N.B. Backward incompatible change: was `.close(force=False) Using\n  `force` parameter for the method is deprecated: use `.release()`\n  instead.\n- Properly requote URL's path (`#480 <https://github.com/aio-libs/aiohttp/pull/480>`_)\n- add `skip_auto_headers` parameter for client API (`#486 <https://github.com/aio-libs/aiohttp/pull/486>`_)\n- Properly parse URL path in aiohttp.web.Request (`#489 <https://github.com/aio-libs/aiohttp/pull/489>`_)\n- Raise RuntimeError when chunked enabled and HTTP is 1.0 (`#488 <https://github.com/aio-libs/aiohttp/pull/488>`_)\n- Fix a bug with processing io.BytesIO as data parameter for client API (`#500 <https://github.com/aio-libs/aiohttp/pull/500>`_)\n- Skip auto-generation of Content-Type header (`#507 <https://github.com/aio-libs/aiohttp/pull/507>`_)\n- Use sendfile facility for static file handling (`#503 <https://github.com/aio-libs/aiohttp/pull/503>`_)\n- Default `response_factory` in `app.router.add_static` now is\n  `StreamResponse`, not `None`. The functionality is not changed if\n  default is not specified.\n- Drop `ClientResponse.message` attribute, it was always implementation detail.\n- Streams are optimized for speed and mostly memory in case of a big\n  HTTP message sizes (`#496 <https://github.com/aio-libs/aiohttp/pull/496>`_)\n- Fix a bug for server-side cookies for dropping cookie and setting it\n  again without Max-Age parameter.\n- Don't trim redirect URL in client API (`#499 <https://github.com/aio-libs/aiohttp/pull/499>`_)\n- Extend precision of access log \"D\" to milliseconds (`#527 <https://github.com/aio-libs/aiohttp/pull/527>`_)\n- Deprecate `StreamResponse.start()` method in favor of\n  `StreamResponse.prepare()` coroutine (`#525 <https://github.com/aio-libs/aiohttp/pull/525>`_)\n\n  `.start()` is still supported but responses begun with `.start()`\n  does not call signal for response preparing to be sent.\n- Add `StreamReader.__repr__`\n- Drop Python 3.3 support, from now minimal required version is Python\n  3.4.1 (`#541 <https://github.com/aio-libs/aiohttp/pull/541>`_)\n- Add `async with` support for `ClientSession.request()` and family (`#536 <https://github.com/aio-libs/aiohttp/pull/536>`_)\n- Ignore message body on 204 and 304 responses (`#505 <https://github.com/aio-libs/aiohttp/pull/505>`_)\n- `TCPConnector` processed both IPv4 and IPv6 by default (`#559 <https://github.com/aio-libs/aiohttp/pull/559>`_)\n- Add `.routes()` view for urldispatcher (`#519 <https://github.com/aio-libs/aiohttp/pull/519>`_)\n- Route name should be a valid identifier name from now (`#567 <https://github.com/aio-libs/aiohttp/pull/567>`_)\n- Implement server signals (`#562 <https://github.com/aio-libs/aiohttp/pull/562>`_)\n- Drop a year-old deprecated *files* parameter from client API.\n- Added `async for` support for aiohttp stream (`#542 <https://github.com/aio-libs/aiohttp/pull/542>`_)\n\n\n\n----\n\n0.17.4 (09-29-2015)\n===================\n\n- Properly parse URL path in aiohttp.web.Request (`#489 <https://github.com/aio-libs/aiohttp/pull/489>`_)\n- Add missing coroutine decorator, the client api is await-compatible now\n\n\n\n----\n\n0.17.3 (08-28-2015)\n===================\n\n- Remove Content-Length header on compressed responses (`#450 <https://github.com/aio-libs/aiohttp/pull/450>`_)\n- Support Python 3.5\n- Improve performance of transport in-use list (`#472 <https://github.com/aio-libs/aiohttp/pull/472>`_)\n- Fix connection pooling (`#473 <https://github.com/aio-libs/aiohttp/pull/473>`_)\n\n\n\n----\n\n0.17.2 (08-11-2015)\n===================\n\n- Don't forget to pass `data` argument forward (`#462 <https://github.com/aio-libs/aiohttp/pull/462>`_)\n- Fix multipart read bytes count (`#463 <https://github.com/aio-libs/aiohttp/pull/463>`_)\n\n\n\n----\n\n0.17.1 (08-10-2015)\n===================\n\n- Fix multidict comparison to arbitrary abc.Mapping\n\n\n\n----\n\n0.17.0 (08-04-2015)\n===================\n\n- Make StaticRoute support Last-Modified and If-Modified-Since headers (`#386 <https://github.com/aio-libs/aiohttp/pull/386>`_)\n- Add Request.if_modified_since and Stream.Response.last_modified properties\n- Fix deflate compression when writing a chunked response (`#395 <https://github.com/aio-libs/aiohttp/pull/395>`_)\n- Request`s content-length header is cleared now after redirect from\n  POST method (`#391 <https://github.com/aio-libs/aiohttp/pull/391>`_)\n- Return a 400 if server received a non HTTP content (`#405 <https://github.com/aio-libs/aiohttp/pull/405>`_)\n- Fix keep-alive support for aiohttp clients (`#406 <https://github.com/aio-libs/aiohttp/pull/406>`_)\n- Allow gzip compression in high-level server response interface (`#403 <https://github.com/aio-libs/aiohttp/pull/403>`_)\n- Rename TCPConnector.resolve and family to dns_cache (`#415 <https://github.com/aio-libs/aiohttp/pull/415>`_)\n- Make UrlDispatcher ignore quoted characters during url matching (`#414 <https://github.com/aio-libs/aiohttp/pull/414>`_)\n  Backward-compatibility warning: this may change the url matched by\n  your queries if they send quoted character (like %2F for /) (`#414 <https://github.com/aio-libs/aiohttp/pull/414>`_)\n- Use optional cchardet accelerator if present (`#418 <https://github.com/aio-libs/aiohttp/pull/418>`_)\n- Borrow loop from Connector in ClientSession if loop is not set\n- Add context manager support to ClientSession for session closing.\n- Add toplevel get(), post(), put(), head(), delete(), options(),\n  patch() coroutines.\n- Fix IPv6 support for client API (`#425 <https://github.com/aio-libs/aiohttp/pull/425>`_)\n- Pass SSL context through proxy connector (`#421 <https://github.com/aio-libs/aiohttp/pull/421>`_)\n- Make the rule: path for add_route should start with slash\n- Don't process request finishing by low-level server on closed event loop\n- Don't override data if multiple files are uploaded with same key (`#433 <https://github.com/aio-libs/aiohttp/pull/433>`_)\n- Ensure multipart.BodyPartReader.read_chunk read all the necessary data\n  to avoid false assertions about malformed multipart payload\n- Don't send body for 204, 205 and 304 http exceptions (`#442 <https://github.com/aio-libs/aiohttp/pull/442>`_)\n- Correctly skip Cython compilation in MSVC not found (`#453 <https://github.com/aio-libs/aiohttp/pull/453>`_)\n- Add response factory to StaticRoute (`#456 <https://github.com/aio-libs/aiohttp/pull/456>`_)\n- Don't append trailing CRLF for multipart.BodyPartReader (`#454 <https://github.com/aio-libs/aiohttp/pull/454>`_)\n\n\n\n----\n\n\n0.16.6 (07-15-2015)\n===================\n\n- Skip compilation on Windows if vcvarsall.bat cannot be found (`#438 <https://github.com/aio-libs/aiohttp/pull/438>`_)\n\n\n\n----\n\n0.16.5 (06-13-2015)\n===================\n\n- Get rid of all comprehensions and yielding in _multidict (`#410 <https://github.com/aio-libs/aiohttp/pull/410>`_)\n\n\n\n----\n\n\n0.16.4 (06-13-2015)\n===================\n\n- Don't clear current exception in multidict's `__repr__` (cythonized\n  versions) (`#410 <https://github.com/aio-libs/aiohttp/pull/410>`_)\n\n\n\n----\n\n\n0.16.3 (05-30-2015)\n===================\n\n- Fix StaticRoute vulnerability to directory traversal attacks (`#380 <https://github.com/aio-libs/aiohttp/pull/380>`_)\n\n\n\n----\n\n\n0.16.2 (05-27-2015)\n===================\n\n- Update python version required for `__del__` usage: it's actually\n  3.4.1 instead of 3.4.0\n- Add check for presence of loop.is_closed() method before call the\n  former (`#378 <https://github.com/aio-libs/aiohttp/pull/378>`_)\n\n\n\n----\n\n\n0.16.1 (05-27-2015)\n===================\n\n- Fix regression in static file handling (`#377 <https://github.com/aio-libs/aiohttp/pull/377>`_)\n\n\n\n----\n\n0.16.0 (05-26-2015)\n===================\n\n- Unset waiter future after cancellation (`#363 <https://github.com/aio-libs/aiohttp/pull/363>`_)\n- Update request url with query parameters (`#372 <https://github.com/aio-libs/aiohttp/pull/372>`_)\n- Support new `fingerprint` param of TCPConnector to enable verifying\n  SSL certificates via MD5, SHA1, or SHA256 digest (`#366 <https://github.com/aio-libs/aiohttp/pull/366>`_)\n- Setup uploaded filename if field value is binary and transfer\n  encoding is not specified (`#349 <https://github.com/aio-libs/aiohttp/pull/349>`_)\n- Implement `ClientSession.close()` method\n- Implement `connector.closed` readonly property\n- Implement `ClientSession.closed` readonly property\n- Implement `ClientSession.connector` readonly property\n- Implement `ClientSession.detach` method\n- Add `__del__` to client-side objects: sessions, connectors,\n  connections, requests, responses.\n- Refactor connections cleanup by connector (`#357 <https://github.com/aio-libs/aiohttp/pull/357>`_)\n- Add `limit` parameter to connector constructor (`#358 <https://github.com/aio-libs/aiohttp/pull/358>`_)\n- Add `request.has_body` property (`#364 <https://github.com/aio-libs/aiohttp/pull/364>`_)\n- Add `response_class` parameter to `ws_connect()` (`#367 <https://github.com/aio-libs/aiohttp/pull/367>`_)\n- `ProxyConnector` does not support keep-alive requests by default\n  starting from now (`#368 <https://github.com/aio-libs/aiohttp/pull/368>`_)\n- Add `connector.force_close` property\n- Add ws_connect to ClientSession (`#374 <https://github.com/aio-libs/aiohttp/pull/374>`_)\n- Support optional `chunk_size` parameter in `router.add_static()`\n\n\n\n----\n\n\n0.15.3 (04-22-2015)\n===================\n\n- Fix graceful shutdown handling\n- Fix `Expect` header handling for not found and not allowed routes (`#340 <https://github.com/aio-libs/aiohttp/pull/340>`_)\n\n\n\n----\n\n\n0.15.2 (04-19-2015)\n===================\n\n- Flow control subsystem refactoring\n- HTTP server performance optimizations\n- Allow to match any request method with `*`\n- Explicitly call drain on transport (`#316 <https://github.com/aio-libs/aiohttp/pull/316>`_)\n- Make chardet module dependency mandatory (`#318 <https://github.com/aio-libs/aiohttp/pull/318>`_)\n- Support keep-alive for HTTP 1.0 (`#325 <https://github.com/aio-libs/aiohttp/pull/325>`_)\n- Do not chunk single file during upload (`#327 <https://github.com/aio-libs/aiohttp/pull/327>`_)\n- Add ClientSession object for cookie storage and default headers (`#328 <https://github.com/aio-libs/aiohttp/pull/328>`_)\n- Add `keep_alive_on` argument for HTTP server handler.\n\n\n\n----\n\n\n0.15.1 (03-31-2015)\n===================\n\n- Pass Autobahn Testsuite tests\n- Fixed websocket fragmentation\n- Fixed websocket close procedure\n- Fixed parser buffer limits\n- Added `timeout` parameter to WebSocketResponse ctor\n- Added `WebSocketResponse.close_code` attribute\n\n\n\n----\n\n\n0.15.0 (03-27-2015)\n===================\n\n- Client WebSockets support\n- New Multipart system (`#273 <https://github.com/aio-libs/aiohttp/pull/273>`_)\n- Support for \"Except\" header (`#287 <https://github.com/aio-libs/aiohttp/pull/287>`_) (`#267 <https://github.com/aio-libs/aiohttp/pull/267>`_)\n- Set default Content-Type for post requests (`#184 <https://github.com/aio-libs/aiohttp/pull/184>`_)\n- Fix issue with construction dynamic route with regexps and trailing slash (`#266 <https://github.com/aio-libs/aiohttp/pull/266>`_)\n- Add repr to web.Request\n- Add repr to web.Response\n- Add repr for NotFound and NotAllowed match infos\n- Add repr for web.Application\n- Add repr to UrlMappingMatchInfo (`#217 <https://github.com/aio-libs/aiohttp/pull/217>`_)\n- Gunicorn 19.2.x compatibility\n\n\n\n----\n\n\n0.14.4 (01-29-2015)\n===================\n\n- Fix issue with error during constructing of url with regex parts (`#264 <https://github.com/aio-libs/aiohttp/pull/264>`_)\n\n\n\n----\n\n\n0.14.3 (01-28-2015)\n===================\n\n- Use path='/' by default for cookies (`#261 <https://github.com/aio-libs/aiohttp/pull/261>`_)\n\n\n\n----\n\n\n0.14.2 (01-23-2015)\n===================\n\n- Connections leak in BaseConnector (`#253 <https://github.com/aio-libs/aiohttp/pull/253>`_)\n- Do not swallow websocket reader exceptions (`#255 <https://github.com/aio-libs/aiohttp/pull/255>`_)\n- web.Request's read, text, json are memorized (`#250 <https://github.com/aio-libs/aiohttp/pull/250>`_)\n\n\n\n----\n\n\n0.14.1 (01-15-2015)\n===================\n\n- HttpMessage._add_default_headers does not overwrite existing headers (`#216 <https://github.com/aio-libs/aiohttp/pull/216>`_)\n- Expose multidict classes at package level\n- add `aiohttp.web.WebSocketResponse`\n- According to RFC 6455 websocket subprotocol preference order is\n  provided by client, not by server\n- websocket's ping and pong accept optional message parameter\n- multidict views do not accept `getall` parameter anymore, it\n  returns the full body anyway.\n- multidicts have optional Cython optimization, cythonized version of\n  multidicts is about 5 times faster than pure Python.\n- multidict.getall() returns `list`, not `tuple`.\n- Backward incompatible change: now there are two mutable multidicts\n  (`MultiDict`, `CIMultiDict`) and two immutable multidict proxies\n  (`MultiDictProxy` and `CIMultiDictProxy`). Previous edition of\n  multidicts was not a part of public API BTW.\n- Router refactoring to push Not Allowed and Not Found in middleware processing\n- Convert `ConnectionError` to `aiohttp.DisconnectedError` and don't\n  eat `ConnectionError` exceptions from web handlers.\n- Remove hop headers from Response class, wsgi response still uses hop headers.\n- Allow to send raw chunked encoded response.\n- Allow to encode output bytes stream into chunked encoding.\n- Allow to compress output bytes stream with `deflate` encoding.\n- Server has 75 seconds keepalive timeout now, was non-keepalive by default.\n- Application does not accept `**kwargs` anymore ((`#243 <https://github.com/aio-libs/aiohttp/pull/243>`_)).\n- Request is inherited from dict now for making per-request storage to\n  middlewares ((`#242 <https://github.com/aio-libs/aiohttp/pull/242>`_)).\n\n\n\n----\n\n\n0.13.1 (12-31-2014)\n===================\n\n- Add `aiohttp.web.StreamResponse.started` property (`#213 <https://github.com/aio-libs/aiohttp/pull/213>`_)\n- HTML escape traceback text in `ServerHttpProtocol.handle_error`\n- Mention handler and middlewares in `aiohttp.web.RequestHandler.handle_request`\n  on error ((`#218 <https://github.com/aio-libs/aiohttp/pull/218>`_))\n\n\n\n----\n\n\n0.13.0 (12-29-2014)\n===================\n\n- `StreamResponse.charset` converts value to lower-case on assigning.\n- Chain exceptions when raise `ClientRequestError`.\n- Support custom regexps in route variables (`#204 <https://github.com/aio-libs/aiohttp/pull/204>`_)\n- Fixed graceful shutdown, disable keep-alive on connection closing.\n- Decode HTTP message with `utf-8` encoding, some servers send headers\n  in utf-8 encoding (`#207 <https://github.com/aio-libs/aiohttp/pull/207>`_)\n- Support `aiohtt.web` middlewares (`#209 <https://github.com/aio-libs/aiohttp/pull/209>`_)\n- Add ssl_context to TCPConnector (`#206 <https://github.com/aio-libs/aiohttp/pull/206>`_)\n\n\n\n----\n\n\n0.12.0 (12-12-2014)\n===================\n\n- Deep refactoring of `aiohttp.web` in backward-incompatible manner.\n  Sorry, we have to do this.\n- Automatically force aiohttp.web handlers to coroutines in\n  `UrlDispatcher.add_route()` (`#186 <https://github.com/aio-libs/aiohttp/pull/186>`_)\n- Rename `Request.POST()` function to `Request.post()`\n- Added POST attribute\n- Response processing refactoring: constructor does not accept Request\n  instance anymore.\n- Pass application instance to finish callback\n- Exceptions refactoring\n- Do not unquote query string in `aiohttp.web.Request`\n- Fix concurrent access to payload in `RequestHandle.handle_request()`\n- Add access logging to `aiohttp.web`\n- Gunicorn worker for `aiohttp.web`\n- Removed deprecated `AsyncGunicornWorker`\n- Removed deprecated HttpClient\n\n\n\n----\n\n\n0.11.0 (11-29-2014)\n===================\n\n- Support named routes in `aiohttp.web.UrlDispatcher` (`#179 <https://github.com/aio-libs/aiohttp/pull/179>`_)\n- Make websocket subprotocols conform to spec (`#181 <https://github.com/aio-libs/aiohttp/pull/181>`_)\n\n\n\n----\n\n\n0.10.2 (11-19-2014)\n===================\n\n- Don't unquote `environ['PATH_INFO']` in wsgi.py (`#177 <https://github.com/aio-libs/aiohttp/pull/177>`_)\n\n\n\n----\n\n\n0.10.1 (11-17-2014)\n===================\n\n- aiohttp.web.HTTPException and descendants now files response body\n  with string like `404: NotFound`\n- Fix multidict `__iter__`, the method should iterate over keys, not\n  (key, value) pairs.\n\n\n\n----\n\n\n0.10.0 (11-13-2014)\n===================\n\n- Add aiohttp.web subpackage for highlevel HTTP server support.\n- Add *reason* optional parameter to aiohttp.protocol.Response ctor.\n- Fix aiohttp.client bug for sending file without content-type.\n- Change error text for connection closed between server responses\n  from 'Can not read status line' to explicit 'Connection closed by\n  server'\n- Drop closed connections from connector (`#173 <https://github.com/aio-libs/aiohttp/pull/173>`_)\n- Set server.transport to None on .closing() (`#172 <https://github.com/aio-libs/aiohttp/pull/172>`_)\n\n\n\n----\n\n\n0.9.3 (10-30-2014)\n==================\n\n- Fix compatibility with asyncio 3.4.1+ (`#170 <https://github.com/aio-libs/aiohttp/pull/170>`_)\n\n\n\n----\n\n\n0.9.2 (10-16-2014)\n==================\n\n- Improve redirect handling (`#157 <https://github.com/aio-libs/aiohttp/pull/157>`_)\n- Send raw files as is (`#153 <https://github.com/aio-libs/aiohttp/pull/153>`_)\n- Better websocket support (`#150 <https://github.com/aio-libs/aiohttp/pull/150>`_)\n\n\n\n----\n\n\n0.9.1 (08-30-2014)\n==================\n\n- Added MultiDict support for client request params and data (`#114 <https://github.com/aio-libs/aiohttp/pull/114>`_).\n- Fixed parameter type for IncompleteRead exception (`#118 <https://github.com/aio-libs/aiohttp/pull/118>`_).\n- Strictly require ASCII headers names and values (`#137 <https://github.com/aio-libs/aiohttp/pull/137>`_)\n- Keep port in ProxyConnector (`#128 <https://github.com/aio-libs/aiohttp/pull/128>`_).\n- Python 3.4.1 compatibility (`#131 <https://github.com/aio-libs/aiohttp/pull/131>`_).\n\n\n\n----\n\n\n0.9.0 (07-08-2014)\n==================\n\n- Better client basic authentication support (`#112 <https://github.com/aio-libs/aiohttp/pull/112>`_).\n- Fixed incorrect line splitting in HttpRequestParser (`#97 <https://github.com/aio-libs/aiohttp/pull/97>`_).\n- Support StreamReader and DataQueue as request data.\n- Client files handling refactoring (`#20 <https://github.com/aio-libs/aiohttp/pull/20>`_).\n- Backward incompatible: Replace DataQueue with StreamReader for\n  request payload (`#87 <https://github.com/aio-libs/aiohttp/pull/87>`_).\n\n\n\n----\n\n\n0.8.4 (07-04-2014)\n==================\n\n- Change ProxyConnector authorization parameters.\n\n\n\n----\n\n\n0.8.3 (07-03-2014)\n==================\n\n- Publish TCPConnector properties: verify_ssl, family, resolve, resolved_hosts.\n- Don't parse message body for HEAD responses.\n- Refactor client response decoding.\n\n\n\n----\n\n\n0.8.2 (06-22-2014)\n==================\n\n- Make ProxyConnector.proxy immutable property.\n- Make UnixConnector.path immutable property.\n- Fix resource leak for aiohttp.request() with implicit connector.\n- Rename Connector's reuse_timeout to keepalive_timeout.\n\n\n\n----\n\n\n0.8.1 (06-18-2014)\n==================\n\n- Use case insensitive multidict for server request/response headers.\n- MultiDict.getall() accepts default value.\n- Catch server ConnectionError.\n- Accept MultiDict (and derived) instances in aiohttp.request header argument.\n- Proxy 'CONNECT' support.\n\n\n\n----\n\n\n0.8.0 (06-06-2014)\n==================\n\n- Add support for utf-8 values in HTTP headers\n- Allow to use custom response class instead of HttpResponse\n- Use MultiDict for client request headers\n- Use MultiDict for server request/response headers\n- Store response headers in ClientResponse.headers attribute\n- Get rid of timeout parameter in aiohttp.client API\n- Exceptions refactoring\n\n\n\n----\n\n\n0.7.3 (05-20-2014)\n==================\n\n- Simple HTTP proxy support.\n\n\n\n----\n\n\n0.7.2 (05-14-2014)\n==================\n\n- Get rid of `__del__` methods\n- Use ResourceWarning instead of logging warning record.\n\n\n\n----\n\n\n0.7.1 (04-28-2014)\n==================\n\n- Do not unquote client request urls.\n- Allow multiple waiters on transport drain.\n- Do not return client connection to pool in case of exceptions.\n- Rename SocketConnector to TCPConnector and UnixSocketConnector to\n  UnixConnector.\n\n\n\n----\n\n\n0.7.0 (04-16-2014)\n==================\n\n- Connection flow control.\n- HTTP client session/connection pool refactoring.\n- Better handling for bad server requests.\n\n\n\n----\n\n\n0.6.5 (03-29-2014)\n==================\n\n- Added client session reuse timeout.\n- Better client request cancellation support.\n- Better handling responses without content length.\n- Added HttpClient verify_ssl parameter support.\n\n\n\n----\n\n\n0.6.4 (02-27-2014)\n==================\n\n- Log content-length missing warning only for put and post requests.\n\n\n\n----\n\n\n0.6.3 (02-27-2014)\n==================\n\n- Better support for server exit.\n- Read response body until EOF if content-length is not defined (`#14 <https://github.com/aio-libs/aiohttp/pull/14>`_)\n\n\n\n----\n\n\n0.6.2 (02-18-2014)\n==================\n\n- Fix trailing char in allowed_methods.\n- Start slow request timer for first request.\n\n\n\n----\n\n\n0.6.1 (02-17-2014)\n==================\n\n- Added utility method HttpResponse.read_and_close()\n- Added slow request timeout.\n- Enable socket SO_KEEPALIVE if available.\n\n\n\n----\n\n\n0.6.0 (02-12-2014)\n==================\n\n- Better handling for process exit.\n\n\n\n----\n\n\n0.5.0 (01-29-2014)\n==================\n- Allow to use custom HttpRequest client class.\n- Use gunicorn keepalive setting for asynchronous worker.\n- Log leaking responses.\n- python 3.4 compatibility\n\n\n\n----\n\n\n0.4.4 (11-15-2013)\n==================\n\n- Resolve only AF_INET family, because it is not clear how to pass\n  extra info to asyncio.\n\n\n\n----\n\n\n0.4.3 (11-15-2013)\n==================\n\n- Allow to wait completion of request with `HttpResponse.wait_for_close()`\n\n\n\n----\n\n\n0.4.2 (11-14-2013)\n==================\n\n- Handle exception in client request stream.\n- Prevent host resolving for each client request.\n\n\n\n----\n\n\n0.4.1 (11-12-2013)\n==================\n\n- Added client support for `expect: 100-continue` header.\n\n\n\n----\n\n\n0.4 (11-06-2013)\n================\n\n- Added custom wsgi application close procedure\n- Fixed concurrent host failure in HttpClient\n\n\n\n----\n\n\n0.3 (11-04-2013)\n================\n\n- Added PortMapperWorker\n- Added HttpClient\n- Added TCP connection timeout to HTTP client\n- Better client connection errors handling\n- Gracefully handle process exit\n\n\n\n----\n\n\n0.2\n===\n\n- Fix packaging\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at andrew.svetlov@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "CONTRIBUTING.rst",
    "content": "Contributing\n============\n\nInstructions for contributors\n-----------------------------\n\n\nIn order to make a clone of the GitHub_ repo: open the link and press the\n\"Fork\" button on the upper-right menu of the web page.\n\nI hope everybody knows how to work with git and github nowadays :)\n\nWorkflow is pretty straightforward:\n\n  1. Clone the GitHub_ repo using the ``--recurse-submodules`` argument\n\n  2. Setup your machine with the required development environment\n\n  3. Make a change\n\n  4. Make sure all tests passed\n\n  5. Add a file into the ``CHANGES`` folder, named after the ticket or PR number\n\n  6. Commit changes to your own aiohttp clone\n\n  7. Make a pull request from the github page of your clone against the master branch\n\n  8. Optionally make backport Pull Request(s) for landing a bug fix into released aiohttp versions.\n\n.. important::\n\n    Please open the \"`contributing <https://docs.aiohttp.org/en/stable/contributing.html>`_\"\n    documentation page to get detailed information about all steps.\n\n.. _GitHub: https://github.com/aio-libs/aiohttp\n"
  },
  {
    "path": "CONTRIBUTORS.txt",
    "content": "- Contributors -\n----------------\nA. Jesse Jiryu Davis\nAbdur Rehman Ali\nAdam Bannister\nAdam Cooper\nAdam Horacek\nAdam Mills\nAdrian Krupa\nAdrián Chaves\nAhmed Tahri\nAlan Bogarin\nAlan Tse\nAlec Hanefeld\nAlejandro Gómez\nAleksandr Danshyn\nAleksey Kutepov\nAlex Hayes\nAlex Key\nAlex Khomchenko\nAlex Kuzmenko\nAlex Lisovoy\nAlexander Bayandin\nAlexander Karpinsky\nAlexander Koshevoy\nAlexander Malev\nAlexander Mohr\nAlexander Shorin\nAlexander Travov\nAlexandru Mihai\nAlexey Firsov\nAlexey Nikitin\nAlexey Popravka\nAlexey Stavrov\nAlexey Stepanov\nAlmaz Salakhov\nAmin Etesamian\nAmit Tulshyan\nAmy Boyle\nAnas El Amraoui\nAnders Melchiorsen\nAndrei Ursulenko\nAndrej Antonov\nAndrew Leech\nAndrew Lytvyn\nAndrew Svetlov\nAndrew Top\nAndrew Zhou\nAndrii Soldatenko\nAnes Abismail\nAntoine Pietri\nAnton Kasyanov\nAnton Zhdan-Pushkin\nArcadiy Ivanov\nArie Bovenberg\nArseny Timoniq\nArtem Yushkovskiy\nArthur Darcet\nAustin Scola\nBai Haoran\nBen Bader\nBen Beasley\nBen Greiner\nBen Kallus\nBen Timby\nBenedikt Reinartz\nBob Haddleton\nBoris Feld\nBorys Vorona\nBoyi Chen\nBrett Cannon\nBrett Higgins\nBrian Bouterse\nBrian C. Lane\nBrian Muller\nBruce Merry\nBruno Souza Cabral\nBryan Kok\nBryce Drennan\nCarl George\nCecile Tonglet\nChien-Wei Huang\nChih-Yuan Chen\nChris AtLee\nChris Laws\nChris Moore\nChris Shucksmith\nChristophe Bornet\nChristopher Schmitt\nClaudiu Popa\nColin Dunklau\nCong Xu\nDamien Nadé\nDan King\nDan Xu\nDaniel García\nDaniel Golding\nDaniel Grossmann-Kavanagh\nDaniel Nelson\nDaniele Trifirò\nDanny Song\nDavid Bibb\nDavid Dzhalaev\nDavid Michael Brown\nDenilson Amorim\nDenis Matiychuk\nDenis Moshensky\nDennis Kliban\nDevanshu Koyalkar\nDima Veselov\nDimitar Dimitrov\nDiogo Dutra da Mata\nDmitriy Safonov\nDmitry Doroshev\nDmitry Erlikh\nDmitry Lukashin\nDmitry Marakasov\nDmitry Shamov\nDmitry Trofimov\nDmytro Bohomiakov\nDmytro Kuznetsov\nDustin J. Mitchell\nEarle Lowe\nEduard Iskandarov\nEli Ribble\nElizabeth Leddy\nEmil Melnikov\nEnrique Saez\nEric Sheng\nErich Healy\nErik Peterson\nEugene Chernyshov\nEugene Ershov\nEugene Naydenov\nEugene Nikolaiev\nEugene Tolmachev\nEvan Kepner\nEvert Lammerts\nFelix Yan\nFernanda Guimarães\nFichteFoll\nFlorian Scheffler\nFranek Magiera\nFrederik Gladhorn\nFrederik Peter Aalund\nGabriel Tremblay\nGang Ji\nGary Leung\nGary Wilson Jr.\nGene Hoffman\nGennady Andreyev\nGeorges Dubus\nGreg Holt\nGregory Haynes\nGrigoriy Soldatov\nGuillaume Leurquin\nGus Goulart\nGustavo Carneiro\nGünther Jena\nHans Adema\nHarmon Y.\nHarry Liu\nHiroshi Ogawa\nHrishikesh Paranjape\nHu Bo\nHugh Young\nHugo Herter\nHugo Hromic\nHugo van Kemenade\nHynek Schlawack\nIgor Alexandrov\nIgor Bolshakov\nIgor Davydenko\nIgor Mozharovsky\nIgor Pavlov\nIllia Volochii\nIlya Chichak\nIlya Gruzinov\nIngmar Steen\nIvan Lakovic\nIvan Larin\nJ. Nick Koston\nJacob Champion\nJacob Henner\nJaesung Lee\nJake Davis\nJakob Ackermann\nJakub Wilk\nJames Ward\nJan Buchar\nJan Gosmann\nJarno Elonen\nJashandeep Sohi\nJavier Torres\nJean-Baptiste Estival\nJens Steinhauser\nJeonghun Lee\nJeongkyu Shin\nJeroen van der Heijden\nJesus Cea\nJian Zeng\nJinkyu Yi\nJoel Watts\nJohn Feusi\nJohn Parton\nJon Nabozny\nJonas Krüger Svensson\nJonas Obrist\nJonathan Ballet\nJonathan Wright\nJonny Tan\nJoongi Kim\nJordan Borean\nJosep Cugat\nJosh Junon\nJoshu Coats\nJulia Tsemusheva\nJulien Duponchelle\nJungkook Park\nJunjie Tao\nJunyeong Jeong\nJustas Trimailovas\nJustin Foo\nJustin Turner Arthur\nKay Zheng\nKevin Samuel\nKilian Guillaume\nKimmo Parviainen-Jalanko\nKirill Klenov\nKirill Malovitsa\nKirill Potapenko\nKonstantin Shutkin\nKonstantin Valetov\nKrzysztof Blazewicz\nKyrylo Perevozchikov\nKyungmin Lee\nLars P. Søndergaard\nLee LieWhite\nLiu Hua\nLouis-Philippe Huberdeau\nLoïc Lajeanne\nLu Gong\nLubomir Gelo\nLudovic Gasc\nLuis Pedrosa\nLukasz Marcin Dobrzanski\nLénárd Szolnoki\nMakc Belousow\nManuel Miranda\nMarat Sharafutdinov\nMarc Mueller\nMarco Paolini\nMarcus Stojcevich\nMariano Anaya\nMariusz Masztalerczuk\nMark Larah\nMarko Kohtala\nMartijn Pieters\nMartin Melka\nMartin Richard\nMartin Sucha\nMathias Fröjdman\nMathieu Dugré\nMatt VanEseltine\nMatthew Go\nMatthias Marquardt\nMatthieu Hauglustaine\nMatthieu Rigal\nMatvey Tingaev\nMeet Mangukiya\nMeshya\nMichael Ihnatenko\nMichał Górny\nMikhail Burshteyn\nMikhail Kashkin\nMikhail Lukyanchenko\nMikhail Nacharov\nMingjie Zhao\nMisha Behersky\nMitchell Ferree\nMorgan Delahaye-Prat\nMoss Collum\nMun Gwan-gyeong\nNavid Sheikhol\nNicolas Braem\nNikolay Kim\nNikolay Novik\nNikolay Tiunov\nNándor Mátravölgyi\nOisin Aylward\nOlaf Conradi\nOleg Höfling\nPahaz Blinov\nPanagiotis Kolokotronis\nPankaj Pandey\nParag Jain\nParman Mohammadalizadeh\nPatrick Lee\nPau Freixes\nPaul Colomiets\nPaul J. Dorn\nPaulius Šileikis\nPaulus Schoutsen\nPavel Kamaev\nPavel Polyakov\nPavel Sapezhko\nPavol Vargovčík\nPawel Kowalski\nPawel Miech\nPepe Osca\nPhebe Polk\nPhilipp A.\nPierre-Louis Peeters\nPieter van Beek\nQiao Han\nRafael Viotti\nRahul Nahata\nRaphael Bialon\nRaúl Cumplido\nRequired Field\nRobert Lu\nRobert Nikolich\nRodrigo Nogueira\nRoman Markeloff\nRoman Podoliaka\nRoman Postnov\nRong Zhang\nRouven Bauer\nSamir Akarioh\nSamuel Colvin\nSamuel Gaist\nSean Hunt\nSebastian Acuna\nSebastian Hanula\nSebastian Hüther\nSebastien Geffroy\nSeongSoo Cho\nSergey Ninua\nSergey Skripnick\nSerhii Charykov\nSerhii Kostel\nSerhiy Storchaka\nShubh Agarwal\nSimon Kennedy\nSin-Woo Bang\nSoheil Dolatabadi\nStanislas Plum\nStanislav Prokop\nStefan Tjarks\nStepan Pletnev\nStephan Jaensch\nStephen Cirelli\nStephen Granade\nSteve Repsher\nSteven Seguin\nSunghyun Hwang\nSunit Deshpande\nSviatoslav Bulbakha\nSviatoslav Sydorenko\nTaha Jahangir\nTaras Voinarovskyi\nTerence Honles\nThanos Lefteris\nThijs Vermeir\nThomas Forbes\nThomas Grainger\nTim Menninger\nTolga Tezel\nTom Whittock\nTomasz Trebski\nToshiaki Tanaka\nTrevor Gamblin\nTrinh Hoang Nhu\nTymofii Tsiapa\nVadim Suharnikov\nVaibhav Sagar\nVamsi Krishna Avula\nVasiliy Faronov\nVasyl Baran\nViacheslav Greshilov\nVictor Collod\nVictor Kovtun\nVictor Makarov\nVikas Kawadia\nViktor Danyliuk\nVille Skyttä\nVincent Maillol\nVitalik Verhovodov\nVitaly Haritonsky\nVitaly Magerya\nVizonex\nVladimir Kamarzin\nVladimir Kozlovski\nVladimir Rutsky\nVladimir Shulyak\nVladimir Vinogradenko\nVladimir Zakharov\nVladyslav Bohaichuk\nVladyslav Bondar\nVojtěch Boček\nW. Trevor King\nWei Lin\nWeiwei Wang\nWill Fatherley\nWill McGugan\nWillem de Groot\nWilliam Grzybowski\nWilliam S.\nWilson Ong\nwouter bolsterlee\nXavier Halloran\nXi Rui\nXiang Li\nYang Zhou\nYannick Koechlin\nYannick Péroux\nYe Cao\nYegor Roganov\nYifei Kong\nYoung-Ho Cha\nYuriy Shatrov\nYury Pliner\nYury Selivanov\nYusuke Tsutsumi\nYuval Ofir\nYuvi Panda\nZainab Lawal\nZeal Wierslee\nZlatan Sičanica\nŁukasz Setla\nМарк Коренберг\nСемён Марьясин\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "   Copyright aio-libs contributors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include LICENSE.txt\ninclude CHANGES.rst\ninclude README.rst\ninclude CONTRIBUTORS.txt\ninclude Makefile\ngraft aiohttp\ngraft docs\ngraft examples\ngraft tests\ngraft tools\ngraft requirements\ngraft vendor\nglobal-exclude *.pyc\nglobal-exclude *.pyd\nglobal-exclude *.so\nglobal-exclude *.lib\nglobal-exclude *.dll\nglobal-exclude *.a\nglobal-exclude *.obj\nexclude aiohttp/*.html\nprune docs/_build\n"
  },
  {
    "path": "Makefile",
    "content": "# Some simple testing tasks (sorry, UNIX only).\n\nto-hash-one = $(dir $1).hash/$(addsuffix .hash,$(notdir $1))\nto-hash = $(foreach fname,$1,$(call to-hash-one,$(fname)))\n\nCYS := $(wildcard aiohttp/*.pyx) $(wildcard aiohttp/*.pyi)  $(wildcard aiohttp/*.pxd) $(wildcard aiohttp/_websocket/*.pyx) $(wildcard aiohttp/_websocket/*.pyi) $(wildcard aiohttp/_websocket/*.pxd)\nPYXS := $(wildcard aiohttp/*.pyx) $(wildcard aiohttp/_websocket/*.pyx)\nCS := $(wildcard aiohttp/*.c) $(wildcard aiohttp/_websocket/*.c)\nPYS := $(wildcard aiohttp/*.py) $(wildcard aiohttp/_websocket/*.py)\nIN := doc-spelling lint cython dev\nALLS := $(sort $(CYS) $(CS) $(PYS) $(REQS))\n\n\n.PHONY: all\nall: test\n\ntst:\n\t@echo $(call to-hash,requirements/cython.txt)\n\t@echo $(call to-hash,aiohttp/%.pyx)\n\n\n# Recipe from https://www.cmcrossroads.com/article/rebuilding-when-files-checksum-changes\nFORCE:\n\n# check_sum.py works perfectly fine but slow when called for every file from $(ALLS)\n# (perhaps even several times for each file).\n# That is why much less readable but faster solution exists\nifneq (, $(shell command -v sha256sum))\n%.hash: FORCE\n\t$(eval $@_ABS := $(abspath $@))\n\t$(eval $@_NAME := $($@_ABS))\n\t$(eval $@_HASHDIR := $(dir $($@_ABS)))\n\t$(eval $@_TMP := $($@_HASHDIR)../$(notdir $($@_ABS)))\n\t$(eval $@_ORIG := $(subst /.hash/../,/,$(basename $($@_TMP))))\n\t@#echo ==== $($@_ABS) $($@_HASHDIR) $($@_NAME) $($@_TMP) $($@_ORIG)\n\t@if ! (sha256sum --check $($@_ABS) 1>/dev/null 2>/dev/null); then \\\n\t  mkdir -p $($@_HASHDIR); \\\n\t  echo re-hash $($@_ORIG); \\\n\t  sha256sum $($@_ORIG) > $($@_ABS); \\\n\tfi\nelse\n%.hash: FORCE\n\t@./tools/check_sum.py $@ # --debug\nendif\n\n# Enumerate intermediate files to don't remove them automatically.\n.SECONDARY: $(call to-hash,$(ALLS))\n\n.update-pip:\n\t@python -m pip install --upgrade pip\n\n.install-cython: .update-pip $(call to-hash,requirements/cython.txt)\n\t@python -m pip install -r requirements/cython.in -c requirements/cython.txt\n\t@touch .install-cython\n\naiohttp/_find_header.c: $(call to-hash,aiohttp/hdrs.py ./tools/gen.py)\n\t./tools/gen.py\n\n# Special case for reader since we want to be able to disable\n# the extension with AIOHTTP_NO_EXTENSIONS\naiohttp/_websocket/reader_c.c: aiohttp/_websocket/reader_c.py\n\tcython -3 -X freethreading_compatible=True -o $@ $< -I aiohttp -Werror\n\n# _find_headers generator creates _headers.pyi as well\naiohttp/%.c: aiohttp/%.pyx $(call to-hash,$(CYS)) aiohttp/_find_header.c\n\tcython -3 -X freethreading_compatible=True -o $@ $< -I aiohttp -Werror\n\naiohttp/_websocket/%.c: aiohttp/_websocket/%.pyx $(call to-hash,$(CYS))\n\tcython -3 -X freethreading_compatible=True -o $@ $< -I aiohttp -Werror\n\nvendor/llhttp/node_modules: vendor/llhttp/package.json\n\tcd vendor/llhttp; npm ci\n\n.llhttp-gen: vendor/llhttp/node_modules\n\t$(MAKE) -C vendor/llhttp generate\n\t@touch .llhttp-gen\n\n.PHONY: generate-llhttp\ngenerate-llhttp: .llhttp-gen\n\n.PHONY: cythonize\ncythonize: .install-cython $(PYXS:.pyx=.c) aiohttp/_websocket/reader_c.c\n\n.PHONY: cythonize-nodeps\ncythonize-nodeps: $(PYXS:.pyx=.c) aiohttp/_websocket/reader_c.c\n\n.install-deps: .install-cython $(PYXS:.pyx=.c) aiohttp/_websocket/reader_c.c $(call to-hash,$(CYS) $(REQS))\n\t@python -m pip install -r requirements/dev.in -c requirements/dev.txt\n\t@touch .install-deps\n\n.PHONY: lint\nlint: fmt mypy\n\n.PHONY: fmt format\nfmt format:\n\tpython -m pre_commit run --all-files --show-diff-on-failure\n\n.PHONY: mypy\nmypy:\n\tmypy\n\n.develop: .install-deps generate-llhttp $(call to-hash,$(PYS) $(CYS) $(CS))\n\tpython -m pip install -e . -c requirements/runtime-deps.txt\n\t@touch .develop\n\n.PHONY: test\ntest: .develop\n\t@pytest -q\n\n.PHONY: vtest\nvtest: .develop\n\t@pytest -s -v\n\t@python -X dev -m pytest --cov-append -s -v -m dev_mode\n\n.PHONY: vvtest\nvvtest: .develop\n\t@pytest -vv\n\t@python -X dev -m pytest --cov-append -s -vv -m dev_mode\n\n.PHONY: cov-dev\ncov-dev: .develop\n\t@pytest --cov-report=html\n\t@echo \"xdg-open file://`pwd`/htmlcov/index.html\"\n\n\ndefine run_tests_in_docker\n\tDOCKER_BUILDKIT=1 docker build --build-arg PYTHON_VERSION=$(1) --build-arg AIOHTTP_NO_EXTENSIONS=$(2) -t \"aiohttp-test-$(1)-$(2)\" -f tools/testing/Dockerfile .\n\tdocker run --rm -ti -v `pwd`:/src -w /src \"aiohttp-test-$(1)-$(2)\" $(TEST_SPEC)\nendef\n\n.PHONY: clean\nclean:\n\t@rm -rf `find . -name __pycache__`\n\t@rm -rf `find . -name .hash`\n\t@rm -rf `find . -name .md5`  # old styling\n\t@rm -f `find . -type f -name '*.py[co]' `\n\t@rm -f `find . -type f -name '*~' `\n\t@rm -f `find . -type f -name '.*~' `\n\t@rm -f `find . -type f -name '@*' `\n\t@rm -f `find . -type f -name '#*#' `\n\t@rm -f `find . -type f -name '*.orig' `\n\t@rm -f `find . -type f -name '*.rej' `\n\t@rm -f `find . -type f -name '*.md5' `  # old styling\n\t@rm -f .coverage\n\t@rm -rf htmlcov\n\t@rm -rf build\n\t@rm -rf cover\n\t@make -C docs clean\n\t@python setup.py clean\n\t@rm -f aiohttp/*.so\n\t@rm -f aiohttp/*.pyd\n\t@rm -f aiohttp/*.html\n\t@rm -f aiohttp/_frozenlist.c\n\t@rm -f aiohttp/_find_header.c\n\t@rm -f aiohttp/_http_parser.c\n\t@rm -f aiohttp/_http_writer.c\n\t@rm -f aiohttp/_websocket.c\n\t@rm -f aiohttp/_websocket/reader_c.c\n\t@rm -rf .tox\n\t@rm -f .develop\n\t@rm -f .flake\n\t@rm -rf aiohttp.egg-info\n\t@rm -f .install-deps\n\t@rm -f .install-cython\n\t@rm -rf vendor/llhttp/node_modules\n\t@rm -f .llhttp-gen\n\t@$(MAKE) -C vendor/llhttp clean\n\n.PHONY: doc\ndoc:\n\t@make -C docs html SPHINXOPTS=\"-W --keep-going -n -E\"\n\t@echo \"open file://`pwd`/docs/_build/html/index.html\"\n\n.PHONY: doc-spelling\ndoc-spelling:\n\t@make -C docs spelling SPHINXOPTS=\"-W --keep-going -n -E\"\n\n.PHONY: install\ninstall: .update-pip\n\t@python -m pip install -r requirements/dev.in -c requirements/dev.txt\n\n.PHONY: install-dev\ninstall-dev: .develop\n\n.PHONY: sync-direct-runtime-deps\nsync-direct-runtime-deps:\n\t@echo Updating 'requirements/runtime-deps.in' from 'pyproject.toml'... >&2\n\t@python requirements/sync-direct-runtime-deps.py\n"
  },
  {
    "path": "README.rst",
    "content": "==================================\nAsync http client/server framework\n==================================\n\n.. image:: https://raw.githubusercontent.com/aio-libs/aiohttp/master/docs/aiohttp-plain.svg\n   :height: 64px\n   :width: 64px\n   :alt: aiohttp logo\n\n|\n\n.. image:: https://github.com/aio-libs/aiohttp/workflows/CI/badge.svg\n   :target: https://github.com/aio-libs/aiohttp/actions?query=workflow%3ACI\n   :alt: GitHub Actions status for master branch\n\n.. image:: https://codecov.io/gh/aio-libs/aiohttp/branch/master/graph/badge.svg\n   :target: https://codecov.io/gh/aio-libs/aiohttp\n   :alt: codecov.io status for master branch\n\n.. image:: https://badge.fury.io/py/aiohttp.svg\n   :target: https://pypi.org/project/aiohttp\n   :alt: Latest PyPI package version\n\n.. image:: https://img.shields.io/pypi/dm/aiohttp\n   :target: https://pypistats.org/packages/aiohttp\n   :alt: Downloads count\n\n.. image:: https://readthedocs.org/projects/aiohttp/badge/?version=latest\n   :target: https://docs.aiohttp.org/\n   :alt: Latest Read The Docs\n\n.. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json\n   :target: https://codspeed.io/aio-libs/aiohttp\n   :alt: Codspeed.io status for aiohttp\n\n\nKey Features\n============\n\n- Supports both client and server side of HTTP protocol.\n- Supports both client and server Web-Sockets out-of-the-box and avoids\n  Callback Hell.\n- Provides Web-server with middleware and pluggable routing.\n\n\nGetting started\n===============\n\nClient\n------\n\nTo get something from the web:\n\n.. code-block:: python\n\n  import aiohttp\n  import asyncio\n\n  async def main():\n\n      async with aiohttp.ClientSession() as session:\n          async with session.get('http://python.org') as response:\n\n              print(\"Status:\", response.status)\n              print(\"Content-type:\", response.headers['content-type'])\n\n              html = await response.text()\n              print(\"Body:\", html[:15], \"...\")\n\n  asyncio.run(main())\n\nThis prints:\n\n.. code-block::\n\n    Status: 200\n    Content-type: text/html; charset=utf-8\n    Body: <!doctype html> ...\n\nComing from `requests <https://requests.readthedocs.io/>`_ ? Read `why we need so many lines <https://aiohttp.readthedocs.io/en/latest/http_request_lifecycle.html>`_.\n\nServer\n------\n\nAn example using a simple server:\n\n.. code-block:: python\n\n    # examples/server_simple.py\n    from aiohttp import web\n\n    async def handle(request):\n        name = request.match_info.get('name', \"Anonymous\")\n        text = \"Hello, \" + name\n        return web.Response(text=text)\n\n    async def wshandle(request):\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        async for msg in ws:\n            if msg.type == web.WSMsgType.text:\n                await ws.send_str(\"Hello, {}\".format(msg.data))\n            elif msg.type == web.WSMsgType.binary:\n                await ws.send_bytes(msg.data)\n            elif msg.type == web.WSMsgType.close:\n                break\n\n        return ws\n\n\n    app = web.Application()\n    app.add_routes([web.get('/', handle),\n                    web.get('/echo', wshandle),\n                    web.get('/{name}', handle)])\n\n    if __name__ == '__main__':\n        web.run_app(app)\n\n\nDocumentation\n=============\n\nhttps://aiohttp.readthedocs.io/\n\n\nDemos\n=====\n\nhttps://github.com/aio-libs/aiohttp-demos\n\n\nExternal links\n==============\n\n* `Third party libraries\n  <http://aiohttp.readthedocs.io/en/latest/third_party.html>`_\n* `Built with aiohttp\n  <http://aiohttp.readthedocs.io/en/latest/built_with.html>`_\n* `Powered by aiohttp\n  <http://aiohttp.readthedocs.io/en/latest/powered_by.html>`_\n\nFeel free to make a Pull Request for adding your link to these pages!\n\n\nCommunication channels\n======================\n\n*aio-libs Discussions*: https://github.com/aio-libs/aiohttp/discussions\n\n*Matrix*: `#aio-libs:matrix.org <https://matrix.to/#/#aio-libs:matrix.org>`_\n\nWe support `Stack Overflow\n<https://stackoverflow.com/questions/tagged/aiohttp>`_.\nPlease add *aiohttp* tag to your question there.\n\nRequirements\n============\n\n- multidict_\n- yarl_\n- frozenlist_\n\nOptionally you may install the aiodns_ library (highly recommended for sake of speed).\n\n.. _aiodns: https://pypi.python.org/pypi/aiodns\n.. _multidict: https://pypi.python.org/pypi/multidict\n.. _frozenlist: https://pypi.org/project/frozenlist/\n.. _yarl: https://pypi.python.org/pypi/yarl\n\nLicense\n=======\n\n``aiohttp`` is offered under the Apache 2 license.\n\n\nKeepsafe\n========\n\nThe aiohttp community would like to thank Keepsafe\n(https://www.getkeepsafe.com) for its support in the early days of\nthe project.\n\n\nSource code\n===========\n\nThe latest developer version is available in a GitHub repository:\nhttps://github.com/aio-libs/aiohttp\n\nBenchmarks\n==========\n\nIf you are interested in efficiency, the AsyncIO community maintains a\nlist of benchmarks on the official wiki:\nhttps://github.com/python/asyncio/wiki/Benchmarks\n\n--------\n\n.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat\n   :target: https://matrix.to/#/%23aio-libs:matrix.org\n   :alt: Matrix Room — #aio-libs:matrix.org\n\n.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat\n   :target: https://matrix.to/#/%23aio-libs-space:matrix.org\n   :alt: Matrix Space — #aio-libs-space:matrix.org\n\n.. image:: https://insights.linuxfoundation.org/api/badge/health-score?project=aiohttp\n   :target: https://insights.linuxfoundation.org/project/aiohttp\n   :alt: LFX Health Score\n"
  },
  {
    "path": "aiohttp/__init__.py",
    "content": "__version__ = \"4.0.0a2.dev0\"\n\nfrom typing import TYPE_CHECKING\n\nfrom . import hdrs\nfrom .client import (\n    BaseConnector,\n    ClientConnectionError,\n    ClientConnectionResetError,\n    ClientConnectorCertificateError,\n    ClientConnectorDNSError,\n    ClientConnectorError,\n    ClientConnectorSSLError,\n    ClientError,\n    ClientHttpProxyError,\n    ClientOSError,\n    ClientPayloadError,\n    ClientProxyConnectionError,\n    ClientRequest,\n    ClientResponse,\n    ClientResponseError,\n    ClientSession,\n    ClientSSLError,\n    ClientTimeout,\n    ClientWebSocketResponse,\n    ClientWSTimeout,\n    ConnectionTimeoutError,\n    ContentTypeError,\n    Fingerprint,\n    InvalidURL,\n    InvalidUrlClientError,\n    InvalidUrlRedirectClientError,\n    NamedPipeConnector,\n    NonHttpUrlClientError,\n    NonHttpUrlRedirectClientError,\n    RedirectClientError,\n    RequestInfo,\n    ServerConnectionError,\n    ServerDisconnectedError,\n    ServerFingerprintMismatch,\n    ServerTimeoutError,\n    SocketTimeoutError,\n    TCPConnector,\n    TooManyRedirects,\n    UnixConnector,\n    WSMessageTypeError,\n    WSServerHandshakeError,\n    request,\n)\nfrom .client_middleware_digest_auth import DigestAuthMiddleware\nfrom .client_middlewares import ClientHandlerType, ClientMiddlewareType\nfrom .compression_utils import set_zlib_backend\nfrom .connector import AddrInfoType, SocketFactoryType\nfrom .cookiejar import CookieJar, DummyCookieJar\nfrom .formdata import FormData\nfrom .helpers import BasicAuth, ChainMapProxy, ETag\nfrom .http import (\n    HttpVersion,\n    HttpVersion10,\n    HttpVersion11,\n    WebSocketError,\n    WSCloseCode,\n    WSMessage,\n    WSMsgType,\n)\nfrom .multipart import (\n    BadContentDispositionHeader,\n    BadContentDispositionParam,\n    BodyPartReader,\n    MultipartReader,\n    MultipartWriter,\n    content_disposition_filename,\n    parse_content_disposition,\n)\nfrom .payload import (\n    PAYLOAD_REGISTRY,\n    AsyncIterablePayload,\n    BufferedReaderPayload,\n    BytesIOPayload,\n    BytesPayload,\n    IOBasePayload,\n    JsonPayload,\n    Payload,\n    StringIOPayload,\n    StringPayload,\n    TextIOPayload,\n    get_payload,\n    payload_type,\n)\nfrom .resolver import AsyncResolver, DefaultResolver, ThreadedResolver\nfrom .streams import EMPTY_PAYLOAD, DataQueue, EofStream, StreamReader\nfrom .tracing import (\n    TraceConfig,\n    TraceConnectionCreateEndParams,\n    TraceConnectionCreateStartParams,\n    TraceConnectionQueuedEndParams,\n    TraceConnectionQueuedStartParams,\n    TraceConnectionReuseconnParams,\n    TraceDnsCacheHitParams,\n    TraceDnsCacheMissParams,\n    TraceDnsResolveHostEndParams,\n    TraceDnsResolveHostStartParams,\n    TraceRequestChunkSentParams,\n    TraceRequestEndParams,\n    TraceRequestExceptionParams,\n    TraceRequestHeadersSentParams,\n    TraceRequestRedirectParams,\n    TraceRequestStartParams,\n    TraceResponseChunkReceivedParams,\n)\n\nif TYPE_CHECKING:\n    # At runtime these are lazy-loaded at the bottom of the file.\n    from .worker import GunicornUVLoopWebWorker, GunicornWebWorker\n\n__all__: tuple[str, ...] = (\n    \"hdrs\",\n    # client\n    \"AddrInfoType\",\n    \"BaseConnector\",\n    \"ClientConnectionError\",\n    \"ClientConnectionResetError\",\n    \"ClientConnectorCertificateError\",\n    \"ClientConnectorDNSError\",\n    \"ClientConnectorError\",\n    \"ClientConnectorSSLError\",\n    \"ClientError\",\n    \"ClientHttpProxyError\",\n    \"ClientOSError\",\n    \"ClientPayloadError\",\n    \"ClientProxyConnectionError\",\n    \"ClientResponse\",\n    \"ClientRequest\",\n    \"ClientResponseError\",\n    \"ClientSSLError\",\n    \"ClientSession\",\n    \"ClientTimeout\",\n    \"ClientWebSocketResponse\",\n    \"ClientWSTimeout\",\n    \"ConnectionTimeoutError\",\n    \"ContentTypeError\",\n    \"Fingerprint\",\n    \"InvalidURL\",\n    \"InvalidUrlClientError\",\n    \"InvalidUrlRedirectClientError\",\n    \"NonHttpUrlClientError\",\n    \"NonHttpUrlRedirectClientError\",\n    \"RedirectClientError\",\n    \"RequestInfo\",\n    \"ServerConnectionError\",\n    \"ServerDisconnectedError\",\n    \"ServerFingerprintMismatch\",\n    \"ServerTimeoutError\",\n    \"SocketFactoryType\",\n    \"SocketTimeoutError\",\n    \"TCPConnector\",\n    \"TooManyRedirects\",\n    \"UnixConnector\",\n    \"NamedPipeConnector\",\n    \"WSServerHandshakeError\",\n    \"request\",\n    # client_middleware\n    \"ClientMiddlewareType\",\n    \"ClientHandlerType\",\n    # cookiejar\n    \"CookieJar\",\n    \"DummyCookieJar\",\n    # formdata\n    \"FormData\",\n    # helpers\n    \"BasicAuth\",\n    \"ChainMapProxy\",\n    \"DigestAuthMiddleware\",\n    \"ETag\",\n    \"set_zlib_backend\",\n    # http\n    \"HttpVersion\",\n    \"HttpVersion10\",\n    \"HttpVersion11\",\n    \"WSMsgType\",\n    \"WSCloseCode\",\n    \"WSMessage\",\n    \"WebSocketError\",\n    # multipart\n    \"BadContentDispositionHeader\",\n    \"BadContentDispositionParam\",\n    \"BodyPartReader\",\n    \"MultipartReader\",\n    \"MultipartWriter\",\n    \"content_disposition_filename\",\n    \"parse_content_disposition\",\n    # payload\n    \"AsyncIterablePayload\",\n    \"BufferedReaderPayload\",\n    \"BytesIOPayload\",\n    \"BytesPayload\",\n    \"IOBasePayload\",\n    \"JsonPayload\",\n    \"PAYLOAD_REGISTRY\",\n    \"Payload\",\n    \"StringIOPayload\",\n    \"StringPayload\",\n    \"TextIOPayload\",\n    \"get_payload\",\n    \"payload_type\",\n    # resolver\n    \"AsyncResolver\",\n    \"DefaultResolver\",\n    \"ThreadedResolver\",\n    # streams\n    \"DataQueue\",\n    \"EMPTY_PAYLOAD\",\n    \"EofStream\",\n    \"StreamReader\",\n    # tracing\n    \"TraceConfig\",\n    \"TraceConnectionCreateEndParams\",\n    \"TraceConnectionCreateStartParams\",\n    \"TraceConnectionQueuedEndParams\",\n    \"TraceConnectionQueuedStartParams\",\n    \"TraceConnectionReuseconnParams\",\n    \"TraceDnsCacheHitParams\",\n    \"TraceDnsCacheMissParams\",\n    \"TraceDnsResolveHostEndParams\",\n    \"TraceDnsResolveHostStartParams\",\n    \"TraceRequestChunkSentParams\",\n    \"TraceRequestEndParams\",\n    \"TraceRequestExceptionParams\",\n    \"TraceRequestHeadersSentParams\",\n    \"TraceRequestRedirectParams\",\n    \"TraceRequestStartParams\",\n    \"TraceResponseChunkReceivedParams\",\n    # workers (imported lazily with __getattr__)\n    \"GunicornUVLoopWebWorker\",\n    \"GunicornWebWorker\",\n    \"WSMessageTypeError\",\n)\n\n\ndef __dir__() -> tuple[str, ...]:\n    return __all__ + (\"__doc__\",)\n\n\ndef __getattr__(name: str) -> object:\n    global GunicornUVLoopWebWorker, GunicornWebWorker\n\n    # Importing gunicorn takes a long time (>100ms), so only import if actually needed.\n    if name in (\"GunicornUVLoopWebWorker\", \"GunicornWebWorker\"):\n        try:\n            from .worker import GunicornUVLoopWebWorker as guv, GunicornWebWorker as gw\n        except ImportError:\n            return None\n\n        GunicornUVLoopWebWorker = guv  # type: ignore[misc]\n        GunicornWebWorker = gw  # type: ignore[misc]\n        return guv if name == \"GunicornUVLoopWebWorker\" else gw\n\n    raise AttributeError(f\"module {__name__} has no attribute {name}\")\n"
  },
  {
    "path": "aiohttp/_cookie_helpers.py",
    "content": "\"\"\"\nInternal cookie handling helpers.\n\nThis module contains internal utilities for cookie parsing and manipulation.\nThese are not part of the public API and may change without notice.\n\"\"\"\n\nimport re\nfrom collections.abc import Sequence\nfrom http.cookies import Morsel\nfrom typing import cast\n\nfrom .log import internal_logger\n\n__all__ = (\n    \"parse_set_cookie_headers\",\n    \"parse_cookie_header\",\n    \"preserve_morsel_with_coded_value\",\n)\n\n# Cookie parsing constants\n# Allow more characters in cookie names to handle real-world cookies\n# that don't strictly follow RFC standards (fixes #2683)\n# RFC 6265 defines cookie-name token as per RFC 2616 Section 2.2,\n# but many servers send cookies with characters like {} [] () etc.\n# This makes the cookie parser more tolerant of real-world cookies\n# while still providing some validation to catch obviously malformed names.\n_COOKIE_NAME_RE = re.compile(r\"^[!#$%&\\'()*+\\-./0-9:<=>?@A-Z\\[\\]^_`a-z{|}~]+$\")\n_COOKIE_KNOWN_ATTRS = frozenset(  # AKA Morsel._reserved\n    (\n        \"path\",\n        \"domain\",\n        \"max-age\",\n        \"expires\",\n        \"secure\",\n        \"httponly\",\n        \"samesite\",\n        \"partitioned\",\n        \"version\",\n        \"comment\",\n    )\n)\n_COOKIE_BOOL_ATTRS = frozenset(  # AKA Morsel._flags\n    (\"secure\", \"httponly\", \"partitioned\")\n)\n\n# SimpleCookie's pattern for parsing cookies with relaxed validation\n# Based on http.cookies pattern but extended to allow more characters in cookie names\n# to handle real-world cookies (fixes #2683)\n_COOKIE_PATTERN = re.compile(\n    r\"\"\"\n    \\s*                            # Optional whitespace at start of cookie\n    (?P<key>                       # Start of group 'key'\n    # aiohttp has extended to include [] for compatibility with real-world cookies\n    [\\w\\d!#%&'~_`><@,:/\\$\\*\\+\\-\\.\\^\\|\\)\\(\\?\\}\\{\\[\\]]+   # Any word of at least one letter\n    )                              # End of group 'key'\n    (                              # Optional group: there may not be a value.\n    \\s*=\\s*                          # Equal Sign\n    (?P<val>                         # Start of group 'val'\n    \"(?:[^\\\\\"]|\\\\.)*\"                  # Any double-quoted string (properly closed)\n    |                                  # or\n    \"[^\";]*                            # Unmatched opening quote (differs from SimpleCookie - issue #7993)\n    |                                  # or\n    # Special case for \"expires\" attr - RFC 822, RFC 850, RFC 1036, RFC 1123\n    (\\w{3,6}day|\\w{3}),\\s              # Day of the week or abbreviated day (with comma)\n    [\\w\\d\\s-]{9,11}\\s[\\d:]{8}\\s        # Date and time in specific format\n    (GMT|[+-]\\d{4})                     # Timezone: GMT or RFC 2822 offset like -0000, +0100\n                                        # NOTE: RFC 2822 timezone support is an aiohttp extension\n                                        # for issue #4493 - SimpleCookie does NOT support this\n    |                                  # or\n    # ANSI C asctime() format: \"Wed Jun  9 10:18:14 2021\"\n    # NOTE: This is an aiohttp extension for issue #4327 - SimpleCookie does NOT support this format\n    \\w{3}\\s+\\w{3}\\s+[\\s\\d]\\d\\s+\\d{2}:\\d{2}:\\d{2}\\s+\\d{4}\n    |                                  # or\n    [\\w\\d!#%&'~_`><@,:/\\$\\*\\+\\-\\.\\^\\|\\)\\(\\?\\}\\{\\=\\[\\]]*      # Any word or empty string\n    )                                # End of group 'val'\n    )?                             # End of optional value group\n    \\s*                            # Any number of spaces.\n    (\\s+|;|$)                      # Ending either at space, semicolon, or EOS.\n    \"\"\",\n    re.VERBOSE | re.ASCII,\n)\n\n\ndef preserve_morsel_with_coded_value(cookie: Morsel[str]) -> Morsel[str]:\n    \"\"\"\n    Preserve a Morsel's coded_value exactly as received from the server.\n\n    This function ensures that cookie encoding is preserved exactly as sent by\n    the server, which is critical for compatibility with old servers that have\n    strict requirements about cookie formats.\n\n    This addresses the issue described in https://github.com/aio-libs/aiohttp/pull/1453\n    where Python's SimpleCookie would re-encode cookies, breaking authentication\n    with certain servers.\n\n    Args:\n        cookie: A Morsel object from SimpleCookie\n\n    Returns:\n        A Morsel object with preserved coded_value\n\n    \"\"\"\n    mrsl_val = cast(\"Morsel[str]\", cookie.get(cookie.key, Morsel()))\n    # We use __setstate__ instead of the public set() API because it allows us to\n    # bypass validation and set already validated state. This is more stable than\n    # setting protected attributes directly and unlikely to change since it would\n    # break pickling.\n    mrsl_val.__setstate__(  # type: ignore[attr-defined]\n        {\"key\": cookie.key, \"value\": cookie.value, \"coded_value\": cookie.coded_value}\n    )\n    return mrsl_val\n\n\n_unquote_sub = re.compile(r\"\\\\(?:([0-3][0-7][0-7])|(.))\").sub\n\n\ndef _unquote_replace(m: re.Match[str]) -> str:\n    \"\"\"\n    Replace function for _unquote_sub regex substitution.\n\n    Handles escaped characters in cookie values:\n    - Octal sequences are converted to their character representation\n    - Other escaped characters are unescaped by removing the backslash\n    \"\"\"\n    if m[1]:\n        return chr(int(m[1], 8))\n    return m[2]\n\n\ndef _unquote(value: str) -> str:\n    \"\"\"\n    Unquote a cookie value.\n\n    Vendored from http.cookies._unquote to ensure compatibility.\n\n    Note: The original implementation checked for None, but we've removed\n    that check since all callers already ensure the value is not None.\n    \"\"\"\n    # If there aren't any doublequotes,\n    # then there can't be any special characters.  See RFC 2109.\n    if len(value) < 2:\n        return value\n    if value[0] != '\"' or value[-1] != '\"':\n        return value\n\n    # We have to assume that we must decode this string.\n    # Down to work.\n\n    # Remove the \"s\n    value = value[1:-1]\n\n    # Check for special sequences.  Examples:\n    #    \\012 --> \\n\n    #    \\\"   --> \"\n    #\n    return _unquote_sub(_unquote_replace, value)\n\n\ndef parse_cookie_header(header: str) -> list[tuple[str, Morsel[str]]]:\n    \"\"\"\n    Parse a Cookie header according to RFC 6265 Section 5.4.\n\n    Cookie headers contain only name-value pairs separated by semicolons.\n    There are no attributes in Cookie headers - even names that match\n    attribute names (like 'path' or 'secure') should be treated as cookies.\n\n    This parser uses the same regex-based approach as parse_set_cookie_headers\n    to properly handle quoted values that may contain semicolons. When the\n    regex fails to match a malformed cookie, it falls back to simple parsing\n    to ensure subsequent cookies are not lost\n    https://github.com/aio-libs/aiohttp/issues/11632\n\n    Args:\n        header: The Cookie header value to parse\n\n    Returns:\n        List of (name, Morsel) tuples for compatibility with SimpleCookie.update()\n    \"\"\"\n    if not header:\n        return []\n\n    morsel: Morsel[str]\n    cookies: list[tuple[str, Morsel[str]]] = []\n    i = 0\n    n = len(header)\n\n    invalid_names = []\n    while i < n:\n        # Use the same pattern as parse_set_cookie_headers to find cookies\n        match = _COOKIE_PATTERN.match(header, i)\n        if not match:\n            # Fallback for malformed cookies https://github.com/aio-libs/aiohttp/issues/11632\n            # Find next semicolon to skip or attempt simple key=value parsing\n            next_semi = header.find(\";\", i)\n            eq_pos = header.find(\"=\", i)\n\n            # Try to extract key=value if '=' comes before ';'\n            if eq_pos != -1 and (next_semi == -1 or eq_pos < next_semi):\n                end_pos = next_semi if next_semi != -1 else n\n                key = header[i:eq_pos].strip()\n                value = header[eq_pos + 1 : end_pos].strip()\n\n                # Validate the name (same as regex path)\n                if not _COOKIE_NAME_RE.match(key):\n                    invalid_names.append(key)\n                else:\n                    morsel = Morsel()\n                    morsel.__setstate__(  # type: ignore[attr-defined]\n                        {\"key\": key, \"value\": _unquote(value), \"coded_value\": value}\n                    )\n                    cookies.append((key, morsel))\n\n            # Move to next cookie or end\n            i = next_semi + 1 if next_semi != -1 else n\n            continue\n\n        key = match.group(\"key\")\n        value = match.group(\"val\") or \"\"\n        i = match.end(0)\n\n        # Validate the name\n        if not key or not _COOKIE_NAME_RE.match(key):\n            invalid_names.append(key)\n            continue\n\n        # Create new morsel\n        morsel = Morsel()\n        # Preserve the original value as coded_value (with quotes if present)\n        # We use __setstate__ instead of the public set() API because it allows us to\n        # bypass validation and set already validated state. This is more stable than\n        # setting protected attributes directly and unlikely to change since it would\n        # break pickling.\n        morsel.__setstate__(  # type: ignore[attr-defined]\n            {\"key\": key, \"value\": _unquote(value), \"coded_value\": value}\n        )\n\n        cookies.append((key, morsel))\n\n    if invalid_names:\n        internal_logger.debug(\n            \"Cannot load cookie. Illegal cookie names: %r\", invalid_names\n        )\n\n    return cookies\n\n\ndef parse_set_cookie_headers(headers: Sequence[str]) -> list[tuple[str, Morsel[str]]]:\n    \"\"\"\n    Parse cookie headers using a vendored version of SimpleCookie parsing.\n\n    This implementation is based on SimpleCookie.__parse_string to ensure\n    compatibility with how SimpleCookie parses cookies, including handling\n    of malformed cookies with missing semicolons.\n\n    This function is used for both Cookie and Set-Cookie headers in order to be\n    forgiving. Ideally we would have followed RFC 6265 Section 5.2 (for Cookie\n    headers) and RFC 6265 Section 4.2.1 (for Set-Cookie headers), but the\n    real world data makes it impossible since we need to be a bit more forgiving.\n\n    NOTE: This implementation differs from SimpleCookie in handling unmatched quotes.\n    SimpleCookie will stop parsing when it encounters a cookie value with an unmatched\n    quote (e.g., 'cookie=\"value'), causing subsequent cookies to be silently dropped.\n    This implementation handles unmatched quotes more gracefully to prevent cookie loss.\n    See https://github.com/aio-libs/aiohttp/issues/7993\n    \"\"\"\n    parsed_cookies: list[tuple[str, Morsel[str]]] = []\n\n    for header in headers:\n        if not header:\n            continue\n\n        # Parse cookie string using SimpleCookie's algorithm\n        i = 0\n        n = len(header)\n        current_morsel: Morsel[str] | None = None\n        morsel_seen = False\n\n        while 0 <= i < n:\n            # Start looking for a cookie\n            match = _COOKIE_PATTERN.match(header, i)\n            if not match:\n                # No more cookies\n                break\n\n            key, value = match.group(\"key\"), match.group(\"val\")\n            i = match.end(0)\n            lower_key = key.lower()\n\n            if key[0] == \"$\":\n                if not morsel_seen:\n                    # We ignore attributes which pertain to the cookie\n                    # mechanism as a whole, such as \"$Version\".\n                    continue\n                # Process as attribute\n                if current_morsel is not None:\n                    attr_lower_key = lower_key[1:]\n                    if attr_lower_key in _COOKIE_KNOWN_ATTRS:\n                        current_morsel[attr_lower_key] = value or \"\"\n            elif lower_key in _COOKIE_KNOWN_ATTRS:\n                if not morsel_seen:\n                    # Invalid cookie string - attribute before cookie\n                    break\n                if lower_key in _COOKIE_BOOL_ATTRS:\n                    # Boolean attribute with any value should be True\n                    if current_morsel is not None and current_morsel.isReservedKey(key):\n                        current_morsel[lower_key] = True\n                elif value is None:\n                    # Invalid cookie string - non-boolean attribute without value\n                    break\n                elif current_morsel is not None:\n                    # Regular attribute with value\n                    current_morsel[lower_key] = _unquote(value)\n            elif value is not None:\n                # This is a cookie name=value pair\n                # Validate the name\n                if key in _COOKIE_KNOWN_ATTRS or not _COOKIE_NAME_RE.match(key):\n                    internal_logger.warning(\n                        \"Can not load cookies: Illegal cookie name %r\", key\n                    )\n                    current_morsel = None\n                else:\n                    # Create new morsel\n                    current_morsel = Morsel()\n                    # Preserve the original value as coded_value (with quotes if present)\n                    # We use __setstate__ instead of the public set() API because it allows us to\n                    # bypass validation and set already validated state. This is more stable than\n                    # setting protected attributes directly and unlikely to change since it would\n                    # break pickling.\n                    current_morsel.__setstate__(  # type: ignore[attr-defined]\n                        {\"key\": key, \"value\": _unquote(value), \"coded_value\": value}\n                    )\n                    parsed_cookies.append((key, current_morsel))\n                    morsel_seen = True\n            else:\n                # Invalid cookie string - no value for non-attribute\n                break\n\n    return parsed_cookies\n"
  },
  {
    "path": "aiohttp/_cparser.pxd",
    "content": "from libc.stdint cimport int32_t, uint8_t, uint16_t, uint64_t\n\n\ncdef extern from \"llhttp.h\":\n\n    struct llhttp__internal_s:\n        int32_t _index\n        void* _span_pos0\n        void* _span_cb0\n        int32_t error\n        const char* reason\n        const char* error_pos\n        void* data\n        void* _current\n        uint64_t content_length\n        uint8_t type\n        uint8_t method\n        uint8_t http_major\n        uint8_t http_minor\n        uint8_t header_state\n        uint8_t lenient_flags\n        uint8_t upgrade\n        uint8_t finish\n        uint16_t flags\n        uint16_t status_code\n        void* settings\n\n    ctypedef llhttp__internal_s llhttp__internal_t\n    ctypedef llhttp__internal_t llhttp_t\n\n    ctypedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length) except -1\n    ctypedef int (*llhttp_cb)(llhttp_t*) except -1\n\n    struct llhttp_settings_s:\n        llhttp_cb      on_message_begin\n        llhttp_data_cb on_url\n        llhttp_data_cb on_status\n        llhttp_data_cb on_header_field\n        llhttp_data_cb on_header_value\n        llhttp_cb      on_headers_complete\n        llhttp_data_cb on_body\n        llhttp_cb      on_message_complete\n        llhttp_cb      on_chunk_header\n        llhttp_cb      on_chunk_complete\n\n        llhttp_cb      on_url_complete\n        llhttp_cb      on_status_complete\n        llhttp_cb      on_header_field_complete\n        llhttp_cb      on_header_value_complete\n\n    ctypedef llhttp_settings_s llhttp_settings_t\n\n    enum llhttp_errno:\n        HPE_OK,\n        HPE_INTERNAL,\n        HPE_STRICT,\n        HPE_LF_EXPECTED,\n        HPE_UNEXPECTED_CONTENT_LENGTH,\n        HPE_CLOSED_CONNECTION,\n        HPE_INVALID_METHOD,\n        HPE_INVALID_URL,\n        HPE_INVALID_CONSTANT,\n        HPE_INVALID_VERSION,\n        HPE_INVALID_HEADER_TOKEN,\n        HPE_INVALID_CONTENT_LENGTH,\n        HPE_INVALID_CHUNK_SIZE,\n        HPE_INVALID_STATUS,\n        HPE_INVALID_EOF_STATE,\n        HPE_INVALID_TRANSFER_ENCODING,\n        HPE_CB_MESSAGE_BEGIN,\n        HPE_CB_HEADERS_COMPLETE,\n        HPE_CB_MESSAGE_COMPLETE,\n        HPE_CB_CHUNK_HEADER,\n        HPE_CB_CHUNK_COMPLETE,\n        HPE_PAUSED,\n        HPE_PAUSED_UPGRADE,\n        HPE_USER\n\n    ctypedef llhttp_errno llhttp_errno_t\n\n    enum llhttp_flags:\n        F_CHUNKED,\n        F_CONTENT_LENGTH\n\n    enum llhttp_type:\n        HTTP_REQUEST,\n        HTTP_RESPONSE,\n        HTTP_BOTH\n\n    enum llhttp_method:\n        HTTP_DELETE,\n        HTTP_GET,\n        HTTP_HEAD,\n        HTTP_POST,\n        HTTP_PUT,\n        HTTP_CONNECT,\n        HTTP_OPTIONS,\n        HTTP_TRACE,\n        HTTP_COPY,\n        HTTP_LOCK,\n        HTTP_MKCOL,\n        HTTP_MOVE,\n        HTTP_PROPFIND,\n        HTTP_PROPPATCH,\n        HTTP_SEARCH,\n        HTTP_UNLOCK,\n        HTTP_BIND,\n        HTTP_REBIND,\n        HTTP_UNBIND,\n        HTTP_ACL,\n        HTTP_REPORT,\n        HTTP_MKACTIVITY,\n        HTTP_CHECKOUT,\n        HTTP_MERGE,\n        HTTP_MSEARCH,\n        HTTP_NOTIFY,\n        HTTP_SUBSCRIBE,\n        HTTP_UNSUBSCRIBE,\n        HTTP_PATCH,\n        HTTP_PURGE,\n        HTTP_MKCALENDAR,\n        HTTP_LINK,\n        HTTP_UNLINK,\n        HTTP_SOURCE,\n        HTTP_PRI,\n        HTTP_DESCRIBE,\n        HTTP_ANNOUNCE,\n        HTTP_SETUP,\n        HTTP_PLAY,\n        HTTP_PAUSE,\n        HTTP_TEARDOWN,\n        HTTP_GET_PARAMETER,\n        HTTP_SET_PARAMETER,\n        HTTP_REDIRECT,\n        HTTP_RECORD,\n        HTTP_FLUSH\n\n    ctypedef llhttp_method llhttp_method_t;\n\n    void llhttp_settings_init(llhttp_settings_t* settings)\n    void llhttp_init(llhttp_t* parser, llhttp_type type,\n                 const llhttp_settings_t* settings)\n\n    llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len)\n\n    int llhttp_should_keep_alive(const llhttp_t* parser)\n\n    void llhttp_resume_after_upgrade(llhttp_t* parser)\n\n    llhttp_errno_t llhttp_get_errno(const llhttp_t* parser)\n    const char* llhttp_get_error_reason(const llhttp_t* parser)\n    const char* llhttp_get_error_pos(const llhttp_t* parser)\n\n    const char* llhttp_method_name(llhttp_method_t method)\n\n    void llhttp_set_lenient_headers(llhttp_t* parser, int enabled)\n    void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled)\n    void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled)\n"
  },
  {
    "path": "aiohttp/_find_header.h",
    "content": "#ifndef _FIND_HEADERS_H\n#define _FIND_HEADERS_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nint find_header(const char *str, int size);\n\n\n#ifdef __cplusplus\n}\n#endif\n#endif\n"
  },
  {
    "path": "aiohttp/_find_header.pxd",
    "content": "cdef extern from \"_find_header.h\":\n    int find_header(char *, int)\n"
  },
  {
    "path": "aiohttp/_http_parser.pyx",
    "content": "# Based on https://github.com/MagicStack/httptools\n#\n\nfrom cpython cimport (\n    Py_buffer,\n    PyBUF_SIMPLE,\n    PyBuffer_Release,\n    PyBytes_AsString,\n    PyBytes_AsStringAndSize,\n    PyObject_GetBuffer,\n)\nfrom cpython.mem cimport PyMem_Free, PyMem_Malloc\nfrom libc.limits cimport ULLONG_MAX\nfrom libc.string cimport memcpy\n\nfrom multidict import CIMultiDict as _CIMultiDict, CIMultiDictProxy as _CIMultiDictProxy\nfrom yarl import URL as _URL\n\nfrom aiohttp import hdrs\nfrom aiohttp.helpers import DEBUG, set_exception\n\nfrom .http_exceptions import (\n    BadHttpMessage,\n    BadHttpMethod,\n    BadStatusLine,\n    ContentLengthError,\n    InvalidHeader,\n    InvalidURLError,\n    LineTooLong,\n    PayloadEncodingError,\n    TransferEncodingError,\n)\nfrom .http_parser import DeflateBuffer as _DeflateBuffer\nfrom .http_writer import (\n    HttpVersion as _HttpVersion,\n    HttpVersion10 as _HttpVersion10,\n    HttpVersion11 as _HttpVersion11,\n)\nfrom .streams import EMPTY_PAYLOAD as _EMPTY_PAYLOAD, StreamReader as _StreamReader\n\ncimport cython\n\nfrom aiohttp cimport _cparser as cparser\n\ninclude \"_headers.pxi\"\n\nfrom aiohttp cimport _find_header\n\nALLOWED_UPGRADES = frozenset({\"websocket\"})\nDEF DEFAULT_FREELIST_SIZE = 250\n\ncdef extern from \"Python.h\":\n    int PyByteArray_Resize(object, Py_ssize_t) except -1\n    Py_ssize_t PyByteArray_Size(object) except -1\n    char* PyByteArray_AsString(object)\n\n__all__ = ('HttpRequestParser', 'HttpResponseParser',\n           'RawRequestMessage', 'RawResponseMessage')\n\ncdef object URL = _URL\ncdef object URL_build = URL.build\ncdef object CIMultiDict = _CIMultiDict\ncdef object CIMultiDictProxy = _CIMultiDictProxy\ncdef object HttpVersion = _HttpVersion\ncdef object HttpVersion10 = _HttpVersion10\ncdef object HttpVersion11 = _HttpVersion11\ncdef object SEC_WEBSOCKET_KEY1 = hdrs.SEC_WEBSOCKET_KEY1\ncdef object CONTENT_ENCODING = hdrs.CONTENT_ENCODING\ncdef object EMPTY_PAYLOAD = _EMPTY_PAYLOAD\ncdef object StreamReader = _StreamReader\ncdef object DeflateBuffer = _DeflateBuffer\ncdef bytes EMPTY_BYTES = b\"\"\n\n# https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5-6\ncdef tuple SINGLETON_HEADERS = (\n    hdrs.CONTENT_LENGTH,\n    hdrs.CONTENT_LOCATION,\n    hdrs.CONTENT_RANGE,\n    hdrs.CONTENT_TYPE,\n    hdrs.ETAG,\n    hdrs.HOST,\n    hdrs.MAX_FORWARDS,\n    hdrs.SERVER,\n    hdrs.TRANSFER_ENCODING,\n    hdrs.USER_AGENT,\n)\n\ncdef inline object extend(object buf, const char* at, size_t length):\n    cdef Py_ssize_t s\n    cdef char* ptr\n    s = PyByteArray_Size(buf)\n    PyByteArray_Resize(buf, s + length)\n    ptr = PyByteArray_AsString(buf)\n    memcpy(ptr + s, at, length)\n\n\nDEF METHODS_COUNT = 46;\n\ncdef list _http_method = []\n\nfor i in range(METHODS_COUNT):\n    _http_method.append(\n        cparser.llhttp_method_name(<cparser.llhttp_method_t> i).decode('ascii'))\n\n\ncdef inline str http_method_str(int i):\n    if i < METHODS_COUNT:\n        return <str>_http_method[i]\n    else:\n        return \"<unknown>\"\n\ncdef inline object find_header(bytes raw_header):\n    cdef Py_ssize_t size\n    cdef char *buf\n    cdef int idx\n    PyBytes_AsStringAndSize(raw_header, &buf, &size)\n    idx = _find_header.find_header(buf, size)\n    if idx == -1:\n        return raw_header.decode('utf-8', 'surrogateescape')\n    return headers[idx]\n\n\n@cython.freelist(DEFAULT_FREELIST_SIZE)\ncdef class RawRequestMessage:\n    cdef readonly str method\n    cdef readonly str path\n    cdef readonly object version  # HttpVersion\n    cdef readonly object headers  # CIMultiDict\n    cdef readonly object raw_headers  # tuple\n    cdef readonly object should_close\n    cdef readonly object compression\n    cdef readonly object upgrade\n    cdef readonly object chunked\n    cdef readonly object url  # yarl.URL\n\n    def __init__(self, method, path, version, headers, raw_headers,\n                 should_close, compression, upgrade, chunked, url):\n        self.method = method\n        self.path = path\n        self.version = version\n        self.headers = headers\n        self.raw_headers = raw_headers\n        self.should_close = should_close\n        self.compression = compression\n        self.upgrade = upgrade\n        self.chunked = chunked\n        self.url = url\n\n    def __repr__(self):\n        info = []\n        info.append((\"method\", self.method))\n        info.append((\"path\", self.path))\n        info.append((\"version\", self.version))\n        info.append((\"headers\", self.headers))\n        info.append((\"raw_headers\", self.raw_headers))\n        info.append((\"should_close\", self.should_close))\n        info.append((\"compression\", self.compression))\n        info.append((\"upgrade\", self.upgrade))\n        info.append((\"chunked\", self.chunked))\n        info.append((\"url\", self.url))\n        sinfo = ', '.join(name + '=' + repr(val) for name, val in info)\n        return '<RawRequestMessage(' + sinfo + ')>'\n\n    def _replace(self, **dct):\n        cdef RawRequestMessage ret\n        ret = _new_request_message(self.method,\n                                   self.path,\n                                   self.version,\n                                   self.headers,\n                                   self.raw_headers,\n                                   self.should_close,\n                                   self.compression,\n                                   self.upgrade,\n                                   self.chunked,\n                                   self.url)\n        if \"method\" in dct:\n            ret.method = dct[\"method\"]\n        if \"path\" in dct:\n            ret.path = dct[\"path\"]\n        if \"version\" in dct:\n            ret.version = dct[\"version\"]\n        if \"headers\" in dct:\n            ret.headers = dct[\"headers\"]\n        if \"raw_headers\" in dct:\n            ret.raw_headers = dct[\"raw_headers\"]\n        if \"should_close\" in dct:\n            ret.should_close = dct[\"should_close\"]\n        if \"compression\" in dct:\n            ret.compression = dct[\"compression\"]\n        if \"upgrade\" in dct:\n            ret.upgrade = dct[\"upgrade\"]\n        if \"chunked\" in dct:\n            ret.chunked = dct[\"chunked\"]\n        if \"url\" in dct:\n            ret.url = dct[\"url\"]\n        return ret\n\ncdef _new_request_message(str method,\n                           str path,\n                           object version,\n                           object headers,\n                           object raw_headers,\n                           bint should_close,\n                           object compression,\n                           bint upgrade,\n                           bint chunked,\n                           object url):\n    cdef RawRequestMessage ret\n    ret = RawRequestMessage.__new__(RawRequestMessage)\n    ret.method = method\n    ret.path = path\n    ret.version = version\n    ret.headers = headers\n    ret.raw_headers = raw_headers\n    ret.should_close = should_close\n    ret.compression = compression\n    ret.upgrade = upgrade\n    ret.chunked = chunked\n    ret.url = url\n    return ret\n\n\n@cython.freelist(DEFAULT_FREELIST_SIZE)\ncdef class RawResponseMessage:\n    cdef readonly object version  # HttpVersion\n    cdef readonly int code\n    cdef readonly str reason\n    cdef readonly object headers  # CIMultiDict\n    cdef readonly object raw_headers  # tuple\n    cdef readonly object should_close\n    cdef readonly object compression\n    cdef readonly object upgrade\n    cdef readonly object chunked\n\n    def __init__(self, version, code, reason, headers, raw_headers,\n                 should_close, compression, upgrade, chunked):\n        self.version = version\n        self.code = code\n        self.reason = reason\n        self.headers = headers\n        self.raw_headers = raw_headers\n        self.should_close = should_close\n        self.compression = compression\n        self.upgrade = upgrade\n        self.chunked = chunked\n\n    def __repr__(self):\n        info = []\n        info.append((\"version\", self.version))\n        info.append((\"code\", self.code))\n        info.append((\"reason\", self.reason))\n        info.append((\"headers\", self.headers))\n        info.append((\"raw_headers\", self.raw_headers))\n        info.append((\"should_close\", self.should_close))\n        info.append((\"compression\", self.compression))\n        info.append((\"upgrade\", self.upgrade))\n        info.append((\"chunked\", self.chunked))\n        sinfo = ', '.join(name + '=' + repr(val) for name, val in info)\n        return '<RawResponseMessage(' + sinfo + ')>'\n\n\ncdef _new_response_message(object version,\n                           int code,\n                           str reason,\n                           object headers,\n                           object raw_headers,\n                           bint should_close,\n                           object compression,\n                           bint upgrade,\n                           bint chunked):\n    cdef RawResponseMessage ret\n    ret = RawResponseMessage.__new__(RawResponseMessage)\n    ret.version = version\n    ret.code = code\n    ret.reason = reason\n    ret.headers = headers\n    ret.raw_headers = raw_headers\n    ret.should_close = should_close\n    ret.compression = compression\n    ret.upgrade = upgrade\n    ret.chunked = chunked\n    return ret\n\n\n@cython.internal\ncdef class HttpParser:\n\n    cdef:\n        cparser.llhttp_t* _cparser\n        cparser.llhttp_settings_t* _csettings\n\n        bytes _raw_name\n        object _name\n        bytes _raw_value\n        bint      _has_value\n        int _header_name_size\n\n        object _protocol\n        object _loop\n        object _timer\n\n        size_t _max_line_size\n        size_t _max_field_size\n        size_t _max_headers\n        bint _response_with_body\n        bint _read_until_eof\n\n        bint    _started\n        object  _url\n        bytearray   _buf\n        str     _path\n        str     _reason\n        list    _headers\n        list    _raw_headers\n        bint    _upgraded\n        list    _messages\n        object  _payload\n        bint    _payload_error\n        object  _payload_exception\n        object  _last_error\n        bint    _auto_decompress\n        int     _limit\n\n        str     _content_encoding\n\n        Py_buffer py_buf\n\n    def __cinit__(self):\n        self._cparser = <cparser.llhttp_t*> \\\n                                PyMem_Malloc(sizeof(cparser.llhttp_t))\n        if self._cparser is NULL:\n            raise MemoryError()\n\n        self._csettings = <cparser.llhttp_settings_t*> \\\n                                PyMem_Malloc(sizeof(cparser.llhttp_settings_t))\n        if self._csettings is NULL:\n            raise MemoryError()\n\n    def __dealloc__(self):\n        PyMem_Free(self._cparser)\n        PyMem_Free(self._csettings)\n\n    cdef _init(\n        self, cparser.llhttp_type mode,\n        object protocol, object loop, int limit,\n        object timer=None,\n        size_t max_line_size=8190, size_t max_headers=128,\n        size_t max_field_size=8190, payload_exception=None,\n        bint response_with_body=True, bint read_until_eof=False,\n        bint auto_decompress=True,\n    ):\n        cparser.llhttp_settings_init(self._csettings)\n        cparser.llhttp_init(self._cparser, mode, self._csettings)\n        self._cparser.data = <void*>self\n        self._cparser.content_length = 0\n\n        self._protocol = protocol\n        self._loop = loop\n        self._timer = timer\n\n        self._buf = bytearray()\n        self._payload = None\n        self._payload_error = 0\n        self._payload_exception = payload_exception\n        self._messages = []\n\n        self._raw_name = EMPTY_BYTES\n        self._raw_value = EMPTY_BYTES\n        self._has_value = False\n        self._header_name_size = 0\n\n        self._max_line_size = max_line_size\n        self._max_headers = max_headers\n        self._max_field_size = max_field_size\n        self._response_with_body = response_with_body\n        self._read_until_eof = read_until_eof\n        self._upgraded = False\n        self._auto_decompress = auto_decompress\n        self._content_encoding = None\n\n        self._csettings.on_url = cb_on_url\n        self._csettings.on_status = cb_on_status\n        self._csettings.on_header_field = cb_on_header_field\n        self._csettings.on_header_value = cb_on_header_value\n        self._csettings.on_headers_complete = cb_on_headers_complete\n        self._csettings.on_body = cb_on_body\n        self._csettings.on_message_begin = cb_on_message_begin\n        self._csettings.on_message_complete = cb_on_message_complete\n        self._csettings.on_chunk_header = cb_on_chunk_header\n        self._csettings.on_chunk_complete = cb_on_chunk_complete\n\n        self._last_error = None\n        self._limit = limit\n\n    cdef _process_header(self):\n        cdef str value\n        if self._raw_name is not EMPTY_BYTES:\n            name = find_header(self._raw_name)\n            value = self._raw_value.decode('utf-8', 'surrogateescape')\n\n            # reject null bytes in header values - matches the Python parser\n            # check at http_parser.py. llhttp in lenient mode doesn't reject\n            # these itself, so we need to catch them here.\n            # ref: RFC 9110 section 5.5 (CTL chars forbidden in field values)\n            if \"\\x00\" in value:\n                raise InvalidHeader(self._raw_value)\n\n            self._headers.append((name, value))\n            if len(self._headers) > self._max_headers:\n                raise BadHttpMessage(\"Too many headers received\")\n\n            if name is CONTENT_ENCODING:\n                self._content_encoding = value\n\n            self._has_value = False\n            self._header_name_size = 0\n            self._raw_headers.append((self._raw_name, self._raw_value))\n            self._raw_name = EMPTY_BYTES\n            self._raw_value = EMPTY_BYTES\n\n    cdef _on_header_field(self, char* at, size_t length):\n        if self._has_value:\n            self._process_header()\n\n        if self._raw_name is EMPTY_BYTES:\n            self._raw_name = at[:length]\n        else:\n            self._raw_name += at[:length]\n\n    cdef _on_header_value(self, char* at, size_t length):\n        if self._raw_value is EMPTY_BYTES:\n            self._raw_value = at[:length]\n        else:\n            self._raw_value += at[:length]\n        self._has_value = True\n\n    cdef _on_headers_complete(self):\n        self._process_header()\n\n        should_close = not cparser.llhttp_should_keep_alive(self._cparser)\n        upgrade = self._cparser.upgrade\n        chunked = self._cparser.flags & cparser.F_CHUNKED\n\n        raw_headers = tuple(self._raw_headers)\n        headers = CIMultiDictProxy(CIMultiDict(self._headers))\n\n        # https://www.rfc-editor.org/rfc/rfc9110.html#name-collected-abnf\n        bad_hdr = next(\n            (h for h in SINGLETON_HEADERS if len(headers.getall(h, ())) > 1),\n            None,\n        )\n        if bad_hdr is not None:\n            raise BadHttpMessage(f\"Duplicate '{bad_hdr}' header found.\")\n\n        if self._cparser.type == cparser.HTTP_REQUEST:\n            h_upg = headers.get(\"upgrade\", \"\")\n            allowed = upgrade and h_upg.isascii() and h_upg.lower() in ALLOWED_UPGRADES\n            if allowed or self._cparser.method == cparser.HTTP_CONNECT:\n                self._upgraded = True\n        else:\n            if upgrade and self._cparser.status_code == 101:\n                self._upgraded = True\n\n        # do not support old websocket spec\n        if SEC_WEBSOCKET_KEY1 in headers:\n            raise InvalidHeader(SEC_WEBSOCKET_KEY1)\n\n        encoding = None\n        enc = self._content_encoding\n        if enc is not None:\n            self._content_encoding = None\n            if enc.isascii() and enc.lower() in {\"gzip\", \"deflate\", \"br\", \"zstd\"}:\n                encoding = enc\n\n        if self._cparser.type == cparser.HTTP_REQUEST:\n            method = http_method_str(self._cparser.method)\n            msg = _new_request_message(\n                method, self._path,\n                self.http_version(), headers, raw_headers,\n                should_close, encoding, upgrade, chunked, self._url)\n        else:\n            msg = _new_response_message(\n                self.http_version(), self._cparser.status_code, self._reason,\n                headers, raw_headers, should_close, encoding,\n                upgrade, chunked)\n\n        if (\n            ULLONG_MAX > self._cparser.content_length > 0 or chunked or\n            self._cparser.method == cparser.HTTP_CONNECT or\n            (self._cparser.status_code >= 199 and\n             self._cparser.content_length == 0 and\n             self._read_until_eof)\n        ):\n            payload = StreamReader(\n                self._protocol, timer=self._timer, loop=self._loop,\n                limit=self._limit)\n        else:\n            payload = EMPTY_PAYLOAD\n\n        self._payload = payload\n        if encoding is not None and self._auto_decompress:\n            self._payload = DeflateBuffer(payload, encoding)\n\n        if not self._response_with_body:\n            payload = EMPTY_PAYLOAD\n\n        self._messages.append((msg, payload))\n\n    cdef _on_message_complete(self):\n        self._payload.feed_eof()\n        self._payload = None\n\n    cdef _on_chunk_header(self):\n        self._payload.begin_http_chunk_receiving()\n\n    cdef _on_chunk_complete(self):\n        self._payload.end_http_chunk_receiving()\n\n    cdef object _on_status_complete(self):\n        pass\n\n    cdef inline http_version(self):\n        cdef cparser.llhttp_t* parser = self._cparser\n\n        if parser.http_major == 1:\n            if parser.http_minor == 0:\n                return HttpVersion10\n            elif parser.http_minor == 1:\n                return HttpVersion11\n\n        return HttpVersion(parser.http_major, parser.http_minor)\n\n    ### Public API ###\n\n    def feed_eof(self):\n        cdef bytes desc\n\n        if self._payload is not None:\n            if self._cparser.flags & cparser.F_CHUNKED:\n                raise TransferEncodingError(\n                    \"Not enough data to satisfy transfer length header.\")\n            elif self._cparser.flags & cparser.F_CONTENT_LENGTH:\n                raise ContentLengthError(\n                    \"Not enough data to satisfy content length header.\")\n            elif cparser.llhttp_get_errno(self._cparser) != cparser.HPE_OK:\n                desc = cparser.llhttp_get_error_reason(self._cparser)\n                raise PayloadEncodingError(desc.decode('latin-1'))\n            else:\n                self._payload.feed_eof()\n        elif self._started:\n            self._on_headers_complete()\n            if self._messages:\n                return self._messages[-1][0]\n\n    def feed_data(self, data):\n        cdef:\n            size_t data_len\n            size_t nb\n            cdef cparser.llhttp_errno_t errno\n\n        PyObject_GetBuffer(data, &self.py_buf, PyBUF_SIMPLE)\n        data_len = <size_t>self.py_buf.len\n\n        errno = cparser.llhttp_execute(\n            self._cparser,\n            <char*>self.py_buf.buf,\n            data_len)\n\n        if errno is cparser.HPE_PAUSED_UPGRADE:\n            cparser.llhttp_resume_after_upgrade(self._cparser)\n\n            nb = cparser.llhttp_get_error_pos(self._cparser) - <char*>self.py_buf.buf\n\n        PyBuffer_Release(&self.py_buf)\n\n        if errno not in (cparser.HPE_OK, cparser.HPE_PAUSED_UPGRADE):\n            if self._payload_error == 0:\n                if self._last_error is not None:\n                    ex = self._last_error\n                    self._last_error = None\n                else:\n                    after = cparser.llhttp_get_error_pos(self._cparser)\n                    before = data[:after - <char*>self.py_buf.buf]\n                    after_b = after.split(b\"\\r\\n\", 1)[0]\n                    before = before.rsplit(b\"\\r\\n\", 1)[-1]\n                    data = before + after_b\n                    pointer = \" \" * (len(repr(before))-1) + \"^\"\n                    ex = parser_error_from_errno(self._cparser, data, pointer)\n                self._payload = None\n                raise ex\n\n        if self._messages:\n            messages = self._messages\n            self._messages = []\n        else:\n            messages = ()\n\n        if self._upgraded:\n            return messages, True, data[nb:]\n        else:\n            return messages, False, b\"\"\n\n    def set_upgraded(self, val):\n        self._upgraded = val\n\n\ncdef class HttpRequestParser(HttpParser):\n\n    def __init__(\n        self, protocol, loop, int limit, timer=None,\n        size_t max_line_size=8190, size_t max_headers=128,\n        size_t max_field_size=8190, payload_exception=None,\n        bint response_with_body=True, bint read_until_eof=False,\n        bint auto_decompress=True,\n    ):\n        self._init(cparser.HTTP_REQUEST, protocol, loop, limit, timer,\n                   max_line_size, max_headers, max_field_size,\n                   payload_exception, response_with_body, read_until_eof,\n                   auto_decompress)\n\n    cdef object _on_status_complete(self):\n        cdef int idx1, idx2\n        if not self._buf:\n            return\n        self._path = self._buf.decode('utf-8', 'surrogateescape')\n        try:\n            idx3 = len(self._path)\n            if self._cparser.method == cparser.HTTP_CONNECT:\n                # authority-form,\n                # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.3\n                self._url = URL.build(authority=self._path, encoded=True)\n            elif idx3 > 1 and self._path[0] == '/':\n                # origin-form,\n                # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.1\n                idx1 = self._path.find(\"?\")\n                if idx1 == -1:\n                    query = \"\"\n                    idx2 = self._path.find(\"#\")\n                    if idx2 == -1:\n                        path = self._path\n                        fragment = \"\"\n                    else:\n                        path = self._path[0: idx2]\n                        fragment = self._path[idx2+1:]\n\n                else:\n                    path = self._path[0:idx1]\n                    idx1 += 1\n                    idx2 = self._path.find(\"#\", idx1+1)\n                    if idx2 == -1:\n                        query = self._path[idx1:]\n                        fragment = \"\"\n                    else:\n                        query = self._path[idx1: idx2]\n                        fragment = self._path[idx2+1:]\n\n                self._url = URL.build(\n                    path=path,\n                    query_string=query,\n                    fragment=fragment,\n                    encoded=True,\n                )\n            else:\n                # absolute-form for proxy maybe,\n                # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.2\n                self._url = URL(self._path, encoded=True)\n        finally:\n            PyByteArray_Resize(self._buf, 0)\n\n\ncdef class HttpResponseParser(HttpParser):\n\n    def __init__(\n        self, protocol, loop, int limit, timer=None,\n            size_t max_line_size=8190, size_t max_headers=128,\n            size_t max_field_size=8190, payload_exception=None,\n            bint response_with_body=True, bint read_until_eof=False,\n            bint auto_decompress=True\n    ):\n        self._init(cparser.HTTP_RESPONSE, protocol, loop, limit, timer,\n                   max_line_size, max_headers, max_field_size,\n                   payload_exception, response_with_body, read_until_eof,\n                   auto_decompress)\n        # Use strict parsing on dev mode, so users are warned about broken servers.\n        if not DEBUG:\n            cparser.llhttp_set_lenient_headers(self._cparser, 1)\n            cparser.llhttp_set_lenient_optional_cr_before_lf(self._cparser, 1)\n            cparser.llhttp_set_lenient_spaces_after_chunk_size(self._cparser, 1)\n\n    cdef object _on_status_complete(self):\n        if self._buf:\n            self._reason = self._buf.decode('utf-8', 'surrogateescape')\n            PyByteArray_Resize(self._buf, 0)\n        else:\n            self._reason = self._reason or ''\n\ncdef int cb_on_message_begin(cparser.llhttp_t* parser) except -1:\n    cdef HttpParser pyparser = <HttpParser>parser.data\n\n    pyparser._started = True\n    pyparser._headers = []\n    pyparser._raw_headers = []\n    PyByteArray_Resize(pyparser._buf, 0)\n    pyparser._path = None\n    pyparser._reason = None\n    return 0\n\n\ncdef int cb_on_url(cparser.llhttp_t* parser,\n                   const char *at, size_t length) except -1:\n    cdef HttpParser pyparser = <HttpParser>parser.data\n    try:\n        if length > pyparser._max_line_size:\n            status = pyparser._buf + at[:length]\n            raise LineTooLong(status[:100] + b\"...\", pyparser._max_line_size)\n        extend(pyparser._buf, at, length)\n    except BaseException as ex:\n        pyparser._last_error = ex\n        return -1\n    else:\n        return 0\n\n\ncdef int cb_on_status(cparser.llhttp_t* parser,\n                      const char *at, size_t length) except -1:\n    cdef HttpParser pyparser = <HttpParser>parser.data\n    try:\n        if length > pyparser._max_line_size:\n            reason = pyparser._buf + at[:length]\n            raise LineTooLong(reason[:100] + b\"...\", pyparser._max_line_size)\n        extend(pyparser._buf, at, length)\n    except BaseException as ex:\n        pyparser._last_error = ex\n        return -1\n    else:\n        return 0\n\n\ncdef int cb_on_header_field(cparser.llhttp_t* parser,\n                            const char *at, size_t length) except -1:\n    cdef HttpParser pyparser = <HttpParser>parser.data\n    cdef Py_ssize_t size\n    try:\n        pyparser._on_status_complete()\n        size = len(pyparser._raw_name) + length\n        if size > pyparser._max_field_size:\n            name = pyparser._raw_name + at[:length]\n            raise LineTooLong(name[:100] + b\"...\", pyparser._max_field_size)\n        pyparser._header_name_size = size\n        pyparser._on_header_field(at, length)\n    except BaseException as ex:\n        pyparser._last_error = ex\n        return -1\n    else:\n        return 0\n\n\ncdef int cb_on_header_value(cparser.llhttp_t* parser,\n                            const char *at, size_t length) except -1:\n    cdef HttpParser pyparser = <HttpParser>parser.data\n    cdef Py_ssize_t size\n    try:\n        size = len(pyparser._raw_value) + length\n        if pyparser._header_name_size + size > pyparser._max_field_size:\n            value = pyparser._raw_value + at[:length]\n            raise LineTooLong(value[:100] + b\"...\", pyparser._max_field_size)\n        pyparser._on_header_value(at, length)\n    except BaseException as ex:\n        pyparser._last_error = ex\n        return -1\n    else:\n        return 0\n\n\ncdef int cb_on_headers_complete(cparser.llhttp_t* parser) except -1:\n    cdef HttpParser pyparser = <HttpParser>parser.data\n    try:\n        pyparser._on_status_complete()\n        pyparser._on_headers_complete()\n    except BaseException as exc:\n        pyparser._last_error = exc\n        return -1\n    else:\n        if pyparser._upgraded or pyparser._cparser.method == cparser.HTTP_CONNECT:\n            return 2\n        else:\n            return 0\n\n\ncdef int cb_on_body(cparser.llhttp_t* parser,\n                    const char *at, size_t length) except -1:\n    cdef HttpParser pyparser = <HttpParser>parser.data\n    cdef bytes body = at[:length]\n    try:\n        pyparser._payload.feed_data(body)\n    except BaseException as underlying_exc:\n        reraised_exc = underlying_exc\n        if pyparser._payload_exception is not None:\n            reraised_exc = pyparser._payload_exception(str(underlying_exc))\n\n        set_exception(pyparser._payload, reraised_exc, underlying_exc)\n\n        pyparser._payload_error = 1\n        return -1\n    else:\n        return 0\n\n\ncdef int cb_on_message_complete(cparser.llhttp_t* parser) except -1:\n    cdef HttpParser pyparser = <HttpParser>parser.data\n    try:\n        pyparser._started = False\n        pyparser._on_message_complete()\n    except BaseException as exc:\n        pyparser._last_error = exc\n        return -1\n    else:\n        return 0\n\n\ncdef int cb_on_chunk_header(cparser.llhttp_t* parser) except -1:\n    cdef HttpParser pyparser = <HttpParser>parser.data\n    try:\n        pyparser._on_chunk_header()\n    except BaseException as exc:\n        pyparser._last_error = exc\n        return -1\n    else:\n        return 0\n\n\ncdef int cb_on_chunk_complete(cparser.llhttp_t* parser) except -1:\n    cdef HttpParser pyparser = <HttpParser>parser.data\n    try:\n        pyparser._on_chunk_complete()\n    except BaseException as exc:\n        pyparser._last_error = exc\n        return -1\n    else:\n        return 0\n\n\ncdef parser_error_from_errno(cparser.llhttp_t* parser, data, pointer):\n    cdef cparser.llhttp_errno_t errno = cparser.llhttp_get_errno(parser)\n    cdef bytes desc = cparser.llhttp_get_error_reason(parser)\n\n    err_msg = \"{}:\\n\\n  {!r}\\n  {}\".format(desc.decode(\"latin-1\"), data, pointer)\n\n    if errno in {cparser.HPE_CB_MESSAGE_BEGIN,\n                 cparser.HPE_CB_HEADERS_COMPLETE,\n                 cparser.HPE_CB_MESSAGE_COMPLETE,\n                 cparser.HPE_CB_CHUNK_HEADER,\n                 cparser.HPE_CB_CHUNK_COMPLETE,\n                 cparser.HPE_INVALID_CONSTANT,\n                 cparser.HPE_INVALID_HEADER_TOKEN,\n                 cparser.HPE_INVALID_CONTENT_LENGTH,\n                 cparser.HPE_INVALID_CHUNK_SIZE,\n                 cparser.HPE_INVALID_EOF_STATE,\n                 cparser.HPE_INVALID_TRANSFER_ENCODING}:\n        return BadHttpMessage(err_msg)\n    elif errno == cparser.HPE_INVALID_METHOD:\n        return BadHttpMethod(error=err_msg)\n    elif errno in {cparser.HPE_INVALID_STATUS,\n                   cparser.HPE_INVALID_VERSION}:\n        return BadStatusLine(error=err_msg)\n    elif errno == cparser.HPE_INVALID_URL:\n        return InvalidURLError(err_msg)\n\n    return BadHttpMessage(err_msg)\n"
  },
  {
    "path": "aiohttp/_http_writer.pyx",
    "content": "from cpython.bytes cimport PyBytes_FromStringAndSize\nfrom cpython.exc cimport PyErr_NoMemory\nfrom cpython.mem cimport PyMem_Free, PyMem_Malloc, PyMem_Realloc\nfrom cpython.object cimport PyObject_Str\nfrom libc.stdint cimport uint8_t, uint64_t\nfrom libc.string cimport memcpy\n\nfrom multidict import istr\n\nDEF BUF_SIZE = 16 * 1024  # 16KiB\n\ncdef object _istr = istr\n\n\n# ----------------- writer ---------------------------\n\ncdef struct Writer:\n    char *buf\n    Py_ssize_t size\n    Py_ssize_t pos\n    bint heap_allocated\n\ncdef inline void _init_writer(Writer* writer, char *buf):\n    writer.buf = buf\n    writer.size = BUF_SIZE\n    writer.pos = 0\n    writer.heap_allocated = 0\n\n\ncdef inline void _release_writer(Writer* writer):\n    if writer.heap_allocated:\n        PyMem_Free(writer.buf)\n\n\ncdef inline int _write_byte(Writer* writer, uint8_t ch):\n    cdef char * buf\n    cdef Py_ssize_t size\n\n    if writer.pos == writer.size:\n        # reallocate\n        size = writer.size + BUF_SIZE\n        if not writer.heap_allocated:\n            buf = <char*>PyMem_Malloc(size)\n            if buf == NULL:\n                PyErr_NoMemory()\n                return -1\n            memcpy(buf, writer.buf, writer.size)\n        else:\n            buf = <char*>PyMem_Realloc(writer.buf, size)\n            if buf == NULL:\n                PyErr_NoMemory()\n                return -1\n        writer.buf = buf\n        writer.size = size\n        writer.heap_allocated = 1\n    writer.buf[writer.pos] = <char>ch\n    writer.pos += 1\n    return 0\n\n\ncdef inline int _write_utf8(Writer* writer, Py_UCS4 symbol):\n    cdef uint64_t utf = <uint64_t> symbol\n\n    if utf < 0x80:\n        return _write_byte(writer, <uint8_t>utf)\n    elif utf < 0x800:\n        if _write_byte(writer, <uint8_t>(0xc0 | (utf >> 6))) < 0:\n            return -1\n        return _write_byte(writer,  <uint8_t>(0x80 | (utf & 0x3f)))\n    elif 0xD800 <= utf <= 0xDFFF:\n        # surogate pair, ignored\n        return 0\n    elif utf < 0x10000:\n        if _write_byte(writer, <uint8_t>(0xe0 | (utf >> 12))) < 0:\n            return -1\n        if _write_byte(writer, <uint8_t>(0x80 | ((utf >> 6) & 0x3f))) < 0:\n            return -1\n        return _write_byte(writer, <uint8_t>(0x80 | (utf & 0x3f)))\n    elif utf > 0x10FFFF:\n        # symbol is too large\n        return 0\n    else:\n        if _write_byte(writer,  <uint8_t>(0xf0 | (utf >> 18))) < 0:\n            return -1\n        if _write_byte(writer,\n                       <uint8_t>(0x80 | ((utf >> 12) & 0x3f))) < 0:\n           return -1\n        if _write_byte(writer,\n                       <uint8_t>(0x80 | ((utf >> 6) & 0x3f))) < 0:\n            return -1\n        return _write_byte(writer, <uint8_t>(0x80 | (utf & 0x3f)))\n\n\ncdef inline int _write_str(Writer* writer, str s):\n    cdef Py_UCS4 ch\n    for ch in s:\n        if _write_utf8(writer, ch) < 0:\n            return -1\n\n\ncdef inline int _write_str_raise_on_nlcr(Writer* writer, object s):\n    cdef Py_UCS4 ch\n    cdef str out_str\n    if type(s) is str:\n        out_str = <str>s\n    elif type(s) is _istr:\n        out_str = PyObject_Str(s)\n    elif not isinstance(s, str):\n        raise TypeError(\"Cannot serialize non-str key {!r}\".format(s))\n    else:\n        out_str = str(s)\n\n    for ch in out_str:\n        if ch in {0x0D, 0x0A, 0x00}:\n            raise ValueError(\n                \"Newline, carriage return, or null byte detected in headers. \"\n                \"Potential header injection attack.\"\n            )\n        if _write_utf8(writer, ch) < 0:\n            return -1\n\n\n# --------------- _serialize_headers ----------------------\n\ndef _serialize_headers(str status_line, headers):\n    cdef Writer writer\n    cdef object key\n    cdef object val\n    cdef char buf[BUF_SIZE]\n\n    _init_writer(&writer, buf)\n\n    try:\n        if _write_str_raise_on_nlcr(&writer, status_line) < 0:\n            raise\n        if _write_byte(&writer, b'\\r') < 0:\n            raise\n        if _write_byte(&writer, b'\\n') < 0:\n            raise\n\n        for key, val in headers.items():\n            if _write_str_raise_on_nlcr(&writer, key) < 0:\n                raise\n            if _write_byte(&writer, b':') < 0:\n                raise\n            if _write_byte(&writer, b' ') < 0:\n                raise\n            if _write_str_raise_on_nlcr(&writer, val) < 0:\n                raise\n            if _write_byte(&writer, b'\\r') < 0:\n                raise\n            if _write_byte(&writer, b'\\n') < 0:\n                raise\n\n        if _write_byte(&writer, b'\\r') < 0:\n            raise\n        if _write_byte(&writer, b'\\n') < 0:\n            raise\n\n        return PyBytes_FromStringAndSize(writer.buf, writer.pos)\n    finally:\n        _release_writer(&writer)\n"
  },
  {
    "path": "aiohttp/_websocket/__init__.py",
    "content": "\"\"\"WebSocket protocol versions 13 and 8.\"\"\"\n"
  },
  {
    "path": "aiohttp/_websocket/helpers.py",
    "content": "\"\"\"Helpers for WebSocket protocol versions 13 and 8.\"\"\"\n\nimport functools\nimport re\nfrom re import Pattern\nfrom struct import Struct\nfrom typing import TYPE_CHECKING, Final\n\nfrom ..helpers import NO_EXTENSIONS\nfrom .models import WSHandshakeError\n\nUNPACK_LEN3 = Struct(\"!Q\").unpack_from\nUNPACK_CLOSE_CODE = Struct(\"!H\").unpack\nPACK_LEN1 = Struct(\"!BB\").pack\nPACK_LEN2 = Struct(\"!BBH\").pack\nPACK_LEN3 = Struct(\"!BBQ\").pack\nPACK_CLOSE_CODE = Struct(\"!H\").pack\nPACK_RANDBITS = Struct(\"!L\").pack\nMSG_SIZE: Final[int] = 2**14\nMASK_LEN: Final[int] = 4\n\nWS_KEY: Final[bytes] = b\"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\"\n\n\n# Used by _websocket_mask_python\n@functools.lru_cache\ndef _xor_table() -> list[bytes]:\n    return [bytes(a ^ b for a in range(256)) for b in range(256)]\n\n\ndef _websocket_mask_python(mask: bytes, data: bytearray) -> None:\n    \"\"\"Websocket masking function.\n\n    `mask` is a `bytes` object of length 4; `data` is a `bytearray`\n    object of any length. The contents of `data` are masked with `mask`,\n    as specified in section 5.3 of RFC 6455.\n\n    Note that this function mutates the `data` argument.\n\n    This pure-python implementation may be replaced by an optimized\n    version when available.\n\n    \"\"\"\n    assert isinstance(data, bytearray), data\n    assert len(mask) == 4, mask\n\n    if data:\n        _XOR_TABLE = _xor_table()\n        a, b, c, d = (_XOR_TABLE[n] for n in mask)\n        data[::4] = data[::4].translate(a)\n        data[1::4] = data[1::4].translate(b)\n        data[2::4] = data[2::4].translate(c)\n        data[3::4] = data[3::4].translate(d)\n\n\nif TYPE_CHECKING or NO_EXTENSIONS:\n    websocket_mask = _websocket_mask_python\nelse:\n    try:\n        from .mask import _websocket_mask_cython  # type: ignore[import-not-found]\n\n        websocket_mask = _websocket_mask_cython\n    except ImportError:  # pragma: no cover\n        websocket_mask = _websocket_mask_python\n\n\n_WS_EXT_RE: Final[Pattern[str]] = re.compile(\n    r\"^(?:;\\s*(?:\"\n    r\"(server_no_context_takeover)|\"\n    r\"(client_no_context_takeover)|\"\n    r\"(server_max_window_bits(?:=(\\d+))?)|\"\n    r\"(client_max_window_bits(?:=(\\d+))?)))*$\"\n)\n\n_WS_EXT_RE_SPLIT: Final[Pattern[str]] = re.compile(r\"permessage-deflate([^,]+)?\")\n\n\ndef ws_ext_parse(extstr: str | None, isserver: bool = False) -> tuple[int, bool]:\n    if not extstr:\n        return 0, False\n\n    compress = 0\n    notakeover = False\n    for ext in _WS_EXT_RE_SPLIT.finditer(extstr):\n        defext = ext.group(1)\n        # Return compress = 15 when get `permessage-deflate`\n        if not defext:\n            compress = 15\n            break\n        match = _WS_EXT_RE.match(defext)\n        if match:\n            compress = 15\n            if isserver:\n                # Server never fail to detect compress handshake.\n                # Server does not need to send max wbit to client\n                if match.group(4):\n                    compress = int(match.group(4))\n                    # Group3 must match if group4 matches\n                    # Compress wbit 8 does not support in zlib\n                    # If compress level not support,\n                    # CONTINUE to next extension\n                    if compress > 15 or compress < 9:\n                        compress = 0\n                        continue\n                if match.group(1):\n                    notakeover = True\n                # Ignore regex group 5 & 6 for client_max_window_bits\n                break\n            else:\n                if match.group(6):\n                    compress = int(match.group(6))\n                    # Group5 must match if group6 matches\n                    # Compress wbit 8 does not support in zlib\n                    # If compress level not support,\n                    # FAIL the parse progress\n                    if compress > 15 or compress < 9:\n                        raise WSHandshakeError(\"Invalid window size\")\n                if match.group(2):\n                    notakeover = True\n                # Ignore regex group 5 & 6 for client_max_window_bits\n                break\n        # Return Fail if client side and not match\n        elif not isserver:\n            raise WSHandshakeError(\"Extension for deflate not supported\" + ext.group(1))\n\n    return compress, notakeover\n\n\ndef ws_ext_gen(\n    compress: int = 15, isserver: bool = False, server_notakeover: bool = False\n) -> str:\n    # client_notakeover=False not used for server\n    # compress wbit 8 does not support in zlib\n    if compress < 9 or compress > 15:\n        raise ValueError(\n            \"Compress wbits must between 9 and 15, zlib does not support wbits=8\"\n        )\n    enabledext = [\"permessage-deflate\"]\n    if not isserver:\n        enabledext.append(\"client_max_window_bits\")\n\n    if compress < 15:\n        enabledext.append(\"server_max_window_bits=\" + str(compress))\n    if server_notakeover:\n        enabledext.append(\"server_no_context_takeover\")\n    # if client_notakeover:\n    #     enabledext.append('client_no_context_takeover')\n    return \"; \".join(enabledext)\n"
  },
  {
    "path": "aiohttp/_websocket/mask.pxd",
    "content": "\"\"\"Cython declarations for websocket masking.\"\"\"\n\ncpdef void _websocket_mask_cython(bytes mask, bytearray data)\n"
  },
  {
    "path": "aiohttp/_websocket/mask.pyx",
    "content": "from cpython cimport PyBytes_AsString\n\n\n#from cpython cimport PyByteArray_AsString # cython still not exports that\ncdef extern from \"Python.h\":\n    char* PyByteArray_AsString(bytearray ba) except NULL\n\nfrom libc.stdint cimport uint32_t, uint64_t, uintmax_t\n\n\ncpdef void _websocket_mask_cython(bytes mask, bytearray data):\n    \"\"\"Note, this function mutates its `data` argument\n    \"\"\"\n    cdef:\n        Py_ssize_t data_len, i\n        # bit operations on signed integers are implementation-specific\n        unsigned char * in_buf\n        const unsigned char * mask_buf\n        uint32_t uint32_msk\n        uint64_t uint64_msk\n\n    assert len(mask) == 4\n\n    data_len = len(data)\n    in_buf = <unsigned char*>PyByteArray_AsString(data)\n    mask_buf = <const unsigned char*>PyBytes_AsString(mask)\n    uint32_msk = (<uint32_t*>mask_buf)[0]\n\n    # TODO: align in_data ptr to achieve even faster speeds\n    # does it need in python ?! malloc() always aligns to sizeof(long) bytes\n\n    if sizeof(size_t) >= 8:\n        uint64_msk = uint32_msk\n        uint64_msk = (uint64_msk << 32) | uint32_msk\n\n        while data_len >= 8:\n            (<uint64_t*>in_buf)[0] ^= uint64_msk\n            in_buf += 8\n            data_len -= 8\n\n\n    while data_len >= 4:\n        (<uint32_t*>in_buf)[0] ^= uint32_msk\n        in_buf += 4\n        data_len -= 4\n\n    for i in range(0, data_len):\n        in_buf[i] ^= mask_buf[i]\n"
  },
  {
    "path": "aiohttp/_websocket/models.py",
    "content": "\"\"\"Models for WebSocket protocol versions 13 and 8.\"\"\"\n\nimport json\nfrom collections.abc import Callable\nfrom enum import IntEnum\nfrom typing import Any, Final, Literal, NamedTuple, cast\n\nWS_DEFLATE_TRAILING: Final[bytes] = bytes([0x00, 0x00, 0xFF, 0xFF])\n\n\nclass WSCloseCode(IntEnum):\n    OK = 1000\n    GOING_AWAY = 1001\n    PROTOCOL_ERROR = 1002\n    UNSUPPORTED_DATA = 1003\n    ABNORMAL_CLOSURE = 1006\n    INVALID_TEXT = 1007\n    POLICY_VIOLATION = 1008\n    MESSAGE_TOO_BIG = 1009\n    MANDATORY_EXTENSION = 1010\n    INTERNAL_ERROR = 1011\n    SERVICE_RESTART = 1012\n    TRY_AGAIN_LATER = 1013\n    BAD_GATEWAY = 1014\n\n\nclass WSMsgType(IntEnum):\n    # websocket spec types\n    CONTINUATION = 0x0\n    TEXT = 0x1\n    BINARY = 0x2\n    PING = 0x9\n    PONG = 0xA\n    CLOSE = 0x8\n\n    # aiohttp specific types\n    CLOSING = 0x100\n    CLOSED = 0x101\n    ERROR = 0x102\n\n\nclass WSMessageContinuation(NamedTuple):\n    data: bytes\n    size: int\n    extra: str | None = None\n    type: Literal[WSMsgType.CONTINUATION] = WSMsgType.CONTINUATION\n\n\nclass WSMessageText(NamedTuple):\n    data: str\n    size: int\n    extra: str | None = None\n    type: Literal[WSMsgType.TEXT] = WSMsgType.TEXT\n\n    def json(\n        self, *, loads: Callable[[str | bytes | bytearray], Any] = json.loads\n    ) -> Any:\n        \"\"\"Return parsed JSON data.\"\"\"\n        return loads(self.data)\n\n\nclass WSMessageTextBytes(NamedTuple):\n    \"\"\"WebSocket TEXT message with raw bytes (no UTF-8 decoding).\"\"\"\n\n    data: bytes\n    size: int\n    extra: str | None = None\n    type: Literal[WSMsgType.TEXT] = WSMsgType.TEXT\n\n    def json(self, *, loads: Callable[[bytes], Any] = json.loads) -> Any:\n        \"\"\"Return parsed JSON data.\"\"\"\n        return loads(self.data)\n\n\nclass WSMessageBinary(NamedTuple):\n    data: bytes\n    size: int\n    extra: str | None = None\n    type: Literal[WSMsgType.BINARY] = WSMsgType.BINARY\n\n    def json(\n        self, *, loads: Callable[[str | bytes | bytearray], Any] = json.loads\n    ) -> Any:\n        \"\"\"Return parsed JSON data.\"\"\"\n        return loads(self.data)\n\n\nclass WSMessagePing(NamedTuple):\n    data: bytes\n    size: int\n    extra: str | None = None\n    type: Literal[WSMsgType.PING] = WSMsgType.PING\n\n\nclass WSMessagePong(NamedTuple):\n    data: bytes\n    size: int\n    extra: str | None = None\n    type: Literal[WSMsgType.PONG] = WSMsgType.PONG\n\n\nclass WSMessageClose(NamedTuple):\n    data: int\n    size: int\n    extra: str | None = None\n    type: Literal[WSMsgType.CLOSE] = WSMsgType.CLOSE\n\n\nclass WSMessageClosing(NamedTuple):\n    data: None = None\n    size: int = 0\n    extra: str | None = None\n    type: Literal[WSMsgType.CLOSING] = WSMsgType.CLOSING\n\n\nclass WSMessageClosed(NamedTuple):\n    data: None = None\n    size: int = 0\n    extra: str | None = None\n    type: Literal[WSMsgType.CLOSED] = WSMsgType.CLOSED\n\n\nclass WSMessageError(NamedTuple):\n    data: BaseException\n    size: int = 0\n    extra: str | None = None\n    type: Literal[WSMsgType.ERROR] = WSMsgType.ERROR\n\n\n# Base message types (excluding TEXT variants)\n_WSMessageBase = (\n    WSMessageContinuation\n    | WSMessageBinary\n    | WSMessagePing\n    | WSMessagePong\n    | WSMessageClose\n    | WSMessageClosing\n    | WSMessageClosed\n    | WSMessageError\n)\n\n# All message types\nWSMessage = _WSMessageBase | WSMessageText | WSMessageTextBytes\n\n# Message type when decode_text=True (default) - TEXT messages have str data\nWSMessageDecodeText = _WSMessageBase | WSMessageText\n\n# Message type when decode_text=False - TEXT messages have bytes data\nWSMessageNoDecodeText = _WSMessageBase | WSMessageTextBytes\n\nWS_CLOSED_MESSAGE = WSMessageClosed()\nWS_CLOSING_MESSAGE = WSMessageClosing()\n\n\nclass WebSocketError(Exception):\n    \"\"\"WebSocket protocol parser error.\"\"\"\n\n    def __init__(self, code: int, message: str) -> None:\n        self.code = code\n        super().__init__(code, message)\n\n    def __str__(self) -> str:\n        return cast(str, self.args[1])\n\n\nclass WSHandshakeError(Exception):\n    \"\"\"WebSocket protocol handshake error.\"\"\"\n"
  },
  {
    "path": "aiohttp/_websocket/reader.py",
    "content": "\"\"\"Reader for WebSocket protocol versions 13 and 8.\"\"\"\n\nfrom typing import TYPE_CHECKING\n\nfrom ..helpers import NO_EXTENSIONS\n\nif TYPE_CHECKING or NO_EXTENSIONS:\n    from .reader_py import (\n        WebSocketDataQueue as WebSocketDataQueuePython,\n        WebSocketReader as WebSocketReaderPython,\n    )\n\n    WebSocketReader = WebSocketReaderPython\n    WebSocketDataQueue = WebSocketDataQueuePython\nelse:\n    try:\n        from .reader_c import (  # type: ignore[import-not-found]\n            WebSocketDataQueue as WebSocketDataQueueCython,\n            WebSocketReader as WebSocketReaderCython,\n        )\n\n        WebSocketReader = WebSocketReaderCython\n        WebSocketDataQueue = WebSocketDataQueueCython\n    except ImportError:  # pragma: no cover\n        from .reader_py import (\n            WebSocketDataQueue as WebSocketDataQueuePython,\n            WebSocketReader as WebSocketReaderPython,\n        )\n\n        WebSocketReader = WebSocketReaderPython\n        WebSocketDataQueue = WebSocketDataQueuePython\n"
  },
  {
    "path": "aiohttp/_websocket/reader_c.pxd",
    "content": "import cython\n\nfrom .mask cimport _websocket_mask_cython as websocket_mask\n\n\ncdef unsigned int READ_HEADER\ncdef unsigned int READ_PAYLOAD_LENGTH\ncdef unsigned int READ_PAYLOAD_MASK\ncdef unsigned int READ_PAYLOAD\n\ncdef int OP_CODE_NOT_SET\ncdef int OP_CODE_CONTINUATION\ncdef int OP_CODE_TEXT\ncdef int OP_CODE_BINARY\ncdef int OP_CODE_CLOSE\ncdef int OP_CODE_PING\ncdef int OP_CODE_PONG\n\ncdef int COMPRESSED_NOT_SET\ncdef int COMPRESSED_FALSE\ncdef int COMPRESSED_TRUE\n\ncdef object UNPACK_LEN3\ncdef object UNPACK_CLOSE_CODE\ncdef object TUPLE_NEW\n\ncdef object WSMsgType\n\ncdef object WSMessageText\ncdef object WSMessageTextBytes\ncdef object WSMessageBinary\ncdef object WSMessagePing\ncdef object WSMessagePong\ncdef object WSMessageClose\n\ncdef object WS_MSG_TYPE_TEXT\ncdef object WS_MSG_TYPE_BINARY\n\ncdef set ALLOWED_CLOSE_CODES\ncdef set MESSAGE_TYPES_WITH_CONTENT\n\ncdef tuple EMPTY_FRAME\ncdef tuple EMPTY_FRAME_ERROR\n\ncdef class WebSocketDataQueue:\n\n    cdef unsigned int _size\n    cdef public object _protocol\n    cdef unsigned int _limit\n    cdef object _loop\n    cdef bint _eof\n    cdef object _waiter\n    cdef object _exception\n    cdef public object _buffer\n    cdef object _get_buffer\n    cdef object _put_buffer\n\n    cdef void _release_waiter(self)\n\n    @cython.locals(size=\"unsigned int\")\n    cpdef void feed_data(self, object data)\n\n    @cython.locals(size=\"unsigned int\")\n    cdef _read_from_buffer(self)\n\ncdef class WebSocketReader:\n\n    cdef WebSocketDataQueue queue\n    cdef unsigned int _max_msg_size\n    cdef bint _decode_text\n\n    cdef Exception _exc\n    cdef bytearray _partial\n    cdef unsigned int _state\n\n    cdef int _opcode\n    cdef bint _frame_fin\n    cdef int _frame_opcode\n    cdef list _payload_fragments\n    cdef Py_ssize_t _frame_payload_len\n\n    cdef bytes _tail\n    cdef bint _has_mask\n    cdef bytes _frame_mask\n    cdef Py_ssize_t _payload_bytes_to_read\n    cdef unsigned int _payload_len_flag\n    cdef int _compressed\n    cdef object _decompressobj\n    cdef bint _compress\n\n    cpdef tuple feed_data(self, object data)\n\n    @cython.locals(\n        is_continuation=bint,\n        fin=bint,\n        has_partial=bint,\n        payload_merged=bytes,\n    )\n    cpdef void _handle_frame(self, bint fin, int opcode, object payload, int compressed) except *\n\n    @cython.locals(\n        start_pos=Py_ssize_t,\n        data_len=Py_ssize_t,\n        length=Py_ssize_t,\n        chunk_size=Py_ssize_t,\n        chunk_len=Py_ssize_t,\n        data_len=Py_ssize_t,\n        data_cstr=\"const unsigned char *\",\n        first_byte=\"unsigned char\",\n        second_byte=\"unsigned char\",\n        f_start_pos=Py_ssize_t,\n        f_end_pos=Py_ssize_t,\n        has_mask=bint,\n        fin=bint,\n        had_fragments=Py_ssize_t,\n        payload_bytearray=bytearray,\n    )\n    cpdef void _feed_data(self, bytes data) except *\n"
  },
  {
    "path": "aiohttp/_websocket/reader_py.py",
    "content": "\"\"\"Reader for WebSocket protocol versions 13 and 8.\"\"\"\n\nimport asyncio\nimport builtins\nfrom collections import deque\nfrom typing import Final\n\nfrom ..base_protocol import BaseProtocol\nfrom ..compression_utils import ZLibDecompressor\nfrom ..helpers import _EXC_SENTINEL, set_exception\nfrom ..streams import EofStream\nfrom .helpers import UNPACK_CLOSE_CODE, UNPACK_LEN3, websocket_mask\nfrom .models import (\n    WS_DEFLATE_TRAILING,\n    WebSocketError,\n    WSCloseCode,\n    WSMessage,\n    WSMessageBinary,\n    WSMessageClose,\n    WSMessagePing,\n    WSMessagePong,\n    WSMessageText,\n    WSMessageTextBytes,\n    WSMsgType,\n)\n\nALLOWED_CLOSE_CODES: Final[set[int]] = {int(i) for i in WSCloseCode}\n\n# States for the reader, used to parse the WebSocket frame\n# integer values are used so they can be cythonized\nREAD_HEADER = 1\nREAD_PAYLOAD_LENGTH = 2\nREAD_PAYLOAD_MASK = 3\nREAD_PAYLOAD = 4\n\nWS_MSG_TYPE_BINARY = WSMsgType.BINARY\nWS_MSG_TYPE_TEXT = WSMsgType.TEXT\n\n# WSMsgType values unpacked so they can by cythonized to ints\nOP_CODE_NOT_SET = -1\nOP_CODE_CONTINUATION = WSMsgType.CONTINUATION.value\nOP_CODE_TEXT = WSMsgType.TEXT.value\nOP_CODE_BINARY = WSMsgType.BINARY.value\nOP_CODE_CLOSE = WSMsgType.CLOSE.value\nOP_CODE_PING = WSMsgType.PING.value\nOP_CODE_PONG = WSMsgType.PONG.value\n\nEMPTY_FRAME_ERROR = (True, b\"\")\nEMPTY_FRAME = (False, b\"\")\n\nCOMPRESSED_NOT_SET = -1\nCOMPRESSED_FALSE = 0\nCOMPRESSED_TRUE = 1\n\nTUPLE_NEW = tuple.__new__\n\ncython_int = int  # Typed to int in Python, but cython with use a signed int in the pxd\n\n\nclass WebSocketDataQueue:\n    \"\"\"WebSocketDataQueue resumes and pauses an underlying stream.\n\n    It is a destination for WebSocket data.\n    \"\"\"\n\n    def __init__(\n        self, protocol: BaseProtocol, limit: int, *, loop: asyncio.AbstractEventLoop\n    ) -> None:\n        self._size = 0\n        self._protocol = protocol\n        self._limit = limit * 2\n        self._loop = loop\n        self._eof = False\n        self._waiter: asyncio.Future[None] | None = None\n        self._exception: type[BaseException] | BaseException | None = None\n        self._buffer: deque[WSMessage] = deque()\n        self._get_buffer = self._buffer.popleft\n        self._put_buffer = self._buffer.append\n\n    def is_eof(self) -> bool:\n        return self._eof\n\n    def exception(self) -> type[BaseException] | BaseException | None:\n        return self._exception\n\n    def set_exception(\n        self,\n        exc: type[BaseException] | BaseException,\n        exc_cause: builtins.BaseException = _EXC_SENTINEL,\n    ) -> None:\n        self._eof = True\n        self._exception = exc\n        if (waiter := self._waiter) is not None:\n            self._waiter = None\n            set_exception(waiter, exc, exc_cause)\n\n    def _release_waiter(self) -> None:\n        if (waiter := self._waiter) is None:\n            return\n        self._waiter = None\n        if not waiter.done():\n            waiter.set_result(None)\n\n    def feed_eof(self) -> None:\n        self._eof = True\n        self._release_waiter()\n        self._exception = None  # Break cyclic references\n\n    def feed_data(self, data: \"WSMessage\") -> None:\n        size = data.size\n        self._size += size\n        self._put_buffer(data)\n        self._release_waiter()\n        if self._size > self._limit and not self._protocol._reading_paused:\n            self._protocol.pause_reading()\n\n    async def read(self) -> WSMessage:\n        if not self._buffer and not self._eof:\n            assert not self._waiter\n            self._waiter = self._loop.create_future()\n            try:\n                await self._waiter\n            except (asyncio.CancelledError, asyncio.TimeoutError):\n                self._waiter = None\n                raise\n        return self._read_from_buffer()\n\n    def _read_from_buffer(self) -> WSMessage:\n        if self._buffer:\n            data = self._get_buffer()\n            size = data.size\n            self._size -= size\n            if self._size < self._limit and self._protocol._reading_paused:\n                self._protocol.resume_reading()\n            return data\n        if self._exception is not None:\n            raise self._exception\n        raise EofStream\n\n\nclass WebSocketReader:\n    def __init__(\n        self,\n        queue: WebSocketDataQueue,\n        max_msg_size: int,\n        compress: bool = True,\n        decode_text: bool = True,\n    ) -> None:\n        self.queue = queue\n        self._max_msg_size = max_msg_size\n        self._decode_text = decode_text\n\n        self._exc: Exception | None = None\n        self._partial = bytearray()\n        self._state = READ_HEADER\n\n        self._opcode: int = OP_CODE_NOT_SET\n        self._frame_fin = False\n        self._frame_opcode: int = OP_CODE_NOT_SET\n        self._payload_fragments: list[bytes] = []\n        self._frame_payload_len = 0\n\n        self._tail: bytes = b\"\"\n        self._has_mask = False\n        self._frame_mask: bytes | None = None\n        self._payload_bytes_to_read = 0\n        self._payload_len_flag = 0\n        self._compressed: int = COMPRESSED_NOT_SET\n        self._decompressobj: ZLibDecompressor | None = None\n        self._compress = compress\n\n    def feed_eof(self) -> None:\n        self.queue.feed_eof()\n\n    # data can be bytearray on Windows because proactor event loop uses bytearray\n    # and asyncio types this to Union[bytes, bytearray, memoryview] so we need\n    # coerce data to bytes if it is not\n    def feed_data(self, data: bytes | bytearray | memoryview) -> tuple[bool, bytes]:\n        if type(data) is not bytes:\n            data = bytes(data)\n\n        if self._exc is not None:\n            return True, data\n\n        try:\n            self._feed_data(data)\n        except Exception as exc:\n            self._exc = exc\n            set_exception(self.queue, exc)\n            return EMPTY_FRAME_ERROR\n\n        return EMPTY_FRAME\n\n    def _handle_frame(\n        self,\n        fin: bool,\n        opcode: int | cython_int,  # Union intended: Cython pxd uses C int\n        payload: bytes | bytearray,\n        compressed: int | cython_int,  # Union intended: Cython pxd uses C int\n    ) -> None:\n        msg: WSMessage\n        if opcode in {OP_CODE_TEXT, OP_CODE_BINARY, OP_CODE_CONTINUATION}:\n            # Validate continuation frames before processing\n            if opcode == OP_CODE_CONTINUATION and self._opcode == OP_CODE_NOT_SET:\n                raise WebSocketError(\n                    WSCloseCode.PROTOCOL_ERROR,\n                    \"Continuation frame for non started message\",\n                )\n\n            # load text/binary\n            if not fin:\n                # got partial frame payload\n                if opcode != OP_CODE_CONTINUATION:\n                    self._opcode = opcode\n                self._partial += payload\n                if self._max_msg_size and len(self._partial) >= self._max_msg_size:\n                    raise WebSocketError(\n                        WSCloseCode.MESSAGE_TOO_BIG,\n                        f\"Message size {len(self._partial)} \"\n                        f\"exceeds limit {self._max_msg_size}\",\n                    )\n                return\n\n            has_partial = bool(self._partial)\n            if opcode == OP_CODE_CONTINUATION:\n                opcode = self._opcode\n                self._opcode = OP_CODE_NOT_SET\n            # previous frame was non finished\n            # we should get continuation opcode\n            elif has_partial:\n                raise WebSocketError(\n                    WSCloseCode.PROTOCOL_ERROR,\n                    \"The opcode in non-fin frame is expected \"\n                    f\"to be zero, got {opcode!r}\",\n                )\n\n            assembled_payload: bytes | bytearray\n            if has_partial:\n                assembled_payload = self._partial + payload\n                self._partial.clear()\n            else:\n                assembled_payload = payload\n\n            if self._max_msg_size and len(assembled_payload) >= self._max_msg_size:\n                raise WebSocketError(\n                    WSCloseCode.MESSAGE_TOO_BIG,\n                    f\"Message size {len(assembled_payload)} \"\n                    f\"exceeds limit {self._max_msg_size}\",\n                )\n\n            # Decompress process must to be done after all packets\n            # received.\n            if compressed:\n                if not self._decompressobj:\n                    self._decompressobj = ZLibDecompressor(suppress_deflate_header=True)\n                # XXX: It's possible that the zlib backend (isal is known to\n                # do this, maybe others too?) will return max_length bytes,\n                # but internally buffer more data such that the payload is\n                # >max_length, so we return one extra byte and if we're able\n                # to do that, then the message is too big.\n                payload_merged = self._decompressobj.decompress_sync(\n                    assembled_payload + WS_DEFLATE_TRAILING,\n                    (\n                        self._max_msg_size + 1\n                        if self._max_msg_size\n                        else self._max_msg_size\n                    ),\n                )\n                if self._max_msg_size and len(payload_merged) > self._max_msg_size:\n                    raise WebSocketError(\n                        WSCloseCode.MESSAGE_TOO_BIG,\n                        f\"Decompressed message exceeds size limit {self._max_msg_size}\",\n                    )\n            elif type(assembled_payload) is bytes:\n                payload_merged = assembled_payload\n            else:\n                payload_merged = bytes(assembled_payload)\n\n            size = len(payload_merged)\n            if opcode == OP_CODE_TEXT:\n                if self._decode_text:\n                    try:\n                        text = payload_merged.decode(\"utf-8\")\n                    except UnicodeDecodeError as exc:\n                        raise WebSocketError(\n                            WSCloseCode.INVALID_TEXT, \"Invalid UTF-8 text message\"\n                        ) from exc\n\n                    # XXX: The Text and Binary messages here can be a performance\n                    # bottleneck, so we use tuple.__new__ to improve performance.\n                    # This is not type safe, but many tests should fail in\n                    # test_client_ws_functional.py if this is wrong.\n                    msg = TUPLE_NEW(WSMessageText, (text, size, \"\", WS_MSG_TYPE_TEXT))\n                else:\n                    # Return raw bytes for TEXT messages when decode_text=False\n                    msg = TUPLE_NEW(\n                        WSMessageTextBytes, (payload_merged, size, \"\", WS_MSG_TYPE_TEXT)\n                    )\n            else:\n                msg = TUPLE_NEW(\n                    WSMessageBinary, (payload_merged, size, \"\", WS_MSG_TYPE_BINARY)\n                )\n\n            self.queue.feed_data(msg)\n        elif opcode == OP_CODE_CLOSE:\n            payload_len = len(payload)\n            if payload_len >= 2:\n                close_code = UNPACK_CLOSE_CODE(payload[:2])[0]\n                if close_code < 3000 and close_code not in ALLOWED_CLOSE_CODES:\n                    raise WebSocketError(\n                        WSCloseCode.PROTOCOL_ERROR,\n                        f\"Invalid close code: {close_code}\",\n                    )\n                try:\n                    close_message = payload[2:].decode(\"utf-8\")\n                except UnicodeDecodeError as exc:\n                    raise WebSocketError(\n                        WSCloseCode.INVALID_TEXT, \"Invalid UTF-8 text message\"\n                    ) from exc\n                msg = WSMessageClose(\n                    data=close_code, size=payload_len, extra=close_message\n                )\n            elif payload:\n                raise WebSocketError(\n                    WSCloseCode.PROTOCOL_ERROR,\n                    f\"Invalid close frame: {fin} {opcode} {payload!r}\",\n                )\n            else:\n                msg = WSMessageClose(data=0, size=payload_len, extra=\"\")\n\n            self.queue.feed_data(msg)\n        elif opcode == OP_CODE_PING:\n            self.queue.feed_data(\n                WSMessagePing(data=bytes(payload), size=len(payload), extra=\"\")\n            )\n        elif opcode == OP_CODE_PONG:\n            self.queue.feed_data(\n                WSMessagePong(data=bytes(payload), size=len(payload), extra=\"\")\n            )\n        else:\n            raise WebSocketError(\n                WSCloseCode.PROTOCOL_ERROR, f\"Unexpected opcode={opcode!r}\"\n            )\n\n    def _feed_data(self, data: bytes) -> None:\n        \"\"\"Return the next frame from the socket.\"\"\"\n        if self._tail:\n            data, self._tail = self._tail + data, b\"\"\n\n        start_pos: int = 0\n        data_len = len(data)\n        data_cstr = data\n\n        while True:\n            # read header\n            if self._state == READ_HEADER:\n                if data_len - start_pos < 2:\n                    break\n                first_byte = data_cstr[start_pos]\n                second_byte = data_cstr[start_pos + 1]\n                start_pos += 2\n\n                fin = (first_byte >> 7) & 1\n                rsv1 = (first_byte >> 6) & 1\n                rsv2 = (first_byte >> 5) & 1\n                rsv3 = (first_byte >> 4) & 1\n                opcode = first_byte & 0xF\n\n                # frame-fin = %x0 ; more frames of this message follow\n                #           / %x1 ; final frame of this message\n                # frame-rsv1 = %x0 ;\n                #    1 bit, MUST be 0 unless negotiated otherwise\n                # frame-rsv2 = %x0 ;\n                #    1 bit, MUST be 0 unless negotiated otherwise\n                # frame-rsv3 = %x0 ;\n                #    1 bit, MUST be 0 unless negotiated otherwise\n                #\n                # Remove rsv1 from this test for deflate development\n                if rsv2 or rsv3 or (rsv1 and not self._compress):\n                    raise WebSocketError(\n                        WSCloseCode.PROTOCOL_ERROR,\n                        \"Received frame with non-zero reserved bits\",\n                    )\n\n                if opcode > 0x7 and fin == 0:\n                    raise WebSocketError(\n                        WSCloseCode.PROTOCOL_ERROR,\n                        \"Received fragmented control frame\",\n                    )\n\n                has_mask = (second_byte >> 7) & 1\n                length = second_byte & 0x7F\n\n                # Control frames MUST have a payload\n                # length of 125 bytes or less\n                if opcode > 0x7 and length > 125:\n                    raise WebSocketError(\n                        WSCloseCode.PROTOCOL_ERROR,\n                        \"Control frame payload cannot be larger than 125 bytes\",\n                    )\n\n                # Set compress status if last package is FIN\n                # OR set compress status if this is first fragment\n                # Raise error if not first fragment with rsv1 = 0x1\n                if self._frame_fin or self._compressed == COMPRESSED_NOT_SET:\n                    self._compressed = COMPRESSED_TRUE if rsv1 else COMPRESSED_FALSE\n                elif rsv1:\n                    raise WebSocketError(\n                        WSCloseCode.PROTOCOL_ERROR,\n                        \"Received frame with non-zero reserved bits\",\n                    )\n\n                self._frame_fin = bool(fin)\n                self._frame_opcode = opcode\n                self._has_mask = bool(has_mask)\n                self._payload_len_flag = length\n                self._state = READ_PAYLOAD_LENGTH\n\n            # read payload length\n            if self._state == READ_PAYLOAD_LENGTH:\n                len_flag = self._payload_len_flag\n                if len_flag == 126:\n                    if data_len - start_pos < 2:\n                        break\n                    first_byte = data_cstr[start_pos]\n                    second_byte = data_cstr[start_pos + 1]\n                    start_pos += 2\n                    self._payload_bytes_to_read = first_byte << 8 | second_byte\n                elif len_flag > 126:\n                    if data_len - start_pos < 8:\n                        break\n                    self._payload_bytes_to_read = UNPACK_LEN3(data, start_pos)[0]\n                    start_pos += 8\n                else:\n                    self._payload_bytes_to_read = len_flag\n\n                self._state = READ_PAYLOAD_MASK if self._has_mask else READ_PAYLOAD\n\n            # read payload mask\n            if self._state == READ_PAYLOAD_MASK:\n                if data_len - start_pos < 4:\n                    break\n                self._frame_mask = data_cstr[start_pos : start_pos + 4]\n                start_pos += 4\n                self._state = READ_PAYLOAD\n\n            if self._state == READ_PAYLOAD:\n                chunk_len = data_len - start_pos\n                if self._payload_bytes_to_read >= chunk_len:\n                    f_end_pos = data_len\n                    self._payload_bytes_to_read -= chunk_len\n                else:\n                    f_end_pos = start_pos + self._payload_bytes_to_read\n                    self._payload_bytes_to_read = 0\n\n                had_fragments = self._frame_payload_len\n                self._frame_payload_len += f_end_pos - start_pos\n                f_start_pos = start_pos\n                start_pos = f_end_pos\n\n                if self._payload_bytes_to_read != 0:\n                    # If we don't have a complete frame, we need to save the\n                    # data for the next call to feed_data.\n                    self._payload_fragments.append(data_cstr[f_start_pos:f_end_pos])\n                    break\n\n                payload: bytes | bytearray\n                if had_fragments:\n                    # We have to join the payload fragments get the payload\n                    self._payload_fragments.append(data_cstr[f_start_pos:f_end_pos])\n                    if self._has_mask:\n                        assert self._frame_mask is not None\n                        payload_bytearray = bytearray(b\"\".join(self._payload_fragments))\n                        websocket_mask(self._frame_mask, payload_bytearray)\n                        payload = payload_bytearray\n                    else:\n                        payload = b\"\".join(self._payload_fragments)\n                    self._payload_fragments.clear()\n                elif self._has_mask:\n                    assert self._frame_mask is not None\n                    payload_bytearray = data_cstr[f_start_pos:f_end_pos]  # type: ignore[assignment]\n                    if type(payload_bytearray) is not bytearray:  # pragma: no branch\n                        # Cython will do the conversion for us\n                        # but we need to do it for Python and we\n                        # will always get here in Python\n                        payload_bytearray = bytearray(payload_bytearray)\n                    websocket_mask(self._frame_mask, payload_bytearray)\n                    payload = payload_bytearray\n                else:\n                    payload = data_cstr[f_start_pos:f_end_pos]\n\n                self._handle_frame(\n                    self._frame_fin, self._frame_opcode, payload, self._compressed\n                )\n                self._frame_payload_len = 0\n                self._state = READ_HEADER\n\n        # XXX: Cython needs slices to be bounded, so we can't omit the slice end here.\n        self._tail = data_cstr[start_pos:data_len] if start_pos < data_len else b\"\"\n"
  },
  {
    "path": "aiohttp/_websocket/writer.py",
    "content": "\"\"\"WebSocket protocol versions 13 and 8.\"\"\"\n\nimport asyncio\nimport random\nimport sys\nfrom functools import partial\nfrom typing import Final\n\nfrom ..base_protocol import BaseProtocol\nfrom ..client_exceptions import ClientConnectionResetError\nfrom ..compression_utils import ZLibBackend, ZLibCompressor\nfrom .helpers import (\n    MASK_LEN,\n    MSG_SIZE,\n    PACK_CLOSE_CODE,\n    PACK_LEN1,\n    PACK_LEN2,\n    PACK_LEN3,\n    PACK_RANDBITS,\n    websocket_mask,\n)\nfrom .models import WS_DEFLATE_TRAILING, WSMsgType\n\nDEFAULT_LIMIT: Final[int] = 2**16\n\n# WebSocket opcode boundary: opcodes 0-7 are data frames, 8-15 are control frames\n# Control frames (ping, pong, close) are never compressed\nWS_CONTROL_FRAME_OPCODE: Final[int] = 8\n\n# For websockets, keeping latency low is extremely important as implementations\n# generally expect to be able to send and receive messages quickly. We use a\n# larger chunk size to reduce the number of executor calls and avoid task\n# creation overhead, since both are significant sources of latency when chunks\n# are small. A size of 16KiB was chosen as a balance between avoiding task\n# overhead and not blocking the event loop too long with synchronous compression.\n\nWEBSOCKET_MAX_SYNC_CHUNK_SIZE = 16 * 1024\n\n\nclass WebSocketWriter:\n    \"\"\"WebSocket writer.\n\n    The writer is responsible for sending messages to the client. It is\n    created by the protocol when a connection is established. The writer\n    should avoid implementing any application logic and should only be\n    concerned with the low-level details of the WebSocket protocol.\n    \"\"\"\n\n    def __init__(\n        self,\n        protocol: BaseProtocol,\n        transport: asyncio.Transport,\n        *,\n        use_mask: bool = False,\n        limit: int = DEFAULT_LIMIT,\n        random: random.Random = random.Random(),\n        compress: int = 0,\n        notakeover: bool = False,\n    ) -> None:\n        \"\"\"Initialize a WebSocket writer.\"\"\"\n        self.protocol = protocol\n        self.transport = transport\n        self.use_mask = use_mask\n        self.get_random_bits = partial(random.getrandbits, 32)\n        self.compress = compress\n        self.notakeover = notakeover\n        self._closing = False\n        self._limit = limit\n        self._output_size = 0\n        self._compressobj: ZLibCompressor | None = None\n        self._send_lock = asyncio.Lock()\n        self._background_tasks: set[asyncio.Task[None]] = set()\n\n    async def send_frame(\n        self, message: bytes, opcode: int, compress: int | None = None\n    ) -> None:\n        \"\"\"Send a frame over the websocket with message as its payload.\"\"\"\n        if self._closing and not (opcode & WSMsgType.CLOSE):\n            raise ClientConnectionResetError(\"Cannot write to closing transport\")\n\n        if not (compress or self.compress) or opcode >= WS_CONTROL_FRAME_OPCODE:\n            # Non-compressed frames don't need lock or shield\n            self._write_websocket_frame(message, opcode, 0)\n        elif len(message) <= WEBSOCKET_MAX_SYNC_CHUNK_SIZE:\n            # Small compressed payloads - compress synchronously in event loop\n            # We need the lock even though sync compression has no await points.\n            # This prevents small frames from interleaving with large frames that\n            # compress in the executor, avoiding compressor state corruption.\n            async with self._send_lock:\n                self._send_compressed_frame_sync(message, opcode, compress)\n        else:\n            # Large compressed frames need shield to prevent corruption\n            # For large compressed frames, the entire compress+send\n            # operation must be atomic. If cancelled after compression but\n            # before send, the compressor state would be advanced but data\n            # not sent, corrupting subsequent frames.\n            # Create a task to shield from cancellation\n            # The lock is acquired inside the shielded task so the entire\n            # operation (lock + compress + send) completes atomically.\n            # Use eager_start on Python 3.12+ to avoid scheduling overhead\n            loop = asyncio.get_running_loop()\n            coro = self._send_compressed_frame_async_locked(message, opcode, compress)\n            if sys.version_info >= (3, 12):\n                send_task = asyncio.Task(coro, loop=loop, eager_start=True)\n            else:\n                send_task = loop.create_task(coro)\n            # Keep a strong reference to prevent garbage collection\n            self._background_tasks.add(send_task)\n            send_task.add_done_callback(self._background_tasks.discard)\n            await asyncio.shield(send_task)\n\n        # It is safe to return control to the event loop when using compression\n        # after this point as we have already sent or buffered all the data.\n        # Once we have written output_size up to the limit, we call the\n        # drain helper which waits for the transport to be ready to accept\n        # more data. This is a flow control mechanism to prevent the buffer\n        # from growing too large. The drain helper will return right away\n        # if the writer is not paused.\n        if self._output_size > self._limit:\n            self._output_size = 0\n            if self.protocol._paused:\n                await self.protocol._drain_helper()\n\n    def _write_websocket_frame(self, message: bytes, opcode: int, rsv: int) -> None:\n        \"\"\"\n        Write a websocket frame to the transport.\n\n        This method handles frame header construction, masking, and writing to transport.\n        It does not handle compression or flow control - those are the responsibility\n        of the caller.\n        \"\"\"\n        msg_length = len(message)\n\n        use_mask = self.use_mask\n        mask_bit = 0x80 if use_mask else 0\n\n        # Depending on the message length, the header is assembled differently.\n        # The first byte is reserved for the opcode and the RSV bits.\n        first_byte = 0x80 | rsv | opcode\n        if msg_length < 126:\n            header = PACK_LEN1(first_byte, msg_length | mask_bit)\n            header_len = 2\n        elif msg_length < 65536:\n            header = PACK_LEN2(first_byte, 126 | mask_bit, msg_length)\n            header_len = 4\n        else:\n            header = PACK_LEN3(first_byte, 127 | mask_bit, msg_length)\n            header_len = 10\n\n        if self.transport.is_closing():\n            raise ClientConnectionResetError(\"Cannot write to closing transport\")\n\n        # https://datatracker.ietf.org/doc/html/rfc6455#section-5.3\n        # If we are using a mask, we need to generate it randomly\n        # and apply it to the message before sending it. A mask is\n        # a 32-bit value that is applied to the message using a\n        # bitwise XOR operation. It is used to prevent certain types\n        # of attacks on the websocket protocol. The mask is only used\n        # when aiohttp is acting as a client. Servers do not use a mask.\n        if use_mask:\n            mask = PACK_RANDBITS(self.get_random_bits())\n            message_arr = bytearray(message)\n            websocket_mask(mask, message_arr)\n            self.transport.write(header + mask + message_arr)\n            self._output_size += MASK_LEN\n        elif msg_length > MSG_SIZE:\n            self.transport.write(header)\n            self.transport.write(message)\n        else:\n            self.transport.write(header + message)\n\n        self._output_size += header_len + msg_length\n\n    def _get_compressor(self, compress: int | None) -> ZLibCompressor:\n        \"\"\"Get or create a compressor object for the given compression level.\"\"\"\n        if compress:\n            # Do not set self._compress if compressing is for this frame\n            return ZLibCompressor(\n                level=ZLibBackend.Z_BEST_SPEED,\n                wbits=-compress,\n                max_sync_chunk_size=WEBSOCKET_MAX_SYNC_CHUNK_SIZE,\n            )\n        if not self._compressobj:\n            self._compressobj = ZLibCompressor(\n                level=ZLibBackend.Z_BEST_SPEED,\n                wbits=-self.compress,\n                max_sync_chunk_size=WEBSOCKET_MAX_SYNC_CHUNK_SIZE,\n            )\n        return self._compressobj\n\n    def _send_compressed_frame_sync(\n        self, message: bytes, opcode: int, compress: int | None\n    ) -> None:\n        \"\"\"\n        Synchronous send for small compressed frames.\n\n        This is used for small compressed payloads that compress synchronously in the event loop.\n        Since there are no await points, this is inherently cancellation-safe.\n        \"\"\"\n        # RSV are the reserved bits in the frame header. They are used to\n        # indicate that the frame is using an extension.\n        # https://datatracker.ietf.org/doc/html/rfc6455#section-5.2\n        compressobj = self._get_compressor(compress)\n        # (0x40) RSV1 is set for compressed frames\n        # https://datatracker.ietf.org/doc/html/rfc7692#section-7.2.3.1\n        self._write_websocket_frame(\n            (\n                compressobj.compress_sync(message)\n                + compressobj.flush(\n                    ZLibBackend.Z_FULL_FLUSH\n                    if self.notakeover\n                    else ZLibBackend.Z_SYNC_FLUSH\n                )\n            ).removesuffix(WS_DEFLATE_TRAILING),\n            opcode,\n            0x40,\n        )\n\n    async def _send_compressed_frame_async_locked(\n        self, message: bytes, opcode: int, compress: int | None\n    ) -> None:\n        \"\"\"\n        Async send for large compressed frames with lock.\n\n        Acquires the lock and compresses large payloads asynchronously in\n        the executor. The lock is held for the entire operation to ensure\n        the compressor state is not corrupted by concurrent sends.\n\n        MUST be run shielded from cancellation. If cancelled after\n        compression but before sending, the compressor state would be\n        advanced but data not sent, corrupting subsequent frames.\n        \"\"\"\n        async with self._send_lock:\n            # RSV are the reserved bits in the frame header. They are used to\n            # indicate that the frame is using an extension.\n            # https://datatracker.ietf.org/doc/html/rfc6455#section-5.2\n            compressobj = self._get_compressor(compress)\n            # (0x40) RSV1 is set for compressed frames\n            # https://datatracker.ietf.org/doc/html/rfc7692#section-7.2.3.1\n            self._write_websocket_frame(\n                (\n                    await compressobj.compress(message)\n                    + compressobj.flush(\n                        ZLibBackend.Z_FULL_FLUSH\n                        if self.notakeover\n                        else ZLibBackend.Z_SYNC_FLUSH\n                    )\n                ).removesuffix(WS_DEFLATE_TRAILING),\n                opcode,\n                0x40,\n            )\n\n    async def close(self, code: int = 1000, message: bytes | str = b\"\") -> None:\n        \"\"\"Close the websocket, sending the specified code and message.\"\"\"\n        if isinstance(message, str):\n            message = message.encode(\"utf-8\")\n        try:\n            await self.send_frame(\n                PACK_CLOSE_CODE(code) + message, opcode=WSMsgType.CLOSE\n            )\n        finally:\n            self._closing = True\n"
  },
  {
    "path": "aiohttp/abc.py",
    "content": "import logging\nimport socket\nfrom abc import ABC, abstractmethod\nfrom collections.abc import Awaitable, Callable, Generator, Iterable, Sequence, Sized\nfrom http.cookies import BaseCookie, Morsel\nfrom typing import TYPE_CHECKING, Any, TypedDict\n\nfrom multidict import CIMultiDict\nfrom yarl import URL\n\nfrom ._cookie_helpers import parse_set_cookie_headers\nfrom .typedefs import LooseCookies\n\nif TYPE_CHECKING:\n    from .web_app import Application\n    from .web_exceptions import HTTPException\n    from .web_request import BaseRequest, Request\n    from .web_response import StreamResponse\nelse:\n    BaseRequest = Request = Application = StreamResponse = Any\n    HTTPException = Any\n\n\nclass AbstractRouter(ABC):\n    def __init__(self) -> None:\n        self._frozen = False\n\n    def post_init(self, app: Application) -> None:\n        \"\"\"Post init stage.\n\n        Not an abstract method for sake of backward compatibility,\n        but if the router wants to be aware of the application\n        it can override this.\n        \"\"\"\n\n    @property\n    def frozen(self) -> bool:\n        return self._frozen\n\n    def freeze(self) -> None:\n        \"\"\"Freeze router.\"\"\"\n        self._frozen = True\n\n    @abstractmethod\n    async def resolve(self, request: Request) -> \"AbstractMatchInfo\":\n        \"\"\"Return MATCH_INFO for given request\"\"\"\n\n\nclass AbstractMatchInfo(ABC):\n\n    __slots__ = ()\n\n    @property  # pragma: no branch\n    @abstractmethod\n    def handler(self) -> Callable[[Request], Awaitable[StreamResponse]]:\n        \"\"\"Execute matched request handler\"\"\"\n\n    @property\n    @abstractmethod\n    def expect_handler(\n        self,\n    ) -> Callable[[Request], Awaitable[StreamResponse | None]]:\n        \"\"\"Expect handler for 100-continue processing\"\"\"\n\n    @property  # pragma: no branch\n    @abstractmethod\n    def http_exception(self) -> HTTPException | None:\n        \"\"\"HTTPException instance raised on router's resolving, or None\"\"\"\n\n    @abstractmethod  # pragma: no branch\n    def get_info(self) -> dict[str, Any]:\n        \"\"\"Return a dict with additional info useful for introspection\"\"\"\n\n    @property  # pragma: no branch\n    @abstractmethod\n    def apps(self) -> tuple[Application, ...]:\n        \"\"\"Stack of nested applications.\n\n        Top level application is left-most element.\n\n        \"\"\"\n\n    @abstractmethod\n    def add_app(self, app: Application) -> None:\n        \"\"\"Add application to the nested apps stack.\"\"\"\n\n    @abstractmethod\n    def freeze(self) -> None:\n        \"\"\"Freeze the match info.\n\n        The method is called after route resolution.\n\n        After the call .add_app() is forbidden.\n\n        \"\"\"\n\n\nclass AbstractView(ABC):\n    \"\"\"Abstract class based view.\"\"\"\n\n    def __init__(self, request: Request) -> None:\n        self._request = request\n\n    @property\n    def request(self) -> Request:\n        \"\"\"Request instance.\"\"\"\n        return self._request\n\n    @abstractmethod\n    def __await__(self) -> Generator[None, None, StreamResponse]:\n        \"\"\"Execute the view handler.\"\"\"\n\n\nclass ResolveResult(TypedDict):\n    \"\"\"Resolve result.\n\n    This is the result returned from an AbstractResolver's\n    resolve method.\n\n    :param hostname: The hostname that was provided.\n    :param host: The IP address that was resolved.\n    :param port: The port that was resolved.\n    :param family: The address family that was resolved.\n    :param proto: The protocol that was resolved.\n    :param flags: The flags that were resolved.\n    \"\"\"\n\n    hostname: str\n    host: str\n    port: int\n    family: int\n    proto: int\n    flags: int\n\n\nclass AbstractResolver(ABC):\n    \"\"\"Abstract DNS resolver.\"\"\"\n\n    @abstractmethod\n    async def resolve(\n        self, host: str, port: int = 0, family: socket.AddressFamily = socket.AF_INET\n    ) -> list[ResolveResult]:\n        \"\"\"Return IP address for given hostname\"\"\"\n\n    @abstractmethod\n    async def close(self) -> None:\n        \"\"\"Release resolver\"\"\"\n\n\nClearCookiePredicate = Callable[[Morsel[str]], bool]\n\n\nclass AbstractCookieJar(Sized, Iterable[Morsel[str]]):\n    \"\"\"Abstract Cookie Jar.\"\"\"\n\n    @property\n    @abstractmethod\n    def quote_cookie(self) -> bool:\n        \"\"\"Return True if cookies should be quoted.\"\"\"\n\n    @abstractmethod\n    def clear(self, predicate: ClearCookiePredicate | None = None) -> None:\n        \"\"\"Clear all cookies if no predicate is passed.\"\"\"\n\n    @abstractmethod\n    def clear_domain(self, domain: str) -> None:\n        \"\"\"Clear all cookies for domain and all subdomains.\"\"\"\n\n    @abstractmethod\n    def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None:\n        \"\"\"Update cookies.\"\"\"\n\n    def update_cookies_from_headers(\n        self, headers: Sequence[str], response_url: URL\n    ) -> None:\n        \"\"\"Update cookies from raw Set-Cookie headers.\"\"\"\n        if headers and (cookies_to_update := parse_set_cookie_headers(headers)):\n            self.update_cookies(cookies_to_update, response_url)\n\n    @abstractmethod\n    def filter_cookies(self, request_url: URL) -> BaseCookie[str]:\n        \"\"\"Return the jar's cookies filtered by their attributes.\"\"\"\n\n\nclass AbstractStreamWriter(ABC):\n    \"\"\"Abstract stream writer.\"\"\"\n\n    buffer_size: int = 0\n    output_size: int = 0\n    length: int | None = 0\n\n    @abstractmethod\n    async def write(\n        self, chunk: \"bytes | bytearray | memoryview[int] | memoryview[bytes]\"\n    ) -> None:\n        \"\"\"Write chunk into stream.\"\"\"\n\n    @abstractmethod\n    async def write_eof(self, chunk: bytes = b\"\") -> None:\n        \"\"\"Write last chunk.\"\"\"\n\n    @abstractmethod\n    async def drain(self) -> None:\n        \"\"\"Flush the write buffer.\"\"\"\n\n    @abstractmethod\n    def enable_compression(\n        self, encoding: str = \"deflate\", strategy: int | None = None\n    ) -> None:\n        \"\"\"Enable HTTP body compression\"\"\"\n\n    @abstractmethod\n    def enable_chunking(self) -> None:\n        \"\"\"Enable HTTP chunked mode\"\"\"\n\n    @abstractmethod\n    async def write_headers(self, status_line: str, headers: CIMultiDict[str]) -> None:\n        \"\"\"Write HTTP headers\"\"\"\n\n    def send_headers(self) -> None:\n        \"\"\"Force sending buffered headers if not already sent.\n\n        Required only if write_headers() buffers headers instead of sending immediately.\n        For backwards compatibility, this method does nothing by default.\n        \"\"\"\n\n\nclass AbstractAccessLogger(ABC):\n    \"\"\"Abstract writer to access log.\"\"\"\n\n    __slots__ = (\"logger\", \"log_format\")\n\n    def __init__(self, logger: logging.Logger, log_format: str) -> None:\n        self.logger = logger\n        self.log_format = log_format\n\n    @abstractmethod\n    def log(self, request: BaseRequest, response: StreamResponse, time: float) -> None:\n        \"\"\"Emit log to logger.\"\"\"\n\n    @property\n    def enabled(self) -> bool:\n        \"\"\"Check if logger is enabled.\"\"\"\n        return True\n\n\nclass AbstractAsyncAccessLogger(ABC):\n    \"\"\"Abstract asynchronous writer to access log.\"\"\"\n\n    __slots__ = ()\n\n    @abstractmethod\n    async def log(\n        self, request: BaseRequest, response: StreamResponse, request_start: float\n    ) -> None:\n        \"\"\"Emit log to logger.\"\"\"\n\n    @property\n    def enabled(self) -> bool:\n        \"\"\"Check if logger is enabled.\"\"\"\n        return True\n"
  },
  {
    "path": "aiohttp/base_protocol.py",
    "content": "import asyncio\nfrom typing import cast\n\nfrom .client_exceptions import ClientConnectionResetError\nfrom .helpers import set_exception\nfrom .tcp_helpers import tcp_nodelay\n\n\nclass BaseProtocol(asyncio.Protocol):\n    __slots__ = (\n        \"_loop\",\n        \"_paused\",\n        \"_drain_waiter\",\n        \"_connection_lost\",\n        \"_reading_paused\",\n        \"transport\",\n    )\n\n    def __init__(self, loop: asyncio.AbstractEventLoop) -> None:\n        self._loop: asyncio.AbstractEventLoop = loop\n        self._paused = False\n        self._drain_waiter: asyncio.Future[None] | None = None\n        self._reading_paused = False\n\n        self.transport: asyncio.Transport | None = None\n\n    @property\n    def connected(self) -> bool:\n        \"\"\"Return True if the connection is open.\"\"\"\n        return self.transport is not None\n\n    @property\n    def writing_paused(self) -> bool:\n        return self._paused\n\n    def pause_writing(self) -> None:\n        assert not self._paused\n        self._paused = True\n\n    def resume_writing(self) -> None:\n        assert self._paused\n        self._paused = False\n\n        waiter = self._drain_waiter\n        if waiter is not None:\n            self._drain_waiter = None\n            if not waiter.done():\n                waiter.set_result(None)\n\n    def pause_reading(self) -> None:\n        if not self._reading_paused and self.transport is not None:\n            try:\n                self.transport.pause_reading()\n            except (AttributeError, NotImplementedError, RuntimeError):\n                pass\n            self._reading_paused = True\n\n    def resume_reading(self) -> None:\n        if self._reading_paused and self.transport is not None:\n            try:\n                self.transport.resume_reading()\n            except (AttributeError, NotImplementedError, RuntimeError):\n                pass\n            self._reading_paused = False\n\n    def connection_made(self, transport: asyncio.BaseTransport) -> None:\n        tr = cast(asyncio.Transport, transport)\n        tcp_nodelay(tr, True)\n        self.transport = tr\n\n    def connection_lost(self, exc: BaseException | None) -> None:\n        # Wake up the writer if currently paused.\n        self.transport = None\n        if not self._paused:\n            return\n        waiter = self._drain_waiter\n        if waiter is None:\n            return\n        self._drain_waiter = None\n        if waiter.done():\n            return\n        if exc is None:\n            waiter.set_result(None)\n        else:\n            set_exception(\n                waiter,\n                ConnectionError(\"Connection lost\"),\n                exc,\n            )\n\n    async def _drain_helper(self) -> None:\n        if self.transport is None:\n            raise ClientConnectionResetError(\"Connection lost\")\n        if not self._paused:\n            return\n        waiter = self._drain_waiter\n        if waiter is None:\n            waiter = self._loop.create_future()\n            self._drain_waiter = waiter\n        await asyncio.shield(waiter)\n"
  },
  {
    "path": "aiohttp/client.py",
    "content": "\"\"\"HTTP Client for asyncio.\"\"\"\n\nimport asyncio\nimport base64\nimport dataclasses\nimport hashlib\nimport json\nimport os\nimport sys\nimport traceback\nimport warnings\nfrom collections.abc import (\n    Awaitable,\n    Callable,\n    Collection,\n    Coroutine,\n    Generator,\n    Iterable,\n    Sequence,\n)\nfrom contextlib import suppress\nfrom types import TracebackType\nfrom typing import (\n    TYPE_CHECKING,\n    Any,\n    Final,\n    Generic,\n    Literal,\n    TypedDict,\n    TypeVar,\n    final,\n    overload,\n)\n\nfrom multidict import CIMultiDict, MultiDict, MultiDictProxy, istr\nfrom yarl import URL, Query\n\nfrom . import hdrs, http, payload\nfrom ._websocket.reader import WebSocketDataQueue\nfrom .abc import AbstractCookieJar\nfrom .client_exceptions import (\n    ClientConnectionError,\n    ClientConnectionResetError,\n    ClientConnectorCertificateError,\n    ClientConnectorDNSError,\n    ClientConnectorError,\n    ClientConnectorSSLError,\n    ClientError,\n    ClientHttpProxyError,\n    ClientOSError,\n    ClientPayloadError,\n    ClientProxyConnectionError,\n    ClientResponseError,\n    ClientSSLError,\n    ConnectionTimeoutError,\n    ContentTypeError,\n    InvalidURL,\n    InvalidUrlClientError,\n    InvalidUrlRedirectClientError,\n    NonHttpUrlClientError,\n    NonHttpUrlRedirectClientError,\n    RedirectClientError,\n    ServerConnectionError,\n    ServerDisconnectedError,\n    ServerFingerprintMismatch,\n    ServerTimeoutError,\n    SocketTimeoutError,\n    TooManyRedirects,\n    WSMessageTypeError,\n    WSServerHandshakeError,\n)\nfrom .client_middlewares import ClientMiddlewareType, build_client_middlewares\nfrom .client_reqrep import (\n    SSL_ALLOWED_TYPES,\n    ClientRequest,\n    ClientResponse,\n    Fingerprint,\n    RequestInfo,\n)\nfrom .client_ws import (\n    DEFAULT_WS_CLIENT_TIMEOUT,\n    ClientWebSocketResponse,\n    ClientWSTimeout,\n)\nfrom .connector import (\n    HTTP_AND_EMPTY_SCHEMA_SET,\n    BaseConnector,\n    NamedPipeConnector,\n    TCPConnector,\n    UnixConnector,\n)\nfrom .cookiejar import CookieJar\nfrom .helpers import (\n    _SENTINEL,\n    EMPTY_BODY_METHODS,\n    BasicAuth,\n    TimeoutHandle,\n    basicauth_from_netrc,\n    frozen_dataclass_decorator,\n    get_env_proxy_for_url,\n    netrc_from_env,\n    sentinel,\n    strip_auth_from_url,\n)\nfrom .http import WS_KEY, HttpVersion, WebSocketReader, WebSocketWriter\nfrom .http_websocket import WSHandshakeError, ws_ext_gen, ws_ext_parse\nfrom .tracing import Trace, TraceConfig\nfrom .typedefs import (\n    JSONBytesEncoder,\n    JSONEncoder,\n    LooseCookies,\n    LooseHeaders,\n    StrOrURL,\n)\n\n__all__ = (\n    # client_exceptions\n    \"ClientConnectionError\",\n    \"ClientConnectionResetError\",\n    \"ClientConnectorCertificateError\",\n    \"ClientConnectorDNSError\",\n    \"ClientConnectorError\",\n    \"ClientConnectorSSLError\",\n    \"ClientError\",\n    \"ClientHttpProxyError\",\n    \"ClientOSError\",\n    \"ClientPayloadError\",\n    \"ClientProxyConnectionError\",\n    \"ClientResponseError\",\n    \"ClientSSLError\",\n    \"ConnectionTimeoutError\",\n    \"ContentTypeError\",\n    \"InvalidURL\",\n    \"InvalidUrlClientError\",\n    \"RedirectClientError\",\n    \"NonHttpUrlClientError\",\n    \"InvalidUrlRedirectClientError\",\n    \"NonHttpUrlRedirectClientError\",\n    \"ServerConnectionError\",\n    \"ServerDisconnectedError\",\n    \"ServerFingerprintMismatch\",\n    \"ServerTimeoutError\",\n    \"SocketTimeoutError\",\n    \"TooManyRedirects\",\n    \"WSServerHandshakeError\",\n    # client_reqrep\n    \"ClientRequest\",\n    \"ClientResponse\",\n    \"Fingerprint\",\n    \"RequestInfo\",\n    # connector\n    \"BaseConnector\",\n    \"TCPConnector\",\n    \"UnixConnector\",\n    \"NamedPipeConnector\",\n    # client_ws\n    \"ClientWebSocketResponse\",\n    # client\n    \"ClientSession\",\n    \"ClientTimeout\",\n    \"ClientWSTimeout\",\n    \"request\",\n    \"WSMessageTypeError\",\n)\n\n\nif TYPE_CHECKING:\n    from ssl import SSLContext\nelse:\n    SSLContext = None\n\nif sys.version_info >= (3, 11) and TYPE_CHECKING:\n    from typing import Unpack\n\n\nclass _RequestOptions(TypedDict, total=False):\n    params: Query\n    data: Any\n    json: Any\n    cookies: LooseCookies | None\n    headers: LooseHeaders | None\n    skip_auto_headers: Iterable[str] | None\n    auth: BasicAuth | None\n    allow_redirects: bool\n    max_redirects: int\n    compress: str | bool\n    chunked: bool | None\n    expect100: bool\n    raise_for_status: None | bool | Callable[[ClientResponse], Awaitable[None]]\n    read_until_eof: bool\n    proxy: StrOrURL | None\n    proxy_auth: BasicAuth | None\n    timeout: \"ClientTimeout | _SENTINEL | None\"\n    ssl: SSLContext | bool | Fingerprint\n    server_hostname: str | None\n    proxy_headers: LooseHeaders | None\n    trace_request_ctx: object\n    read_bufsize: int | None\n    auto_decompress: bool | None\n    max_line_size: int | None\n    max_field_size: int | None\n    max_headers: int | None\n    middlewares: Sequence[ClientMiddlewareType] | None\n\n\nclass _WSConnectOptions(TypedDict, total=False):\n    method: str\n    protocols: Collection[str]\n    timeout: \"ClientWSTimeout | _SENTINEL\"\n    receive_timeout: float | None\n    autoclose: bool\n    autoping: bool\n    heartbeat: float | None\n    auth: BasicAuth | None\n    origin: str | None\n    params: Query\n    headers: LooseHeaders | None\n    proxy: StrOrURL | None\n    proxy_auth: BasicAuth | None\n    ssl: SSLContext | bool | Fingerprint\n    server_hostname: str | None\n    proxy_headers: LooseHeaders | None\n    compress: int\n    max_msg_size: int\n\n\n@frozen_dataclass_decorator\nclass ClientTimeout:\n    total: float | None = None\n    connect: float | None = None\n    sock_read: float | None = None\n    sock_connect: float | None = None\n    ceil_threshold: float = 5\n\n    # pool_queue_timeout: Optional[float] = None\n    # dns_resolution_timeout: Optional[float] = None\n    # socket_connect_timeout: Optional[float] = None\n    # connection_acquiring_timeout: Optional[float] = None\n    # new_connection_timeout: Optional[float] = None\n    # http_header_timeout: Optional[float] = None\n    # response_body_timeout: Optional[float] = None\n\n    # to create a timeout specific for a single request, either\n    # - create a completely new one to overwrite the default\n    # - or use https://docs.python.org/3/library/dataclasses.html#dataclasses.replace\n    # to overwrite the defaults\n\n    def __post_init__(self) -> None:\n        if self.total is not None and self.total == 0:\n            raise ValueError(\n                \"total timeout must be a positive number or None to disable, \"\n                \"got 0. Using 0 to disable timeouts is no longer supported, \"\n                \"use None instead.\"\n            )\n\n\n# 5 Minute default read timeout\nDEFAULT_TIMEOUT: Final[ClientTimeout] = ClientTimeout(total=5 * 60, sock_connect=30)\n\n# https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2\nIDEMPOTENT_METHODS = frozenset({\"GET\", \"HEAD\", \"OPTIONS\", \"TRACE\", \"PUT\", \"DELETE\"})\n\n_RetType_co = TypeVar(\n    \"_RetType_co\",\n    bound=\"ClientResponse | ClientWebSocketResponse[bool]\",\n    covariant=True,\n)\n_CharsetResolver = Callable[[ClientResponse, bytes], str]\n\n\n@final\nclass ClientSession:\n    \"\"\"First-class interface for making HTTP requests.\"\"\"\n\n    __slots__ = (\n        \"_base_url\",\n        \"_base_url_origin\",\n        \"_source_traceback\",\n        \"_connector\",\n        \"_loop\",\n        \"_cookie_jar\",\n        \"_connector_owner\",\n        \"_default_auth\",\n        \"_version\",\n        \"_json_serialize\",\n        \"_json_serialize_bytes\",\n        \"_requote_redirect_url\",\n        \"_timeout\",\n        \"_raise_for_status\",\n        \"_auto_decompress\",\n        \"_trust_env\",\n        \"_default_headers\",\n        \"_skip_auto_headers\",\n        \"_request_class\",\n        \"_response_class\",\n        \"_ws_response_class\",\n        \"_trace_configs\",\n        \"_read_bufsize\",\n        \"_max_line_size\",\n        \"_max_field_size\",\n        \"_max_headers\",\n        \"_resolve_charset\",\n        \"_default_proxy\",\n        \"_default_proxy_auth\",\n        \"_retry_connection\",\n        \"_middlewares\",\n    )\n\n    def __init__(\n        self,\n        base_url: StrOrURL | None = None,\n        *,\n        connector: BaseConnector | None = None,\n        cookies: LooseCookies | None = None,\n        headers: LooseHeaders | None = None,\n        proxy: StrOrURL | None = None,\n        proxy_auth: BasicAuth | None = None,\n        skip_auto_headers: Iterable[str] | None = None,\n        auth: BasicAuth | None = None,\n        json_serialize: JSONEncoder = json.dumps,\n        json_serialize_bytes: JSONBytesEncoder | None = None,\n        request_class: type[ClientRequest] = ClientRequest,\n        response_class: type[ClientResponse] = ClientResponse,\n        ws_response_class: type[ClientWebSocketResponse] = ClientWebSocketResponse,\n        version: HttpVersion = http.HttpVersion11,\n        cookie_jar: AbstractCookieJar | None = None,\n        connector_owner: bool = True,\n        raise_for_status: bool | Callable[[ClientResponse], Awaitable[None]] = False,\n        timeout: _SENTINEL | ClientTimeout | None = sentinel,\n        auto_decompress: bool = True,\n        trust_env: bool = False,\n        requote_redirect_url: bool = True,\n        trace_configs: list[TraceConfig[object]] | None = None,\n        read_bufsize: int = 2**16,\n        max_line_size: int = 8190,\n        max_field_size: int = 8190,\n        max_headers: int = 128,\n        fallback_charset_resolver: _CharsetResolver = lambda r, b: \"utf-8\",\n        middlewares: Sequence[ClientMiddlewareType] = (),\n        ssl_shutdown_timeout: _SENTINEL | None | float = sentinel,\n    ) -> None:\n        # We initialise _connector to None immediately, as it's referenced in __del__()\n        # and could cause issues if an exception occurs during initialisation.\n        self._connector: BaseConnector | None = None\n        if base_url is None or isinstance(base_url, URL):\n            self._base_url: URL | None = base_url\n            self._base_url_origin = None if base_url is None else base_url.origin()\n        else:\n            self._base_url = URL(base_url)\n            self._base_url_origin = self._base_url.origin()\n            assert self._base_url.absolute, \"Only absolute URLs are supported\"\n        if self._base_url is not None and not self._base_url.path.endswith(\"/\"):\n            raise ValueError(\"base_url must have a trailing '/'\")\n\n        loop = asyncio.get_running_loop()\n\n        if timeout is sentinel or timeout is None:\n            timeout = DEFAULT_TIMEOUT\n        if not isinstance(timeout, ClientTimeout):\n            raise ValueError(\n                f\"timeout parameter cannot be of {type(timeout)} type, \"\n                \"please use 'timeout=ClientTimeout(...)'\",\n            )\n        self._timeout = timeout\n\n        if ssl_shutdown_timeout is not sentinel:\n            warnings.warn(\n                \"The ssl_shutdown_timeout parameter is deprecated and will be removed in aiohttp 4.0\",\n                DeprecationWarning,\n                stacklevel=2,\n            )\n\n        if connector is None:\n            connector = TCPConnector(ssl_shutdown_timeout=ssl_shutdown_timeout)\n        # Initialize these three attrs before raising any exception,\n        # they are used in __del__\n        self._connector = connector\n        self._loop = loop\n        if loop.get_debug():\n            self._source_traceback: traceback.StackSummary | None = (\n                traceback.extract_stack(sys._getframe(1))\n            )\n        else:\n            self._source_traceback = None\n\n        if connector._loop is not loop:\n            raise RuntimeError(\"Session and connector have to use same event loop\")\n\n        if cookie_jar is None:\n            cookie_jar = CookieJar()\n        self._cookie_jar = cookie_jar\n\n        if cookies:\n            self._cookie_jar.update_cookies(cookies)\n\n        self._connector_owner = connector_owner\n        self._default_auth = auth\n        self._version = version\n        self._json_serialize = json_serialize\n        self._json_serialize_bytes = json_serialize_bytes\n        self._raise_for_status = raise_for_status\n        self._auto_decompress = auto_decompress\n        self._trust_env = trust_env\n        self._requote_redirect_url = requote_redirect_url\n        self._read_bufsize = read_bufsize\n        self._max_line_size = max_line_size\n        self._max_field_size = max_field_size\n        self._max_headers = max_headers\n\n        # Convert to list of tuples\n        if headers:\n            real_headers: CIMultiDict[str] = CIMultiDict(headers)\n        else:\n            real_headers = CIMultiDict()\n        self._default_headers: CIMultiDict[str] = real_headers\n        if skip_auto_headers is not None:\n            self._skip_auto_headers = frozenset(istr(i) for i in skip_auto_headers)\n        else:\n            self._skip_auto_headers = frozenset()\n\n        self._request_class = request_class\n        self._response_class = response_class\n        self._ws_response_class = ws_response_class\n\n        self._trace_configs = trace_configs or []\n        for trace_config in self._trace_configs:\n            trace_config.freeze()\n\n        self._resolve_charset = fallback_charset_resolver\n\n        self._default_proxy = proxy\n        self._default_proxy_auth = proxy_auth\n        self._retry_connection: bool = True\n        self._middlewares = middlewares\n\n    def __init_subclass__(cls: type[\"ClientSession\"]) -> None:\n        raise TypeError(\n            f\"Inheritance class {cls.__name__} from ClientSession is forbidden\"\n        )\n\n    def __del__(self, _warnings: Any = warnings) -> None:\n        if not self.closed:\n            _warnings.warn(\n                f\"Unclosed client session {self!r}\",\n                ResourceWarning,\n                source=self,\n            )\n            context = {\"client_session\": self, \"message\": \"Unclosed client session\"}\n            if self._source_traceback is not None:\n                context[\"source_traceback\"] = self._source_traceback\n            self._loop.call_exception_handler(context)\n\n    if sys.version_info >= (3, 11) and TYPE_CHECKING:\n\n        def request(\n            self,\n            method: str,\n            url: StrOrURL,\n            **kwargs: Unpack[_RequestOptions],\n        ) -> \"_RequestContextManager\": ...\n\n    else:\n\n        def request(\n            self, method: str, url: StrOrURL, **kwargs: Any\n        ) -> \"_RequestContextManager\":\n            \"\"\"Perform HTTP request.\"\"\"\n            return _RequestContextManager(self._request(method, url, **kwargs))\n\n    def _build_url(self, str_or_url: StrOrURL) -> URL:\n        url = URL(str_or_url)\n        if self._base_url and not url.absolute:\n            return self._base_url.join(url)\n        return url\n\n    async def _request(\n        self,\n        method: str,\n        str_or_url: StrOrURL,\n        *,\n        params: Query = None,\n        data: Any = None,\n        json: Any = None,\n        cookies: LooseCookies | None = None,\n        headers: LooseHeaders | None = None,\n        skip_auto_headers: Iterable[str] | None = None,\n        auth: BasicAuth | None = None,\n        allow_redirects: bool = True,\n        max_redirects: int = 10,\n        compress: str | bool = False,\n        chunked: bool | None = None,\n        expect100: bool = False,\n        raise_for_status: (\n            None | bool | Callable[[ClientResponse], Awaitable[None]]\n        ) = None,\n        read_until_eof: bool = True,\n        proxy: StrOrURL | None = None,\n        proxy_auth: BasicAuth | None = None,\n        timeout: ClientTimeout | _SENTINEL | None = sentinel,\n        ssl: SSLContext | bool | Fingerprint = True,\n        server_hostname: str | None = None,\n        proxy_headers: LooseHeaders | None = None,\n        trace_request_ctx: object = None,\n        read_bufsize: int | None = None,\n        auto_decompress: bool | None = None,\n        max_line_size: int | None = None,\n        max_field_size: int | None = None,\n        max_headers: int | None = None,\n        middlewares: Sequence[ClientMiddlewareType] | None = None,\n    ) -> ClientResponse:\n        # NOTE: timeout clamps existing connect and read timeouts.  We cannot\n        # set the default to None because we need to detect if the user wants\n        # to use the existing timeouts by setting timeout to None.\n\n        if self.closed:\n            raise RuntimeError(\"Session is closed\")\n\n        if not isinstance(ssl, SSL_ALLOWED_TYPES):\n            raise TypeError(\n                \"ssl should be SSLContext, Fingerprint, or bool, \"\n                f\"got {ssl!r} instead.\"\n            )\n\n        if data is not None and json is not None:\n            raise ValueError(\n                \"data and json parameters can not be used at the same time\"\n            )\n        elif json is not None:\n            if self._json_serialize_bytes is not None:\n                data = payload.JsonBytesPayload(json, dumps=self._json_serialize_bytes)\n            else:\n                data = payload.JsonPayload(json, dumps=self._json_serialize)\n\n        redirects = 0\n        history: list[ClientResponse] = []\n        version = self._version\n        params = params or {}\n\n        # Merge with default headers and transform to CIMultiDict\n        headers = self._prepare_headers(headers)\n\n        try:\n            url = self._build_url(str_or_url)\n        except ValueError as e:\n            raise InvalidUrlClientError(str_or_url) from e\n\n        assert self._connector is not None\n        if url.scheme not in self._connector.allowed_protocol_schema_set:\n            raise NonHttpUrlClientError(url)\n\n        skip_headers: Iterable[istr] | None\n        if skip_auto_headers is not None:\n            skip_headers = {\n                istr(i) for i in skip_auto_headers\n            } | self._skip_auto_headers\n        elif self._skip_auto_headers:\n            skip_headers = self._skip_auto_headers\n        else:\n            skip_headers = None\n\n        if proxy is None:\n            proxy = self._default_proxy\n        if proxy_auth is None:\n            proxy_auth = self._default_proxy_auth\n\n        if proxy is None:\n            proxy_headers = None\n        else:\n            proxy_headers = self._prepare_headers(proxy_headers)\n            try:\n                proxy = URL(proxy)\n            except ValueError as e:\n                raise InvalidURL(proxy) from e\n\n        if timeout is sentinel or timeout is None:\n            real_timeout: ClientTimeout = self._timeout\n        else:\n            real_timeout = timeout\n        # timeout is cumulative for all request operations\n        # (request, redirects, responses, data consuming)\n        tm = TimeoutHandle(\n            self._loop, real_timeout.total, ceil_threshold=real_timeout.ceil_threshold\n        )\n        handle = tm.start()\n\n        if read_bufsize is None:\n            read_bufsize = self._read_bufsize\n\n        if auto_decompress is None:\n            auto_decompress = self._auto_decompress\n\n        if max_line_size is None:\n            max_line_size = self._max_line_size\n\n        if max_field_size is None:\n            max_field_size = self._max_field_size\n\n        if max_headers is None:\n            max_headers = self._max_headers\n\n        traces = [\n            Trace(\n                self,\n                trace_config,\n                trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx),\n            )\n            for trace_config in self._trace_configs\n        ]\n\n        for trace in traces:\n            await trace.send_request_start(method, url.update_query(params), headers)\n\n        timer = tm.timer()\n        try:\n            with timer:\n                # https://www.rfc-editor.org/rfc/rfc9112.html#name-retrying-requests\n                retry_persistent_connection = (\n                    self._retry_connection and method in IDEMPOTENT_METHODS\n                )\n                while True:\n                    url, auth_from_url = strip_auth_from_url(url)\n                    if not url.raw_host:\n                        # NOTE: Bail early, otherwise, causes `InvalidURL` through\n                        # NOTE: `self._request_class()` below.\n                        err_exc_cls = (\n                            InvalidUrlRedirectClientError\n                            if redirects\n                            else InvalidUrlClientError\n                        )\n                        raise err_exc_cls(url)\n                    # If `auth` was passed for an already authenticated URL,\n                    # disallow only if this is the initial URL; this is to avoid issues\n                    # with sketchy redirects that are not the caller's responsibility\n                    if not history and (auth and auth_from_url):\n                        raise ValueError(\n                            \"Cannot combine AUTH argument with \"\n                            \"credentials encoded in URL\"\n                        )\n\n                    # Override the auth with the one from the URL only if we\n                    # have no auth, or if we got an auth from a redirect URL\n                    if auth is None or (history and auth_from_url is not None):\n                        auth = auth_from_url\n\n                    if (\n                        auth is None\n                        and self._default_auth\n                        and (\n                            not self._base_url or self._base_url_origin == url.origin()\n                        )\n                    ):\n                        auth = self._default_auth\n\n                    # Try netrc if auth is still None and trust_env is enabled.\n                    if auth is None and self._trust_env and url.host is not None:\n                        auth = await self._loop.run_in_executor(\n                            None, self._get_netrc_auth, url.host\n                        )\n\n                    # It would be confusing if we support explicit\n                    # Authorization header with auth argument\n                    if auth is not None and hdrs.AUTHORIZATION in headers:\n                        raise ValueError(\n                            \"Cannot combine AUTHORIZATION header \"\n                            \"with AUTH argument or credentials \"\n                            \"encoded in URL\"\n                        )\n\n                    all_cookies = self._cookie_jar.filter_cookies(url)\n\n                    if cookies is not None:\n                        tmp_cookie_jar = CookieJar(\n                            quote_cookie=self._cookie_jar.quote_cookie\n                        )\n                        tmp_cookie_jar.update_cookies(cookies)\n                        req_cookies = tmp_cookie_jar.filter_cookies(url)\n                        if req_cookies:\n                            all_cookies.load(req_cookies)\n\n                    proxy_: URL | None = None\n                    if proxy is not None:\n                        proxy_ = URL(proxy)\n                    elif self._trust_env:\n                        with suppress(LookupError):\n                            proxy_, proxy_auth = await asyncio.to_thread(\n                                get_env_proxy_for_url, url\n                            )\n\n                    req = self._request_class(\n                        method,\n                        url,\n                        params=params,\n                        headers=headers,\n                        skip_auto_headers=skip_headers,\n                        data=data,\n                        cookies=all_cookies,\n                        auth=auth,\n                        version=version,\n                        compress=compress,\n                        chunked=chunked,\n                        expect100=expect100,\n                        loop=self._loop,\n                        response_class=self._response_class,\n                        proxy=proxy_,\n                        proxy_auth=proxy_auth,\n                        timer=timer,\n                        session=self,\n                        ssl=ssl,\n                        server_hostname=server_hostname,\n                        proxy_headers=proxy_headers,\n                        traces=traces,\n                        trust_env=self.trust_env,\n                    )\n\n                    async def _connect_and_send_request(\n                        req: ClientRequest,\n                    ) -> ClientResponse:\n                        # connection timeout\n                        assert self._connector is not None\n                        try:\n                            conn = await self._connector.connect(\n                                req, traces=traces, timeout=real_timeout\n                            )\n                        except asyncio.TimeoutError as exc:\n                            raise ConnectionTimeoutError(\n                                f\"Connection timeout to host {req.url}\"\n                            ) from exc\n\n                        assert conn.protocol is not None\n                        conn.protocol.set_response_params(\n                            timer=timer,\n                            skip_payload=req.method in EMPTY_BODY_METHODS,\n                            read_until_eof=read_until_eof,\n                            auto_decompress=auto_decompress,\n                            read_timeout=real_timeout.sock_read,\n                            read_bufsize=read_bufsize,\n                            timeout_ceil_threshold=self._connector._timeout_ceil_threshold,\n                            max_line_size=max_line_size,\n                            max_field_size=max_field_size,\n                            max_headers=max_headers,\n                        )\n                        try:\n                            resp = await req._send(conn)\n                            try:\n                                await resp.start(conn)\n                            except BaseException:\n                                resp.close()\n                                raise\n                        except BaseException:\n                            conn.close()\n                            raise\n                        return resp\n\n                    # Apply middleware (if any) - per-request middleware overrides session middleware\n                    effective_middlewares = (\n                        self._middlewares if middlewares is None else middlewares\n                    )\n\n                    if effective_middlewares:\n                        handler = build_client_middlewares(\n                            _connect_and_send_request, effective_middlewares\n                        )\n                    else:\n                        handler = _connect_and_send_request\n\n                    try:\n                        resp = await handler(req)\n                    # Client connector errors should not be retried\n                    except (\n                        ConnectionTimeoutError,\n                        ClientConnectorError,\n                        ClientConnectorCertificateError,\n                        ClientConnectorSSLError,\n                    ):\n                        raise\n                    except (ClientOSError, ServerDisconnectedError):\n                        if retry_persistent_connection:\n                            retry_persistent_connection = False\n                            continue\n                        raise\n                    except ClientError:\n                        raise\n                    except OSError as exc:\n                        if exc.errno is None and isinstance(exc, asyncio.TimeoutError):\n                            raise\n                        raise ClientOSError(*exc.args) from exc\n\n                    # Update cookies from raw headers to preserve duplicates\n                    if resp._raw_cookie_headers:\n                        self._cookie_jar.update_cookies_from_headers(\n                            resp._raw_cookie_headers, resp.url\n                        )\n\n                    # redirects\n                    if resp.status in (301, 302, 303, 307, 308) and allow_redirects:\n                        for trace in traces:\n                            await trace.send_request_redirect(\n                                method, url.update_query(params), headers, resp\n                            )\n\n                        redirects += 1\n                        history.append(resp)\n                        if max_redirects and redirects >= max_redirects:\n                            if req._body is not None:\n                                await req._body.close()\n                            resp.close()\n                            raise TooManyRedirects(\n                                history[0].request_info, tuple(history)\n                            )\n\n                        # For 301 and 302, mimic IE, now changed in RFC\n                        # https://github.com/kennethreitz/requests/pull/269\n                        if (resp.status == 303 and resp.method != hdrs.METH_HEAD) or (\n                            resp.status in (301, 302) and resp.method == hdrs.METH_POST\n                        ):\n                            method = hdrs.METH_GET\n                            data = None\n                            if headers.get(hdrs.CONTENT_LENGTH):\n                                headers.pop(hdrs.CONTENT_LENGTH)\n                        else:\n                            # For 307/308, always preserve the request body\n                            # For 301/302 with non-POST methods, preserve the request body\n                            # https://www.rfc-editor.org/rfc/rfc9110#section-15.4.3-3.1\n                            # Use the existing payload to avoid recreating it from\n                            # a potentially consumed file.\n                            #\n                            # If the payload is already consumed and cannot be replayed,\n                            # fail fast instead of silently sending an empty body.\n                            if req._body.consumed:\n                                resp.close()\n                                raise ClientPayloadError(\n                                    \"Cannot follow redirect with a consumed request \"\n                                    \"body. Use bytes, a seekable file-like object, \"\n                                    \"or set allow_redirects=False.\"\n                                )\n                            data = req._body\n\n                        r_url = resp.headers.get(hdrs.LOCATION) or resp.headers.get(\n                            hdrs.URI\n                        )\n                        if r_url is None:\n                            # see github.com/aio-libs/aiohttp/issues/2022\n                            break\n                        else:\n                            # reading from correct redirection\n                            # response is forbidden\n                            resp.release()\n\n                        try:\n                            parsed_redirect_url = URL(\n                                r_url, encoded=not self._requote_redirect_url\n                            )\n                        except ValueError as e:\n                            if req._body is not None:\n                                await req._body.close()\n                            resp.close()\n                            raise InvalidUrlRedirectClientError(\n                                r_url,\n                                \"Server attempted redirecting to a location that does not look like a URL\",\n                            ) from e\n\n                        scheme = parsed_redirect_url.scheme\n                        if scheme not in HTTP_AND_EMPTY_SCHEMA_SET:\n                            if req._body is not None:\n                                await req._body.close()\n                            resp.close()\n                            raise NonHttpUrlRedirectClientError(r_url)\n                        elif not scheme:\n                            parsed_redirect_url = url.join(parsed_redirect_url)\n\n                        is_same_host_https_redirect = (\n                            url.host == parsed_redirect_url.host\n                            and parsed_redirect_url.scheme == \"https\"\n                            and url.scheme == \"http\"\n                        )\n\n                        try:\n                            redirect_origin = parsed_redirect_url.origin()\n                        except ValueError as origin_val_err:\n                            if req._body is not None:\n                                await req._body.close()\n                            resp.close()\n                            raise InvalidUrlRedirectClientError(\n                                parsed_redirect_url,\n                                \"Invalid redirect URL origin\",\n                            ) from origin_val_err\n\n                        if (\n                            not is_same_host_https_redirect\n                            and url.origin() != redirect_origin\n                        ):\n                            auth = None\n                            headers.pop(hdrs.AUTHORIZATION, None)\n                            headers.pop(hdrs.COOKIE, None)\n                            headers.pop(hdrs.PROXY_AUTHORIZATION, None)\n\n                        url = parsed_redirect_url\n                        params = {}\n                        resp.release()\n                        continue\n\n                    break\n\n            if req._body is not None:\n                await req._body.close()\n            # check response status\n            if raise_for_status is None:\n                raise_for_status = self._raise_for_status\n\n            if raise_for_status is None:\n                pass\n            elif callable(raise_for_status):\n                await raise_for_status(resp)\n            elif raise_for_status:\n                resp.raise_for_status()\n\n            # register connection\n            if handle is not None:\n                if resp.connection is not None:\n                    resp.connection.add_callback(handle.cancel)\n                else:\n                    handle.cancel()\n\n            resp._history = tuple(history)\n\n            for trace in traces:\n                await trace.send_request_end(\n                    method, url.update_query(params), headers, resp\n                )\n            return resp\n\n        except BaseException as e:\n            # cleanup timer\n            tm.close()\n            if handle:\n                handle.cancel()\n                handle = None\n\n            for trace in traces:\n                await trace.send_request_exception(\n                    method, url.update_query(params), headers, e\n                )\n            raise\n\n    if sys.version_info >= (3, 11) and TYPE_CHECKING:\n\n        @overload\n        def ws_connect(\n            self,\n            url: StrOrURL,\n            *,\n            decode_text: Literal[True] = ...,\n            **kwargs: Unpack[_WSConnectOptions],\n        ) -> \"_BaseRequestContextManager[ClientWebSocketResponse[Literal[True]]]\": ...\n\n        @overload\n        def ws_connect(\n            self,\n            url: StrOrURL,\n            *,\n            decode_text: Literal[False],\n            **kwargs: Unpack[_WSConnectOptions],\n        ) -> \"_BaseRequestContextManager[ClientWebSocketResponse[Literal[False]]]\": ...\n\n        @overload\n        def ws_connect(\n            self,\n            url: StrOrURL,\n            *,\n            decode_text: bool = ...,\n            **kwargs: Unpack[_WSConnectOptions],\n        ) -> \"_BaseRequestContextManager[ClientWebSocketResponse[bool]]\": ...\n\n    def ws_connect(\n        self,\n        url: StrOrURL,\n        *,\n        method: str = hdrs.METH_GET,\n        protocols: Collection[str] = (),\n        timeout: ClientWSTimeout | _SENTINEL = sentinel,\n        receive_timeout: float | None = None,\n        autoclose: bool = True,\n        autoping: bool = True,\n        heartbeat: float | None = None,\n        auth: BasicAuth | None = None,\n        origin: str | None = None,\n        params: Query = None,\n        headers: LooseHeaders | None = None,\n        proxy: StrOrURL | None = None,\n        proxy_auth: BasicAuth | None = None,\n        ssl: SSLContext | bool | Fingerprint = True,\n        server_hostname: str | None = None,\n        proxy_headers: LooseHeaders | None = None,\n        compress: int = 0,\n        max_msg_size: int = 4 * 1024 * 1024,\n        decode_text: bool = True,\n    ) -> \"_BaseRequestContextManager[ClientWebSocketResponse[bool]]\":\n        \"\"\"Initiate websocket connection.\"\"\"\n        return _WSRequestContextManager(\n            self._ws_connect(\n                url,\n                method=method,\n                protocols=protocols,\n                timeout=timeout,\n                receive_timeout=receive_timeout,\n                autoclose=autoclose,\n                autoping=autoping,\n                heartbeat=heartbeat,\n                auth=auth,\n                origin=origin,\n                params=params,\n                headers=headers,\n                proxy=proxy,\n                proxy_auth=proxy_auth,\n                ssl=ssl,\n                server_hostname=server_hostname,\n                proxy_headers=proxy_headers,\n                compress=compress,\n                max_msg_size=max_msg_size,\n                decode_text=decode_text,\n            )\n        )\n\n    if sys.version_info >= (3, 11) and TYPE_CHECKING:\n\n        @overload\n        async def _ws_connect(\n            self,\n            url: StrOrURL,\n            *,\n            decode_text: Literal[True] = ...,\n            **kwargs: Unpack[_WSConnectOptions],\n        ) -> \"ClientWebSocketResponse[Literal[True]]\": ...\n\n        @overload\n        async def _ws_connect(\n            self,\n            url: StrOrURL,\n            *,\n            decode_text: Literal[False],\n            **kwargs: Unpack[_WSConnectOptions],\n        ) -> \"ClientWebSocketResponse[Literal[False]]\": ...\n\n        @overload\n        async def _ws_connect(\n            self,\n            url: StrOrURL,\n            *,\n            decode_text: bool = ...,\n            **kwargs: Unpack[_WSConnectOptions],\n        ) -> \"ClientWebSocketResponse[bool]\": ...\n\n    async def _ws_connect(\n        self,\n        url: StrOrURL,\n        *,\n        method: str = hdrs.METH_GET,\n        protocols: Collection[str] = (),\n        timeout: ClientWSTimeout | _SENTINEL = sentinel,\n        receive_timeout: float | None = None,\n        autoclose: bool = True,\n        autoping: bool = True,\n        heartbeat: float | None = None,\n        auth: BasicAuth | None = None,\n        origin: str | None = None,\n        params: Query = None,\n        headers: LooseHeaders | None = None,\n        proxy: StrOrURL | None = None,\n        proxy_auth: BasicAuth | None = None,\n        ssl: SSLContext | bool | Fingerprint = True,\n        server_hostname: str | None = None,\n        proxy_headers: LooseHeaders | None = None,\n        compress: int = 0,\n        max_msg_size: int = 4 * 1024 * 1024,\n        decode_text: bool = True,\n    ) -> \"ClientWebSocketResponse[bool]\":\n        if timeout is not sentinel:\n            if isinstance(timeout, ClientWSTimeout):\n                ws_timeout = timeout\n            else:\n                warnings.warn(  # type: ignore[unreachable]\n                    \"parameter 'timeout' of type 'float' \"\n                    \"is deprecated, please use \"\n                    \"'timeout=ClientWSTimeout(ws_close=...)'\",\n                    DeprecationWarning,\n                    stacklevel=2,\n                )\n                ws_timeout = ClientWSTimeout(ws_close=timeout)\n        else:\n            ws_timeout = DEFAULT_WS_CLIENT_TIMEOUT\n        if receive_timeout is not None:\n            warnings.warn(\n                \"float parameter 'receive_timeout' \"\n                \"is deprecated, please use parameter \"\n                \"'timeout=ClientWSTimeout(ws_receive=...)'\",\n                DeprecationWarning,\n                stacklevel=2,\n            )\n            ws_timeout = dataclasses.replace(ws_timeout, ws_receive=receive_timeout)\n\n        if headers is None:\n            real_headers: CIMultiDict[str] = CIMultiDict()\n        else:\n            real_headers = CIMultiDict(headers)\n\n        default_headers = {\n            hdrs.UPGRADE: \"websocket\",\n            hdrs.CONNECTION: \"Upgrade\",\n            hdrs.SEC_WEBSOCKET_VERSION: \"13\",\n        }\n\n        for key, value in default_headers.items():\n            real_headers.setdefault(key, value)\n\n        sec_key = base64.b64encode(os.urandom(16))\n        real_headers[hdrs.SEC_WEBSOCKET_KEY] = sec_key.decode()\n\n        if protocols:\n            real_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = \",\".join(protocols)\n        if origin is not None:\n            real_headers[hdrs.ORIGIN] = origin\n        if compress:\n            extstr = ws_ext_gen(compress=compress)\n            real_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = extstr\n\n        if not isinstance(ssl, SSL_ALLOWED_TYPES):\n            raise TypeError(\n                \"ssl should be SSLContext, Fingerprint, or bool, \"\n                f\"got {ssl!r} instead.\"\n            )\n\n        # send request\n        resp = await self.request(\n            method,\n            url,\n            params=params,\n            headers=real_headers,\n            read_until_eof=False,\n            auth=auth,\n            proxy=proxy,\n            proxy_auth=proxy_auth,\n            ssl=ssl,\n            server_hostname=server_hostname,\n            proxy_headers=proxy_headers,\n        )\n\n        try:\n            # check handshake\n            if resp.status != 101:\n                raise WSServerHandshakeError(\n                    resp.request_info,\n                    resp.history,\n                    message=\"Invalid response status\",\n                    status=resp.status,\n                    headers=resp.headers,\n                )\n\n            if resp.headers.get(hdrs.UPGRADE, \"\").lower() != \"websocket\":\n                raise WSServerHandshakeError(\n                    resp.request_info,\n                    resp.history,\n                    message=\"Invalid upgrade header\",\n                    status=resp.status,\n                    headers=resp.headers,\n                )\n\n            if resp.headers.get(hdrs.CONNECTION, \"\").lower() != \"upgrade\":\n                raise WSServerHandshakeError(\n                    resp.request_info,\n                    resp.history,\n                    message=\"Invalid connection header\",\n                    status=resp.status,\n                    headers=resp.headers,\n                )\n\n            # key calculation\n            r_key = resp.headers.get(hdrs.SEC_WEBSOCKET_ACCEPT, \"\")\n            match = base64.b64encode(hashlib.sha1(sec_key + WS_KEY).digest()).decode()\n            if r_key != match:\n                raise WSServerHandshakeError(\n                    resp.request_info,\n                    resp.history,\n                    message=\"Invalid challenge response\",\n                    status=resp.status,\n                    headers=resp.headers,\n                )\n\n            # websocket protocol\n            protocol = None\n            if protocols and hdrs.SEC_WEBSOCKET_PROTOCOL in resp.headers:\n                resp_protocols = [\n                    proto.strip()\n                    for proto in resp.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(\",\")\n                ]\n\n                for proto in resp_protocols:\n                    if proto in protocols:\n                        protocol = proto\n                        break\n\n            # websocket compress\n            notakeover = False\n            if compress:\n                compress_hdrs = resp.headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS)\n                if compress_hdrs:\n                    try:\n                        compress, notakeover = ws_ext_parse(compress_hdrs)\n                    except WSHandshakeError as exc:\n                        raise WSServerHandshakeError(\n                            resp.request_info,\n                            resp.history,\n                            message=exc.args[0],\n                            status=resp.status,\n                            headers=resp.headers,\n                        ) from exc\n                else:\n                    compress = 0\n                    notakeover = False\n\n            conn = resp.connection\n            assert conn is not None\n            conn_proto = conn.protocol\n            assert conn_proto is not None\n\n            # For WS connection the read_timeout must be either ws_timeout.ws_receive or greater\n            # None == no timeout, i.e. infinite timeout, so None is the max timeout possible\n            if ws_timeout.ws_receive is None:\n                # Reset regardless\n                conn_proto.read_timeout = None\n            elif conn_proto.read_timeout is not None:\n                conn_proto.read_timeout = max(\n                    ws_timeout.ws_receive, conn_proto.read_timeout\n                )\n\n            transport = conn.transport\n            assert transport is not None\n            reader = WebSocketDataQueue(conn_proto, 2**16, loop=self._loop)\n            writer = WebSocketWriter(\n                conn_proto,\n                transport,\n                use_mask=True,\n                compress=compress,\n                notakeover=notakeover,\n            )\n        except BaseException:\n            resp.close()\n            raise\n        else:\n            ws_resp = self._ws_response_class(\n                reader,\n                writer,\n                protocol,\n                resp,\n                ws_timeout,\n                autoclose,\n                autoping,\n                self._loop,\n                heartbeat=heartbeat,\n                compress=compress,\n                client_notakeover=notakeover,\n            )\n            parser = WebSocketReader(reader, max_msg_size, decode_text=decode_text)\n            cb = None if heartbeat is None else ws_resp._on_data_received\n            conn_proto.set_parser(parser, reader, data_received_cb=cb)\n            return ws_resp\n\n    def _prepare_headers(self, headers: LooseHeaders | None) -> \"CIMultiDict[str]\":\n        \"\"\"Add default headers and transform it to CIMultiDict\"\"\"\n        # Convert headers to MultiDict\n        result = CIMultiDict(self._default_headers)\n        if headers:\n            if not isinstance(headers, (MultiDictProxy, MultiDict)):\n                headers = CIMultiDict(headers)\n            added_names: set[str] = set()\n            for key, value in headers.items():\n                if key in added_names:\n                    result.add(key, value)\n                else:\n                    result[key] = value\n                    added_names.add(key)\n        return result\n\n    def _get_netrc_auth(self, host: str) -> BasicAuth | None:\n        \"\"\"\n        Get auth from netrc for the given host.\n\n        This method is designed to be called in an executor to avoid\n        blocking I/O in the event loop.\n        \"\"\"\n        netrc_obj = netrc_from_env()\n        try:\n            return basicauth_from_netrc(netrc_obj, host)\n        except LookupError:\n            return None\n\n    if sys.version_info >= (3, 11) and TYPE_CHECKING:\n\n        def get(\n            self,\n            url: StrOrURL,\n            **kwargs: Unpack[_RequestOptions],\n        ) -> \"_RequestContextManager\": ...\n\n        def options(\n            self,\n            url: StrOrURL,\n            **kwargs: Unpack[_RequestOptions],\n        ) -> \"_RequestContextManager\": ...\n\n        def head(\n            self,\n            url: StrOrURL,\n            **kwargs: Unpack[_RequestOptions],\n        ) -> \"_RequestContextManager\": ...\n\n        def post(\n            self,\n            url: StrOrURL,\n            **kwargs: Unpack[_RequestOptions],\n        ) -> \"_RequestContextManager\": ...\n\n        def put(\n            self,\n            url: StrOrURL,\n            **kwargs: Unpack[_RequestOptions],\n        ) -> \"_RequestContextManager\": ...\n\n        def patch(\n            self,\n            url: StrOrURL,\n            **kwargs: Unpack[_RequestOptions],\n        ) -> \"_RequestContextManager\": ...\n\n        def delete(\n            self,\n            url: StrOrURL,\n            **kwargs: Unpack[_RequestOptions],\n        ) -> \"_RequestContextManager\": ...\n\n    else:\n\n        def get(\n            self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any\n        ) -> \"_RequestContextManager\":\n            \"\"\"Perform HTTP GET request.\"\"\"\n            return _RequestContextManager(\n                self._request(\n                    hdrs.METH_GET, url, allow_redirects=allow_redirects, **kwargs\n                )\n            )\n\n        def options(\n            self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any\n        ) -> \"_RequestContextManager\":\n            \"\"\"Perform HTTP OPTIONS request.\"\"\"\n            return _RequestContextManager(\n                self._request(\n                    hdrs.METH_OPTIONS, url, allow_redirects=allow_redirects, **kwargs\n                )\n            )\n\n        def head(\n            self, url: StrOrURL, *, allow_redirects: bool = False, **kwargs: Any\n        ) -> \"_RequestContextManager\":\n            \"\"\"Perform HTTP HEAD request.\"\"\"\n            return _RequestContextManager(\n                self._request(\n                    hdrs.METH_HEAD, url, allow_redirects=allow_redirects, **kwargs\n                )\n            )\n\n        def post(\n            self, url: StrOrURL, *, data: Any = None, **kwargs: Any\n        ) -> \"_RequestContextManager\":\n            \"\"\"Perform HTTP POST request.\"\"\"\n            return _RequestContextManager(\n                self._request(hdrs.METH_POST, url, data=data, **kwargs)\n            )\n\n        def put(\n            self, url: StrOrURL, *, data: Any = None, **kwargs: Any\n        ) -> \"_RequestContextManager\":\n            \"\"\"Perform HTTP PUT request.\"\"\"\n            return _RequestContextManager(\n                self._request(hdrs.METH_PUT, url, data=data, **kwargs)\n            )\n\n        def patch(\n            self, url: StrOrURL, *, data: Any = None, **kwargs: Any\n        ) -> \"_RequestContextManager\":\n            \"\"\"Perform HTTP PATCH request.\"\"\"\n            return _RequestContextManager(\n                self._request(hdrs.METH_PATCH, url, data=data, **kwargs)\n            )\n\n        def delete(self, url: StrOrURL, **kwargs: Any) -> \"_RequestContextManager\":\n            \"\"\"Perform HTTP DELETE request.\"\"\"\n            return _RequestContextManager(\n                self._request(hdrs.METH_DELETE, url, **kwargs)\n            )\n\n    async def close(self) -> None:\n        \"\"\"Close underlying connector.\n\n        Release all acquired resources.\n        \"\"\"\n        if not self.closed:\n            if self._connector is not None and self._connector_owner:\n                await self._connector.close()\n            self._connector = None\n\n    @property\n    def closed(self) -> bool:\n        \"\"\"Is client session closed.\n\n        A readonly property.\n        \"\"\"\n        return self._connector is None or self._connector.closed\n\n    @property\n    def connector(self) -> BaseConnector | None:\n        \"\"\"Connector instance used for the session.\"\"\"\n        return self._connector\n\n    @property\n    def cookie_jar(self) -> AbstractCookieJar:\n        \"\"\"The session cookies.\"\"\"\n        return self._cookie_jar\n\n    @property\n    def version(self) -> tuple[int, int]:\n        \"\"\"The session HTTP protocol version.\"\"\"\n        return self._version\n\n    @property\n    def requote_redirect_url(self) -> bool:\n        \"\"\"Do URL requoting on redirection handling.\"\"\"\n        return self._requote_redirect_url\n\n    @property\n    def timeout(self) -> ClientTimeout:\n        \"\"\"Timeout for the session.\"\"\"\n        return self._timeout\n\n    @property\n    def headers(self) -> \"CIMultiDict[str]\":\n        \"\"\"The default headers of the client session.\"\"\"\n        return self._default_headers\n\n    @property\n    def skip_auto_headers(self) -> frozenset[istr]:\n        \"\"\"Headers for which autogeneration should be skipped\"\"\"\n        return self._skip_auto_headers\n\n    @property\n    def auth(self) -> BasicAuth | None:\n        \"\"\"An object that represents HTTP Basic Authorization\"\"\"\n        return self._default_auth\n\n    @property\n    def json_serialize(self) -> JSONEncoder:\n        \"\"\"Json serializer callable\"\"\"\n        return self._json_serialize\n\n    @property\n    def connector_owner(self) -> bool:\n        \"\"\"Should connector be closed on session closing\"\"\"\n        return self._connector_owner\n\n    @property\n    def raise_for_status(\n        self,\n    ) -> bool | Callable[[ClientResponse], Awaitable[None]]:\n        \"\"\"Should `ClientResponse.raise_for_status()` be called for each response.\"\"\"\n        return self._raise_for_status\n\n    @property\n    def auto_decompress(self) -> bool:\n        \"\"\"Should the body response be automatically decompressed.\"\"\"\n        return self._auto_decompress\n\n    @property\n    def trust_env(self) -> bool:\n        \"\"\"\n        Should proxies information from environment or netrc be trusted.\n\n        Information is from HTTP_PROXY / HTTPS_PROXY environment variables\n        or ~/.netrc file if present.\n        \"\"\"\n        return self._trust_env\n\n    @property\n    def trace_configs(self) -> list[TraceConfig[Any]]:\n        \"\"\"A list of TraceConfig instances used for client tracing\"\"\"\n        return self._trace_configs\n\n    def detach(self) -> None:\n        \"\"\"Detach connector from session without closing the former.\n\n        Session is switched to closed state anyway.\n        \"\"\"\n        self._connector = None\n\n    async def __aenter__(self) -> \"ClientSession\":\n        return self\n\n    async def __aexit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc_val: BaseException | None,\n        exc_tb: TracebackType | None,\n    ) -> None:\n        await self.close()\n\n\nclass _BaseRequestContextManager(\n    Coroutine[Any, Any, _RetType_co], Generic[_RetType_co]\n):\n    __slots__ = (\"_coro\", \"_resp\")\n\n    def __init__(self, coro: Coroutine[asyncio.Future[Any], None, _RetType_co]) -> None:\n        self._coro: Coroutine[asyncio.Future[Any], None, _RetType_co] = coro\n\n    def send(self, arg: None) -> asyncio.Future[Any]:\n        return self._coro.send(arg)\n\n    def throw(self, *args: Any, **kwargs: Any) -> asyncio.Future[Any]:\n        return self._coro.throw(*args, **kwargs)\n\n    def close(self) -> None:\n        return self._coro.close()\n\n    def __await__(self) -> Generator[Any, None, _RetType_co]:\n        ret = self._coro.__await__()\n        return ret\n\n    def __iter__(self) -> Generator[Any, None, _RetType_co]:\n        return self.__await__()\n\n    async def __aenter__(self) -> _RetType_co:\n        self._resp: _RetType_co = await self._coro\n        return await self._resp.__aenter__()  # type: ignore[return-value]\n\n    async def __aexit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc: BaseException | None,\n        tb: TracebackType | None,\n    ) -> None:\n        await self._resp.__aexit__(exc_type, exc, tb)\n\n\n_RequestContextManager = _BaseRequestContextManager[ClientResponse]\n_WSRequestContextManager = _BaseRequestContextManager[ClientWebSocketResponse[bool]]\n\n\nclass _SessionRequestContextManager:\n    __slots__ = (\"_coro\", \"_resp\", \"_session\")\n\n    def __init__(\n        self,\n        coro: Coroutine[asyncio.Future[Any], None, ClientResponse],\n        session: ClientSession,\n    ) -> None:\n        self._coro = coro\n        self._resp: ClientResponse | None = None\n        self._session = session\n\n    async def __aenter__(self) -> ClientResponse:\n        try:\n            self._resp = await self._coro\n        except BaseException:\n            await self._session.close()\n            raise\n        else:\n            return self._resp\n\n    async def __aexit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc: BaseException | None,\n        tb: TracebackType | None,\n    ) -> None:\n        assert self._resp is not None\n        self._resp.close()\n        await self._session.close()\n\n\nif sys.version_info >= (3, 11) and TYPE_CHECKING:\n\n    def request(\n        method: str,\n        url: StrOrURL,\n        *,\n        version: HttpVersion = http.HttpVersion11,\n        connector: BaseConnector | None = None,\n        **kwargs: Unpack[_RequestOptions],\n    ) -> _SessionRequestContextManager: ...\n\nelse:\n\n    def request(\n        method: str,\n        url: StrOrURL,\n        *,\n        version: HttpVersion = http.HttpVersion11,\n        connector: BaseConnector | None = None,\n        **kwargs: Any,\n    ) -> _SessionRequestContextManager:\n        \"\"\"Constructs and sends a request.\n\n        Returns response object.\n        method - HTTP method\n        url - request url\n        params - (optional) Dictionary or bytes to be sent in the query\n        string of the new request\n        data - (optional) Dictionary, bytes, or file-like object to\n        send in the body of the request\n        json - (optional) Any json compatible python object\n        headers - (optional) Dictionary of HTTP Headers to send with\n        the request\n        cookies - (optional) Dict object to send with the request\n        auth - (optional) BasicAuth named tuple represent HTTP Basic Auth\n        auth - aiohttp.helpers.BasicAuth\n        allow_redirects - (optional) If set to False, do not follow\n        redirects\n        version - Request HTTP version.\n        compress - Set to True if request has to be compressed\n        with deflate encoding.\n        chunked - Set to chunk size for chunked transfer encoding.\n        expect100 - Expect 100-continue response from server.\n        connector - BaseConnector sub-class instance to support\n        connection pooling.\n        read_until_eof - Read response until eof if response\n        does not have Content-Length header.\n        loop - Optional event loop.\n        timeout - Optional ClientTimeout settings structure, 5min\n        total timeout by default.\n        Usage::\n        >>> import aiohttp\n        >>> async with aiohttp.request('GET', 'http://python.org/') as resp:\n        ...    print(resp)\n        ...    data = await resp.read()\n        <ClientResponse(https://www.python.org/) [200 OK]>\n        \"\"\"\n        connector_owner = False\n        if connector is None:\n            connector_owner = True\n            connector = TCPConnector(force_close=True)\n\n        session = ClientSession(\n            cookies=kwargs.pop(\"cookies\", None),\n            version=version,\n            timeout=kwargs.pop(\"timeout\", sentinel),\n            connector=connector,\n            connector_owner=connector_owner,\n        )\n\n        return _SessionRequestContextManager(\n            session._request(method, url, **kwargs),\n            session,\n        )\n"
  },
  {
    "path": "aiohttp/client_exceptions.py",
    "content": "\"\"\"HTTP related errors.\"\"\"\n\nimport asyncio\nfrom typing import TYPE_CHECKING, Union\n\nfrom multidict import MultiMapping\n\nfrom .typedefs import StrOrURL\n\ntry:\n    import ssl\n\n    SSLContext = ssl.SSLContext\nexcept ImportError:  # pragma: no cover\n    ssl = SSLContext = None  # type: ignore[assignment]\n\nif TYPE_CHECKING:\n    from .client_reqrep import ClientResponse, ConnectionKey, Fingerprint, RequestInfo\n    from .http_parser import RawResponseMessage\nelse:\n    RequestInfo = ClientResponse = ConnectionKey = RawResponseMessage = None\n\n__all__ = (\n    \"ClientError\",\n    \"ClientConnectionError\",\n    \"ClientConnectionResetError\",\n    \"ClientOSError\",\n    \"ClientConnectorError\",\n    \"ClientProxyConnectionError\",\n    \"ClientSSLError\",\n    \"ClientConnectorDNSError\",\n    \"ClientConnectorSSLError\",\n    \"ClientConnectorCertificateError\",\n    \"ConnectionTimeoutError\",\n    \"SocketTimeoutError\",\n    \"ServerConnectionError\",\n    \"ServerTimeoutError\",\n    \"ServerDisconnectedError\",\n    \"ServerFingerprintMismatch\",\n    \"ClientResponseError\",\n    \"ClientHttpProxyError\",\n    \"WSServerHandshakeError\",\n    \"ContentTypeError\",\n    \"ClientPayloadError\",\n    \"InvalidURL\",\n    \"InvalidUrlClientError\",\n    \"RedirectClientError\",\n    \"NonHttpUrlClientError\",\n    \"InvalidUrlRedirectClientError\",\n    \"NonHttpUrlRedirectClientError\",\n    \"WSMessageTypeError\",\n)\n\n\nclass ClientError(Exception):\n    \"\"\"Base class for client connection errors.\"\"\"\n\n\nclass ClientResponseError(ClientError):\n    \"\"\"Base class for exceptions that occur after getting a response.\n\n    request_info: An instance of RequestInfo.\n    history: A sequence of responses, if redirects occurred.\n    status: HTTP status code.\n    message: Error message.\n    headers: Response headers.\n    \"\"\"\n\n    def __init__(\n        self,\n        request_info: RequestInfo,\n        history: tuple[ClientResponse, ...],\n        *,\n        status: int | None = None,\n        message: str = \"\",\n        headers: MultiMapping[str] | None = None,\n    ) -> None:\n        self.request_info = request_info\n        if status is not None:\n            self.status = status\n        else:\n            self.status = 0\n        self.message = message\n        self.headers = headers\n        self.history = history\n        self.args = (request_info, history)\n\n    def __str__(self) -> str:\n        return f\"{self.status}, message={self.message!r}, url={str(self.request_info.real_url)!r}\"\n\n    def __repr__(self) -> str:\n        args = f\"{self.request_info!r}, {self.history!r}\"\n        if self.status != 0:\n            args += f\", status={self.status!r}\"\n        if self.message != \"\":\n            args += f\", message={self.message!r}\"\n        if self.headers is not None:\n            args += f\", headers={self.headers!r}\"\n        return f\"{type(self).__name__}({args})\"\n\n\nclass ContentTypeError(ClientResponseError):\n    \"\"\"ContentType found is not valid.\"\"\"\n\n\nclass WSServerHandshakeError(ClientResponseError):\n    \"\"\"websocket server handshake error.\"\"\"\n\n\nclass ClientHttpProxyError(ClientResponseError):\n    \"\"\"HTTP proxy error.\n\n    Raised in :class:`aiohttp.connector.TCPConnector` if\n    proxy responds with status other than ``200 OK``\n    on ``CONNECT`` request.\n    \"\"\"\n\n\nclass TooManyRedirects(ClientResponseError):\n    \"\"\"Client was redirected too many times.\"\"\"\n\n\nclass ClientConnectionError(ClientError):\n    \"\"\"Base class for client socket errors.\"\"\"\n\n\nclass ClientConnectionResetError(ClientConnectionError, ConnectionResetError):\n    \"\"\"ConnectionResetError\"\"\"\n\n\nclass ClientOSError(ClientConnectionError, OSError):\n    \"\"\"OSError error.\"\"\"\n\n\nclass ClientConnectorError(ClientOSError):\n    \"\"\"Client connector error.\n\n    Raised in :class:`aiohttp.connector.TCPConnector` if\n        a connection can not be established.\n    \"\"\"\n\n    def __init__(self, connection_key: ConnectionKey, os_error: OSError) -> None:\n        self._conn_key = connection_key\n        self._os_error = os_error\n        super().__init__(os_error.errno, os_error.strerror)\n        self.args = (connection_key, os_error)\n\n    @property\n    def os_error(self) -> OSError:\n        return self._os_error\n\n    @property\n    def host(self) -> str:\n        return self._conn_key.host\n\n    @property\n    def port(self) -> int | None:\n        return self._conn_key.port\n\n    @property\n    def ssl(self) -> Union[SSLContext, bool, \"Fingerprint\"]:\n        return self._conn_key.ssl\n\n    def __str__(self) -> str:\n        return \"Cannot connect to host {0.host}:{0.port} ssl:{1} [{2}]\".format(\n            self, \"default\" if self.ssl is True else self.ssl, self.strerror\n        )\n\n    # OSError.__reduce__ does too much black magick\n    __reduce__ = BaseException.__reduce__\n\n\nclass ClientConnectorDNSError(ClientConnectorError):\n    \"\"\"DNS resolution failed during client connection.\n\n    Raised in :class:`aiohttp.connector.TCPConnector` if\n        DNS resolution fails.\n    \"\"\"\n\n\nclass ClientProxyConnectionError(ClientConnectorError):\n    \"\"\"Proxy connection error.\n\n    Raised in :class:`aiohttp.connector.TCPConnector` if\n        connection to proxy can not be established.\n    \"\"\"\n\n\nclass UnixClientConnectorError(ClientConnectorError):\n    \"\"\"Unix connector error.\n\n    Raised in :py:class:`aiohttp.connector.UnixConnector`\n    if connection to unix socket can not be established.\n    \"\"\"\n\n    def __init__(\n        self, path: str, connection_key: ConnectionKey, os_error: OSError\n    ) -> None:\n        self._path = path\n        super().__init__(connection_key, os_error)\n\n    @property\n    def path(self) -> str:\n        return self._path\n\n    def __str__(self) -> str:\n        return \"Cannot connect to unix socket {0.path} ssl:{1} [{2}]\".format(\n            self, \"default\" if self.ssl is True else self.ssl, self.strerror\n        )\n\n\nclass ServerConnectionError(ClientConnectionError):\n    \"\"\"Server connection errors.\"\"\"\n\n\nclass ServerDisconnectedError(ServerConnectionError):\n    \"\"\"Server disconnected.\"\"\"\n\n    def __init__(self, message: RawResponseMessage | str | None = None) -> None:\n        if message is None:\n            message = \"Server disconnected\"\n\n        self.args = (message,)\n        self.message = message\n\n\nclass ServerTimeoutError(ServerConnectionError, asyncio.TimeoutError):\n    \"\"\"Server timeout error.\"\"\"\n\n\nclass ConnectionTimeoutError(ServerTimeoutError):\n    \"\"\"Connection timeout error.\"\"\"\n\n\nclass SocketTimeoutError(ServerTimeoutError):\n    \"\"\"Socket timeout error.\"\"\"\n\n\nclass ServerFingerprintMismatch(ServerConnectionError):\n    \"\"\"SSL certificate does not match expected fingerprint.\"\"\"\n\n    def __init__(self, expected: bytes, got: bytes, host: str, port: int) -> None:\n        self.expected = expected\n        self.got = got\n        self.host = host\n        self.port = port\n        self.args = (expected, got, host, port)\n\n    def __repr__(self) -> str:\n        return f\"<{self.__class__.__name__} expected={self.expected!r} got={self.got!r} host={self.host!r} port={self.port!r}>\"\n\n\nclass ClientPayloadError(ClientError):\n    \"\"\"Response payload error.\"\"\"\n\n\nclass InvalidURL(ClientError, ValueError):\n    \"\"\"Invalid URL.\n\n    URL used for fetching is malformed, e.g. it doesn't contains host\n    part.\n    \"\"\"\n\n    # Derive from ValueError for backward compatibility\n\n    def __init__(self, url: StrOrURL, description: str | None = None) -> None:\n        # The type of url is not yarl.URL because the exception can be raised\n        # on URL(url) call\n        self._url = url\n        self._description = description\n\n        if description:\n            super().__init__(url, description)\n        else:\n            super().__init__(url)\n\n    @property\n    def url(self) -> StrOrURL:\n        return self._url\n\n    @property\n    def description(self) -> \"str | None\":\n        return self._description\n\n    def __repr__(self) -> str:\n        return f\"<{self.__class__.__name__} {self}>\"\n\n    def __str__(self) -> str:\n        if self._description:\n            return f\"{self._url} - {self._description}\"\n        return str(self._url)\n\n\nclass InvalidUrlClientError(InvalidURL):\n    \"\"\"Invalid URL client error.\"\"\"\n\n\nclass RedirectClientError(ClientError):\n    \"\"\"Client redirect error.\"\"\"\n\n\nclass NonHttpUrlClientError(ClientError):\n    \"\"\"Non http URL client error.\"\"\"\n\n\nclass InvalidUrlRedirectClientError(InvalidUrlClientError, RedirectClientError):\n    \"\"\"Invalid URL redirect client error.\"\"\"\n\n\nclass NonHttpUrlRedirectClientError(NonHttpUrlClientError, RedirectClientError):\n    \"\"\"Non http URL redirect client error.\"\"\"\n\n\nclass ClientSSLError(ClientConnectorError):\n    \"\"\"Base error for ssl.*Errors.\"\"\"\n\n\nif ssl is not None:\n    cert_errors = (ssl.CertificateError,)\n    cert_errors_bases = (\n        ClientSSLError,\n        ssl.CertificateError,\n    )\n\n    ssl_errors = (ssl.SSLError,)\n    ssl_error_bases = (ClientSSLError, ssl.SSLError)\nelse:  # pragma: no cover\n    cert_errors = tuple()  # type: ignore[unreachable]\n    cert_errors_bases = (\n        ClientSSLError,\n        ValueError,\n    )\n\n    ssl_errors = tuple()\n    ssl_error_bases = (ClientSSLError,)\n\n\nclass ClientConnectorSSLError(*ssl_error_bases):  # type: ignore[misc]\n    \"\"\"Response ssl error.\"\"\"\n\n\nclass ClientConnectorCertificateError(*cert_errors_bases):  # type: ignore[misc]\n    \"\"\"Response certificate error.\"\"\"\n\n    _conn_key: ConnectionKey\n\n    def __init__(\n        # TODO: If we require ssl in future, this can become ssl.CertificateError\n        self,\n        connection_key: ConnectionKey,\n        certificate_error: Exception,\n    ) -> None:\n        if isinstance(certificate_error, cert_errors + (OSError,)):\n            # ssl.CertificateError has errno and strerror, so we should be fine\n            os_error = certificate_error\n        else:\n            os_error = OSError()\n\n        super().__init__(connection_key, os_error)\n        self._certificate_error = certificate_error\n        self.args = (connection_key, certificate_error)\n\n    @property\n    def certificate_error(self) -> Exception:\n        return self._certificate_error\n\n    @property\n    def host(self) -> str:\n        return self._conn_key.host\n\n    @property\n    def port(self) -> int | None:\n        return self._conn_key.port\n\n    @property\n    def ssl(self) -> bool:\n        return self._conn_key.is_ssl\n\n    def __str__(self) -> str:\n        return (\n            f\"Cannot connect to host {self.host}:{self.port} ssl:{self.ssl} \"\n            f\"[{self.certificate_error.__class__.__name__}: \"\n            f\"{self.certificate_error.args}]\"\n        )\n\n\nclass WSMessageTypeError(TypeError):\n    \"\"\"WebSocket message type is not valid.\"\"\"\n"
  },
  {
    "path": "aiohttp/client_middleware_digest_auth.py",
    "content": "\"\"\"\nDigest authentication middleware for aiohttp client.\n\nThis middleware implements HTTP Digest Authentication according to RFC 7616,\nproviding a more secure alternative to Basic Authentication. It supports all\nstandard hash algorithms including MD5, SHA, SHA-256, SHA-512 and their session\nvariants, as well as both 'auth' and 'auth-int' quality of protection (qop) options.\n\"\"\"\n\nimport hashlib\nimport os\nimport re\nimport sys\nimport time\nfrom collections.abc import Callable\nfrom typing import Final, Literal, TypedDict\n\nfrom yarl import URL\n\nfrom . import hdrs\nfrom .client_exceptions import ClientError\nfrom .client_middlewares import ClientHandlerType\nfrom .client_reqrep import ClientRequest, ClientResponse\nfrom .payload import Payload\n\n\nclass DigestAuthChallenge(TypedDict, total=False):\n    realm: str\n    nonce: str\n    qop: str\n    algorithm: str\n    opaque: str\n    domain: str\n    stale: str\n\n\nDigestFunctions: dict[str, Callable[[bytes], \"hashlib._Hash\"]] = {\n    \"MD5\": hashlib.md5,\n    \"MD5-SESS\": hashlib.md5,\n    \"SHA\": hashlib.sha1,\n    \"SHA-SESS\": hashlib.sha1,\n    \"SHA256\": hashlib.sha256,\n    \"SHA256-SESS\": hashlib.sha256,\n    \"SHA-256\": hashlib.sha256,\n    \"SHA-256-SESS\": hashlib.sha256,\n    \"SHA512\": hashlib.sha512,\n    \"SHA512-SESS\": hashlib.sha512,\n    \"SHA-512\": hashlib.sha512,\n    \"SHA-512-SESS\": hashlib.sha512,\n}\n\n\n# Compile the regex pattern once at module level for performance\n_HEADER_PAIRS_PATTERN = re.compile(\n    r'(?:^|\\s|,\\s*)(\\w+)\\s*=\\s*(?:\"((?:[^\"\\\\]|\\\\.)*)\"|([^\\s,]+))'\n    if sys.version_info < (3, 11)\n    else r'(?:^|\\s|,\\s*)((?>\\w+))\\s*=\\s*(?:\"((?:[^\"\\\\]|\\\\.)*)\"|([^\\s,]+))'\n    # +------------|--------|--|-|-|--|----|------|----|--||-----|-> Match valid start/sep\n    #              +--------|--|-|-|--|----|------|----|--||-----|-> alphanumeric key (atomic\n    #                       |  | | |  |    |      |    |  ||     |   group reduces backtracking)\n    #                       +--|-|-|--|----|------|----|--||-----|-> maybe whitespace\n    #                          | | |  |    |      |    |  ||     |\n    #                          +-|-|--|----|------|----|--||-----|-> = (delimiter)\n    #                            +-|--|----|------|----|--||-----|-> maybe whitespace\n    #                              |  |    |      |    |  ||     |\n    #                              +--|----|------|----|--||-----|-> group quoted or unquoted\n    #                                 |    |      |    |  ||     |\n    #                                 +----|------|----|--||-----|-> if quoted...\n    #                                      +------|----|--||-----|-> anything but \" or \\\n    #                                             +----|--||-----|-> escaped characters allowed\n    #                                                  +--||-----|-> or can be empty string\n    #                                                     ||     |\n    #                                                     +|-----|-> if unquoted...\n    #                                                      +-----|-> anything but , or <space>\n    #                                                            +-> at least one char req'd\n)\n\n\n# RFC 7616: Challenge parameters to extract\nCHALLENGE_FIELDS: Final[\n    tuple[\n        Literal[\"realm\", \"nonce\", \"qop\", \"algorithm\", \"opaque\", \"domain\", \"stale\"], ...\n    ]\n] = (\n    \"realm\",\n    \"nonce\",\n    \"qop\",\n    \"algorithm\",\n    \"opaque\",\n    \"domain\",\n    \"stale\",\n)\n\n# Supported digest authentication algorithms\n# Use a tuple of sorted keys for predictable documentation and error messages\nSUPPORTED_ALGORITHMS: Final[tuple[str, ...]] = tuple(sorted(DigestFunctions.keys()))\n\n# RFC 7616: Fields that require quoting in the Digest auth header\n# These fields must be enclosed in double quotes in the Authorization header.\n# Algorithm, qop, and nc are never quoted per RFC specifications.\n# This frozen set is used by the template-based header construction to\n# automatically determine which fields need quotes.\nQUOTED_AUTH_FIELDS: Final[frozenset[str]] = frozenset(\n    {\"username\", \"realm\", \"nonce\", \"uri\", \"response\", \"opaque\", \"cnonce\"}\n)\n\n\ndef escape_quotes(value: str) -> str:\n    \"\"\"Escape double quotes for HTTP header values.\"\"\"\n    return value.replace('\"', '\\\\\"')\n\n\ndef unescape_quotes(value: str) -> str:\n    \"\"\"Unescape double quotes in HTTP header values.\"\"\"\n    return value.replace('\\\\\"', '\"')\n\n\ndef parse_header_pairs(header: str) -> dict[str, str]:\n    \"\"\"\n    Parse key-value pairs from WWW-Authenticate or similar HTTP headers.\n\n    This function handles the complex format of WWW-Authenticate header values,\n    supporting both quoted and unquoted values, proper handling of commas in\n    quoted values, and whitespace variations per RFC 7616.\n\n    Examples of supported formats:\n      - key1=\"value1\", key2=value2\n      - key1 = \"value1\" , key2=\"value, with, commas\"\n      - key1=value1,key2=\"value2\"\n      - realm=\"example.com\", nonce=\"12345\", qop=\"auth\"\n\n    Args:\n        header: The header value string to parse\n\n    Returns:\n        Dictionary mapping parameter names to their values\n    \"\"\"\n    return {\n        stripped_key: unescape_quotes(quoted_val) if quoted_val else unquoted_val\n        for key, quoted_val, unquoted_val in _HEADER_PAIRS_PATTERN.findall(header)\n        if (stripped_key := key.strip())\n    }\n\n\nclass DigestAuthMiddleware:\n    \"\"\"\n    HTTP digest authentication middleware for aiohttp client.\n\n    This middleware intercepts 401 Unauthorized responses containing a Digest\n    authentication challenge, calculates the appropriate digest credentials,\n    and automatically retries the request with the proper Authorization header.\n\n    Features:\n    - Handles all aspects of Digest authentication handshake automatically\n    - Supports all standard hash algorithms:\n      - MD5, MD5-SESS\n      - SHA, SHA-SESS\n      - SHA256, SHA256-SESS, SHA-256, SHA-256-SESS\n      - SHA512, SHA512-SESS, SHA-512, SHA-512-SESS\n    - Supports 'auth' and 'auth-int' quality of protection modes\n    - Properly handles quoted strings and parameter parsing\n    - Includes replay attack protection with client nonce count tracking\n    - Supports preemptive authentication per RFC 7616 Section 3.6\n\n    Standards compliance:\n    - RFC 7616: HTTP Digest Access Authentication (primary reference)\n    - RFC 2617: HTTP Authentication (deprecated by RFC 7616)\n    - RFC 1945: Section 11.1 (username restrictions)\n\n    Implementation notes:\n    The core digest calculation is inspired by the implementation in\n    https://github.com/requests/requests/blob/v2.18.4/requests/auth.py\n    with added support for modern digest auth features and error handling.\n    \"\"\"\n\n    def __init__(\n        self,\n        login: str,\n        password: str,\n        preemptive: bool = True,\n    ) -> None:\n        if login is None:\n            raise ValueError(\"None is not allowed as login value\")\n\n        if password is None:\n            raise ValueError(\"None is not allowed as password value\")\n\n        if \":\" in login:\n            raise ValueError('A \":\" is not allowed in username (RFC 1945#section-11.1)')\n\n        self._login_str: Final[str] = login\n        self._login_bytes: Final[bytes] = login.encode(\"utf-8\")\n        self._password_bytes: Final[bytes] = password.encode(\"utf-8\")\n\n        self._last_nonce_bytes = b\"\"\n        self._nonce_count = 0\n        self._challenge: DigestAuthChallenge = {}\n        self._preemptive: bool = preemptive\n        # Set of URLs defining the protection space\n        self._protection_space: list[str] = []\n\n    async def _encode(self, method: str, url: URL, body: Payload | Literal[b\"\"]) -> str:\n        \"\"\"\n        Build digest authorization header for the current challenge.\n\n        Args:\n            method: The HTTP method (GET, POST, etc.)\n            url: The request URL\n            body: The request body (used for qop=auth-int)\n\n        Returns:\n            A fully formatted Digest authorization header string\n\n        Raises:\n            ClientError: If the challenge is missing required parameters or\n                         contains unsupported values\n\n        \"\"\"\n        challenge = self._challenge\n        if \"realm\" not in challenge:\n            raise ClientError(\n                \"Malformed Digest auth challenge: Missing 'realm' parameter\"\n            )\n\n        if \"nonce\" not in challenge:\n            raise ClientError(\n                \"Malformed Digest auth challenge: Missing 'nonce' parameter\"\n            )\n\n        # Empty realm values are allowed per RFC 7616 (SHOULD, not MUST, contain host name)\n        realm = challenge[\"realm\"]\n        nonce = challenge[\"nonce\"]\n\n        # Empty nonce values are not allowed as they are security-critical for replay protection\n        if not nonce:\n            raise ClientError(\n                \"Security issue: Digest auth challenge contains empty 'nonce' value\"\n            )\n\n        qop_raw = challenge.get(\"qop\", \"\")\n        # Preserve original algorithm case for response while using uppercase for processing\n        algorithm_original = challenge.get(\"algorithm\", \"MD5\")\n        algorithm = algorithm_original.upper()\n        opaque = challenge.get(\"opaque\", \"\")\n\n        # Convert string values to bytes once\n        nonce_bytes = nonce.encode(\"utf-8\")\n        realm_bytes = realm.encode(\"utf-8\")\n        path = URL(url).path_qs\n\n        # Process QoP\n        qop = \"\"\n        qop_bytes = b\"\"\n        if qop_raw:\n            valid_qops = {\"auth\", \"auth-int\"}.intersection(\n                {q.strip() for q in qop_raw.split(\",\") if q.strip()}\n            )\n            if not valid_qops:\n                raise ClientError(\n                    f\"Digest auth error: Unsupported Quality of Protection (qop) value(s): {qop_raw}\"\n                )\n\n            qop = \"auth-int\" if \"auth-int\" in valid_qops else \"auth\"\n            qop_bytes = qop.encode(\"utf-8\")\n\n        if algorithm not in DigestFunctions:\n            raise ClientError(\n                f\"Digest auth error: Unsupported hash algorithm: {algorithm}. \"\n                f\"Supported algorithms: {', '.join(SUPPORTED_ALGORITHMS)}\"\n            )\n        hash_fn: Final = DigestFunctions[algorithm]\n\n        def H(x: bytes) -> bytes:\n            \"\"\"RFC 7616 Section 3: Hash function H(data) = hex(hash(data)).\"\"\"\n            return hash_fn(x).hexdigest().encode()\n\n        def KD(s: bytes, d: bytes) -> bytes:\n            \"\"\"RFC 7616 Section 3: KD(secret, data) = H(concat(secret, \":\", data)).\"\"\"\n            return H(b\":\".join((s, d)))\n\n        # Calculate A1 and A2\n        A1 = b\":\".join((self._login_bytes, realm_bytes, self._password_bytes))\n        A2 = f\"{method.upper()}:{path}\".encode()\n        if qop == \"auth-int\":\n            if isinstance(body, Payload):  # will always be empty bytes unless Payload\n                entity_bytes = await body.as_bytes()  # Get bytes from Payload\n            else:\n                entity_bytes = body\n            entity_hash = H(entity_bytes)\n            A2 = b\":\".join((A2, entity_hash))\n\n        HA1 = H(A1)\n        HA2 = H(A2)\n\n        # Nonce count handling\n        if nonce_bytes == self._last_nonce_bytes:\n            self._nonce_count += 1\n        else:\n            self._nonce_count = 1\n\n        self._last_nonce_bytes = nonce_bytes\n        ncvalue = f\"{self._nonce_count:08x}\"\n        ncvalue_bytes = ncvalue.encode(\"utf-8\")\n\n        # Generate client nonce\n        cnonce = hashlib.sha1(\n            b\"\".join(\n                [\n                    str(self._nonce_count).encode(\"utf-8\"),\n                    nonce_bytes,\n                    time.ctime().encode(\"utf-8\"),\n                    os.urandom(8),\n                ]\n            )\n        ).hexdigest()[:16]\n        cnonce_bytes = cnonce.encode(\"utf-8\")\n\n        # Special handling for session-based algorithms\n        if algorithm.upper().endswith(\"-SESS\"):\n            HA1 = H(b\":\".join((HA1, nonce_bytes, cnonce_bytes)))\n\n        # Calculate the response digest\n        if qop:\n            noncebit = b\":\".join(\n                (nonce_bytes, ncvalue_bytes, cnonce_bytes, qop_bytes, HA2)\n            )\n            response_digest = KD(HA1, noncebit)\n        else:\n            response_digest = KD(HA1, b\":\".join((nonce_bytes, HA2)))\n\n        # Define a dict mapping of header fields to their values\n        # Group fields into always-present, optional, and qop-dependent\n        header_fields = {\n            # Always present fields\n            \"username\": escape_quotes(self._login_str),\n            \"realm\": escape_quotes(realm),\n            \"nonce\": escape_quotes(nonce),\n            \"uri\": path,\n            \"response\": response_digest.decode(),\n            \"algorithm\": algorithm_original,\n        }\n\n        # Optional fields\n        if opaque:\n            header_fields[\"opaque\"] = escape_quotes(opaque)\n\n        # QoP-dependent fields\n        if qop:\n            header_fields[\"qop\"] = qop\n            header_fields[\"nc\"] = ncvalue\n            header_fields[\"cnonce\"] = cnonce\n\n        # Build header using templates for each field type\n        pairs: list[str] = []\n        for field, value in header_fields.items():\n            if field in QUOTED_AUTH_FIELDS:\n                pairs.append(f'{field}=\"{value}\"')\n            else:\n                pairs.append(f\"{field}={value}\")\n\n        return f\"Digest {', '.join(pairs)}\"\n\n    def _in_protection_space(self, url: URL) -> bool:\n        \"\"\"\n        Check if the given URL is within the current protection space.\n\n        According to RFC 7616, a URI is in the protection space if any URI\n        in the protection space is a prefix of it (after both have been made absolute).\n        \"\"\"\n        request_str = str(url)\n        for space_str in self._protection_space:\n            # Check if request starts with space URL\n            if not request_str.startswith(space_str):\n                continue\n            # Exact match or space ends with / (proper directory prefix)\n            if len(request_str) == len(space_str) or space_str[-1] == \"/\":\n                return True\n            # Check next char is / to ensure proper path boundary\n            if request_str[len(space_str)] == \"/\":\n                return True\n        return False\n\n    def _authenticate(self, response: ClientResponse) -> bool:\n        \"\"\"\n        Takes the given response and tries digest-auth, if needed.\n\n        Returns true if the original request must be resent.\n        \"\"\"\n        if response.status != 401:\n            return False\n\n        auth_header = response.headers.get(\"www-authenticate\", \"\")\n        if not auth_header:\n            return False  # No authentication header present\n\n        method, sep, headers = auth_header.partition(\" \")\n        if not sep:\n            # No space found in www-authenticate header\n            return False  # Malformed auth header, missing scheme separator\n\n        if method.lower() != \"digest\":\n            # Not a digest auth challenge (could be Basic, Bearer, etc.)\n            return False\n\n        if not headers:\n            # We have a digest scheme but no parameters\n            return False  # Malformed digest header, missing parameters\n\n        # We have a digest auth header with content\n        if not (header_pairs := parse_header_pairs(headers)):\n            # Failed to parse any key-value pairs\n            return False  # Malformed digest header, no valid parameters\n\n        # Extract challenge parameters\n        self._challenge = {}\n        for field in CHALLENGE_FIELDS:\n            if (value := header_pairs.get(field)) is not None:\n                self._challenge[field] = value\n\n        # Update protection space based on domain parameter or default to origin\n        origin = response.url.origin()\n\n        if domain := self._challenge.get(\"domain\"):\n            # Parse space-separated list of URIs\n            self._protection_space = []\n            for uri in domain.split():\n                # Remove quotes if present\n                uri = uri.strip('\"')\n                if uri.startswith(\"/\"):\n                    # Path-absolute, relative to origin\n                    self._protection_space.append(str(origin.join(URL(uri))))\n                else:\n                    # Absolute URI\n                    self._protection_space.append(str(URL(uri)))\n        else:\n            # No domain specified, protection space is entire origin\n            self._protection_space = [str(origin)]\n\n        # Return True only if we found at least one challenge parameter\n        return bool(self._challenge)\n\n    async def __call__(\n        self, request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        \"\"\"Run the digest auth middleware.\"\"\"\n        response = None\n        for retry_count in range(2):\n            # Apply authorization header if:\n            # 1. This is a retry after 401 (retry_count > 0), OR\n            # 2. Preemptive auth is enabled AND we have a challenge AND the URL is in protection space\n            if retry_count > 0 or (\n                self._preemptive\n                and self._challenge\n                and self._in_protection_space(request.url)\n            ):\n                request.headers[hdrs.AUTHORIZATION] = await self._encode(\n                    request.method, request.url, request.body\n                )\n\n            # Send the request\n            response = await handler(request)\n\n            # Check if we need to authenticate\n            if not self._authenticate(response):\n                break\n\n        # At this point, response is guaranteed to be defined\n        assert response is not None\n        return response\n"
  },
  {
    "path": "aiohttp/client_middlewares.py",
    "content": "\"\"\"Client middleware support.\"\"\"\n\nfrom collections.abc import Awaitable, Callable, Sequence\n\nfrom .client_reqrep import ClientRequest, ClientResponse\n\n__all__ = (\"ClientMiddlewareType\", \"ClientHandlerType\", \"build_client_middlewares\")\n\n# Type alias for client request handlers - functions that process requests and return responses\nClientHandlerType = Callable[[ClientRequest], Awaitable[ClientResponse]]\n\n# Type for client middleware - similar to server but uses ClientRequest/ClientResponse\nClientMiddlewareType = Callable[\n    [ClientRequest, ClientHandlerType], Awaitable[ClientResponse]\n]\n\n\ndef build_client_middlewares(\n    handler: ClientHandlerType,\n    middlewares: Sequence[ClientMiddlewareType],\n) -> ClientHandlerType:\n    \"\"\"\n    Apply middlewares to request handler.\n\n    The middlewares are applied in reverse order, so the first middleware\n    in the list wraps all subsequent middlewares and the handler.\n\n    This implementation avoids using partial/update_wrapper to minimize overhead\n    and doesn't cache to avoid holding references to stateful middleware.\n    \"\"\"\n    # Optimize for single middleware case\n    if len(middlewares) == 1:\n        middleware = middlewares[0]\n\n        async def single_middleware_handler(req: ClientRequest) -> ClientResponse:\n            return await middleware(req, handler)\n\n        return single_middleware_handler\n\n    # Build the chain for multiple middlewares\n    current_handler = handler\n\n    for middleware in reversed(middlewares):\n        # Create a new closure that captures the current state\n        def make_wrapper(\n            mw: ClientMiddlewareType, next_h: ClientHandlerType\n        ) -> ClientHandlerType:\n            async def wrapped(req: ClientRequest) -> ClientResponse:\n                return await mw(req, next_h)\n\n            return wrapped\n\n        current_handler = make_wrapper(middleware, current_handler)\n\n    return current_handler\n"
  },
  {
    "path": "aiohttp/client_proto.py",
    "content": "import asyncio\nfrom contextlib import suppress\nfrom typing import Callable, Protocol\n\nfrom ._websocket.reader import WebSocketDataQueue\nfrom .base_protocol import BaseProtocol\nfrom .client_exceptions import (\n    ClientConnectionError,\n    ClientOSError,\n    ClientPayloadError,\n    ServerDisconnectedError,\n    SocketTimeoutError,\n)\nfrom .helpers import (\n    _EXC_SENTINEL,\n    EMPTY_BODY_STATUS_CODES,\n    BaseTimerContext,\n    ErrorableProtocol,\n    set_exception,\n    set_result,\n)\nfrom .http import HttpResponseParser, RawResponseMessage, WebSocketReader\nfrom .http_exceptions import HttpProcessingError\nfrom .streams import EMPTY_PAYLOAD, DataQueue, StreamReader\n\n\nclass _Payload(ErrorableProtocol, Protocol):\n    def is_eof(self) -> bool: ...\n\n\nclass ResponseHandler(BaseProtocol, DataQueue[tuple[RawResponseMessage, StreamReader]]):\n    \"\"\"Helper class to adapt between Protocol and StreamReader.\"\"\"\n\n    def __init__(self, loop: asyncio.AbstractEventLoop) -> None:\n        BaseProtocol.__init__(self, loop=loop)\n        DataQueue.__init__(self, loop)\n\n        self._should_close = False\n\n        self._payload: _Payload | None = None\n        self._skip_payload = False\n        self._payload_parser: WebSocketReader | None = None\n        self._data_received_cb: Callable[[], None] | None = None\n\n        self._timer = None\n\n        self._tail = b\"\"\n        self._upgraded = False\n        self._parser: HttpResponseParser | None = None\n\n        self._read_timeout: float | None = None\n        self._read_timeout_handle: asyncio.TimerHandle | None = None\n\n        self._timeout_ceil_threshold: float | None = 5\n\n        self._closed: None | asyncio.Future[None] = None\n        self._connection_lost_called = False\n\n    @property\n    def closed(self) -> None | asyncio.Future[None]:\n        \"\"\"Future that is set when the connection is closed.\n\n        This property returns a Future that will be completed when the connection\n        is closed. The Future is created lazily on first access to avoid creating\n        futures that will never be awaited.\n\n        Returns:\n            - A Future[None] if the connection is still open or was closed after\n              this property was accessed\n            - None if connection_lost() was already called before this property\n              was ever accessed (indicating no one is waiting for the closure)\n        \"\"\"\n        if self._closed is None and not self._connection_lost_called:\n            self._closed = self._loop.create_future()\n        return self._closed\n\n    @property\n    def upgraded(self) -> bool:\n        return self._upgraded\n\n    @property\n    def should_close(self) -> bool:\n        return bool(\n            self._should_close\n            or (self._payload is not None and not self._payload.is_eof())\n            or self._upgraded\n            or self._exception is not None\n            or self._payload_parser is not None\n            or self._buffer\n            or self._tail\n        )\n\n    def force_close(self) -> None:\n        self._should_close = True\n\n    def close(self) -> None:\n        self._exception = None  # Break cyclic references\n        transport = self.transport\n        if transport is not None:\n            transport.close()\n            self.transport = None\n            self._payload = None\n            self._drop_timeout()\n\n    def abort(self) -> None:\n        self._exception = None  # Break cyclic references\n        transport = self.transport\n        if transport is not None:\n            transport.abort()\n            self.transport = None\n            self._payload = None\n            self._drop_timeout()\n\n    def is_connected(self) -> bool:\n        return self.transport is not None and not self.transport.is_closing()\n\n    def connection_lost(self, exc: BaseException | None) -> None:\n        self._connection_lost_called = True\n        self._drop_timeout()\n\n        original_connection_error = exc\n        reraised_exc = original_connection_error\n\n        connection_closed_cleanly = original_connection_error is None\n\n        if self._closed is not None:\n            # If someone is waiting for the closed future,\n            # we should set it to None or an exception. If\n            # self._closed is None, it means that\n            # connection_lost() was called already\n            # or nobody is waiting for it.\n            if connection_closed_cleanly:\n                set_result(self._closed, None)\n            else:\n                assert original_connection_error is not None\n                set_exception(\n                    self._closed,\n                    ClientConnectionError(\n                        f\"Connection lost: {original_connection_error !s}\",\n                    ),\n                    original_connection_error,\n                )\n\n        if self._payload_parser is not None:\n            with suppress(Exception):  # FIXME: log this somehow?\n                self._payload_parser.feed_eof()\n\n        uncompleted = None\n        if self._parser is not None:\n            try:\n                uncompleted = self._parser.feed_eof()\n            except Exception as underlying_exc:\n                if self._payload is not None:\n                    client_payload_exc_msg = (\n                        f\"Response payload is not completed: {underlying_exc !r}\"\n                    )\n                    if not connection_closed_cleanly:\n                        client_payload_exc_msg = (\n                            f\"{client_payload_exc_msg !s}. \"\n                            f\"{original_connection_error !r}\"\n                        )\n                    set_exception(\n                        self._payload,\n                        ClientPayloadError(client_payload_exc_msg),\n                        underlying_exc,\n                    )\n\n        if not self.is_eof():\n            if isinstance(original_connection_error, OSError):\n                reraised_exc = ClientOSError(*original_connection_error.args)\n            if connection_closed_cleanly:\n                reraised_exc = ServerDisconnectedError(uncompleted)\n            # assigns self._should_close to True as side effect,\n            # we do it anyway below\n            underlying_non_eof_exc = (\n                _EXC_SENTINEL\n                if connection_closed_cleanly\n                else original_connection_error\n            )\n            assert underlying_non_eof_exc is not None\n            assert reraised_exc is not None\n            self.set_exception(reraised_exc, underlying_non_eof_exc)\n\n        self._should_close = True\n        self._parser = None\n        self._payload = None\n        self._payload_parser = None\n        self._reading_paused = False\n\n        super().connection_lost(reraised_exc)\n\n    def eof_received(self) -> None:\n        # should call parser.feed_eof() most likely\n        self._drop_timeout()\n\n    def pause_reading(self) -> None:\n        super().pause_reading()\n        self._drop_timeout()\n\n    def resume_reading(self) -> None:\n        super().resume_reading()\n        self._reschedule_timeout()\n\n    def set_exception(\n        self,\n        exc: type[BaseException] | BaseException,\n        exc_cause: BaseException = _EXC_SENTINEL,\n    ) -> None:\n        self._should_close = True\n        self._drop_timeout()\n        super().set_exception(exc, exc_cause)\n\n    def set_parser(\n        self,\n        parser: WebSocketReader,\n        payload: WebSocketDataQueue,\n        data_received_cb: Callable[[], None] | None = None,\n    ) -> None:\n        self._payload = payload\n        self._payload_parser = parser\n        self._data_received_cb = data_received_cb\n\n        self._drop_timeout()\n\n        if self._tail:\n            data, self._tail = self._tail, b\"\"\n            self.data_received(data)\n\n    def set_response_params(\n        self,\n        *,\n        timer: BaseTimerContext | None = None,\n        skip_payload: bool = False,\n        read_until_eof: bool = False,\n        auto_decompress: bool = True,\n        read_timeout: float | None = None,\n        read_bufsize: int = 2**16,\n        timeout_ceil_threshold: float = 5,\n        max_line_size: int = 8190,\n        max_field_size: int = 8190,\n        max_headers: int = 128,\n    ) -> None:\n        self._skip_payload = skip_payload\n\n        self._read_timeout = read_timeout\n\n        self._timeout_ceil_threshold = timeout_ceil_threshold\n\n        self._parser = HttpResponseParser(\n            self,\n            self._loop,\n            read_bufsize,\n            timer=timer,\n            payload_exception=ClientPayloadError,\n            response_with_body=not skip_payload,\n            read_until_eof=read_until_eof,\n            auto_decompress=auto_decompress,\n            max_line_size=max_line_size,\n            max_field_size=max_field_size,\n            max_headers=max_headers,\n        )\n\n        if self._tail:\n            data, self._tail = self._tail, b\"\"\n            self.data_received(data)\n\n    def _drop_timeout(self) -> None:\n        if self._read_timeout_handle is not None:\n            self._read_timeout_handle.cancel()\n            self._read_timeout_handle = None\n\n    def _reschedule_timeout(self) -> None:\n        timeout = self._read_timeout\n        if self._read_timeout_handle is not None:\n            self._read_timeout_handle.cancel()\n\n        if timeout:\n            self._read_timeout_handle = self._loop.call_later(\n                timeout, self._on_read_timeout\n            )\n        else:\n            self._read_timeout_handle = None\n\n    def start_timeout(self) -> None:\n        self._reschedule_timeout()\n\n    @property\n    def read_timeout(self) -> float | None:\n        return self._read_timeout\n\n    @read_timeout.setter\n    def read_timeout(self, read_timeout: float | None) -> None:\n        self._read_timeout = read_timeout\n\n    def _on_read_timeout(self) -> None:\n        exc = SocketTimeoutError(\"Timeout on reading data from socket\")\n        self.set_exception(exc)\n        if self._payload is not None:\n            set_exception(self._payload, exc)\n\n    def data_received(self, data: bytes) -> None:\n        self._reschedule_timeout()\n\n        if not data:\n            return\n\n        # custom payload parser - currently always WebSocketReader\n        if self._payload_parser is not None:\n            if self._data_received_cb is not None:\n                self._data_received_cb()\n            eof, tail = self._payload_parser.feed_data(data)\n            if eof:\n                self._payload = None\n                self._payload_parser = None\n\n                if tail:\n                    self.data_received(tail)\n            return\n\n        if self._upgraded or self._parser is None:\n            # i.e. websocket connection, websocket parser is not set yet\n            self._tail += data\n            return\n\n        # parse http messages\n        try:\n            messages, upgraded, tail = self._parser.feed_data(data)\n        except Exception as underlying_exc:\n            if self.transport is not None:\n                # connection.release() could be called BEFORE\n                # data_received(), the transport is already\n                # closed in this case\n                self.transport.close()\n            # should_close is True after the call\n            if isinstance(underlying_exc, HttpProcessingError):\n                exc = HttpProcessingError(\n                    code=underlying_exc.code,\n                    message=underlying_exc.message,\n                    headers=underlying_exc.headers,\n                )\n            else:\n                exc = HttpProcessingError()\n            self.set_exception(exc, underlying_exc)\n            return\n\n        self._upgraded = upgraded\n\n        payload: StreamReader | None = None\n        for message, payload in messages:\n            if message.should_close:\n                self._should_close = True\n\n            self._payload = payload\n\n            if self._skip_payload or message.code in EMPTY_BODY_STATUS_CODES:\n                self.feed_data((message, EMPTY_PAYLOAD))\n            else:\n                self.feed_data((message, payload))\n\n        if payload is not None:\n            # new message(s) was processed\n            # register timeout handler unsubscribing\n            # either on end-of-stream or immediately for\n            # EMPTY_PAYLOAD\n            if payload is not EMPTY_PAYLOAD:\n                payload.on_eof(self._drop_timeout)\n            else:\n                self._drop_timeout()\n\n        if upgraded and tail:\n            self.data_received(tail)\n"
  },
  {
    "path": "aiohttp/client_reqrep.py",
    "content": "import asyncio\nimport codecs\nimport contextlib\nimport functools\nimport io\nimport re\nimport sys\nimport traceback\nimport warnings\nfrom collections.abc import Callable, Iterable, Sequence\nfrom hashlib import md5, sha1, sha256\nfrom http.cookies import BaseCookie, SimpleCookie\nfrom types import MappingProxyType, TracebackType\nfrom typing import TYPE_CHECKING, Any, NamedTuple, TypedDict\n\nfrom multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy\nfrom yarl import URL, Query\n\nfrom . import hdrs, multipart, payload\nfrom ._cookie_helpers import (\n    parse_cookie_header,\n    parse_set_cookie_headers,\n    preserve_morsel_with_coded_value,\n)\nfrom .abc import AbstractStreamWriter\nfrom .base_protocol import BaseProtocol\nfrom .client_exceptions import (\n    ClientConnectionError,\n    ClientOSError,\n    ClientResponseError,\n    ContentTypeError,\n    InvalidURL,\n    ServerFingerprintMismatch,\n)\nfrom .compression_utils import HAS_BROTLI, HAS_ZSTD\nfrom .formdata import FormData\nfrom .helpers import (\n    _SENTINEL,\n    BaseTimerContext,\n    BasicAuth,\n    HeadersMixin,\n    TimerNoop,\n    frozen_dataclass_decorator,\n    is_expected_content_type,\n    parse_mimetype,\n    reify,\n    sentinel,\n    set_exception,\n    set_result,\n)\nfrom .http import (\n    SERVER_SOFTWARE,\n    HttpProcessingError,\n    HttpVersion,\n    HttpVersion10,\n    HttpVersion11,\n    StreamWriter,\n)\nfrom .streams import StreamReader\nfrom .typedefs import DEFAULT_JSON_DECODER, JSONDecoder, RawHeaders\n\ntry:\n    import ssl\n    from ssl import SSLContext\nexcept ImportError:  # pragma: no cover\n    ssl = None  # type: ignore[assignment]\n    SSLContext = object  # type: ignore[misc,assignment]\n\n\n__all__ = (\"ClientRequest\", \"ClientResponse\", \"RequestInfo\", \"Fingerprint\")\n\n\nif TYPE_CHECKING:\n    from .client import ClientSession\n    from .connector import Connection\n    from .tracing import Trace\n\n\n_CONNECTION_CLOSED_EXCEPTION = ClientConnectionError(\"Connection closed\")\n_CONTAINS_CONTROL_CHAR_RE = re.compile(r\"[^-!#$%&'*+.^_`|~0-9a-zA-Z]\")\n\n\ndef _gen_default_accept_encoding() -> str:\n    encodings = [\n        \"gzip\",\n        \"deflate\",\n    ]\n    if HAS_BROTLI:\n        encodings.append(\"br\")\n    if HAS_ZSTD:\n        encodings.append(\"zstd\")\n    return \", \".join(encodings)\n\n\n@frozen_dataclass_decorator\nclass ContentDisposition:\n    type: str | None\n    parameters: \"MappingProxyType[str, str]\"\n    filename: str | None\n\n\nclass _RequestInfo(NamedTuple):\n    url: URL\n    method: str\n    headers: \"CIMultiDictProxy[str]\"\n    real_url: URL\n\n\nclass RequestInfo(_RequestInfo):\n\n    def __new__(\n        cls,\n        url: URL,\n        method: str,\n        headers: \"CIMultiDictProxy[str]\",\n        real_url: URL | _SENTINEL = sentinel,\n    ) -> \"RequestInfo\":\n        \"\"\"Create a new RequestInfo instance.\n\n        For backwards compatibility, the real_url parameter is optional.\n        \"\"\"\n        return tuple.__new__(\n            cls, (url, method, headers, url if real_url is sentinel else real_url)\n        )\n\n\nclass Fingerprint:\n    HASHFUNC_BY_DIGESTLEN = {\n        16: md5,\n        20: sha1,\n        32: sha256,\n    }\n\n    def __init__(self, fingerprint: bytes) -> None:\n        digestlen = len(fingerprint)\n        hashfunc = self.HASHFUNC_BY_DIGESTLEN.get(digestlen)\n        if not hashfunc:\n            raise ValueError(\"fingerprint has invalid length\")\n        elif hashfunc is md5 or hashfunc is sha1:\n            raise ValueError(\"md5 and sha1 are insecure and not supported. Use sha256.\")\n        self._hashfunc = hashfunc\n        self._fingerprint = fingerprint\n\n    @property\n    def fingerprint(self) -> bytes:\n        return self._fingerprint\n\n    def check(self, transport: asyncio.Transport) -> None:\n        if not transport.get_extra_info(\"sslcontext\"):\n            return\n        sslobj = transport.get_extra_info(\"ssl_object\")\n        cert = sslobj.getpeercert(binary_form=True)\n        got = self._hashfunc(cert).digest()\n        if got != self._fingerprint:\n            host, port, *_ = transport.get_extra_info(\"peername\")\n            raise ServerFingerprintMismatch(self._fingerprint, got, host, port)\n\n\nif ssl is not None:\n    SSL_ALLOWED_TYPES = (ssl.SSLContext, bool, Fingerprint)\nelse:  # pragma: no cover\n    SSL_ALLOWED_TYPES = (bool,)  # type: ignore[unreachable]\n\n\n_CONNECTION_CLOSED_EXCEPTION = ClientConnectionError(\"Connection closed\")\n_SSL_SCHEMES = frozenset((\"https\", \"wss\"))\n\n\n# ConnectionKey is a NamedTuple because it is used as a key in a dict\n# and a set in the connector. Since a NamedTuple is a tuple it uses\n# the fast native tuple __hash__ and __eq__ implementation in CPython.\nclass ConnectionKey(NamedTuple):\n    # the key should contain an information about used proxy / TLS\n    # to prevent reusing wrong connections from a pool\n    host: str\n    port: int | None\n    is_ssl: bool\n    ssl: SSLContext | bool | Fingerprint\n    proxy: URL | None\n    proxy_auth: BasicAuth | None\n    proxy_headers_hash: int | None  # hash(CIMultiDict)\n\n\nclass ClientResponse(HeadersMixin):\n    # Some of these attributes are None when created,\n    # but will be set by the start() method.\n    # As the end user will likely never see the None values, we cheat the types below.\n    # from the Status-Line of the response\n    version: HttpVersion | None = None  # HTTP-Version\n    status: int = None  # type: ignore[assignment] # Status-Code\n    reason: str | None = None  # Reason-Phrase\n\n    content: StreamReader = None  # type: ignore[assignment] # Payload stream\n    _body: bytes | None = None\n    _headers: CIMultiDictProxy[str] = None  # type: ignore[assignment]\n    _history: tuple[\"ClientResponse\", ...] = ()\n    _raw_headers: RawHeaders = None  # type: ignore[assignment]\n\n    _connection: \"Connection | None\" = None  # current connection\n    _cookies: SimpleCookie | None = None\n    _raw_cookie_headers: tuple[str, ...] | None = None\n    _continue: asyncio.Future[bool] | None = None\n    _source_traceback: traceback.StackSummary | None = None\n    _session: \"ClientSession | None\" = None\n    # set up by ClientRequest after ClientResponse object creation\n    # post-init stage allows to not change ctor signature\n    _closed = True  # to allow __del__ for non-initialized properly response\n    _released = False\n    _in_context = False\n\n    _resolve_charset: Callable[[\"ClientResponse\", bytes], str] = lambda *_: \"utf-8\"\n\n    __writer: asyncio.Task[None] | None = None\n\n    def __init__(\n        self,\n        method: str,\n        url: URL,\n        *,\n        writer: asyncio.Task[None] | None,\n        continue100: asyncio.Future[bool] | None,\n        timer: BaseTimerContext | None,\n        traces: Sequence[\"Trace\"],\n        loop: asyncio.AbstractEventLoop,\n        session: \"ClientSession | None\",\n        request_headers: CIMultiDict[str],\n        original_url: URL,\n        **kwargs: object,\n    ) -> None:\n        # kwargs exists so authors of subclasses should expect to pass through unknown\n        # arguments. This allows us to safely add new arguments in future releases.\n        # But, we should never receive unknown arguments here in the parent class, this\n        # would indicate an argument has been named wrong or similar in the subclass.\n        assert not kwargs, \"Unexpected arguments to ClientResponse\"\n        # URL forbids subclasses, so a simple type check is enough.\n        assert type(url) is URL\n\n        self.method = method\n\n        self._real_url = url\n        self._url = url.with_fragment(None) if url.raw_fragment else url\n        if writer is not None:\n            self._writer = writer\n        if continue100 is not None:\n            self._continue = continue100\n        self._request_headers = request_headers\n        self._original_url = original_url\n        self._timer = timer if timer is not None else TimerNoop()\n        self._cache: dict[str, Any] = {}\n        self._traces = traces\n        self._loop = loop\n        # Save reference to _resolve_charset, so that get_encoding() will still\n        # work after the response has finished reading the body.\n        if session is not None:\n            # store a reference to session #1985\n            self._session = session\n            self._resolve_charset = session._resolve_charset\n        if loop.get_debug():\n            self._source_traceback = traceback.extract_stack(sys._getframe(1))\n\n    def __reset_writer(self, _: object = None) -> None:\n        self.__writer = None\n\n    @property\n    def _writer(self) -> asyncio.Task[None] | None:\n        \"\"\"The writer task for streaming data.\n\n        _writer is only provided for backwards compatibility\n        for subclasses that may need to access it.\n        \"\"\"\n        return self.__writer\n\n    @_writer.setter\n    def _writer(self, writer: asyncio.Task[None] | None) -> None:\n        \"\"\"Set the writer task for streaming data.\"\"\"\n        if self.__writer is not None:\n            self.__writer.remove_done_callback(self.__reset_writer)\n        self.__writer = writer\n        if writer is None:\n            return\n        if writer.done():\n            # The writer is already done, so we can clear it immediately.\n            self.__writer = None\n        else:\n            writer.add_done_callback(self.__reset_writer)\n\n    @property\n    def cookies(self) -> SimpleCookie:\n        if self._cookies is None:\n            if self._raw_cookie_headers is not None:\n                # Parse cookies for response.cookies (SimpleCookie for backward compatibility)\n                cookies = SimpleCookie()\n                # Use parse_set_cookie_headers for more lenient parsing that handles\n                # malformed cookies better than SimpleCookie.load\n                cookies.update(parse_set_cookie_headers(self._raw_cookie_headers))\n                self._cookies = cookies\n            else:\n                self._cookies = SimpleCookie()\n        return self._cookies\n\n    @cookies.setter\n    def cookies(self, cookies: SimpleCookie) -> None:\n        self._cookies = cookies\n        # Generate raw cookie headers from the SimpleCookie\n        if cookies:\n            self._raw_cookie_headers = tuple(\n                morsel.OutputString() for morsel in cookies.values()\n            )\n        else:\n            self._raw_cookie_headers = None\n\n    @reify\n    def url(self) -> URL:\n        return self._url\n\n    @reify\n    def real_url(self) -> URL:\n        return self._real_url\n\n    @reify\n    def host(self) -> str:\n        assert self._url.host is not None\n        return self._url.host\n\n    @reify\n    def headers(self) -> \"CIMultiDictProxy[str]\":\n        return self._headers\n\n    @reify\n    def raw_headers(self) -> RawHeaders:\n        return self._raw_headers\n\n    @reify\n    def request_info(self) -> RequestInfo:\n        # Build RequestInfo lazily from components\n        headers = CIMultiDictProxy(self._request_headers)\n        return tuple.__new__(\n            RequestInfo, (self._url, self.method, headers, self._original_url)\n        )\n\n    @reify\n    def content_disposition(self) -> ContentDisposition | None:\n        raw = self._headers.get(hdrs.CONTENT_DISPOSITION)\n        if raw is None:\n            return None\n        disposition_type, params_dct = multipart.parse_content_disposition(raw)\n        params = MappingProxyType(params_dct)\n        filename = multipart.content_disposition_filename(params)\n        return ContentDisposition(disposition_type, params, filename)\n\n    def __del__(self, _warnings: Any = warnings) -> None:\n        if self._closed:\n            return\n\n        if self._connection is not None:\n            self._connection.release()\n            self._cleanup_writer()\n\n            if self._loop.get_debug():\n                _warnings.warn(\n                    f\"Unclosed response {self!r}\", ResourceWarning, source=self\n                )\n                context = {\"client_response\": self, \"message\": \"Unclosed response\"}\n                if self._source_traceback:\n                    context[\"source_traceback\"] = self._source_traceback\n                self._loop.call_exception_handler(context)\n\n    def __repr__(self) -> str:\n        out = io.StringIO()\n        ascii_encodable_url = str(self.url)\n        if self.reason:\n            ascii_encodable_reason = self.reason.encode(\n                \"ascii\", \"backslashreplace\"\n            ).decode(\"ascii\")\n        else:\n            ascii_encodable_reason = \"None\"\n        print(\n            f\"<ClientResponse({ascii_encodable_url}) [{self.status} {ascii_encodable_reason}]>\",\n            file=out,\n        )\n        print(self.headers, file=out)\n        return out.getvalue()\n\n    @property\n    def connection(self) -> \"Connection | None\":\n        return self._connection\n\n    @reify\n    def history(self) -> tuple[\"ClientResponse\", ...]:\n        \"\"\"A sequence of responses, if redirects occurred.\"\"\"\n        return self._history\n\n    @reify\n    def links(self) -> \"MultiDictProxy[MultiDictProxy[str | URL]]\":\n        links_str = \", \".join(self.headers.getall(\"link\", []))\n\n        if not links_str:\n            return MultiDictProxy(MultiDict())\n\n        links: MultiDict[MultiDictProxy[str | URL]] = MultiDict()\n\n        for val in re.split(r\",(?=\\s*<)\", links_str):\n            match = re.match(r\"\\s*<(.*)>(.*)\", val)\n            if match is None:  # Malformed link\n                continue\n            url, params_str = match.groups()\n            params = params_str.split(\";\")[1:]\n\n            link: MultiDict[str | URL] = MultiDict()\n\n            for param in params:\n                match = re.match(r\"^\\s*(\\S*)\\s*=\\s*(['\\\"]?)(.*?)(\\2)\\s*$\", param, re.M)\n                if match is None:  # Malformed param\n                    continue\n                key, _, value, _ = match.groups()\n\n                link.add(key, value)\n\n            key = link.get(\"rel\", url)\n\n            link.add(\"url\", self.url.join(URL(url)))\n\n            links.add(str(key), MultiDictProxy(link))\n\n        return MultiDictProxy(links)\n\n    async def start(self, connection: \"Connection\") -> \"ClientResponse\":\n        \"\"\"Start response processing.\"\"\"\n        self._closed = False\n        self._protocol = connection.protocol\n        self._connection = connection\n\n        with self._timer:\n            while True:\n                # read response\n                try:\n                    protocol = self._protocol\n                    message, payload = await protocol.read()  # type: ignore[union-attr]\n                except HttpProcessingError as exc:\n                    raise ClientResponseError(\n                        self.request_info,\n                        self.history,\n                        status=exc.code,\n                        message=exc.message,\n                        headers=exc.headers,\n                    ) from exc\n\n                if message.code < 100 or message.code > 199 or message.code == 101:\n                    break\n\n                if self._continue is not None:\n                    set_result(self._continue, True)\n                    self._continue = None\n\n        # payload eof handler\n        payload.on_eof(self._response_eof)\n\n        # response status\n        self.version = message.version\n        self.status = message.code\n        self.reason = message.reason\n\n        # headers\n        self._headers = message.headers  # type is CIMultiDictProxy\n        self._raw_headers = message.raw_headers  # type is Tuple[bytes, bytes]\n\n        # payload\n        self.content = payload\n\n        # cookies\n        if cookie_hdrs := self.headers.getall(hdrs.SET_COOKIE, ()):\n            # Store raw cookie headers for CookieJar\n            self._raw_cookie_headers = tuple(cookie_hdrs)\n        return self\n\n    def _response_eof(self) -> None:\n        if self._closed:\n            return\n\n        # protocol could be None because connection could be detached\n        protocol = self._connection and self._connection.protocol\n        if protocol is not None and protocol.upgraded:\n            return\n\n        self._closed = True\n        self._cleanup_writer()\n        self._release_connection()\n\n    @property\n    def closed(self) -> bool:\n        return self._closed\n\n    def close(self) -> None:\n        if not self._released:\n            self._notify_content()\n\n        self._closed = True\n        if self._loop.is_closed():\n            return\n\n        self._cleanup_writer()\n        if self._connection is not None:\n            self._connection.close()\n            self._connection = None\n\n    def release(self) -> None:\n        if not self._released:\n            self._notify_content()\n\n        self._closed = True\n\n        self._cleanup_writer()\n        self._release_connection()\n\n    @property\n    def ok(self) -> bool:\n        \"\"\"Returns ``True`` if ``status`` is less than ``400``, ``False`` if not.\n\n        This is **not** a check for ``200 OK`` but a check that the response\n        status is under 400.\n        \"\"\"\n        return 400 > self.status\n\n    def raise_for_status(self) -> None:\n        if not self.ok:\n            # reason should always be not None for a started response\n            assert self.reason is not None\n\n            # If we're in a context we can rely on __aexit__() to release as the\n            # exception propagates.\n            if not self._in_context:\n                self.release()\n\n            raise ClientResponseError(\n                self.request_info,\n                self.history,\n                status=self.status,\n                message=self.reason,\n                headers=self.headers,\n            )\n\n    def _release_connection(self) -> None:\n        if self._connection is not None:\n            if self.__writer is None:\n                self._connection.release()\n                self._connection = None\n            else:\n                self.__writer.add_done_callback(lambda f: self._release_connection())\n\n    async def _wait_released(self) -> None:\n        if self.__writer is not None:\n            try:\n                await self.__writer\n            except asyncio.CancelledError:\n                if (\n                    sys.version_info >= (3, 11)\n                    and (task := asyncio.current_task())\n                    and task.cancelling()\n                ):\n                    raise\n        self._release_connection()\n\n    def _cleanup_writer(self) -> None:\n        if self.__writer is not None:\n            self.__writer.cancel()\n        self._session = None\n\n    def _notify_content(self) -> None:\n        content = self.content\n        # content can be None here, but the types are cheated elsewhere.\n        if content and content.exception() is None:  # type: ignore[truthy-bool]\n            set_exception(content, _CONNECTION_CLOSED_EXCEPTION)\n        self._released = True\n\n    async def wait_for_close(self) -> None:\n        if self.__writer is not None:\n            try:\n                await self.__writer\n            except asyncio.CancelledError:\n                if (\n                    sys.version_info >= (3, 11)\n                    and (task := asyncio.current_task())\n                    and task.cancelling()\n                ):\n                    raise\n        self.release()\n\n    async def read(self) -> bytes:\n        \"\"\"Read response payload.\"\"\"\n        if self._body is None:\n            try:\n                self._body = await self.content.read()\n                for trace in self._traces:\n                    await trace.send_response_chunk_received(\n                        self.method, self.url, self._body\n                    )\n            except BaseException:\n                self.close()\n                raise\n        elif self._released:  # Response explicitly released\n            raise ClientConnectionError(\"Connection closed\")\n\n        protocol = self._connection and self._connection.protocol\n        if protocol is None or not protocol.upgraded:\n            await self._wait_released()  # Underlying connection released\n        return self._body\n\n    def get_encoding(self) -> str:\n        ctype = self.headers.get(hdrs.CONTENT_TYPE, \"\").lower()\n        mimetype = parse_mimetype(ctype)\n\n        encoding = mimetype.parameters.get(\"charset\")\n        if encoding:\n            with contextlib.suppress(LookupError, ValueError):\n                return codecs.lookup(encoding).name\n\n        if mimetype.type == \"application\" and (\n            mimetype.subtype == \"json\" or mimetype.subtype == \"rdap\"\n        ):\n            # RFC 7159 states that the default encoding is UTF-8.\n            # RFC 7483 defines application/rdap+json\n            return \"utf-8\"\n\n        if self._body is None:\n            raise RuntimeError(\n                \"Cannot compute fallback encoding of a not yet read body\"\n            )\n\n        return self._resolve_charset(self, self._body)\n\n    async def text(self, encoding: str | None = None, errors: str = \"strict\") -> str:\n        \"\"\"Read response payload and decode.\"\"\"\n        await self.read()\n\n        if encoding is None:\n            encoding = self.get_encoding()\n\n        return self._body.decode(encoding, errors=errors)  # type: ignore[union-attr]\n\n    async def json(\n        self,\n        *,\n        encoding: str | None = None,\n        loads: JSONDecoder = DEFAULT_JSON_DECODER,\n        content_type: str | None = \"application/json\",\n    ) -> Any:\n        \"\"\"Read and decodes JSON response.\"\"\"\n        await self.read()\n\n        if content_type:\n            if not is_expected_content_type(self.content_type, content_type):\n                raise ContentTypeError(\n                    self.request_info,\n                    self.history,\n                    status=self.status,\n                    message=(\n                        \"Attempt to decode JSON with \"\n                        \"unexpected mimetype: %s\" % self.content_type\n                    ),\n                    headers=self.headers,\n                )\n\n        if encoding is None:\n            encoding = self.get_encoding()\n\n        return loads(self._body.decode(encoding))  # type: ignore[union-attr]\n\n    async def __aenter__(self) -> \"ClientResponse\":\n        self._in_context = True\n        return self\n\n    async def __aexit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc_val: BaseException | None,\n        exc_tb: TracebackType | None,\n    ) -> None:\n        self._in_context = False\n        # similar to _RequestContextManager, we do not need to check\n        # for exceptions, response object can close connection\n        # if state is broken\n        self.release()\n        await self.wait_for_close()\n\n\nclass ClientRequestBase:\n    \"\"\"An internal class for proxy requests.\"\"\"\n\n    POST_METHODS = {hdrs.METH_PATCH, hdrs.METH_POST, hdrs.METH_PUT}\n\n    auth = None\n    proxy: URL | None = None\n    response_class = ClientResponse\n    server_hostname: str | None = None  # Needed in connector.py\n    version = HttpVersion11\n    _response = None\n\n    # These class defaults help create_autospec() work correctly.\n    # If autospec is improved in future, maybe these can be removed.\n    url = URL()\n    method = \"GET\"\n\n    _writer_task: asyncio.Task[None] | None = None  # async task for streaming data\n\n    _skip_auto_headers: \"CIMultiDict[None] | None\" = None\n\n    # N.B.\n    # Adding __del__ method with self._writer closing doesn't make sense\n    # because _writer is instance method, thus it keeps a reference to self.\n    # Until writer has finished finalizer will not be called.\n\n    def __init__(\n        self,\n        method: str,\n        url: URL,\n        *,\n        headers: CIMultiDict[str],\n        auth: BasicAuth | None,\n        loop: asyncio.AbstractEventLoop,\n        ssl: SSLContext | bool | Fingerprint,\n        trust_env: bool = False,\n    ):\n        if match := _CONTAINS_CONTROL_CHAR_RE.search(method):\n            raise ValueError(\n                f\"Method cannot contain non-token characters {method!r} \"\n                f\"(found at least {match.group()!r})\"\n            )\n        # URL forbids subclasses, so a simple type check is enough.\n        assert type(url) is URL, url\n        self.original_url = url\n        self.url = url.with_fragment(None) if url.raw_fragment else url\n        self.method = method.upper()\n        self.loop = loop\n        self._ssl = ssl\n\n        if loop.get_debug():\n            self._source_traceback = traceback.extract_stack(sys._getframe(1))\n\n        self._update_host(url)\n        self._update_headers(headers)\n        self._update_auth(auth, trust_env)\n\n    def _reset_writer(self, _: object = None) -> None:\n        self._writer_task = None\n\n    def _get_content_length(self) -> int | None:\n        \"\"\"Extract and validate Content-Length header value.\n\n        Returns parsed Content-Length value or None if not set.\n        Raises ValueError if header exists but cannot be parsed as an integer.\n        \"\"\"\n        if hdrs.CONTENT_LENGTH not in self.headers:\n            return None\n\n        content_length_hdr = self.headers[hdrs.CONTENT_LENGTH]\n        try:\n            return int(content_length_hdr)\n        except ValueError:\n            raise ValueError(\n                f\"Invalid Content-Length header: {content_length_hdr}\"\n            ) from None\n\n    @property\n    def _writer(self) -> asyncio.Task[None] | None:\n        return self._writer_task\n\n    @_writer.setter\n    def _writer(self, writer: asyncio.Task[None]) -> None:\n        if self._writer_task is not None:\n            self._writer_task.remove_done_callback(self._reset_writer)\n        self._writer_task = writer\n        writer.add_done_callback(self._reset_writer)\n\n    def is_ssl(self) -> bool:\n        return self.url.scheme in _SSL_SCHEMES\n\n    @property\n    def ssl(self) -> \"SSLContext | bool | Fingerprint\":\n        return self._ssl\n\n    @property\n    def connection_key(self) -> ConnectionKey:\n        url = self.url\n        return tuple.__new__(\n            ConnectionKey,\n            (\n                url.raw_host or \"\",\n                url.port,\n                url.scheme in _SSL_SCHEMES,\n                self._ssl,\n                None,\n                None,\n                None,\n            ),\n        )\n\n    def _update_auth(self, auth: BasicAuth | None, trust_env: bool = False) -> None:\n        \"\"\"Set basic auth.\"\"\"\n        if auth is None:\n            auth = self.auth\n        if auth is None:\n            return\n\n        if not isinstance(auth, BasicAuth):\n            raise TypeError(\"BasicAuth() tuple is required instead\")\n\n        self.headers[hdrs.AUTHORIZATION] = auth.encode()\n\n    def _update_host(self, url: URL) -> None:\n        \"\"\"Update destination host, port and connection type (ssl).\"\"\"\n        # get host/port\n        if not url.raw_host:\n            raise InvalidURL(url)\n\n        # basic auth info\n        if url.raw_user or url.raw_password:\n            self.auth = BasicAuth(url.user or \"\", url.password or \"\")\n\n    def _update_headers(self, headers: CIMultiDict[str]) -> None:\n        \"\"\"Update request headers.\"\"\"\n        self.headers: CIMultiDict[str] = CIMultiDict()\n\n        # Build the host header\n        host = self.url.host_port_subcomponent\n\n        # host_port_subcomponent is None when the URL is a relative URL.\n        # but we know we do not have a relative URL here.\n        assert host is not None\n        self.headers[hdrs.HOST] = headers.pop(hdrs.HOST, host)\n        self.headers.extend(headers)\n\n    def _create_response(self, task: asyncio.Task[None] | None) -> ClientResponse:\n        return self.response_class(\n            self.method,\n            self.original_url,\n            writer=task,\n            continue100=None,\n            timer=TimerNoop(),\n            traces=(),\n            loop=self.loop,\n            session=None,\n            request_headers=self.headers,\n            original_url=self.original_url,\n        )\n\n    def _create_writer(self, protocol: BaseProtocol) -> StreamWriter:\n        return StreamWriter(protocol, self.loop)\n\n    def _should_write(self, protocol: BaseProtocol) -> bool:\n        return protocol.writing_paused\n\n    async def _send(self, conn: \"Connection\") -> ClientResponse:\n        # Specify request target:\n        # - CONNECT request must send authority form URI\n        # - not CONNECT proxy must send absolute form URI\n        # - most common is origin form URI\n        if self.method == hdrs.METH_CONNECT:\n            connect_host = self.url.host_subcomponent\n            assert connect_host is not None\n            path = f\"{connect_host}:{self.url.port}\"\n        elif self.proxy and not self.is_ssl():\n            path = str(self.url)\n        else:\n            path = self.url.raw_path_qs\n\n        protocol = conn.protocol\n        assert protocol is not None\n        writer = self._create_writer(protocol)\n\n        # set default content-type\n        if (\n            self.method in self.POST_METHODS\n            and (\n                self._skip_auto_headers is None\n                or hdrs.CONTENT_TYPE not in self._skip_auto_headers\n            )\n            and hdrs.CONTENT_TYPE not in self.headers\n        ):\n            self.headers[hdrs.CONTENT_TYPE] = \"application/octet-stream\"\n\n        v = self.version\n        if hdrs.CONNECTION not in self.headers:\n            if conn._connector.force_close:\n                if v == HttpVersion11:\n                    self.headers[hdrs.CONNECTION] = \"close\"\n            elif v == HttpVersion10:\n                self.headers[hdrs.CONNECTION] = \"keep-alive\"\n\n        # status + headers\n        status_line = f\"{self.method} {path} HTTP/{v.major}.{v.minor}\"\n\n        # Buffer headers for potential coalescing with body\n        await writer.write_headers(status_line, self.headers)\n\n        task: asyncio.Task[None] | None\n        if self._should_write(protocol):\n            coro = self._write_bytes(writer, conn, self._get_content_length())\n            if sys.version_info >= (3, 12):\n                # Optimization for Python 3.12, try to write\n                # bytes immediately to avoid having to schedule\n                # the task on the event loop.\n                task = asyncio.Task(coro, loop=self.loop, eager_start=True)\n            else:\n                task = self.loop.create_task(coro)\n            if task.done():\n                task = None\n            else:\n                self._writer = task\n        else:\n            # We have nothing to write because\n            # - there is no body\n            # - the protocol does not have writing paused\n            # - we are not waiting for a 100-continue response\n            protocol.start_timeout()\n            writer.set_eof()\n            task = None\n        self._response = self._create_response(task)\n        return self._response\n\n    async def _write_bytes(\n        self,\n        writer: AbstractStreamWriter,\n        conn: \"Connection\",\n        content_length: int | None,\n    ) -> None:\n        # Base class never has a body, this will never be run.\n        assert False\n\n\nclass ClientRequestArgs(TypedDict, total=False):\n    params: Query\n    headers: CIMultiDict[str]\n    skip_auto_headers: Iterable[str] | None\n    data: Any\n    cookies: BaseCookie[str]\n    auth: BasicAuth | None\n    version: HttpVersion\n    compress: str | bool\n    chunked: bool | None\n    expect100: bool\n    loop: asyncio.AbstractEventLoop\n    response_class: type[ClientResponse]\n    proxy: URL | None\n    proxy_auth: BasicAuth | None\n    timer: BaseTimerContext\n    session: \"ClientSession\"\n    ssl: SSLContext | bool | Fingerprint\n    proxy_headers: CIMultiDict[str] | None\n    traces: list[\"Trace\"]\n    trust_env: bool\n    server_hostname: str | None\n\n\nclass ClientRequest(ClientRequestBase):\n    _EMPTY_BODY = payload.PAYLOAD_REGISTRY.get(b\"\", disposition=None)\n    _body = _EMPTY_BODY\n    _continue = None  # waiter future for '100 Continue' response\n\n    GET_METHODS = {\n        hdrs.METH_GET,\n        hdrs.METH_HEAD,\n        hdrs.METH_OPTIONS,\n        hdrs.METH_TRACE,\n    }\n    DEFAULT_HEADERS = {\n        hdrs.ACCEPT: \"*/*\",\n        hdrs.ACCEPT_ENCODING: _gen_default_accept_encoding(),\n    }\n\n    def __init__(\n        self,\n        method: str,\n        url: URL,\n        *,\n        params: Query,\n        headers: CIMultiDict[str],\n        skip_auto_headers: Iterable[str] | None,\n        data: Any,\n        cookies: BaseCookie[str],\n        auth: BasicAuth | None,\n        version: HttpVersion,\n        compress: str | bool,\n        chunked: bool | None,\n        expect100: bool,\n        loop: asyncio.AbstractEventLoop,\n        response_class: type[ClientResponse],\n        proxy: URL | None,\n        proxy_auth: BasicAuth | None,\n        timer: BaseTimerContext,\n        session: \"ClientSession\",\n        ssl: SSLContext | bool | Fingerprint,\n        proxy_headers: CIMultiDict[str] | None,\n        traces: list[\"Trace\"],\n        trust_env: bool,\n        server_hostname: str | None,\n        **kwargs: object,\n    ):\n        # kwargs exists so authors of subclasses should expect to pass through unknown\n        # arguments. This allows us to safely add new arguments in future releases.\n        # But, we should never receive unknown arguments here in the parent class, this\n        # would indicate an argument has been named wrong or similar in the subclass.\n        assert not kwargs, \"Unexpected arguments to ClientRequest\"\n\n        if params:\n            url = url.extend_query(params)\n        super().__init__(method, url, headers=headers, auth=auth, loop=loop, ssl=ssl)\n\n        if proxy is not None:\n            assert type(proxy) is URL, proxy\n        self._session = session\n        self.chunked = chunked\n        self.response_class = response_class\n        self._timer = timer\n        self.server_hostname = server_hostname\n        self.version = version\n\n        self._update_auto_headers(skip_auto_headers)\n        self._update_cookies(cookies)\n        self._update_content_encoding(data, compress)\n        self._update_proxy(proxy, proxy_auth, proxy_headers)\n\n        self._update_body_from_data(data)\n        if data is not None or self.method not in self.GET_METHODS:\n            self._update_transfer_encoding()\n        self._update_expect_continue(expect100)\n        self._traces = traces\n\n    @property\n    def body(self) -> payload.Payload:\n        return self._body\n\n    @property\n    def skip_auto_headers(self) -> CIMultiDict[None]:\n        return self._skip_auto_headers or CIMultiDict()\n\n    @property\n    def connection_key(self) -> ConnectionKey:\n        if proxy_headers := self.proxy_headers:\n            h: int | None = hash(tuple(proxy_headers.items()))\n        else:\n            h = None\n        url = self.url\n        return tuple.__new__(\n            ConnectionKey,\n            (\n                url.raw_host or \"\",\n                url.port,\n                url.scheme in _SSL_SCHEMES,\n                self._ssl,\n                self.proxy,\n                self.proxy_auth,\n                h,\n            ),\n        )\n\n    @property\n    def session(self) -> \"ClientSession\":\n        \"\"\"Return the ClientSession instance.\n\n        This property provides access to the ClientSession that initiated\n        this request, allowing middleware to make additional requests\n        using the same session.\n        \"\"\"\n        return self._session\n\n    def _update_auto_headers(self, skip_auto_headers: Iterable[str] | None) -> None:\n        if skip_auto_headers is not None:\n            self._skip_auto_headers = CIMultiDict(\n                (hdr, None) for hdr in sorted(skip_auto_headers)\n            )\n            used_headers = self.headers.copy()\n            used_headers.extend(self._skip_auto_headers)  # type: ignore[arg-type]\n        else:\n            # Fast path when there are no headers to skip\n            # which is the most common case.\n            used_headers = self.headers\n\n        for hdr, val in self.DEFAULT_HEADERS.items():\n            if hdr not in used_headers:\n                self.headers[hdr] = val\n\n        if hdrs.USER_AGENT not in used_headers:\n            self.headers[hdrs.USER_AGENT] = SERVER_SOFTWARE\n\n    def _update_cookies(self, cookies: BaseCookie[str]) -> None:\n        \"\"\"Update request cookies header.\"\"\"\n        if not cookies:\n            return\n\n        c = SimpleCookie()\n        if hdrs.COOKIE in self.headers:\n            # parse_cookie_header for RFC 6265 compliant Cookie header parsing\n            c.update(parse_cookie_header(self.headers.get(hdrs.COOKIE, \"\")))\n            del self.headers[hdrs.COOKIE]\n\n        for name, value in cookies.items():\n            # Use helper to preserve coded_value exactly as sent by server\n            c[name] = preserve_morsel_with_coded_value(value)\n\n        self.headers[hdrs.COOKIE] = c.output(header=\"\", sep=\";\").strip()\n\n    def _update_content_encoding(self, data: Any, compress: bool | str) -> None:\n        \"\"\"Set request content encoding.\"\"\"\n        self.compress = None\n        if not data:\n            return\n\n        if self.headers.get(hdrs.CONTENT_ENCODING):\n            if compress:\n                raise ValueError(\n                    \"compress can not be set if Content-Encoding header is set\"\n                )\n        elif compress:\n            self.compress = compress if isinstance(compress, str) else \"deflate\"\n            self.headers[hdrs.CONTENT_ENCODING] = self.compress\n            self.chunked = True  # enable chunked, no need to deal with length\n\n    def _update_transfer_encoding(self) -> None:\n        \"\"\"Analyze transfer-encoding header.\"\"\"\n        te = self.headers.get(hdrs.TRANSFER_ENCODING, \"\").lower()\n\n        if \"chunked\" in te:\n            if self.chunked:\n                raise ValueError(\n                    \"chunked can not be set \"\n                    'if \"Transfer-Encoding: chunked\" header is set'\n                )\n\n        elif self.chunked:\n            if hdrs.CONTENT_LENGTH in self.headers:\n                raise ValueError(\n                    \"chunked can not be set if Content-Length header is set\"\n                )\n\n            self.headers[hdrs.TRANSFER_ENCODING] = \"chunked\"\n\n    def _update_body_from_data(self, body: Any) -> None:\n        \"\"\"Update request body from data.\"\"\"\n        if body is None:\n            self._body = self._EMPTY_BODY\n            # Set Content-Length to 0 when body is None for methods that expect a body\n            if (\n                self.method not in self.GET_METHODS\n                and not self.chunked\n                and hdrs.CONTENT_LENGTH not in self.headers\n            ):\n                self.headers[hdrs.CONTENT_LENGTH] = \"0\"\n            return\n\n        # FormData\n        if isinstance(body, FormData):\n            body = body()\n        else:\n            try:\n                body = payload.PAYLOAD_REGISTRY.get(body, disposition=None)\n            except payload.LookupError:\n                boundary = None\n                if hdrs.CONTENT_TYPE in self.headers:\n                    boundary = parse_mimetype(\n                        self.headers[hdrs.CONTENT_TYPE]\n                    ).parameters.get(\"boundary\")\n                body = FormData(body, boundary=boundary)()\n\n        self._body = body\n\n        # enable chunked encoding if needed\n        if not self.chunked and hdrs.CONTENT_LENGTH not in self.headers:\n            if (size := body.size) is not None:\n                self.headers[hdrs.CONTENT_LENGTH] = str(size)\n            else:\n                self.chunked = True\n\n        # copy payload headers\n        assert body.headers\n        headers = self.headers\n        skip_headers = self._skip_auto_headers\n        for key, value in body.headers.items():\n            if key in headers or (skip_headers is not None and key in skip_headers):\n                continue\n            headers[key] = value\n\n    def _update_body(self, body: Any) -> None:\n        \"\"\"Update request body after its already been set.\"\"\"\n        # Remove existing Content-Length header since body is changing\n        if hdrs.CONTENT_LENGTH in self.headers:\n            del self.headers[hdrs.CONTENT_LENGTH]\n\n        # Remove existing Transfer-Encoding header to avoid conflicts\n        if self.chunked and hdrs.TRANSFER_ENCODING in self.headers:\n            del self.headers[hdrs.TRANSFER_ENCODING]\n\n        # Now update the body using the existing method\n        self._update_body_from_data(body)\n\n        # Update transfer encoding headers if needed (same logic as __init__)\n        if body is not None or self.method not in self.GET_METHODS:\n            self._update_transfer_encoding()\n\n    async def update_body(self, body: Any) -> None:\n        \"\"\"\n        Update request body and close previous payload if needed.\n\n        This method safely updates the request body by first closing any existing\n        payload to prevent resource leaks, then setting the new body.\n\n        IMPORTANT: Always use this method instead of setting request.body directly.\n        Direct assignment to request.body will leak resources if the previous body\n        contains file handles, streams, or other resources that need cleanup.\n\n        Args:\n            body: The new body content. Can be:\n                - bytes/bytearray: Raw binary data\n                - str: Text data (will be encoded using charset from Content-Type)\n                - FormData: Form data that will be encoded as multipart/form-data\n                - Payload: A pre-configured payload object\n                - AsyncIterable: An async iterable of bytes chunks\n                - File-like object: Will be read and sent as binary data\n                - None: Clears the body\n\n        Usage:\n            # CORRECT: Use update_body\n            await request.update_body(b\"new request data\")\n\n            # WRONG: Don't set body directly\n            # request.body = b\"new request data\"  # This will leak resources!\n\n            # Update with form data\n            form_data = FormData()\n            form_data.add_field('field', 'value')\n            await request.update_body(form_data)\n\n            # Clear body\n            await request.update_body(None)\n\n        Note:\n            This method is async because it may need to close file handles or\n            other resources associated with the previous payload. Always await\n            this method to ensure proper cleanup.\n\n        Warning:\n            Setting request.body directly is highly discouraged and can lead to:\n            - Resource leaks (unclosed file handles, streams)\n            - Memory leaks (unreleased buffers)\n            - Unexpected behavior with streaming payloads\n\n            It is not recommended to change the payload type in middleware. If the\n            body was already set (e.g., as bytes), it's best to keep the same type\n            rather than converting it (e.g., to str) as this may result in unexpected\n            behavior.\n\n        See Also:\n            - update_body_from_data: Synchronous body update without cleanup\n            - body property: Direct body access (STRONGLY DISCOURAGED)\n\n        \"\"\"\n        # Close existing payload if it exists and needs closing\n        if self._body is not None:\n            await self._body.close()\n        self._update_body(body)\n\n    def _update_expect_continue(self, expect: bool = False) -> None:\n        if expect:\n            self.headers[hdrs.EXPECT] = \"100-continue\"\n        elif (\n            hdrs.EXPECT in self.headers\n            and self.headers[hdrs.EXPECT].lower() == \"100-continue\"\n        ):\n            expect = True\n\n        if expect:\n            self._continue = self.loop.create_future()\n\n    def _update_proxy(\n        self,\n        proxy: URL | None,\n        proxy_auth: BasicAuth | None,\n        proxy_headers: CIMultiDict[str] | None,\n    ) -> None:\n        self.proxy = proxy\n        if proxy is None:\n            self.proxy_auth = None\n            self.proxy_headers = None\n            return\n\n        if proxy_auth and not isinstance(proxy_auth, BasicAuth):\n            raise ValueError(\"proxy_auth must be None or BasicAuth() tuple\")\n        self.proxy_auth = proxy_auth\n        self.proxy_headers = proxy_headers\n\n    def _create_response(self, task: asyncio.Task[None] | None) -> ClientResponse:\n        return self.response_class(\n            self.method,\n            self.original_url,\n            writer=task,\n            continue100=self._continue,\n            timer=self._timer,\n            traces=self._traces,\n            loop=self.loop,\n            session=self._session,\n            request_headers=self.headers,\n            original_url=self.original_url,\n        )\n\n    def _create_writer(self, protocol: BaseProtocol) -> StreamWriter:\n        writer = StreamWriter(\n            protocol,\n            self.loop,\n            on_chunk_sent=(\n                functools.partial(self._on_chunk_request_sent, self.method, self.url)\n                if self._traces\n                else None\n            ),\n            on_headers_sent=(\n                functools.partial(self._on_headers_request_sent, self.method, self.url)\n                if self._traces\n                else None\n            ),\n        )\n\n        if self.compress:\n            writer.enable_compression(self.compress)\n\n        if self.chunked is not None:\n            writer.enable_chunking()\n        return writer\n\n    def _should_write(self, protocol: BaseProtocol) -> bool:\n        return (\n            self.body.size != 0 or self._continue is not None or protocol.writing_paused\n        )\n\n    async def _write_bytes(\n        self,\n        writer: AbstractStreamWriter,\n        conn: \"Connection\",\n        content_length: int | None,\n    ) -> None:\n        \"\"\"\n        Write the request body to the connection stream.\n\n        This method handles writing different types of request bodies:\n        1. Payload objects (using their specialized write_with_length method)\n        2. Bytes/bytearray objects\n        3. Iterable body content\n\n        Args:\n            writer: The stream writer to write the body to\n            conn: The connection being used for this request\n            content_length: Optional maximum number of bytes to write from the body\n                            (None means write the entire body)\n\n        The method properly handles:\n        - Waiting for 100-Continue responses if required\n        - Content length constraints for chunked encoding\n        - Error handling for network issues, cancellation, and other exceptions\n        - Signaling EOF and timeout management\n\n        Raises:\n            ClientOSError: When there's an OS-level error writing the body\n            ClientConnectionError: When there's a general connection error\n            asyncio.CancelledError: When the operation is cancelled\n\n        \"\"\"\n        # 100 response\n        if self._continue is not None:\n            # Force headers to be sent before waiting for 100-continue\n            writer.send_headers()\n            await writer.drain()\n            await self._continue\n\n        protocol = conn.protocol\n        assert protocol is not None\n        try:\n            await self._body.write_with_length(writer, content_length)\n        except OSError as underlying_exc:\n            reraised_exc = underlying_exc\n\n            # Distinguish between timeout and other OS errors for better error reporting\n            exc_is_not_timeout = underlying_exc.errno is not None or not isinstance(\n                underlying_exc, asyncio.TimeoutError\n            )\n            if exc_is_not_timeout:\n                reraised_exc = ClientOSError(\n                    underlying_exc.errno,\n                    f\"Can not write request body for {self.url !s}\",\n                )\n\n            set_exception(protocol, reraised_exc, underlying_exc)\n        except asyncio.CancelledError:\n            # Body hasn't been fully sent, so connection can't be reused\n            conn.close()\n            raise\n        except Exception as underlying_exc:\n            set_exception(\n                protocol,\n                ClientConnectionError(\n                    \"Failed to send bytes into the underlying connection \"\n                    f\"{conn !s}: {underlying_exc!r}\",\n                ),\n                underlying_exc,\n            )\n        else:\n            # Successfully wrote the body, signal EOF and start response timeout\n            await writer.write_eof()\n            protocol.start_timeout()\n\n    async def _close(self) -> None:\n        if self._writer_task is not None:\n            try:\n                await self._writer_task\n            except asyncio.CancelledError:\n                if (\n                    sys.version_info >= (3, 11)\n                    and (task := asyncio.current_task())\n                    and task.cancelling()\n                ):\n                    raise\n\n    def _terminate(self) -> None:\n        if self._writer_task is not None:\n            if not self.loop.is_closed():\n                self._writer_task.cancel()\n            self._writer_task.remove_done_callback(self._reset_writer)\n            self._writer_task = None\n\n    async def _on_chunk_request_sent(self, method: str, url: URL, chunk: bytes) -> None:\n        for trace in self._traces:\n            await trace.send_request_chunk_sent(method, url, chunk)\n\n    async def _on_headers_request_sent(\n        self, method: str, url: URL, headers: \"CIMultiDict[str]\"\n    ) -> None:\n        for trace in self._traces:\n            await trace.send_request_headers(method, url, headers)\n"
  },
  {
    "path": "aiohttp/client_ws.py",
    "content": "\"\"\"WebSocket client for asyncio.\"\"\"\n\nimport asyncio\nimport sys\nfrom collections.abc import Callable\nfrom types import TracebackType\nfrom typing import Any, Final, Generic, Literal, overload\n\nfrom ._websocket.reader import WebSocketDataQueue\nfrom .client_exceptions import ClientError, ServerTimeoutError, WSMessageTypeError\nfrom .client_reqrep import ClientResponse\nfrom .helpers import calculate_timeout_when, frozen_dataclass_decorator, set_result\nfrom .http import (\n    WS_CLOSED_MESSAGE,\n    WS_CLOSING_MESSAGE,\n    WebSocketError,\n    WSCloseCode,\n    WSMessageDecodeText,\n    WSMessageNoDecodeText,\n    WSMsgType,\n)\nfrom .http_websocket import _INTERNAL_RECEIVE_TYPES, WebSocketWriter, WSMessageError\nfrom .streams import EofStream\nfrom .typedefs import (\n    DEFAULT_JSON_DECODER,\n    DEFAULT_JSON_ENCODER,\n    JSONBytesEncoder,\n    JSONDecoder,\n    JSONEncoder,\n)\n\nif sys.version_info >= (3, 13):\n    from typing import TypeVar\nelse:\n    from typing_extensions import TypeVar\n\nif sys.version_info >= (3, 11):\n    import asyncio as async_timeout\n    from typing import Self\nelse:\n    import async_timeout\n    from typing_extensions import Self\n\n# TypeVar for whether text messages are decoded to str (True) or kept as bytes (False)\n# Covariant because it only affects return types, not input types\n_DecodeText = TypeVar(\"_DecodeText\", bound=bool, covariant=True, default=Literal[True])\n\n\n@frozen_dataclass_decorator\nclass ClientWSTimeout:\n    ws_receive: float | None = None\n    ws_close: float | None = None\n\n\nDEFAULT_WS_CLIENT_TIMEOUT: Final[ClientWSTimeout] = ClientWSTimeout(\n    ws_receive=None, ws_close=10.0\n)\n\n\nclass ClientWebSocketResponse(Generic[_DecodeText]):\n    def __init__(\n        self,\n        reader: WebSocketDataQueue,\n        writer: WebSocketWriter,\n        protocol: str | None,\n        response: ClientResponse,\n        timeout: ClientWSTimeout,\n        autoclose: bool,\n        autoping: bool,\n        loop: asyncio.AbstractEventLoop,\n        *,\n        heartbeat: float | None = None,\n        compress: int = 0,\n        client_notakeover: bool = False,\n    ) -> None:\n        self._response = response\n        self._conn = response.connection\n\n        self._writer = writer\n        self._reader = reader\n        self._protocol = protocol\n        self._closed = False\n        self._closing = False\n        self._close_code: int | None = None\n        self._timeout = timeout\n        self._autoclose = autoclose\n        self._autoping = autoping\n        self._heartbeat = heartbeat\n        self._heartbeat_cb: asyncio.TimerHandle | None = None\n        self._heartbeat_when: float = 0.0\n        if heartbeat is not None:\n            self._pong_heartbeat = heartbeat / 2.0\n        self._pong_response_cb: asyncio.TimerHandle | None = None\n        self._loop = loop\n        self._waiting: bool = False\n        self._close_wait: asyncio.Future[None] | None = None\n        self._exception: BaseException | None = None\n        self._compress = compress\n        self._client_notakeover = client_notakeover\n        self._ping_task: asyncio.Task[None] | None = None\n        self._need_heartbeat_reset = False\n        self._heartbeat_reset_handle: asyncio.Handle | None = None\n\n        self._reset_heartbeat()\n\n    def _cancel_heartbeat(self) -> None:\n        self._cancel_pong_response_cb()\n        if self._heartbeat_reset_handle is not None:\n            self._heartbeat_reset_handle.cancel()\n            self._heartbeat_reset_handle = None\n        self._need_heartbeat_reset = False\n        if self._heartbeat_cb is not None:\n            self._heartbeat_cb.cancel()\n            self._heartbeat_cb = None\n        if self._ping_task is not None:\n            self._ping_task.cancel()\n            self._ping_task = None\n\n    def _cancel_pong_response_cb(self) -> None:\n        if self._pong_response_cb is not None:\n            self._pong_response_cb.cancel()\n            self._pong_response_cb = None\n\n    def _on_data_received(self) -> None:\n        if self._heartbeat is None or self._need_heartbeat_reset:\n            return\n        loop = self._loop\n        assert loop is not None\n        # Coalesce multiple chunks received in the same loop tick into a single\n        # heartbeat reset. Resetting immediately per chunk increases timer churn.\n        self._need_heartbeat_reset = True\n        self._heartbeat_reset_handle = loop.call_soon(self._flush_heartbeat_reset)\n\n    def _flush_heartbeat_reset(self) -> None:\n        self._heartbeat_reset_handle = None\n        if not self._need_heartbeat_reset:\n            return\n        self._reset_heartbeat()\n        self._need_heartbeat_reset = False\n\n    def _reset_heartbeat(self) -> None:\n        if self._heartbeat is None:\n            return\n        self._cancel_pong_response_cb()\n        loop = self._loop\n        assert loop is not None\n        conn = self._conn\n        timeout_ceil_threshold = (\n            conn._connector._timeout_ceil_threshold if conn is not None else 5\n        )\n        now = loop.time()\n        when = calculate_timeout_when(now, self._heartbeat, timeout_ceil_threshold)\n        self._heartbeat_when = when\n        if self._heartbeat_cb is None:\n            # We do not cancel the previous heartbeat_cb here because\n            # it generates a significant amount of TimerHandle churn\n            # which causes asyncio to rebuild the heap frequently.\n            # Instead _send_heartbeat() will reschedule the next\n            # heartbeat if it fires too early.\n            self._heartbeat_cb = loop.call_at(when, self._send_heartbeat)\n\n    def _send_heartbeat(self) -> None:\n        self._heartbeat_cb = None\n\n        # If heartbeat reset is pending (data is being received), skip sending\n        # the ping and let the reset callback handle rescheduling the heartbeat.\n        if self._need_heartbeat_reset:\n            return\n\n        loop = self._loop\n        now = loop.time()\n        if now < self._heartbeat_when:\n            # Heartbeat fired too early, reschedule\n            self._heartbeat_cb = loop.call_at(\n                self._heartbeat_when, self._send_heartbeat\n            )\n            return\n\n        conn = self._conn\n        timeout_ceil_threshold = (\n            conn._connector._timeout_ceil_threshold if conn is not None else 5\n        )\n        when = calculate_timeout_when(now, self._pong_heartbeat, timeout_ceil_threshold)\n        self._cancel_pong_response_cb()\n        self._pong_response_cb = loop.call_at(when, self._pong_not_received)\n\n        coro = self._writer.send_frame(b\"\", WSMsgType.PING)\n        if sys.version_info >= (3, 12):\n            # Optimization for Python 3.12, try to send the ping\n            # immediately to avoid having to schedule\n            # the task on the event loop.\n            ping_task = asyncio.Task(coro, loop=loop, eager_start=True)\n        else:\n            ping_task = loop.create_task(coro)\n\n        if not ping_task.done():\n            self._ping_task = ping_task\n            ping_task.add_done_callback(self._ping_task_done)\n        else:\n            self._ping_task_done(ping_task)\n\n    def _ping_task_done(self, task: \"asyncio.Task[None]\") -> None:\n        \"\"\"Callback for when the ping task completes.\"\"\"\n        if not task.cancelled() and (exc := task.exception()):\n            self._handle_ping_pong_exception(exc)\n        self._ping_task = None\n\n    def _pong_not_received(self) -> None:\n        self._handle_ping_pong_exception(\n            ServerTimeoutError(f\"No PONG received after {self._pong_heartbeat} seconds\")\n        )\n\n    def _handle_ping_pong_exception(self, exc: BaseException) -> None:\n        \"\"\"Handle exceptions raised during ping/pong processing.\"\"\"\n        if self._closed:\n            return\n        self._set_closed()\n        self._close_code = WSCloseCode.ABNORMAL_CLOSURE\n        self._exception = exc\n        self._response.close()\n        if self._waiting and not self._closing:\n            self._reader.feed_data(WSMessageError(data=exc, extra=None))\n\n    def _set_closed(self) -> None:\n        \"\"\"Set the connection to closed.\n\n        Cancel any heartbeat timers and set the closed flag.\n        \"\"\"\n        self._closed = True\n        self._cancel_heartbeat()\n\n    def _set_closing(self) -> None:\n        \"\"\"Set the connection to closing.\n\n        Cancel any heartbeat timers and set the closing flag.\n        \"\"\"\n        self._closing = True\n        self._cancel_heartbeat()\n\n    @property\n    def closed(self) -> bool:\n        return self._closed\n\n    @property\n    def close_code(self) -> int | None:\n        return self._close_code\n\n    @property\n    def protocol(self) -> str | None:\n        return self._protocol\n\n    @property\n    def compress(self) -> int:\n        return self._compress\n\n    @property\n    def client_notakeover(self) -> bool:\n        return self._client_notakeover\n\n    def get_extra_info(self, name: str, default: Any = None) -> Any:\n        \"\"\"extra info from connection transport\"\"\"\n        conn = self._response.connection\n        if conn is None:\n            return default\n        transport = conn.transport\n        if transport is None:\n            return default\n        return transport.get_extra_info(name, default)\n\n    def exception(self) -> BaseException | None:\n        return self._exception\n\n    async def ping(self, message: bytes = b\"\") -> None:\n        await self._writer.send_frame(message, WSMsgType.PING)\n\n    async def pong(self, message: bytes = b\"\") -> None:\n        await self._writer.send_frame(message, WSMsgType.PONG)\n\n    async def send_frame(\n        self, message: bytes, opcode: WSMsgType, compress: int | None = None\n    ) -> None:\n        \"\"\"Send a frame over the websocket.\"\"\"\n        await self._writer.send_frame(message, opcode, compress)\n\n    async def send_str(self, data: str, compress: int | None = None) -> None:\n        if not isinstance(data, str):\n            raise TypeError(\"data argument must be str (%r)\" % type(data))\n        await self._writer.send_frame(\n            data.encode(\"utf-8\"), WSMsgType.TEXT, compress=compress\n        )\n\n    async def send_bytes(self, data: bytes, compress: int | None = None) -> None:\n        if not isinstance(data, (bytes, bytearray, memoryview)):\n            raise TypeError(\"data argument must be byte-ish (%r)\" % type(data))\n        await self._writer.send_frame(data, WSMsgType.BINARY, compress=compress)\n\n    async def send_json(\n        self,\n        data: Any,\n        compress: int | None = None,\n        *,\n        dumps: JSONEncoder = DEFAULT_JSON_ENCODER,\n    ) -> None:\n        await self.send_str(dumps(data), compress=compress)\n\n    async def send_json_bytes(\n        self,\n        data: Any,\n        compress: int | None = None,\n        *,\n        dumps: JSONBytesEncoder,\n    ) -> None:\n        \"\"\"Send JSON data using a bytes-returning encoder as a binary frame.\n\n        Use this when your JSON encoder (like orjson) returns bytes\n        instead of str, avoiding the encode/decode overhead.\n        \"\"\"\n        await self.send_bytes(dumps(data), compress=compress)\n\n    async def close(self, *, code: int = WSCloseCode.OK, message: bytes = b\"\") -> bool:\n        # we need to break `receive()` cycle first,\n        # `close()` may be called from different task\n        if self._waiting and not self._closing:\n            assert self._loop is not None\n            self._close_wait = self._loop.create_future()\n            self._set_closing()\n            self._reader.feed_data(WS_CLOSING_MESSAGE)\n            await self._close_wait\n\n        if self._closed:\n            return False\n\n        self._set_closed()\n        try:\n            await self._writer.close(code, message)\n        except asyncio.CancelledError:\n            self._close_code = WSCloseCode.ABNORMAL_CLOSURE\n            self._response.close()\n            raise\n        except Exception as exc:\n            self._close_code = WSCloseCode.ABNORMAL_CLOSURE\n            self._exception = exc\n            self._response.close()\n            return True\n\n        if self._close_code:\n            self._response.close()\n            return True\n\n        while True:\n            try:\n                async with async_timeout.timeout(self._timeout.ws_close):\n                    msg = await self._reader.read()\n            except asyncio.CancelledError:\n                self._close_code = WSCloseCode.ABNORMAL_CLOSURE\n                self._response.close()\n                raise\n            except Exception as exc:\n                self._close_code = WSCloseCode.ABNORMAL_CLOSURE\n                self._exception = exc\n                self._response.close()\n                return True\n\n            if msg.type is WSMsgType.CLOSE:\n                self._close_code = msg.data\n                self._response.close()\n                return True\n\n    @overload\n    async def receive(\n        self: \"ClientWebSocketResponse[Literal[True]]\", timeout: float | None = None\n    ) -> WSMessageDecodeText: ...\n\n    @overload\n    async def receive(\n        self: \"ClientWebSocketResponse[Literal[False]]\", timeout: float | None = None\n    ) -> WSMessageNoDecodeText: ...\n\n    @overload\n    async def receive(\n        self: \"ClientWebSocketResponse[_DecodeText]\", timeout: float | None = None\n    ) -> WSMessageDecodeText | WSMessageNoDecodeText: ...\n\n    async def receive(\n        self, timeout: float | None = None\n    ) -> WSMessageDecodeText | WSMessageNoDecodeText:\n        receive_timeout = timeout or self._timeout.ws_receive\n\n        while True:\n            if self._waiting:\n                raise RuntimeError(\"Concurrent call to receive() is not allowed\")\n\n            if self._closed:\n                return WS_CLOSED_MESSAGE\n            elif self._closing:\n                await self.close()\n                return WS_CLOSED_MESSAGE\n\n            try:\n                self._waiting = True\n                try:\n                    if receive_timeout:\n                        # Entering the context manager and creating\n                        # Timeout() object can take almost 50% of the\n                        # run time in this loop so we avoid it if\n                        # there is no read timeout.\n                        async with async_timeout.timeout(receive_timeout):\n                            msg = await self._reader.read()\n                    else:\n                        msg = await self._reader.read()\n                finally:\n                    self._waiting = False\n                    if self._close_wait:\n                        set_result(self._close_wait, None)\n            except (asyncio.CancelledError, asyncio.TimeoutError):\n                self._close_code = WSCloseCode.ABNORMAL_CLOSURE\n                raise\n            except EofStream:\n                self._close_code = WSCloseCode.OK\n                await self.close()\n                return WS_CLOSED_MESSAGE\n            except ClientError:\n                # Likely ServerDisconnectedError when connection is lost\n                self._set_closed()\n                self._close_code = WSCloseCode.ABNORMAL_CLOSURE\n                return WS_CLOSED_MESSAGE\n            except WebSocketError as exc:\n                self._close_code = exc.code\n                await self.close(code=exc.code)\n                return WSMessageError(data=exc)\n            except Exception as exc:\n                self._exception = exc\n                self._set_closing()\n                self._close_code = WSCloseCode.ABNORMAL_CLOSURE\n                await self.close()\n                return WSMessageError(data=exc)\n\n            if msg.type not in _INTERNAL_RECEIVE_TYPES:\n                # If its not a close/closing/ping/pong message\n                # we can return it immediately\n                return msg\n\n            if msg.type is WSMsgType.CLOSE:\n                self._set_closing()\n                self._close_code = msg.data\n                # Could be closed elsewhere while awaiting reader\n                if not self._closed and self._autoclose:  # type: ignore[redundant-expr]\n                    await self.close()\n            elif msg.type is WSMsgType.CLOSING:\n                self._set_closing()\n            elif msg.type is WSMsgType.PING and self._autoping:\n                await self.pong(msg.data)\n                continue\n            elif msg.type is WSMsgType.PONG and self._autoping:\n                continue\n\n            return msg\n\n    @overload\n    async def receive_str(\n        self: \"ClientWebSocketResponse[Literal[True]]\", *, timeout: float | None = None\n    ) -> str: ...\n\n    @overload\n    async def receive_str(\n        self: \"ClientWebSocketResponse[Literal[False]]\", *, timeout: float | None = None\n    ) -> bytes: ...\n\n    @overload\n    async def receive_str(\n        self: \"ClientWebSocketResponse[_DecodeText]\", *, timeout: float | None = None\n    ) -> str | bytes: ...\n\n    async def receive_str(self, *, timeout: float | None = None) -> str | bytes:\n        \"\"\"Receive TEXT message.\n\n        Returns str when decode_text=True (default), bytes when decode_text=False.\n        \"\"\"\n        msg = await self.receive(timeout)\n        if msg.type is not WSMsgType.TEXT:\n            raise WSMessageTypeError(\n                f\"Received message {msg.type}:{msg.data!r} is not WSMsgType.TEXT\"\n            )\n        return msg.data\n\n    async def receive_bytes(self, *, timeout: float | None = None) -> bytes:\n        msg = await self.receive(timeout)\n        if msg.type is not WSMsgType.BINARY:\n            raise WSMessageTypeError(\n                f\"Received message {msg.type}:{msg.data!r} is not WSMsgType.BINARY\"\n            )\n        return msg.data\n\n    @overload\n    async def receive_json(\n        self: \"ClientWebSocketResponse[Literal[True]]\",\n        *,\n        loads: JSONDecoder = ...,\n        timeout: float | None = None,\n    ) -> Any: ...\n\n    @overload\n    async def receive_json(\n        self: \"ClientWebSocketResponse[Literal[False]]\",\n        *,\n        loads: Callable[[bytes], Any] = ...,\n        timeout: float | None = None,\n    ) -> Any: ...\n\n    @overload\n    async def receive_json(\n        self: \"ClientWebSocketResponse[_DecodeText]\",\n        *,\n        loads: JSONDecoder | Callable[[bytes], Any] = ...,\n        timeout: float | None = None,\n    ) -> Any: ...\n\n    async def receive_json(\n        self,\n        *,\n        loads: JSONDecoder | Callable[[bytes], Any] = DEFAULT_JSON_DECODER,\n        timeout: float | None = None,\n    ) -> Any:\n        data = await self.receive_str(timeout=timeout)\n        return loads(data)  # type: ignore[arg-type]\n\n    def __aiter__(self) -> Self:\n        return self\n\n    @overload\n    async def __anext__(\n        self: \"ClientWebSocketResponse[Literal[True]]\",\n    ) -> WSMessageDecodeText: ...\n\n    @overload\n    async def __anext__(\n        self: \"ClientWebSocketResponse[Literal[False]]\",\n    ) -> WSMessageNoDecodeText: ...\n\n    @overload\n    async def __anext__(\n        self: \"ClientWebSocketResponse[_DecodeText]\",\n    ) -> WSMessageDecodeText | WSMessageNoDecodeText: ...\n\n    async def __anext__(self) -> WSMessageDecodeText | WSMessageNoDecodeText:\n        msg = await self.receive()\n        if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED):\n            raise StopAsyncIteration\n        return msg\n\n    async def __aenter__(self) -> Self:\n        return self\n\n    async def __aexit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc_val: BaseException | None,\n        exc_tb: TracebackType | None,\n    ) -> None:\n        await self.close()\n"
  },
  {
    "path": "aiohttp/compression_utils.py",
    "content": "import asyncio\nimport sys\nimport zlib\nfrom abc import ABC, abstractmethod\nfrom concurrent.futures import Executor\nfrom typing import Any, Final, Protocol, TypedDict, cast\n\nif sys.version_info >= (3, 12):\n    from collections.abc import Buffer\nelse:\n    from typing import Union\n\n    Buffer = Union[bytes, bytearray, \"memoryview[int]\", \"memoryview[bytes]\"]\n\ntry:\n    try:\n        import brotlicffi as brotli\n    except ImportError:\n        import brotli\n\n    HAS_BROTLI = True\nexcept ImportError:\n    HAS_BROTLI = False\n\ntry:\n    if sys.version_info >= (3, 14):\n        from compression.zstd import ZstdDecompressor  # noqa: I900\n    else:  # TODO(PY314): Remove mentions of backports.zstd across codebase\n        from backports.zstd import ZstdDecompressor\n\n    HAS_ZSTD = True\nexcept ImportError:\n    HAS_ZSTD = False\n\n\nMAX_SYNC_CHUNK_SIZE = 4096\nDEFAULT_MAX_DECOMPRESS_SIZE = 2**25  # 32MiB\n\n# Unlimited decompression constants - different libraries use different conventions\nZLIB_MAX_LENGTH_UNLIMITED = 0  # zlib uses 0 to mean unlimited\nZSTD_MAX_LENGTH_UNLIMITED = -1  # zstd uses -1 to mean unlimited\n\n\nclass ZLibCompressObjProtocol(Protocol):\n    def compress(self, data: Buffer) -> bytes: ...\n    def flush(self, mode: int = ..., /) -> bytes: ...\n\n\nclass ZLibDecompressObjProtocol(Protocol):\n    def decompress(self, data: Buffer, max_length: int = ...) -> bytes: ...\n    def flush(self, length: int = ..., /) -> bytes: ...\n\n    @property\n    def eof(self) -> bool: ...\n\n\nclass ZLibBackendProtocol(Protocol):\n    MAX_WBITS: int\n    Z_FULL_FLUSH: int\n    Z_SYNC_FLUSH: int\n    Z_BEST_SPEED: int\n    Z_FINISH: int\n\n    def compressobj(\n        self,\n        level: int = ...,\n        method: int = ...,\n        wbits: int = ...,\n        memLevel: int = ...,\n        strategy: int = ...,\n        zdict: Buffer | None = ...,\n    ) -> ZLibCompressObjProtocol: ...\n    def decompressobj(\n        self, wbits: int = ..., zdict: Buffer = ...\n    ) -> ZLibDecompressObjProtocol: ...\n\n    def compress(\n        self, data: Buffer, /, level: int = ..., wbits: int = ...\n    ) -> bytes: ...\n    def decompress(\n        self, data: Buffer, /, wbits: int = ..., bufsize: int = ...\n    ) -> bytes: ...\n\n\nclass CompressObjArgs(TypedDict, total=False):\n    wbits: int\n    strategy: int\n    level: int\n\n\nclass ZLibBackendWrapper:\n    def __init__(self, _zlib_backend: ZLibBackendProtocol):\n        self._zlib_backend: ZLibBackendProtocol = _zlib_backend\n\n    @property\n    def name(self) -> str:\n        return getattr(self._zlib_backend, \"__name__\", \"undefined\")\n\n    @property\n    def MAX_WBITS(self) -> int:\n        return self._zlib_backend.MAX_WBITS\n\n    @property\n    def Z_FULL_FLUSH(self) -> int:\n        return self._zlib_backend.Z_FULL_FLUSH\n\n    @property\n    def Z_SYNC_FLUSH(self) -> int:\n        return self._zlib_backend.Z_SYNC_FLUSH\n\n    @property\n    def Z_BEST_SPEED(self) -> int:\n        return self._zlib_backend.Z_BEST_SPEED\n\n    @property\n    def Z_FINISH(self) -> int:\n        return self._zlib_backend.Z_FINISH\n\n    def compressobj(self, *args: Any, **kwargs: Any) -> ZLibCompressObjProtocol:\n        return self._zlib_backend.compressobj(*args, **kwargs)\n\n    def decompressobj(self, *args: Any, **kwargs: Any) -> ZLibDecompressObjProtocol:\n        return self._zlib_backend.decompressobj(*args, **kwargs)\n\n    def compress(self, data: Buffer, *args: Any, **kwargs: Any) -> bytes:\n        return self._zlib_backend.compress(data, *args, **kwargs)\n\n    def decompress(self, data: Buffer, *args: Any, **kwargs: Any) -> bytes:\n        return self._zlib_backend.decompress(data, *args, **kwargs)\n\n    # Everything not explicitly listed in the Protocol we just pass through\n    def __getattr__(self, attrname: str) -> Any:\n        return getattr(self._zlib_backend, attrname)\n\n\nZLibBackend: ZLibBackendWrapper = ZLibBackendWrapper(zlib)\n\n\ndef set_zlib_backend(new_zlib_backend: ZLibBackendProtocol) -> None:\n    ZLibBackend._zlib_backend = new_zlib_backend\n\n\ndef encoding_to_mode(\n    encoding: str | None = None,\n    suppress_deflate_header: bool = False,\n) -> int:\n    if encoding == \"gzip\":\n        return 16 + ZLibBackend.MAX_WBITS\n\n    return -ZLibBackend.MAX_WBITS if suppress_deflate_header else ZLibBackend.MAX_WBITS\n\n\nclass DecompressionBaseHandler(ABC):\n    def __init__(\n        self,\n        executor: Executor | None = None,\n        max_sync_chunk_size: int | None = MAX_SYNC_CHUNK_SIZE,\n    ):\n        \"\"\"Base class for decompression handlers.\"\"\"\n        self._executor = executor\n        self._max_sync_chunk_size = max_sync_chunk_size\n\n    @abstractmethod\n    def decompress_sync(\n        self, data: bytes, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED\n    ) -> bytes:\n        \"\"\"Decompress the given data.\"\"\"\n\n    async def decompress(\n        self, data: bytes, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED\n    ) -> bytes:\n        \"\"\"Decompress the given data.\"\"\"\n        if (\n            self._max_sync_chunk_size is not None\n            and len(data) > self._max_sync_chunk_size\n        ):\n            return await asyncio.get_event_loop().run_in_executor(\n                self._executor, self.decompress_sync, data, max_length\n            )\n        return self.decompress_sync(data, max_length)\n\n\nclass ZLibCompressor:\n    def __init__(\n        self,\n        encoding: str | None = None,\n        suppress_deflate_header: bool = False,\n        level: int | None = None,\n        wbits: int | None = None,\n        strategy: int | None = None,\n        executor: Executor | None = None,\n        max_sync_chunk_size: int | None = MAX_SYNC_CHUNK_SIZE,\n    ):\n        self._executor = executor\n        self._max_sync_chunk_size = max_sync_chunk_size\n        self._mode = (\n            encoding_to_mode(encoding, suppress_deflate_header)\n            if wbits is None\n            else wbits\n        )\n        self._zlib_backend: Final = ZLibBackendWrapper(ZLibBackend._zlib_backend)\n\n        kwargs: CompressObjArgs = {}\n        kwargs[\"wbits\"] = self._mode\n        if strategy is not None:\n            kwargs[\"strategy\"] = strategy\n        if level is not None:\n            kwargs[\"level\"] = level\n        self._compressor = self._zlib_backend.compressobj(**kwargs)\n\n    def compress_sync(self, data: Buffer) -> bytes:\n        return self._compressor.compress(data)\n\n    async def compress(self, data: Buffer) -> bytes:\n        \"\"\"Compress the data and returned the compressed bytes.\n\n        Note that flush() must be called after the last call to compress()\n\n        If the data size is large than the max_sync_chunk_size, the compression\n        will be done in the executor. Otherwise, the compression will be done\n        in the event loop.\n\n        **WARNING: This method is NOT cancellation-safe when used with flush().**\n        If this operation is cancelled, the compressor state may be corrupted.\n        The connection MUST be closed after cancellation to avoid data corruption\n        in subsequent compress operations.\n\n        For cancellation-safe compression (e.g., WebSocket), the caller MUST wrap\n        compress() + flush() + send operations in a shield and lock to ensure atomicity.\n        \"\"\"\n        # For large payloads, offload compression to executor to avoid blocking event loop\n        should_use_executor = (\n            self._max_sync_chunk_size is not None\n            and len(data) > self._max_sync_chunk_size\n        )\n        if should_use_executor:\n            return await asyncio.get_running_loop().run_in_executor(\n                self._executor, self._compressor.compress, data\n            )\n        return self.compress_sync(data)\n\n    def flush(self, mode: int | None = None) -> bytes:\n        \"\"\"Flush the compressor synchronously.\n\n        **WARNING: This method is NOT cancellation-safe when called after compress().**\n        The flush() operation accesses shared compressor state. If compress() was\n        cancelled, calling flush() may result in corrupted data. The connection MUST\n        be closed after compress() cancellation.\n\n        For cancellation-safe compression (e.g., WebSocket), the caller MUST wrap\n        compress() + flush() + send operations in a shield and lock to ensure atomicity.\n        \"\"\"\n        return self._compressor.flush(\n            mode if mode is not None else self._zlib_backend.Z_FINISH\n        )\n\n\nclass ZLibDecompressor(DecompressionBaseHandler):\n    def __init__(\n        self,\n        encoding: str | None = None,\n        suppress_deflate_header: bool = False,\n        executor: Executor | None = None,\n        max_sync_chunk_size: int | None = MAX_SYNC_CHUNK_SIZE,\n    ):\n        super().__init__(executor=executor, max_sync_chunk_size=max_sync_chunk_size)\n        self._mode = encoding_to_mode(encoding, suppress_deflate_header)\n        self._zlib_backend: Final = ZLibBackendWrapper(ZLibBackend._zlib_backend)\n        self._decompressor = self._zlib_backend.decompressobj(wbits=self._mode)\n\n    def decompress_sync(\n        self, data: Buffer, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED\n    ) -> bytes:\n        return self._decompressor.decompress(data, max_length)\n\n    def flush(self, length: int = 0) -> bytes:\n        return (\n            self._decompressor.flush(length)\n            if length > 0\n            else self._decompressor.flush()\n        )\n\n    @property\n    def eof(self) -> bool:\n        return self._decompressor.eof\n\n\nclass BrotliDecompressor(DecompressionBaseHandler):\n    # Supports both 'brotlipy' and 'Brotli' packages\n    # since they share an import name. The top branches\n    # are for 'brotlipy' and bottom branches for 'Brotli'\n    def __init__(\n        self,\n        executor: Executor | None = None,\n        max_sync_chunk_size: int | None = MAX_SYNC_CHUNK_SIZE,\n    ) -> None:\n        \"\"\"Decompress data using the Brotli library.\"\"\"\n        if not HAS_BROTLI:\n            raise RuntimeError(\n                \"The brotli decompression is not available. \"\n                \"Please install `Brotli` module\"\n            )\n        self._obj = brotli.Decompressor()\n        super().__init__(executor=executor, max_sync_chunk_size=max_sync_chunk_size)\n\n    def decompress_sync(\n        self, data: Buffer, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED\n    ) -> bytes:\n        \"\"\"Decompress the given data.\"\"\"\n        if hasattr(self._obj, \"decompress\"):\n            return cast(bytes, self._obj.decompress(data, max_length))\n        return cast(bytes, self._obj.process(data, max_length))\n\n    def flush(self) -> bytes:\n        \"\"\"Flush the decompressor.\"\"\"\n        if hasattr(self._obj, \"flush\"):\n            return cast(bytes, self._obj.flush())\n        return b\"\"\n\n\nclass ZSTDDecompressor(DecompressionBaseHandler):\n    def __init__(\n        self,\n        executor: Executor | None = None,\n        max_sync_chunk_size: int | None = MAX_SYNC_CHUNK_SIZE,\n    ) -> None:\n        if not HAS_ZSTD:\n            raise RuntimeError(\n                \"The zstd decompression is not available. \"\n                \"Please install `backports.zstd` module\"\n            )\n        self._obj = ZstdDecompressor()\n        super().__init__(executor=executor, max_sync_chunk_size=max_sync_chunk_size)\n\n    def decompress_sync(\n        self, data: bytes, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED\n    ) -> bytes:\n        # zstd uses -1 for unlimited, while zlib uses 0 for unlimited\n        # Convert the zlib convention (0=unlimited) to zstd convention (-1=unlimited)\n        zstd_max_length = (\n            ZSTD_MAX_LENGTH_UNLIMITED\n            if max_length == ZLIB_MAX_LENGTH_UNLIMITED\n            else max_length\n        )\n        return self._obj.decompress(data, zstd_max_length)\n\n    def flush(self) -> bytes:\n        return b\"\"\n"
  },
  {
    "path": "aiohttp/connector.py",
    "content": "import asyncio\nimport functools\nimport random\nimport socket\nimport sys\nimport traceback\nimport warnings\nfrom collections import OrderedDict, defaultdict, deque\nfrom collections.abc import Awaitable, Callable, Iterator, Sequence\nfrom contextlib import suppress\nfrom http import HTTPStatus\nfrom itertools import chain, cycle, islice\nfrom time import monotonic\nfrom types import TracebackType\nfrom typing import TYPE_CHECKING, Any, Literal, cast\n\nimport aiohappyeyeballs\nfrom aiohappyeyeballs import AddrInfoType, SocketFactoryType\nfrom multidict import CIMultiDict\n\nfrom . import hdrs, helpers\nfrom .abc import AbstractResolver, ResolveResult\nfrom .client_exceptions import (\n    ClientConnectionError,\n    ClientConnectorCertificateError,\n    ClientConnectorDNSError,\n    ClientConnectorError,\n    ClientConnectorSSLError,\n    ClientHttpProxyError,\n    ClientProxyConnectionError,\n    ServerFingerprintMismatch,\n    UnixClientConnectorError,\n    cert_errors,\n    ssl_errors,\n)\nfrom .client_proto import ResponseHandler\nfrom .client_reqrep import (\n    SSL_ALLOWED_TYPES,\n    ClientRequest,\n    ClientRequestBase,\n    Fingerprint,\n)\nfrom .helpers import (\n    _SENTINEL,\n    ceil_timeout,\n    is_ip_address,\n    sentinel,\n    set_exception,\n    set_result,\n)\nfrom .log import client_logger\nfrom .resolver import DefaultResolver\n\nif sys.version_info >= (3, 12):\n    from collections.abc import Buffer\nelse:\n    Buffer = \"bytes | bytearray | memoryview[int] | memoryview[bytes]\"\n\ntry:\n    import ssl\n\n    SSLContext = ssl.SSLContext\nexcept ImportError:  # pragma: no cover\n    ssl = None  # type: ignore[assignment]\n    SSLContext = object  # type: ignore[misc,assignment]\n\nEMPTY_SCHEMA_SET = frozenset({\"\"})\nHTTP_SCHEMA_SET = frozenset({\"http\", \"https\"})\nWS_SCHEMA_SET = frozenset({\"ws\", \"wss\"})\n\nHTTP_AND_EMPTY_SCHEMA_SET = HTTP_SCHEMA_SET | EMPTY_SCHEMA_SET\nHIGH_LEVEL_SCHEMA_SET = HTTP_AND_EMPTY_SCHEMA_SET | WS_SCHEMA_SET\n\nNEEDS_CLEANUP_CLOSED = (3, 13, 0) <= sys.version_info < (\n    3,\n    13,\n    1,\n) or sys.version_info < (3, 12, 8)\n# Cleanup closed is no longer needed after https://github.com/python/cpython/pull/118960\n# which first appeared in Python 3.12.8 and 3.13.1\n\n\n__all__ = (\n    \"BaseConnector\",\n    \"TCPConnector\",\n    \"UnixConnector\",\n    \"NamedPipeConnector\",\n    \"AddrInfoType\",\n    \"SocketFactoryType\",\n)\n\n\nif TYPE_CHECKING:\n    from .client import ClientTimeout\n    from .client_reqrep import ConnectionKey\n    from .tracing import Trace\n\n\nclass Connection:\n    \"\"\"Represents a single connection.\"\"\"\n\n    __slots__ = (\n        \"_key\",\n        \"_connector\",\n        \"_loop\",\n        \"_protocol\",\n        \"_callbacks\",\n        \"_source_traceback\",\n    )\n\n    def __init__(\n        self,\n        connector: \"BaseConnector\",\n        key: \"ConnectionKey\",\n        protocol: ResponseHandler,\n        loop: asyncio.AbstractEventLoop,\n    ) -> None:\n        self._key = key\n        self._connector = connector\n        self._loop = loop\n        self._protocol: ResponseHandler | None = protocol\n        self._callbacks: list[Callable[[], None]] = []\n        self._source_traceback = (\n            traceback.extract_stack(sys._getframe(1)) if loop.get_debug() else None\n        )\n\n    def __repr__(self) -> str:\n        return f\"Connection<{self._key}>\"\n\n    def __del__(self, _warnings: Any = warnings) -> None:\n        if self._protocol is not None:\n            _warnings.warn(\n                f\"Unclosed connection {self!r}\", ResourceWarning, source=self\n            )\n            if self._loop.is_closed():\n                return\n\n            self._connector._release(self._key, self._protocol, should_close=True)\n\n            context = {\"client_connection\": self, \"message\": \"Unclosed connection\"}\n            if self._source_traceback is not None:\n                context[\"source_traceback\"] = self._source_traceback\n            self._loop.call_exception_handler(context)\n\n    def __bool__(self) -> Literal[True]:\n        \"\"\"Force subclasses to not be falsy, to make checks simpler.\"\"\"\n        return True\n\n    @property\n    def transport(self) -> asyncio.Transport | None:\n        if self._protocol is None:\n            return None\n        return self._protocol.transport\n\n    @property\n    def protocol(self) -> ResponseHandler | None:\n        return self._protocol\n\n    def add_callback(self, callback: Callable[[], None]) -> None:\n        if callback is not None:\n            self._callbacks.append(callback)\n\n    def _notify_release(self) -> None:\n        callbacks, self._callbacks = self._callbacks[:], []\n\n        for cb in callbacks:\n            with suppress(Exception):\n                cb()\n\n    def close(self) -> None:\n        self._notify_release()\n\n        if self._protocol is not None:\n            self._connector._release(self._key, self._protocol, should_close=True)\n            self._protocol = None\n\n    def release(self) -> None:\n        self._notify_release()\n\n        if self._protocol is not None:\n            self._connector._release(self._key, self._protocol)\n            self._protocol = None\n\n    @property\n    def closed(self) -> bool:\n        return self._protocol is None or not self._protocol.is_connected()\n\n\nclass _ConnectTunnelConnection(Connection):\n    \"\"\"Special connection wrapper for CONNECT tunnels that must never be pooled.\n\n    This connection wraps the proxy connection that will be upgraded with TLS.\n    It must never be released to the pool because:\n    1. Its 'closed' future will never complete, causing session.close() to hang\n    2. It represents an intermediate state, not a reusable connection\n    3. The real connection (with TLS) will be created separately\n    \"\"\"\n\n    def release(self) -> None:\n        \"\"\"Do nothing - don't pool or close the connection.\n\n        These connections are an intermediate state during the CONNECT tunnel\n        setup and will be cleaned up naturally after the TLS upgrade. If they\n        were to be pooled, they would never be properly closed, causing\n        session.close() to wait forever for their 'closed' future.\n        \"\"\"\n\n\nclass _TransportPlaceholder:\n    \"\"\"placeholder for BaseConnector.connect function\"\"\"\n\n    __slots__ = (\"closed\", \"transport\")\n\n    def __init__(self, closed_future: asyncio.Future[Exception | None]) -> None:\n        \"\"\"Initialize a placeholder for a transport.\"\"\"\n        self.closed = closed_future\n        self.transport = None\n\n    def close(self) -> None:\n        \"\"\"Close the placeholder.\"\"\"\n\n    def abort(self) -> None:\n        \"\"\"Abort the placeholder (does nothing).\"\"\"\n\n\nclass BaseConnector:\n    \"\"\"Base connector class.\n\n    keepalive_timeout - (optional) Keep-alive timeout.\n    force_close - Set to True to force close and do reconnect\n        after each request (and between redirects).\n    limit - The total number of simultaneous connections.\n    limit_per_host - Number of simultaneous connections to one host.\n    enable_cleanup_closed - Enables clean-up closed ssl transports.\n                            Disabled by default.\n    timeout_ceil_threshold - Trigger ceiling of timeout values when\n                             it's above timeout_ceil_threshold.\n    loop - Optional event loop.\n    \"\"\"\n\n    _closed = True  # prevent AttributeError in __del__ if ctor was failed\n    _source_traceback = None\n\n    # abort transport after 2 seconds (cleanup broken connections)\n    _cleanup_closed_period = 2.0\n\n    allowed_protocol_schema_set = HIGH_LEVEL_SCHEMA_SET\n\n    def __init__(\n        self,\n        *,\n        keepalive_timeout: _SENTINEL | None | float = sentinel,\n        force_close: bool = False,\n        limit: int = 100,\n        limit_per_host: int = 0,\n        enable_cleanup_closed: bool = False,\n        timeout_ceil_threshold: float = 5,\n    ) -> None:\n        if force_close:\n            if keepalive_timeout is not None and keepalive_timeout is not sentinel:\n                raise ValueError(\n                    \"keepalive_timeout cannot be set if force_close is True\"\n                )\n        else:\n            if keepalive_timeout is sentinel:\n                keepalive_timeout = 15.0\n\n        self._timeout_ceil_threshold = timeout_ceil_threshold\n\n        loop = asyncio.get_running_loop()\n\n        self._closed = False\n        if loop.get_debug():\n            self._source_traceback = traceback.extract_stack(sys._getframe(1))\n\n        # Connection pool of reusable connections.\n        # We use a deque to store connections because it has O(1) popleft()\n        # and O(1) append() operations to implement a FIFO queue.\n        self._conns: defaultdict[\n            ConnectionKey, deque[tuple[ResponseHandler, float]]\n        ] = defaultdict(deque)\n        self._limit = limit\n        self._limit_per_host = limit_per_host\n        self._acquired: set[ResponseHandler] = set()\n        self._acquired_per_host: defaultdict[ConnectionKey, set[ResponseHandler]] = (\n            defaultdict(set)\n        )\n        self._keepalive_timeout = cast(float, keepalive_timeout)\n        self._force_close = force_close\n\n        # {host_key: FIFO list of waiters}\n        # The FIFO is implemented with an OrderedDict with None keys because\n        # python does not have an ordered set.\n        self._waiters: defaultdict[\n            ConnectionKey, OrderedDict[asyncio.Future[None], None]\n        ] = defaultdict(OrderedDict)\n\n        self._loop = loop\n        self._factory = functools.partial(ResponseHandler, loop=loop)\n\n        # start keep-alive connection cleanup task\n        self._cleanup_handle: asyncio.TimerHandle | None = None\n\n        # start cleanup closed transports task\n        self._cleanup_closed_handle: asyncio.TimerHandle | None = None\n\n        if enable_cleanup_closed and not NEEDS_CLEANUP_CLOSED:\n            warnings.warn(\n                \"enable_cleanup_closed ignored because \"\n                \"https://github.com/python/cpython/pull/118960 is fixed \"\n                f\"in Python version {sys.version_info}\",\n                DeprecationWarning,\n                stacklevel=2,\n            )\n            enable_cleanup_closed = False\n\n        self._cleanup_closed_disabled = not enable_cleanup_closed\n        self._cleanup_closed_transports: list[asyncio.Transport | None] = []\n\n        self._placeholder_future: asyncio.Future[Exception | None] = (\n            loop.create_future()\n        )\n        self._placeholder_future.set_result(None)\n        self._cleanup_closed()\n\n    def __del__(self, _warnings: Any = warnings) -> None:\n        if self._closed:\n            return\n        if not self._conns:\n            return\n\n        conns = [repr(c) for c in self._conns.values()]\n\n        self._close_immediately()\n\n        _warnings.warn(f\"Unclosed connector {self!r}\", ResourceWarning, source=self)\n        context = {\n            \"connector\": self,\n            \"connections\": conns,\n            \"message\": \"Unclosed connector\",\n        }\n        if self._source_traceback is not None:\n            context[\"source_traceback\"] = self._source_traceback\n        self._loop.call_exception_handler(context)\n\n    async def __aenter__(self) -> \"BaseConnector\":\n        return self\n\n    async def __aexit__(\n        self,\n        exc_type: type[BaseException] | None = None,\n        exc_value: BaseException | None = None,\n        exc_traceback: TracebackType | None = None,\n    ) -> None:\n        await self.close()\n\n    @property\n    def force_close(self) -> bool:\n        \"\"\"Ultimately close connection on releasing if True.\"\"\"\n        return self._force_close\n\n    @property\n    def limit(self) -> int:\n        \"\"\"The total number for simultaneous connections.\n\n        If limit is 0 the connector has no limit.\n        The default limit size is 100.\n        \"\"\"\n        return self._limit\n\n    @property\n    def limit_per_host(self) -> int:\n        \"\"\"The limit for simultaneous connections to the same endpoint.\n\n        Endpoints are the same if they are have equal\n        (host, port, is_ssl) triple.\n        \"\"\"\n        return self._limit_per_host\n\n    def _cleanup(self) -> None:\n        \"\"\"Cleanup unused transports.\"\"\"\n        if self._cleanup_handle:\n            self._cleanup_handle.cancel()\n            # _cleanup_handle should be unset, otherwise _release() will not\n            # recreate it ever!\n            self._cleanup_handle = None\n\n        now = monotonic()\n        timeout = self._keepalive_timeout\n\n        if self._conns:\n            connections = defaultdict(deque)\n            deadline = now - timeout\n            for key, conns in self._conns.items():\n                alive: deque[tuple[ResponseHandler, float]] = deque()\n                for proto, use_time in conns:\n                    if proto.is_connected() and use_time - deadline >= 0:\n                        alive.append((proto, use_time))\n                        continue\n                    transport = proto.transport\n                    proto.close()\n                    if not self._cleanup_closed_disabled and key.is_ssl:\n                        self._cleanup_closed_transports.append(transport)\n\n                if alive:\n                    connections[key] = alive\n\n            self._conns = connections\n\n        if self._conns:\n            self._cleanup_handle = helpers.weakref_handle(\n                self,\n                \"_cleanup\",\n                timeout,\n                self._loop,\n                timeout_ceil_threshold=self._timeout_ceil_threshold,\n            )\n\n    def _cleanup_closed(self) -> None:\n        \"\"\"Double confirmation for transport close.\n\n        Some broken ssl servers may leave socket open without proper close.\n        \"\"\"\n        if self._cleanup_closed_handle:\n            self._cleanup_closed_handle.cancel()\n\n        for transport in self._cleanup_closed_transports:\n            if transport is not None:\n                transport.abort()\n\n        self._cleanup_closed_transports = []\n\n        if not self._cleanup_closed_disabled:\n            self._cleanup_closed_handle = helpers.weakref_handle(\n                self,\n                \"_cleanup_closed\",\n                self._cleanup_closed_period,\n                self._loop,\n                timeout_ceil_threshold=self._timeout_ceil_threshold,\n            )\n\n    async def close(self, *, abort_ssl: bool = False) -> None:\n        \"\"\"Close all opened transports.\n\n        :param abort_ssl: If True, SSL connections will be aborted immediately\n                         without performing the shutdown handshake. This provides\n                         faster cleanup at the cost of less graceful disconnection.\n        \"\"\"\n        waiters = self._close_immediately(abort_ssl=abort_ssl)\n        if waiters:\n            results = await asyncio.gather(*waiters, return_exceptions=True)\n            for res in results:\n                if isinstance(res, Exception):\n                    err_msg = \"Error while closing connector: \" + repr(res)\n                    client_logger.debug(err_msg)\n\n    def _close_immediately(self, *, abort_ssl: bool = False) -> list[Awaitable[object]]:\n        waiters: list[Awaitable[object]] = []\n\n        if self._closed:\n            return waiters\n\n        self._closed = True\n\n        try:\n            if self._loop.is_closed():\n                return waiters\n\n            # cancel cleanup task\n            if self._cleanup_handle:\n                self._cleanup_handle.cancel()\n\n            # cancel cleanup close task\n            if self._cleanup_closed_handle:\n                self._cleanup_closed_handle.cancel()\n\n            for data in self._conns.values():\n                for proto, _ in data:\n                    if (\n                        abort_ssl\n                        and proto.transport\n                        and proto.transport.get_extra_info(\"sslcontext\") is not None\n                    ):\n                        proto.abort()\n                    else:\n                        proto.close()\n                    if closed := proto.closed:\n                        waiters.append(closed)\n\n            for proto in self._acquired:\n                if (\n                    abort_ssl\n                    and proto.transport\n                    and proto.transport.get_extra_info(\"sslcontext\") is not None\n                ):\n                    proto.abort()\n                else:\n                    proto.close()\n                if closed := proto.closed:\n                    waiters.append(closed)\n\n            # TODO (A.Yushovskiy, 24-May-2019) collect transp. closing futures\n            for transport in self._cleanup_closed_transports:\n                if transport is not None:\n                    transport.abort()\n\n            return waiters\n\n        finally:\n            self._conns.clear()\n            self._acquired.clear()\n            for keyed_waiters in self._waiters.values():\n                for keyed_waiter in keyed_waiters:\n                    keyed_waiter.cancel()\n            self._waiters.clear()\n            self._cleanup_handle = None\n            self._cleanup_closed_transports.clear()\n            self._cleanup_closed_handle = None\n\n    @property\n    def closed(self) -> bool:\n        \"\"\"Is connector closed.\n\n        A readonly property.\n        \"\"\"\n        return self._closed\n\n    def _available_connections(self, key: \"ConnectionKey\") -> int:\n        \"\"\"\n        Return number of available connections.\n\n        The limit, limit_per_host and the connection key are taken into account.\n\n        If it returns less than 1 means that there are no connections\n        available.\n        \"\"\"\n        # check total available connections\n        # If there are no limits, this will always return 1\n        total_remain = 1\n\n        if self._limit and (total_remain := self._limit - len(self._acquired)) <= 0:\n            return total_remain\n\n        # check limit per host\n        if host_remain := self._limit_per_host:\n            if acquired := self._acquired_per_host.get(key):\n                host_remain -= len(acquired)\n            if total_remain > host_remain:\n                return host_remain\n\n        return total_remain\n\n    def _update_proxy_auth_header_and_build_proxy_req(\n        self, req: ClientRequest\n    ) -> ClientRequestBase:\n        \"\"\"Set Proxy-Authorization header for non-SSL proxy requests and builds the proxy request for SSL proxy requests.\"\"\"\n        url = req.proxy\n        assert url is not None\n        headers = req.proxy_headers or CIMultiDict[str]()\n        headers[hdrs.HOST] = req.headers[hdrs.HOST]\n        proxy_req = ClientRequestBase(\n            hdrs.METH_GET,\n            url,\n            headers=headers,\n            auth=req.proxy_auth,\n            loop=self._loop,\n            ssl=req.ssl,\n        )\n        auth = proxy_req.headers.pop(hdrs.AUTHORIZATION, None)\n        if auth is not None:\n            if not req.is_ssl():\n                req.headers[hdrs.PROXY_AUTHORIZATION] = auth\n            else:\n                proxy_req.headers[hdrs.PROXY_AUTHORIZATION] = auth\n        return proxy_req\n\n    async def connect(\n        self, req: ClientRequest, traces: list[\"Trace\"], timeout: \"ClientTimeout\"\n    ) -> Connection:\n        \"\"\"Get from pool or create new connection.\"\"\"\n        key = req.connection_key\n        if (conn := await self._get(key, traces)) is not None:\n            # If we do not have to wait and we can get a connection from the pool\n            # we can avoid the timeout ceil logic and directly return the connection\n            if req.proxy:\n                self._update_proxy_auth_header_and_build_proxy_req(req)\n            return conn\n\n        async with ceil_timeout(timeout.connect, timeout.ceil_threshold):\n            if self._available_connections(key) <= 0:\n                await self._wait_for_available_connection(key, traces)\n                if (conn := await self._get(key, traces)) is not None:\n                    if req.proxy:\n                        self._update_proxy_auth_header_and_build_proxy_req(req)\n                    return conn\n\n            placeholder = cast(\n                ResponseHandler, _TransportPlaceholder(self._placeholder_future)\n            )\n            self._acquired.add(placeholder)\n            if self._limit_per_host:\n                self._acquired_per_host[key].add(placeholder)\n\n            try:\n                # Traces are done inside the try block to ensure that the\n                # that the placeholder is still cleaned up if an exception\n                # is raised.\n                if traces:\n                    for trace in traces:\n                        await trace.send_connection_create_start()\n                proto = await self._create_connection(req, traces, timeout)\n                if traces:\n                    for trace in traces:\n                        await trace.send_connection_create_end()\n            except BaseException:\n                self._release_acquired(key, placeholder)\n                raise\n            else:\n                if self._closed:\n                    proto.close()\n                    raise ClientConnectionError(\"Connector is closed.\")\n\n        # The connection was successfully created, drop the placeholder\n        # and add the real connection to the acquired set. There should\n        # be no awaits after the proto is added to the acquired set\n        # to ensure that the connection is not left in the acquired set\n        # on cancellation.\n        self._acquired.remove(placeholder)\n        self._acquired.add(proto)\n        if self._limit_per_host:\n            acquired_per_host = self._acquired_per_host[key]\n            acquired_per_host.remove(placeholder)\n            acquired_per_host.add(proto)\n        return Connection(self, key, proto, self._loop)\n\n    async def _wait_for_available_connection(\n        self, key: \"ConnectionKey\", traces: list[\"Trace\"]\n    ) -> None:\n        \"\"\"Wait for an available connection slot.\"\"\"\n        # We loop here because there is a race between\n        # the connection limit check and the connection\n        # being acquired. If the connection is acquired\n        # between the check and the await statement, we\n        # need to loop again to check if the connection\n        # slot is still available.\n        attempts = 0\n        while True:\n            fut: asyncio.Future[None] = self._loop.create_future()\n            keyed_waiters = self._waiters[key]\n            keyed_waiters[fut] = None\n            if attempts:\n                # If we have waited before, we need to move the waiter\n                # to the front of the queue as otherwise we might get\n                # starved and hit the timeout.\n                keyed_waiters.move_to_end(fut, last=False)\n\n            try:\n                # Traces happen in the try block to ensure that the\n                # the waiter is still cleaned up if an exception is raised.\n                if traces:\n                    for trace in traces:\n                        await trace.send_connection_queued_start()\n                await fut\n                if traces:\n                    for trace in traces:\n                        await trace.send_connection_queued_end()\n            finally:\n                # pop the waiter from the queue if its still\n                # there and not already removed by _release_waiter\n                keyed_waiters.pop(fut, None)\n                if not self._waiters.get(key, True):\n                    del self._waiters[key]\n\n            if self._available_connections(key) > 0:\n                break\n            attempts += 1\n\n    async def _get(\n        self, key: \"ConnectionKey\", traces: list[\"Trace\"]\n    ) -> Connection | None:\n        \"\"\"Get next reusable connection for the key or None.\n\n        The connection will be marked as acquired.\n        \"\"\"\n        if (conns := self._conns.get(key)) is None:\n            return None\n\n        t1 = monotonic()\n        while conns:\n            proto, t0 = conns.popleft()\n            # We will we reuse the connection if its connected and\n            # the keepalive timeout has not been exceeded\n            if proto.is_connected() and t1 - t0 <= self._keepalive_timeout:\n                if not conns:\n                    # The very last connection was reclaimed: drop the key\n                    del self._conns[key]\n                self._acquired.add(proto)\n                if self._limit_per_host:\n                    self._acquired_per_host[key].add(proto)\n                if traces:\n                    for trace in traces:\n                        try:\n                            await trace.send_connection_reuseconn()\n                        except BaseException:\n                            self._release_acquired(key, proto)\n                            raise\n                return Connection(self, key, proto, self._loop)\n\n            # Connection cannot be reused, close it\n            transport = proto.transport\n            proto.close()\n            # only for SSL transports\n            if not self._cleanup_closed_disabled and key.is_ssl:\n                self._cleanup_closed_transports.append(transport)\n\n        # No more connections: drop the key\n        del self._conns[key]\n        return None\n\n    def _release_waiter(self) -> None:\n        \"\"\"\n        Iterates over all waiters until one to be released is found.\n\n        The one to be released is not finished and\n        belongs to a host that has available connections.\n        \"\"\"\n        if not self._waiters:\n            return\n\n        # Having the dict keys ordered this avoids to iterate\n        # at the same order at each call.\n        queues = list(self._waiters)\n        random.shuffle(queues)\n\n        for key in queues:\n            if self._available_connections(key) < 1:\n                continue\n\n            waiters = self._waiters[key]\n            while waiters:\n                waiter, _ = waiters.popitem(last=False)\n                if not waiter.done():\n                    waiter.set_result(None)\n                    return\n\n    def _release_acquired(self, key: \"ConnectionKey\", proto: ResponseHandler) -> None:\n        \"\"\"Release acquired connection.\"\"\"\n        if self._closed:\n            # acquired connection is already released on connector closing\n            return\n\n        self._acquired.discard(proto)\n        if self._limit_per_host and (conns := self._acquired_per_host.get(key)):\n            conns.discard(proto)\n            if not conns:\n                del self._acquired_per_host[key]\n        self._release_waiter()\n\n    def _release(\n        self,\n        key: \"ConnectionKey\",\n        protocol: ResponseHandler,\n        *,\n        should_close: bool = False,\n    ) -> None:\n        if self._closed:\n            # acquired connection is already released on connector closing\n            return\n\n        self._release_acquired(key, protocol)\n\n        if self._force_close or should_close or protocol.should_close:\n            transport = protocol.transport\n            protocol.close()\n            if key.is_ssl and not self._cleanup_closed_disabled:\n                self._cleanup_closed_transports.append(transport)\n            return\n\n        self._conns[key].append((protocol, monotonic()))\n\n        if self._cleanup_handle is None:\n            self._cleanup_handle = helpers.weakref_handle(\n                self,\n                \"_cleanup\",\n                self._keepalive_timeout,\n                self._loop,\n                timeout_ceil_threshold=self._timeout_ceil_threshold,\n            )\n\n    async def _create_connection(\n        self, req: ClientRequest, traces: list[\"Trace\"], timeout: \"ClientTimeout\"\n    ) -> ResponseHandler:\n        raise NotImplementedError()\n\n\nclass _DNSCacheTable:\n    def __init__(self, ttl: float | None = None, max_size: int = 1000) -> None:\n        self._addrs_rr: OrderedDict[\n            tuple[str, int], tuple[Iterator[ResolveResult], int]\n        ] = OrderedDict()\n        self._timestamps: dict[tuple[str, int], float] = {}\n        self._ttl = ttl\n        self._max_size = max_size\n\n    def __contains__(self, host: object) -> bool:\n        return host in self._addrs_rr\n\n    def add(self, key: tuple[str, int], addrs: list[ResolveResult]) -> None:\n        if key in self._addrs_rr:\n            self._addrs_rr.move_to_end(key)\n\n        self._addrs_rr[key] = (cycle(addrs), len(addrs))\n\n        if self._ttl is not None:\n            self._timestamps[key] = monotonic()\n\n        if len(self._addrs_rr) > self._max_size:\n            oldest_key, _ = self._addrs_rr.popitem(last=False)\n            self._timestamps.pop(oldest_key, None)\n\n    def remove(self, key: tuple[str, int]) -> None:\n        self._addrs_rr.pop(key, None)\n        self._timestamps.pop(key, None)\n\n    def clear(self) -> None:\n        self._addrs_rr.clear()\n        self._timestamps.clear()\n\n    def next_addrs(self, key: tuple[str, int]) -> list[ResolveResult]:\n        loop, length = self._addrs_rr[key]\n        addrs = list(islice(loop, length))\n        # Consume one more element to shift internal state of `cycle`\n        next(loop)\n        self._addrs_rr.move_to_end(key)\n        return addrs\n\n    def expired(self, key: tuple[str, int]) -> bool:\n        if self._ttl is None:\n            return False\n\n        return self._timestamps[key] + self._ttl < monotonic()\n\n\ndef _make_ssl_context(verified: bool) -> SSLContext:\n    \"\"\"Create SSL context.\n\n    This method is not async-friendly and should be called from a thread\n    because it will load certificates from disk and do other blocking I/O.\n    \"\"\"\n    if ssl is None:\n        # No ssl support\n        return None  # type: ignore[unreachable]\n    if verified:\n        sslcontext = ssl.create_default_context()\n    else:\n        sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\n        sslcontext.options |= ssl.OP_NO_SSLv2\n        sslcontext.options |= ssl.OP_NO_SSLv3\n        sslcontext.check_hostname = False\n        sslcontext.verify_mode = ssl.CERT_NONE\n        sslcontext.options |= ssl.OP_NO_COMPRESSION\n        sslcontext.set_default_verify_paths()\n    sslcontext.set_alpn_protocols((\"http/1.1\",))\n    return sslcontext\n\n\n# The default SSLContext objects are created at import time\n# since they do blocking I/O to load certificates from disk,\n# and imports should always be done before the event loop starts\n# or in a thread.\n_SSL_CONTEXT_VERIFIED = _make_ssl_context(True)\n_SSL_CONTEXT_UNVERIFIED = _make_ssl_context(False)\n\n\nclass TCPConnector(BaseConnector):\n    \"\"\"TCP connector.\n\n    verify_ssl - Set to True to check ssl certifications.\n    fingerprint - Pass the binary sha256\n        digest of the expected certificate in DER format to verify\n        that the certificate the server presents matches. See also\n        https://en.wikipedia.org/wiki/HTTP_Public_Key_Pinning\n    resolver - Enable DNS lookups and use this\n        resolver\n    use_dns_cache - Use memory cache for DNS lookups.\n    ttl_dns_cache - Max seconds having cached a DNS entry, None forever.\n    family - socket address family\n    local_addr - local tuple of (host, port) to bind socket to\n\n    keepalive_timeout - (optional) Keep-alive timeout.\n    force_close - Set to True to force close and do reconnect\n        after each request (and between redirects).\n    limit - The total number of simultaneous connections.\n    limit_per_host - Number of simultaneous connections to one host.\n    enable_cleanup_closed - Enables clean-up closed ssl transports.\n                            Disabled by default.\n    happy_eyeballs_delay - This is the “Connection Attempt Delay”\n                           as defined in RFC 8305. To disable\n                           the happy eyeballs algorithm, set to None.\n    interleave - “First Address Family Count” as defined in RFC 8305\n    loop - Optional event loop.\n    socket_factory - A SocketFactoryType function that, if supplied,\n                     will be used to create sockets given an\n                     AddrInfoType.\n    ssl_shutdown_timeout - DEPRECATED. Will be removed in aiohttp 4.0.\n                           Grace period for SSL shutdown handshake on TLS\n                           connections. Default is 0 seconds (immediate abort).\n                           This parameter allowed for a clean SSL shutdown by\n                           notifying the remote peer of connection closure,\n                           while avoiding excessive delays during connector cleanup.\n                           Note: Only takes effect on Python 3.11+.\n    \"\"\"\n\n    allowed_protocol_schema_set = HIGH_LEVEL_SCHEMA_SET | frozenset({\"tcp\"})\n\n    def __init__(\n        self,\n        *,\n        use_dns_cache: bool = True,\n        ttl_dns_cache: int | None = 10,\n        dns_cache_max_size: int = 1000,\n        family: socket.AddressFamily = socket.AddressFamily.AF_UNSPEC,\n        ssl: bool | Fingerprint | SSLContext = True,\n        local_addr: tuple[str, int] | None = None,\n        resolver: AbstractResolver | None = None,\n        keepalive_timeout: None | float | _SENTINEL = sentinel,\n        force_close: bool = False,\n        limit: int = 100,\n        limit_per_host: int = 0,\n        enable_cleanup_closed: bool = False,\n        timeout_ceil_threshold: float = 5,\n        happy_eyeballs_delay: float | None = 0.25,\n        interleave: int | None = None,\n        socket_factory: SocketFactoryType | None = None,\n        ssl_shutdown_timeout: _SENTINEL | None | float = sentinel,\n    ):\n        super().__init__(\n            keepalive_timeout=keepalive_timeout,\n            force_close=force_close,\n            limit=limit,\n            limit_per_host=limit_per_host,\n            enable_cleanup_closed=enable_cleanup_closed,\n            timeout_ceil_threshold=timeout_ceil_threshold,\n        )\n\n        if not isinstance(ssl, SSL_ALLOWED_TYPES):\n            raise TypeError(\n                \"ssl should be SSLContext, Fingerprint, or bool, \"\n                f\"got {ssl!r} instead.\"\n            )\n        self._ssl = ssl\n\n        self._resolver: AbstractResolver\n        if resolver is None:\n            self._resolver = DefaultResolver()\n            self._resolver_owner = True\n        else:\n            self._resolver = resolver\n            self._resolver_owner = False\n\n        self._use_dns_cache = use_dns_cache\n        self._cached_hosts = _DNSCacheTable(\n            ttl=ttl_dns_cache, max_size=dns_cache_max_size\n        )\n        self._throttle_dns_futures: dict[tuple[str, int], set[asyncio.Future[None]]] = (\n            {}\n        )\n        self._family = family\n        self._local_addr_infos = aiohappyeyeballs.addr_to_addr_infos(local_addr)\n        self._happy_eyeballs_delay = happy_eyeballs_delay\n        self._interleave = interleave\n        self._resolve_host_tasks: set[asyncio.Task[list[ResolveResult]]] = set()\n        self._socket_factory = socket_factory\n        self._ssl_shutdown_timeout: float | None\n\n        # Handle ssl_shutdown_timeout with warning for Python < 3.11\n        if ssl_shutdown_timeout is sentinel:\n            self._ssl_shutdown_timeout = 0\n        else:\n            # Deprecation warning for ssl_shutdown_timeout parameter\n            warnings.warn(\n                \"The ssl_shutdown_timeout parameter is deprecated and will be removed in aiohttp 4.0\",\n                DeprecationWarning,\n                stacklevel=2,\n            )\n            if (\n                sys.version_info < (3, 11)\n                and ssl_shutdown_timeout is not None\n                and ssl_shutdown_timeout != 0\n            ):\n                warnings.warn(\n                    f\"ssl_shutdown_timeout={ssl_shutdown_timeout} is ignored on Python < 3.11; \"\n                    \"only ssl_shutdown_timeout=0 is supported. The timeout will be ignored.\",\n                    RuntimeWarning,\n                    stacklevel=2,\n                )\n            self._ssl_shutdown_timeout = ssl_shutdown_timeout\n\n    async def close(self, *, abort_ssl: bool = False) -> None:\n        \"\"\"Close all opened transports.\n\n        :param abort_ssl: If True, SSL connections will be aborted immediately\n                         without performing the shutdown handshake. If False (default),\n                         the behavior is determined by ssl_shutdown_timeout:\n                         - If ssl_shutdown_timeout=0: connections are aborted\n                         - If ssl_shutdown_timeout>0: graceful shutdown is performed\n        \"\"\"\n        if self._resolver_owner:\n            await self._resolver.close()\n        # Use abort_ssl param if explicitly set, otherwise use ssl_shutdown_timeout default\n        await super().close(abort_ssl=abort_ssl or self._ssl_shutdown_timeout == 0)\n\n    def _close_immediately(self, *, abort_ssl: bool = False) -> list[Awaitable[object]]:\n        for fut in chain.from_iterable(self._throttle_dns_futures.values()):\n            fut.cancel()\n\n        waiters = super()._close_immediately(abort_ssl=abort_ssl)\n\n        for t in self._resolve_host_tasks:\n            t.cancel()\n            waiters.append(t)\n\n        return waiters\n\n    @property\n    def family(self) -> int:\n        \"\"\"Socket family like AF_INET.\"\"\"\n        return self._family\n\n    @property\n    def use_dns_cache(self) -> bool:\n        \"\"\"True if local DNS caching is enabled.\"\"\"\n        return self._use_dns_cache\n\n    def clear_dns_cache(self, host: str | None = None, port: int | None = None) -> None:\n        \"\"\"Remove specified host/port or clear all dns local cache.\"\"\"\n        if host is not None and port is not None:\n            self._cached_hosts.remove((host, port))\n        elif host is not None or port is not None:\n            raise ValueError(\"either both host and port or none of them are allowed\")\n        else:\n            self._cached_hosts.clear()\n\n    async def _resolve_host(\n        self, host: str, port: int, traces: Sequence[\"Trace\"] | None = None\n    ) -> list[ResolveResult]:\n        \"\"\"Resolve host and return list of addresses.\"\"\"\n        if is_ip_address(host):\n            return [\n                {\n                    \"hostname\": host,\n                    \"host\": host,\n                    \"port\": port,\n                    \"family\": self._family,\n                    \"proto\": 0,\n                    \"flags\": 0,\n                }\n            ]\n\n        if not self._use_dns_cache:\n            if traces:\n                for trace in traces:\n                    await trace.send_dns_resolvehost_start(host)\n\n            res = await self._resolver.resolve(host, port, family=self._family)\n\n            if traces:\n                for trace in traces:\n                    await trace.send_dns_resolvehost_end(host)\n\n            return res\n\n        key = (host, port)\n        if key in self._cached_hosts and not self._cached_hosts.expired(key):\n            # get result early, before any await (#4014)\n            result = self._cached_hosts.next_addrs(key)\n\n            if traces:\n                for trace in traces:\n                    await trace.send_dns_cache_hit(host)\n            return result\n\n        futures: set[asyncio.Future[None]]\n        #\n        # If multiple connectors are resolving the same host, we wait\n        # for the first one to resolve and then use the result for all of them.\n        # We use a throttle to ensure that we only resolve the host once\n        # and then use the result for all the waiters.\n        #\n        if key in self._throttle_dns_futures:\n            # get futures early, before any await (#4014)\n            futures = self._throttle_dns_futures[key]\n            future: asyncio.Future[None] = self._loop.create_future()\n            futures.add(future)\n            if traces:\n                for trace in traces:\n                    await trace.send_dns_cache_hit(host)\n            try:\n                await future\n            finally:\n                futures.discard(future)\n            return self._cached_hosts.next_addrs(key)\n\n        # update dict early, before any await (#4014)\n        self._throttle_dns_futures[key] = futures = set()\n        # In this case we need to create a task to ensure that we can shield\n        # the task from cancellation as cancelling this lookup should not cancel\n        # the underlying lookup or else the cancel event will get broadcast to\n        # all the waiters across all connections.\n        #\n        coro = self._resolve_host_with_throttle(key, host, port, futures, traces)\n        loop = asyncio.get_running_loop()\n        if sys.version_info >= (3, 12):\n            # Optimization for Python 3.12, try to send immediately\n            resolved_host_task = asyncio.Task(coro, loop=loop, eager_start=True)\n        else:\n            resolved_host_task = loop.create_task(coro)\n\n        if not resolved_host_task.done():\n            self._resolve_host_tasks.add(resolved_host_task)\n            resolved_host_task.add_done_callback(self._resolve_host_tasks.discard)\n\n        try:\n            return await asyncio.shield(resolved_host_task)\n        except asyncio.CancelledError:\n\n            def drop_exception(fut: \"asyncio.Future[list[ResolveResult]]\") -> None:\n                with suppress(Exception, asyncio.CancelledError):\n                    fut.result()\n\n            resolved_host_task.add_done_callback(drop_exception)\n            raise\n\n    async def _resolve_host_with_throttle(\n        self,\n        key: tuple[str, int],\n        host: str,\n        port: int,\n        futures: set[asyncio.Future[None]],\n        traces: Sequence[\"Trace\"] | None,\n    ) -> list[ResolveResult]:\n        \"\"\"Resolve host and set result for all waiters.\n\n        This method must be run in a task and shielded from cancellation\n        to avoid cancelling the underlying lookup.\n        \"\"\"\n        try:\n            if traces:\n                for trace in traces:\n                    await trace.send_dns_cache_miss(host)\n\n                for trace in traces:\n                    await trace.send_dns_resolvehost_start(host)\n\n            addrs = await self._resolver.resolve(host, port, family=self._family)\n            if traces:\n                for trace in traces:\n                    await trace.send_dns_resolvehost_end(host)\n\n            self._cached_hosts.add(key, addrs)\n            for fut in futures:\n                set_result(fut, None)\n        except BaseException as e:\n            # any DNS exception is set for the waiters to raise the same exception.\n            # This coro is always run in task that is shielded from cancellation so\n            # we should never be propagating cancellation here.\n            for fut in futures:\n                set_exception(fut, e)\n            raise\n        finally:\n            self._throttle_dns_futures.pop(key)\n\n        return self._cached_hosts.next_addrs(key)\n\n    async def _create_connection(\n        self, req: ClientRequest, traces: list[\"Trace\"], timeout: \"ClientTimeout\"\n    ) -> ResponseHandler:\n        \"\"\"Create connection.\n\n        Has same keyword arguments as BaseEventLoop.create_connection.\n        \"\"\"\n        if req.proxy:\n            _, proto = await self._create_proxy_connection(req, traces, timeout)\n        else:\n            _, proto = await self._create_direct_connection(req, traces, timeout)\n\n        return proto\n\n    def _get_ssl_context(self, req: ClientRequestBase) -> SSLContext | None:\n        \"\"\"Logic to get the correct SSL context\n\n        0. if req.ssl is false, return None\n\n        1. if ssl_context is specified in req, use it\n        2. if _ssl_context is specified in self, use it\n        3. otherwise:\n            1. if verify_ssl is not specified in req, use self.ssl_context\n               (will generate a default context according to self.verify_ssl)\n            2. if verify_ssl is True in req, generate a default SSL context\n            3. if verify_ssl is False in req, generate a SSL context that\n               won't verify\n        \"\"\"\n        if not req.is_ssl():\n            return None\n\n        if ssl is None:  # pragma: no cover\n            raise RuntimeError(\"SSL is not supported.\")\n        sslcontext = req.ssl\n        if isinstance(sslcontext, ssl.SSLContext):\n            return sslcontext\n        if sslcontext is not True:\n            # not verified or fingerprinted\n            return _SSL_CONTEXT_UNVERIFIED\n        sslcontext = self._ssl\n        if isinstance(sslcontext, ssl.SSLContext):\n            return sslcontext\n        if sslcontext is not True:\n            # not verified or fingerprinted\n            return _SSL_CONTEXT_UNVERIFIED\n        return _SSL_CONTEXT_VERIFIED\n\n    def _get_fingerprint(self, req: ClientRequestBase) -> \"Fingerprint | None\":\n        ret = req.ssl\n        if isinstance(ret, Fingerprint):\n            return ret\n        ret = self._ssl\n        if isinstance(ret, Fingerprint):\n            return ret\n        return None\n\n    async def _wrap_create_connection(\n        self,\n        *args: Any,\n        addr_infos: list[AddrInfoType],\n        req: ClientRequestBase,\n        timeout: \"ClientTimeout\",\n        client_error: type[Exception] = ClientConnectorError,\n        **kwargs: Any,\n    ) -> tuple[asyncio.Transport, ResponseHandler]:\n        try:\n            async with ceil_timeout(\n                timeout.sock_connect, ceil_threshold=timeout.ceil_threshold\n            ):\n                sock = await aiohappyeyeballs.start_connection(\n                    addr_infos=addr_infos,\n                    local_addr_infos=self._local_addr_infos,\n                    happy_eyeballs_delay=self._happy_eyeballs_delay,\n                    interleave=self._interleave,\n                    loop=self._loop,\n                    socket_factory=self._socket_factory,\n                )\n                # Add ssl_shutdown_timeout for Python 3.11+ when SSL is used\n                if (\n                    kwargs.get(\"ssl\")\n                    and self._ssl_shutdown_timeout\n                    and sys.version_info >= (3, 11)\n                ):\n                    kwargs[\"ssl_shutdown_timeout\"] = self._ssl_shutdown_timeout\n                return await self._loop.create_connection(*args, **kwargs, sock=sock)\n        except cert_errors as exc:\n            raise ClientConnectorCertificateError(req.connection_key, exc) from exc\n        except ssl_errors as exc:\n            raise ClientConnectorSSLError(req.connection_key, exc) from exc\n        except OSError as exc:\n            if exc.errno is None and isinstance(exc, asyncio.TimeoutError):\n                raise\n            raise client_error(req.connection_key, exc) from exc\n\n    def _warn_about_tls_in_tls(\n        self,\n        underlying_transport: asyncio.Transport,\n        req: ClientRequest,\n    ) -> None:\n        \"\"\"Issue a warning if the requested URL has HTTPS scheme.\"\"\"\n        if req.url.scheme != \"https\":\n            return\n\n        # TLS-in-TLS only applies when the proxy itself is HTTPS.\n        # When the proxy is HTTP, start_tls upgrades a plain TCP connection,\n        # which is standard TLS and works on all event loops and Python versions.\n        if req.proxy is None or req.proxy.scheme != \"https\":\n            return\n\n        # Check if uvloop is being used, which supports TLS in TLS,\n        # otherwise assume that asyncio's native transport is being used.\n        if type(underlying_transport).__module__.startswith(\"uvloop\"):\n            return\n\n        # Support in asyncio was added in Python 3.11 (bpo-44011)\n        asyncio_supports_tls_in_tls = sys.version_info >= (3, 11) or getattr(\n            underlying_transport,\n            \"_start_tls_compatible\",\n            False,\n        )\n\n        if asyncio_supports_tls_in_tls:\n            return\n\n        warnings.warn(\n            \"An HTTPS request is being sent through an HTTPS proxy. \"\n            \"This support for TLS in TLS is known to be disabled \"\n            \"in the stdlib asyncio. This is why you'll probably see \"\n            \"an error in the log below.\\n\\n\"\n            \"It is possible to enable it via monkeypatching. \"\n            \"For more details, see:\\n\"\n            \"* https://bugs.python.org/issue37179\\n\"\n            \"* https://github.com/python/cpython/pull/28073\\n\\n\"\n            \"You can temporarily patch this as follows:\\n\"\n            \"* https://docs.aiohttp.org/en/stable/client_advanced.html#proxy-support\\n\"\n            \"* https://github.com/aio-libs/aiohttp/discussions/6044\\n\",\n            RuntimeWarning,\n            source=self,\n            # Why `4`? At least 3 of the calls in the stack originate\n            # from the methods in this class.\n            stacklevel=3,\n        )\n\n    async def _start_tls_connection(\n        self,\n        underlying_transport: asyncio.Transport,\n        req: ClientRequest,\n        timeout: \"ClientTimeout\",\n        client_error: type[Exception] = ClientConnectorError,\n    ) -> tuple[asyncio.BaseTransport, ResponseHandler]:\n        \"\"\"Wrap the raw TCP transport with TLS.\"\"\"\n        tls_proto = self._factory()  # Create a brand new proto for TLS\n        sslcontext = self._get_ssl_context(req)\n        if TYPE_CHECKING:\n            # _start_tls_connection is unreachable in the current code path\n            # if sslcontext is None.\n            assert sslcontext is not None\n\n        try:\n            async with ceil_timeout(\n                timeout.sock_connect, ceil_threshold=timeout.ceil_threshold\n            ):\n                try:\n                    # ssl_shutdown_timeout is only available in Python 3.11+\n                    if sys.version_info >= (3, 11) and self._ssl_shutdown_timeout:\n                        tls_transport = await self._loop.start_tls(\n                            underlying_transport,\n                            tls_proto,\n                            sslcontext,\n                            server_hostname=req.server_hostname or req.url.raw_host,\n                            ssl_handshake_timeout=timeout.total,\n                            ssl_shutdown_timeout=self._ssl_shutdown_timeout,\n                        )\n                    else:\n                        tls_transport = await self._loop.start_tls(\n                            underlying_transport,\n                            tls_proto,\n                            sslcontext,\n                            server_hostname=req.server_hostname or req.url.raw_host,\n                            ssl_handshake_timeout=timeout.total,\n                        )\n                except BaseException:\n                    # We need to close the underlying transport since\n                    # `start_tls()` probably failed before it had a\n                    # chance to do this:\n                    if self._ssl_shutdown_timeout == 0:\n                        underlying_transport.abort()\n                    else:\n                        underlying_transport.close()\n                    raise\n                if isinstance(tls_transport, asyncio.Transport):\n                    fingerprint = self._get_fingerprint(req)\n                    if fingerprint:\n                        try:\n                            fingerprint.check(tls_transport)\n                        except ServerFingerprintMismatch:\n                            tls_transport.close()\n                            if not self._cleanup_closed_disabled:\n                                self._cleanup_closed_transports.append(tls_transport)\n                            raise\n        except cert_errors as exc:\n            raise ClientConnectorCertificateError(req.connection_key, exc) from exc\n        except ssl_errors as exc:\n            raise ClientConnectorSSLError(req.connection_key, exc) from exc\n        except OSError as exc:\n            if exc.errno is None and isinstance(exc, asyncio.TimeoutError):\n                raise\n            raise client_error(req.connection_key, exc) from exc\n        except TypeError as type_err:\n            # Example cause looks like this:\n            # TypeError: transport <asyncio.sslproto._SSLProtocolTransport\n            # object at 0x7f760615e460> is not supported by start_tls()\n\n            raise ClientConnectionError(\n                \"Cannot initialize a TLS-in-TLS connection to host \"\n                f\"{req.url.host!s}:{req.url.port:d} through an underlying connection \"\n                f\"to an HTTPS proxy {req.proxy!s} ssl:{req.ssl or 'default'} \"\n                f\"[{type_err!s}]\"\n            ) from type_err\n        else:\n            if tls_transport is None:\n                msg = \"Failed to start TLS (possibly caused by closing transport)\"\n                raise client_error(req.connection_key, OSError(msg))\n            tls_proto.connection_made(\n                tls_transport\n            )  # Kick the state machine of the new TLS protocol\n\n        return tls_transport, tls_proto\n\n    def _convert_hosts_to_addr_infos(\n        self, hosts: list[ResolveResult]\n    ) -> list[AddrInfoType]:\n        \"\"\"Converts the list of hosts to a list of addr_infos.\n\n        The list of hosts is the result of a DNS lookup. The list of\n        addr_infos is the result of a call to `socket.getaddrinfo()`.\n        \"\"\"\n        addr_infos: list[AddrInfoType] = []\n        for hinfo in hosts:\n            host = hinfo[\"host\"]\n            is_ipv6 = \":\" in host\n            family = socket.AF_INET6 if is_ipv6 else socket.AF_INET\n            if self._family and self._family != family:\n                continue\n            addr = (host, hinfo[\"port\"], 0, 0) if is_ipv6 else (host, hinfo[\"port\"])\n            addr_infos.append(\n                (family, socket.SOCK_STREAM, socket.IPPROTO_TCP, \"\", addr)\n            )\n        return addr_infos\n\n    async def _create_direct_connection(\n        self,\n        req: ClientRequestBase,\n        traces: list[\"Trace\"],\n        timeout: \"ClientTimeout\",\n        *,\n        client_error: type[Exception] = ClientConnectorError,\n    ) -> tuple[asyncio.Transport, ResponseHandler]:\n        sslcontext = self._get_ssl_context(req)\n        fingerprint = self._get_fingerprint(req)\n\n        host = req.url.raw_host\n        assert host is not None\n        # Replace multiple trailing dots with a single one.\n        # A trailing dot is only present for fully-qualified domain names.\n        # See https://github.com/aio-libs/aiohttp/pull/7364.\n        if host.endswith(\"..\"):\n            host = host.rstrip(\".\") + \".\"\n        port = req.url.port\n        assert port is not None\n        try:\n            # Cancelling this lookup should not cancel the underlying lookup\n            #  or else the cancel event will get broadcast to all the waiters\n            #  across all connections.\n            hosts = await self._resolve_host(host, port, traces=traces)\n        except OSError as exc:\n            if exc.errno is None and isinstance(exc, asyncio.TimeoutError):\n                raise\n            # in case of proxy it is not ClientProxyConnectionError\n            # it is problem of resolving proxy ip itself\n            raise ClientConnectorDNSError(req.connection_key, exc) from exc\n\n        last_exc: Exception | None = None\n        addr_infos = self._convert_hosts_to_addr_infos(hosts)\n        while addr_infos:\n            # Strip trailing dots, certificates contain FQDN without dots.\n            # See https://github.com/aio-libs/aiohttp/issues/3636\n            server_hostname = (\n                (req.server_hostname or host).rstrip(\".\") if sslcontext else None\n            )\n\n            try:\n                transp, proto = await self._wrap_create_connection(\n                    self._factory,\n                    timeout=timeout,\n                    ssl=sslcontext,\n                    addr_infos=addr_infos,\n                    server_hostname=server_hostname,\n                    req=req,\n                    client_error=client_error,\n                )\n            except (ClientConnectorError, asyncio.TimeoutError) as exc:\n                last_exc = exc\n                aiohappyeyeballs.pop_addr_infos_interleave(addr_infos, self._interleave)\n                continue\n\n            if req.is_ssl() and fingerprint:\n                try:\n                    fingerprint.check(transp)\n                except ServerFingerprintMismatch as exc:\n                    transp.close()\n                    if not self._cleanup_closed_disabled:\n                        self._cleanup_closed_transports.append(transp)\n                    last_exc = exc\n                    # Remove the bad peer from the list of addr_infos\n                    sock: socket.socket = transp.get_extra_info(\"socket\")\n                    bad_peer = sock.getpeername()\n                    aiohappyeyeballs.remove_addr_infos(addr_infos, bad_peer)\n                    continue\n\n            return transp, proto\n        assert last_exc is not None\n        raise last_exc\n\n    async def _create_proxy_connection(\n        self, req: ClientRequest, traces: list[\"Trace\"], timeout: \"ClientTimeout\"\n    ) -> tuple[asyncio.BaseTransport, ResponseHandler]:\n        proxy_req = self._update_proxy_auth_header_and_build_proxy_req(req)\n\n        # create connection to proxy server\n        transport, proto = await self._create_direct_connection(\n            proxy_req, [], timeout, client_error=ClientProxyConnectionError\n        )\n\n        if req.is_ssl():\n            self._warn_about_tls_in_tls(transport, req)\n\n            # For HTTPS requests over HTTP proxy\n            # we must notify proxy to tunnel connection\n            # so we send CONNECT command:\n            #   CONNECT www.python.org:443 HTTP/1.1\n            #   Host: www.python.org\n            #\n            # next we must do TLS handshake and so on\n            # to do this we must wrap raw socket into secure one\n            # asyncio handles this perfectly\n            proxy_req.method = hdrs.METH_CONNECT\n            proxy_req.url = req.url\n            key = req.connection_key._replace(\n                proxy=None, proxy_auth=None, proxy_headers_hash=None\n            )\n            conn = _ConnectTunnelConnection(self, key, proto, self._loop)\n            proxy_resp = await proxy_req._send(conn)\n            try:\n                protocol = conn._protocol\n                assert protocol is not None\n\n                # read_until_eof=True will ensure the connection isn't closed\n                # once the response is received and processed allowing\n                # START_TLS to work on the connection below.\n                protocol.set_response_params(\n                    read_until_eof=True,\n                    timeout_ceil_threshold=self._timeout_ceil_threshold,\n                )\n                resp = await proxy_resp.start(conn)\n            except BaseException:\n                proxy_resp.close()\n                conn.close()\n                raise\n            else:\n                conn._protocol = None\n                try:\n                    if resp.status != 200:\n                        message = resp.reason\n                        if message is None:\n                            message = HTTPStatus(resp.status).phrase\n                        raise ClientHttpProxyError(\n                            proxy_resp.request_info,\n                            resp.history,\n                            status=resp.status,\n                            message=message,\n                            headers=resp.headers,\n                        )\n                except BaseException:\n                    # It shouldn't be closed in `finally` because it's fed to\n                    # `loop.start_tls()` and the docs say not to touch it after\n                    # passing there.\n                    transport.close()\n                    raise\n\n                return await self._start_tls_connection(\n                    # Access the old transport for the last time before it's\n                    # closed and forgotten forever:\n                    transport,\n                    req=req,\n                    timeout=timeout,\n                )\n            finally:\n                proxy_resp.close()\n\n        return transport, proto\n\n\nclass UnixConnector(BaseConnector):\n    \"\"\"Unix socket connector.\n\n    path - Unix socket path.\n    keepalive_timeout - (optional) Keep-alive timeout.\n    force_close - Set to True to force close and do reconnect\n        after each request (and between redirects).\n    limit - The total number of simultaneous connections.\n    limit_per_host - Number of simultaneous connections to one host.\n    loop - Optional event loop.\n    \"\"\"\n\n    allowed_protocol_schema_set = HIGH_LEVEL_SCHEMA_SET | frozenset({\"unix\"})\n\n    def __init__(\n        self,\n        path: str,\n        force_close: bool = False,\n        keepalive_timeout: _SENTINEL | float | None = sentinel,\n        limit: int = 100,\n        limit_per_host: int = 0,\n    ) -> None:\n        super().__init__(\n            force_close=force_close,\n            keepalive_timeout=keepalive_timeout,\n            limit=limit,\n            limit_per_host=limit_per_host,\n        )\n        self._path = path\n\n    @property\n    def path(self) -> str:\n        \"\"\"Path to unix socket.\"\"\"\n        return self._path\n\n    async def _create_connection(\n        self, req: ClientRequest, traces: list[\"Trace\"], timeout: \"ClientTimeout\"\n    ) -> ResponseHandler:\n        try:\n            async with ceil_timeout(\n                timeout.sock_connect, ceil_threshold=timeout.ceil_threshold\n            ):\n                _, proto = await self._loop.create_unix_connection(\n                    self._factory, self._path\n                )\n        except OSError as exc:\n            if exc.errno is None and isinstance(exc, asyncio.TimeoutError):\n                raise\n            raise UnixClientConnectorError(self.path, req.connection_key, exc) from exc\n\n        return proto\n\n\nclass NamedPipeConnector(BaseConnector):\n    \"\"\"Named pipe connector.\n\n    Only supported by the proactor event loop.\n    See also: https://docs.python.org/3/library/asyncio-eventloop.html\n\n    path - Windows named pipe path.\n    keepalive_timeout - (optional) Keep-alive timeout.\n    force_close - Set to True to force close and do reconnect\n        after each request (and between redirects).\n    limit - The total number of simultaneous connections.\n    limit_per_host - Number of simultaneous connections to one host.\n    loop - Optional event loop.\n    \"\"\"\n\n    allowed_protocol_schema_set = HIGH_LEVEL_SCHEMA_SET | frozenset({\"npipe\"})\n\n    def __init__(\n        self,\n        path: str,\n        force_close: bool = False,\n        keepalive_timeout: _SENTINEL | float | None = sentinel,\n        limit: int = 100,\n        limit_per_host: int = 0,\n    ) -> None:\n        super().__init__(\n            force_close=force_close,\n            keepalive_timeout=keepalive_timeout,\n            limit=limit,\n            limit_per_host=limit_per_host,\n        )\n        if not isinstance(\n            self._loop,\n            asyncio.ProactorEventLoop,  # type: ignore[attr-defined]\n        ):\n            raise RuntimeError(\n                \"Named Pipes only available in proactor loop under windows\"\n            )\n        self._path = path\n\n    @property\n    def path(self) -> str:\n        \"\"\"Path to the named pipe.\"\"\"\n        return self._path\n\n    async def _create_connection(\n        self, req: ClientRequest, traces: list[\"Trace\"], timeout: \"ClientTimeout\"\n    ) -> ResponseHandler:\n        try:\n            async with ceil_timeout(\n                timeout.sock_connect, ceil_threshold=timeout.ceil_threshold\n            ):\n                _, proto = await self._loop.create_pipe_connection(  # type: ignore[attr-defined]\n                    self._factory, self._path\n                )\n                # the drain is required so that the connection_made is called\n                # and transport is set otherwise it is not set before the\n                # `assert conn.transport is not None`\n                # in client.py's _request method\n                await asyncio.sleep(0)\n                # other option is to manually set transport like\n                # `proto.transport = trans`\n        except OSError as exc:\n            if exc.errno is None and isinstance(exc, asyncio.TimeoutError):\n                raise\n            raise ClientConnectorError(req.connection_key, exc) from exc\n\n        return cast(ResponseHandler, proto)\n"
  },
  {
    "path": "aiohttp/cookiejar.py",
    "content": "import calendar\nimport contextlib\nimport datetime\nimport heapq\nimport itertools\nimport json\nimport os  # noqa\nimport pathlib\nimport pickle\nimport re\nimport time\nimport warnings\nfrom collections import defaultdict\nfrom collections.abc import Iterable, Iterator, Mapping\nfrom http.cookies import BaseCookie, Morsel, SimpleCookie\nfrom typing import Union\n\nfrom yarl import URL\n\nfrom ._cookie_helpers import preserve_morsel_with_coded_value\nfrom .abc import AbstractCookieJar, ClearCookiePredicate\nfrom .helpers import is_ip_address\nfrom .typedefs import LooseCookies, PathLike, StrOrURL\n\n__all__ = (\"CookieJar\", \"DummyCookieJar\")\n\n\nCookieItem = Union[str, \"Morsel[str]\"]\n\n# We cache these string methods here as their use is in performance critical code.\n_FORMAT_PATH = \"{}/{}\".format\n_FORMAT_DOMAIN_REVERSED = \"{1}.{0}\".format\n\n# The minimum number of scheduled cookie expirations before we start cleaning up\n# the expiration heap. This is a performance optimization to avoid cleaning up the\n# heap too often when there are only a few scheduled expirations.\n_MIN_SCHEDULED_COOKIE_EXPIRATION = 100\n_SIMPLE_COOKIE = SimpleCookie()\n\n\nclass _RestrictedCookieUnpickler(pickle.Unpickler):\n    \"\"\"A restricted unpickler that only allows cookie-related types.\n\n    This prevents arbitrary code execution when loading pickled cookie data\n    from untrusted sources. Only types that are expected in a serialized\n    CookieJar are permitted.\n\n    See: https://docs.python.org/3/library/pickle.html#restricting-globals\n    \"\"\"\n\n    _ALLOWED_CLASSES: frozenset[tuple[str, str]] = frozenset(\n        {\n            # Core cookie types\n            (\"http.cookies\", \"SimpleCookie\"),\n            (\"http.cookies\", \"Morsel\"),\n            # Container types used by CookieJar._cookies\n            (\"collections\", \"defaultdict\"),\n            # builtins that pickle uses for reconstruction\n            (\"builtins\", \"tuple\"),\n            (\"builtins\", \"set\"),\n            (\"builtins\", \"frozenset\"),\n            (\"builtins\", \"dict\"),\n        }\n    )\n\n    def find_class(self, module: str, name: str) -> type:\n        if (module, name) not in self._ALLOWED_CLASSES:\n            raise pickle.UnpicklingError(\n                f\"Forbidden class: {module}.{name}. \"\n                \"CookieJar.load() only allows cookie-related types for security. \"\n                \"See https://docs.python.org/3/library/pickle.html#restricting-globals\"\n            )\n        return super().find_class(module, name)  # type: ignore[no-any-return]\n\n\nclass CookieJar(AbstractCookieJar):\n    \"\"\"Implements cookie storage adhering to RFC 6265.\"\"\"\n\n    DATE_TOKENS_RE = re.compile(\n        r\"[\\x09\\x20-\\x2F\\x3B-\\x40\\x5B-\\x60\\x7B-\\x7E]*\"\n        r\"(?P<token>[\\x00-\\x08\\x0A-\\x1F\\d:a-zA-Z\\x7F-\\xFF]+)\"\n    )\n\n    DATE_HMS_TIME_RE = re.compile(r\"(\\d{1,2}):(\\d{1,2}):(\\d{1,2})\")\n\n    DATE_DAY_OF_MONTH_RE = re.compile(r\"(\\d{1,2})\")\n\n    DATE_MONTH_RE = re.compile(\n        \"(jan)|(feb)|(mar)|(apr)|(may)|(jun)|(jul)|(aug)|(sep)|(oct)|(nov)|(dec)\",\n        re.I,\n    )\n\n    DATE_YEAR_RE = re.compile(r\"(\\d{2,4})\")\n\n    # calendar.timegm() fails for timestamps after datetime.datetime.max\n    # Minus one as a loss of precision occurs when timestamp() is called.\n    MAX_TIME = (\n        int(datetime.datetime.max.replace(tzinfo=datetime.timezone.utc).timestamp()) - 1\n    )\n    try:\n        calendar.timegm(time.gmtime(MAX_TIME))\n    except OSError:\n        # Hit the maximum representable time on Windows\n        # https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-localtime32-localtime64\n        MAX_TIME = calendar.timegm((3000, 12, 31, 23, 59, 59, -1, -1, -1))\n    except OverflowError:\n        # #4515: datetime.max may not be representable on 32-bit platforms\n        MAX_TIME = 2**31 - 1\n    # Avoid minuses in the future, 3x faster\n    SUB_MAX_TIME = MAX_TIME - 1\n\n    def __init__(\n        self,\n        *,\n        unsafe: bool = False,\n        quote_cookie: bool = True,\n        treat_as_secure_origin: StrOrURL | Iterable[StrOrURL] | None = None,\n    ) -> None:\n        self._cookies: defaultdict[tuple[str, str], SimpleCookie] = defaultdict(\n            SimpleCookie\n        )\n        self._morsel_cache: defaultdict[tuple[str, str], dict[str, Morsel[str]]] = (\n            defaultdict(dict)\n        )\n        self._host_only_cookies: set[tuple[str, str]] = set()\n        self._unsafe = unsafe\n        self._quote_cookie = quote_cookie\n        if treat_as_secure_origin is None:\n            self._treat_as_secure_origin: frozenset[URL] = frozenset()\n        elif isinstance(treat_as_secure_origin, URL):\n            self._treat_as_secure_origin = frozenset({treat_as_secure_origin.origin()})\n        elif isinstance(treat_as_secure_origin, str):\n            self._treat_as_secure_origin = frozenset(\n                {URL(treat_as_secure_origin).origin()}\n            )\n        else:\n            self._treat_as_secure_origin = frozenset(\n                {\n                    URL(url).origin() if isinstance(url, str) else url.origin()\n                    for url in treat_as_secure_origin\n                }\n            )\n        self._expire_heap: list[tuple[float, tuple[str, str, str]]] = []\n        self._expirations: dict[tuple[str, str, str], float] = {}\n\n    @property\n    def quote_cookie(self) -> bool:\n        return self._quote_cookie\n\n    def save(self, file_path: PathLike) -> None:\n        \"\"\"Save cookies to a file using JSON format.\n\n        :param file_path: Path to file where cookies will be serialized,\n            :class:`str` or :class:`pathlib.Path` instance.\n        \"\"\"\n        file_path = pathlib.Path(file_path)\n        data: dict[str, dict[str, dict[str, str | bool]]] = {}\n        for (domain, path), cookie in self._cookies.items():\n            key = f\"{domain}|{path}\"\n            data[key] = {}\n            for name, morsel in cookie.items():\n                morsel_data: dict[str, str | bool] = {\n                    \"key\": morsel.key,\n                    \"value\": morsel.value,\n                    \"coded_value\": morsel.coded_value,\n                }\n                # Save all morsel attributes that have values\n                for attr in morsel._reserved:  # type: ignore[attr-defined]\n                    attr_val = morsel[attr]\n                    if attr_val:\n                        morsel_data[attr] = attr_val\n                data[key][name] = morsel_data\n        with file_path.open(mode=\"w\", encoding=\"utf-8\") as f:\n            json.dump(data, f, indent=2)\n\n    def load(self, file_path: PathLike) -> None:\n        \"\"\"Load cookies from a file.\n\n        Tries to load JSON format first. Falls back to loading legacy\n        pickle format (using a restricted unpickler) for backward\n        compatibility with existing cookie files.\n\n        :param file_path: Path to file from where cookies will be\n            imported, :class:`str` or :class:`pathlib.Path` instance.\n        \"\"\"\n        file_path = pathlib.Path(file_path)\n        # Try JSON format first\n        try:\n            with file_path.open(mode=\"r\", encoding=\"utf-8\") as f:\n                data = json.load(f)\n            self._cookies = self._load_json_data(data)\n        except (json.JSONDecodeError, UnicodeDecodeError, ValueError):\n            # Fall back to legacy pickle format with restricted unpickler\n            with file_path.open(mode=\"rb\") as f:\n                self._cookies = _RestrictedCookieUnpickler(f).load()\n\n    def _load_json_data(\n        self, data: dict[str, dict[str, dict[str, str | bool]]]\n    ) -> defaultdict[tuple[str, str], SimpleCookie]:\n        \"\"\"Load cookies from parsed JSON data.\"\"\"\n        cookies: defaultdict[tuple[str, str], SimpleCookie] = defaultdict(SimpleCookie)\n        for compound_key, cookie_data in data.items():\n            domain, path = compound_key.split(\"|\", 1)\n            key = (domain, path)\n            for name, morsel_data in cookie_data.items():\n                morsel: Morsel[str] = Morsel()\n                morsel_key = morsel_data[\"key\"]\n                morsel_value = morsel_data[\"value\"]\n                morsel_coded_value = morsel_data[\"coded_value\"]\n                # Use __setstate__ to bypass validation, same pattern\n                # used in _build_morsel and _cookie_helpers.\n                morsel.__setstate__(  # type: ignore[attr-defined]\n                    {\n                        \"key\": morsel_key,\n                        \"value\": morsel_value,\n                        \"coded_value\": morsel_coded_value,\n                    }\n                )\n                # Restore morsel attributes\n                for attr in morsel._reserved:  # type: ignore[attr-defined]\n                    if attr in morsel_data and attr not in (\n                        \"key\",\n                        \"value\",\n                        \"coded_value\",\n                    ):\n                        morsel[attr] = morsel_data[attr]\n                cookies[key][name] = morsel\n        return cookies\n\n    def clear(self, predicate: ClearCookiePredicate | None = None) -> None:\n        if predicate is None:\n            self._expire_heap.clear()\n            self._cookies.clear()\n            self._morsel_cache.clear()\n            self._host_only_cookies.clear()\n            self._expirations.clear()\n            return\n\n        now = time.time()\n        to_del = [\n            key\n            for (domain, path), cookie in self._cookies.items()\n            for name, morsel in cookie.items()\n            if (\n                (key := (domain, path, name)) in self._expirations\n                and self._expirations[key] <= now\n            )\n            or predicate(morsel)\n        ]\n        if to_del:\n            self._delete_cookies(to_del)\n\n    def clear_domain(self, domain: str) -> None:\n        self.clear(lambda x: self._is_domain_match(domain, x[\"domain\"]))\n\n    def __iter__(self) -> \"Iterator[Morsel[str]]\":\n        self._do_expiration()\n        for val in self._cookies.values():\n            yield from val.values()\n\n    def __len__(self) -> int:\n        \"\"\"Return number of cookies.\n\n        This function does not iterate self to avoid unnecessary expiration\n        checks.\n        \"\"\"\n        return sum(len(cookie.values()) for cookie in self._cookies.values())\n\n    def _do_expiration(self) -> None:\n        \"\"\"Remove expired cookies.\"\"\"\n        if not (expire_heap_len := len(self._expire_heap)):\n            return\n\n        # If the expiration heap grows larger than the number expirations\n        # times two, we clean it up to avoid keeping expired entries in\n        # the heap and consuming memory. We guard this with a minimum\n        # threshold to avoid cleaning up the heap too often when there are\n        # only a few scheduled expirations.\n        if (\n            expire_heap_len > _MIN_SCHEDULED_COOKIE_EXPIRATION\n            and expire_heap_len > len(self._expirations) * 2\n        ):\n            # Remove any expired entries from the expiration heap\n            # that do not match the expiration time in the expirations\n            # as it means the cookie has been re-added to the heap\n            # with a different expiration time.\n            self._expire_heap = [\n                entry\n                for entry in self._expire_heap\n                if self._expirations.get(entry[1]) == entry[0]\n            ]\n            heapq.heapify(self._expire_heap)\n\n        now = time.time()\n        to_del: list[tuple[str, str, str]] = []\n        # Find any expired cookies and add them to the to-delete list\n        while self._expire_heap:\n            when, cookie_key = self._expire_heap[0]\n            if when > now:\n                break\n            heapq.heappop(self._expire_heap)\n            # Check if the cookie hasn't been re-added to the heap\n            # with a different expiration time as it will be removed\n            # later when it reaches the top of the heap and its\n            # expiration time is met.\n            if self._expirations.get(cookie_key) == when:\n                to_del.append(cookie_key)\n\n        if to_del:\n            self._delete_cookies(to_del)\n\n    def _delete_cookies(self, to_del: list[tuple[str, str, str]]) -> None:\n        for domain, path, name in to_del:\n            self._host_only_cookies.discard((domain, name))\n            self._cookies[(domain, path)].pop(name, None)\n            self._morsel_cache[(domain, path)].pop(name, None)\n            self._expirations.pop((domain, path, name), None)\n\n    def _expire_cookie(self, when: float, domain: str, path: str, name: str) -> None:\n        cookie_key = (domain, path, name)\n        if self._expirations.get(cookie_key) == when:\n            # Avoid adding duplicates to the heap\n            return\n        heapq.heappush(self._expire_heap, (when, cookie_key))\n        self._expirations[cookie_key] = when\n\n    def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None:\n        \"\"\"Update cookies.\"\"\"\n        hostname = response_url.raw_host\n\n        if not self._unsafe and is_ip_address(hostname):\n            # Don't accept cookies from IPs\n            return\n\n        if isinstance(cookies, Mapping):\n            cookies = cookies.items()\n\n        for name, cookie in cookies:\n            if not isinstance(cookie, Morsel):\n                tmp = SimpleCookie()\n                tmp[name] = cookie  # type: ignore[assignment]\n                cookie = tmp[name]\n\n            domain = cookie[\"domain\"]\n\n            # ignore domains with trailing dots\n            if domain and domain[-1] == \".\":\n                domain = \"\"\n                del cookie[\"domain\"]\n\n            if not domain and hostname is not None:\n                # Set the cookie's domain to the response hostname\n                # and set its host-only-flag\n                self._host_only_cookies.add((hostname, name))\n                domain = cookie[\"domain\"] = hostname\n\n            if domain and domain[0] == \".\":\n                # Remove leading dot\n                domain = domain[1:]\n                cookie[\"domain\"] = domain\n\n            if hostname and not self._is_domain_match(domain, hostname):\n                # Setting cookies for different domains is not allowed\n                continue\n\n            path = cookie[\"path\"]\n            if not path or path[0] != \"/\":\n                # Set the cookie's path to the response path\n                path = response_url.path\n                if not path.startswith(\"/\"):\n                    path = \"/\"\n                else:\n                    # Cut everything from the last slash to the end\n                    path = \"/\" + path[1 : path.rfind(\"/\")]\n                cookie[\"path\"] = path\n            path = path.rstrip(\"/\")\n\n            if max_age := cookie[\"max-age\"]:\n                try:\n                    delta_seconds = int(max_age)\n                    max_age_expiration = min(time.time() + delta_seconds, self.MAX_TIME)\n                    self._expire_cookie(max_age_expiration, domain, path, name)\n                except ValueError:\n                    cookie[\"max-age\"] = \"\"\n\n            elif expires := cookie[\"expires\"]:\n                if expire_time := self._parse_date(expires):\n                    self._expire_cookie(expire_time, domain, path, name)\n                else:\n                    cookie[\"expires\"] = \"\"\n\n            key = (domain, path)\n            if self._cookies[key].get(name) != cookie:\n                # Don't blow away the cache if the same\n                # cookie gets set again\n                self._cookies[key][name] = cookie\n                self._morsel_cache[key].pop(name, None)\n\n        self._do_expiration()\n\n    def filter_cookies(self, request_url: URL) -> \"BaseCookie[str]\":\n        \"\"\"Returns this jar's cookies filtered by their attributes.\"\"\"\n        if not isinstance(request_url, URL):\n            warnings.warn(  # type: ignore[unreachable]\n                f\"The method accepts yarl.URL instances only, got {type(request_url)}\",\n                DeprecationWarning,\n            )\n            request_url = URL(request_url)\n        # We always use BaseCookie now since all\n        # cookies set on on filtered are fully constructed\n        # Morsels, not just names and values.\n        filtered: BaseCookie[str] = BaseCookie()\n        if not self._cookies:\n            # Skip do_expiration() if there are no cookies.\n            return filtered\n        self._do_expiration()\n        if not self._cookies:\n            # Skip rest of function if no non-expired cookies.\n            return filtered\n        hostname = request_url.raw_host or \"\"\n\n        is_not_secure = request_url.scheme not in (\"https\", \"wss\")\n        if is_not_secure and self._treat_as_secure_origin:\n            request_origin = URL()\n            with contextlib.suppress(ValueError):\n                request_origin = request_url.origin()\n            is_not_secure = request_origin not in self._treat_as_secure_origin\n\n        # Send shared cookie\n        key = (\"\", \"\")\n        for c in self._cookies[key].values():\n            # Check cache first\n            if c.key in self._morsel_cache[key]:\n                filtered[c.key] = self._morsel_cache[key][c.key]\n                continue\n\n            # Build and cache the morsel\n            mrsl_val = self._build_morsel(c)\n            self._morsel_cache[key][c.key] = mrsl_val\n            filtered[c.key] = mrsl_val\n\n        if is_ip_address(hostname):\n            if not self._unsafe:\n                return filtered\n            domains: Iterable[str] = (hostname,)\n        else:\n            # Get all the subdomains that might match a cookie (e.g. \"foo.bar.com\", \"bar.com\", \"com\")\n            domains = itertools.accumulate(\n                reversed(hostname.split(\".\")), _FORMAT_DOMAIN_REVERSED\n            )\n\n        # Get all the path prefixes that might match a cookie (e.g. \"\", \"/foo\", \"/foo/bar\")\n        paths = itertools.accumulate(request_url.path.split(\"/\"), _FORMAT_PATH)\n        # Create every combination of (domain, path) pairs.\n        pairs = itertools.product(domains, paths)\n\n        path_len = len(request_url.path)\n        # Point 2: https://www.rfc-editor.org/rfc/rfc6265.html#section-5.4\n        for p in pairs:\n            if p not in self._cookies:\n                continue\n            for name, cookie in self._cookies[p].items():\n                domain = cookie[\"domain\"]\n\n                if (domain, name) in self._host_only_cookies and domain != hostname:\n                    continue\n\n                # Skip edge case when the cookie has a trailing slash but request doesn't.\n                if len(cookie[\"path\"]) > path_len:\n                    continue\n\n                if is_not_secure and cookie[\"secure\"]:\n                    continue\n\n                # We already built the Morsel so reuse it here\n                if name in self._morsel_cache[p]:\n                    filtered[name] = self._morsel_cache[p][name]\n                    continue\n\n                # Build and cache the morsel\n                mrsl_val = self._build_morsel(cookie)\n                self._morsel_cache[p][name] = mrsl_val\n                filtered[name] = mrsl_val\n\n        return filtered\n\n    def _build_morsel(self, cookie: Morsel[str]) -> Morsel[str]:\n        \"\"\"Build a morsel for sending, respecting quote_cookie setting.\"\"\"\n        if self._quote_cookie and cookie.coded_value and cookie.coded_value[0] == '\"':\n            return preserve_morsel_with_coded_value(cookie)\n        morsel: Morsel[str] = Morsel()\n        if self._quote_cookie:\n            value, coded_value = _SIMPLE_COOKIE.value_encode(cookie.value)\n        else:\n            coded_value = value = cookie.value\n        # We use __setstate__ instead of the public set() API because it allows us to\n        # bypass validation and set already validated state. This is more stable than\n        # setting protected attributes directly and unlikely to change since it would\n        # break pickling.\n        morsel.__setstate__({\"key\": cookie.key, \"value\": value, \"coded_value\": coded_value})  # type: ignore[attr-defined]\n        return morsel\n\n    @staticmethod\n    def _is_domain_match(domain: str, hostname: str) -> bool:\n        \"\"\"Implements domain matching adhering to RFC 6265.\"\"\"\n        if hostname == domain:\n            return True\n\n        if not hostname.endswith(domain):\n            return False\n\n        non_matching = hostname[: -len(domain)]\n\n        if not non_matching.endswith(\".\"):\n            return False\n\n        return not is_ip_address(hostname)\n\n    @classmethod\n    def _parse_date(cls, date_str: str) -> int | None:\n        \"\"\"Implements date string parsing adhering to RFC 6265.\"\"\"\n        if not date_str:\n            return None\n\n        found_time = False\n        found_day = False\n        found_month = False\n        found_year = False\n\n        hour = minute = second = 0\n        day = 0\n        month = 0\n        year = 0\n\n        for token_match in cls.DATE_TOKENS_RE.finditer(date_str):\n            token = token_match.group(\"token\")\n\n            if not found_time:\n                time_match = cls.DATE_HMS_TIME_RE.match(token)\n                if time_match:\n                    found_time = True\n                    hour, minute, second = (int(s) for s in time_match.groups())\n                    continue\n\n            if not found_day:\n                day_match = cls.DATE_DAY_OF_MONTH_RE.match(token)\n                if day_match:\n                    found_day = True\n                    day = int(day_match.group())\n                    continue\n\n            if not found_month:\n                month_match = cls.DATE_MONTH_RE.match(token)\n                if month_match:\n                    found_month = True\n                    assert month_match.lastindex is not None\n                    month = month_match.lastindex\n                    continue\n\n            if not found_year:\n                year_match = cls.DATE_YEAR_RE.match(token)\n                if year_match:\n                    found_year = True\n                    year = int(year_match.group())\n\n        if 70 <= year <= 99:\n            year += 1900\n        elif 0 <= year <= 69:\n            year += 2000\n\n        if False in (found_day, found_month, found_year, found_time):\n            return None\n\n        if not 1 <= day <= 31:\n            return None\n\n        if year < 1601 or hour > 23 or minute > 59 or second > 59:\n            return None\n\n        return calendar.timegm((year, month, day, hour, minute, second, -1, -1, -1))\n\n\nclass DummyCookieJar(AbstractCookieJar):\n    \"\"\"Implements a dummy cookie storage.\n\n    It can be used with the ClientSession when no cookie processing is needed.\n\n    \"\"\"\n\n    def __iter__(self) -> \"Iterator[Morsel[str]]\":\n        while False:\n            yield None  # type: ignore[unreachable]\n\n    def __len__(self) -> int:\n        return 0\n\n    @property\n    def quote_cookie(self) -> bool:\n        return True\n\n    def clear(self, predicate: ClearCookiePredicate | None = None) -> None:\n        pass\n\n    def clear_domain(self, domain: str) -> None:\n        pass\n\n    def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None:\n        pass\n\n    def filter_cookies(self, request_url: URL) -> \"BaseCookie[str]\":\n        return SimpleCookie()\n"
  },
  {
    "path": "aiohttp/formdata.py",
    "content": "import io\nfrom collections import deque\nfrom collections.abc import Iterable\nfrom typing import Any\nfrom urllib.parse import urlencode\n\nfrom multidict import MultiDict, MultiDictProxy\n\nfrom . import hdrs, multipart, payload\nfrom .helpers import guess_filename\nfrom .payload import Payload\n\n__all__ = (\"FormData\",)\n\n\nclass FormData:\n    \"\"\"Helper class for form body generation.\n\n    Supports multipart/form-data and application/x-www-form-urlencoded.\n    \"\"\"\n\n    def __init__(\n        self,\n        fields: Iterable[Any] = (),\n        quote_fields: bool = True,\n        charset: str | None = None,\n        boundary: str | None = None,\n        *,\n        default_to_multipart: bool = False,\n    ) -> None:\n        self._boundary = boundary\n        self._writer = multipart.MultipartWriter(\"form-data\", boundary=self._boundary)\n        self._fields: list[Any] = []\n        self._is_multipart = default_to_multipart\n        self._quote_fields = quote_fields\n        self._charset = charset\n\n        if isinstance(fields, dict):\n            fields = list(fields.items())\n        elif not isinstance(fields, (list, tuple)):\n            fields = (fields,)\n        self.add_fields(*fields)\n\n    @property\n    def is_multipart(self) -> bool:\n        return self._is_multipart\n\n    def add_field(\n        self,\n        name: str,\n        value: Any,\n        *,\n        content_type: str | None = None,\n        filename: str | None = None,\n    ) -> None:\n        if isinstance(value, (io.IOBase, bytes, bytearray, memoryview)):\n            self._is_multipart = True\n\n        type_options: MultiDict[str] = MultiDict({\"name\": name})\n        if filename is not None and not isinstance(filename, str):\n            raise TypeError(\"filename must be an instance of str. Got: %s\" % filename)\n        if filename is None and isinstance(value, io.IOBase):\n            filename = guess_filename(value, name)\n        if filename is not None:\n            type_options[\"filename\"] = filename\n            self._is_multipart = True\n\n        headers = {}\n        if content_type is not None:\n            if not isinstance(content_type, str):\n                raise TypeError(\n                    \"content_type must be an instance of str. Got: %s\" % content_type\n                )\n            if \"\\r\" in content_type or \"\\n\" in content_type:\n                raise ValueError(\n                    \"Newline or carriage return detected in headers. \"\n                    \"Potential header injection attack.\"\n                )\n            headers[hdrs.CONTENT_TYPE] = content_type\n            self._is_multipart = True\n\n        self._fields.append((type_options, headers, value))\n\n    def add_fields(self, *fields: Any) -> None:\n        to_add: deque[Any] = deque(fields)\n\n        while to_add:\n            rec = to_add.popleft()\n\n            if isinstance(rec, io.IOBase):\n                k = guess_filename(rec, \"unknown\")\n                self.add_field(k, rec)  # type: ignore[arg-type]\n\n            elif isinstance(rec, (MultiDictProxy, MultiDict)):\n                to_add.extend(rec.items())\n\n            elif isinstance(rec, (list, tuple)) and len(rec) == 2:\n                k, fp = rec\n                self.add_field(k, fp)\n\n            else:\n                raise TypeError(\n                    \"Only io.IOBase, multidict and (name, file) \"\n                    \"pairs allowed, use .add_field() for passing \"\n                    f\"more complex parameters, got {rec!r}\"\n                )\n\n    def _gen_form_urlencoded(self) -> payload.BytesPayload:\n        # form data (x-www-form-urlencoded)\n        data = []\n        for type_options, _, value in self._fields:\n            if not isinstance(value, str):\n                raise TypeError(f\"expected str, got {value!r}\")\n            data.append((type_options[\"name\"], value))\n\n        charset = self._charset if self._charset is not None else \"utf-8\"\n\n        if charset == \"utf-8\":\n            content_type = \"application/x-www-form-urlencoded\"\n        else:\n            content_type = \"application/x-www-form-urlencoded; charset=%s\" % charset\n\n        return payload.BytesPayload(\n            urlencode(data, doseq=True, encoding=charset).encode(),\n            content_type=content_type,\n        )\n\n    def _gen_form_data(self) -> multipart.MultipartWriter:\n        \"\"\"Encode a list of fields using the multipart/form-data MIME format\"\"\"\n        for dispparams, headers, value in self._fields:\n            try:\n                if hdrs.CONTENT_TYPE in headers:\n                    part = payload.get_payload(\n                        value,\n                        content_type=headers[hdrs.CONTENT_TYPE],\n                        headers=headers,\n                        encoding=self._charset,\n                    )\n                else:\n                    part = payload.get_payload(\n                        value, headers=headers, encoding=self._charset\n                    )\n            except Exception as exc:\n                raise TypeError(\n                    \"Can not serialize value type: %r\\n \"\n                    \"headers: %r\\n value: %r\" % (type(value), headers, value)\n                ) from exc\n\n            if dispparams:\n                part.set_content_disposition(\n                    \"form-data\", quote_fields=self._quote_fields, **dispparams\n                )\n                # FIXME cgi.FieldStorage doesn't likes body parts with\n                # Content-Length which were sent via chunked transfer encoding\n                assert part.headers is not None\n                part.headers.popall(hdrs.CONTENT_LENGTH, None)\n\n            self._writer.append_payload(part)\n\n        self._fields.clear()\n        return self._writer\n\n    def __call__(self) -> Payload:\n        if self._is_multipart:\n            return self._gen_form_data()\n        else:\n            return self._gen_form_urlencoded()\n"
  },
  {
    "path": "aiohttp/hdrs.py",
    "content": "\"\"\"HTTP Headers constants.\"\"\"\n\n# After changing the file content call ./tools/gen.py\n# to regenerate the headers parser\nimport itertools\nfrom typing import Final\n\nfrom multidict import istr\n\nMETH_ANY: Final[str] = \"*\"\nMETH_CONNECT: Final[str] = \"CONNECT\"\nMETH_HEAD: Final[str] = \"HEAD\"\nMETH_GET: Final[str] = \"GET\"\nMETH_DELETE: Final[str] = \"DELETE\"\nMETH_OPTIONS: Final[str] = \"OPTIONS\"\nMETH_PATCH: Final[str] = \"PATCH\"\nMETH_POST: Final[str] = \"POST\"\nMETH_PUT: Final[str] = \"PUT\"\nMETH_TRACE: Final[str] = \"TRACE\"\n\nMETH_ALL: Final[set[str]] = {\n    METH_CONNECT,\n    METH_HEAD,\n    METH_GET,\n    METH_DELETE,\n    METH_OPTIONS,\n    METH_PATCH,\n    METH_POST,\n    METH_PUT,\n    METH_TRACE,\n}\n\nACCEPT: Final[istr] = istr(\"Accept\")\nACCEPT_CHARSET: Final[istr] = istr(\"Accept-Charset\")\nACCEPT_ENCODING: Final[istr] = istr(\"Accept-Encoding\")\nACCEPT_LANGUAGE: Final[istr] = istr(\"Accept-Language\")\nACCEPT_RANGES: Final[istr] = istr(\"Accept-Ranges\")\nACCESS_CONTROL_MAX_AGE: Final[istr] = istr(\"Access-Control-Max-Age\")\nACCESS_CONTROL_ALLOW_CREDENTIALS: Final[istr] = istr(\"Access-Control-Allow-Credentials\")\nACCESS_CONTROL_ALLOW_HEADERS: Final[istr] = istr(\"Access-Control-Allow-Headers\")\nACCESS_CONTROL_ALLOW_METHODS: Final[istr] = istr(\"Access-Control-Allow-Methods\")\nACCESS_CONTROL_ALLOW_ORIGIN: Final[istr] = istr(\"Access-Control-Allow-Origin\")\nACCESS_CONTROL_EXPOSE_HEADERS: Final[istr] = istr(\"Access-Control-Expose-Headers\")\nACCESS_CONTROL_REQUEST_HEADERS: Final[istr] = istr(\"Access-Control-Request-Headers\")\nACCESS_CONTROL_REQUEST_METHOD: Final[istr] = istr(\"Access-Control-Request-Method\")\nAGE: Final[istr] = istr(\"Age\")\nALLOW: Final[istr] = istr(\"Allow\")\nAUTHORIZATION: Final[istr] = istr(\"Authorization\")\nCACHE_CONTROL: Final[istr] = istr(\"Cache-Control\")\nCONNECTION: Final[istr] = istr(\"Connection\")\nCONTENT_DISPOSITION: Final[istr] = istr(\"Content-Disposition\")\nCONTENT_ENCODING: Final[istr] = istr(\"Content-Encoding\")\nCONTENT_LANGUAGE: Final[istr] = istr(\"Content-Language\")\nCONTENT_LENGTH: Final[istr] = istr(\"Content-Length\")\nCONTENT_LOCATION: Final[istr] = istr(\"Content-Location\")\nCONTENT_MD5: Final[istr] = istr(\"Content-MD5\")\nCONTENT_RANGE: Final[istr] = istr(\"Content-Range\")\nCONTENT_TRANSFER_ENCODING: Final[istr] = istr(\"Content-Transfer-Encoding\")\nCONTENT_TYPE: Final[istr] = istr(\"Content-Type\")\nCOOKIE: Final[istr] = istr(\"Cookie\")\nDATE: Final[istr] = istr(\"Date\")\nDESTINATION: Final[istr] = istr(\"Destination\")\nDIGEST: Final[istr] = istr(\"Digest\")\nETAG: Final[istr] = istr(\"Etag\")\nEXPECT: Final[istr] = istr(\"Expect\")\nEXPIRES: Final[istr] = istr(\"Expires\")\nFORWARDED: Final[istr] = istr(\"Forwarded\")\nFROM: Final[istr] = istr(\"From\")\nHOST: Final[istr] = istr(\"Host\")\nIF_MATCH: Final[istr] = istr(\"If-Match\")\nIF_MODIFIED_SINCE: Final[istr] = istr(\"If-Modified-Since\")\nIF_NONE_MATCH: Final[istr] = istr(\"If-None-Match\")\nIF_RANGE: Final[istr] = istr(\"If-Range\")\nIF_UNMODIFIED_SINCE: Final[istr] = istr(\"If-Unmodified-Since\")\nKEEP_ALIVE: Final[istr] = istr(\"Keep-Alive\")\nLAST_EVENT_ID: Final[istr] = istr(\"Last-Event-ID\")\nLAST_MODIFIED: Final[istr] = istr(\"Last-Modified\")\nLINK: Final[istr] = istr(\"Link\")\nLOCATION: Final[istr] = istr(\"Location\")\nMAX_FORWARDS: Final[istr] = istr(\"Max-Forwards\")\nORIGIN: Final[istr] = istr(\"Origin\")\nPRAGMA: Final[istr] = istr(\"Pragma\")\nPROXY_AUTHENTICATE: Final[istr] = istr(\"Proxy-Authenticate\")\nPROXY_AUTHORIZATION: Final[istr] = istr(\"Proxy-Authorization\")\nRANGE: Final[istr] = istr(\"Range\")\nREFERER: Final[istr] = istr(\"Referer\")\nRETRY_AFTER: Final[istr] = istr(\"Retry-After\")\nSEC_WEBSOCKET_ACCEPT: Final[istr] = istr(\"Sec-WebSocket-Accept\")\nSEC_WEBSOCKET_VERSION: Final[istr] = istr(\"Sec-WebSocket-Version\")\nSEC_WEBSOCKET_PROTOCOL: Final[istr] = istr(\"Sec-WebSocket-Protocol\")\nSEC_WEBSOCKET_EXTENSIONS: Final[istr] = istr(\"Sec-WebSocket-Extensions\")\nSEC_WEBSOCKET_KEY: Final[istr] = istr(\"Sec-WebSocket-Key\")\nSEC_WEBSOCKET_KEY1: Final[istr] = istr(\"Sec-WebSocket-Key1\")\nSERVER: Final[istr] = istr(\"Server\")\nSET_COOKIE: Final[istr] = istr(\"Set-Cookie\")\nTE: Final[istr] = istr(\"TE\")\nTRAILER: Final[istr] = istr(\"Trailer\")\nTRANSFER_ENCODING: Final[istr] = istr(\"Transfer-Encoding\")\nUPGRADE: Final[istr] = istr(\"Upgrade\")\nURI: Final[istr] = istr(\"URI\")\nUSER_AGENT: Final[istr] = istr(\"User-Agent\")\nVARY: Final[istr] = istr(\"Vary\")\nVIA: Final[istr] = istr(\"Via\")\nWANT_DIGEST: Final[istr] = istr(\"Want-Digest\")\nWARNING: Final[istr] = istr(\"Warning\")\nWWW_AUTHENTICATE: Final[istr] = istr(\"WWW-Authenticate\")\nX_FORWARDED_FOR: Final[istr] = istr(\"X-Forwarded-For\")\nX_FORWARDED_HOST: Final[istr] = istr(\"X-Forwarded-Host\")\nX_FORWARDED_PROTO: Final[istr] = istr(\"X-Forwarded-Proto\")\n\n# These are the upper/lower case variants of the headers/methods\n# Example: {'hOst', 'host', 'HoST', 'HOSt', 'hOsT', 'HosT', 'hoSt', ...}\nMETH_HEAD_ALL: Final = frozenset(\n    map(\"\".join, itertools.product(*zip(METH_HEAD.upper(), METH_HEAD.lower())))\n)\nMETH_CONNECT_ALL: Final = frozenset(\n    map(\"\".join, itertools.product(*zip(METH_CONNECT.upper(), METH_CONNECT.lower())))\n)\nHOST_ALL: Final = frozenset(\n    map(\"\".join, itertools.product(*zip(HOST.upper(), HOST.lower())))\n)\n"
  },
  {
    "path": "aiohttp/helpers.py",
    "content": "\"\"\"Various helper functions\"\"\"\n\nimport asyncio\nimport base64\nimport binascii\nimport contextlib\nimport dataclasses\nimport datetime\nimport enum\nimport functools\nimport inspect\nimport netrc\nimport os\nimport platform\nimport re\nimport sys\nimport time\nimport warnings\nimport weakref\nfrom collections import namedtuple\nfrom collections.abc import Callable, Iterable, Iterator, Mapping\nfrom contextlib import suppress\nfrom email.message import EmailMessage\nfrom email.parser import HeaderParser\nfrom email.policy import HTTP\nfrom email.utils import parsedate\nfrom http.cookies import SimpleCookie\nfrom math import ceil\nfrom pathlib import Path\nfrom types import MappingProxyType, TracebackType\nfrom typing import (\n    TYPE_CHECKING,\n    Any,\n    ContextManager,\n    Generic,\n    Optional,\n    Protocol,\n    TypeVar,\n    Union,\n    final,\n    get_args,\n    overload,\n)\nfrom urllib.parse import quote\nfrom urllib.request import getproxies, proxy_bypass\n\nfrom multidict import CIMultiDict, MultiDict, MultiDictProxy, MultiMapping\nfrom propcache.api import under_cached_property as reify\nfrom yarl import URL\n\nfrom . import hdrs\nfrom .log import client_logger\nfrom .typedefs import PathLike  # noqa\n\nif sys.version_info >= (3, 11):\n    import asyncio as async_timeout\nelse:\n    import async_timeout\n\nif TYPE_CHECKING:\n    from dataclasses import dataclass as frozen_dataclass_decorator\nelse:\n    frozen_dataclass_decorator = functools.partial(\n        dataclasses.dataclass, frozen=True, slots=True\n    )\n\n__all__ = (\"BasicAuth\", \"ChainMapProxy\", \"ETag\", \"frozen_dataclass_decorator\", \"reify\")\n\nCOOKIE_MAX_LENGTH = 4096\n\n_T = TypeVar(\"_T\")\n_S = TypeVar(\"_S\")\n\n_SENTINEL = enum.Enum(\"_SENTINEL\", \"sentinel\")\nsentinel = _SENTINEL.sentinel\n\nNO_EXTENSIONS = bool(os.environ.get(\"AIOHTTP_NO_EXTENSIONS\"))\n\n# https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.1\nEMPTY_BODY_STATUS_CODES = frozenset((204, 304, *range(100, 200)))\n# https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.1\n# https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.2\nEMPTY_BODY_METHODS = hdrs.METH_HEAD_ALL\n\nDEBUG = sys.flags.dev_mode or (\n    not sys.flags.ignore_environment and bool(os.environ.get(\"PYTHONASYNCIODEBUG\"))\n)\n\n\nCHAR = {chr(i) for i in range(0, 128)}\nCTL = {chr(i) for i in range(0, 32)} | {\n    chr(127),\n}\nSEPARATORS = {\n    \"(\",\n    \")\",\n    \"<\",\n    \">\",\n    \"@\",\n    \",\",\n    \";\",\n    \":\",\n    \"\\\\\",\n    '\"',\n    \"/\",\n    \"[\",\n    \"]\",\n    \"?\",\n    \"=\",\n    \"{\",\n    \"}\",\n    \" \",\n    chr(9),\n}\nTOKEN = CHAR ^ CTL ^ SEPARATORS\n\n\njson_re = re.compile(r\"^(?:application/|[\\w.-]+/[\\w.+-]+?\\+)json$\", re.IGNORECASE)\n\n\nclass BasicAuth(namedtuple(\"BasicAuth\", [\"login\", \"password\", \"encoding\"])):\n    \"\"\"Http basic authentication helper.\"\"\"\n\n    def __new__(\n        cls, login: str, password: str = \"\", encoding: str = \"latin1\"\n    ) -> \"BasicAuth\":\n        if login is None:\n            raise ValueError(\"None is not allowed as login value\")\n\n        if password is None:\n            raise ValueError(\"None is not allowed as password value\")\n\n        if \":\" in login:\n            raise ValueError('A \":\" is not allowed in login (RFC 1945#section-11.1)')\n\n        return super().__new__(cls, login, password, encoding)\n\n    @classmethod\n    def decode(cls, auth_header: str, encoding: str = \"latin1\") -> \"BasicAuth\":\n        \"\"\"Create a BasicAuth object from an Authorization HTTP header.\"\"\"\n        try:\n            auth_type, encoded_credentials = auth_header.split(\" \", 1)\n        except ValueError:\n            raise ValueError(\"Could not parse authorization header.\")\n\n        if auth_type.lower() != \"basic\":\n            raise ValueError(\"Unknown authorization method %s\" % auth_type)\n\n        try:\n            decoded = base64.b64decode(\n                encoded_credentials.encode(\"ascii\"), validate=True\n            ).decode(encoding)\n        except binascii.Error:\n            raise ValueError(\"Invalid base64 encoding.\")\n\n        try:\n            # RFC 2617 HTTP Authentication\n            # https://www.ietf.org/rfc/rfc2617.txt\n            # the colon must be present, but the username and password may be\n            # otherwise blank.\n            username, password = decoded.split(\":\", 1)\n        except ValueError:\n            raise ValueError(\"Invalid credentials.\")\n\n        return cls(username, password, encoding=encoding)\n\n    @classmethod\n    def from_url(cls, url: URL, *, encoding: str = \"latin1\") -> Optional[\"BasicAuth\"]:\n        \"\"\"Create BasicAuth from url.\"\"\"\n        if not isinstance(url, URL):\n            raise TypeError(\"url should be yarl.URL instance\")\n        # Check raw_user and raw_password first as yarl is likely\n        # to already have these values parsed from the netloc in the cache.\n        if url.raw_user is None and url.raw_password is None:\n            return None\n        return cls(url.user or \"\", url.password or \"\", encoding=encoding)\n\n    def encode(self) -> str:\n        \"\"\"Encode credentials.\"\"\"\n        creds = (f\"{self.login}:{self.password}\").encode(self.encoding)\n        return \"Basic %s\" % base64.b64encode(creds).decode(self.encoding)\n\n\ndef strip_auth_from_url(url: URL) -> tuple[URL, BasicAuth | None]:\n    \"\"\"Remove user and password from URL if present and return BasicAuth object.\"\"\"\n    # Check raw_user and raw_password first as yarl is likely\n    # to already have these values parsed from the netloc in the cache.\n    if url.raw_user is None and url.raw_password is None:\n        return url, None\n    return url.with_user(None), BasicAuth(url.user or \"\", url.password or \"\")\n\n\ndef netrc_from_env() -> netrc.netrc | None:\n    \"\"\"Load netrc from file.\n\n    Attempt to load it from the path specified by the env-var\n    NETRC or in the default location in the user's home directory.\n\n    Returns None if it couldn't be found or fails to parse.\n    \"\"\"\n    netrc_env = os.environ.get(\"NETRC\")\n\n    if netrc_env is not None:\n        netrc_path = Path(netrc_env)\n    else:\n        try:\n            home_dir = Path.home()\n        except RuntimeError as e:\n            # if pathlib can't resolve home, it may raise a RuntimeError\n            client_logger.debug(\n                \"Could not resolve home directory when \"\n                \"trying to look for .netrc file: %s\",\n                e,\n            )\n            return None\n\n        netrc_path = home_dir / (\n            \"_netrc\" if platform.system() == \"Windows\" else \".netrc\"\n        )\n\n    try:\n        return netrc.netrc(str(netrc_path))\n    except netrc.NetrcParseError as e:\n        client_logger.warning(\"Could not parse .netrc file: %s\", e)\n    except OSError as e:\n        netrc_exists = False\n        with contextlib.suppress(OSError):\n            netrc_exists = netrc_path.is_file()\n        # we couldn't read the file (doesn't exist, permissions, etc.)\n        if netrc_env or netrc_exists:\n            # only warn if the environment wanted us to load it,\n            # or it appears like the default file does actually exist\n            client_logger.warning(\"Could not read .netrc file: %s\", e)\n\n    return None\n\n\n@frozen_dataclass_decorator\nclass ProxyInfo:\n    proxy: URL\n    proxy_auth: BasicAuth | None\n\n\ndef basicauth_from_netrc(netrc_obj: netrc.netrc | None, host: str) -> BasicAuth:\n    \"\"\"\n    Return :py:class:`~aiohttp.BasicAuth` credentials for ``host`` from ``netrc_obj``.\n\n    :raises LookupError: if ``netrc_obj`` is :py:data:`None` or if no\n            entry is found for the ``host``.\n    \"\"\"\n    if netrc_obj is None:\n        raise LookupError(\"No .netrc file found\")\n    auth_from_netrc = netrc_obj.authenticators(host)\n\n    if auth_from_netrc is None:\n        raise LookupError(f\"No entry for {host!s} found in the `.netrc` file.\")\n    login, account, password = auth_from_netrc\n\n    # TODO(PY311): username = login or account\n    # Up to python 3.10, account could be None if not specified,\n    # and login will be empty string if not specified. From 3.11,\n    # login and account will be empty string if not specified.\n    username = login if (login or account is None) else account\n\n    # TODO(PY311): Remove this, as password will be empty string\n    # if not specified\n    if password is None:\n        password = \"\"  # type: ignore[unreachable]\n\n    return BasicAuth(username, password)\n\n\ndef proxies_from_env() -> dict[str, ProxyInfo]:\n    proxy_urls = {\n        k: URL(v)\n        for k, v in getproxies().items()\n        if k in (\"http\", \"https\", \"ws\", \"wss\")\n    }\n    netrc_obj = netrc_from_env()\n    stripped = {k: strip_auth_from_url(v) for k, v in proxy_urls.items()}\n    ret = {}\n    for proto, val in stripped.items():\n        proxy, auth = val\n        if proxy.scheme in (\"https\", \"wss\"):\n            client_logger.warning(\n                \"%s proxies %s are not supported, ignoring\", proxy.scheme.upper(), proxy\n            )\n            continue\n        if netrc_obj and auth is None:\n            if proxy.host is not None:\n                try:\n                    auth = basicauth_from_netrc(netrc_obj, proxy.host)\n                except LookupError:\n                    auth = None\n        ret[proto] = ProxyInfo(proxy, auth)\n    return ret\n\n\ndef get_env_proxy_for_url(url: URL) -> tuple[URL, BasicAuth | None]:\n    \"\"\"Get a permitted proxy for the given URL from the env.\"\"\"\n    if url.host is not None and proxy_bypass(url.host):\n        raise LookupError(f\"Proxying is disallowed for `{url.host!r}`\")\n\n    proxies_in_env = proxies_from_env()\n    try:\n        proxy_info = proxies_in_env[url.scheme]\n    except KeyError:\n        raise LookupError(f\"No proxies found for `{url!s}` in the env\")\n    else:\n        return proxy_info.proxy, proxy_info.proxy_auth\n\n\n@frozen_dataclass_decorator\nclass MimeType:\n    type: str\n    subtype: str\n    suffix: str\n    parameters: \"MultiDictProxy[str]\"\n\n\n@functools.lru_cache(maxsize=56)\ndef parse_mimetype(mimetype: str) -> MimeType:\n    \"\"\"Parses a MIME type into its components.\n\n    mimetype is a MIME type string.\n\n    Returns a MimeType object.\n\n    Example:\n\n    >>> parse_mimetype('text/html; charset=utf-8')\n    MimeType(type='text', subtype='html', suffix='',\n             parameters={'charset': 'utf-8'})\n\n    \"\"\"\n    if not mimetype:\n        return MimeType(\n            type=\"\", subtype=\"\", suffix=\"\", parameters=MultiDictProxy(MultiDict())\n        )\n\n    parts = mimetype.split(\";\")\n    params: MultiDict[str] = MultiDict()\n    for item in parts[1:]:\n        if not item:\n            continue\n        key, _, value = item.partition(\"=\")\n        params.add(key.lower().strip(), value.strip(' \"'))\n\n    fulltype = parts[0].strip().lower()\n    if fulltype == \"*\":\n        fulltype = \"*/*\"\n\n    mtype, _, stype = fulltype.partition(\"/\")\n    stype, _, suffix = stype.partition(\"+\")\n\n    return MimeType(\n        type=mtype, subtype=stype, suffix=suffix, parameters=MultiDictProxy(params)\n    )\n\n\nclass EnsureOctetStream(EmailMessage):\n    def __init__(self) -> None:\n        super().__init__()\n        # https://www.rfc-editor.org/rfc/rfc9110#section-8.3-5\n        self.set_default_type(\"application/octet-stream\")\n\n    def get_content_type(self) -> str:\n        \"\"\"Re-implementation from Message\n\n        Returns application/octet-stream in place of plain/text when\n        value is wrong.\n\n        The way this class is used guarantees that content-type will\n        be present so simplify the checks wrt to the base implementation.\n        \"\"\"\n        value = self.get(\"content-type\", \"\").lower()\n\n        # Based on the implementation of _splitparam in the standard library\n        ctype, _, _ = value.partition(\";\")\n        ctype = ctype.strip()\n        if ctype.count(\"/\") != 1:\n            return self.get_default_type()\n        return ctype\n\n\n@functools.lru_cache(maxsize=56)\ndef parse_content_type(raw: str) -> tuple[str, MappingProxyType[str, str]]:\n    \"\"\"Parse Content-Type header.\n\n    Returns a tuple of the parsed content type and a\n    MappingProxyType of parameters. The default returned value\n    is `application/octet-stream`\n    \"\"\"\n    msg = HeaderParser(EnsureOctetStream, policy=HTTP).parsestr(f\"Content-Type: {raw}\")\n    content_type = msg.get_content_type()\n    params = msg.get_params(())\n    content_dict = dict(params[1:])  # First element is content type again\n    return content_type, MappingProxyType(content_dict)\n\n\ndef guess_filename(obj: Any, default: str | None = None) -> str | None:\n    name = getattr(obj, \"name\", None)\n    if name and isinstance(name, str) and name[0] != \"<\" and name[-1] != \">\":\n        return Path(name).name\n    return default\n\n\nnot_qtext_re = re.compile(r\"[^\\041\\043-\\133\\135-\\176]\")\nQCONTENT = {chr(i) for i in range(0x20, 0x7F)} | {\"\\t\"}\n\n\ndef quoted_string(content: str) -> str:\n    \"\"\"Return 7-bit content as quoted-string.\n\n    Format content into a quoted-string as defined in RFC5322 for\n    Internet Message Format. Notice that this is not the 8-bit HTTP\n    format, but the 7-bit email format. Content must be in usascii or\n    a ValueError is raised.\n    \"\"\"\n    if not (QCONTENT > set(content)):\n        raise ValueError(f\"bad content for quoted-string {content!r}\")\n    return not_qtext_re.sub(lambda x: \"\\\\\" + x.group(0), content)\n\n\ndef content_disposition_header(\n    disptype: str,\n    quote_fields: bool = True,\n    _charset: str = \"utf-8\",\n    params: dict[str, str] | None = None,\n) -> str:\n    \"\"\"Sets ``Content-Disposition`` header for MIME.\n\n    This is the MIME payload Content-Disposition header from RFC 2183\n    and RFC 7579 section 4.2, not the HTTP Content-Disposition from\n    RFC 6266.\n\n    disptype is a disposition type: inline, attachment, form-data.\n    Should be valid extension token (see RFC 2183)\n\n    quote_fields performs value quoting to 7-bit MIME headers\n    according to RFC 7578. Set to quote_fields to False if recipient\n    can take 8-bit file names and field values.\n\n    _charset specifies the charset to use when quote_fields is True.\n\n    params is a dict with disposition params.\n    \"\"\"\n    if not disptype or not (TOKEN > set(disptype)):\n        raise ValueError(f\"bad content disposition type {disptype!r}\")\n\n    value = disptype\n    if params:\n        lparams = []\n        for key, val in params.items():\n            if not key or not (TOKEN > set(key)):\n                raise ValueError(f\"bad content disposition parameter {key!r}={val!r}\")\n            if quote_fields:\n                if key.lower() == \"filename\":\n                    qval = quote(val, \"\", encoding=_charset)\n                    lparams.append((key, '\"%s\"' % qval))\n                else:\n                    try:\n                        qval = quoted_string(val)\n                    except ValueError:\n                        qval = \"\".join(\n                            (_charset, \"''\", quote(val, \"\", encoding=_charset))\n                        )\n                        lparams.append((key + \"*\", qval))\n                    else:\n                        lparams.append((key, '\"%s\"' % qval))\n            else:\n                qval = val.replace(\"\\\\\", \"\\\\\\\\\").replace('\"', '\\\\\"')\n                lparams.append((key, '\"%s\"' % qval))\n        sparams = \"; \".join(\"=\".join(pair) for pair in lparams)\n        value = \"; \".join((value, sparams))\n    return value\n\n\ndef is_expected_content_type(\n    response_content_type: str, expected_content_type: str\n) -> bool:\n    \"\"\"Checks if received content type is processable as an expected one.\n\n    Both arguments should be given without parameters.\n    \"\"\"\n    if expected_content_type == \"application/json\":\n        return json_re.match(response_content_type) is not None\n    return expected_content_type in response_content_type\n\n\ndef is_ip_address(host: str | None) -> bool:\n    \"\"\"Check if host looks like an IP Address.\n\n    This check is only meant as a heuristic to ensure that\n    a host is not a domain name.\n    \"\"\"\n    if not host:\n        return False\n    # For a host to be an ipv4 address, it must be all numeric.\n    # The host must contain a colon to be an IPv6 address.\n    return \":\" in host or host.replace(\".\", \"\").isdigit()\n\n\n_cached_current_datetime: int | None = None\n_cached_formatted_datetime = \"\"\n\n\ndef rfc822_formatted_time() -> str:\n    global _cached_current_datetime\n    global _cached_formatted_datetime\n\n    now = int(time.time())\n    if now != _cached_current_datetime:\n        # Weekday and month names for HTTP date/time formatting;\n        # always English!\n        # Tuples are constants stored in codeobject!\n        _weekdayname = (\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\")\n        _monthname = (\n            \"\",  # Dummy so we can use 1-based month numbers\n            \"Jan\",\n            \"Feb\",\n            \"Mar\",\n            \"Apr\",\n            \"May\",\n            \"Jun\",\n            \"Jul\",\n            \"Aug\",\n            \"Sep\",\n            \"Oct\",\n            \"Nov\",\n            \"Dec\",\n        )\n\n        year, month, day, hh, mm, ss, wd, *tail = time.gmtime(now)\n        _cached_formatted_datetime = \"%s, %02d %3s %4d %02d:%02d:%02d GMT\" % (\n            _weekdayname[wd],\n            day,\n            _monthname[month],\n            year,\n            hh,\n            mm,\n            ss,\n        )\n        _cached_current_datetime = now\n    return _cached_formatted_datetime\n\n\ndef _weakref_handle(info: \"tuple[weakref.ref[object], str]\") -> None:\n    ref, name = info\n    ob = ref()\n    if ob is not None:\n        with suppress(Exception):\n            getattr(ob, name)()\n\n\ndef weakref_handle(\n    ob: object,\n    name: str,\n    timeout: float | None,\n    loop: asyncio.AbstractEventLoop,\n    timeout_ceil_threshold: float = 5,\n) -> asyncio.TimerHandle | None:\n    if timeout is not None and timeout > 0:\n        when = loop.time() + timeout\n        if timeout >= timeout_ceil_threshold:\n            when = ceil(when)\n\n        return loop.call_at(when, _weakref_handle, (weakref.ref(ob), name))\n    return None\n\n\ndef call_later(\n    cb: Callable[[], Any],\n    timeout: float | None,\n    loop: asyncio.AbstractEventLoop,\n    timeout_ceil_threshold: float = 5,\n) -> asyncio.TimerHandle | None:\n    if timeout is None or timeout <= 0:\n        return None\n    now = loop.time()\n    when = calculate_timeout_when(now, timeout, timeout_ceil_threshold)\n    return loop.call_at(when, cb)\n\n\ndef calculate_timeout_when(\n    loop_time: float,\n    timeout: float,\n    timeout_ceiling_threshold: float,\n) -> float:\n    \"\"\"Calculate when to execute a timeout.\"\"\"\n    when = loop_time + timeout\n    if timeout > timeout_ceiling_threshold:\n        return ceil(when)\n    return when\n\n\nclass TimeoutHandle:\n    \"\"\"Timeout handle\"\"\"\n\n    __slots__ = (\"_timeout\", \"_loop\", \"_ceil_threshold\", \"_callbacks\")\n\n    def __init__(\n        self,\n        loop: asyncio.AbstractEventLoop,\n        timeout: float | None,\n        ceil_threshold: float = 5,\n    ) -> None:\n        self._timeout = timeout\n        self._loop = loop\n        self._ceil_threshold = ceil_threshold\n        self._callbacks: list[\n            tuple[Callable[..., None], tuple[Any, ...], dict[str, Any]]\n        ] = []\n\n    def register(\n        self, callback: Callable[..., None], *args: Any, **kwargs: Any\n    ) -> None:\n        self._callbacks.append((callback, args, kwargs))\n\n    def close(self) -> None:\n        self._callbacks.clear()\n\n    def start(self) -> asyncio.TimerHandle | None:\n        timeout = self._timeout\n        if timeout is not None and timeout > 0:\n            when = self._loop.time() + timeout\n            if timeout >= self._ceil_threshold:\n                when = ceil(when)\n            return self._loop.call_at(when, self.__call__)\n        else:\n            return None\n\n    def timer(self) -> \"BaseTimerContext\":\n        if self._timeout is not None and self._timeout > 0:\n            timer = TimerContext(self._loop)\n            self.register(timer.timeout)\n            return timer\n        else:\n            return TimerNoop()\n\n    def __call__(self) -> None:\n        for cb, args, kwargs in self._callbacks:\n            with suppress(Exception):\n                cb(*args, **kwargs)\n\n        self._callbacks.clear()\n\n\nclass BaseTimerContext(ContextManager[\"BaseTimerContext\"]):\n\n    __slots__ = ()\n\n    def assert_timeout(self) -> None:\n        \"\"\"Raise TimeoutError if timeout has been exceeded.\"\"\"\n\n\nclass TimerNoop(BaseTimerContext):\n\n    __slots__ = ()\n\n    def __enter__(self) -> BaseTimerContext:\n        return self\n\n    def __exit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc_val: BaseException | None,\n        exc_tb: TracebackType | None,\n    ) -> None:\n        return\n\n\nclass TimerContext(BaseTimerContext):\n    \"\"\"Low resolution timeout context manager\"\"\"\n\n    __slots__ = (\"_loop\", \"_tasks\", \"_cancelled\", \"_cancelling\")\n\n    def __init__(self, loop: asyncio.AbstractEventLoop) -> None:\n        self._loop = loop\n        self._tasks: list[asyncio.Task[Any]] = []\n        self._cancelled = False\n        self._cancelling = 0\n\n    def assert_timeout(self) -> None:\n        \"\"\"Raise TimeoutError if timer has already been cancelled.\"\"\"\n        if self._cancelled:\n            raise asyncio.TimeoutError from None\n\n    def __enter__(self) -> BaseTimerContext:\n        task = asyncio.current_task(loop=self._loop)\n        if task is None:\n            raise RuntimeError(\"Timeout context manager should be used inside a task\")\n\n        if sys.version_info >= (3, 11):\n            # Remember if the task was already cancelling\n            # so when we __exit__ we can decide if we should\n            # raise asyncio.TimeoutError or let the cancellation propagate\n            self._cancelling = task.cancelling()\n\n        if self._cancelled:\n            raise asyncio.TimeoutError from None\n\n        self._tasks.append(task)\n        return self\n\n    def __exit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc_val: BaseException | None,\n        exc_tb: TracebackType | None,\n    ) -> bool | None:\n        enter_task: asyncio.Task[Any] | None = None\n        if self._tasks:\n            enter_task = self._tasks.pop()\n\n        if exc_type is asyncio.CancelledError and self._cancelled:\n            assert enter_task is not None\n            # The timeout was hit, and the task was cancelled\n            # so we need to uncancel the last task that entered the context manager\n            # since the cancellation should not leak out of the context manager\n            if sys.version_info >= (3, 11):\n                # If the task was already cancelling don't raise\n                # asyncio.TimeoutError and instead return None\n                # to allow the cancellation to propagate\n                if enter_task.uncancel() > self._cancelling:\n                    return None\n            raise asyncio.TimeoutError from exc_val\n        return None\n\n    def timeout(self) -> None:\n        if not self._cancelled:\n            for task in set(self._tasks):\n                task.cancel()\n\n            self._cancelled = True\n\n\ndef ceil_timeout(\n    delay: float | None, ceil_threshold: float = 5\n) -> async_timeout.Timeout:\n    if delay is None or delay <= 0:\n        return async_timeout.timeout(None)\n\n    loop = asyncio.get_running_loop()\n    now = loop.time()\n    when = now + delay\n    if delay > ceil_threshold:\n        when = ceil(when)\n    return async_timeout.timeout_at(when)\n\n\nclass HeadersMixin:\n    \"\"\"Mixin for handling headers.\"\"\"\n\n    _headers: MultiMapping[str]\n    _content_type: str | None = None\n    _content_dict: dict[str, str] | None = None\n    _stored_content_type: str | None | _SENTINEL = sentinel\n\n    def _parse_content_type(self, raw: str | None) -> None:\n        self._stored_content_type = raw\n        if raw is None:\n            # default value according to RFC 2616\n            self._content_type = \"application/octet-stream\"\n            self._content_dict = {}\n        else:\n            content_type, content_mapping_proxy = parse_content_type(raw)\n            self._content_type = content_type\n            # _content_dict needs to be mutable so we can update it\n            self._content_dict = content_mapping_proxy.copy()\n\n    @property\n    def content_type(self) -> str:\n        \"\"\"The value of content part for Content-Type HTTP header.\"\"\"\n        raw = self._headers.get(hdrs.CONTENT_TYPE)\n        if self._stored_content_type != raw:\n            self._parse_content_type(raw)\n        assert self._content_type is not None\n        return self._content_type\n\n    @property\n    def charset(self) -> str | None:\n        \"\"\"The value of charset part for Content-Type HTTP header.\"\"\"\n        raw = self._headers.get(hdrs.CONTENT_TYPE)\n        if self._stored_content_type != raw:\n            self._parse_content_type(raw)\n        assert self._content_dict is not None\n        return self._content_dict.get(\"charset\")\n\n    @property\n    def content_length(self) -> int | None:\n        \"\"\"The value of Content-Length HTTP header.\"\"\"\n        content_length = self._headers.get(hdrs.CONTENT_LENGTH)\n        return None if content_length is None else int(content_length)\n\n\ndef set_result(fut: \"asyncio.Future[_T]\", result: _T) -> None:\n    if not fut.done():\n        fut.set_result(result)\n\n\n_EXC_SENTINEL = BaseException()\n\n\nclass ErrorableProtocol(Protocol):\n    def set_exception(\n        self,\n        exc: type[BaseException] | BaseException,\n        exc_cause: BaseException = ...,\n    ) -> None: ...\n\n\ndef set_exception(\n    fut: Union[\"asyncio.Future[_T]\", ErrorableProtocol],\n    exc: type[BaseException] | BaseException,\n    exc_cause: BaseException = _EXC_SENTINEL,\n) -> None:\n    \"\"\"Set future exception.\n\n    If the future is marked as complete, this function is a no-op.\n\n    :param exc_cause: An exception that is a direct cause of ``exc``.\n                      Only set if provided.\n    \"\"\"\n    if asyncio.isfuture(fut) and fut.done():\n        return\n\n    exc_is_sentinel = exc_cause is _EXC_SENTINEL\n    exc_causes_itself = exc is exc_cause\n    if not exc_is_sentinel and not exc_causes_itself:\n        exc.__cause__ = exc_cause\n\n    fut.set_exception(exc)\n\n\n@functools.total_ordering\nclass BaseKey(Generic[_T]):\n    \"\"\"Base for concrete context storage key classes.\n\n    Each storage is provided with its own sub-class for the sake of some additional type safety.\n    \"\"\"\n\n    __slots__ = (\"_name\", \"_t\", \"__orig_class__\")\n\n    # This may be set by Python when instantiating with a generic type. We need to\n    # support this, in order to support types that are not concrete classes,\n    # like Iterable, which can't be passed as the second parameter to __init__.\n    __orig_class__: type[object]\n\n    # TODO(PY314): Change Type to TypeForm (this should resolve unreachable below).\n    def __init__(self, name: str, t: type[_T] | None = None):\n        # Prefix with module name to help deduplicate key names.\n        frame = inspect.currentframe()\n        while frame:\n            if frame.f_code.co_name == \"<module>\":\n                module: str = frame.f_globals[\"__name__\"]\n                break\n            frame = frame.f_back\n        else:\n            raise RuntimeError(\"Failed to get module name.\")\n\n        # https://github.com/python/mypy/issues/14209\n        self._name = module + \".\" + name  # type: ignore[possibly-undefined]\n        self._t = t\n\n    def __lt__(self, other: object) -> bool:\n        if isinstance(other, BaseKey):\n            return self._name < other._name\n        return True  # Order BaseKey above other types.\n\n    def __repr__(self) -> str:\n        t = self._t\n        if t is None:\n            with suppress(AttributeError):\n                # Set to type arg.\n                t = get_args(self.__orig_class__)[0]\n\n        if t is None:\n            t_repr = \"<<Unknown>>\"\n        elif isinstance(t, type):\n            if t.__module__ == \"builtins\":\n                t_repr = t.__qualname__\n            else:\n                t_repr = f\"{t.__module__}.{t.__qualname__}\"\n        else:\n            t_repr = repr(t)  # type: ignore[unreachable]\n        return f\"<{self.__class__.__name__}({self._name}, type={t_repr})>\"\n\n\nclass AppKey(BaseKey[_T]):\n    \"\"\"Keys for static typing support in Application.\"\"\"\n\n\nclass RequestKey(BaseKey[_T]):\n    \"\"\"Keys for static typing support in Request.\"\"\"\n\n\nclass ResponseKey(BaseKey[_T]):\n    \"\"\"Keys for static typing support in Response.\"\"\"\n\n\n@final\nclass ChainMapProxy(Mapping[str | AppKey[Any], Any]):\n    __slots__ = (\"_maps\",)\n\n    def __init__(self, maps: Iterable[Mapping[str | AppKey[Any], Any]]) -> None:\n        self._maps = tuple(maps)\n\n    def __init_subclass__(cls) -> None:\n        raise TypeError(\n            f\"Inheritance class {cls.__name__} from ChainMapProxy is forbidden\"\n        )\n\n    @overload  # type: ignore[override]\n    def __getitem__(self, key: AppKey[_T]) -> _T: ...\n\n    @overload\n    def __getitem__(self, key: str) -> Any: ...\n\n    def __getitem__(self, key: str | AppKey[_T]) -> Any:\n        for mapping in self._maps:\n            try:\n                return mapping[key]\n            except KeyError:\n                pass\n        raise KeyError(key)\n\n    @overload  # type: ignore[override]\n    def get(self, key: AppKey[_T], default: _S) -> _T | _S: ...\n\n    @overload\n    def get(self, key: AppKey[_T], default: None = ...) -> _T | None: ...\n\n    @overload\n    def get(self, key: str, default: Any = ...) -> Any: ...\n\n    def get(self, key: str | AppKey[_T], default: Any = None) -> Any:\n        try:\n            return self[key]\n        except KeyError:\n            return default\n\n    def __len__(self) -> int:\n        # reuses stored hash values if possible\n        return len(set().union(*self._maps))\n\n    def __iter__(self) -> Iterator[str | AppKey[Any]]:\n        d: dict[str | AppKey[Any], Any] = {}\n        for mapping in reversed(self._maps):\n            # reuses stored hash values if possible\n            d.update(mapping)\n        return iter(d)\n\n    def __contains__(self, key: object) -> bool:\n        return any(key in m for m in self._maps)\n\n    def __bool__(self) -> bool:\n        return any(self._maps)\n\n    def __repr__(self) -> str:\n        content = \", \".join(map(repr, self._maps))\n        return f\"ChainMapProxy({content})\"\n\n\nclass CookieMixin:\n    \"\"\"Mixin for handling cookies.\"\"\"\n\n    _cookies: SimpleCookie | None = None\n\n    @property\n    def cookies(self) -> SimpleCookie:\n        if self._cookies is None:\n            self._cookies = SimpleCookie()\n        return self._cookies\n\n    def set_cookie(\n        self,\n        name: str,\n        value: str,\n        *,\n        expires: str | None = None,\n        domain: str | None = None,\n        max_age: int | str | None = None,\n        path: str = \"/\",\n        secure: bool | None = None,\n        httponly: bool | None = None,\n        samesite: str | None = None,\n        partitioned: bool | None = None,\n    ) -> None:\n        \"\"\"Set or update response cookie.\n\n        Sets new cookie or updates existent with new value.\n        Also updates only those params which are not None.\n        \"\"\"\n        if self._cookies is None:\n            self._cookies = SimpleCookie()\n\n        self._cookies[name] = value\n        c = self._cookies[name]\n\n        if expires is not None:\n            c[\"expires\"] = expires\n        elif c.get(\"expires\") == \"Thu, 01 Jan 1970 00:00:00 GMT\":\n            del c[\"expires\"]\n\n        if domain is not None:\n            c[\"domain\"] = domain\n\n        if max_age is not None:\n            c[\"max-age\"] = str(max_age)\n        elif \"max-age\" in c:\n            del c[\"max-age\"]\n\n        c[\"path\"] = path\n\n        if secure is not None:\n            c[\"secure\"] = secure\n        if httponly is not None:\n            c[\"httponly\"] = httponly\n        if samesite is not None:\n            c[\"samesite\"] = samesite\n\n        if partitioned is not None:\n            c[\"partitioned\"] = partitioned\n\n        if DEBUG:\n            cookie_length = len(c.output(header=\"\")[1:])\n            if cookie_length > COOKIE_MAX_LENGTH:\n                warnings.warn(\n                    \"The size of is too large, it might get ignored by the client.\",\n                    UserWarning,\n                    stacklevel=2,\n                )\n\n    def del_cookie(\n        self,\n        name: str,\n        *,\n        domain: str | None = None,\n        path: str = \"/\",\n        secure: bool | None = None,\n        httponly: bool | None = None,\n        samesite: str | None = None,\n    ) -> None:\n        \"\"\"Delete cookie.\n\n        Creates new empty expired cookie.\n        \"\"\"\n        # TODO: do we need domain/path here?\n        if self._cookies is not None:\n            self._cookies.pop(name, None)\n        self.set_cookie(\n            name,\n            \"\",\n            max_age=0,\n            expires=\"Thu, 01 Jan 1970 00:00:00 GMT\",\n            domain=domain,\n            path=path,\n            secure=secure,\n            httponly=httponly,\n            samesite=samesite,\n        )\n\n\ndef populate_with_cookies(headers: \"CIMultiDict[str]\", cookies: SimpleCookie) -> None:\n    for cookie in cookies.values():\n        value = cookie.output(header=\"\")[1:]\n        headers.add(hdrs.SET_COOKIE, value)\n\n\n# https://tools.ietf.org/html/rfc7232#section-2.3\n_ETAGC = r\"[!\\x23-\\x7E\\x80-\\xff]+\"\n_ETAGC_RE = re.compile(_ETAGC)\n_QUOTED_ETAG = rf'(W/)?\"({_ETAGC})\"'\nQUOTED_ETAG_RE = re.compile(_QUOTED_ETAG)\nLIST_QUOTED_ETAG_RE = re.compile(rf\"({_QUOTED_ETAG})(?:\\s*,\\s*|$)|(.)\")\n\nETAG_ANY = \"*\"\n\n\n@frozen_dataclass_decorator\nclass ETag:\n    value: str\n    is_weak: bool = False\n\n\ndef validate_etag_value(value: str) -> None:\n    if value != ETAG_ANY and not _ETAGC_RE.fullmatch(value):\n        raise ValueError(\n            f\"Value {value!r} is not a valid etag. Maybe it contains '\\\"'?\"\n        )\n\n\ndef parse_http_date(date_str: str | None) -> datetime.datetime | None:\n    \"\"\"Process a date string, return a datetime object\"\"\"\n    if date_str is not None:\n        timetuple = parsedate(date_str)\n        if timetuple is not None:\n            with suppress(ValueError):\n                return datetime.datetime(*timetuple[:6], tzinfo=datetime.timezone.utc)\n    return None\n\n\n@functools.lru_cache\ndef must_be_empty_body(method: str, code: int) -> bool:\n    \"\"\"Check if a request must return an empty body.\"\"\"\n    return (\n        code in EMPTY_BODY_STATUS_CODES\n        or method in EMPTY_BODY_METHODS\n        or (200 <= code < 300 and method in hdrs.METH_CONNECT_ALL)\n    )\n\n\ndef should_remove_content_length(method: str, code: int) -> bool:\n    \"\"\"Check if a Content-Length header should be removed.\n\n    This should always be a subset of must_be_empty_body\n    \"\"\"\n    # https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6-8\n    # https://www.rfc-editor.org/rfc/rfc9110.html#section-15.4.5-4\n    return code in EMPTY_BODY_STATUS_CODES or (\n        200 <= code < 300 and method in hdrs.METH_CONNECT_ALL\n    )\n"
  },
  {
    "path": "aiohttp/http.py",
    "content": "import sys\n\nfrom . import __version__\nfrom .http_exceptions import HttpProcessingError\nfrom .http_parser import (\n    HeadersParser,\n    HttpParser,\n    HttpRequestParser,\n    HttpResponseParser,\n    RawRequestMessage,\n    RawResponseMessage,\n)\nfrom .http_websocket import (\n    WS_CLOSED_MESSAGE,\n    WS_CLOSING_MESSAGE,\n    WS_KEY,\n    WebSocketError,\n    WebSocketReader,\n    WebSocketWriter,\n    WSCloseCode,\n    WSMessage,\n    WSMessageDecodeText,\n    WSMessageNoDecodeText,\n    WSMsgType,\n    ws_ext_gen,\n    ws_ext_parse,\n)\nfrom .http_writer import HttpVersion, HttpVersion10, HttpVersion11, StreamWriter\n\n__all__ = (\n    \"HttpProcessingError\",\n    \"SERVER_SOFTWARE\",\n    # .http_writer\n    \"StreamWriter\",\n    \"HttpVersion\",\n    \"HttpVersion10\",\n    \"HttpVersion11\",\n    # .http_parser\n    \"HeadersParser\",\n    \"HttpParser\",\n    \"HttpRequestParser\",\n    \"HttpResponseParser\",\n    \"RawRequestMessage\",\n    \"RawResponseMessage\",\n    # .http_websocket\n    \"WS_CLOSED_MESSAGE\",\n    \"WS_CLOSING_MESSAGE\",\n    \"WS_KEY\",\n    \"WebSocketReader\",\n    \"WebSocketWriter\",\n    \"ws_ext_gen\",\n    \"ws_ext_parse\",\n    \"WSMessage\",\n    \"WSMessageDecodeText\",\n    \"WSMessageNoDecodeText\",\n    \"WebSocketError\",\n    \"WSMsgType\",\n    \"WSCloseCode\",\n)\n\n\nSERVER_SOFTWARE: str = (\n    f\"Python/{sys.version_info[0]}.{sys.version_info[1]} aiohttp/{__version__}\"\n)\n"
  },
  {
    "path": "aiohttp/http_exceptions.py",
    "content": "\"\"\"Low-level http related exceptions.\"\"\"\n\nfrom textwrap import indent\n\nfrom multidict import CIMultiDict\n\n__all__ = (\"HttpProcessingError\",)\n\n\nclass HttpProcessingError(Exception):\n    \"\"\"HTTP error.\n\n    Shortcut for raising HTTP errors with custom code, message and headers.\n\n    code: HTTP Error code.\n    message: (optional) Error message.\n    headers: (optional) Headers to be sent in response, a list of pairs\n    \"\"\"\n\n    code = 0\n    message = \"\"\n    headers = None\n\n    def __init__(\n        self,\n        *,\n        code: int | None = None,\n        message: str = \"\",\n        headers: CIMultiDict[str] | None = None,\n    ) -> None:\n        if code is not None:\n            self.code = code\n        self.headers = headers\n        self.message = message\n\n    def __str__(self) -> str:\n        msg = indent(self.message, \"  \")\n        return f\"{self.code}, message:\\n{msg}\"\n\n    def __repr__(self) -> str:\n        return f\"<{self.__class__.__name__}: {self.code}, message={self.message!r}>\"\n\n\nclass BadHttpMessage(HttpProcessingError):\n    code = 400\n    message = \"Bad Request\"\n\n    def __init__(\n        self, message: str, *, headers: CIMultiDict[str] | None = None\n    ) -> None:\n        super().__init__(message=message, headers=headers)\n        self.args = (message,)\n\n\nclass HttpBadRequest(BadHttpMessage):\n    code = 400\n    message = \"Bad Request\"\n\n\nclass PayloadEncodingError(BadHttpMessage):\n    \"\"\"Base class for payload errors\"\"\"\n\n\nclass ContentEncodingError(PayloadEncodingError):\n    \"\"\"Content encoding error.\"\"\"\n\n\nclass TransferEncodingError(PayloadEncodingError):\n    \"\"\"transfer encoding error.\"\"\"\n\n\nclass ContentLengthError(PayloadEncodingError):\n    \"\"\"Not enough data to satisfy content length header.\"\"\"\n\n\nclass DecompressSizeError(PayloadEncodingError):\n    \"\"\"Decompressed size exceeds the configured limit.\"\"\"\n\n\nclass LineTooLong(BadHttpMessage):\n    def __init__(self, line: bytes, limit: int) -> None:\n        super().__init__(f\"Got more than {limit} bytes when reading: {line!r}.\")\n        self.args = (line, limit)\n\n\nclass InvalidHeader(BadHttpMessage):\n    def __init__(self, hdr: bytes | str) -> None:\n        hdr_s = hdr.decode(errors=\"backslashreplace\") if isinstance(hdr, bytes) else hdr\n        super().__init__(f\"Invalid HTTP header: {hdr!r}\")\n        self.hdr = hdr_s\n        self.args = (hdr,)\n\n\nclass BadStatusLine(BadHttpMessage):\n    def __init__(self, line: str = \"\", error: str | None = None) -> None:\n        super().__init__(error or f\"Bad status line {line!r}\")\n        self.args = (line,)\n        self.line = line\n\n\nclass BadHttpMethod(BadStatusLine):\n    \"\"\"Invalid HTTP method in status line.\"\"\"\n\n    def __init__(self, line: str = \"\", error: str | None = None) -> None:\n        super().__init__(line, error or f\"Bad HTTP method in status line {line!r}\")\n\n\nclass InvalidURLError(BadHttpMessage):\n    pass\n"
  },
  {
    "path": "aiohttp/http_parser.py",
    "content": "import abc\nimport asyncio\nimport re\nimport string\nfrom contextlib import suppress\nfrom enum import IntEnum\nfrom re import Pattern\nfrom typing import Any, ClassVar, Final, Generic, Literal, NamedTuple, TypeVar\n\nfrom multidict import CIMultiDict, CIMultiDictProxy, istr\nfrom yarl import URL\n\nfrom . import hdrs\nfrom .base_protocol import BaseProtocol\nfrom .compression_utils import (\n    DEFAULT_MAX_DECOMPRESS_SIZE,\n    HAS_BROTLI,\n    HAS_ZSTD,\n    BrotliDecompressor,\n    ZLibDecompressor,\n    ZSTDDecompressor,\n)\nfrom .helpers import (\n    _EXC_SENTINEL,\n    DEBUG,\n    EMPTY_BODY_METHODS,\n    EMPTY_BODY_STATUS_CODES,\n    NO_EXTENSIONS,\n    BaseTimerContext,\n    set_exception,\n)\nfrom .http_exceptions import (\n    BadHttpMessage,\n    BadHttpMethod,\n    BadStatusLine,\n    ContentEncodingError,\n    ContentLengthError,\n    DecompressSizeError,\n    InvalidHeader,\n    InvalidURLError,\n    LineTooLong,\n    TransferEncodingError,\n)\nfrom .http_writer import HttpVersion, HttpVersion10\nfrom .streams import EMPTY_PAYLOAD, StreamReader\nfrom .typedefs import RawHeaders\n\n__all__ = (\n    \"HeadersParser\",\n    \"HttpParser\",\n    \"HttpRequestParser\",\n    \"HttpResponseParser\",\n    \"RawRequestMessage\",\n    \"RawResponseMessage\",\n)\n\n_SEP = Literal[b\"\\r\\n\", b\"\\n\"]\n\nASCIISET: Final[set[str]] = set(string.printable)\n\n# See https://www.rfc-editor.org/rfc/rfc9110.html#name-overview\n# and https://www.rfc-editor.org/rfc/rfc9110.html#name-tokens\n#\n#     method = token\n#     tchar = \"!\" / \"#\" / \"$\" / \"%\" / \"&\" / \"'\" / \"*\" / \"+\" / \"-\" / \".\" /\n#             \"^\" / \"_\" / \"`\" / \"|\" / \"~\" / DIGIT / ALPHA\n#     token = 1*tchar\n_TCHAR_SPECIALS: Final[str] = re.escape(\"!#$%&'*+-.^_`|~\")\nTOKENRE: Final[Pattern[str]] = re.compile(f\"[0-9A-Za-z{_TCHAR_SPECIALS}]+\")\n# https://www.rfc-editor.org/rfc/rfc9110#section-5.5-5\n_FIELD_VALUE_FORBIDDEN_CTL_RE: Final[Pattern[str]] = re.compile(\n    r\"[\\x00-\\x08\\x0a-\\x1f\\x7f]\"\n)\nVERSRE: Final[Pattern[str]] = re.compile(r\"HTTP/(\\d)\\.(\\d)\", re.ASCII)\nDIGITS: Final[Pattern[str]] = re.compile(r\"\\d+\", re.ASCII)\nHEXDIGITS: Final[Pattern[bytes]] = re.compile(rb\"[0-9a-fA-F]+\")\n\n\nclass RawRequestMessage(NamedTuple):\n    method: str\n    path: str\n    version: HttpVersion\n    headers: CIMultiDictProxy[str]\n    raw_headers: RawHeaders\n    should_close: bool\n    compression: str | None\n    upgrade: bool\n    chunked: bool\n    url: URL\n\n\nclass RawResponseMessage(NamedTuple):\n    version: HttpVersion\n    code: int\n    reason: str\n    headers: CIMultiDictProxy[str]\n    raw_headers: RawHeaders\n    should_close: bool\n    compression: str | None\n    upgrade: bool\n    chunked: bool\n\n\n_MsgT = TypeVar(\"_MsgT\", RawRequestMessage, RawResponseMessage)\n\n\nclass ParseState(IntEnum):\n    PARSE_NONE = 0\n    PARSE_LENGTH = 1\n    PARSE_CHUNKED = 2\n    PARSE_UNTIL_EOF = 3\n\n\nclass ChunkState(IntEnum):\n    PARSE_CHUNKED_SIZE = 0\n    PARSE_CHUNKED_CHUNK = 1\n    PARSE_CHUNKED_CHUNK_EOF = 2\n    PARSE_TRAILERS = 4\n\n\nclass HeadersParser:\n    def __init__(self, max_field_size: int = 8190, lax: bool = False) -> None:\n        self.max_field_size = max_field_size\n        self._lax = lax\n\n    def parse_headers(\n        self, lines: list[bytes]\n    ) -> tuple[\"CIMultiDictProxy[str]\", RawHeaders]:\n        headers: CIMultiDict[str] = CIMultiDict()\n        # note: \"raw\" does not mean inclusion of OWS before/after the field value\n        raw_headers = []\n\n        lines_idx = 0\n        line = lines[lines_idx]\n        line_count = len(lines)\n\n        while line:\n            # Parse initial header name : value pair.\n            try:\n                bname, bvalue = line.split(b\":\", 1)\n            except ValueError:\n                raise InvalidHeader(line) from None\n\n            if len(bname) == 0:\n                raise InvalidHeader(bname)\n\n            # https://www.rfc-editor.org/rfc/rfc9112.html#section-5.1-2\n            if {bname[0], bname[-1]} & {32, 9}:  # {\" \", \"\\t\"}\n                raise InvalidHeader(line)\n\n            bvalue = bvalue.lstrip(b\" \\t\")\n            name = bname.decode(\"utf-8\", \"surrogateescape\")\n            if not TOKENRE.fullmatch(name):\n                raise InvalidHeader(bname)\n\n            # next line\n            lines_idx += 1\n            line = lines[lines_idx]\n\n            # consume continuation lines\n            continuation = self._lax and line and line[0] in (32, 9)  # (' ', '\\t')\n\n            # Deprecated: https://www.rfc-editor.org/rfc/rfc9112.html#name-obsolete-line-folding\n            if continuation:\n                header_length = len(bvalue)\n                bvalue_lst = [bvalue]\n                while continuation:\n                    header_length += len(line)\n                    if header_length > self.max_field_size:\n                        header_line = bname + b\": \" + b\"\".join(bvalue_lst)\n                        raise LineTooLong(\n                            header_line[:100] + b\"...\", self.max_field_size\n                        )\n                    bvalue_lst.append(line)\n\n                    # next line\n                    lines_idx += 1\n                    if lines_idx < line_count:\n                        line = lines[lines_idx]\n                        if line:\n                            continuation = line[0] in (32, 9)  # (' ', '\\t')\n                    else:\n                        line = b\"\"\n                        break\n                bvalue = b\"\".join(bvalue_lst)\n\n            bvalue = bvalue.strip(b\" \\t\")\n            value = bvalue.decode(\"utf-8\", \"surrogateescape\")\n\n            # https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5-5\n            if self._lax:\n                if \"\\n\" in value or \"\\r\" in value or \"\\x00\" in value:\n                    raise InvalidHeader(bvalue)\n            elif _FIELD_VALUE_FORBIDDEN_CTL_RE.search(value):\n                raise InvalidHeader(bvalue)\n\n            headers.add(name, value)\n            raw_headers.append((bname, bvalue))\n\n        return (CIMultiDictProxy(headers), tuple(raw_headers))\n\n\ndef _is_supported_upgrade(headers: CIMultiDictProxy[str]) -> bool:\n    \"\"\"Check if the upgrade header is supported.\"\"\"\n    u = headers.get(hdrs.UPGRADE, \"\")\n    # .lower() can transform non-ascii characters.\n    return u.isascii() and u.lower() in {\"tcp\", \"websocket\"}\n\n\nclass HttpParser(abc.ABC, Generic[_MsgT]):\n    lax: ClassVar[bool] = False\n\n    def __init__(\n        self,\n        protocol: BaseProtocol,\n        loop: asyncio.AbstractEventLoop,\n        limit: int,\n        max_line_size: int = 8190,\n        max_headers: int = 128,\n        max_field_size: int = 8190,\n        timer: BaseTimerContext | None = None,\n        code: int | None = None,\n        method: str | None = None,\n        payload_exception: type[BaseException] | None = None,\n        response_with_body: bool = True,\n        read_until_eof: bool = False,\n        auto_decompress: bool = True,\n    ) -> None:\n        self.protocol = protocol\n        self.loop = loop\n        self.max_line_size = max_line_size\n        self.max_field_size = max_field_size\n        self.max_headers = max_headers\n        self.timer = timer\n        self.code = code\n        self.method = method\n        self.payload_exception = payload_exception\n        self.response_with_body = response_with_body\n        self.read_until_eof = read_until_eof\n\n        self._lines: list[bytes] = []\n        self._tail = b\"\"\n        self._upgraded = False\n        self._payload = None\n        self._payload_parser: HttpPayloadParser | None = None\n        self._auto_decompress = auto_decompress\n        self._limit = limit\n        self._headers_parser = HeadersParser(max_field_size, self.lax)\n\n    @abc.abstractmethod\n    def parse_message(self, lines: list[bytes]) -> _MsgT: ...\n\n    @abc.abstractmethod\n    def _is_chunked_te(self, te: str) -> bool: ...\n\n    def feed_eof(self) -> _MsgT | None:\n        if self._payload_parser is not None:\n            self._payload_parser.feed_eof()\n            self._payload_parser = None\n        else:\n            # try to extract partial message\n            if self._tail:\n                self._lines.append(self._tail)\n\n            if self._lines:\n                if self._lines[-1] != \"\\r\\n\":\n                    self._lines.append(b\"\")\n                with suppress(Exception):\n                    return self.parse_message(self._lines)\n        return None\n\n    def feed_data(\n        self,\n        data: bytes,\n        SEP: _SEP = b\"\\r\\n\",\n        EMPTY: bytes = b\"\",\n        CONTENT_LENGTH: istr = hdrs.CONTENT_LENGTH,\n        METH_CONNECT: str = hdrs.METH_CONNECT,\n        SEC_WEBSOCKET_KEY1: istr = hdrs.SEC_WEBSOCKET_KEY1,\n    ) -> tuple[list[tuple[_MsgT, StreamReader]], bool, bytes]:\n        messages = []\n\n        if self._tail:\n            data, self._tail = self._tail + data, b\"\"\n\n        data_len = len(data)\n        start_pos = 0\n        loop = self.loop\n        max_line_length = self.max_line_size\n\n        should_close = False\n        while start_pos < data_len:\n            # read HTTP message (request/response line + headers), \\r\\n\\r\\n\n            # and split by lines\n            if self._payload_parser is None and not self._upgraded:\n                pos = data.find(SEP, start_pos)\n                # consume \\r\\n\n                if pos == start_pos and not self._lines:\n                    start_pos = pos + len(SEP)\n                    continue\n\n                if pos >= start_pos:\n                    if should_close:\n                        raise BadHttpMessage(\"Data after `Connection: close`\")\n\n                    # line found\n                    line = data[start_pos:pos]\n                    if SEP == b\"\\n\":  # For lax response parsing\n                        line = line.rstrip(b\"\\r\")\n                    if len(line) > max_line_length:\n                        raise LineTooLong(line[:100] + b\"...\", max_line_length)\n\n                    self._lines.append(line)\n                    # After processing the status/request line, everything is a header.\n                    max_line_length = self.max_field_size\n\n                    if len(self._lines) > self.max_headers:\n                        raise BadHttpMessage(\"Too many headers received\")\n\n                    start_pos = pos + len(SEP)\n\n                    # \\r\\n\\r\\n found\n                    if self._lines[-1] == EMPTY:\n                        max_trailers = self.max_headers - len(self._lines)\n                        try:\n                            msg: _MsgT = self.parse_message(self._lines)\n                        finally:\n                            self._lines.clear()\n\n                        def get_content_length() -> int | None:\n                            # payload length\n                            length_hdr = msg.headers.get(CONTENT_LENGTH)\n                            if length_hdr is None:\n                                return None\n\n                            # Shouldn't allow +/- or other number formats.\n                            # https://www.rfc-editor.org/rfc/rfc9110#section-8.6-2\n                            # msg.headers is already stripped of leading/trailing wsp\n                            if not DIGITS.fullmatch(length_hdr):\n                                raise InvalidHeader(CONTENT_LENGTH)\n\n                            return int(length_hdr)\n\n                        length = get_content_length()\n                        # do not support old websocket spec\n                        if SEC_WEBSOCKET_KEY1 in msg.headers:\n                            raise InvalidHeader(SEC_WEBSOCKET_KEY1)\n\n                        self._upgraded = msg.upgrade and _is_supported_upgrade(\n                            msg.headers\n                        )\n\n                        method = getattr(msg, \"method\", self.method)\n                        # code is only present on responses\n                        code = getattr(msg, \"code\", 0)\n\n                        assert self.protocol is not None\n                        # calculate payload\n                        empty_body = code in EMPTY_BODY_STATUS_CODES or bool(\n                            method and method in EMPTY_BODY_METHODS\n                        )\n                        if not empty_body and (\n                            ((length is not None and length > 0) or msg.chunked)\n                            and not self._upgraded\n                        ):\n                            payload = StreamReader(\n                                self.protocol,\n                                timer=self.timer,\n                                loop=loop,\n                                limit=self._limit,\n                            )\n                            payload_parser = HttpPayloadParser(\n                                payload,\n                                length=length,\n                                chunked=msg.chunked,\n                                method=method,\n                                compression=msg.compression,\n                                code=self.code,\n                                response_with_body=self.response_with_body,\n                                auto_decompress=self._auto_decompress,\n                                lax=self.lax,\n                                headers_parser=self._headers_parser,\n                                max_line_size=self.max_line_size,\n                                max_field_size=self.max_field_size,\n                                max_trailers=max_trailers,\n                            )\n                            if not payload_parser.done:\n                                self._payload_parser = payload_parser\n                        elif method == METH_CONNECT:\n                            assert isinstance(msg, RawRequestMessage)\n                            payload = StreamReader(\n                                self.protocol,\n                                timer=self.timer,\n                                loop=loop,\n                                limit=self._limit,\n                            )\n                            self._upgraded = True\n                            self._payload_parser = HttpPayloadParser(\n                                payload,\n                                method=msg.method,\n                                compression=msg.compression,\n                                auto_decompress=self._auto_decompress,\n                                lax=self.lax,\n                                headers_parser=self._headers_parser,\n                                max_line_size=self.max_line_size,\n                                max_field_size=self.max_field_size,\n                                max_trailers=max_trailers,\n                            )\n                        elif not empty_body and length is None and self.read_until_eof:\n                            payload = StreamReader(\n                                self.protocol,\n                                timer=self.timer,\n                                loop=loop,\n                                limit=self._limit,\n                            )\n                            payload_parser = HttpPayloadParser(\n                                payload,\n                                length=length,\n                                chunked=msg.chunked,\n                                method=method,\n                                compression=msg.compression,\n                                code=self.code,\n                                response_with_body=self.response_with_body,\n                                auto_decompress=self._auto_decompress,\n                                lax=self.lax,\n                                headers_parser=self._headers_parser,\n                                max_line_size=self.max_line_size,\n                                max_field_size=self.max_field_size,\n                                max_trailers=max_trailers,\n                            )\n                            if not payload_parser.done:\n                                self._payload_parser = payload_parser\n                        else:\n                            payload = EMPTY_PAYLOAD\n\n                        messages.append((msg, payload))\n                        should_close = msg.should_close\n                else:\n                    self._tail = data[start_pos:]\n                    if len(self._tail) > self.max_line_size:\n                        raise LineTooLong(self._tail[:100] + b\"...\", self.max_line_size)\n                    data = EMPTY\n                    break\n\n            # no parser, just store\n            elif self._payload_parser is None and self._upgraded:\n                assert not self._lines\n                break\n\n            # feed payload\n            elif data and start_pos < data_len:\n                assert not self._lines\n                assert self._payload_parser is not None\n                try:\n                    eof, data = self._payload_parser.feed_data(data[start_pos:], SEP)\n                except Exception as underlying_exc:\n                    reraised_exc: BaseException = underlying_exc\n                    if self.payload_exception is not None:\n                        reraised_exc = self.payload_exception(str(underlying_exc))\n\n                    set_exception(\n                        self._payload_parser.payload,\n                        reraised_exc,\n                        underlying_exc,\n                    )\n\n                    eof = True\n                    data = b\"\"\n                    if isinstance(\n                        underlying_exc, (InvalidHeader, TransferEncodingError)\n                    ):\n                        raise\n\n                if eof:\n                    start_pos = 0\n                    data_len = len(data)\n                    self._payload_parser = None\n                    continue\n            else:\n                break\n\n        if data and start_pos < data_len:\n            data = data[start_pos:]\n        else:\n            data = EMPTY\n\n        return messages, self._upgraded, data\n\n    def parse_headers(\n        self, lines: list[bytes]\n    ) -> tuple[\n        \"CIMultiDictProxy[str]\", RawHeaders, bool | None, str | None, bool, bool\n    ]:\n        \"\"\"Parses RFC 5322 headers from a stream.\n\n        Line continuations are supported. Returns list of header name\n        and value pairs. Header name is in upper case.\n        \"\"\"\n        headers, raw_headers = self._headers_parser.parse_headers(lines)\n        close_conn = None\n        encoding = None\n        upgrade = False\n        chunked = False\n\n        # https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5-6\n        # https://www.rfc-editor.org/rfc/rfc9110.html#name-collected-abnf\n        singletons = (\n            hdrs.CONTENT_LENGTH,\n            hdrs.CONTENT_LOCATION,\n            hdrs.CONTENT_RANGE,\n            hdrs.CONTENT_TYPE,\n            hdrs.ETAG,\n            hdrs.HOST,\n            hdrs.MAX_FORWARDS,\n            hdrs.SERVER,\n            hdrs.TRANSFER_ENCODING,\n            hdrs.USER_AGENT,\n        )\n        bad_hdr = next((h for h in singletons if len(headers.getall(h, ())) > 1), None)\n        if bad_hdr is not None:\n            raise BadHttpMessage(f\"Duplicate '{bad_hdr}' header found.\")\n\n        # keep-alive and protocol switching\n        # RFC 9110 section 7.6.1 defines Connection as a comma-separated list.\n        conn_values = headers.getall(hdrs.CONNECTION, ())\n        if conn_values:\n            conn_tokens = {\n                token.lower()\n                for conn_value in conn_values\n                for token in (part.strip(\" \\t\") for part in conn_value.split(\",\"))\n                if token and token.isascii()\n            }\n\n            if \"close\" in conn_tokens:\n                close_conn = True\n            elif \"keep-alive\" in conn_tokens:\n                close_conn = False\n\n            # https://www.rfc-editor.org/rfc/rfc9110.html#name-101-switching-protocols\n            if \"upgrade\" in conn_tokens and headers.get(hdrs.UPGRADE):\n                upgrade = True\n\n        # encoding\n        enc = headers.get(hdrs.CONTENT_ENCODING, \"\")\n        if enc.isascii() and enc.lower() in {\"gzip\", \"deflate\", \"br\", \"zstd\"}:\n            encoding = enc\n\n        # chunking\n        te = headers.get(hdrs.TRANSFER_ENCODING)\n        if te is not None:\n            if self._is_chunked_te(te):\n                chunked = True\n\n            if hdrs.CONTENT_LENGTH in headers:\n                raise BadHttpMessage(\n                    \"Transfer-Encoding can't be present with Content-Length\",\n                )\n\n        return (headers, raw_headers, close_conn, encoding, upgrade, chunked)\n\n    def set_upgraded(self, val: bool) -> None:\n        \"\"\"Set connection upgraded (to websocket) mode.\n\n        :param bool val: new state.\n        \"\"\"\n        self._upgraded = val\n\n\nclass HttpRequestParser(HttpParser[RawRequestMessage]):\n    \"\"\"Read request status line.\n\n    Exception .http_exceptions.BadStatusLine\n    could be raised in case of any errors in status line.\n    Returns RawRequestMessage.\n    \"\"\"\n\n    def parse_message(self, lines: list[bytes]) -> RawRequestMessage:\n        # request line\n        line = lines[0].decode(\"utf-8\", \"surrogateescape\")\n        try:\n            method, path, version = line.split(\" \", maxsplit=2)\n        except ValueError:\n            raise BadHttpMethod(line) from None\n\n        # method\n        if not TOKENRE.fullmatch(method):\n            raise BadHttpMethod(method)\n\n        # version\n        match = VERSRE.fullmatch(version)\n        if match is None:\n            raise BadStatusLine(line)\n        version_o = HttpVersion(int(match.group(1)), int(match.group(2)))\n\n        if method == \"CONNECT\":\n            # authority-form,\n            # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.3\n            url = URL.build(authority=path, encoded=True)\n        elif path.startswith(\"/\"):\n            # origin-form,\n            # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.1\n            path_part, _hash_separator, url_fragment = path.partition(\"#\")\n            path_part, _question_mark_separator, qs_part = path_part.partition(\"?\")\n\n            # NOTE: `yarl.URL.build()` is used to mimic what the Cython-based\n            # NOTE: parser does, otherwise it results into the same\n            # NOTE: HTTP Request-Line input producing different\n            # NOTE: `yarl.URL()` objects\n            url = URL.build(\n                path=path_part,\n                query_string=qs_part,\n                fragment=url_fragment,\n                encoded=True,\n            )\n        elif path == \"*\" and method == \"OPTIONS\":\n            # asterisk-form,\n            url = URL(path, encoded=True)\n        else:\n            # absolute-form for proxy maybe,\n            # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.2\n            url = URL(path, encoded=True)\n            if url.scheme == \"\":\n                # not absolute-form\n                raise InvalidURLError(\n                    path.encode(errors=\"surrogateescape\").decode(\"latin1\")\n                )\n\n        # read headers\n        (\n            headers,\n            raw_headers,\n            close,\n            compression,\n            upgrade,\n            chunked,\n        ) = self.parse_headers(lines[1:])\n\n        if close is None:  # then the headers weren't set in the request\n            if version_o <= HttpVersion10:  # HTTP 1.0 must asks to not close\n                close = True\n            else:  # HTTP 1.1 must ask to close.\n                close = False\n\n        return RawRequestMessage(\n            method,\n            path,\n            version_o,\n            headers,\n            raw_headers,\n            close,\n            compression,\n            upgrade,\n            chunked,\n            url,\n        )\n\n    def _is_chunked_te(self, te: str) -> bool:\n        # https://www.rfc-editor.org/rfc/rfc9112#section-7.1-3\n        # \"A sender MUST NOT apply the chunked transfer coding more\n        #  than once to a message body\"\n        parts = [p.strip(\" \\t\") for p in te.split(\",\")]\n        chunked_count = sum(1 for p in parts if p.isascii() and p.lower() == \"chunked\")\n        if chunked_count > 1:\n            raise BadHttpMessage(\"Request has duplicate `chunked` Transfer-Encoding\")\n        last = parts[-1]\n        # .lower() transforms some non-ascii chars, so must check first.\n        if last.isascii() and last.lower() == \"chunked\":\n            return True\n        # https://www.rfc-editor.org/rfc/rfc9112#section-6.3-2.4.3\n        raise BadHttpMessage(\"Request has invalid `Transfer-Encoding`\")\n\n\nclass HttpResponseParser(HttpParser[RawResponseMessage]):\n    \"\"\"Read response status line and headers.\n\n    BadStatusLine could be raised in case of any errors in status line.\n    Returns RawResponseMessage.\n    \"\"\"\n\n    # Lax mode should only be enabled on response parser.\n    lax = not DEBUG\n\n    def feed_data(\n        self,\n        data: bytes,\n        SEP: _SEP | None = None,\n        *args: Any,\n        **kwargs: Any,\n    ) -> tuple[list[tuple[RawResponseMessage, StreamReader]], bool, bytes]:\n        if SEP is None:\n            SEP = b\"\\r\\n\" if DEBUG else b\"\\n\"\n        return super().feed_data(data, SEP, *args, **kwargs)\n\n    def parse_message(self, lines: list[bytes]) -> RawResponseMessage:\n        line = lines[0].decode(\"utf-8\", \"surrogateescape\")\n        try:\n            version, status = line.split(maxsplit=1)\n        except ValueError:\n            raise BadStatusLine(line) from None\n\n        try:\n            status, reason = status.split(maxsplit=1)\n        except ValueError:\n            status = status.strip()\n            reason = \"\"\n\n        # version\n        match = VERSRE.fullmatch(version)\n        if match is None:\n            raise BadStatusLine(line)\n        version_o = HttpVersion(int(match.group(1)), int(match.group(2)))\n\n        # The status code is a three-digit ASCII number, no padding\n        if len(status) != 3 or not DIGITS.fullmatch(status):\n            raise BadStatusLine(line)\n        status_i = int(status)\n\n        # read headers\n        (\n            headers,\n            raw_headers,\n            close,\n            compression,\n            upgrade,\n            chunked,\n        ) = self.parse_headers(lines[1:])\n\n        if close is None:\n            if version_o <= HttpVersion10:\n                close = True\n            # https://www.rfc-editor.org/rfc/rfc9112.html#name-message-body-length\n            elif 100 <= status_i < 200 or status_i in {204, 304}:\n                close = False\n            elif hdrs.CONTENT_LENGTH in headers or hdrs.TRANSFER_ENCODING in headers:\n                close = False\n            else:\n                # https://www.rfc-editor.org/rfc/rfc9112.html#section-6.3-2.8\n                close = True\n\n        return RawResponseMessage(\n            version_o,\n            status_i,\n            reason.strip(),\n            headers,\n            raw_headers,\n            close,\n            compression,\n            upgrade,\n            chunked,\n        )\n\n    def _is_chunked_te(self, te: str) -> bool:\n        # https://www.rfc-editor.org/rfc/rfc9112#section-6.3-2.4.2\n        return te.rsplit(\",\", maxsplit=1)[-1].strip(\" \\t\").lower() == \"chunked\"\n\n\nclass HttpPayloadParser:\n    def __init__(\n        self,\n        payload: StreamReader,\n        length: int | None = None,\n        chunked: bool = False,\n        compression: str | None = None,\n        code: int | None = None,\n        method: str | None = None,\n        response_with_body: bool = True,\n        auto_decompress: bool = True,\n        lax: bool = False,\n        *,\n        headers_parser: HeadersParser,\n        max_line_size: int = 8190,\n        max_field_size: int = 8190,\n        max_trailers: int = 128,\n    ) -> None:\n        self._length = 0\n        self._type = ParseState.PARSE_UNTIL_EOF\n        self._chunk = ChunkState.PARSE_CHUNKED_SIZE\n        self._chunk_size = 0\n        self._chunk_tail = b\"\"\n        self._auto_decompress = auto_decompress\n        self._lax = lax\n        self._headers_parser = headers_parser\n        self._max_line_size = max_line_size\n        self._max_field_size = max_field_size\n        self._max_trailers = max_trailers\n        self._trailer_lines: list[bytes] = []\n        self.done = False\n\n        # payload decompression wrapper\n        if response_with_body and compression and self._auto_decompress:\n            real_payload: StreamReader | DeflateBuffer = DeflateBuffer(\n                payload, compression\n            )\n        else:\n            real_payload = payload\n\n        # payload parser\n        if not response_with_body:\n            # don't parse payload if it's not expected to be received\n            self._type = ParseState.PARSE_NONE\n            real_payload.feed_eof()\n            self.done = True\n        elif chunked:\n            self._type = ParseState.PARSE_CHUNKED\n        elif length is not None:\n            self._type = ParseState.PARSE_LENGTH\n            self._length = length\n            if self._length == 0:\n                real_payload.feed_eof()\n                self.done = True\n\n        self.payload = real_payload\n\n    def feed_eof(self) -> None:\n        if self._type == ParseState.PARSE_UNTIL_EOF:\n            self.payload.feed_eof()\n        elif self._type == ParseState.PARSE_LENGTH:\n            raise ContentLengthError(\n                \"Not enough data to satisfy content length header.\"\n            )\n        elif self._type == ParseState.PARSE_CHUNKED:\n            raise TransferEncodingError(\n                \"Not enough data to satisfy transfer length header.\"\n            )\n\n    def feed_data(\n        self, chunk: bytes, SEP: _SEP = b\"\\r\\n\", CHUNK_EXT: bytes = b\";\"\n    ) -> tuple[bool, bytes]:\n        # Read specified amount of bytes\n        if self._type == ParseState.PARSE_LENGTH:\n            required = self._length\n            self._length = max(required - len(chunk), 0)\n            self.payload.feed_data(chunk[:required])\n            if self._length == 0:\n                self.payload.feed_eof()\n                return True, chunk[required:]\n\n        # Chunked transfer encoding parser\n        elif self._type == ParseState.PARSE_CHUNKED:\n            if self._chunk_tail:\n                # We should never have a tail if we're inside the payload body.\n                assert self._chunk != ChunkState.PARSE_CHUNKED_CHUNK\n                # We should check the length is sane.\n                max_line_length = self._max_line_size\n                if self._chunk == ChunkState.PARSE_TRAILERS:\n                    max_line_length = self._max_field_size\n                if len(self._chunk_tail) > max_line_length:\n                    raise LineTooLong(self._chunk_tail[:100] + b\"...\", max_line_length)\n\n                chunk = self._chunk_tail + chunk\n                self._chunk_tail = b\"\"\n\n            while chunk:\n                # read next chunk size\n                if self._chunk == ChunkState.PARSE_CHUNKED_SIZE:\n                    pos = chunk.find(SEP)\n                    if pos >= 0:\n                        i = chunk.find(CHUNK_EXT, 0, pos)\n                        if i >= 0:\n                            size_b = chunk[:i]  # strip chunk-extensions\n                            # Verify no LF in the chunk-extension\n                            if b\"\\n\" in (ext := chunk[i:pos]):\n                                exc = TransferEncodingError(\n                                    f\"Unexpected LF in chunk-extension: {ext!r}\"\n                                )\n                                set_exception(self.payload, exc)\n                                raise exc\n                        else:\n                            size_b = chunk[:pos]\n\n                        if self._lax:  # Allow whitespace in lax mode.\n                            size_b = size_b.strip()\n\n                        if not re.fullmatch(HEXDIGITS, size_b):\n                            exc = TransferEncodingError(\n                                chunk[:pos].decode(\"ascii\", \"surrogateescape\")\n                            )\n                            set_exception(self.payload, exc)\n                            raise exc\n                        size = int(bytes(size_b), 16)\n\n                        chunk = chunk[pos + len(SEP) :]\n                        if size == 0:  # eof marker\n                            self._chunk = ChunkState.PARSE_TRAILERS\n                            if self._lax and chunk.startswith(b\"\\r\"):\n                                chunk = chunk[1:]\n                        else:\n                            self._chunk = ChunkState.PARSE_CHUNKED_CHUNK\n                            self._chunk_size = size\n                            self.payload.begin_http_chunk_receiving()\n                    else:\n                        self._chunk_tail = chunk\n                        return False, b\"\"\n\n                # read chunk and feed buffer\n                if self._chunk == ChunkState.PARSE_CHUNKED_CHUNK:\n                    required = self._chunk_size\n                    self._chunk_size = max(required - len(chunk), 0)\n                    self.payload.feed_data(chunk[:required])\n\n                    if self._chunk_size:\n                        return False, b\"\"\n                    chunk = chunk[required:]\n                    self._chunk = ChunkState.PARSE_CHUNKED_CHUNK_EOF\n                    self.payload.end_http_chunk_receiving()\n\n                # toss the CRLF at the end of the chunk\n                if self._chunk == ChunkState.PARSE_CHUNKED_CHUNK_EOF:\n                    if self._lax and chunk.startswith(b\"\\r\"):\n                        chunk = chunk[1:]\n                    if chunk[: len(SEP)] == SEP:\n                        chunk = chunk[len(SEP) :]\n                        self._chunk = ChunkState.PARSE_CHUNKED_SIZE\n                    elif len(chunk) >= len(SEP) or chunk != SEP[: len(chunk)]:\n                        exc = TransferEncodingError(\n                            \"Chunk size mismatch: expected CRLF after chunk data\"\n                        )\n                        set_exception(self.payload, exc)\n                        raise exc\n                    else:\n                        self._chunk_tail = chunk\n                        return False, b\"\"\n\n                if self._chunk == ChunkState.PARSE_TRAILERS:\n                    pos = chunk.find(SEP)\n                    if pos < 0:  # No line found\n                        self._chunk_tail = chunk\n                        return False, b\"\"\n\n                    line = chunk[:pos]\n                    chunk = chunk[pos + len(SEP) :]\n                    if SEP == b\"\\n\":  # For lax response parsing\n                        line = line.rstrip(b\"\\r\")\n\n                    if len(line) > self._max_field_size:\n                        raise LineTooLong(line[:100] + b\"...\", self._max_field_size)\n\n                    self._trailer_lines.append(line)\n\n                    if len(self._trailer_lines) > self._max_trailers:\n                        raise BadHttpMessage(\"Too many trailers received\")\n\n                    # \\r\\n\\r\\n found, end of stream\n                    if self._trailer_lines[-1] == b\"\":\n                        # Headers and trailers are defined the same way,\n                        # so we reuse the HeadersParser here.\n                        try:\n                            trailers, raw_trailers = self._headers_parser.parse_headers(\n                                self._trailer_lines\n                            )\n                        finally:\n                            self._trailer_lines.clear()\n                        self.payload.feed_eof()\n                        return True, chunk\n\n        # Read all bytes until eof\n        elif self._type == ParseState.PARSE_UNTIL_EOF:\n            self.payload.feed_data(chunk)\n\n        return False, b\"\"\n\n\nclass DeflateBuffer:\n    \"\"\"DeflateStream decompress stream and feed data into specified stream.\"\"\"\n\n    def __init__(\n        self,\n        out: StreamReader,\n        encoding: str | None,\n        max_decompress_size: int = DEFAULT_MAX_DECOMPRESS_SIZE,\n    ) -> None:\n        self.out = out\n        self.size = 0\n        out.total_compressed_bytes = self.size\n        self.encoding = encoding\n        self._started_decoding = False\n\n        self.decompressor: BrotliDecompressor | ZLibDecompressor | ZSTDDecompressor\n        if encoding == \"br\":\n            if not HAS_BROTLI:\n                raise ContentEncodingError(\n                    \"Can not decode content-encoding: brotli (br). \"\n                    \"Please install `Brotli`\"\n                )\n            self.decompressor = BrotliDecompressor()\n        elif encoding == \"zstd\":\n            if not HAS_ZSTD:\n                raise ContentEncodingError(\n                    \"Can not decode content-encoding: zstandard (zstd). \"\n                    \"Please install `backports.zstd`\"\n                )\n            self.decompressor = ZSTDDecompressor()\n        else:\n            self.decompressor = ZLibDecompressor(encoding=encoding)\n\n        self._max_decompress_size = max_decompress_size\n\n    def set_exception(\n        self,\n        exc: type[BaseException] | BaseException,\n        exc_cause: BaseException = _EXC_SENTINEL,\n    ) -> None:\n        set_exception(self.out, exc, exc_cause)\n\n    def feed_data(self, chunk: bytes) -> None:\n        if not chunk:\n            return\n\n        self.size += len(chunk)\n        self.out.total_compressed_bytes = self.size\n\n        # RFC1950\n        # bits 0..3 = CM = 0b1000 = 8 = \"deflate\"\n        # bits 4..7 = CINFO = 1..7 = windows size.\n        if (\n            not self._started_decoding\n            and self.encoding == \"deflate\"\n            and chunk[0] & 0xF != 8\n        ):\n            # Change the decoder to decompress incorrectly compressed data\n            # Actually we should issue a warning about non-RFC-compliant data.\n            self.decompressor = ZLibDecompressor(\n                encoding=self.encoding, suppress_deflate_header=True\n            )\n\n        try:\n            # Decompress with limit + 1 so we can detect if output exceeds limit\n            chunk = self.decompressor.decompress_sync(\n                chunk, max_length=self._max_decompress_size + 1\n            )\n        except Exception:\n            raise ContentEncodingError(\n                \"Can not decode content-encoding: %s\" % self.encoding\n            )\n\n        self._started_decoding = True\n\n        # Check if decompression limit was exceeded\n        if len(chunk) > self._max_decompress_size:\n            raise DecompressSizeError(\n                \"Decompressed data exceeds the configured limit of %d bytes\"\n                % self._max_decompress_size\n            )\n\n        if chunk:\n            self.out.feed_data(chunk)\n\n    def feed_eof(self) -> None:\n        chunk = self.decompressor.flush()\n\n        if chunk or self.size > 0:\n            self.out.feed_data(chunk)\n            # decompressor is not brotli unless encoding is \"br\"\n            if self.encoding == \"deflate\" and not self.decompressor.eof:  # type: ignore[union-attr]\n                raise ContentEncodingError(\"deflate\")\n\n        self.out.feed_eof()\n\n    def begin_http_chunk_receiving(self) -> None:\n        self.out.begin_http_chunk_receiving()\n\n    def end_http_chunk_receiving(self) -> None:\n        self.out.end_http_chunk_receiving()\n\n\nHttpRequestParserPy = HttpRequestParser\nHttpResponseParserPy = HttpResponseParser\nRawRequestMessagePy = RawRequestMessage\nRawResponseMessagePy = RawResponseMessage\n\nwith suppress(ImportError):\n    if not NO_EXTENSIONS:\n        from ._http_parser import (  # type: ignore[import-not-found,no-redef]\n            HttpRequestParser,\n            HttpResponseParser,\n            RawRequestMessage,\n            RawResponseMessage,\n        )\n\n        HttpRequestParserC = HttpRequestParser\n        HttpResponseParserC = HttpResponseParser\n        RawRequestMessageC = RawRequestMessage\n        RawResponseMessageC = RawResponseMessage\n"
  },
  {
    "path": "aiohttp/http_websocket.py",
    "content": "\"\"\"WebSocket protocol versions 13 and 8.\"\"\"\n\nfrom ._websocket.helpers import WS_KEY, ws_ext_gen, ws_ext_parse\nfrom ._websocket.models import (\n    WS_CLOSED_MESSAGE,\n    WS_CLOSING_MESSAGE,\n    WebSocketError,\n    WSCloseCode,\n    WSHandshakeError,\n    WSMessage,\n    WSMessageBinary,\n    WSMessageClose,\n    WSMessageClosed,\n    WSMessageClosing,\n    WSMessageContinuation,\n    WSMessageDecodeText,\n    WSMessageError,\n    WSMessageNoDecodeText,\n    WSMessagePing,\n    WSMessagePong,\n    WSMessageText,\n    WSMessageTextBytes,\n    WSMsgType,\n)\nfrom ._websocket.reader import WebSocketReader\nfrom ._websocket.writer import WebSocketWriter\n\n# Messages that the WebSocketResponse.receive needs to handle internally\n_INTERNAL_RECEIVE_TYPES = frozenset(\n    (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.PING, WSMsgType.PONG)\n)\n\n\n__all__ = (\n    \"WS_CLOSED_MESSAGE\",\n    \"WS_CLOSING_MESSAGE\",\n    \"WS_KEY\",\n    \"WebSocketReader\",\n    \"WebSocketWriter\",\n    \"WSMessage\",\n    \"WSMessageDecodeText\",\n    \"WSMessageNoDecodeText\",\n    \"WebSocketError\",\n    \"WSMsgType\",\n    \"WSCloseCode\",\n    \"ws_ext_gen\",\n    \"ws_ext_parse\",\n    \"WSMessageError\",\n    \"WSHandshakeError\",\n    \"WSMessageClose\",\n    \"WSMessageClosed\",\n    \"WSMessageClosing\",\n    \"WSMessagePong\",\n    \"WSMessageBinary\",\n    \"WSMessageText\",\n    \"WSMessageTextBytes\",\n    \"WSMessagePing\",\n    \"WSMessageContinuation\",\n)\n"
  },
  {
    "path": "aiohttp/http_writer.py",
    "content": "\"\"\"Http related parsers and protocol.\"\"\"\n\nimport asyncio\nimport sys\nfrom typing import (  # noqa\n    TYPE_CHECKING,\n    Any,\n    Awaitable,\n    Callable,\n    Iterable,\n    List,\n    NamedTuple,\n    Optional,\n    Union,\n)\n\nfrom multidict import CIMultiDict\n\nfrom .abc import AbstractStreamWriter\nfrom .base_protocol import BaseProtocol\nfrom .client_exceptions import ClientConnectionResetError\nfrom .compression_utils import ZLibCompressor\nfrom .helpers import NO_EXTENSIONS\n\n__all__ = (\"StreamWriter\", \"HttpVersion\", \"HttpVersion10\", \"HttpVersion11\")\n\n\nMIN_PAYLOAD_FOR_WRITELINES = 2048\nIS_PY313_BEFORE_313_2 = (3, 13, 0) <= sys.version_info < (3, 13, 2)\nIS_PY_BEFORE_312_9 = sys.version_info < (3, 12, 9)\nSKIP_WRITELINES = IS_PY313_BEFORE_313_2 or IS_PY_BEFORE_312_9\n# writelines is not safe for use\n# on Python 3.12+ until 3.12.9\n# on Python 3.13+ until 3.13.2\n# and on older versions it not any faster than write\n# CVE-2024-12254: https://github.com/python/cpython/pull/127656\n\n\nclass HttpVersion(NamedTuple):\n    major: int\n    minor: int\n\n\nHttpVersion10 = HttpVersion(1, 0)\nHttpVersion11 = HttpVersion(1, 1)\n\n\n_T_OnChunkSent = Optional[\n    Callable[\n        [Union[bytes, bytearray, \"memoryview[int]\", \"memoryview[bytes]\"]],\n        Awaitable[None],\n    ]\n]\n_T_OnHeadersSent = Optional[Callable[[\"CIMultiDict[str]\"], Awaitable[None]]]\n\n\nclass StreamWriter(AbstractStreamWriter):\n\n    length: int | None = None\n    chunked: bool = False\n    _eof: bool = False\n    _compress: ZLibCompressor | None = None\n\n    def __init__(\n        self,\n        protocol: BaseProtocol,\n        loop: asyncio.AbstractEventLoop,\n        on_chunk_sent: _T_OnChunkSent = None,\n        on_headers_sent: _T_OnHeadersSent = None,\n    ) -> None:\n        self._protocol = protocol\n        self.loop = loop\n        self._on_chunk_sent: _T_OnChunkSent = on_chunk_sent\n        self._on_headers_sent: _T_OnHeadersSent = on_headers_sent\n        self._headers_buf: bytes | None = None\n        self._headers_written: bool = False\n\n    @property\n    def transport(self) -> asyncio.Transport | None:\n        return self._protocol.transport\n\n    @property\n    def protocol(self) -> BaseProtocol:\n        return self._protocol\n\n    def enable_chunking(self) -> None:\n        self.chunked = True\n\n    def enable_compression(\n        self, encoding: str = \"deflate\", strategy: int | None = None\n    ) -> None:\n        self._compress = ZLibCompressor(encoding=encoding, strategy=strategy)\n\n    def _write(\n        self, chunk: Union[bytes, bytearray, \"memoryview[int]\", \"memoryview[bytes]\"]\n    ) -> None:\n        size = len(chunk)\n        self.buffer_size += size\n        self.output_size += size\n        transport = self._protocol.transport\n        if transport is None or transport.is_closing():\n            raise ClientConnectionResetError(\"Cannot write to closing transport\")\n        transport.write(chunk)\n\n    def _writelines(\n        self,\n        chunks: Iterable[\n            Union[bytes, bytearray, \"memoryview[int]\", \"memoryview[bytes]\"]\n        ],\n    ) -> None:\n        size = 0\n        for chunk in chunks:\n            size += len(chunk)\n        self.buffer_size += size\n        self.output_size += size\n        transport = self._protocol.transport\n        if transport is None or transport.is_closing():\n            raise ClientConnectionResetError(\"Cannot write to closing transport\")\n        if SKIP_WRITELINES or size < MIN_PAYLOAD_FOR_WRITELINES:\n            transport.write(b\"\".join(chunks))\n        else:\n            transport.writelines(chunks)\n\n    def _write_chunked_payload(\n        self, chunk: Union[bytes, bytearray, \"memoryview[int]\", \"memoryview[bytes]\"]\n    ) -> None:\n        \"\"\"Write a chunk with proper chunked encoding.\"\"\"\n        chunk_len_pre = f\"{len(chunk):x}\\r\\n\".encode(\"ascii\")\n        self._writelines((chunk_len_pre, chunk, b\"\\r\\n\"))\n\n    def _send_headers_with_payload(\n        self,\n        chunk: Union[bytes, bytearray, \"memoryview[int]\", \"memoryview[bytes]\"],\n        is_eof: bool,\n    ) -> None:\n        \"\"\"Send buffered headers with payload, coalescing into single write.\"\"\"\n        # Mark headers as written\n        self._headers_written = True\n        headers_buf = self._headers_buf\n        self._headers_buf = None\n\n        if TYPE_CHECKING:\n            # Safe because callers (write() and write_eof()) only invoke this method\n            # after checking that self._headers_buf is truthy\n            assert headers_buf is not None\n\n        if not self.chunked:\n            # Non-chunked: coalesce headers with body\n            if chunk:\n                self._writelines((headers_buf, chunk))\n            else:\n                self._write(headers_buf)\n            return\n\n        # Coalesce headers with chunked data\n        if chunk:\n            chunk_len_pre = f\"{len(chunk):x}\\r\\n\".encode(\"ascii\")\n            if is_eof:\n                self._writelines((headers_buf, chunk_len_pre, chunk, b\"\\r\\n0\\r\\n\\r\\n\"))\n            else:\n                self._writelines((headers_buf, chunk_len_pre, chunk, b\"\\r\\n\"))\n        elif is_eof:\n            self._writelines((headers_buf, b\"0\\r\\n\\r\\n\"))\n        else:\n            self._write(headers_buf)\n\n    async def write(\n        self,\n        chunk: Union[bytes, bytearray, \"memoryview[int]\", \"memoryview[bytes]\"],\n        *,\n        drain: bool = True,\n        LIMIT: int = 0x10000,\n    ) -> None:\n        \"\"\"\n        Writes chunk of data to a stream.\n\n        write_eof() indicates end of stream.\n        writer can't be used after write_eof() method being called.\n        write() return drain future.\n        \"\"\"\n        if self._on_chunk_sent is not None:\n            await self._on_chunk_sent(chunk)\n\n        if isinstance(chunk, memoryview):\n            if chunk.nbytes != len(chunk):\n                # just reshape it\n                chunk = chunk.cast(\"c\")\n\n        if self._compress is not None:\n            chunk = await self._compress.compress(chunk)\n            if not chunk:\n                return\n\n        if self.length is not None:\n            chunk_len = len(chunk)\n            if self.length >= chunk_len:\n                self.length = self.length - chunk_len\n            else:\n                chunk = chunk[: self.length]\n                self.length = 0\n                if not chunk:\n                    return\n\n        # Handle buffered headers for small payload optimization\n        if self._headers_buf and not self._headers_written:\n            self._send_headers_with_payload(chunk, False)\n            if drain and self.buffer_size > LIMIT:\n                self.buffer_size = 0\n                await self.drain()\n            return\n\n        if chunk:\n            if self.chunked:\n                self._write_chunked_payload(chunk)\n            else:\n                self._write(chunk)\n\n            if drain and self.buffer_size > LIMIT:\n                self.buffer_size = 0\n                await self.drain()\n\n    async def write_headers(\n        self, status_line: str, headers: \"CIMultiDict[str]\"\n    ) -> None:\n        \"\"\"Write headers to the stream.\"\"\"\n        if self._on_headers_sent is not None:\n            await self._on_headers_sent(headers)\n        # status + headers\n        buf = _serialize_headers(status_line, headers)\n        self._headers_written = False\n        self._headers_buf = buf\n\n    def send_headers(self) -> None:\n        \"\"\"Force sending buffered headers if not already sent.\"\"\"\n        if not self._headers_buf or self._headers_written:\n            return\n\n        self._headers_written = True\n        headers_buf = self._headers_buf\n        self._headers_buf = None\n\n        if TYPE_CHECKING:\n            # Safe because we only enter this block when self._headers_buf is truthy\n            assert headers_buf is not None\n\n        self._write(headers_buf)\n\n    def set_eof(self) -> None:\n        \"\"\"Indicate that the message is complete.\"\"\"\n        if self._eof:\n            return\n\n        # If headers haven't been sent yet, send them now\n        # This handles the case where there's no body at all\n        if self._headers_buf and not self._headers_written:\n            self._headers_written = True\n            headers_buf = self._headers_buf\n            self._headers_buf = None\n\n            if TYPE_CHECKING:\n                # Safe because we only enter this block when self._headers_buf is truthy\n                assert headers_buf is not None\n\n            # Combine headers and chunked EOF marker in a single write\n            if self.chunked:\n                self._writelines((headers_buf, b\"0\\r\\n\\r\\n\"))\n            else:\n                self._write(headers_buf)\n        elif self.chunked and self._headers_written:\n            # Headers already sent, just send the final chunk marker\n            self._write(b\"0\\r\\n\\r\\n\")\n\n        self._eof = True\n\n    async def write_eof(self, chunk: bytes = b\"\") -> None:\n        if self._eof:\n            return\n\n        if chunk and self._on_chunk_sent is not None:\n            await self._on_chunk_sent(chunk)\n\n        # Handle body/compression\n        if self._compress:\n            chunks: list[bytes] = []\n            chunks_len = 0\n            if chunk and (compressed_chunk := await self._compress.compress(chunk)):\n                chunks_len = len(compressed_chunk)\n                chunks.append(compressed_chunk)\n\n            flush_chunk = self._compress.flush()\n            chunks_len += len(flush_chunk)\n            chunks.append(flush_chunk)\n            assert chunks_len\n\n            # Send buffered headers with compressed data if not yet sent\n            if self._headers_buf and not self._headers_written:\n                self._headers_written = True\n                headers_buf = self._headers_buf\n                self._headers_buf = None\n\n                if self.chunked:\n                    # Coalesce headers with compressed chunked data\n                    chunk_len_pre = f\"{chunks_len:x}\\r\\n\".encode(\"ascii\")\n                    self._writelines(\n                        (headers_buf, chunk_len_pre, *chunks, b\"\\r\\n0\\r\\n\\r\\n\")\n                    )\n                else:\n                    # Coalesce headers with compressed data\n                    self._writelines((headers_buf, *chunks))\n                await self.drain()\n                self._eof = True\n                return\n\n            # Headers already sent, just write compressed data\n            if self.chunked:\n                chunk_len_pre = f\"{chunks_len:x}\\r\\n\".encode(\"ascii\")\n                self._writelines((chunk_len_pre, *chunks, b\"\\r\\n0\\r\\n\\r\\n\"))\n            elif len(chunks) > 1:\n                self._writelines(chunks)\n            else:\n                self._write(chunks[0])\n            await self.drain()\n            self._eof = True\n            return\n\n        # No compression - send buffered headers if not yet sent\n        if self._headers_buf and not self._headers_written:\n            # Use helper to send headers with payload\n            self._send_headers_with_payload(chunk, True)\n            await self.drain()\n            self._eof = True\n            return\n\n        # Handle remaining body\n        if self.chunked:\n            if chunk:\n                # Write final chunk with EOF marker\n                self._writelines(\n                    (f\"{len(chunk):x}\\r\\n\".encode(\"ascii\"), chunk, b\"\\r\\n0\\r\\n\\r\\n\")\n                )\n            else:\n                self._write(b\"0\\r\\n\\r\\n\")\n            await self.drain()\n            self._eof = True\n            return\n\n        if chunk:\n            self._write(chunk)\n            await self.drain()\n\n        self._eof = True\n\n    async def drain(self) -> None:\n        \"\"\"Flush the write buffer.\n\n        The intended use is to write\n\n          await w.write(data)\n          await w.drain()\n        \"\"\"\n        protocol = self._protocol\n        if protocol.transport is not None and protocol._paused:\n            await protocol._drain_helper()\n\n\ndef _safe_header(string: str) -> str:\n    if \"\\r\" in string or \"\\n\" in string or \"\\x00\" in string:\n        raise ValueError(\n            \"Newline, carriage return, or null byte detected in headers. \"\n            \"Potential header injection attack.\"\n        )\n    return string\n\n\ndef _py_serialize_headers(status_line: str, headers: \"CIMultiDict[str]\") -> bytes:\n    _safe_header(status_line)\n    headers_gen = (_safe_header(k) + \": \" + _safe_header(v) for k, v in headers.items())\n    line = status_line + \"\\r\\n\" + \"\\r\\n\".join(headers_gen) + \"\\r\\n\\r\\n\"\n    return line.encode(\"utf-8\")\n\n\n_serialize_headers = _py_serialize_headers\n\ntry:\n    import aiohttp._http_writer as _http_writer  # type: ignore[import-not-found]\n\n    _c_serialize_headers = _http_writer._serialize_headers\n    if not NO_EXTENSIONS:\n        _serialize_headers = _c_serialize_headers\nexcept ImportError:\n    pass\n"
  },
  {
    "path": "aiohttp/log.py",
    "content": "import logging\n\naccess_logger = logging.getLogger(\"aiohttp.access\")\nclient_logger = logging.getLogger(\"aiohttp.client\")\ninternal_logger = logging.getLogger(\"aiohttp.internal\")\nserver_logger = logging.getLogger(\"aiohttp.server\")\nweb_logger = logging.getLogger(\"aiohttp.web\")\nws_logger = logging.getLogger(\"aiohttp.websocket\")\n"
  },
  {
    "path": "aiohttp/multipart.py",
    "content": "import base64\nimport binascii\nimport json\nimport re\nimport sys\nimport uuid\nimport warnings\nfrom collections import deque\nfrom collections.abc import AsyncIterator, Iterator, Mapping, Sequence\nfrom types import TracebackType\nfrom typing import TYPE_CHECKING, Any, Union, cast\nfrom urllib.parse import parse_qsl, unquote, urlencode\n\nfrom multidict import CIMultiDict, CIMultiDictProxy\n\nfrom .abc import AbstractStreamWriter\nfrom .compression_utils import (\n    DEFAULT_MAX_DECOMPRESS_SIZE,\n    ZLibCompressor,\n    ZLibDecompressor,\n)\nfrom .hdrs import (\n    CONTENT_DISPOSITION,\n    CONTENT_ENCODING,\n    CONTENT_LENGTH,\n    CONTENT_TRANSFER_ENCODING,\n    CONTENT_TYPE,\n)\nfrom .helpers import CHAR, TOKEN, parse_mimetype, reify\nfrom .http import HeadersParser\nfrom .http_exceptions import BadHttpMessage\nfrom .log import internal_logger\nfrom .payload import (\n    JsonPayload,\n    LookupError,\n    Order,\n    Payload,\n    StringPayload,\n    get_payload,\n    payload_type,\n)\nfrom .streams import StreamReader\n\nif sys.version_info >= (3, 11):\n    from typing import Self\nelse:\n    from typing import TypeVar\n\n    Self = TypeVar(\"Self\", bound=\"BodyPartReader\")\n\n__all__ = (\n    \"MultipartReader\",\n    \"MultipartWriter\",\n    \"BodyPartReader\",\n    \"BadContentDispositionHeader\",\n    \"BadContentDispositionParam\",\n    \"parse_content_disposition\",\n    \"content_disposition_filename\",\n)\n\n\nif TYPE_CHECKING:\n    from .client_reqrep import ClientResponse\n\n\nclass BadContentDispositionHeader(RuntimeWarning):\n    pass\n\n\nclass BadContentDispositionParam(RuntimeWarning):\n    pass\n\n\ndef parse_content_disposition(\n    header: str | None,\n) -> tuple[str | None, dict[str, str]]:\n    def is_token(string: str) -> bool:\n        return bool(string) and TOKEN >= set(string)\n\n    def is_quoted(string: str) -> bool:\n        return string[0] == string[-1] == '\"'\n\n    def is_rfc5987(string: str) -> bool:\n        return is_token(string) and string.count(\"'\") == 2\n\n    def is_extended_param(string: str) -> bool:\n        return string.endswith(\"*\")\n\n    def is_continuous_param(string: str) -> bool:\n        pos = string.find(\"*\") + 1\n        if not pos:\n            return False\n        substring = string[pos:-1] if string.endswith(\"*\") else string[pos:]\n        return substring.isdigit()\n\n    def unescape(text: str, *, chars: str = \"\".join(map(re.escape, CHAR))) -> str:\n        return re.sub(f\"\\\\\\\\([{chars}])\", \"\\\\1\", text)\n\n    if not header:\n        return None, {}\n\n    disptype, *parts = header.split(\";\")\n    if not is_token(disptype):\n        warnings.warn(BadContentDispositionHeader(header))\n        return None, {}\n\n    params: dict[str, str] = {}\n    while parts:\n        item = parts.pop(0)\n\n        if not item:  # To handle trailing semicolons\n            warnings.warn(BadContentDispositionHeader(header))\n            continue\n\n        if \"=\" not in item:\n            warnings.warn(BadContentDispositionHeader(header))\n            return None, {}\n\n        key, value = item.split(\"=\", 1)\n        key = key.lower().strip()\n        value = value.lstrip()\n\n        if key in params:\n            warnings.warn(BadContentDispositionHeader(header))\n            return None, {}\n\n        if not is_token(key):\n            warnings.warn(BadContentDispositionParam(item))\n            continue\n\n        elif is_continuous_param(key):\n            if is_quoted(value):\n                value = unescape(value[1:-1])\n            elif not is_token(value):\n                warnings.warn(BadContentDispositionParam(item))\n                continue\n\n        elif is_extended_param(key):\n            if is_rfc5987(value):\n                encoding, _, value = value.split(\"'\", 2)\n                encoding = encoding or \"utf-8\"\n            else:\n                warnings.warn(BadContentDispositionParam(item))\n                continue\n\n            try:\n                value = unquote(value, encoding, \"strict\")\n            except UnicodeDecodeError:  # pragma: nocover\n                warnings.warn(BadContentDispositionParam(item))\n                continue\n\n        else:\n            failed = True\n            if is_quoted(value):\n                failed = False\n                value = unescape(value[1:-1].lstrip(\"\\\\/\"))\n            elif is_token(value):\n                failed = False\n            elif parts:\n                # maybe just ; in filename, in any case this is just\n                # one case fix, for proper fix we need to redesign parser\n                _value = f\"{value};{parts[0]}\"\n                if is_quoted(_value):\n                    parts.pop(0)\n                    value = unescape(_value[1:-1].lstrip(\"\\\\/\"))\n                    failed = False\n\n            if failed:\n                warnings.warn(BadContentDispositionHeader(header))\n                return None, {}\n\n        params[key] = value\n\n    return disptype.lower(), params\n\n\ndef content_disposition_filename(\n    params: Mapping[str, str], name: str = \"filename\"\n) -> str | None:\n    name_suf = \"%s*\" % name\n    if not params:\n        return None\n    elif name_suf in params:\n        return params[name_suf]\n    elif name in params:\n        return params[name]\n    else:\n        parts = []\n        fnparams = sorted(\n            (key, value) for key, value in params.items() if key.startswith(name_suf)\n        )\n        for num, (key, value) in enumerate(fnparams):\n            _, tail = key.split(\"*\", 1)\n            if tail.endswith(\"*\"):\n                tail = tail[:-1]\n            if tail == str(num):\n                parts.append(value)\n            else:\n                break\n        if not parts:\n            return None\n        value = \"\".join(parts)\n        if \"'\" in value:\n            encoding, _, value = value.split(\"'\", 2)\n            encoding = encoding or \"utf-8\"\n            return unquote(value, encoding, \"strict\")\n        return value\n\n\nclass MultipartResponseWrapper:\n    \"\"\"Wrapper around the MultipartReader.\n\n    It takes care about\n    underlying connection and close it when it needs in.\n    \"\"\"\n\n    def __init__(\n        self,\n        resp: \"ClientResponse\",\n        stream: \"MultipartReader\",\n    ) -> None:\n        self.resp = resp\n        self.stream = stream\n\n    def __aiter__(self) -> \"MultipartResponseWrapper\":\n        return self\n\n    async def __anext__(\n        self,\n    ) -> Union[\"MultipartReader\", \"BodyPartReader\"]:\n        part = await self.next()\n        if part is None:\n            raise StopAsyncIteration\n        return part\n\n    def at_eof(self) -> bool:\n        \"\"\"Returns True when all response data had been read.\"\"\"\n        return self.resp.content.at_eof()\n\n    async def next(\n        self,\n    ) -> Union[\"MultipartReader\", \"BodyPartReader\"] | None:\n        \"\"\"Emits next multipart reader object.\"\"\"\n        item = await self.stream.next()\n        if self.stream.at_eof():\n            await self.release()\n        return item\n\n    async def release(self) -> None:\n        \"\"\"Release the connection gracefully.\n\n        All remaining content is read to the void.\n        \"\"\"\n        self.resp.release()\n\n\nclass BodyPartReader:\n    \"\"\"Multipart reader for single body part.\"\"\"\n\n    chunk_size = 8192\n\n    def __init__(\n        self,\n        boundary: bytes,\n        headers: \"CIMultiDictProxy[str]\",\n        content: StreamReader,\n        *,\n        subtype: str = \"mixed\",\n        default_charset: str | None = None,\n        max_decompress_size: int = DEFAULT_MAX_DECOMPRESS_SIZE,\n    ) -> None:\n        self.headers = headers\n        self._boundary = boundary\n        self._boundary_len = len(boundary) + 2  # Boundary + \\r\\n\n        self._content = content\n        self._default_charset = default_charset\n        self._at_eof = False\n        self._is_form_data = subtype == \"form-data\"\n        # https://datatracker.ietf.org/doc/html/rfc7578#section-4.8\n        length = None if self._is_form_data else self.headers.get(CONTENT_LENGTH, None)\n        self._length = int(length) if length is not None else None\n        self._read_bytes = 0\n        self._unread: deque[bytes] = deque()\n        self._prev_chunk: bytes | None = None\n        self._content_eof = 0\n        self._cache: dict[str, Any] = {}\n        self._max_decompress_size = max_decompress_size\n\n    def __aiter__(self) -> Self:\n        return self\n\n    async def __anext__(self) -> bytes:\n        part = await self.next()\n        if part is None:\n            raise StopAsyncIteration\n        return part\n\n    async def next(self) -> bytes | None:\n        item = await self.read()\n        if not item:\n            return None\n        return item\n\n    async def read(self, *, decode: bool = False) -> bytes:\n        \"\"\"Reads body part data.\n\n        decode: Decodes data following by encoding\n                method from Content-Encoding header. If it missed\n                data remains untouched\n        \"\"\"\n        if self._at_eof:\n            return b\"\"\n        data = bytearray()\n        while not self._at_eof:\n            data.extend(await self.read_chunk(self.chunk_size))\n        # https://github.com/python/mypy/issues/17537\n        if decode:  # type: ignore[unreachable]\n            decoded_data = bytearray()\n            async for d in self.decode_iter(data):\n                decoded_data.extend(d)\n            return decoded_data\n        return data\n\n    async def read_chunk(self, size: int = chunk_size) -> bytes:\n        \"\"\"Reads body part content chunk of the specified size.\n\n        size: chunk size\n        \"\"\"\n        if self._at_eof:\n            return b\"\"\n        if self._length:\n            chunk = await self._read_chunk_from_length(size)\n        else:\n            chunk = await self._read_chunk_from_stream(size)\n\n        # For the case of base64 data, we must read a fragment of size with a\n        # remainder of 0 by dividing by 4 for string without symbols \\n or \\r\n        encoding = self.headers.get(CONTENT_TRANSFER_ENCODING)\n        if encoding and encoding.lower() == \"base64\":\n            stripped_chunk = b\"\".join(chunk.split())\n            remainder = len(stripped_chunk) % 4\n\n            while remainder != 0 and not self.at_eof():\n                over_chunk_size = 4 - remainder\n                over_chunk = b\"\"\n\n                if self._prev_chunk:\n                    over_chunk = self._prev_chunk[:over_chunk_size]\n                    self._prev_chunk = self._prev_chunk[len(over_chunk) :]\n\n                if len(over_chunk) != over_chunk_size:\n                    over_chunk += await self._content.read(4 - len(over_chunk))\n\n                if not over_chunk:\n                    self._at_eof = True\n\n                stripped_chunk += b\"\".join(over_chunk.split())\n                chunk += over_chunk\n                remainder = len(stripped_chunk) % 4\n\n        self._read_bytes += len(chunk)\n        if self._read_bytes == self._length:\n            self._at_eof = True\n        if self._at_eof and await self._content.readline() != b\"\\r\\n\":\n            raise ValueError(\"Reader did not read all the data or it is malformed\")\n        return chunk\n\n    async def _read_chunk_from_length(self, size: int) -> bytes:\n        # Reads body part content chunk of the specified size.\n        # The body part must has Content-Length header with proper value.\n        assert self._length is not None, \"Content-Length required for chunked read\"\n        chunk_size = min(size, self._length - self._read_bytes)\n        chunk = await self._content.read(chunk_size)\n        if self._content.at_eof():\n            self._at_eof = True\n        return chunk\n\n    async def _read_chunk_from_stream(self, size: int) -> bytes:\n        # Reads content chunk of body part with unknown length.\n        # The Content-Length header for body part is not necessary.\n        assert (\n            size >= self._boundary_len\n        ), \"Chunk size must be greater or equal than boundary length + 2\"\n        first_chunk = self._prev_chunk is None\n        if first_chunk:\n            # We need to re-add the CRLF that got removed from headers parsing.\n            self._prev_chunk = b\"\\r\\n\" + await self._content.read(size)\n\n        chunk = b\"\"\n        # content.read() may return less than size, so we need to loop to ensure\n        # we have enough data to detect the boundary.\n        while len(chunk) < self._boundary_len:\n            chunk += await self._content.read(size)\n            self._content_eof += int(self._content.at_eof())\n            if self._content_eof > 2:\n                raise ValueError(\"Reading after EOF\")\n            if self._content_eof:\n                break\n        if len(chunk) > size:\n            self._content.unread_data(chunk[size:])\n            chunk = chunk[:size]\n\n        assert self._prev_chunk is not None\n        window = self._prev_chunk + chunk\n        sub = b\"\\r\\n\" + self._boundary\n        if first_chunk:\n            idx = window.find(sub)\n        else:\n            idx = window.find(sub, max(0, len(self._prev_chunk) - len(sub)))\n        if idx >= 0:\n            # pushing boundary back to content\n            with warnings.catch_warnings():\n                warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n                self._content.unread_data(window[idx:])\n            self._prev_chunk = self._prev_chunk[:idx]\n            chunk = window[len(self._prev_chunk) : idx]\n            if not chunk:\n                self._at_eof = True\n        result = self._prev_chunk[2 if first_chunk else 0 :]  # Strip initial CRLF\n        self._prev_chunk = chunk\n        return result\n\n    async def readline(self) -> bytes:\n        \"\"\"Reads body part by line by line.\"\"\"\n        if self._at_eof:\n            return b\"\"\n\n        if self._unread:\n            line = self._unread.popleft()\n        else:\n            line = await self._content.readline()\n\n        if line.startswith(self._boundary):\n            # the very last boundary may not come with \\r\\n,\n            # so set single rules for everyone\n            sline = line.rstrip(b\"\\r\\n\")\n            boundary = self._boundary\n            last_boundary = self._boundary + b\"--\"\n            # ensure that we read exactly the boundary, not something alike\n            if sline == boundary or sline == last_boundary:\n                self._at_eof = True\n                self._unread.append(line)\n                return b\"\"\n        else:\n            next_line = await self._content.readline()\n            if next_line.startswith(self._boundary):\n                line = line[:-2]  # strip CRLF but only once\n            self._unread.append(next_line)\n\n        return line\n\n    async def release(self) -> None:\n        \"\"\"Like read(), but reads all the data to the void.\"\"\"\n        if self._at_eof:\n            return\n        while not self._at_eof:\n            await self.read_chunk(self.chunk_size)\n\n    async def text(self, *, encoding: str | None = None) -> str:\n        \"\"\"Like read(), but assumes that body part contains text data.\"\"\"\n        data = await self.read(decode=True)\n        # see https://www.w3.org/TR/html5/forms.html#multipart/form-data-encoding-algorithm\n        # and https://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-send\n        encoding = encoding or self.get_charset(default=\"utf-8\")\n        return data.decode(encoding)\n\n    async def json(self, *, encoding: str | None = None) -> dict[str, Any] | None:\n        \"\"\"Like read(), but assumes that body parts contains JSON data.\"\"\"\n        data = await self.read(decode=True)\n        if not data:\n            return None\n        encoding = encoding or self.get_charset(default=\"utf-8\")\n        return cast(dict[str, Any], json.loads(data.decode(encoding)))\n\n    async def form(self, *, encoding: str | None = None) -> list[tuple[str, str]]:\n        \"\"\"Like read(), but assumes that body parts contain form urlencoded data.\"\"\"\n        data = await self.read(decode=True)\n        if not data:\n            return []\n        if encoding is not None:\n            real_encoding = encoding\n        else:\n            real_encoding = self.get_charset(default=\"utf-8\")\n        try:\n            decoded_data = data.rstrip().decode(real_encoding)\n        except UnicodeDecodeError:\n            raise ValueError(\"data cannot be decoded with %s encoding\" % real_encoding)\n\n        return parse_qsl(\n            decoded_data,\n            keep_blank_values=True,\n            encoding=real_encoding,\n        )\n\n    def at_eof(self) -> bool:\n        \"\"\"Returns True if the boundary was reached or False otherwise.\"\"\"\n        return self._at_eof\n\n    def _apply_content_transfer_decoding(self, data: bytes) -> bytes:\n        \"\"\"Apply Content-Transfer-Encoding decoding if header is present.\"\"\"\n        if CONTENT_TRANSFER_ENCODING in self.headers:\n            return self._decode_content_transfer(data)\n        return data\n\n    def _needs_content_decoding(self) -> bool:\n        \"\"\"Check if Content-Encoding decoding should be applied.\"\"\"\n        # https://datatracker.ietf.org/doc/html/rfc7578#section-4.8\n        return not self._is_form_data and CONTENT_ENCODING in self.headers\n\n    def decode(self, data: bytes) -> bytes:\n        \"\"\"Decodes data synchronously.\n\n        Decodes data according the specified Content-Encoding\n        or Content-Transfer-Encoding headers value.\n\n        Note: For large payloads, consider using decode_iter() instead\n        to avoid blocking the event loop during decompression.\n        \"\"\"\n        data = self._apply_content_transfer_decoding(data)\n        if self._needs_content_decoding():\n            return self._decode_content(data)\n        return data\n\n    async def decode_iter(self, data: bytes) -> AsyncIterator[bytes]:\n        \"\"\"Async generator that yields decoded data chunks.\n\n        Decodes data according the specified Content-Encoding\n        or Content-Transfer-Encoding headers value.\n\n        This method offloads decompression to an executor for large payloads\n        to avoid blocking the event loop.\n        \"\"\"\n        data = self._apply_content_transfer_decoding(data)\n        if self._needs_content_decoding():\n            async for d in self._decode_content_async(data):\n                yield d\n        else:\n            yield data\n\n    def _decode_content(self, data: bytes) -> bytes:\n        encoding = self.headers.get(CONTENT_ENCODING, \"\").lower()\n        if encoding == \"identity\":\n            return data\n        if encoding in {\"deflate\", \"gzip\"}:\n            return ZLibDecompressor(\n                encoding=encoding,\n                suppress_deflate_header=True,\n            ).decompress_sync(data, max_length=self._max_decompress_size)\n\n        raise RuntimeError(f\"unknown content encoding: {encoding}\")\n\n    async def _decode_content_async(self, data: bytes) -> AsyncIterator[bytes]:\n        encoding = self.headers.get(CONTENT_ENCODING, \"\").lower()\n        if encoding == \"identity\":\n            yield data\n        elif encoding in {\"deflate\", \"gzip\"}:\n            d = ZLibDecompressor(\n                encoding=encoding,\n                suppress_deflate_header=True,\n            )\n            yield await d.decompress(data, max_length=self._max_decompress_size)\n        else:\n            raise RuntimeError(f\"unknown content encoding: {encoding}\")\n\n    def _decode_content_transfer(self, data: bytes) -> bytes:\n        encoding = self.headers.get(CONTENT_TRANSFER_ENCODING, \"\").lower()\n\n        if encoding == \"base64\":\n            return base64.b64decode(data)\n        elif encoding == \"quoted-printable\":\n            return binascii.a2b_qp(data)\n        elif encoding in (\"binary\", \"8bit\", \"7bit\"):\n            return data\n        else:\n            raise RuntimeError(f\"unknown content transfer encoding: {encoding}\")\n\n    def get_charset(self, default: str) -> str:\n        \"\"\"Returns charset parameter from Content-Type header or default.\"\"\"\n        ctype = self.headers.get(CONTENT_TYPE, \"\")\n        mimetype = parse_mimetype(ctype)\n        return mimetype.parameters.get(\"charset\", self._default_charset or default)\n\n    @reify\n    def name(self) -> str | None:\n        \"\"\"Returns name specified in Content-Disposition header.\n\n        If the header is missing or malformed, returns None.\n        \"\"\"\n        _, params = parse_content_disposition(self.headers.get(CONTENT_DISPOSITION))\n        return content_disposition_filename(params, \"name\")\n\n    @reify\n    def filename(self) -> str | None:\n        \"\"\"Returns filename specified in Content-Disposition header.\n\n        Returns None if the header is missing or malformed.\n        \"\"\"\n        _, params = parse_content_disposition(self.headers.get(CONTENT_DISPOSITION))\n        return content_disposition_filename(params, \"filename\")\n\n\n@payload_type(BodyPartReader, order=Order.try_first)\nclass BodyPartReaderPayload(Payload):\n    _value: BodyPartReader\n    # _autoclose = False (inherited) - Streaming reader that may have resources\n\n    def __init__(self, value: BodyPartReader, *args: Any, **kwargs: Any) -> None:\n        super().__init__(value, *args, **kwargs)\n\n        params: dict[str, str] = {}\n        if value.name is not None:\n            params[\"name\"] = value.name\n        if value.filename is not None:\n            params[\"filename\"] = value.filename\n\n        if params:\n            self.set_content_disposition(\"attachment\", True, **params)\n\n    def decode(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> str:\n        raise TypeError(\"Unable to decode.\")\n\n    async def as_bytes(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> bytes:\n        \"\"\"Raises TypeError as body parts should be consumed via write().\n\n        This is intentional: BodyPartReader payloads are designed for streaming\n        large data (potentially gigabytes) and must be consumed only once via\n        the write() method to avoid memory exhaustion. They cannot be buffered\n        in memory for reuse.\n        \"\"\"\n        raise TypeError(\"Unable to read body part as bytes. Use write() to consume.\")\n\n    async def write(self, writer: AbstractStreamWriter) -> None:\n        field = self._value\n        while chunk := await field.read_chunk(size=2**18):\n            async for d in field.decode_iter(chunk):\n                await writer.write(d)\n\n\nclass MultipartReader:\n    \"\"\"Multipart body reader.\"\"\"\n\n    #: Response wrapper, used when multipart readers constructs from response.\n    response_wrapper_cls = MultipartResponseWrapper\n    #: Multipart reader class, used to handle multipart/* body parts.\n    #: None points to type(self)\n    multipart_reader_cls: type[\"MultipartReader\"] | None = None\n    #: Body part reader class for non multipart/* content types.\n    part_reader_cls = BodyPartReader\n\n    def __init__(\n        self,\n        headers: Mapping[str, str],\n        content: StreamReader,\n        *,\n        max_field_size: int = 8190,\n        max_headers: int = 128,\n    ) -> None:\n        self._mimetype = parse_mimetype(headers[CONTENT_TYPE])\n        assert self._mimetype.type == \"multipart\", \"multipart/* content type expected\"\n        if \"boundary\" not in self._mimetype.parameters:\n            raise ValueError(\n                \"boundary missed for Content-Type: %s\" % headers[CONTENT_TYPE]\n            )\n\n        self.headers = headers\n        self._boundary = (\"--\" + self._get_boundary()).encode()\n        self._content = content\n        self._default_charset: str | None = None\n        self._last_part: MultipartReader | BodyPartReader | None = None\n        self._max_field_size = max_field_size\n        self._max_headers = max_headers\n        self._at_eof = False\n        self._at_bof = True\n        self._unread: list[bytes] = []\n\n    def __aiter__(self) -> Self:\n        return self\n\n    async def __anext__(\n        self,\n    ) -> Union[\"MultipartReader\", BodyPartReader] | None:\n        part = await self.next()\n        if part is None:\n            raise StopAsyncIteration\n        return part\n\n    @classmethod\n    def from_response(\n        cls,\n        response: \"ClientResponse\",\n    ) -> MultipartResponseWrapper:\n        \"\"\"Constructs reader instance from HTTP response.\n\n        :param response: :class:`~aiohttp.client.ClientResponse` instance\n        \"\"\"\n        obj = cls.response_wrapper_cls(\n            response, cls(response.headers, response.content)\n        )\n        return obj\n\n    def at_eof(self) -> bool:\n        \"\"\"Returns True if the final boundary was reached, false otherwise.\"\"\"\n        return self._at_eof\n\n    async def next(\n        self,\n    ) -> Union[\"MultipartReader\", BodyPartReader] | None:\n        \"\"\"Emits the next multipart body part.\"\"\"\n        # So, if we're at BOF, we need to skip till the boundary.\n        if self._at_eof:\n            return None\n        await self._maybe_release_last_part()\n        if self._at_bof:\n            await self._read_until_first_boundary()\n            self._at_bof = False\n        else:\n            await self._read_boundary()\n        if self._at_eof:  # we just read the last boundary, nothing to do there\n            # https://github.com/python/mypy/issues/17537\n            return None  # type: ignore[unreachable]\n\n        part = await self.fetch_next_part()\n        # https://datatracker.ietf.org/doc/html/rfc7578#section-4.6\n        if (\n            self._last_part is None\n            and self._mimetype.subtype == \"form-data\"\n            and isinstance(part, BodyPartReader)\n        ):\n            _, params = parse_content_disposition(part.headers.get(CONTENT_DISPOSITION))\n            if params.get(\"name\") == \"_charset_\":\n                # Longest encoding in https://encoding.spec.whatwg.org/encodings.json\n                # is 19 characters, so 32 should be more than enough for any valid encoding.\n                charset = await part.read_chunk(32)\n                if len(charset) > 31:\n                    raise RuntimeError(\"Invalid default charset\")\n                self._default_charset = charset.strip().decode()\n                part = await self.fetch_next_part()\n        self._last_part = part\n        return self._last_part\n\n    async def release(self) -> None:\n        \"\"\"Reads all the body parts to the void till the final boundary.\"\"\"\n        while not self._at_eof:\n            item = await self.next()\n            if item is None:\n                break\n            await item.release()\n\n    async def fetch_next_part(\n        self,\n    ) -> Union[\"MultipartReader\", BodyPartReader]:\n        \"\"\"Returns the next body part reader.\"\"\"\n        headers = await self._read_headers()\n        return self._get_part_reader(headers)\n\n    def _get_part_reader(\n        self,\n        headers: \"CIMultiDictProxy[str]\",\n    ) -> Union[\"MultipartReader\", BodyPartReader]:\n        \"\"\"Dispatches the response by the `Content-Type` header.\n\n        Returns a suitable reader instance.\n\n        :param dict headers: Response headers\n        \"\"\"\n        ctype = headers.get(CONTENT_TYPE, \"\")\n        mimetype = parse_mimetype(ctype)\n\n        if mimetype.type == \"multipart\":\n            if self.multipart_reader_cls is None:\n                return type(self)(headers, self._content)\n            return self.multipart_reader_cls(\n                headers,\n                self._content,\n                max_field_size=self._max_field_size,\n                max_headers=self._max_headers,\n            )\n        else:\n            return self.part_reader_cls(\n                self._boundary,\n                headers,\n                self._content,\n                subtype=self._mimetype.subtype,\n                default_charset=self._default_charset,\n            )\n\n    def _get_boundary(self) -> str:\n        boundary = self._mimetype.parameters[\"boundary\"]\n        if len(boundary) > 70:\n            raise ValueError(\"boundary %r is too long (70 chars max)\" % boundary)\n\n        return boundary\n\n    async def _readline(self) -> bytes:\n        if self._unread:\n            return self._unread.pop()\n        return await self._content.readline()\n\n    async def _read_until_first_boundary(self) -> None:\n        while True:\n            chunk = await self._readline()\n            if chunk == b\"\":\n                raise ValueError(f\"Could not find starting boundary {self._boundary!r}\")\n            chunk = chunk.rstrip()\n            if chunk == self._boundary:\n                return\n            elif chunk == self._boundary + b\"--\":\n                self._at_eof = True\n                return\n\n    async def _read_boundary(self) -> None:\n        chunk = (await self._readline()).rstrip()\n        if chunk == self._boundary:\n            pass\n        elif chunk == self._boundary + b\"--\":\n            self._at_eof = True\n            epilogue = await self._readline()\n            next_line = await self._readline()\n\n            # the epilogue is expected and then either the end of input or the\n            # parent multipart boundary, if the parent boundary is found then\n            # it should be marked as unread and handed to the parent for\n            # processing\n            if next_line[:2] == b\"--\":\n                self._unread.append(next_line)\n            # otherwise the request is likely missing an epilogue and both\n            # lines should be passed to the parent for processing\n            # (this handles the old behavior gracefully)\n            else:\n                self._unread.extend([next_line, epilogue])\n        else:\n            raise ValueError(f\"Invalid boundary {chunk!r}, expected {self._boundary!r}\")\n\n    async def _read_headers(self) -> \"CIMultiDictProxy[str]\":\n        lines = []\n        while True:\n            chunk = await self._content.readline(max_line_length=self._max_field_size)\n            chunk = chunk.rstrip(b\"\\r\\n\")\n            lines.append(chunk)\n            if not chunk:\n                break\n            if len(lines) > self._max_headers:\n                raise BadHttpMessage(\"Too many headers received\")\n        parser = HeadersParser(max_field_size=self._max_field_size)\n        headers, raw_headers = parser.parse_headers(lines)\n        return headers\n\n    async def _maybe_release_last_part(self) -> None:\n        \"\"\"Ensures that the last read body part is read completely.\"\"\"\n        if self._last_part is not None:\n            if not self._last_part.at_eof():\n                await self._last_part.release()\n            self._unread.extend(self._last_part._unread)\n            self._last_part = None\n\n\n_Part = tuple[Payload, str, str]\n\n\nclass MultipartWriter(Payload):\n    \"\"\"Multipart body writer.\"\"\"\n\n    _value: None\n    # _consumed = False (inherited) - Can be encoded multiple times\n    _autoclose = True  # No file handles, just collects parts in memory\n\n    def __init__(self, subtype: str = \"mixed\", boundary: str | None = None) -> None:\n        boundary = boundary if boundary is not None else uuid.uuid4().hex\n        # The underlying Payload API demands a str (utf-8), not bytes,\n        # so we need to ensure we don't lose anything during conversion.\n        # As a result, require the boundary to be ASCII only.\n        # In both situations.\n\n        try:\n            self._boundary = boundary.encode(\"ascii\")\n        except UnicodeEncodeError:\n            raise ValueError(\"boundary should contain ASCII only chars\") from None\n\n        if len(boundary) > 70:\n            raise ValueError(\"boundary %r is too long (70 chars max)\" % boundary)\n\n        ctype = f\"multipart/{subtype}; boundary={self._boundary_value}\"\n\n        super().__init__(None, content_type=ctype)\n\n        self._parts: list[_Part] = []\n        self._is_form_data = subtype == \"form-data\"\n\n    def __enter__(self) -> \"MultipartWriter\":\n        return self\n\n    def __exit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc_val: BaseException | None,\n        exc_tb: TracebackType | None,\n    ) -> None:\n        pass\n\n    def __iter__(self) -> Iterator[_Part]:\n        return iter(self._parts)\n\n    def __len__(self) -> int:\n        return len(self._parts)\n\n    def __bool__(self) -> bool:\n        return True\n\n    _valid_tchar_regex = re.compile(rb\"\\A[!#$%&'*+\\-.^_`|~\\w]+\\Z\")\n    _invalid_qdtext_char_regex = re.compile(rb\"[\\x00-\\x08\\x0A-\\x1F\\x7F]\")\n\n    @property\n    def _boundary_value(self) -> str:\n        \"\"\"Wrap boundary parameter value in quotes, if necessary.\n\n        Reads self.boundary and returns a unicode string.\n        \"\"\"\n        # Refer to RFCs 7231, 7230, 5234.\n        #\n        # parameter      = token \"=\" ( token / quoted-string )\n        # token          = 1*tchar\n        # quoted-string  = DQUOTE *( qdtext / quoted-pair ) DQUOTE\n        # qdtext         = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text\n        # obs-text       = %x80-FF\n        # quoted-pair    = \"\\\" ( HTAB / SP / VCHAR / obs-text )\n        # tchar          = \"!\" / \"#\" / \"$\" / \"%\" / \"&\" / \"'\" / \"*\"\n        #                  / \"+\" / \"-\" / \".\" / \"^\" / \"_\" / \"`\" / \"|\" / \"~\"\n        #                  / DIGIT / ALPHA\n        #                  ; any VCHAR, except delimiters\n        # VCHAR           = %x21-7E\n        value = self._boundary\n        if re.match(self._valid_tchar_regex, value):\n            return value.decode(\"ascii\")  # cannot fail\n\n        if re.search(self._invalid_qdtext_char_regex, value):\n            raise ValueError(\"boundary value contains invalid characters\")\n\n        # escape %x5C and %x22\n        quoted_value_content = value.replace(b\"\\\\\", b\"\\\\\\\\\")\n        quoted_value_content = quoted_value_content.replace(b'\"', b'\\\\\"')\n\n        return '\"' + quoted_value_content.decode(\"ascii\") + '\"'\n\n    @property\n    def boundary(self) -> str:\n        return self._boundary.decode(\"ascii\")\n\n    def append(self, obj: Any, headers: Mapping[str, str] | None = None) -> Payload:\n        if headers is None:\n            headers = CIMultiDict()\n\n        if isinstance(obj, Payload):\n            obj.headers.update(headers)\n            return self.append_payload(obj)\n        else:\n            try:\n                payload = get_payload(obj, headers=headers)\n            except LookupError:\n                raise TypeError(\"Cannot create payload from %r\" % obj)\n            else:\n                return self.append_payload(payload)\n\n    def append_payload(self, payload: Payload) -> Payload:\n        \"\"\"Adds a new body part to multipart writer.\"\"\"\n        encoding: str | None = None\n        te_encoding: str | None = None\n        if self._is_form_data:\n            # https://datatracker.ietf.org/doc/html/rfc7578#section-4.7\n            # https://datatracker.ietf.org/doc/html/rfc7578#section-4.8\n            assert (\n                not {CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TRANSFER_ENCODING}\n                & payload.headers.keys()\n            )\n            # Set default Content-Disposition in case user doesn't create one\n            if CONTENT_DISPOSITION not in payload.headers:\n                name = f\"section-{len(self._parts)}\"\n                payload.set_content_disposition(\"form-data\", name=name)\n        else:\n            # compression\n            encoding = payload.headers.get(CONTENT_ENCODING, \"\").lower()\n            if encoding and encoding not in (\"deflate\", \"gzip\", \"identity\"):\n                raise RuntimeError(f\"unknown content encoding: {encoding}\")\n            if encoding == \"identity\":\n                encoding = None\n\n            # te encoding\n            te_encoding = payload.headers.get(CONTENT_TRANSFER_ENCODING, \"\").lower()\n            if te_encoding not in (\"\", \"base64\", \"quoted-printable\", \"binary\"):\n                raise RuntimeError(f\"unknown content transfer encoding: {te_encoding}\")\n            if te_encoding == \"binary\":\n                te_encoding = None\n\n            # size\n            size = payload.size\n            if size is not None and not (encoding or te_encoding):\n                payload.headers[CONTENT_LENGTH] = str(size)\n\n        self._parts.append((payload, encoding, te_encoding))  # type: ignore[arg-type]\n        return payload\n\n    def append_json(\n        self, obj: Any, headers: Mapping[str, str] | None = None\n    ) -> Payload:\n        \"\"\"Helper to append JSON part.\"\"\"\n        if headers is None:\n            headers = CIMultiDict()\n\n        return self.append_payload(JsonPayload(obj, headers=headers))\n\n    def append_form(\n        self,\n        obj: Sequence[tuple[str, str]] | Mapping[str, str],\n        headers: Mapping[str, str] | None = None,\n    ) -> Payload:\n        \"\"\"Helper to append form urlencoded part.\"\"\"\n        assert isinstance(obj, (Sequence, Mapping))\n\n        if headers is None:\n            headers = CIMultiDict()\n\n        if isinstance(obj, Mapping):\n            obj = list(obj.items())\n        data = urlencode(obj, doseq=True)\n\n        return self.append_payload(\n            StringPayload(\n                data, headers=headers, content_type=\"application/x-www-form-urlencoded\"\n            )\n        )\n\n    @property\n    def size(self) -> int | None:\n        \"\"\"Size of the payload.\"\"\"\n        total = 0\n        for part, encoding, te_encoding in self._parts:\n            part_size = part.size\n            if encoding or te_encoding or part_size is None:\n                return None\n\n            total += int(\n                2\n                + len(self._boundary)\n                + 2\n                + part_size  # b'--'+self._boundary+b'\\r\\n'\n                + len(part._binary_headers)\n                + 2  # b'\\r\\n'\n            )\n\n        total += 2 + len(self._boundary) + 4  # b'--'+self._boundary+b'--\\r\\n'\n        return total\n\n    def decode(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> str:\n        \"\"\"Return string representation of the multipart data.\n\n        WARNING: This method may do blocking I/O if parts contain file payloads.\n        It should not be called in the event loop. Use as_bytes().decode() instead.\n        \"\"\"\n        return \"\".join(\n            \"--\"\n            + self.boundary\n            + \"\\r\\n\"\n            + part._binary_headers.decode(encoding, errors)\n            + part.decode()\n            for part, _e, _te in self._parts\n        )\n\n    async def as_bytes(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> bytes:\n        \"\"\"Return bytes representation of the multipart data.\n\n        This method is async-safe and calls as_bytes on underlying payloads.\n        \"\"\"\n        parts: list[bytes] = []\n\n        # Process each part\n        for part, _e, _te in self._parts:\n            # Add boundary\n            parts.append(b\"--\" + self._boundary + b\"\\r\\n\")\n\n            # Add headers\n            parts.append(part._binary_headers)\n\n            # Add payload content using as_bytes for async safety\n            part_bytes = await part.as_bytes(encoding, errors)\n            parts.append(part_bytes)\n\n            # Add trailing CRLF\n            parts.append(b\"\\r\\n\")\n\n        # Add closing boundary\n        parts.append(b\"--\" + self._boundary + b\"--\\r\\n\")\n\n        return b\"\".join(parts)\n\n    async def write(\n        self, writer: AbstractStreamWriter, close_boundary: bool = True\n    ) -> None:\n        \"\"\"Write body.\"\"\"\n        for part, encoding, te_encoding in self._parts:\n            if self._is_form_data:\n                # https://datatracker.ietf.org/doc/html/rfc7578#section-4.2\n                assert CONTENT_DISPOSITION in part.headers\n                assert \"name=\" in part.headers[CONTENT_DISPOSITION]\n\n            await writer.write(b\"--\" + self._boundary + b\"\\r\\n\")\n            await writer.write(part._binary_headers)\n\n            if encoding or te_encoding:\n                w = MultipartPayloadWriter(writer)\n                if encoding:\n                    w.enable_compression(encoding)\n                if te_encoding:\n                    w.enable_encoding(te_encoding)\n                await part.write(w)  # type: ignore[arg-type]\n                await w.write_eof()\n            else:\n                await part.write(writer)\n\n            await writer.write(b\"\\r\\n\")\n\n        if close_boundary:\n            await writer.write(b\"--\" + self._boundary + b\"--\\r\\n\")\n\n    async def close(self) -> None:\n        \"\"\"\n        Close all part payloads that need explicit closing.\n\n        IMPORTANT: This method must not await anything that might not finish\n        immediately, as it may be called during cleanup/cancellation. Schedule\n        any long-running operations without awaiting them.\n        \"\"\"\n        if self._consumed:\n            return\n        self._consumed = True\n\n        # Close all parts that need explicit closing\n        # We catch and log exceptions to ensure all parts get a chance to close\n        # we do not use asyncio.gather() here because we are not allowed\n        # to suspend given we may be called during cleanup\n        for idx, (part, _, _) in enumerate(self._parts):\n            if not part.autoclose and not part.consumed:\n                try:\n                    await part.close()\n                except Exception as exc:\n                    internal_logger.error(\n                        \"Failed to close multipart part %d: %s\", idx, exc, exc_info=True\n                    )\n\n\nclass MultipartPayloadWriter:\n    def __init__(self, writer: AbstractStreamWriter) -> None:\n        self._writer = writer\n        self._encoding: str | None = None\n        self._compress: ZLibCompressor | None = None\n        self._encoding_buffer: bytearray | None = None\n\n    def enable_encoding(self, encoding: str) -> None:\n        if encoding == \"base64\":\n            self._encoding = encoding\n            self._encoding_buffer = bytearray()\n        elif encoding == \"quoted-printable\":\n            self._encoding = \"quoted-printable\"\n\n    def enable_compression(\n        self, encoding: str = \"deflate\", strategy: int | None = None\n    ) -> None:\n        self._compress = ZLibCompressor(\n            encoding=encoding,\n            suppress_deflate_header=True,\n            strategy=strategy,\n        )\n\n    async def write_eof(self) -> None:\n        if self._compress is not None:\n            chunk = self._compress.flush()\n            if chunk:\n                self._compress = None\n                await self.write(chunk)\n\n        if self._encoding == \"base64\":\n            if self._encoding_buffer:\n                await self._writer.write(base64.b64encode(self._encoding_buffer))\n\n    async def write(self, chunk: bytes) -> None:\n        if self._compress is not None:\n            if chunk:\n                chunk = await self._compress.compress(chunk)\n                if not chunk:\n                    return\n\n        if self._encoding == \"base64\":\n            buf = self._encoding_buffer\n            assert buf is not None\n            buf.extend(chunk)\n\n            if buf:\n                div, mod = divmod(len(buf), 3)\n                enc_chunk, self._encoding_buffer = (buf[: div * 3], buf[div * 3 :])\n                if enc_chunk:\n                    b64chunk = base64.b64encode(enc_chunk)\n                    await self._writer.write(b64chunk)\n        elif self._encoding == \"quoted-printable\":\n            await self._writer.write(binascii.b2a_qp(chunk))\n        else:\n            await self._writer.write(chunk)\n"
  },
  {
    "path": "aiohttp/payload.py",
    "content": "import asyncio\nimport enum\nimport io\nimport json\nimport mimetypes\nimport os\nimport sys\nimport warnings\nfrom abc import ABC, abstractmethod\nfrom collections.abc import AsyncIterable, AsyncIterator, Iterable\nfrom itertools import chain\nfrom typing import IO, Any, Final, TextIO\n\nfrom multidict import CIMultiDict\n\nfrom . import hdrs\nfrom .abc import AbstractStreamWriter\nfrom .helpers import (\n    _SENTINEL,\n    content_disposition_header,\n    guess_filename,\n    parse_mimetype,\n    sentinel,\n)\nfrom .streams import StreamReader\nfrom .typedefs import JSONBytesEncoder, JSONEncoder\n\n__all__ = (\n    \"PAYLOAD_REGISTRY\",\n    \"get_payload\",\n    \"payload_type\",\n    \"Payload\",\n    \"BytesPayload\",\n    \"StringPayload\",\n    \"IOBasePayload\",\n    \"BytesIOPayload\",\n    \"BufferedReaderPayload\",\n    \"TextIOPayload\",\n    \"StringIOPayload\",\n    \"JsonPayload\",\n    \"JsonBytesPayload\",\n    \"AsyncIterablePayload\",\n)\n\nTOO_LARGE_BYTES_BODY: Final[int] = 2**20  # 1 MB\nREAD_SIZE: Final[int] = 2**16  # 64 KB\n_CLOSE_FUTURES: set[asyncio.Future[None]] = set()\n\n\nclass LookupError(Exception):\n    \"\"\"Raised when no payload factory is found for the given data type.\"\"\"\n\n\nclass Order(str, enum.Enum):\n    normal = \"normal\"\n    try_first = \"try_first\"\n    try_last = \"try_last\"\n\n\ndef get_payload(data: Any, *args: Any, **kwargs: Any) -> \"Payload\":\n    return PAYLOAD_REGISTRY.get(data, *args, **kwargs)\n\n\ndef register_payload(\n    factory: type[\"Payload\"], type: Any, *, order: Order = Order.normal\n) -> None:\n    PAYLOAD_REGISTRY.register(factory, type, order=order)\n\n\nclass payload_type:\n    def __init__(self, type: Any, *, order: Order = Order.normal) -> None:\n        self.type = type\n        self.order = order\n\n    def __call__(self, factory: type[\"Payload\"]) -> type[\"Payload\"]:\n        register_payload(factory, self.type, order=self.order)\n        return factory\n\n\nPayloadType = type[\"Payload\"]\n_PayloadRegistryItem = tuple[PayloadType, Any]\n\n\nclass PayloadRegistry:\n    \"\"\"Payload registry.\n\n    note: we need zope.interface for more efficient adapter search\n    \"\"\"\n\n    __slots__ = (\"_first\", \"_normal\", \"_last\", \"_normal_lookup\")\n\n    def __init__(self) -> None:\n        self._first: list[_PayloadRegistryItem] = []\n        self._normal: list[_PayloadRegistryItem] = []\n        self._last: list[_PayloadRegistryItem] = []\n        self._normal_lookup: dict[Any, PayloadType] = {}\n\n    def get(\n        self,\n        data: Any,\n        *args: Any,\n        _CHAIN: \"type[chain[_PayloadRegistryItem]]\" = chain,\n        **kwargs: Any,\n    ) -> \"Payload\":\n        if self._first:\n            for factory, type_ in self._first:\n                if isinstance(data, type_):\n                    return factory(data, *args, **kwargs)\n        # Try the fast lookup first\n        if lookup_factory := self._normal_lookup.get(type(data)):\n            return lookup_factory(data, *args, **kwargs)\n        # Bail early if its already a Payload\n        if isinstance(data, Payload):\n            return data\n        # Fallback to the slower linear search\n        for factory, type_ in _CHAIN(self._normal, self._last):\n            if isinstance(data, type_):\n                return factory(data, *args, **kwargs)\n        raise LookupError()\n\n    def register(\n        self, factory: PayloadType, type: Any, *, order: Order = Order.normal\n    ) -> None:\n        if order is Order.try_first:\n            self._first.append((factory, type))\n        elif order is Order.normal:\n            self._normal.append((factory, type))\n            if isinstance(type, Iterable):\n                for t in type:\n                    self._normal_lookup[t] = factory\n            else:\n                self._normal_lookup[type] = factory\n        elif order is Order.try_last:\n            self._last.append((factory, type))\n        else:\n            raise ValueError(f\"Unsupported order {order!r}\")\n\n\nclass Payload(ABC):\n    _default_content_type: str = \"application/octet-stream\"\n    _size: int | None = None\n    _consumed: bool = False  # Default: payload has not been consumed yet\n    _autoclose: bool = False  # Default: assume resource needs explicit closing\n\n    def __init__(\n        self,\n        value: Any,\n        headers: (\n            CIMultiDict[str] | dict[str, str] | Iterable[tuple[str, str]] | None\n        ) = None,\n        content_type: None | str | _SENTINEL = sentinel,\n        filename: str | None = None,\n        encoding: str | None = None,\n        **kwargs: Any,\n    ) -> None:\n        self._encoding = encoding\n        self._filename = filename\n        self._headers = CIMultiDict[str]()\n        self._value = value\n        if content_type is not sentinel and content_type is not None:\n            assert isinstance(content_type, str)\n            self._headers[hdrs.CONTENT_TYPE] = content_type\n        elif self._filename is not None:\n            if sys.version_info >= (3, 13):\n                guesser = mimetypes.guess_file_type\n            else:\n                guesser = mimetypes.guess_type\n            content_type = guesser(self._filename)[0]\n            if content_type is None:\n                content_type = self._default_content_type\n            self._headers[hdrs.CONTENT_TYPE] = content_type\n        else:\n            self._headers[hdrs.CONTENT_TYPE] = self._default_content_type\n        if headers:\n            self._headers.update(headers)\n\n    @property\n    def size(self) -> int | None:\n        \"\"\"Size of the payload in bytes.\n\n        Returns the number of bytes that will be transmitted when the payload\n        is written. For string payloads, this is the size after encoding to bytes,\n        not the length of the string.\n        \"\"\"\n        return self._size\n\n    @property\n    def filename(self) -> str | None:\n        \"\"\"Filename of the payload.\"\"\"\n        return self._filename\n\n    @property\n    def headers(self) -> CIMultiDict[str]:\n        \"\"\"Custom item headers\"\"\"\n        return self._headers\n\n    @property\n    def _binary_headers(self) -> bytes:\n        return (\n            \"\".join([k + \": \" + v + \"\\r\\n\" for k, v in self.headers.items()]).encode(\n                \"utf-8\"\n            )\n            + b\"\\r\\n\"\n        )\n\n    @property\n    def encoding(self) -> str | None:\n        \"\"\"Payload encoding\"\"\"\n        return self._encoding\n\n    @property\n    def content_type(self) -> str:\n        \"\"\"Content type\"\"\"\n        return self._headers[hdrs.CONTENT_TYPE]\n\n    @property\n    def consumed(self) -> bool:\n        \"\"\"Whether the payload has been consumed and cannot be reused.\"\"\"\n        return self._consumed\n\n    @property\n    def autoclose(self) -> bool:\n        \"\"\"\n        Whether the payload can close itself automatically.\n\n        Returns True if the payload has no file handles or resources that need\n        explicit closing. If False, callers must await close() to release resources.\n        \"\"\"\n        return self._autoclose\n\n    def set_content_disposition(\n        self,\n        disptype: str,\n        quote_fields: bool = True,\n        _charset: str = \"utf-8\",\n        **params: str,\n    ) -> None:\n        \"\"\"Sets ``Content-Disposition`` header.\"\"\"\n        self._headers[hdrs.CONTENT_DISPOSITION] = content_disposition_header(\n            disptype, quote_fields=quote_fields, _charset=_charset, params=params\n        )\n\n    @abstractmethod\n    def decode(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> str:\n        \"\"\"\n        Return string representation of the value.\n\n        This is named decode() to allow compatibility with bytes objects.\n        \"\"\"\n\n    @abstractmethod\n    async def write(self, writer: AbstractStreamWriter) -> None:\n        \"\"\"\n        Write payload to the writer stream.\n\n        Args:\n            writer: An AbstractStreamWriter instance that handles the actual writing\n\n        This is a legacy method that writes the entire payload without length constraints.\n\n        Important:\n            For new implementations, use write_with_length() instead of this method.\n            This method is maintained for backwards compatibility and will eventually\n            delegate to write_with_length(writer, None) in all implementations.\n\n        All payload subclasses must override this method for backwards compatibility,\n        but new code should use write_with_length for more flexibility and control.\n\n        \"\"\"\n\n    # write_with_length is new in aiohttp 3.12\n    # it should be overridden by subclasses\n    async def write_with_length(\n        self, writer: AbstractStreamWriter, content_length: int | None\n    ) -> None:\n        \"\"\"\n        Write payload with a specific content length constraint.\n\n        Args:\n            writer: An AbstractStreamWriter instance that handles the actual writing\n            content_length: Maximum number of bytes to write (None for unlimited)\n\n        This method allows writing payload content with a specific length constraint,\n        which is particularly useful for HTTP responses with Content-Length header.\n\n        Note:\n            This is the base implementation that provides backwards compatibility\n            for subclasses that don't override this method. Specific payload types\n            should override this method to implement proper length-constrained writing.\n\n        \"\"\"\n        # Backwards compatibility for subclasses that don't override this method\n        # and for the default implementation\n        await self.write(writer)\n\n    async def as_bytes(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> bytes:\n        \"\"\"\n        Return bytes representation of the value.\n\n        This is a convenience method that calls decode() and encodes the result\n        to bytes using the specified encoding.\n        \"\"\"\n        # Use instance encoding if available, otherwise use parameter\n        actual_encoding = self._encoding or encoding\n        return self.decode(actual_encoding, errors).encode(actual_encoding)\n\n    def _close(self) -> None:\n        \"\"\"\n        Async safe synchronous close operations for backwards compatibility.\n\n        This method exists only for backwards compatibility with code that\n        needs to clean up payloads synchronously. In the future, we will\n        drop this method and only support the async close() method.\n\n        WARNING: This method must be safe to call from within the event loop\n        without blocking. Subclasses should not perform any blocking I/O here.\n\n        WARNING: This method must be called from within an event loop for\n        certain payload types (e.g., IOBasePayload). Calling it outside an\n        event loop may raise RuntimeError.\n        \"\"\"\n        # This is a no-op by default, but subclasses can override it\n        # for non-blocking cleanup operations.\n\n    async def close(self) -> None:\n        \"\"\"\n        Close the payload if it holds any resources.\n\n        IMPORTANT: This method must not await anything that might not finish\n        immediately, as it may be called during cleanup/cancellation. Schedule\n        any long-running operations without awaiting them.\n\n        In the future, this will be the only close method supported.\n        \"\"\"\n        self._close()\n\n\nclass BytesPayload(Payload):\n    _value: bytes\n    # _consumed = False (inherited) - Bytes are immutable and can be reused\n    _autoclose = True  # No file handle, just bytes in memory\n\n    def __init__(\n        self, value: bytes | bytearray | memoryview, *args: Any, **kwargs: Any\n    ) -> None:\n        if \"content_type\" not in kwargs:\n            kwargs[\"content_type\"] = \"application/octet-stream\"\n\n        super().__init__(value, *args, **kwargs)\n\n        if isinstance(value, memoryview):\n            self._size = value.nbytes\n        elif isinstance(value, (bytes, bytearray)):\n            self._size = len(value)\n        else:\n            raise TypeError(f\"value argument must be byte-ish, not {type(value)!r}\")\n\n        if self._size > TOO_LARGE_BYTES_BODY:\n            warnings.warn(\n                \"Sending a large body directly with raw bytes might\"\n                \" lock the event loop. You should probably pass an \"\n                \"io.BytesIO object instead\",\n                ResourceWarning,\n                source=self,\n            )\n\n    def decode(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> str:\n        return self._value.decode(encoding, errors)\n\n    async def as_bytes(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> bytes:\n        \"\"\"\n        Return bytes representation of the value.\n\n        This method returns the raw bytes content of the payload.\n        It is equivalent to accessing the _value attribute directly.\n        \"\"\"\n        return self._value\n\n    async def write(self, writer: AbstractStreamWriter) -> None:\n        \"\"\"\n        Write the entire bytes payload to the writer stream.\n\n        Args:\n            writer: An AbstractStreamWriter instance that handles the actual writing\n\n        This method writes the entire bytes content without any length constraint.\n\n        Note:\n            For new implementations that need length control, use write_with_length().\n            This method is maintained for backwards compatibility and is equivalent\n            to write_with_length(writer, None).\n\n        \"\"\"\n        await writer.write(self._value)\n\n    async def write_with_length(\n        self, writer: AbstractStreamWriter, content_length: int | None\n    ) -> None:\n        \"\"\"\n        Write bytes payload with a specific content length constraint.\n\n        Args:\n            writer: An AbstractStreamWriter instance that handles the actual writing\n            content_length: Maximum number of bytes to write (None for unlimited)\n\n        This method writes either the entire byte sequence or a slice of it\n        up to the specified content_length. For BytesPayload, this operation\n        is performed efficiently using array slicing.\n\n        \"\"\"\n        if content_length is not None:\n            await writer.write(self._value[:content_length])\n        else:\n            await writer.write(self._value)\n\n\nclass StringPayload(BytesPayload):\n    def __init__(\n        self,\n        value: str,\n        *args: Any,\n        encoding: str | None = None,\n        content_type: str | None = None,\n        **kwargs: Any,\n    ) -> None:\n        if encoding is None:\n            if content_type is None:\n                real_encoding = \"utf-8\"\n                content_type = \"text/plain; charset=utf-8\"\n            else:\n                mimetype = parse_mimetype(content_type)\n                real_encoding = mimetype.parameters.get(\"charset\", \"utf-8\")\n        else:\n            if content_type is None:\n                content_type = \"text/plain; charset=%s\" % encoding\n            real_encoding = encoding\n\n        super().__init__(\n            value.encode(real_encoding),\n            encoding=real_encoding,\n            content_type=content_type,\n            *args,\n            **kwargs,\n        )\n\n\nclass StringIOPayload(StringPayload):\n    def __init__(self, value: IO[str], *args: Any, **kwargs: Any) -> None:\n        super().__init__(value.read(), *args, **kwargs)\n\n\nclass IOBasePayload(Payload):\n    _value: io.IOBase\n    # _consumed = False (inherited) - File can be re-read from the same position\n    _start_position: int | None = None\n    # _autoclose = False (inherited) - Has file handle that needs explicit closing\n\n    def __init__(\n        self, value: IO[Any], disposition: str = \"attachment\", *args: Any, **kwargs: Any\n    ) -> None:\n        if \"filename\" not in kwargs:\n            kwargs[\"filename\"] = guess_filename(value)\n\n        super().__init__(value, *args, **kwargs)\n\n        if self._filename is not None and disposition is not None:\n            if hdrs.CONTENT_DISPOSITION not in self.headers:\n                self.set_content_disposition(disposition, filename=self._filename)\n\n    def _set_or_restore_start_position(self) -> None:\n        \"\"\"Set or restore the start position of the file-like object.\"\"\"\n        if self._start_position is None:\n            try:\n                self._start_position = self._value.tell()\n            except (OSError, AttributeError):\n                self._consumed = True  # Cannot seek, mark as consumed\n            return\n        try:\n            self._value.seek(self._start_position)\n        except (OSError, AttributeError):\n            # Failed to seek back - mark as consumed since we've already read\n            self._consumed = True\n\n    def _read_and_available_len(\n        self, remaining_content_len: int | None\n    ) -> tuple[int | None, bytes]:\n        \"\"\"\n        Read the file-like object and return both its total size and the first chunk.\n\n        Args:\n            remaining_content_len: Optional limit on how many bytes to read in this operation.\n                If None, READ_SIZE will be used as the default chunk size.\n\n        Returns:\n            A tuple containing:\n            - The total size of the remaining unread content (None if size cannot be determined)\n            - The first chunk of bytes read from the file object\n\n        This method is optimized to perform both size calculation and initial read\n        in a single operation, which is executed in a single executor job to minimize\n        context switches and file operations when streaming content.\n\n        \"\"\"\n        self._set_or_restore_start_position()\n        size = self.size  # Call size only once since it does I/O\n        return size, self._value.read(\n            min(READ_SIZE, size or READ_SIZE, remaining_content_len or READ_SIZE)\n        )\n\n    def _read(self, remaining_content_len: int | None) -> bytes:\n        \"\"\"\n        Read a chunk of data from the file-like object.\n\n        Args:\n            remaining_content_len: Optional maximum number of bytes to read.\n                If None, READ_SIZE will be used as the default chunk size.\n\n        Returns:\n            A chunk of bytes read from the file object, respecting the\n            remaining_content_len limit if specified.\n\n        This method is used for subsequent reads during streaming after\n        the initial _read_and_available_len call has been made.\n\n        \"\"\"\n        return self._value.read(remaining_content_len or READ_SIZE)  # type: ignore[no-any-return]\n\n    @property\n    def size(self) -> int | None:\n        \"\"\"\n        Size of the payload in bytes.\n\n        Returns the total size of the payload content from the initial position.\n        This ensures consistent Content-Length for requests, including 307/308 redirects\n        where the same payload instance is reused.\n\n        Returns None if the size cannot be determined (e.g., for unseekable streams).\n        \"\"\"\n        try:\n            # Store the start position on first access.\n            # This is critical when the same payload instance is reused (e.g., 307/308\n            # redirects). Without storing the initial position, after the payload is\n            # read once, the file position would be at EOF, which would cause the\n            # size calculation to return 0 (file_size - EOF position).\n            # By storing the start position, we ensure the size calculation always\n            # returns the correct total size for any subsequent use.\n            if self._start_position is None:\n                self._start_position = self._value.tell()\n\n            # Return the total size from the start position\n            # This ensures Content-Length is correct even after reading\n            return os.fstat(self._value.fileno()).st_size - self._start_position\n        except (AttributeError, OSError):\n            return None\n\n    async def write(self, writer: AbstractStreamWriter) -> None:\n        \"\"\"\n        Write the entire file-like payload to the writer stream.\n\n        Args:\n            writer: An AbstractStreamWriter instance that handles the actual writing\n\n        This method writes the entire file content without any length constraint.\n        It delegates to write_with_length() with no length limit for implementation\n        consistency.\n\n        Note:\n            For new implementations that need length control, use write_with_length() directly.\n            This method is maintained for backwards compatibility with existing code.\n\n        \"\"\"\n        await self.write_with_length(writer, None)\n\n    async def write_with_length(\n        self, writer: AbstractStreamWriter, content_length: int | None\n    ) -> None:\n        \"\"\"\n        Write file-like payload with a specific content length constraint.\n\n        Args:\n            writer: An AbstractStreamWriter instance that handles the actual writing\n            content_length: Maximum number of bytes to write (None for unlimited)\n\n        This method implements optimized streaming of file content with length constraints:\n\n        1. File reading is performed in a thread pool to avoid blocking the event loop\n        2. Content is read and written in chunks to maintain memory efficiency\n        3. Writing stops when either:\n           - All available file content has been written (when size is known)\n           - The specified content_length has been reached\n        4. File resources are properly closed even if the operation is cancelled\n\n        The implementation carefully handles both known-size and unknown-size payloads,\n        as well as constrained and unconstrained content lengths.\n\n        \"\"\"\n        loop = asyncio.get_running_loop()\n        total_written_len = 0\n        remaining_content_len = content_length\n\n        # Get initial data and available length\n        available_len, chunk = await loop.run_in_executor(\n            None, self._read_and_available_len, remaining_content_len\n        )\n        # Process data chunks until done\n        while chunk:\n            chunk_len = len(chunk)\n\n            # Write data with or without length constraint\n            if remaining_content_len is None:\n                await writer.write(chunk)\n            else:\n                await writer.write(chunk[:remaining_content_len])\n                remaining_content_len -= chunk_len\n\n            total_written_len += chunk_len\n\n            # Check if we're done writing\n            if self._should_stop_writing(\n                available_len, total_written_len, remaining_content_len\n            ):\n                return\n\n            # Read next chunk\n            chunk = await loop.run_in_executor(\n                None,\n                self._read,\n                (\n                    min(READ_SIZE, remaining_content_len)\n                    if remaining_content_len is not None\n                    else READ_SIZE\n                ),\n            )\n\n    def _should_stop_writing(\n        self,\n        available_len: int | None,\n        total_written_len: int,\n        remaining_content_len: int | None,\n    ) -> bool:\n        \"\"\"\n        Determine if we should stop writing data.\n\n        Args:\n            available_len: Known size of the payload if available (None if unknown)\n            total_written_len: Number of bytes already written\n            remaining_content_len: Remaining bytes to be written for content-length limited responses\n\n        Returns:\n            True if we should stop writing data, based on either:\n            - Having written all available data (when size is known)\n            - Having written all requested content (when content-length is specified)\n\n        \"\"\"\n        return (available_len is not None and total_written_len >= available_len) or (\n            remaining_content_len is not None and remaining_content_len <= 0\n        )\n\n    def _close(self) -> None:\n        \"\"\"\n        Async safe synchronous close operations for backwards compatibility.\n\n        This method exists only for backwards\n        compatibility. Use the async close() method instead.\n\n        WARNING: This method MUST be called from within an event loop.\n        Calling it outside an event loop will raise RuntimeError.\n        \"\"\"\n        # Skip if already consumed\n        if self._consumed:\n            return\n        self._consumed = True  # Mark as consumed to prevent further writes\n        # Schedule file closing without awaiting to prevent cancellation issues\n        loop = asyncio.get_running_loop()\n        close_future = loop.run_in_executor(None, self._value.close)\n        # Hold a strong reference to the future to prevent it from being\n        # garbage collected before it completes.\n        _CLOSE_FUTURES.add(close_future)\n        close_future.add_done_callback(_CLOSE_FUTURES.remove)\n\n    async def close(self) -> None:\n        \"\"\"\n        Close the payload if it holds any resources.\n\n        IMPORTANT: This method must not await anything that might not finish\n        immediately, as it may be called during cleanup/cancellation. Schedule\n        any long-running operations without awaiting them.\n        \"\"\"\n        self._close()\n\n    def decode(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> str:\n        \"\"\"\n        Return string representation of the value.\n\n        WARNING: This method does blocking I/O and should not be called in the event loop.\n        \"\"\"\n        return self._read_all().decode(encoding, errors)\n\n    def _read_all(self) -> bytes:\n        \"\"\"Read the entire file-like object and return its content as bytes.\"\"\"\n        self._set_or_restore_start_position()\n        # Use readlines() to ensure we get all content\n        return b\"\".join(self._value.readlines())\n\n    async def as_bytes(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> bytes:\n        \"\"\"\n        Return bytes representation of the value.\n\n        This method reads the entire file content and returns it as bytes.\n        It is equivalent to reading the file-like object directly.\n        The file reading is performed in an executor to avoid blocking the event loop.\n        \"\"\"\n        loop = asyncio.get_running_loop()\n        return await loop.run_in_executor(None, self._read_all)\n\n\nclass TextIOPayload(IOBasePayload):\n    _value: io.TextIOBase\n    # _autoclose = False (inherited) - Has text file handle that needs explicit closing\n\n    def __init__(\n        self,\n        value: TextIO,\n        *args: Any,\n        encoding: str | None = None,\n        content_type: str | None = None,\n        **kwargs: Any,\n    ) -> None:\n        if encoding is None:\n            if content_type is None:\n                encoding = \"utf-8\"\n                content_type = \"text/plain; charset=utf-8\"\n            else:\n                mimetype = parse_mimetype(content_type)\n                encoding = mimetype.parameters.get(\"charset\", \"utf-8\")\n        else:\n            if content_type is None:\n                content_type = \"text/plain; charset=%s\" % encoding\n\n        super().__init__(\n            value,\n            content_type=content_type,\n            encoding=encoding,\n            *args,\n            **kwargs,\n        )\n\n    def _read_and_available_len(\n        self, remaining_content_len: int | None\n    ) -> tuple[int | None, bytes]:\n        \"\"\"\n        Read the text file-like object and return both its total size and the first chunk.\n\n        Args:\n            remaining_content_len: Optional limit on how many bytes to read in this operation.\n                If None, READ_SIZE will be used as the default chunk size.\n\n        Returns:\n            A tuple containing:\n            - The total size of the remaining unread content (None if size cannot be determined)\n            - The first chunk of bytes read from the file object, encoded using the payload's encoding\n\n        This method is optimized to perform both size calculation and initial read\n        in a single operation, which is executed in a single executor job to minimize\n        context switches and file operations when streaming content.\n\n        Note:\n            TextIOPayload handles encoding of the text content before writing it\n            to the stream. If no encoding is specified, UTF-8 is used as the default.\n\n        \"\"\"\n        self._set_or_restore_start_position()\n        size = self.size\n        chunk = self._value.read(\n            min(READ_SIZE, size or READ_SIZE, remaining_content_len or READ_SIZE)\n        )\n        return size, chunk.encode(self._encoding) if self._encoding else chunk.encode()\n\n    def _read(self, remaining_content_len: int | None) -> bytes:\n        \"\"\"\n        Read a chunk of data from the text file-like object.\n\n        Args:\n            remaining_content_len: Optional maximum number of bytes to read.\n                If None, READ_SIZE will be used as the default chunk size.\n\n        Returns:\n            A chunk of bytes read from the file object and encoded using the payload's\n            encoding. The data is automatically converted from text to bytes.\n\n        This method is used for subsequent reads during streaming after\n        the initial _read_and_available_len call has been made. It properly\n        handles text encoding, converting the text content to bytes using\n        the specified encoding (or UTF-8 if none was provided).\n\n        \"\"\"\n        chunk = self._value.read(remaining_content_len or READ_SIZE)\n        return chunk.encode(self._encoding) if self._encoding else chunk.encode()\n\n    def decode(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> str:\n        \"\"\"\n        Return string representation of the value.\n\n        WARNING: This method does blocking I/O and should not be called in the event loop.\n        \"\"\"\n        self._set_or_restore_start_position()\n        return self._value.read()\n\n    async def as_bytes(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> bytes:\n        \"\"\"\n        Return bytes representation of the value.\n\n        This method reads the entire text file content and returns it as bytes.\n        It encodes the text content using the specified encoding.\n        The file reading is performed in an executor to avoid blocking the event loop.\n        \"\"\"\n        loop = asyncio.get_running_loop()\n\n        # Use instance encoding if available, otherwise use parameter\n        actual_encoding = self._encoding or encoding\n\n        def _read_and_encode() -> bytes:\n            self._set_or_restore_start_position()\n            # TextIO read() always returns the full content\n            return self._value.read().encode(actual_encoding, errors)\n\n        return await loop.run_in_executor(None, _read_and_encode)\n\n\nclass BytesIOPayload(IOBasePayload):\n    _value: io.BytesIO\n    _size: int  # Always initialized in __init__\n    _autoclose = True  # BytesIO is in-memory, safe to auto-close\n\n    def __init__(self, value: io.BytesIO, *args: Any, **kwargs: Any) -> None:\n        super().__init__(value, *args, **kwargs)\n        # Calculate size once during initialization\n        self._size = len(self._value.getbuffer()) - self._value.tell()\n\n    @property\n    def size(self) -> int:\n        \"\"\"Size of the payload in bytes.\n\n        Returns the number of bytes in the BytesIO buffer that will be transmitted.\n        This is calculated once during initialization for efficiency.\n        \"\"\"\n        return self._size\n\n    def decode(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> str:\n        self._set_or_restore_start_position()\n        return self._value.read().decode(encoding, errors)\n\n    async def write(self, writer: AbstractStreamWriter) -> None:\n        return await self.write_with_length(writer, None)\n\n    async def write_with_length(\n        self, writer: AbstractStreamWriter, content_length: int | None\n    ) -> None:\n        \"\"\"\n        Write BytesIO payload with a specific content length constraint.\n\n        Args:\n            writer: An AbstractStreamWriter instance that handles the actual writing\n            content_length: Maximum number of bytes to write (None for unlimited)\n\n        This implementation is specifically optimized for BytesIO objects:\n\n        1. Reads content in chunks to maintain memory efficiency\n        2. Yields control back to the event loop periodically to prevent blocking\n           when dealing with large BytesIO objects\n        3. Respects content_length constraints when specified\n        4. Properly cleans up by closing the BytesIO object when done or on error\n\n        The periodic yielding to the event loop is important for maintaining\n        responsiveness when processing large in-memory buffers.\n\n        \"\"\"\n        self._set_or_restore_start_position()\n        loop_count = 0\n        remaining_bytes = content_length\n        while chunk := self._value.read(READ_SIZE):\n            if loop_count > 0:\n                # Avoid blocking the event loop\n                # if they pass a large BytesIO object\n                # and we are not in the first iteration\n                # of the loop\n                await asyncio.sleep(0)\n            if remaining_bytes is None:\n                await writer.write(chunk)\n            else:\n                await writer.write(chunk[:remaining_bytes])\n                remaining_bytes -= len(chunk)\n                if remaining_bytes <= 0:\n                    return\n            loop_count += 1\n\n    async def as_bytes(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> bytes:\n        \"\"\"\n        Return bytes representation of the value.\n\n        This method reads the entire BytesIO content and returns it as bytes.\n        It is equivalent to accessing the _value attribute directly.\n        \"\"\"\n        self._set_or_restore_start_position()\n        return self._value.read()\n\n    async def close(self) -> None:\n        \"\"\"\n        Close the BytesIO payload.\n\n        This does nothing since BytesIO is in-memory and does not require explicit closing.\n        \"\"\"\n\n\nclass BufferedReaderPayload(IOBasePayload):\n    _value: io.BufferedIOBase\n    # _autoclose = False (inherited) - Has buffered file handle that needs explicit closing\n\n    def decode(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> str:\n        self._set_or_restore_start_position()\n        return self._value.read().decode(encoding, errors)\n\n\nclass JsonPayload(BytesPayload):\n    def __init__(\n        self,\n        value: Any,\n        encoding: str = \"utf-8\",\n        content_type: str = \"application/json\",\n        dumps: JSONEncoder = json.dumps,\n        *args: Any,\n        **kwargs: Any,\n    ) -> None:\n        super().__init__(\n            dumps(value).encode(encoding),\n            content_type=content_type,\n            encoding=encoding,\n            *args,\n            **kwargs,\n        )\n\n\nclass JsonBytesPayload(BytesPayload):\n    \"\"\"JSON payload for encoders that return bytes directly.\n\n    Use this when your JSON encoder (like orjson) returns bytes\n    instead of str, avoiding the encode/decode overhead.\n    \"\"\"\n\n    def __init__(\n        self,\n        value: Any,\n        dumps: JSONBytesEncoder,\n        content_type: str = \"application/json\",\n        *args: Any,\n        **kwargs: Any,\n    ) -> None:\n        super().__init__(\n            dumps(value),\n            content_type=content_type,\n            *args,\n            **kwargs,\n        )\n\n\nclass AsyncIterablePayload(Payload):\n    _iter: AsyncIterator[bytes] | None = None\n    _value: AsyncIterable[bytes]\n    _cached_chunks: list[bytes] | None = None\n    # _consumed stays False to allow reuse with cached content\n    _autoclose = True  # Iterator doesn't need explicit closing\n\n    def __init__(self, value: AsyncIterable[bytes], *args: Any, **kwargs: Any) -> None:\n        if not isinstance(value, AsyncIterable):\n            raise TypeError(\n                \"value argument must support \"\n                \"collections.abc.AsyncIterable interface, \"\n                f\"got {type(value)!r}\"\n            )\n\n        if \"content_type\" not in kwargs:\n            kwargs[\"content_type\"] = \"application/octet-stream\"\n\n        super().__init__(value, *args, **kwargs)\n\n        self._iter = value.__aiter__()\n\n    async def write(self, writer: AbstractStreamWriter) -> None:\n        \"\"\"\n        Write the entire async iterable payload to the writer stream.\n\n        Args:\n            writer: An AbstractStreamWriter instance that handles the actual writing\n\n        This method iterates through the async iterable and writes each chunk\n        to the writer without any length constraint.\n\n        Note:\n            For new implementations that need length control, use write_with_length() directly.\n            This method is maintained for backwards compatibility with existing code.\n\n        \"\"\"\n        await self.write_with_length(writer, None)\n\n    async def write_with_length(\n        self, writer: AbstractStreamWriter, content_length: int | None\n    ) -> None:\n        \"\"\"\n        Write async iterable payload with a specific content length constraint.\n\n        Args:\n            writer: An AbstractStreamWriter instance that handles the actual writing\n            content_length: Maximum number of bytes to write (None for unlimited)\n\n        This implementation handles streaming of async iterable content with length constraints:\n\n        1. If cached chunks are available, writes from them\n        2. Otherwise iterates through the async iterable one chunk at a time\n        3. Respects content_length constraints when specified\n        4. Does NOT generate cache - that's done by as_bytes()\n\n        \"\"\"\n        # If we have cached chunks, use them\n        if self._cached_chunks is not None:\n            remaining_bytes = content_length\n            for chunk in self._cached_chunks:\n                if remaining_bytes is None:\n                    await writer.write(chunk)\n                elif remaining_bytes > 0:\n                    await writer.write(chunk[:remaining_bytes])\n                    remaining_bytes -= len(chunk)\n                else:\n                    break\n            return\n\n        # If iterator is exhausted and we don't have cached chunks, nothing to write\n        if self._iter is None:\n            return\n\n        # Stream from the iterator\n        remaining_bytes = content_length\n\n        try:\n            while True:\n                chunk = await anext(self._iter)\n                if remaining_bytes is None:\n                    await writer.write(chunk)\n                # If we have a content length limit\n                elif remaining_bytes > 0:\n                    await writer.write(chunk[:remaining_bytes])\n                    remaining_bytes -= len(chunk)\n                # We still want to exhaust the iterator even\n                # if we have reached the content length limit\n                # since the file handle may not get closed by\n                # the iterator if we don't do this\n        except StopAsyncIteration:\n            # Iterator is exhausted\n            self._iter = None\n            self._consumed = True  # Mark as consumed when streamed without caching\n\n    def decode(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> str:\n        \"\"\"Decode the payload content as a string if cached chunks are available.\"\"\"\n        if self._cached_chunks is not None:\n            return b\"\".join(self._cached_chunks).decode(encoding, errors)\n        raise TypeError(\"Unable to decode - content not cached. Call as_bytes() first.\")\n\n    async def as_bytes(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> bytes:\n        \"\"\"\n        Return bytes representation of the value.\n\n        This method reads the entire async iterable content and returns it as bytes.\n        It generates and caches the chunks for future reuse.\n        \"\"\"\n        # If we have cached chunks, return them joined\n        if self._cached_chunks is not None:\n            return b\"\".join(self._cached_chunks)\n\n        # If iterator is exhausted and no cache, return empty\n        if self._iter is None:\n            return b\"\"\n\n        # Read all chunks and cache them\n        chunks: list[bytes] = []\n        async for chunk in self._iter:\n            chunks.append(chunk)\n\n        # Iterator is exhausted, cache the chunks\n        self._iter = None\n        self._cached_chunks = chunks\n        # Keep _consumed as False to allow reuse with cached chunks\n\n        return b\"\".join(chunks)\n\n\nclass StreamReaderPayload(AsyncIterablePayload):\n    def __init__(self, value: StreamReader, *args: Any, **kwargs: Any) -> None:\n        super().__init__(value.iter_any(), *args, **kwargs)\n\n\nPAYLOAD_REGISTRY = PayloadRegistry()\nPAYLOAD_REGISTRY.register(BytesPayload, (bytes, bytearray, memoryview))\nPAYLOAD_REGISTRY.register(StringPayload, str)\nPAYLOAD_REGISTRY.register(StringIOPayload, io.StringIO)\nPAYLOAD_REGISTRY.register(TextIOPayload, io.TextIOBase)\nPAYLOAD_REGISTRY.register(BytesIOPayload, io.BytesIO)\nPAYLOAD_REGISTRY.register(BufferedReaderPayload, (io.BufferedReader, io.BufferedRandom))\nPAYLOAD_REGISTRY.register(IOBasePayload, io.IOBase)\nPAYLOAD_REGISTRY.register(StreamReaderPayload, StreamReader)\n# try_last for giving a chance to more specialized async interables like\n# multipart.BodyPartReaderPayload override the default\nPAYLOAD_REGISTRY.register(AsyncIterablePayload, AsyncIterable, order=Order.try_last)\n"
  },
  {
    "path": "aiohttp/py.typed",
    "content": "Marker\n"
  },
  {
    "path": "aiohttp/pytest_plugin.py",
    "content": "import asyncio\nimport contextlib\nimport inspect\nimport warnings\nfrom collections.abc import Awaitable, Callable, Iterator\nfrom typing import Any, Protocol, TypeVar, overload\n\nimport pytest\n\nfrom .test_utils import (\n    BaseTestServer,\n    RawTestServer,\n    TestClient,\n    TestServer,\n    loop_context,\n    setup_test_loop,\n    teardown_test_loop,\n    unused_port as _unused_port,\n)\nfrom .web import Application, BaseRequest, Request\nfrom .web_protocol import _RequestHandler\n\ntry:\n    import uvloop\nexcept ImportError:\n    uvloop = None  # type: ignore[assignment]\n\n_Request = TypeVar(\"_Request\", bound=BaseRequest)\n\n\nclass AiohttpClient(Protocol):\n    # TODO(PY311): Use Unpack to specify ClientSession kwargs.\n    @overload\n    async def __call__(\n        self,\n        __param: Application,\n        *,\n        server_kwargs: dict[str, Any] | None = None,\n        **kwargs: Any,\n    ) -> TestClient[Request, Application]: ...\n    @overload\n    async def __call__(\n        self,\n        __param: BaseTestServer[_Request],\n        *,\n        server_kwargs: dict[str, Any] | None = None,\n        **kwargs: Any,\n    ) -> TestClient[_Request, None]: ...\n\n\nclass AiohttpServer(Protocol):\n    def __call__(\n        self, app: Application, *, port: int | None = None, **kwargs: Any\n    ) -> Awaitable[TestServer]: ...\n\n\nclass AiohttpRawServer(Protocol):\n    def __call__(\n        self,\n        handler: _RequestHandler[BaseRequest],\n        *,\n        port: int | None = None,\n        **kwargs: Any,\n    ) -> Awaitable[RawTestServer]: ...\n\n\ndef pytest_addoption(parser):  # type: ignore[no-untyped-def]\n    parser.addoption(\n        \"--aiohttp-fast\",\n        action=\"store_true\",\n        default=False,\n        help=\"run tests faster by disabling extra checks\",\n    )\n    parser.addoption(\n        \"--aiohttp-loop\",\n        action=\"store\",\n        default=\"pyloop\",\n        help=\"run tests with specific loop: pyloop, uvloop or all\",\n    )\n    parser.addoption(\n        \"--aiohttp-enable-loop-debug\",\n        action=\"store_true\",\n        default=False,\n        help=\"enable event loop debug mode\",\n    )\n\n\ndef pytest_fixture_setup(fixturedef):  # type: ignore[no-untyped-def]\n    \"\"\"Set up pytest fixture.\n\n    Allow fixtures to be coroutines. Run coroutine fixtures in an event loop.\n    \"\"\"\n    func = fixturedef.func\n\n    if inspect.isasyncgenfunction(func):\n        # async generator fixture\n        is_async_gen = True\n    elif inspect.iscoroutinefunction(func):\n        # regular async fixture\n        is_async_gen = False\n    else:\n        # not an async fixture, nothing to do\n        return\n\n    strip_request = False\n    if \"request\" not in fixturedef.argnames:\n        fixturedef.argnames += (\"request\",)\n        strip_request = True\n\n    def wrapper(*args, **kwargs):  # type: ignore[no-untyped-def]\n        request = kwargs[\"request\"]\n        if strip_request:\n            del kwargs[\"request\"]\n\n        # if neither the fixture nor the test use the 'loop' fixture,\n        # 'getfixturevalue' will fail because the test is not parameterized\n        # (this can be removed someday if 'loop' is no longer parameterized)\n        if \"loop\" not in request.fixturenames:\n            raise Exception(\n                \"Asynchronous fixtures must depend on the 'loop' fixture or \"\n                \"be used in tests depending from it.\"\n            )\n\n        _loop = request.getfixturevalue(\"loop\")\n\n        if is_async_gen:\n            # for async generators, we need to advance the generator once,\n            # then advance it again in a finalizer\n            gen = func(*args, **kwargs)\n\n            def finalizer():  # type: ignore[no-untyped-def]\n                try:\n                    return _loop.run_until_complete(gen.__anext__())\n                except StopAsyncIteration:\n                    pass\n\n            request.addfinalizer(finalizer)\n            return _loop.run_until_complete(gen.__anext__())\n        else:\n            return _loop.run_until_complete(func(*args, **kwargs))\n\n    fixturedef.func = wrapper\n\n\n@pytest.fixture\ndef fast(request: pytest.FixtureRequest) -> bool:\n    \"\"\"--fast config option\"\"\"\n    return request.config.getoption(\"--aiohttp-fast\")  # type: ignore[no-any-return]\n\n\n@pytest.fixture\ndef loop_debug(request: pytest.FixtureRequest) -> bool:\n    \"\"\"--enable-loop-debug config option\"\"\"\n    return request.config.getoption(\"--aiohttp-enable-loop-debug\")  # type: ignore[no-any-return]\n\n\n@contextlib.contextmanager\ndef _runtime_warning_context() -> Iterator[None]:\n    \"\"\"Context manager which checks for RuntimeWarnings.\n\n    This exists specifically to\n    avoid \"coroutine 'X' was never awaited\" warnings being missed.\n\n    If RuntimeWarnings occur in the context a RuntimeError is raised.\n    \"\"\"\n    with warnings.catch_warnings(record=True) as _warnings:\n        yield\n        rw = [\n            f\"{w.filename}:{w.lineno}:{w.message}\"\n            for w in _warnings\n            if w.category == RuntimeWarning\n        ]\n        if rw:\n            raise RuntimeError(\n                \"{} Runtime Warning{},\\n{}\".format(\n                    len(rw), \"\" if len(rw) == 1 else \"s\", \"\\n\".join(rw)\n                )\n            )\n\n    # Propagate warnings to pytest\n    for msg in _warnings:\n        warnings.showwarning(\n            msg.message, msg.category, msg.filename, msg.lineno, msg.file, msg.line\n        )\n\n\n@contextlib.contextmanager\ndef _passthrough_loop_context(\n    loop: asyncio.AbstractEventLoop | None, fast: bool = False\n) -> Iterator[asyncio.AbstractEventLoop]:\n    \"\"\"Passthrough loop context.\n\n    Sets up and tears down a loop unless one is passed in via the loop\n    argument when it's passed straight through.\n    \"\"\"\n    if loop:\n        # loop already exists, pass it straight through\n        yield loop\n    else:\n        # this shadows loop_context's standard behavior\n        loop = setup_test_loop()\n        yield loop\n        teardown_test_loop(loop, fast=fast)\n\n\ndef pytest_pycollect_makeitem(collector, name, obj):  # type: ignore[no-untyped-def]\n    \"\"\"Fix pytest collecting for coroutines.\"\"\"\n    if collector.funcnamefilter(name) and inspect.iscoroutinefunction(obj):\n        return list(collector._genfunctions(name, obj))\n\n\ndef pytest_pyfunc_call(pyfuncitem):  # type: ignore[no-untyped-def]\n    \"\"\"Run coroutines in an event loop instead of a normal function call.\"\"\"\n    fast = pyfuncitem.config.getoption(\"--aiohttp-fast\")\n    if inspect.iscoroutinefunction(pyfuncitem.function):\n        existing_loop = (\n            pyfuncitem.funcargs.get(\"proactor_loop\")\n            or pyfuncitem.funcargs.get(\"selector_loop\")\n            or pyfuncitem.funcargs.get(\"uvloop_loop\")\n            or pyfuncitem.funcargs.get(\"loop\", None)\n        )\n\n        with _runtime_warning_context():\n            with _passthrough_loop_context(existing_loop, fast=fast) as _loop:\n                testargs = {\n                    arg: pyfuncitem.funcargs[arg]\n                    for arg in pyfuncitem._fixtureinfo.argnames\n                }\n                _loop.run_until_complete(pyfuncitem.obj(**testargs))\n\n        return True\n\n\ndef pytest_generate_tests(metafunc):  # type: ignore[no-untyped-def]\n    if \"loop_factory\" not in metafunc.fixturenames:\n        return\n\n    loops = metafunc.config.option.aiohttp_loop\n    avail_factories: dict[str, Callable[[], asyncio.AbstractEventLoop]]\n    avail_factories = {\"pyloop\": asyncio.new_event_loop}\n\n    if uvloop is not None:\n        avail_factories[\"uvloop\"] = uvloop.new_event_loop\n\n    if loops == \"all\":\n        loops = \"pyloop,uvloop?\"\n\n    factories = {}  # type: ignore[var-annotated]\n    for name in loops.split(\",\"):\n        required = not name.endswith(\"?\")\n        name = name.strip(\" ?\")\n        if name not in avail_factories:\n            if required:\n                raise ValueError(\n                    \"Unknown loop '%s', available loops: %s\"\n                    % (name, list(factories.keys()))\n                )\n            else:\n                continue\n        factories[name] = avail_factories[name]\n    metafunc.parametrize(\n        \"loop_factory\", list(factories.values()), ids=list(factories.keys())\n    )\n\n\n@pytest.fixture\ndef loop(\n    loop_factory: Callable[[], asyncio.AbstractEventLoop],\n    fast: bool,\n    loop_debug: bool,\n) -> Iterator[asyncio.AbstractEventLoop]:\n    \"\"\"Return an instance of the event loop.\"\"\"\n    with loop_context(loop_factory, fast=fast) as _loop:\n        if loop_debug:\n            _loop.set_debug(True)\n        asyncio.set_event_loop(_loop)\n        yield _loop\n\n\n@pytest.fixture\ndef proactor_loop() -> Iterator[asyncio.AbstractEventLoop]:\n    factory = asyncio.ProactorEventLoop  # type: ignore[attr-defined]\n\n    with loop_context(factory) as _loop:\n        asyncio.set_event_loop(_loop)\n        yield _loop\n\n\n@pytest.fixture\ndef aiohttp_unused_port() -> Callable[[], int]:\n    \"\"\"Return a port that is unused on the current host.\"\"\"\n    return _unused_port\n\n\n@pytest.fixture\ndef aiohttp_server(loop: asyncio.AbstractEventLoop) -> Iterator[AiohttpServer]:\n    \"\"\"Factory to create a TestServer instance, given an app.\n\n    aiohttp_server(app, **kwargs)\n    \"\"\"\n    servers = []\n\n    async def go(\n        app: Application,\n        *,\n        host: str = \"127.0.0.1\",\n        port: int | None = None,\n        **kwargs: Any,\n    ) -> TestServer:\n        server = TestServer(app, host=host, port=port)\n        await server.start_server(**kwargs)\n        servers.append(server)\n        return server\n\n    yield go\n\n    async def finalize() -> None:\n        while servers:\n            await servers.pop().close()\n\n    loop.run_until_complete(finalize())\n\n\n@pytest.fixture\ndef aiohttp_raw_server(loop: asyncio.AbstractEventLoop) -> Iterator[AiohttpRawServer]:\n    \"\"\"Factory to create a RawTestServer instance, given a web handler.\n\n    aiohttp_raw_server(handler, **kwargs)\n    \"\"\"\n    servers = []\n\n    async def go(\n        handler: _RequestHandler[BaseRequest],\n        *,\n        port: int | None = None,\n        **kwargs: Any,\n    ) -> RawTestServer:\n        server = RawTestServer(handler, port=port)\n        await server.start_server(**kwargs)\n        servers.append(server)\n        return server\n\n    yield go\n\n    async def finalize() -> None:\n        while servers:\n            await servers.pop().close()\n\n    loop.run_until_complete(finalize())\n\n\n@pytest.fixture\ndef aiohttp_client_cls() -> type[TestClient[Any, Any]]:\n    \"\"\"\n    Client class to use in ``aiohttp_client`` factory.\n\n    Use it for passing custom ``TestClient`` implementations.\n\n    Example::\n\n       class MyClient(TestClient):\n           async def login(self, *, user, pw):\n               payload = {\"username\": user, \"password\": pw}\n               return await self.post(\"/login\", json=payload)\n\n       @pytest.fixture\n       def aiohttp_client_cls():\n           return MyClient\n\n       def test_login(aiohttp_client):\n           app = web.Application()\n           client = await aiohttp_client(app)\n           await client.login(user=\"admin\", pw=\"s3cr3t\")\n\n    \"\"\"\n    return TestClient\n\n\n@pytest.fixture\ndef aiohttp_client(\n    loop: asyncio.AbstractEventLoop, aiohttp_client_cls: type[TestClient[Any, Any]]\n) -> Iterator[AiohttpClient]:\n    \"\"\"Factory to create a TestClient instance.\n\n    aiohttp_client(app, **kwargs)\n    aiohttp_client(server, **kwargs)\n    aiohttp_client(raw_server, **kwargs)\n    \"\"\"\n    clients = []\n\n    @overload\n    async def go(\n        __param: Application,\n        *,\n        server_kwargs: dict[str, Any] | None = None,\n        **kwargs: Any,\n    ) -> TestClient[Request, Application]: ...\n    @overload\n    async def go(\n        __param: BaseTestServer[_Request],\n        *,\n        server_kwargs: dict[str, Any] | None = None,\n        **kwargs: Any,\n    ) -> TestClient[_Request, None]: ...\n    async def go(\n        __param: Application | BaseTestServer[Any],\n        *,\n        server_kwargs: dict[str, Any] | None = None,\n        **kwargs: Any,\n    ) -> TestClient[Any, Any]:\n        # TODO(PY311): Use Unpack to specify ClientSession kwargs and server_kwargs.\n        if isinstance(__param, Application):\n            server_kwargs = server_kwargs or {}\n            server = TestServer(__param, **server_kwargs)\n            client = aiohttp_client_cls(server, **kwargs)\n        elif isinstance(__param, BaseTestServer):\n            client = aiohttp_client_cls(__param, **kwargs)\n        else:\n            raise ValueError(\"Unknown argument type: %r\" % type(__param))\n\n        await client.start_server()\n        clients.append(client)\n        return client\n\n    yield go\n\n    async def finalize() -> None:\n        while clients:\n            await clients.pop().close()\n\n    loop.run_until_complete(finalize())\n"
  },
  {
    "path": "aiohttp/resolver.py",
    "content": "import asyncio\nimport socket\nimport weakref\nfrom typing import Any, Optional\n\nfrom .abc import AbstractResolver, ResolveResult\n\n__all__ = (\"ThreadedResolver\", \"AsyncResolver\", \"DefaultResolver\")\n\n\ntry:\n    import aiodns\n\n    aiodns_default = hasattr(aiodns.DNSResolver, \"getaddrinfo\")\nexcept ImportError:\n    aiodns = None  # type: ignore[assignment]\n    aiodns_default = False\n\n\n_NUMERIC_SOCKET_FLAGS = socket.AI_NUMERICHOST | socket.AI_NUMERICSERV\n_NAME_SOCKET_FLAGS = socket.NI_NUMERICHOST | socket.NI_NUMERICSERV\n_AI_ADDRCONFIG = socket.AI_ADDRCONFIG\nif hasattr(socket, \"AI_MASK\"):\n    _AI_ADDRCONFIG &= socket.AI_MASK\n\n\nclass ThreadedResolver(AbstractResolver):\n    \"\"\"Threaded resolver.\n\n    Uses an Executor for synchronous getaddrinfo() calls.\n    concurrent.futures.ThreadPoolExecutor is used by default.\n    \"\"\"\n\n    def __init__(self) -> None:\n        self._loop = asyncio.get_running_loop()\n\n    async def resolve(\n        self, host: str, port: int = 0, family: socket.AddressFamily = socket.AF_INET\n    ) -> list[ResolveResult]:\n        infos = await self._loop.getaddrinfo(\n            host,\n            port,\n            type=socket.SOCK_STREAM,\n            family=family,\n            flags=_AI_ADDRCONFIG,\n        )\n\n        hosts: list[ResolveResult] = []\n        for family, _, proto, _, address in infos:\n            if family == socket.AF_INET6:\n                if len(address) < 3:\n                    # IPv6 is not supported by Python build,\n                    # or IPv6 is not enabled in the host\n                    continue\n                if address[3]:\n                    # This is essential for link-local IPv6 addresses.\n                    # LL IPv6 is a VERY rare case. Strictly speaking, we should use\n                    # getnameinfo() unconditionally, but performance makes sense.\n                    resolved_host, _port = await self._loop.getnameinfo(\n                        address, _NAME_SOCKET_FLAGS\n                    )\n                    port = int(_port)\n                else:\n                    resolved_host, port = address[:2]\n            else:  # IPv4\n                assert family == socket.AF_INET\n                resolved_host, port = address  # type: ignore[misc]\n            hosts.append(\n                ResolveResult(\n                    hostname=host,\n                    host=resolved_host,\n                    port=port,\n                    family=family,\n                    proto=proto,\n                    flags=_NUMERIC_SOCKET_FLAGS,\n                )\n            )\n\n        return hosts\n\n    async def close(self) -> None:\n        pass\n\n\nclass AsyncResolver(AbstractResolver):\n    \"\"\"Use the `aiodns` package to make asynchronous DNS lookups\"\"\"\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        if aiodns is None:\n            raise RuntimeError(\"Resolver requires aiodns library\")\n\n        self._loop = asyncio.get_running_loop()\n        self._manager: _DNSResolverManager | None = None\n        # If custom args are provided, create a dedicated resolver instance\n        # This means each AsyncResolver with custom args gets its own\n        # aiodns.DNSResolver instance\n        if args or kwargs:\n            self._resolver = aiodns.DNSResolver(*args, **kwargs)\n            return\n        # Use the shared resolver from the manager for default arguments\n        self._manager = _DNSResolverManager()\n        self._resolver = self._manager.get_resolver(self, self._loop)\n\n    async def resolve(\n        self, host: str, port: int = 0, family: socket.AddressFamily = socket.AF_INET\n    ) -> list[ResolveResult]:\n        try:\n            resp = await self._resolver.getaddrinfo(\n                host,\n                port=port,\n                type=socket.SOCK_STREAM,\n                family=family,\n                flags=_AI_ADDRCONFIG,\n            )\n        except aiodns.error.DNSError as exc:\n            msg = exc.args[1] if len(exc.args) >= 1 else \"DNS lookup failed\"\n            raise OSError(None, msg) from exc\n        hosts: list[ResolveResult] = []\n        for node in resp.nodes:\n            address: tuple[bytes, int] | tuple[bytes, int, int, int] = node.addr\n            if node.family == socket.AF_INET6:\n                if len(address) > 3 and address[3]:\n                    # This is essential for link-local IPv6 addresses.\n                    # LL IPv6 is a VERY rare case. Strictly speaking, we should use\n                    # getnameinfo() unconditionally, but performance makes sense.\n                    result = await self._resolver.getnameinfo(\n                        (address[0].decode(\"ascii\"), *address[1:]),\n                        _NAME_SOCKET_FLAGS,\n                    )\n                    resolved_host = result.node\n                else:\n                    resolved_host = address[0].decode(\"ascii\")\n                    port = address[1]\n            else:  # IPv4\n                assert node.family == socket.AF_INET\n                resolved_host = address[0].decode(\"ascii\")\n                port = address[1]\n            hosts.append(\n                ResolveResult(\n                    hostname=host,\n                    host=resolved_host,\n                    port=port,\n                    family=node.family,\n                    proto=0,\n                    flags=_NUMERIC_SOCKET_FLAGS,\n                )\n            )\n\n        if not hosts:\n            raise OSError(None, \"DNS lookup failed\")\n\n        return hosts\n\n    async def close(self) -> None:\n        if self._manager:\n            # Release the resolver from the manager if using the shared resolver\n            self._manager.release_resolver(self, self._loop)\n            self._manager = None  # Clear reference to manager\n            self._resolver = None  # type: ignore[assignment] # Clear reference to resolver\n            return\n        # Otherwise cancel our dedicated resolver\n        if self._resolver is not None:\n            self._resolver.cancel()\n        self._resolver = None  # type: ignore[assignment] # Clear reference\n\n\nclass _DNSResolverManager:\n    \"\"\"Manager for aiodns.DNSResolver objects.\n\n    This class manages shared aiodns.DNSResolver instances\n    with no custom arguments across different event loops.\n    \"\"\"\n\n    _instance: Optional[\"_DNSResolverManager\"] = None\n\n    def __new__(cls) -> \"_DNSResolverManager\":\n        if cls._instance is None:\n            cls._instance = super().__new__(cls)\n            cls._instance._init()\n        return cls._instance\n\n    def _init(self) -> None:\n        # Use WeakKeyDictionary to allow event loops to be garbage collected\n        self._loop_data: weakref.WeakKeyDictionary[\n            asyncio.AbstractEventLoop,\n            tuple[aiodns.DNSResolver, weakref.WeakSet[AsyncResolver]],\n        ] = weakref.WeakKeyDictionary()\n\n    def get_resolver(\n        self, client: \"AsyncResolver\", loop: asyncio.AbstractEventLoop\n    ) -> \"aiodns.DNSResolver\":\n        \"\"\"Get or create the shared aiodns.DNSResolver instance for a specific event loop.\n\n        Args:\n            client: The AsyncResolver instance requesting the resolver.\n                   This is required to track resolver usage.\n            loop: The event loop to use for the resolver.\n        \"\"\"\n        # Create a new resolver and client set for this loop if it doesn't exist\n        if loop not in self._loop_data:\n            resolver = aiodns.DNSResolver(loop=loop)\n            client_set: weakref.WeakSet[AsyncResolver] = weakref.WeakSet()\n            self._loop_data[loop] = (resolver, client_set)\n        else:\n            # Get the existing resolver and client set\n            resolver, client_set = self._loop_data[loop]\n\n        # Register this client with the loop\n        client_set.add(client)\n        return resolver\n\n    def release_resolver(\n        self, client: \"AsyncResolver\", loop: asyncio.AbstractEventLoop\n    ) -> None:\n        \"\"\"Release the resolver for an AsyncResolver client when it's closed.\n\n        Args:\n            client: The AsyncResolver instance to release.\n            loop: The event loop the resolver was using.\n        \"\"\"\n        # Remove client from its loop's tracking\n        current_loop_data = self._loop_data.get(loop)\n        if current_loop_data is None:\n            return\n        resolver, client_set = current_loop_data\n        client_set.discard(client)\n        # If no more clients for this loop, cancel and remove its resolver\n        if not client_set:\n            if resolver is not None:\n                resolver.cancel()\n            del self._loop_data[loop]\n\n\n_DefaultType = type[AsyncResolver | ThreadedResolver]\nDefaultResolver: _DefaultType = AsyncResolver if aiodns_default else ThreadedResolver\n"
  },
  {
    "path": "aiohttp/streams.py",
    "content": "import asyncio\nimport collections\nimport warnings\nfrom collections.abc import Awaitable, Callable\nfrom typing import Final, Generic, TypeVar\n\nfrom .base_protocol import BaseProtocol\nfrom .helpers import (\n    _EXC_SENTINEL,\n    BaseTimerContext,\n    TimerNoop,\n    set_exception,\n    set_result,\n)\nfrom .http_exceptions import LineTooLong\nfrom .log import internal_logger\n\n__all__ = (\n    \"EMPTY_PAYLOAD\",\n    \"EofStream\",\n    \"StreamReader\",\n    \"DataQueue\",\n)\n\n_T = TypeVar(\"_T\")\n\n\nclass EofStream(Exception):\n    \"\"\"eof stream indication.\"\"\"\n\n\nclass AsyncStreamIterator(Generic[_T]):\n\n    __slots__ = (\"read_func\",)\n\n    def __init__(self, read_func: Callable[[], Awaitable[_T]]) -> None:\n        self.read_func = read_func\n\n    def __aiter__(self) -> \"AsyncStreamIterator[_T]\":\n        return self\n\n    async def __anext__(self) -> _T:\n        try:\n            rv = await self.read_func()\n        except EofStream:\n            raise StopAsyncIteration\n        if rv == b\"\":\n            raise StopAsyncIteration\n        return rv\n\n\nclass ChunkTupleAsyncStreamIterator:\n\n    __slots__ = (\"_stream\",)\n\n    def __init__(self, stream: \"StreamReader\") -> None:\n        self._stream = stream\n\n    def __aiter__(self) -> \"ChunkTupleAsyncStreamIterator\":\n        return self\n\n    async def __anext__(self) -> tuple[bytes, bool]:\n        rv = await self._stream.readchunk()\n        if rv == (b\"\", False):\n            raise StopAsyncIteration\n        return rv\n\n\nclass AsyncStreamReaderMixin:\n\n    __slots__ = ()\n\n    def __aiter__(self) -> AsyncStreamIterator[bytes]:\n        return AsyncStreamIterator(self.readline)  # type: ignore[attr-defined]\n\n    def iter_chunked(self, n: int) -> AsyncStreamIterator[bytes]:\n        \"\"\"Returns an asynchronous iterator that yields chunks of size n.\"\"\"\n        return AsyncStreamIterator(lambda: self.read(n))  # type: ignore[attr-defined]\n\n    def iter_any(self) -> AsyncStreamIterator[bytes]:\n        \"\"\"Yield all available data as soon as it is received.\"\"\"\n        return AsyncStreamIterator(self.readany)  # type: ignore[attr-defined]\n\n    def iter_chunks(self) -> ChunkTupleAsyncStreamIterator:\n        \"\"\"Yield chunks of data as they are received by the server.\n\n        The yielded objects are tuples\n        of (bytes, bool) as returned by the StreamReader.readchunk method.\n        \"\"\"\n        return ChunkTupleAsyncStreamIterator(self)  # type: ignore[arg-type]\n\n\nclass StreamReader(AsyncStreamReaderMixin):\n    \"\"\"An enhancement of asyncio.StreamReader.\n\n    Supports asynchronous iteration by line, chunk or as available::\n\n        async for line in reader:\n            ...\n        async for chunk in reader.iter_chunked(1024):\n            ...\n        async for slice in reader.iter_any():\n            ...\n\n    \"\"\"\n\n    __slots__ = (\n        \"_protocol\",\n        \"_low_water\",\n        \"_high_water\",\n        \"_low_water_chunks\",\n        \"_high_water_chunks\",\n        \"_loop\",\n        \"_size\",\n        \"_cursor\",\n        \"_http_chunk_splits\",\n        \"_buffer\",\n        \"_buffer_offset\",\n        \"_eof\",\n        \"_waiter\",\n        \"_eof_waiter\",\n        \"_exception\",\n        \"_timer\",\n        \"_eof_callbacks\",\n        \"_eof_counter\",\n        \"total_bytes\",\n        \"total_compressed_bytes\",\n    )\n\n    def __init__(\n        self,\n        protocol: BaseProtocol,\n        limit: int,\n        *,\n        timer: BaseTimerContext | None = None,\n        loop: asyncio.AbstractEventLoop,\n    ) -> None:\n        self._protocol = protocol\n        self._low_water = limit\n        self._high_water = limit * 2\n        # Ensure high_water_chunks >= 3 so it's always > low_water_chunks.\n        self._high_water_chunks = max(3, limit // 4)\n        # Use max(2, ...) because there's always at least 1 chunk split remaining\n        # (the current position), so we need low_water >= 2 to allow resume.\n        self._low_water_chunks = max(2, self._high_water_chunks // 2)\n        self._loop = loop\n        self._size = 0\n        self._cursor = 0\n        self._http_chunk_splits: collections.deque[int] | None = None\n        self._buffer: collections.deque[bytes] = collections.deque()\n        self._buffer_offset = 0\n        self._eof = False\n        self._waiter: asyncio.Future[None] | None = None\n        self._eof_waiter: asyncio.Future[None] | None = None\n        self._exception: type[BaseException] | BaseException | None = None\n        self._timer = TimerNoop() if timer is None else timer\n        self._eof_callbacks: list[Callable[[], None]] = []\n        self._eof_counter = 0\n        self.total_bytes = 0\n        self.total_compressed_bytes: int | None = None\n\n    def __repr__(self) -> str:\n        info = [self.__class__.__name__]\n        if self._size:\n            info.append(\"%d bytes\" % self._size)\n        if self._eof:\n            info.append(\"eof\")\n        if self._low_water != 2**16:  # default limit\n            info.append(\"low=%d high=%d\" % (self._low_water, self._high_water))\n        if self._waiter:\n            info.append(\"w=%r\" % self._waiter)\n        if self._exception:\n            info.append(\"e=%r\" % self._exception)\n        return \"<%s>\" % \" \".join(info)\n\n    def get_read_buffer_limits(self) -> tuple[int, int]:\n        return (self._low_water, self._high_water)\n\n    def exception(self) -> type[BaseException] | BaseException | None:\n        return self._exception\n\n    def set_exception(\n        self,\n        exc: type[BaseException] | BaseException,\n        exc_cause: BaseException = _EXC_SENTINEL,\n    ) -> None:\n        self._exception = exc\n        self._eof_callbacks.clear()\n\n        waiter = self._waiter\n        if waiter is not None:\n            self._waiter = None\n            set_exception(waiter, exc, exc_cause)\n\n        waiter = self._eof_waiter\n        if waiter is not None:\n            self._eof_waiter = None\n            set_exception(waiter, exc, exc_cause)\n\n    def on_eof(self, callback: Callable[[], None]) -> None:\n        if self._eof:\n            try:\n                callback()\n            except Exception:\n                internal_logger.exception(\"Exception in eof callback\")\n        else:\n            self._eof_callbacks.append(callback)\n\n    def feed_eof(self) -> None:\n        self._eof = True\n\n        waiter = self._waiter\n        if waiter is not None:\n            self._waiter = None\n            set_result(waiter, None)\n\n        waiter = self._eof_waiter\n        if waiter is not None:\n            self._eof_waiter = None\n            set_result(waiter, None)\n\n        if self._protocol._reading_paused:\n            self._protocol.resume_reading()\n\n        for cb in self._eof_callbacks:\n            try:\n                cb()\n            except Exception:\n                internal_logger.exception(\"Exception in eof callback\")\n\n        self._eof_callbacks.clear()\n\n    def is_eof(self) -> bool:\n        \"\"\"Return True if  'feed_eof' was called.\"\"\"\n        return self._eof\n\n    def at_eof(self) -> bool:\n        \"\"\"Return True if the buffer is empty and 'feed_eof' was called.\"\"\"\n        return self._eof and not self._buffer\n\n    async def wait_eof(self) -> None:\n        if self._eof:\n            return\n\n        assert self._eof_waiter is None\n        self._eof_waiter = self._loop.create_future()\n        try:\n            await self._eof_waiter\n        finally:\n            self._eof_waiter = None\n\n    @property\n    def total_raw_bytes(self) -> int:\n        if self.total_compressed_bytes is None:\n            return self.total_bytes\n        return self.total_compressed_bytes\n\n    def unread_data(self, data: bytes) -> None:\n        \"\"\"rollback reading some data from stream, inserting it to buffer head.\"\"\"\n        warnings.warn(\n            \"unread_data() is deprecated \"\n            \"and will be removed in future releases (#3260)\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n        if not data:\n            return\n\n        if self._buffer_offset:\n            self._buffer[0] = self._buffer[0][self._buffer_offset :]\n            self._buffer_offset = 0\n        self._size += len(data)\n        self._cursor -= len(data)\n        self._buffer.appendleft(data)\n        self._eof_counter = 0\n\n    def feed_data(self, data: bytes) -> None:\n        assert not self._eof, \"feed_data after feed_eof\"\n\n        if not data:\n            return\n\n        data_len = len(data)\n        self._size += data_len\n        self._buffer.append(data)\n        self.total_bytes += data_len\n\n        waiter = self._waiter\n        if waiter is not None:\n            self._waiter = None\n            set_result(waiter, None)\n\n        if self._size > self._high_water and not self._protocol._reading_paused:\n            self._protocol.pause_reading()\n\n    def begin_http_chunk_receiving(self) -> None:\n        if self._http_chunk_splits is None:\n            if self.total_bytes:\n                raise RuntimeError(\n                    \"Called begin_http_chunk_receiving when some data was already fed\"\n                )\n            self._http_chunk_splits = collections.deque()\n\n    def end_http_chunk_receiving(self) -> None:\n        if self._http_chunk_splits is None:\n            raise RuntimeError(\n                \"Called end_chunk_receiving without calling \"\n                \"begin_chunk_receiving first\"\n            )\n\n        # self._http_chunk_splits contains logical byte offsets from start of\n        # the body transfer. Each offset is the offset of the end of a chunk.\n        # \"Logical\" means bytes, accessible for a user.\n        # If no chunks containing logical data were received, current position\n        # is difinitely zero.\n        pos = self._http_chunk_splits[-1] if self._http_chunk_splits else 0\n\n        if self.total_bytes == pos:\n            # We should not add empty chunks here. So we check for that.\n            # Note, when chunked + gzip is used, we can receive a chunk\n            # of compressed data, but that data may not be enough for gzip FSM\n            # to yield any uncompressed data. That's why current position may\n            # not change after receiving a chunk.\n            return\n\n        self._http_chunk_splits.append(self.total_bytes)\n\n        # If we get too many small chunks before self._high_water is reached, then any\n        # .read() call becomes computationally expensive, and could block the event loop\n        # for too long, hence an additional self._high_water_chunks here.\n        if (\n            len(self._http_chunk_splits) > self._high_water_chunks\n            and not self._protocol._reading_paused\n        ):\n            self._protocol.pause_reading()\n\n        # wake up readchunk when end of http chunk received\n        waiter = self._waiter\n        if waiter is not None:\n            self._waiter = None\n            set_result(waiter, None)\n\n    async def _wait(self, func_name: str) -> None:\n        if not self._protocol.connected:\n            raise RuntimeError(\"Connection closed.\")\n\n        # StreamReader uses a future to link the protocol feed_data() method\n        # to a read coroutine. Running two read coroutines at the same time\n        # would have an unexpected behaviour. It would not possible to know\n        # which coroutine would get the next data.\n        if self._waiter is not None:\n            raise RuntimeError(\n                \"%s() called while another coroutine is \"\n                \"already waiting for incoming data\" % func_name\n            )\n\n        waiter = self._waiter = self._loop.create_future()\n        try:\n            with self._timer:\n                await waiter\n        finally:\n            self._waiter = None\n\n    async def readline(self, *, max_line_length: int | None = None) -> bytes:\n        return await self.readuntil(max_size=max_line_length)\n\n    async def readuntil(\n        self, separator: bytes = b\"\\n\", *, max_size: int | None = None\n    ) -> bytes:\n        seplen = len(separator)\n        if seplen == 0:\n            raise ValueError(\"Separator should be at least one-byte string\")\n\n        if self._exception is not None:\n            raise self._exception\n\n        chunk = b\"\"\n        chunk_size = 0\n        not_enough = True\n        max_size = max_size or self._high_water\n\n        while not_enough:\n            while self._buffer and not_enough:\n                offset = self._buffer_offset\n                ichar = self._buffer[0].find(separator, offset) + 1\n                # Read from current offset to found separator or to the end.\n                data = self._read_nowait_chunk(\n                    ichar - offset + seplen - 1 if ichar else -1\n                )\n                chunk += data\n                chunk_size += len(data)\n                if ichar:\n                    not_enough = False\n\n                if chunk_size > max_size:\n                    raise LineTooLong(chunk[:100] + b\"...\", max_size)\n\n            if self._eof:\n                break\n\n            if not_enough:\n                await self._wait(\"readuntil\")\n\n        return chunk\n\n    async def read(self, n: int = -1) -> bytes:\n        if self._exception is not None:\n            raise self._exception\n\n        if not n:\n            return b\"\"\n\n        if n < 0:\n            # This used to just loop creating a new waiter hoping to\n            # collect everything in self._buffer, but that would\n            # deadlock if the subprocess sends more than self.limit\n            # bytes.  So just call self.readany() until EOF.\n            blocks = []\n            while True:\n                block = await self.readany()\n                if not block:\n                    break\n                blocks.append(block)\n            return b\"\".join(blocks)\n\n        # TODO: should be `if` instead of `while`\n        # because waiter maybe triggered on chunk end,\n        # without feeding any data\n        while not self._buffer and not self._eof:\n            await self._wait(\"read\")\n\n        return self._read_nowait(n)\n\n    async def readany(self) -> bytes:\n        if self._exception is not None:\n            raise self._exception\n\n        # TODO: should be `if` instead of `while`\n        # because waiter maybe triggered on chunk end,\n        # without feeding any data\n        while not self._buffer and not self._eof:\n            await self._wait(\"readany\")\n\n        return self._read_nowait(-1)\n\n    async def readchunk(self) -> tuple[bytes, bool]:\n        \"\"\"Returns a tuple of (data, end_of_http_chunk).\n\n        When chunked transfer\n        encoding is used, end_of_http_chunk is a boolean indicating if the end\n        of the data corresponds to the end of a HTTP chunk , otherwise it is\n        always False.\n        \"\"\"\n        while True:\n            if self._exception is not None:\n                raise self._exception\n\n            while self._http_chunk_splits:\n                pos = self._http_chunk_splits.popleft()\n                if pos == self._cursor:\n                    return (b\"\", True)\n                if pos > self._cursor:\n                    return (self._read_nowait(pos - self._cursor), True)\n                internal_logger.warning(\n                    \"Skipping HTTP chunk end due to data \"\n                    \"consumption beyond chunk boundary\"\n                )\n\n            if self._buffer:\n                return (self._read_nowait_chunk(-1), False)\n                # return (self._read_nowait(-1), False)\n\n            if self._eof:\n                # Special case for signifying EOF.\n                # (b'', True) is not a final return value actually.\n                return (b\"\", False)\n\n            await self._wait(\"readchunk\")\n\n    async def readexactly(self, n: int) -> bytes:\n        if self._exception is not None:\n            raise self._exception\n\n        blocks: list[bytes] = []\n        while n > 0:\n            block = await self.read(n)\n            if not block:\n                partial = b\"\".join(blocks)\n                raise asyncio.IncompleteReadError(partial, len(partial) + n)\n            blocks.append(block)\n            n -= len(block)\n\n        return b\"\".join(blocks)\n\n    def read_nowait(self, n: int = -1) -> bytes:\n        # default was changed to be consistent with .read(-1)\n        #\n        # I believe the most users don't know about the method and\n        # they are not affected.\n        if self._exception is not None:\n            raise self._exception\n\n        if self._waiter and not self._waiter.done():\n            raise RuntimeError(\n                \"Called while some coroutine is waiting for incoming data.\"\n            )\n\n        return self._read_nowait(n)\n\n    def _read_nowait_chunk(self, n: int) -> bytes:\n        first_buffer = self._buffer[0]\n        offset = self._buffer_offset\n        if n != -1 and len(first_buffer) - offset > n:\n            data = first_buffer[offset : offset + n]\n            self._buffer_offset += n\n\n        elif offset:\n            self._buffer.popleft()\n            data = first_buffer[offset:]\n            self._buffer_offset = 0\n\n        else:\n            data = self._buffer.popleft()\n\n        data_len = len(data)\n        self._size -= data_len\n        self._cursor += data_len\n\n        chunk_splits = self._http_chunk_splits\n        # Prevent memory leak: drop useless chunk splits\n        while chunk_splits and chunk_splits[0] < self._cursor:\n            chunk_splits.popleft()\n\n        if (\n            self._protocol._reading_paused\n            and self._size < self._low_water\n            and (\n                self._http_chunk_splits is None\n                or len(self._http_chunk_splits) < self._low_water_chunks\n            )\n        ):\n            self._protocol.resume_reading()\n        return data\n\n    def _read_nowait(self, n: int) -> bytes:\n        \"\"\"Read not more than n bytes, or whole buffer if n == -1\"\"\"\n        self._timer.assert_timeout()\n\n        chunks = []\n        while self._buffer:\n            chunk = self._read_nowait_chunk(n)\n            chunks.append(chunk)\n            if n != -1:\n                n -= len(chunk)\n                if n == 0:\n                    break\n\n        return b\"\".join(chunks) if chunks else b\"\"\n\n\nclass EmptyStreamReader(StreamReader):  # lgtm [py/missing-call-to-init]\n\n    __slots__ = (\"_read_eof_chunk\",)\n\n    def __init__(self) -> None:\n        self._read_eof_chunk = False\n        self.total_bytes = 0\n\n    def __repr__(self) -> str:\n        return \"<%s>\" % self.__class__.__name__\n\n    def exception(self) -> BaseException | None:\n        return None\n\n    def set_exception(\n        self,\n        exc: type[BaseException] | BaseException,\n        exc_cause: BaseException = _EXC_SENTINEL,\n    ) -> None:\n        pass\n\n    def on_eof(self, callback: Callable[[], None]) -> None:\n        try:\n            callback()\n        except Exception:\n            internal_logger.exception(\"Exception in eof callback\")\n\n    def feed_eof(self) -> None:\n        pass\n\n    def is_eof(self) -> bool:\n        return True\n\n    def at_eof(self) -> bool:\n        return True\n\n    async def wait_eof(self) -> None:\n        return\n\n    def feed_data(self, data: bytes) -> None:\n        pass\n\n    async def readline(self, *, max_line_length: int | None = None) -> bytes:\n        return b\"\"\n\n    async def read(self, n: int = -1) -> bytes:\n        return b\"\"\n\n    # TODO add async def readuntil\n\n    async def readany(self) -> bytes:\n        return b\"\"\n\n    async def readchunk(self) -> tuple[bytes, bool]:\n        if not self._read_eof_chunk:\n            self._read_eof_chunk = True\n            return (b\"\", False)\n\n        return (b\"\", True)\n\n    async def readexactly(self, n: int) -> bytes:\n        raise asyncio.IncompleteReadError(b\"\", n)\n\n    def read_nowait(self, n: int = -1) -> bytes:\n        return b\"\"\n\n\nEMPTY_PAYLOAD: Final[StreamReader] = EmptyStreamReader()\n\n\nclass DataQueue(Generic[_T]):\n    \"\"\"DataQueue is a general-purpose blocking queue with one reader.\"\"\"\n\n    def __init__(self, loop: asyncio.AbstractEventLoop) -> None:\n        self._loop = loop\n        self._eof = False\n        self._waiter: asyncio.Future[None] | None = None\n        self._exception: type[BaseException] | BaseException | None = None\n        self._buffer: collections.deque[_T] = collections.deque()\n\n    def __len__(self) -> int:\n        return len(self._buffer)\n\n    def is_eof(self) -> bool:\n        return self._eof\n\n    def at_eof(self) -> bool:\n        return self._eof and not self._buffer\n\n    def exception(self) -> type[BaseException] | BaseException | None:\n        return self._exception\n\n    def set_exception(\n        self,\n        exc: type[BaseException] | BaseException,\n        exc_cause: BaseException = _EXC_SENTINEL,\n    ) -> None:\n        self._eof = True\n        self._exception = exc\n        if (waiter := self._waiter) is not None:\n            self._waiter = None\n            set_exception(waiter, exc, exc_cause)\n\n    def feed_data(self, data: _T) -> None:\n        self._buffer.append(data)\n        if (waiter := self._waiter) is not None:\n            self._waiter = None\n            set_result(waiter, None)\n\n    def feed_eof(self) -> None:\n        self._eof = True\n        if (waiter := self._waiter) is not None:\n            self._waiter = None\n            set_result(waiter, None)\n\n    async def read(self) -> _T:\n        if not self._buffer and not self._eof:\n            assert not self._waiter\n            self._waiter = self._loop.create_future()\n            try:\n                await self._waiter\n            except (asyncio.CancelledError, asyncio.TimeoutError):\n                self._waiter = None\n                raise\n        if self._buffer:\n            return self._buffer.popleft()\n        if self._exception is not None:\n            raise self._exception\n        raise EofStream\n\n    def __aiter__(self) -> AsyncStreamIterator[_T]:\n        return AsyncStreamIterator(self.read)\n"
  },
  {
    "path": "aiohttp/tcp_helpers.py",
    "content": "\"\"\"Helper methods to tune a TCP connection\"\"\"\n\nimport asyncio\nimport socket\nfrom contextlib import suppress\n\n__all__ = (\"tcp_keepalive\", \"tcp_nodelay\")\n\n\nif hasattr(socket, \"SO_KEEPALIVE\"):\n\n    def tcp_keepalive(transport: asyncio.Transport) -> None:\n        sock = transport.get_extra_info(\"socket\")\n        if sock is not None:\n            sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)\n\nelse:\n\n    def tcp_keepalive(transport: asyncio.Transport) -> None:\n        \"\"\"Noop when keepalive not supported.\"\"\"\n\n\ndef tcp_nodelay(transport: asyncio.Transport, value: bool) -> None:\n    sock = transport.get_extra_info(\"socket\")\n\n    if sock is None:\n        return\n\n    if sock.family not in (socket.AF_INET, socket.AF_INET6):\n        return\n\n    value = bool(value)\n\n    # socket may be closed already, on windows OSError get raised\n    with suppress(OSError):\n        sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, value)\n"
  },
  {
    "path": "aiohttp/test_utils.py",
    "content": "\"\"\"Utilities shared by tests.\"\"\"\n\nimport asyncio\nimport contextlib\nimport gc\nimport ipaddress\nimport os\nimport socket\nimport sys\nfrom abc import ABC, abstractmethod\nfrom collections.abc import Callable, Iterator\nfrom types import TracebackType\nfrom typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar, cast, overload\nfrom unittest import IsolatedAsyncioTestCase, mock\n\nfrom aiosignal import Signal\nfrom multidict import CIMultiDict, CIMultiDictProxy\nfrom yarl import URL\n\nimport aiohttp\nfrom aiohttp.client import (\n    _BaseRequestContextManager,\n    _RequestContextManager,\n    _RequestOptions,\n    _WSRequestContextManager,\n)\n\nfrom . import ClientSession, hdrs\nfrom .abc import AbstractCookieJar, AbstractStreamWriter\nfrom .client_reqrep import ClientResponse\nfrom .client_ws import ClientWebSocketResponse\nfrom .http import HttpVersion, RawRequestMessage\nfrom .streams import EMPTY_PAYLOAD, StreamReader\nfrom .typedefs import LooseHeaders, StrOrURL\nfrom .web import (\n    Application,\n    AppRunner,\n    BaseRequest,\n    BaseRunner,\n    Request,\n    RequestHandler,\n    Server,\n    ServerRunner,\n    SockSite,\n    UrlMappingMatchInfo,\n)\nfrom .web_protocol import _RequestHandler\n\nif TYPE_CHECKING:\n    from ssl import SSLContext\nelse:\n    SSLContext = Any\n\nif sys.version_info >= (3, 11) and TYPE_CHECKING:\n    from typing import Unpack\n\nif sys.version_info >= (3, 11):\n    from typing import Self\nelse:\n    Self = Any\n\n_ApplicationNone = TypeVar(\"_ApplicationNone\", Application, None)\n_Request = TypeVar(\"_Request\", bound=BaseRequest)\n\nREUSE_ADDRESS = os.name == \"posix\" and sys.platform != \"cygwin\"\n\n\ndef get_unused_port_socket(\n    host: str, family: socket.AddressFamily = socket.AF_INET\n) -> socket.socket:\n    return get_port_socket(host, 0, family)\n\n\ndef get_port_socket(\n    host: str, port: int, family: socket.AddressFamily = socket.AF_INET\n) -> socket.socket:\n    s = socket.socket(family, socket.SOCK_STREAM)\n    if REUSE_ADDRESS:\n        # Windows has different semantics for SO_REUSEADDR,\n        # so don't set it. Ref:\n        # https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse\n        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    s.bind((host, port))\n    return s\n\n\ndef unused_port() -> int:\n    \"\"\"Return a port that is unused on the current host.\"\"\"\n    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:\n        s.bind((\"127.0.0.1\", 0))\n        return cast(int, s.getsockname()[1])\n\n\nclass BaseTestServer(ABC, Generic[_Request]):\n    __test__ = False\n\n    def __init__(\n        self,\n        *,\n        scheme: str = \"\",\n        host: str = \"127.0.0.1\",\n        port: int | None = None,\n        skip_url_asserts: bool = False,\n        socket_factory: Callable[\n            [str, int, socket.AddressFamily], socket.socket\n        ] = get_port_socket,\n        **kwargs: Any,\n    ) -> None:\n        self.runner: BaseRunner[_Request] | None = None\n        self._root: URL | None = None\n        self.host = host\n        self.port = port or 0\n        self._closed = False\n        self.scheme = scheme\n        self.skip_url_asserts = skip_url_asserts\n        self.socket_factory = socket_factory\n\n    async def start_server(self, **kwargs: Any) -> None:\n        if self.runner:\n            return\n        self._ssl = kwargs.pop(\"ssl\", None)\n        self.runner = await self._make_runner(handler_cancellation=True, **kwargs)\n        await self.runner.setup()\n        absolute_host = self.host\n        try:\n            version = ipaddress.ip_address(self.host).version\n        except ValueError:\n            version = 4\n        if version == 6:\n            absolute_host = f\"[{self.host}]\"\n        family = socket.AF_INET6 if version == 6 else socket.AF_INET\n        _sock = self.socket_factory(self.host, self.port, family)\n        self.host, self.port = _sock.getsockname()[:2]\n        site = SockSite(self.runner, sock=_sock, ssl_context=self._ssl)\n        await site.start()\n        server = site._server\n        assert server is not None\n        sockets = server.sockets\n        assert sockets is not None\n        self.port = sockets[0].getsockname()[1]\n        if not self.scheme:\n            self.scheme = \"https\" if self._ssl else \"http\"\n        self._root = URL(f\"{self.scheme}://{absolute_host}:{self.port}\")\n\n    @abstractmethod\n    async def _make_runner(self, **kwargs: Any) -> BaseRunner[_Request]:\n        \"\"\"Return a new runner for the server.\"\"\"\n        # TODO(PY311): Use Unpack to specify Server kwargs.\n\n    def make_url(self, path: StrOrURL) -> URL:\n        assert self._root is not None\n        url = URL(path)\n        if not self.skip_url_asserts:\n            assert not url.absolute\n            return self._root.join(url)\n        else:\n            return URL(str(self._root) + str(path))\n\n    @property\n    def started(self) -> bool:\n        return self.runner is not None\n\n    @property\n    def closed(self) -> bool:\n        return self._closed\n\n    @property\n    def handler(self) -> Server[_Request]:\n        # for backward compatibility\n        # web.Server instance\n        runner = self.runner\n        assert runner is not None\n        assert runner.server is not None\n        return runner.server\n\n    async def close(self) -> None:\n        \"\"\"Close all fixtures created by the test client.\n\n        After that point, the TestClient is no longer usable.\n\n        This is an idempotent function: running close multiple times\n        will not have any additional effects.\n\n        close is also run when the object is garbage collected, and on\n        exit when used as a context manager.\n\n        \"\"\"\n        if self.started and not self.closed:\n            assert self.runner is not None\n            await self.runner.cleanup()\n            self._root = None\n            self.port = 0\n            self._closed = True\n\n    async def __aenter__(self) -> Self:\n        await self.start_server()\n        return self\n\n    async def __aexit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc_value: BaseException | None,\n        traceback: TracebackType | None,\n    ) -> None:\n        await self.close()\n\n\nclass TestServer(BaseTestServer[Request]):\n    def __init__(\n        self,\n        app: Application,\n        *,\n        scheme: str = \"\",\n        host: str = \"127.0.0.1\",\n        port: int | None = None,\n        **kwargs: Any,\n    ):\n        self.app = app\n        super().__init__(scheme=scheme, host=host, port=port, **kwargs)\n\n    async def _make_runner(self, **kwargs: Any) -> AppRunner:\n        # TODO(PY311): Use Unpack to specify Server kwargs.\n        return AppRunner(self.app, **kwargs)\n\n\nclass RawTestServer(BaseTestServer[BaseRequest]):\n    def __init__(\n        self,\n        handler: _RequestHandler[BaseRequest],\n        *,\n        scheme: str = \"\",\n        host: str = \"127.0.0.1\",\n        port: int | None = None,\n        **kwargs: Any,\n    ) -> None:\n        self._handler = handler\n        super().__init__(scheme=scheme, host=host, port=port, **kwargs)\n\n    async def _make_runner(self, **kwargs: Any) -> ServerRunner:\n        # TODO(PY311): Use Unpack to specify Server kwargs.\n        srv = Server(self._handler, **kwargs)\n        return ServerRunner(srv, **kwargs)\n\n\nclass TestClient(Generic[_Request, _ApplicationNone]):\n    \"\"\"\n    A test client implementation.\n\n    To write functional tests for aiohttp based servers.\n\n    \"\"\"\n\n    __test__ = False\n\n    @overload\n    def __init__(\n        self: \"TestClient[Request, Application]\",\n        server: TestServer,\n        *,\n        cookie_jar: AbstractCookieJar | None = None,\n        **kwargs: Any,\n    ) -> None: ...\n    @overload\n    def __init__(\n        self: \"TestClient[_Request, None]\",\n        server: BaseTestServer[_Request],\n        *,\n        cookie_jar: AbstractCookieJar | None = None,\n        **kwargs: Any,\n    ) -> None: ...\n    def __init__(  # type: ignore[misc]\n        self,\n        server: BaseTestServer[_Request],\n        *,\n        cookie_jar: AbstractCookieJar | None = None,\n        **kwargs: Any,\n    ) -> None:\n        # TODO(PY311): Use Unpack to specify ClientSession kwargs.\n        if not isinstance(server, BaseTestServer):\n            raise TypeError(\n                \"server must be TestServer instance, found type: %r\" % type(server)\n            )\n        self._server = server\n        if cookie_jar is None:\n            cookie_jar = aiohttp.CookieJar(unsafe=True)\n        self._session = ClientSession(cookie_jar=cookie_jar, **kwargs)\n        self._session._retry_connection = False\n        self._closed = False\n        self._responses: list[ClientResponse] = []\n        self._websockets: list[ClientWebSocketResponse[bool]] = []\n\n    async def start_server(self) -> None:\n        await self._server.start_server()\n\n    @property\n    def scheme(self) -> str | object:\n        return self._server.scheme\n\n    @property\n    def host(self) -> str:\n        return self._server.host\n\n    @property\n    def port(self) -> int:\n        return self._server.port\n\n    @property\n    def server(self) -> BaseTestServer[_Request]:\n        return self._server\n\n    @property\n    def app(self) -> _ApplicationNone:\n        return getattr(self._server, \"app\", None)  # type: ignore[return-value]\n\n    @property\n    def session(self) -> ClientSession:\n        \"\"\"An internal aiohttp.ClientSession.\n\n        Unlike the methods on the TestClient, client session requests\n        do not automatically include the host in the url queried, and\n        will require an absolute path to the resource.\n\n        \"\"\"\n        return self._session\n\n    def make_url(self, path: StrOrURL) -> URL:\n        return self._server.make_url(path)\n\n    async def _request(\n        self, method: str, path: StrOrURL, **kwargs: Any\n    ) -> ClientResponse:\n        resp = await self._session.request(method, self.make_url(path), **kwargs)\n        # save it to close later\n        self._responses.append(resp)\n        return resp\n\n    if sys.version_info >= (3, 11) and TYPE_CHECKING:\n\n        def request(\n            self, method: str, path: StrOrURL, **kwargs: Unpack[_RequestOptions]\n        ) -> _RequestContextManager: ...\n\n        def get(\n            self,\n            path: StrOrURL,\n            **kwargs: Unpack[_RequestOptions],\n        ) -> _RequestContextManager: ...\n\n        def options(\n            self,\n            path: StrOrURL,\n            **kwargs: Unpack[_RequestOptions],\n        ) -> _RequestContextManager: ...\n\n        def head(\n            self,\n            path: StrOrURL,\n            **kwargs: Unpack[_RequestOptions],\n        ) -> _RequestContextManager: ...\n\n        def post(\n            self,\n            path: StrOrURL,\n            **kwargs: Unpack[_RequestOptions],\n        ) -> _RequestContextManager: ...\n\n        def put(\n            self,\n            path: StrOrURL,\n            **kwargs: Unpack[_RequestOptions],\n        ) -> _RequestContextManager: ...\n\n        def patch(\n            self,\n            path: StrOrURL,\n            **kwargs: Unpack[_RequestOptions],\n        ) -> _RequestContextManager: ...\n\n        def delete(\n            self,\n            path: StrOrURL,\n            **kwargs: Unpack[_RequestOptions],\n        ) -> _RequestContextManager: ...\n\n    else:\n\n        def request(\n            self, method: str, path: StrOrURL, **kwargs: Any\n        ) -> _RequestContextManager:\n            \"\"\"Routes a request to tested http server.\n\n            The interface is identical to aiohttp.ClientSession.request,\n            except the loop kwarg is overridden by the instance used by the\n            test server.\n\n            \"\"\"\n            return _RequestContextManager(self._request(method, path, **kwargs))\n\n        def get(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager:\n            \"\"\"Perform an HTTP GET request.\"\"\"\n            return _RequestContextManager(self._request(hdrs.METH_GET, path, **kwargs))\n\n        def post(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager:\n            \"\"\"Perform an HTTP POST request.\"\"\"\n            return _RequestContextManager(self._request(hdrs.METH_POST, path, **kwargs))\n\n        def options(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager:\n            \"\"\"Perform an HTTP OPTIONS request.\"\"\"\n            return _RequestContextManager(\n                self._request(hdrs.METH_OPTIONS, path, **kwargs)\n            )\n\n        def head(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager:\n            \"\"\"Perform an HTTP HEAD request.\"\"\"\n            return _RequestContextManager(self._request(hdrs.METH_HEAD, path, **kwargs))\n\n        def put(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager:\n            \"\"\"Perform an HTTP PUT request.\"\"\"\n            return _RequestContextManager(self._request(hdrs.METH_PUT, path, **kwargs))\n\n        def patch(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager:\n            \"\"\"Perform an HTTP PATCH request.\"\"\"\n            return _RequestContextManager(\n                self._request(hdrs.METH_PATCH, path, **kwargs)\n            )\n\n        def delete(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager:\n            \"\"\"Perform an HTTP PATCH request.\"\"\"\n            return _RequestContextManager(\n                self._request(hdrs.METH_DELETE, path, **kwargs)\n            )\n\n    @overload\n    def ws_connect(\n        self, path: StrOrURL, *, decode_text: Literal[True] = ..., **kwargs: Any\n    ) -> \"_BaseRequestContextManager[ClientWebSocketResponse[Literal[True]]]\": ...\n\n    @overload\n    def ws_connect(\n        self, path: StrOrURL, *, decode_text: Literal[False], **kwargs: Any\n    ) -> \"_BaseRequestContextManager[ClientWebSocketResponse[Literal[False]]]\": ...\n\n    @overload\n    def ws_connect(\n        self, path: StrOrURL, *, decode_text: bool = ..., **kwargs: Any\n    ) -> \"_BaseRequestContextManager[ClientWebSocketResponse[bool]]\": ...\n\n    def ws_connect(\n        self, path: StrOrURL, *, decode_text: bool = True, **kwargs: Any\n    ) -> \"_BaseRequestContextManager[ClientWebSocketResponse[bool]]\":\n        \"\"\"Initiate websocket connection.\n\n        The api corresponds to aiohttp.ClientSession.ws_connect.\n\n        \"\"\"\n        return _WSRequestContextManager(\n            self._ws_connect(path, decode_text=decode_text, **kwargs)\n        )\n\n    @overload\n    async def _ws_connect(\n        self, path: StrOrURL, *, decode_text: Literal[True] = ..., **kwargs: Any\n    ) -> \"ClientWebSocketResponse[Literal[True]]\": ...\n\n    @overload\n    async def _ws_connect(\n        self, path: StrOrURL, *, decode_text: Literal[False], **kwargs: Any\n    ) -> \"ClientWebSocketResponse[Literal[False]]\": ...\n\n    @overload\n    async def _ws_connect(\n        self, path: StrOrURL, *, decode_text: bool = ..., **kwargs: Any\n    ) -> \"ClientWebSocketResponse[bool]\": ...\n\n    async def _ws_connect(\n        self, path: StrOrURL, *, decode_text: bool = True, **kwargs: Any\n    ) -> \"ClientWebSocketResponse[bool]\":\n        ws = await self._session.ws_connect(\n            self.make_url(path), decode_text=decode_text, **kwargs\n        )\n        self._websockets.append(ws)\n        return ws\n\n    async def close(self) -> None:\n        \"\"\"Close all fixtures created by the test client.\n\n        After that point, the TestClient is no longer usable.\n\n        This is an idempotent function: running close multiple times\n        will not have any additional effects.\n\n        close is also run on exit when used as a(n) (asynchronous)\n        context manager.\n\n        \"\"\"\n        if not self._closed:\n            for resp in self._responses:\n                resp.close()\n            for ws in self._websockets:\n                await ws.close()\n            await self._session.close()\n            await self._server.close()\n            self._closed = True\n\n    async def __aenter__(self) -> Self:\n        await self.start_server()\n        return self\n\n    async def __aexit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc: BaseException | None,\n        tb: TracebackType | None,\n    ) -> None:\n        await self.close()\n\n\nclass AioHTTPTestCase(IsolatedAsyncioTestCase, ABC):\n    \"\"\"A base class to allow for unittest web applications using aiohttp.\n\n    Provides the following:\n\n    * self.client (aiohttp.test_utils.TestClient): an aiohttp test client.\n    * self.app (aiohttp.web.Application): the application returned by\n        self.get_application()\n\n    Note that the TestClient's methods are asynchronous: you have to\n    execute function on the test client using asynchronous methods.\n    \"\"\"\n\n    @abstractmethod\n    async def get_application(self) -> Application:\n        \"\"\"Get application.\n\n        This method should be overridden to return the aiohttp.web.Application\n        object to test.\n        \"\"\"\n\n    async def asyncSetUp(self) -> None:\n        self.app = await self.get_application()\n        self.server = await self.get_server(self.app)\n        self.client = await self.get_client(self.server)\n\n        await self.client.start_server()\n\n    async def asyncTearDown(self) -> None:\n        await self.client.close()\n\n    async def get_server(self, app: Application) -> TestServer:\n        \"\"\"Return a TestServer instance.\"\"\"\n        return TestServer(app)\n\n    async def get_client(self, server: TestServer) -> TestClient[Request, Application]:\n        \"\"\"Return a TestClient instance.\"\"\"\n        return TestClient(server)\n\n\n_LOOP_FACTORY = Callable[[], asyncio.AbstractEventLoop]\n\n\n@contextlib.contextmanager\ndef loop_context(\n    loop_factory: _LOOP_FACTORY = asyncio.new_event_loop, fast: bool = False\n) -> Iterator[asyncio.AbstractEventLoop]:\n    \"\"\"A contextmanager that creates an event_loop, for test purposes.\n\n    Handles the creation and cleanup of a test loop.\n    \"\"\"\n    loop = setup_test_loop(loop_factory)\n    yield loop\n    teardown_test_loop(loop, fast=fast)\n\n\ndef setup_test_loop(\n    loop_factory: _LOOP_FACTORY = asyncio.new_event_loop,\n) -> asyncio.AbstractEventLoop:\n    \"\"\"Create and return an asyncio.BaseEventLoop instance.\n\n    The caller should also call teardown_test_loop,\n    once they are done with the loop.\n    \"\"\"\n    loop = loop_factory()\n    asyncio.set_event_loop(loop)\n    return loop\n\n\ndef teardown_test_loop(loop: asyncio.AbstractEventLoop, fast: bool = False) -> None:\n    \"\"\"Teardown and cleanup an event_loop created by setup_test_loop.\"\"\"\n    closed = loop.is_closed()\n    if not closed:\n        loop.call_soon(loop.stop)\n        loop.run_forever()\n        loop.close()\n\n    if not fast:\n        gc.collect()\n\n    asyncio.set_event_loop(None)\n\n\ndef _create_app_mock() -> mock.MagicMock:\n    def get_dict(app: Any, key: str) -> Any:\n        return app.__app_dict[key]\n\n    def set_dict(app: Any, key: str, value: Any) -> None:\n        app.__app_dict[key] = value\n\n    app = mock.MagicMock(spec=Application)\n    app.__app_dict = {}\n    app.__getitem__ = get_dict\n    app.__setitem__ = set_dict\n\n    app.on_response_prepare = Signal(app)\n    app.on_response_prepare.freeze()\n    return app\n\n\ndef _create_transport(sslcontext: SSLContext | None = None) -> mock.Mock:\n    transport = mock.Mock()\n\n    def get_extra_info(key: str) -> SSLContext | None:\n        if key == \"sslcontext\":\n            return sslcontext\n        else:\n            return None\n\n    transport.get_extra_info.side_effect = get_extra_info\n    return transport\n\n\ndef make_mocked_request(\n    method: str,\n    path: str,\n    headers: LooseHeaders | None = None,\n    *,\n    match_info: dict[str, str] | None = None,\n    version: HttpVersion = HttpVersion(1, 1),\n    closing: bool = False,\n    app: Application | None = None,\n    writer: AbstractStreamWriter | None = None,\n    protocol: RequestHandler[Request] | None = None,\n    transport: asyncio.Transport | None = None,\n    payload: StreamReader = EMPTY_PAYLOAD,\n    sslcontext: SSLContext | None = None,\n    client_max_size: int = 1024**2,\n    loop: Any = ...,\n) -> Request:\n    \"\"\"Creates mocked web.Request testing purposes.\n\n    Useful in unit tests, when spinning full web server is overkill or\n    specific conditions and errors are hard to trigger.\n    \"\"\"\n    task = mock.Mock()\n    if loop is ...:\n        # no loop passed, try to get the current one if\n        # its is running as we need a real loop to create\n        # executor jobs to be able to do testing\n        # with a real executor\n        try:\n            loop = asyncio.get_running_loop()\n        except RuntimeError:\n            loop = mock.Mock()\n            loop.create_future.return_value = ()\n\n    if version < HttpVersion(1, 1):\n        closing = True\n\n    if headers:\n        headers = CIMultiDictProxy(CIMultiDict(headers))\n        raw_hdrs = tuple(\n            (k.encode(\"utf-8\"), v.encode(\"utf-8\")) for k, v in headers.items()\n        )\n    else:\n        headers = CIMultiDictProxy(CIMultiDict())\n        raw_hdrs = ()\n\n    chunked = \"chunked\" in headers.get(hdrs.TRANSFER_ENCODING, \"\").lower()\n\n    message = RawRequestMessage(\n        method,\n        path,\n        version,\n        headers,\n        raw_hdrs,\n        closing,\n        None,\n        False,\n        chunked,\n        URL(path),\n    )\n    if app is None:\n        app = _create_app_mock()\n\n    if transport is None:\n        transport = _create_transport(sslcontext)\n\n    if protocol is None:\n        protocol = mock.Mock()\n        protocol.max_field_size = 8190\n        protocol.max_line_length = 8190\n        protocol.max_headers = 128\n        protocol.transport = transport\n        type(protocol).peername = mock.PropertyMock(\n            return_value=transport.get_extra_info(\"peername\")\n        )\n        type(protocol).ssl_context = mock.PropertyMock(return_value=sslcontext)\n\n    if writer is None:\n        writer = mock.Mock()\n        writer.write_headers = mock.AsyncMock(return_value=None)\n        writer.write = mock.AsyncMock(return_value=None)\n        writer.write_eof = mock.AsyncMock(return_value=None)\n        writer.drain = mock.AsyncMock(return_value=None)\n        writer.transport = transport\n\n    protocol.transport = transport\n\n    req = Request(\n        message, payload, protocol, writer, task, loop, client_max_size=client_max_size\n    )\n\n    match_info = UrlMappingMatchInfo(\n        {} if match_info is None else match_info, mock.Mock()\n    )\n    match_info.add_app(app)\n    req._match_info = match_info\n\n    return req\n"
  },
  {
    "path": "aiohttp/tracing.py",
    "content": "from types import SimpleNamespace\nfrom typing import TYPE_CHECKING, Any, Generic, Protocol, TypeVar, overload\n\nfrom aiosignal import Signal\nfrom multidict import CIMultiDict\nfrom yarl import URL\n\nfrom .client_reqrep import ClientResponse\nfrom .helpers import frozen_dataclass_decorator\n\nif TYPE_CHECKING:\n    from .client import ClientSession\n\n\n__all__ = (\n    \"TraceConfig\",\n    \"TraceRequestStartParams\",\n    \"TraceRequestEndParams\",\n    \"TraceRequestExceptionParams\",\n    \"TraceConnectionQueuedStartParams\",\n    \"TraceConnectionQueuedEndParams\",\n    \"TraceConnectionCreateStartParams\",\n    \"TraceConnectionCreateEndParams\",\n    \"TraceConnectionReuseconnParams\",\n    \"TraceDnsResolveHostStartParams\",\n    \"TraceDnsResolveHostEndParams\",\n    \"TraceDnsCacheHitParams\",\n    \"TraceDnsCacheMissParams\",\n    \"TraceRequestRedirectParams\",\n    \"TraceRequestChunkSentParams\",\n    \"TraceResponseChunkReceivedParams\",\n    \"TraceRequestHeadersSentParams\",\n)\n\n_T = TypeVar(\"_T\", covariant=True)\n_ParamT_contra = TypeVar(\"_ParamT_contra\", contravariant=True)\n_TracingSignal = Signal[\"ClientSession\", _T, _ParamT_contra]\n\n\nclass _Factory(Protocol[_T]):\n    def __call__(self, **kwargs: Any) -> _T: ...\n\n\nclass TraceConfig(Generic[_T]):\n    \"\"\"First-class used to trace requests launched via ClientSession objects.\"\"\"\n\n    @overload\n    def __init__(self: \"TraceConfig[SimpleNamespace]\") -> None: ...\n    @overload\n    def __init__(self, trace_config_ctx_factory: _Factory[_T]) -> None: ...\n    def __init__(\n        self, trace_config_ctx_factory: _Factory[Any] = SimpleNamespace\n    ) -> None:\n        self._on_request_start: _TracingSignal[_T, TraceRequestStartParams] = Signal(\n            self\n        )\n        self._on_request_chunk_sent: _TracingSignal[_T, TraceRequestChunkSentParams] = (\n            Signal(self)\n        )\n        self._on_response_chunk_received: _TracingSignal[\n            _T, TraceResponseChunkReceivedParams\n        ] = Signal(self)\n        self._on_request_end: _TracingSignal[_T, TraceRequestEndParams] = Signal(self)\n        self._on_request_exception: _TracingSignal[_T, TraceRequestExceptionParams] = (\n            Signal(self)\n        )\n        self._on_request_redirect: _TracingSignal[_T, TraceRequestRedirectParams] = (\n            Signal(self)\n        )\n        self._on_connection_queued_start: _TracingSignal[\n            _T, TraceConnectionQueuedStartParams\n        ] = Signal(self)\n        self._on_connection_queued_end: _TracingSignal[\n            _T, TraceConnectionQueuedEndParams\n        ] = Signal(self)\n        self._on_connection_create_start: _TracingSignal[\n            _T, TraceConnectionCreateStartParams\n        ] = Signal(self)\n        self._on_connection_create_end: _TracingSignal[\n            _T, TraceConnectionCreateEndParams\n        ] = Signal(self)\n        self._on_connection_reuseconn: _TracingSignal[\n            _T, TraceConnectionReuseconnParams\n        ] = Signal(self)\n        self._on_dns_resolvehost_start: _TracingSignal[\n            _T, TraceDnsResolveHostStartParams\n        ] = Signal(self)\n        self._on_dns_resolvehost_end: _TracingSignal[\n            _T, TraceDnsResolveHostEndParams\n        ] = Signal(self)\n        self._on_dns_cache_hit: _TracingSignal[_T, TraceDnsCacheHitParams] = Signal(\n            self\n        )\n        self._on_dns_cache_miss: _TracingSignal[_T, TraceDnsCacheMissParams] = Signal(\n            self\n        )\n        self._on_request_headers_sent: _TracingSignal[\n            _T, TraceRequestHeadersSentParams\n        ] = Signal(self)\n\n        self._trace_config_ctx_factory: _Factory[_T] = trace_config_ctx_factory\n\n    def trace_config_ctx(self, trace_request_ctx: Any = None) -> _T:\n        \"\"\"Return a new trace_config_ctx instance\"\"\"\n        return self._trace_config_ctx_factory(trace_request_ctx=trace_request_ctx)\n\n    def freeze(self) -> None:\n        self._on_request_start.freeze()\n        self._on_request_chunk_sent.freeze()\n        self._on_response_chunk_received.freeze()\n        self._on_request_end.freeze()\n        self._on_request_exception.freeze()\n        self._on_request_redirect.freeze()\n        self._on_connection_queued_start.freeze()\n        self._on_connection_queued_end.freeze()\n        self._on_connection_create_start.freeze()\n        self._on_connection_create_end.freeze()\n        self._on_connection_reuseconn.freeze()\n        self._on_dns_resolvehost_start.freeze()\n        self._on_dns_resolvehost_end.freeze()\n        self._on_dns_cache_hit.freeze()\n        self._on_dns_cache_miss.freeze()\n        self._on_request_headers_sent.freeze()\n\n    @property\n    def on_request_start(self) -> \"_TracingSignal[_T, TraceRequestStartParams]\":\n        return self._on_request_start\n\n    @property\n    def on_request_chunk_sent(\n        self,\n    ) -> \"_TracingSignal[_T, TraceRequestChunkSentParams]\":\n        return self._on_request_chunk_sent\n\n    @property\n    def on_response_chunk_received(\n        self,\n    ) -> \"_TracingSignal[_T, TraceResponseChunkReceivedParams]\":\n        return self._on_response_chunk_received\n\n    @property\n    def on_request_end(self) -> \"_TracingSignal[_T, TraceRequestEndParams]\":\n        return self._on_request_end\n\n    @property\n    def on_request_exception(\n        self,\n    ) -> \"_TracingSignal[_T, TraceRequestExceptionParams]\":\n        return self._on_request_exception\n\n    @property\n    def on_request_redirect(\n        self,\n    ) -> \"_TracingSignal[_T, TraceRequestRedirectParams]\":\n        return self._on_request_redirect\n\n    @property\n    def on_connection_queued_start(\n        self,\n    ) -> \"_TracingSignal[_T, TraceConnectionQueuedStartParams]\":\n        return self._on_connection_queued_start\n\n    @property\n    def on_connection_queued_end(\n        self,\n    ) -> \"_TracingSignal[_T, TraceConnectionQueuedEndParams]\":\n        return self._on_connection_queued_end\n\n    @property\n    def on_connection_create_start(\n        self,\n    ) -> \"_TracingSignal[_T, TraceConnectionCreateStartParams]\":\n        return self._on_connection_create_start\n\n    @property\n    def on_connection_create_end(\n        self,\n    ) -> \"_TracingSignal[_T, TraceConnectionCreateEndParams]\":\n        return self._on_connection_create_end\n\n    @property\n    def on_connection_reuseconn(\n        self,\n    ) -> \"_TracingSignal[_T, TraceConnectionReuseconnParams]\":\n        return self._on_connection_reuseconn\n\n    @property\n    def on_dns_resolvehost_start(\n        self,\n    ) -> \"_TracingSignal[_T, TraceDnsResolveHostStartParams]\":\n        return self._on_dns_resolvehost_start\n\n    @property\n    def on_dns_resolvehost_end(\n        self,\n    ) -> \"_TracingSignal[_T, TraceDnsResolveHostEndParams]\":\n        return self._on_dns_resolvehost_end\n\n    @property\n    def on_dns_cache_hit(self) -> \"_TracingSignal[_T, TraceDnsCacheHitParams]\":\n        return self._on_dns_cache_hit\n\n    @property\n    def on_dns_cache_miss(self) -> \"_TracingSignal[_T, TraceDnsCacheMissParams]\":\n        return self._on_dns_cache_miss\n\n    @property\n    def on_request_headers_sent(\n        self,\n    ) -> \"_TracingSignal[_T, TraceRequestHeadersSentParams]\":\n        return self._on_request_headers_sent\n\n\n@frozen_dataclass_decorator\nclass TraceRequestStartParams:\n    \"\"\"Parameters sent by the `on_request_start` signal\"\"\"\n\n    method: str\n    url: URL\n    headers: \"CIMultiDict[str]\"\n\n\n@frozen_dataclass_decorator\nclass TraceRequestChunkSentParams:\n    \"\"\"Parameters sent by the `on_request_chunk_sent` signal\"\"\"\n\n    method: str\n    url: URL\n    chunk: bytes\n\n\n@frozen_dataclass_decorator\nclass TraceResponseChunkReceivedParams:\n    \"\"\"Parameters sent by the `on_response_chunk_received` signal\"\"\"\n\n    method: str\n    url: URL\n    chunk: bytes\n\n\n@frozen_dataclass_decorator\nclass TraceRequestEndParams:\n    \"\"\"Parameters sent by the `on_request_end` signal\"\"\"\n\n    method: str\n    url: URL\n    headers: \"CIMultiDict[str]\"\n    response: ClientResponse\n\n\n@frozen_dataclass_decorator\nclass TraceRequestExceptionParams:\n    \"\"\"Parameters sent by the `on_request_exception` signal\"\"\"\n\n    method: str\n    url: URL\n    headers: \"CIMultiDict[str]\"\n    exception: BaseException\n\n\n@frozen_dataclass_decorator\nclass TraceRequestRedirectParams:\n    \"\"\"Parameters sent by the `on_request_redirect` signal\"\"\"\n\n    method: str\n    url: URL\n    headers: \"CIMultiDict[str]\"\n    response: ClientResponse\n\n\n@frozen_dataclass_decorator\nclass TraceConnectionQueuedStartParams:\n    \"\"\"Parameters sent by the `on_connection_queued_start` signal\"\"\"\n\n\n@frozen_dataclass_decorator\nclass TraceConnectionQueuedEndParams:\n    \"\"\"Parameters sent by the `on_connection_queued_end` signal\"\"\"\n\n\n@frozen_dataclass_decorator\nclass TraceConnectionCreateStartParams:\n    \"\"\"Parameters sent by the `on_connection_create_start` signal\"\"\"\n\n\n@frozen_dataclass_decorator\nclass TraceConnectionCreateEndParams:\n    \"\"\"Parameters sent by the `on_connection_create_end` signal\"\"\"\n\n\n@frozen_dataclass_decorator\nclass TraceConnectionReuseconnParams:\n    \"\"\"Parameters sent by the `on_connection_reuseconn` signal\"\"\"\n\n\n@frozen_dataclass_decorator\nclass TraceDnsResolveHostStartParams:\n    \"\"\"Parameters sent by the `on_dns_resolvehost_start` signal\"\"\"\n\n    host: str\n\n\n@frozen_dataclass_decorator\nclass TraceDnsResolveHostEndParams:\n    \"\"\"Parameters sent by the `on_dns_resolvehost_end` signal\"\"\"\n\n    host: str\n\n\n@frozen_dataclass_decorator\nclass TraceDnsCacheHitParams:\n    \"\"\"Parameters sent by the `on_dns_cache_hit` signal\"\"\"\n\n    host: str\n\n\n@frozen_dataclass_decorator\nclass TraceDnsCacheMissParams:\n    \"\"\"Parameters sent by the `on_dns_cache_miss` signal\"\"\"\n\n    host: str\n\n\n@frozen_dataclass_decorator\nclass TraceRequestHeadersSentParams:\n    \"\"\"Parameters sent by the `on_request_headers_sent` signal\"\"\"\n\n    method: str\n    url: URL\n    headers: \"CIMultiDict[str]\"\n\n\nclass Trace:\n    \"\"\"Internal dependency holder class.\n\n    Used to keep together the main dependencies used\n    at the moment of send a signal.\n    \"\"\"\n\n    def __init__(\n        self,\n        session: \"ClientSession\",\n        trace_config: TraceConfig[object],\n        trace_config_ctx: Any,\n    ) -> None:\n        self._trace_config = trace_config\n        self._trace_config_ctx = trace_config_ctx\n        self._session = session\n\n    async def send_request_start(\n        self, method: str, url: URL, headers: \"CIMultiDict[str]\"\n    ) -> None:\n        return await self._trace_config.on_request_start.send(\n            self._session,\n            self._trace_config_ctx,\n            TraceRequestStartParams(method, url, headers),\n        )\n\n    async def send_request_chunk_sent(\n        self, method: str, url: URL, chunk: bytes\n    ) -> None:\n        return await self._trace_config.on_request_chunk_sent.send(\n            self._session,\n            self._trace_config_ctx,\n            TraceRequestChunkSentParams(method, url, chunk),\n        )\n\n    async def send_response_chunk_received(\n        self, method: str, url: URL, chunk: bytes\n    ) -> None:\n        return await self._trace_config.on_response_chunk_received.send(\n            self._session,\n            self._trace_config_ctx,\n            TraceResponseChunkReceivedParams(method, url, chunk),\n        )\n\n    async def send_request_end(\n        self,\n        method: str,\n        url: URL,\n        headers: \"CIMultiDict[str]\",\n        response: ClientResponse,\n    ) -> None:\n        return await self._trace_config.on_request_end.send(\n            self._session,\n            self._trace_config_ctx,\n            TraceRequestEndParams(method, url, headers, response),\n        )\n\n    async def send_request_exception(\n        self,\n        method: str,\n        url: URL,\n        headers: \"CIMultiDict[str]\",\n        exception: BaseException,\n    ) -> None:\n        return await self._trace_config.on_request_exception.send(\n            self._session,\n            self._trace_config_ctx,\n            TraceRequestExceptionParams(method, url, headers, exception),\n        )\n\n    async def send_request_redirect(\n        self,\n        method: str,\n        url: URL,\n        headers: \"CIMultiDict[str]\",\n        response: ClientResponse,\n    ) -> None:\n        return await self._trace_config._on_request_redirect.send(\n            self._session,\n            self._trace_config_ctx,\n            TraceRequestRedirectParams(method, url, headers, response),\n        )\n\n    async def send_connection_queued_start(self) -> None:\n        return await self._trace_config.on_connection_queued_start.send(\n            self._session, self._trace_config_ctx, TraceConnectionQueuedStartParams()\n        )\n\n    async def send_connection_queued_end(self) -> None:\n        return await self._trace_config.on_connection_queued_end.send(\n            self._session, self._trace_config_ctx, TraceConnectionQueuedEndParams()\n        )\n\n    async def send_connection_create_start(self) -> None:\n        return await self._trace_config.on_connection_create_start.send(\n            self._session, self._trace_config_ctx, TraceConnectionCreateStartParams()\n        )\n\n    async def send_connection_create_end(self) -> None:\n        return await self._trace_config.on_connection_create_end.send(\n            self._session, self._trace_config_ctx, TraceConnectionCreateEndParams()\n        )\n\n    async def send_connection_reuseconn(self) -> None:\n        return await self._trace_config.on_connection_reuseconn.send(\n            self._session, self._trace_config_ctx, TraceConnectionReuseconnParams()\n        )\n\n    async def send_dns_resolvehost_start(self, host: str) -> None:\n        return await self._trace_config.on_dns_resolvehost_start.send(\n            self._session, self._trace_config_ctx, TraceDnsResolveHostStartParams(host)\n        )\n\n    async def send_dns_resolvehost_end(self, host: str) -> None:\n        return await self._trace_config.on_dns_resolvehost_end.send(\n            self._session, self._trace_config_ctx, TraceDnsResolveHostEndParams(host)\n        )\n\n    async def send_dns_cache_hit(self, host: str) -> None:\n        return await self._trace_config.on_dns_cache_hit.send(\n            self._session, self._trace_config_ctx, TraceDnsCacheHitParams(host)\n        )\n\n    async def send_dns_cache_miss(self, host: str) -> None:\n        return await self._trace_config.on_dns_cache_miss.send(\n            self._session, self._trace_config_ctx, TraceDnsCacheMissParams(host)\n        )\n\n    async def send_request_headers(\n        self, method: str, url: URL, headers: \"CIMultiDict[str]\"\n    ) -> None:\n        return await self._trace_config._on_request_headers_sent.send(\n            self._session,\n            self._trace_config_ctx,\n            TraceRequestHeadersSentParams(method, url, headers),\n        )\n"
  },
  {
    "path": "aiohttp/typedefs.py",
    "content": "import json\nimport os\nfrom collections.abc import Awaitable, Callable, Iterable, Mapping\nfrom http.cookies import BaseCookie, Morsel\nfrom typing import TYPE_CHECKING, Any, Protocol\n\nfrom multidict import CIMultiDict, CIMultiDictProxy, istr\nfrom yarl import URL\n\nDEFAULT_JSON_ENCODER = json.dumps\nDEFAULT_JSON_DECODER = json.loads\n\nif TYPE_CHECKING:\n    from .web import Request, StreamResponse\n\nByteish = bytes | bytearray | memoryview\nJSONEncoder = Callable[[Any], str]\nJSONBytesEncoder = Callable[[Any], bytes]\nJSONDecoder = Callable[[str], Any]\nLooseHeaders = (\n    Mapping[str, str]\n    | Mapping[istr, str]\n    | CIMultiDict[str]\n    | CIMultiDictProxy[str]\n    | Iterable[tuple[str | istr, str]]\n)\nRawHeaders = tuple[tuple[bytes, bytes], ...]\nStrOrURL = str | URL\n\nLooseCookiesMappings = Mapping[str, str | BaseCookie[str] | Morsel[Any]]\nLooseCookiesIterables = Iterable[tuple[str, str | BaseCookie[str] | Morsel[Any]]]\nLooseCookies = LooseCookiesMappings | LooseCookiesIterables | BaseCookie[str]\n\nHandler = Callable[[\"Request\"], Awaitable[\"StreamResponse\"]]\n\n\nclass Middleware(Protocol):\n    def __call__(\n        self, request: \"Request\", handler: Handler\n    ) -> Awaitable[\"StreamResponse\"]: ...\n\n\nPathLike = str | os.PathLike[str]\n"
  },
  {
    "path": "aiohttp/web.py",
    "content": "import asyncio\nimport logging\nimport os\nimport socket\nimport sys\nimport warnings\nfrom argparse import ArgumentParser\nfrom collections.abc import Awaitable, Callable, Iterable, Iterable as TypingIterable\nfrom contextlib import suppress\nfrom importlib import import_module\nfrom typing import Any, cast\n\nfrom .abc import AbstractAccessLogger\nfrom .helpers import AppKey, RequestKey, ResponseKey\nfrom .log import access_logger\nfrom .typedefs import PathLike\nfrom .web_app import Application, CleanupError\nfrom .web_exceptions import (\n    HTTPAccepted,\n    HTTPBadGateway,\n    HTTPBadRequest,\n    HTTPClientError,\n    HTTPConflict,\n    HTTPCreated,\n    HTTPError,\n    HTTPException,\n    HTTPExpectationFailed,\n    HTTPFailedDependency,\n    HTTPForbidden,\n    HTTPFound,\n    HTTPGatewayTimeout,\n    HTTPGone,\n    HTTPInsufficientStorage,\n    HTTPInternalServerError,\n    HTTPLengthRequired,\n    HTTPMethodNotAllowed,\n    HTTPMisdirectedRequest,\n    HTTPMove,\n    HTTPMovedPermanently,\n    HTTPMultipleChoices,\n    HTTPNetworkAuthenticationRequired,\n    HTTPNoContent,\n    HTTPNonAuthoritativeInformation,\n    HTTPNotAcceptable,\n    HTTPNotExtended,\n    HTTPNotFound,\n    HTTPNotImplemented,\n    HTTPNotModified,\n    HTTPOk,\n    HTTPPartialContent,\n    HTTPPaymentRequired,\n    HTTPPermanentRedirect,\n    HTTPPreconditionFailed,\n    HTTPPreconditionRequired,\n    HTTPProxyAuthenticationRequired,\n    HTTPRedirection,\n    HTTPRequestEntityTooLarge,\n    HTTPRequestHeaderFieldsTooLarge,\n    HTTPRequestRangeNotSatisfiable,\n    HTTPRequestTimeout,\n    HTTPRequestURITooLong,\n    HTTPResetContent,\n    HTTPSeeOther,\n    HTTPServerError,\n    HTTPServiceUnavailable,\n    HTTPSuccessful,\n    HTTPTemporaryRedirect,\n    HTTPTooManyRequests,\n    HTTPUnauthorized,\n    HTTPUnavailableForLegalReasons,\n    HTTPUnprocessableEntity,\n    HTTPUnsupportedMediaType,\n    HTTPUpgradeRequired,\n    HTTPUseProxy,\n    HTTPVariantAlsoNegotiates,\n    HTTPVersionNotSupported,\n    NotAppKeyWarning,\n)\nfrom .web_fileresponse import FileResponse\nfrom .web_log import AccessLogger\nfrom .web_middlewares import middleware, normalize_path_middleware\nfrom .web_protocol import PayloadAccessError, RequestHandler, RequestPayloadError\nfrom .web_request import BaseRequest, FileField, Request\nfrom .web_response import (\n    ContentCoding,\n    Response,\n    StreamResponse,\n    json_bytes_response,\n    json_response,\n)\nfrom .web_routedef import (\n    AbstractRouteDef,\n    RouteDef,\n    RouteTableDef,\n    StaticDef,\n    delete,\n    get,\n    head,\n    options,\n    patch,\n    post,\n    put,\n    route,\n    static,\n    view,\n)\nfrom .web_runner import (\n    AppRunner,\n    BaseRunner,\n    BaseSite,\n    GracefulExit,\n    NamedPipeSite,\n    ServerRunner,\n    SockSite,\n    TCPSite,\n    UnixSite,\n)\nfrom .web_server import Server\nfrom .web_urldispatcher import (\n    AbstractResource,\n    AbstractRoute,\n    DynamicResource,\n    PlainResource,\n    PrefixedSubAppResource,\n    Resource,\n    ResourceRoute,\n    StaticResource,\n    UrlDispatcher,\n    UrlMappingMatchInfo,\n    View,\n)\nfrom .web_ws import WebSocketReady, WebSocketResponse, WSMsgType\n\n__all__ = (\n    # web_app\n    \"AppKey\",\n    \"Application\",\n    \"CleanupError\",\n    # web_exceptions\n    \"NotAppKeyWarning\",\n    \"HTTPAccepted\",\n    \"HTTPBadGateway\",\n    \"HTTPBadRequest\",\n    \"HTTPClientError\",\n    \"HTTPConflict\",\n    \"HTTPCreated\",\n    \"HTTPError\",\n    \"HTTPException\",\n    \"HTTPExpectationFailed\",\n    \"HTTPFailedDependency\",\n    \"HTTPForbidden\",\n    \"HTTPFound\",\n    \"HTTPGatewayTimeout\",\n    \"HTTPGone\",\n    \"HTTPInsufficientStorage\",\n    \"HTTPInternalServerError\",\n    \"HTTPLengthRequired\",\n    \"HTTPMethodNotAllowed\",\n    \"HTTPMisdirectedRequest\",\n    \"HTTPMove\",\n    \"HTTPMovedPermanently\",\n    \"HTTPMultipleChoices\",\n    \"HTTPNetworkAuthenticationRequired\",\n    \"HTTPNoContent\",\n    \"HTTPNonAuthoritativeInformation\",\n    \"HTTPNotAcceptable\",\n    \"HTTPNotExtended\",\n    \"HTTPNotFound\",\n    \"HTTPNotImplemented\",\n    \"HTTPNotModified\",\n    \"HTTPOk\",\n    \"HTTPPartialContent\",\n    \"HTTPPaymentRequired\",\n    \"HTTPPermanentRedirect\",\n    \"HTTPPreconditionFailed\",\n    \"HTTPPreconditionRequired\",\n    \"HTTPProxyAuthenticationRequired\",\n    \"HTTPRedirection\",\n    \"HTTPRequestEntityTooLarge\",\n    \"HTTPRequestHeaderFieldsTooLarge\",\n    \"HTTPRequestRangeNotSatisfiable\",\n    \"HTTPRequestTimeout\",\n    \"HTTPRequestURITooLong\",\n    \"HTTPResetContent\",\n    \"HTTPSeeOther\",\n    \"HTTPServerError\",\n    \"HTTPServiceUnavailable\",\n    \"HTTPSuccessful\",\n    \"HTTPTemporaryRedirect\",\n    \"HTTPTooManyRequests\",\n    \"HTTPUnauthorized\",\n    \"HTTPUnavailableForLegalReasons\",\n    \"HTTPUnprocessableEntity\",\n    \"HTTPUnsupportedMediaType\",\n    \"HTTPUpgradeRequired\",\n    \"HTTPUseProxy\",\n    \"HTTPVariantAlsoNegotiates\",\n    \"HTTPVersionNotSupported\",\n    # web_fileresponse\n    \"FileResponse\",\n    # web_middlewares\n    \"middleware\",\n    \"normalize_path_middleware\",\n    # web_protocol\n    \"PayloadAccessError\",\n    \"RequestHandler\",\n    \"RequestPayloadError\",\n    # web_request\n    \"BaseRequest\",\n    \"FileField\",\n    \"Request\",\n    \"RequestKey\",\n    # web_response\n    \"ContentCoding\",\n    \"Response\",\n    \"StreamResponse\",\n    \"json_bytes_response\",\n    \"json_response\",\n    \"ResponseKey\",\n    # web_routedef\n    \"AbstractRouteDef\",\n    \"RouteDef\",\n    \"RouteTableDef\",\n    \"StaticDef\",\n    \"delete\",\n    \"get\",\n    \"head\",\n    \"options\",\n    \"patch\",\n    \"post\",\n    \"put\",\n    \"route\",\n    \"static\",\n    \"view\",\n    # web_runner\n    \"AppRunner\",\n    \"BaseRunner\",\n    \"BaseSite\",\n    \"GracefulExit\",\n    \"ServerRunner\",\n    \"SockSite\",\n    \"TCPSite\",\n    \"UnixSite\",\n    \"NamedPipeSite\",\n    # web_server\n    \"Server\",\n    # web_urldispatcher\n    \"AbstractResource\",\n    \"AbstractRoute\",\n    \"DynamicResource\",\n    \"PlainResource\",\n    \"PrefixedSubAppResource\",\n    \"Resource\",\n    \"ResourceRoute\",\n    \"StaticResource\",\n    \"UrlDispatcher\",\n    \"UrlMappingMatchInfo\",\n    \"View\",\n    # web_ws\n    \"WebSocketReady\",\n    \"WebSocketResponse\",\n    \"WSMsgType\",\n    # web\n    \"run_app\",\n)\n\n\ntry:\n    from ssl import SSLContext\nexcept ImportError:  # pragma: no cover\n    SSLContext = object  # type: ignore[misc,assignment]\n\n# Only display warning when using -Wdefault, -We, -X dev or similar.\nwarnings.filterwarnings(\"ignore\", category=NotAppKeyWarning, append=True)\n\nHostSequence = TypingIterable[str]\n\n\nasync def _run_app(\n    app: Application | Awaitable[Application],\n    *,\n    host: str | HostSequence | None = None,\n    port: int | None = None,\n    path: PathLike | TypingIterable[PathLike] | None = None,\n    sock: socket.socket | TypingIterable[socket.socket] | None = None,\n    ssl_context: SSLContext | None = None,\n    print: Callable[..., None] | None = print,\n    backlog: int = 128,\n    reuse_address: bool | None = None,\n    reuse_port: bool | None = None,\n    **kwargs: Any,  # TODO(PY311): Use Unpack\n) -> None:\n    # An internal function to actually do all dirty job for application running\n    if asyncio.iscoroutine(app):\n        app = await app\n\n    app = cast(Application, app)\n\n    runner = AppRunner(app, **kwargs)\n\n    await runner.setup()\n\n    sites: list[BaseSite] = []\n\n    try:\n        if host is not None:\n            if isinstance(host, str):\n                sites.append(\n                    TCPSite(\n                        runner,\n                        host,\n                        port,\n                        ssl_context=ssl_context,\n                        backlog=backlog,\n                        reuse_address=reuse_address,\n                        reuse_port=reuse_port,\n                    )\n                )\n            else:\n                for h in host:\n                    sites.append(\n                        TCPSite(\n                            runner,\n                            h,\n                            port,\n                            ssl_context=ssl_context,\n                            backlog=backlog,\n                            reuse_address=reuse_address,\n                            reuse_port=reuse_port,\n                        )\n                    )\n        elif path is None and sock is None or port is not None:\n            sites.append(\n                TCPSite(\n                    runner,\n                    port=port,\n                    ssl_context=ssl_context,\n                    backlog=backlog,\n                    reuse_address=reuse_address,\n                    reuse_port=reuse_port,\n                )\n            )\n\n        if path is not None:\n            if isinstance(path, (str, os.PathLike)):\n                sites.append(\n                    UnixSite(\n                        runner,\n                        path,\n                        ssl_context=ssl_context,\n                        backlog=backlog,\n                    )\n                )\n            else:\n                for p in path:\n                    sites.append(\n                        UnixSite(\n                            runner,\n                            p,\n                            ssl_context=ssl_context,\n                            backlog=backlog,\n                        )\n                    )\n\n        if sock is not None:\n            if not isinstance(sock, Iterable):\n                sites.append(\n                    SockSite(\n                        runner,\n                        sock,\n                        ssl_context=ssl_context,\n                        backlog=backlog,\n                    )\n                )\n            else:\n                for s in sock:\n                    sites.append(\n                        SockSite(\n                            runner,\n                            s,\n                            ssl_context=ssl_context,\n                            backlog=backlog,\n                        )\n                    )\n        for site in sites:\n            await site.start()\n\n        if print:  # pragma: no branch\n            names = sorted(str(s.name) for s in runner.sites)\n            print(\n                \"======== Running on {} ========\\n\"\n                \"(Press CTRL+C to quit)\".format(\", \".join(names))\n            )\n\n        # sleep forever by 1 hour intervals,\n        while True:\n            await asyncio.sleep(3600)\n    finally:\n        await runner.cleanup()\n\n\ndef _cancel_tasks(\n    to_cancel: set[\"asyncio.Task[Any]\"], loop: asyncio.AbstractEventLoop\n) -> None:\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\n\ndef run_app(\n    app: Application | Awaitable[Application],\n    *,\n    debug: bool = False,\n    host: str | HostSequence | None = None,\n    port: int | None = None,\n    path: PathLike | TypingIterable[PathLike] | None = None,\n    sock: socket.socket | TypingIterable[socket.socket] | None = None,\n    shutdown_timeout: float = 60.0,\n    keepalive_timeout: float = 75.0,\n    ssl_context: SSLContext | None = None,\n    print: Callable[..., None] | None = print,\n    backlog: int = 128,\n    access_log_class: type[AbstractAccessLogger] = AccessLogger,\n    access_log_format: str = AccessLogger.LOG_FORMAT,\n    access_log: logging.Logger | None = access_logger,\n    handle_signals: bool = True,\n    reuse_address: bool | None = None,\n    reuse_port: bool | None = None,\n    handler_cancellation: bool = False,\n    loop: asyncio.AbstractEventLoop | None = None,\n    **kwargs: Any,\n) -> None:\n    \"\"\"Run an app locally\"\"\"\n    if loop is None:\n        loop = asyncio.new_event_loop()\n    loop.set_debug(debug)\n\n    # Configure if and only if in debugging mode and using the default logger\n    if loop.get_debug() and access_log and access_log.name == \"aiohttp.access\":\n        if access_log.level == logging.NOTSET:\n            access_log.setLevel(logging.DEBUG)\n        if not access_log.hasHandlers():\n            access_log.addHandler(logging.StreamHandler())\n\n    main_task = loop.create_task(\n        _run_app(\n            app,\n            host=host,\n            port=port,\n            path=path,\n            sock=sock,\n            shutdown_timeout=shutdown_timeout,\n            keepalive_timeout=keepalive_timeout,\n            ssl_context=ssl_context,\n            print=print,\n            backlog=backlog,\n            access_log_class=access_log_class,\n            access_log_format=access_log_format,\n            access_log=access_log,\n            handle_signals=handle_signals,\n            reuse_address=reuse_address,\n            reuse_port=reuse_port,\n            handler_cancellation=handler_cancellation,\n            **kwargs,\n        )\n    )\n\n    try:\n        asyncio.set_event_loop(loop)\n        loop.run_until_complete(main_task)\n    except (GracefulExit, KeyboardInterrupt):\n        pass\n    finally:\n        try:\n            main_task.cancel()\n            with suppress(asyncio.CancelledError):\n                loop.run_until_complete(main_task)\n        finally:\n            _cancel_tasks(asyncio.all_tasks(loop), loop)\n            loop.run_until_complete(loop.shutdown_asyncgens())\n            loop.close()\n            asyncio.set_event_loop(None)\n\n\ndef main(argv: list[str]) -> None:\n    arg_parser = ArgumentParser(\n        description=\"aiohttp.web Application server\", prog=\"aiohttp.web\"\n    )\n    arg_parser.add_argument(\n        \"entry_func\",\n        help=(\n            \"Callable returning the `aiohttp.web.Application` instance to \"\n            \"run. Should be specified in the 'module:function' syntax.\"\n        ),\n        metavar=\"entry-func\",\n    )\n    arg_parser.add_argument(\n        \"-H\",\n        \"--hostname\",\n        help=\"TCP/IP hostname to serve on (default: localhost)\",\n        default=None,\n    )\n    arg_parser.add_argument(\n        \"-P\",\n        \"--port\",\n        help=\"TCP/IP port to serve on (default: %(default)r)\",\n        type=int,\n        default=8080,\n    )\n    arg_parser.add_argument(\n        \"-U\",\n        \"--path\",\n        help=\"Unix file system path to serve on. Can be combined with hostname \"\n        \"to serve on both Unix and TCP.\",\n    )\n    args, extra_argv = arg_parser.parse_known_args(argv)\n\n    # Import logic\n    mod_str, _, func_str = args.entry_func.partition(\":\")\n    if not func_str or not mod_str:\n        arg_parser.error(\"'entry-func' not in 'module:function' syntax\")\n    if mod_str.startswith(\".\"):\n        arg_parser.error(\"relative module names not supported\")\n    try:\n        module = import_module(mod_str)\n    except ImportError as ex:\n        arg_parser.error(f\"unable to import {mod_str}: {ex}\")\n    try:\n        func = getattr(module, func_str)\n    except AttributeError:\n        arg_parser.error(f\"module {mod_str!r} has no attribute {func_str!r}\")\n\n    # Compatibility logic\n    if args.path is not None and not hasattr(socket, \"AF_UNIX\"):\n        arg_parser.error(\n            \"file system paths not supported by your operating environment\"\n        )\n\n    logging.basicConfig(level=logging.DEBUG)\n\n    if args.path and args.hostname is None:\n        host = port = None\n    else:\n        host = args.hostname or \"localhost\"\n        port = args.port\n\n    app = func(extra_argv)\n    run_app(app, host=host, port=port, path=args.path)\n    arg_parser.exit(message=\"Stopped\\n\")\n\n\nif __name__ == \"__main__\":  # pragma: no branch\n    main(sys.argv[1:])  # pragma: no cover\n"
  },
  {
    "path": "aiohttp/web_app.py",
    "content": "import asyncio\nimport logging\nimport warnings\nfrom collections.abc import (\n    AsyncIterator,\n    Awaitable,\n    Callable,\n    Iterable,\n    Iterator,\n    Mapping,\n    MutableMapping,\n    Sequence,\n)\nfrom contextlib import AbstractAsyncContextManager, asynccontextmanager\nfrom functools import lru_cache, partial, update_wrapper\nfrom typing import Any, TypeVar, cast, final, overload\n\nfrom aiosignal import Signal\nfrom frozenlist import FrozenList\n\nfrom . import hdrs\nfrom .helpers import AppKey\nfrom .log import web_logger\nfrom .typedefs import Handler, Middleware\nfrom .web_exceptions import NotAppKeyWarning\nfrom .web_middlewares import _fix_request_current_app\nfrom .web_request import Request\nfrom .web_response import StreamResponse\nfrom .web_routedef import AbstractRouteDef\nfrom .web_urldispatcher import (\n    AbstractResource,\n    AbstractRoute,\n    Domain,\n    MaskDomain,\n    MatchedSubAppResource,\n    PrefixedSubAppResource,\n    SystemRoute,\n    UrlDispatcher,\n)\n\n__all__ = (\"Application\", \"CleanupError\")\n\n_AppSignal = Signal[\"Application\"]\n_RespPrepareSignal = Signal[Request, StreamResponse]\n_Middlewares = FrozenList[Middleware]\n_MiddlewaresHandlers = Sequence[Middleware]\n_Subapps = list[\"Application\"]\n\n_T = TypeVar(\"_T\")\n_U = TypeVar(\"_U\")\n_Resource = TypeVar(\"_Resource\", bound=AbstractResource)\n\n\ndef _build_middlewares(\n    handler: Handler, apps: tuple[\"Application\", ...]\n) -> Callable[[Request], Awaitable[StreamResponse]]:\n    \"\"\"Apply middlewares to handler.\"\"\"\n    # The slice is to reverse the order of the apps\n    # so they are applied in the order they were added\n    for app in apps[::-1]:\n        assert app.pre_frozen, \"middleware handlers are not ready\"\n        for m in app._middlewares_handlers:\n            handler = update_wrapper(partial(m, handler=handler), handler)\n    return handler\n\n\n_cached_build_middleware = lru_cache(maxsize=1024)(_build_middlewares)\n\n\n@final\nclass Application(MutableMapping[str | AppKey[Any], Any]):\n    __slots__ = (\n        \"logger\",\n        \"_router\",\n        \"_loop\",\n        \"_handler_args\",\n        \"_middlewares\",\n        \"_middlewares_handlers\",\n        \"_run_middlewares\",\n        \"_state\",\n        \"_frozen\",\n        \"_pre_frozen\",\n        \"_subapps\",\n        \"_on_response_prepare\",\n        \"_on_startup\",\n        \"_on_shutdown\",\n        \"_on_cleanup\",\n        \"_client_max_size\",\n        \"_cleanup_ctx\",\n    )\n\n    def __init__(\n        self,\n        *,\n        logger: logging.Logger = web_logger,\n        middlewares: Iterable[Middleware] = (),\n        handler_args: Mapping[str, Any] | None = None,\n        client_max_size: int = 1024**2,\n        debug: Any = ...,  # mypy doesn't support ellipsis\n    ) -> None:\n        if debug is not ...:\n            warnings.warn(\n                \"debug argument is no-op since 4.0 and scheduled for removal in 5.0\",\n                DeprecationWarning,\n                stacklevel=2,\n            )\n        self._router = UrlDispatcher()\n        self._handler_args = handler_args\n        self.logger = logger\n\n        self._middlewares: _Middlewares = FrozenList(middlewares)\n\n        # initialized on freezing\n        self._middlewares_handlers: _MiddlewaresHandlers = tuple()\n        # initialized on freezing\n        self._run_middlewares: bool | None = None\n\n        self._state: dict[AppKey[Any] | str, object] = {}\n        self._frozen = False\n        self._pre_frozen = False\n        self._subapps: _Subapps = []\n\n        self._on_response_prepare: _RespPrepareSignal = Signal(self)\n        self._on_startup: _AppSignal = Signal(self)\n        self._on_shutdown: _AppSignal = Signal(self)\n        self._on_cleanup: _AppSignal = Signal(self)\n        self._cleanup_ctx = CleanupContext()\n        self._on_startup.append(self._cleanup_ctx._on_startup)\n        self._on_cleanup.append(self._cleanup_ctx._on_cleanup)\n        self._client_max_size = client_max_size\n\n    def __init_subclass__(cls: type[\"Application\"]) -> None:\n        raise TypeError(\n            f\"Inheritance class {cls.__name__} from web.Application is forbidden\"\n        )\n\n    # MutableMapping API\n\n    def __eq__(self, other: object) -> bool:\n        return self is other\n\n    @overload  # type: ignore[override]\n    def __getitem__(self, key: AppKey[_T]) -> _T: ...\n\n    @overload\n    def __getitem__(self, key: str) -> Any: ...\n\n    def __getitem__(self, key: str | AppKey[_T]) -> Any:\n        return self._state[key]\n\n    def _check_frozen(self) -> None:\n        if self._frozen:\n            raise RuntimeError(\n                \"Changing state of started or joined application is forbidden\"\n            )\n\n    @overload  # type: ignore[override]\n    def __setitem__(self, key: AppKey[_T], value: _T) -> None: ...\n\n    @overload\n    def __setitem__(self, key: str, value: Any) -> None: ...\n\n    def __setitem__(self, key: str | AppKey[_T], value: Any) -> None:\n        self._check_frozen()\n        if not isinstance(key, AppKey):\n            warnings.warn(\n                \"It is recommended to use web.AppKey instances for keys.\\n\"\n                + \"https://docs.aiohttp.org/en/stable/web_advanced.html\"\n                + \"#application-s-config\",\n                category=NotAppKeyWarning,\n                stacklevel=2,\n            )\n        self._state[key] = value\n\n    def __delitem__(self, key: str | AppKey[_T]) -> None:\n        self._check_frozen()\n        del self._state[key]\n\n    def __len__(self) -> int:\n        return len(self._state)\n\n    def __iter__(self) -> Iterator[str | AppKey[Any]]:\n        return iter(self._state)\n\n    def __hash__(self) -> int:\n        return id(self)\n\n    @overload  # type: ignore[override]\n    def get(self, key: AppKey[_T], default: None = ...) -> _T | None: ...\n\n    @overload\n    def get(self, key: AppKey[_T], default: _U) -> _T | _U: ...\n\n    @overload\n    def get(self, key: str, default: Any = ...) -> Any: ...\n\n    def get(self, key: str | AppKey[_T], default: Any = None) -> Any:\n        return self._state.get(key, default)\n\n    ########\n    def _set_loop(self, loop: asyncio.AbstractEventLoop | None) -> None:\n        warnings.warn(\n            \"_set_loop() is no-op since 4.0 and scheduled for removal in 5.0\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n\n    @property\n    def pre_frozen(self) -> bool:\n        return self._pre_frozen\n\n    def pre_freeze(self) -> None:\n        if self._pre_frozen:\n            return\n\n        self._pre_frozen = True\n        self._middlewares.freeze()\n        self._router.freeze()\n        self._on_response_prepare.freeze()\n        self._cleanup_ctx.freeze()\n        self._on_startup.freeze()\n        self._on_shutdown.freeze()\n        self._on_cleanup.freeze()\n        self._middlewares_handlers = tuple(self._prepare_middleware())\n\n        # If current app and any subapp do not have middlewares avoid run all\n        # of the code footprint that it implies, which have a middleware\n        # hardcoded per app that sets up the current_app attribute. If no\n        # middlewares are configured the handler will receive the proper\n        # current_app without needing all of this code.\n        self._run_middlewares = True if self.middlewares else False\n\n        for subapp in self._subapps:\n            subapp.pre_freeze()\n            self._run_middlewares = self._run_middlewares or subapp._run_middlewares\n\n    @property\n    def frozen(self) -> bool:\n        return self._frozen\n\n    def freeze(self) -> None:\n        if self._frozen:\n            return\n\n        self.pre_freeze()\n        self._frozen = True\n        for subapp in self._subapps:\n            subapp.freeze()\n\n    @property\n    def debug(self) -> bool:\n        warnings.warn(\n            \"debug property is deprecated since 4.0 and scheduled for removal in 5.0\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n        return asyncio.get_event_loop().get_debug()\n\n    def _reg_subapp_signals(self, subapp: \"Application\") -> None:\n        def reg_handler(signame: str) -> None:\n            subsig = getattr(subapp, signame)\n\n            async def handler(app: \"Application\") -> None:\n                await subsig.send(subapp)\n\n            appsig = getattr(self, signame)\n            appsig.append(handler)\n\n        reg_handler(\"on_startup\")\n        reg_handler(\"on_shutdown\")\n        reg_handler(\"on_cleanup\")\n\n    def add_subapp(self, prefix: str, subapp: \"Application\") -> PrefixedSubAppResource:\n        if not isinstance(prefix, str):\n            raise TypeError(\"Prefix must be str\")\n        prefix = prefix.rstrip(\"/\")\n        if not prefix:\n            raise ValueError(\"Prefix cannot be empty\")\n        factory = partial(PrefixedSubAppResource, prefix, subapp)\n        return self._add_subapp(factory, subapp)\n\n    def _add_subapp(\n        self, resource_factory: Callable[[], _Resource], subapp: \"Application\"\n    ) -> _Resource:\n        if self.frozen:\n            raise RuntimeError(\"Cannot add sub application to frozen application\")\n        if subapp.frozen:\n            raise RuntimeError(\"Cannot add frozen application\")\n        resource = resource_factory()\n        self.router.register_resource(resource)\n        self._reg_subapp_signals(subapp)\n        self._subapps.append(subapp)\n        subapp.pre_freeze()\n        return resource\n\n    def add_domain(self, domain: str, subapp: \"Application\") -> MatchedSubAppResource:\n        if not isinstance(domain, str):\n            raise TypeError(\"Domain must be str\")\n        elif \"*\" in domain:\n            rule: Domain = MaskDomain(domain)\n        else:\n            rule = Domain(domain)\n        factory = partial(MatchedSubAppResource, rule, subapp)\n        return self._add_subapp(factory, subapp)\n\n    def add_routes(self, routes: Iterable[AbstractRouteDef]) -> list[AbstractRoute]:\n        return self.router.add_routes(routes)\n\n    @property\n    def on_response_prepare(self) -> _RespPrepareSignal:\n        return self._on_response_prepare\n\n    @property\n    def on_startup(self) -> _AppSignal:\n        return self._on_startup\n\n    @property\n    def on_shutdown(self) -> _AppSignal:\n        return self._on_shutdown\n\n    @property\n    def on_cleanup(self) -> _AppSignal:\n        return self._on_cleanup\n\n    @property\n    def cleanup_ctx(self) -> \"CleanupContext\":\n        return self._cleanup_ctx\n\n    @property\n    def router(self) -> UrlDispatcher:\n        return self._router\n\n    @property\n    def middlewares(self) -> _Middlewares:\n        return self._middlewares\n\n    async def startup(self) -> None:\n        \"\"\"Causes on_startup signal\n\n        Should be called in the event loop along with the request handler.\n        \"\"\"\n        await self.on_startup.send(self)\n\n    async def shutdown(self) -> None:\n        \"\"\"Causes on_shutdown signal\n\n        Should be called before cleanup()\n        \"\"\"\n        await self.on_shutdown.send(self)\n\n    async def cleanup(self) -> None:\n        \"\"\"Causes on_cleanup signal\n\n        Should be called after shutdown()\n        \"\"\"\n        if self.on_cleanup.frozen:\n            await self.on_cleanup.send(self)\n        else:\n            # If an exception occurs in startup, ensure cleanup contexts are completed.\n            await self._cleanup_ctx._on_cleanup(self)\n\n    def _prepare_middleware(self) -> Iterator[Middleware]:\n        yield from reversed(self._middlewares)\n        yield _fix_request_current_app(self)\n\n    async def _handle(self, request: Request) -> StreamResponse:\n        match_info = await self._router.resolve(request)\n        match_info.add_app(self)\n        match_info.freeze()\n\n        request._match_info = match_info\n\n        if request.headers.get(hdrs.EXPECT):\n            resp = await match_info.expect_handler(request)\n            await request.writer.drain()\n            if resp is not None:\n                return resp\n\n        handler = match_info.handler\n\n        if self._run_middlewares:\n            # If its a SystemRoute, don't cache building the middlewares since\n            # they are constructed for every MatchInfoError as a new handler\n            # is made each time.\n            if isinstance(match_info.route, SystemRoute):\n                handler = _build_middlewares(handler, match_info.apps)\n            else:\n                handler = _cached_build_middleware(handler, match_info.apps)\n\n        return await handler(request)\n\n    def __call__(self) -> \"Application\":\n        \"\"\"gunicorn compatibility\"\"\"\n        return self\n\n    def __repr__(self) -> str:\n        return f\"<Application 0x{id(self):x}>\"\n\n    def __bool__(self) -> bool:\n        return True\n\n\nclass CleanupError(RuntimeError):\n    @property\n    def exceptions(self) -> list[BaseException]:\n        return cast(list[BaseException], self.args[1])\n\n\n_CleanupContextCallable = (\n    Callable[[Application], AbstractAsyncContextManager[None]]\n    | Callable[[Application], AsyncIterator[None]]\n)\n\n\nclass CleanupContext(FrozenList[_CleanupContextCallable]):\n    def __init__(self) -> None:\n        super().__init__()\n        self._exits: list[AbstractAsyncContextManager[None]] = []\n\n    async def _on_startup(self, app: Application) -> None:\n        for cb in self:\n            ctx = cb(app)\n\n            if not isinstance(ctx, AbstractAsyncContextManager):\n                ctx = asynccontextmanager(cb)(app)  # type: ignore[arg-type]\n\n            await ctx.__aenter__()\n            self._exits.append(ctx)\n\n    async def _on_cleanup(self, app: Application) -> None:\n        errors = []\n        for it in reversed(self._exits):\n            try:\n                await it.__aexit__(None, None, None)\n            except (Exception, asyncio.CancelledError) as exc:\n                errors.append(exc)\n        if errors:\n            if len(errors) == 1:\n                raise errors[0]\n            else:\n                raise CleanupError(\"Multiple errors on cleanup stage\", errors)\n"
  },
  {
    "path": "aiohttp/web_exceptions.py",
    "content": "import warnings\nfrom collections.abc import Iterable\nfrom http import HTTPStatus\nfrom typing import Any\n\nfrom multidict import CIMultiDict\nfrom yarl import URL\n\nfrom . import hdrs\nfrom .helpers import CookieMixin\nfrom .typedefs import LooseHeaders, StrOrURL\n\n__all__ = (\n    \"HTTPException\",\n    \"HTTPError\",\n    \"HTTPRedirection\",\n    \"HTTPSuccessful\",\n    \"HTTPOk\",\n    \"HTTPCreated\",\n    \"HTTPAccepted\",\n    \"HTTPNonAuthoritativeInformation\",\n    \"HTTPNoContent\",\n    \"HTTPResetContent\",\n    \"HTTPPartialContent\",\n    \"HTTPMove\",\n    \"HTTPMultipleChoices\",\n    \"HTTPMovedPermanently\",\n    \"HTTPFound\",\n    \"HTTPSeeOther\",\n    \"HTTPNotModified\",\n    \"HTTPUseProxy\",\n    \"HTTPTemporaryRedirect\",\n    \"HTTPPermanentRedirect\",\n    \"HTTPClientError\",\n    \"HTTPBadRequest\",\n    \"HTTPUnauthorized\",\n    \"HTTPPaymentRequired\",\n    \"HTTPForbidden\",\n    \"HTTPNotFound\",\n    \"HTTPMethodNotAllowed\",\n    \"HTTPNotAcceptable\",\n    \"HTTPProxyAuthenticationRequired\",\n    \"HTTPRequestTimeout\",\n    \"HTTPConflict\",\n    \"HTTPGone\",\n    \"HTTPLengthRequired\",\n    \"HTTPPreconditionFailed\",\n    \"HTTPRequestEntityTooLarge\",\n    \"HTTPRequestURITooLong\",\n    \"HTTPUnsupportedMediaType\",\n    \"HTTPRequestRangeNotSatisfiable\",\n    \"HTTPExpectationFailed\",\n    \"HTTPMisdirectedRequest\",\n    \"HTTPUnprocessableEntity\",\n    \"HTTPFailedDependency\",\n    \"HTTPUpgradeRequired\",\n    \"HTTPPreconditionRequired\",\n    \"HTTPTooManyRequests\",\n    \"HTTPRequestHeaderFieldsTooLarge\",\n    \"HTTPUnavailableForLegalReasons\",\n    \"HTTPServerError\",\n    \"HTTPInternalServerError\",\n    \"HTTPNotImplemented\",\n    \"HTTPBadGateway\",\n    \"HTTPServiceUnavailable\",\n    \"HTTPGatewayTimeout\",\n    \"HTTPVersionNotSupported\",\n    \"HTTPVariantAlsoNegotiates\",\n    \"HTTPInsufficientStorage\",\n    \"HTTPNotExtended\",\n    \"HTTPNetworkAuthenticationRequired\",\n)\n\n\nclass NotAppKeyWarning(UserWarning):\n    \"\"\"Warning when not using AppKey in Application.\"\"\"\n\n\n############################################################\n# HTTP Exceptions\n############################################################\n\n\nclass HTTPException(CookieMixin, Exception):\n    # You should set in subclasses:\n    # status = 200\n\n    status_code = -1\n    empty_body = False\n    default_reason = \"\"  # Initialized at the end of the module\n\n    def __init__(\n        self,\n        *,\n        headers: LooseHeaders | None = None,\n        reason: str | None = None,\n        text: str | None = None,\n        content_type: str | None = None,\n    ) -> None:\n        if reason is None:\n            reason = self.default_reason\n        elif \"\\r\" in reason or \"\\n\" in reason:\n            raise ValueError(\"Reason cannot contain \\\\r or \\\\n\")\n\n        if text is None:\n            if not self.empty_body:\n                text = f\"{self.status_code}: {reason}\"\n        else:\n            if self.empty_body:\n                warnings.warn(\n                    f\"text argument is deprecated for HTTP status {self.status_code} \"\n                    \"since 4.0 and scheduled for removal in 5.0 (#3462),\"\n                    \"the response should be provided without a body\",\n                    DeprecationWarning,\n                    stacklevel=2,\n                )\n\n        if headers is not None:\n            real_headers = CIMultiDict(headers)\n        else:\n            real_headers = CIMultiDict()\n\n        if content_type is not None:\n            if not text:\n                warnings.warn(\n                    \"content_type without text is deprecated \"\n                    \"since 4.0 and scheduled for removal in 5.0 \"\n                    \"(#3462)\",\n                    DeprecationWarning,\n                    stacklevel=2,\n                )\n            real_headers[hdrs.CONTENT_TYPE] = content_type\n        elif hdrs.CONTENT_TYPE not in real_headers and text:\n            real_headers[hdrs.CONTENT_TYPE] = \"text/plain\"\n\n        self._reason = reason\n        self._text = text\n        self._headers = real_headers\n        self.args = ()\n\n    def __bool__(self) -> bool:\n        return True\n\n    @property\n    def status(self) -> int:\n        return self.status_code\n\n    @property\n    def reason(self) -> str:\n        return self._reason\n\n    @property\n    def text(self) -> str | None:\n        return self._text\n\n    @property\n    def headers(self) -> \"CIMultiDict[str]\":\n        return self._headers\n\n    def __str__(self) -> str:\n        return self.reason\n\n    def __repr__(self) -> str:\n        return f\"<{self.__class__.__name__}: {self.reason}>\"\n\n    __reduce__ = object.__reduce__\n\n    def __getnewargs__(self) -> tuple[Any, ...]:\n        return self.args\n\n\nclass HTTPError(HTTPException):\n    \"\"\"Base class for exceptions with status codes in the 400s and 500s.\"\"\"\n\n\nclass HTTPRedirection(HTTPException):\n    \"\"\"Base class for exceptions with status codes in the 300s.\"\"\"\n\n\nclass HTTPSuccessful(HTTPException):\n    \"\"\"Base class for exceptions with status codes in the 200s.\"\"\"\n\n\nclass HTTPOk(HTTPSuccessful):\n    status_code = 200\n\n\nclass HTTPCreated(HTTPSuccessful):\n    status_code = 201\n\n\nclass HTTPAccepted(HTTPSuccessful):\n    status_code = 202\n\n\nclass HTTPNonAuthoritativeInformation(HTTPSuccessful):\n    status_code = 203\n\n\nclass HTTPNoContent(HTTPSuccessful):\n    status_code = 204\n    empty_body = True\n\n\nclass HTTPResetContent(HTTPSuccessful):\n    status_code = 205\n    empty_body = True\n\n\nclass HTTPPartialContent(HTTPSuccessful):\n    status_code = 206\n\n\n############################################################\n# 3xx redirection\n############################################################\n\n\nclass HTTPMove(HTTPRedirection):\n    def __init__(\n        self,\n        location: StrOrURL,\n        *,\n        headers: LooseHeaders | None = None,\n        reason: str | None = None,\n        text: str | None = None,\n        content_type: str | None = None,\n    ) -> None:\n        if not location:\n            raise ValueError(\"HTTP redirects need a location to redirect to.\")\n        super().__init__(\n            headers=headers, reason=reason, text=text, content_type=content_type\n        )\n        self._location = URL(location)\n        self.headers[\"Location\"] = str(self.location)\n\n    @property\n    def location(self) -> URL:\n        return self._location\n\n\nclass HTTPMultipleChoices(HTTPMove):\n    status_code = 300\n\n\nclass HTTPMovedPermanently(HTTPMove):\n    status_code = 301\n\n\nclass HTTPFound(HTTPMove):\n    status_code = 302\n\n\n# This one is safe after a POST (the redirected location will be\n# retrieved with GET):\nclass HTTPSeeOther(HTTPMove):\n    status_code = 303\n\n\nclass HTTPNotModified(HTTPRedirection):\n    # FIXME: this should include a date or etag header\n    status_code = 304\n    empty_body = True\n\n\nclass HTTPUseProxy(HTTPMove):\n    # Not a move, but looks a little like one\n    status_code = 305\n\n\nclass HTTPTemporaryRedirect(HTTPMove):\n    status_code = 307\n\n\nclass HTTPPermanentRedirect(HTTPMove):\n    status_code = 308\n\n\n############################################################\n# 4xx client error\n############################################################\n\n\nclass HTTPClientError(HTTPError):\n    pass\n\n\nclass HTTPBadRequest(HTTPClientError):\n    status_code = 400\n\n\nclass HTTPUnauthorized(HTTPClientError):\n    status_code = 401\n\n\nclass HTTPPaymentRequired(HTTPClientError):\n    status_code = 402\n\n\nclass HTTPForbidden(HTTPClientError):\n    status_code = 403\n\n\nclass HTTPNotFound(HTTPClientError):\n    status_code = 404\n\n\nclass HTTPMethodNotAllowed(HTTPClientError):\n    status_code = 405\n\n    def __init__(\n        self,\n        method: str,\n        allowed_methods: Iterable[str],\n        *,\n        headers: LooseHeaders | None = None,\n        reason: str | None = None,\n        text: str | None = None,\n        content_type: str | None = None,\n    ) -> None:\n        allow = \",\".join(sorted(allowed_methods))\n        super().__init__(\n            headers=headers, reason=reason, text=text, content_type=content_type\n        )\n        self.headers[\"Allow\"] = allow\n        self._allowed: set[str] = set(allowed_methods)\n        self._method = method\n\n    @property\n    def allowed_methods(self) -> set[str]:\n        return self._allowed\n\n    @property\n    def method(self) -> str:\n        return self._method\n\n\nclass HTTPNotAcceptable(HTTPClientError):\n    status_code = 406\n\n\nclass HTTPProxyAuthenticationRequired(HTTPClientError):\n    status_code = 407\n\n\nclass HTTPRequestTimeout(HTTPClientError):\n    status_code = 408\n\n\nclass HTTPConflict(HTTPClientError):\n    status_code = 409\n\n\nclass HTTPGone(HTTPClientError):\n    status_code = 410\n\n\nclass HTTPLengthRequired(HTTPClientError):\n    status_code = 411\n\n\nclass HTTPPreconditionFailed(HTTPClientError):\n    status_code = 412\n\n\nclass HTTPRequestEntityTooLarge(HTTPClientError):\n    status_code = 413\n\n    def __init__(self, max_size: int, actual_size: int, **kwargs: Any) -> None:\n        kwargs.setdefault(\n            \"text\",\n            f\"Maximum request body size {max_size} exceeded, \"\n            f\"actual body size {actual_size}\",\n        )\n        super().__init__(**kwargs)\n\n\nclass HTTPRequestURITooLong(HTTPClientError):\n    status_code = 414\n\n\nclass HTTPUnsupportedMediaType(HTTPClientError):\n    status_code = 415\n\n\nclass HTTPRequestRangeNotSatisfiable(HTTPClientError):\n    status_code = 416\n\n\nclass HTTPExpectationFailed(HTTPClientError):\n    status_code = 417\n\n\nclass HTTPMisdirectedRequest(HTTPClientError):\n    status_code = 421\n\n\nclass HTTPUnprocessableEntity(HTTPClientError):\n    status_code = 422\n\n\nclass HTTPFailedDependency(HTTPClientError):\n    status_code = 424\n\n\nclass HTTPUpgradeRequired(HTTPClientError):\n    status_code = 426\n\n\nclass HTTPPreconditionRequired(HTTPClientError):\n    status_code = 428\n\n\nclass HTTPTooManyRequests(HTTPClientError):\n    status_code = 429\n\n\nclass HTTPRequestHeaderFieldsTooLarge(HTTPClientError):\n    status_code = 431\n\n\nclass HTTPUnavailableForLegalReasons(HTTPClientError):\n    status_code = 451\n\n    def __init__(\n        self,\n        link: StrOrURL | None,\n        *,\n        headers: LooseHeaders | None = None,\n        reason: str | None = None,\n        text: str | None = None,\n        content_type: str | None = None,\n    ) -> None:\n        super().__init__(\n            headers=headers, reason=reason, text=text, content_type=content_type\n        )\n        self._link = None\n        if link:\n            self._link = URL(link)\n            self.headers[\"Link\"] = f'<{str(self._link)}>; rel=\"blocked-by\"'\n\n    @property\n    def link(self) -> URL | None:\n        return self._link\n\n\n############################################################\n# 5xx Server Error\n############################################################\n#  Response status codes beginning with the digit \"5\" indicate cases in\n#  which the server is aware that it has erred or is incapable of\n#  performing the request. Except when responding to a HEAD request, the\n#  server SHOULD include an entity containing an explanation of the error\n#  situation, and whether it is a temporary or permanent condition. User\n#  agents SHOULD display any included entity to the user. These response\n#  codes are applicable to any request method.\n\n\nclass HTTPServerError(HTTPError):\n    pass\n\n\nclass HTTPInternalServerError(HTTPServerError):\n    status_code = 500\n\n\nclass HTTPNotImplemented(HTTPServerError):\n    status_code = 501\n\n\nclass HTTPBadGateway(HTTPServerError):\n    status_code = 502\n\n\nclass HTTPServiceUnavailable(HTTPServerError):\n    status_code = 503\n\n\nclass HTTPGatewayTimeout(HTTPServerError):\n    status_code = 504\n\n\nclass HTTPVersionNotSupported(HTTPServerError):\n    status_code = 505\n\n\nclass HTTPVariantAlsoNegotiates(HTTPServerError):\n    status_code = 506\n\n\nclass HTTPInsufficientStorage(HTTPServerError):\n    status_code = 507\n\n\nclass HTTPNotExtended(HTTPServerError):\n    status_code = 510\n\n\nclass HTTPNetworkAuthenticationRequired(HTTPServerError):\n    status_code = 511\n\n\ndef _initialize_default_reason() -> None:\n    for obj in globals().values():\n        if isinstance(obj, type) and issubclass(obj, HTTPException):\n            if obj.status_code >= 0:\n                try:\n                    status = HTTPStatus(obj.status_code)\n                    obj.default_reason = status.phrase\n                except ValueError:\n                    pass\n\n\n_initialize_default_reason()\ndel _initialize_default_reason\n"
  },
  {
    "path": "aiohttp/web_fileresponse.py",
    "content": "import asyncio\nimport io\nimport os\nimport pathlib\nimport sys\nfrom collections.abc import Awaitable, Callable\nfrom contextlib import suppress\nfrom enum import Enum, auto\nfrom mimetypes import MimeTypes\nfrom stat import S_ISREG\nfrom types import MappingProxyType\nfrom typing import IO, TYPE_CHECKING, Any, Final, Optional\n\nfrom . import hdrs\nfrom .abc import AbstractStreamWriter\nfrom .helpers import ETAG_ANY, ETag, must_be_empty_body\nfrom .typedefs import LooseHeaders, PathLike\nfrom .web_exceptions import (\n    HTTPForbidden,\n    HTTPNotFound,\n    HTTPNotModified,\n    HTTPPartialContent,\n    HTTPPreconditionFailed,\n    HTTPRequestRangeNotSatisfiable,\n)\nfrom .web_response import StreamResponse\n\n__all__ = (\"FileResponse\",)\n\nif TYPE_CHECKING:\n    from .web_request import BaseRequest\n\n\n_T_OnChunkSent = Optional[Callable[[bytes], Awaitable[None]]]\n\n\nNOSENDFILE: Final[bool] = bool(os.environ.get(\"AIOHTTP_NOSENDFILE\"))\n\nCONTENT_TYPES: Final[MimeTypes] = MimeTypes()\n\n# File extension to IANA encodings map that will be checked in the order defined.\nENCODING_EXTENSIONS = MappingProxyType(\n    {ext: CONTENT_TYPES.encodings_map[ext] for ext in (\".br\", \".gz\")}\n)\n\nFALLBACK_CONTENT_TYPE = \"application/octet-stream\"\n\n# Provide additional MIME type/extension pairs to be recognized.\n# https://en.wikipedia.org/wiki/List_of_archive_formats#Compression_only\nADDITIONAL_CONTENT_TYPES = MappingProxyType(\n    {\n        \"application/gzip\": \".gz\",\n        \"application/x-brotli\": \".br\",\n        \"application/x-bzip2\": \".bz2\",\n        \"application/x-compress\": \".Z\",\n        \"application/x-xz\": \".xz\",\n    }\n)\n\n\nclass _FileResponseResult(Enum):\n    \"\"\"The result of the file response.\"\"\"\n\n    SEND_FILE = auto()  # Ie a regular file to send\n    NOT_ACCEPTABLE = auto()  # Ie a socket, or non-regular file\n    PRE_CONDITION_FAILED = auto()  # Ie If-Match or If-None-Match failed\n    NOT_MODIFIED = auto()  # 304 Not Modified\n\n\n# Add custom pairs and clear the encodings map so guess_type ignores them.\nCONTENT_TYPES.encodings_map.clear()\nfor content_type, extension in ADDITIONAL_CONTENT_TYPES.items():\n    CONTENT_TYPES.add_type(content_type, extension)\n\n\n_CLOSE_FUTURES: set[asyncio.Future[None]] = set()\n\n\nclass FileResponse(StreamResponse):\n    \"\"\"A response object can be used to send files.\"\"\"\n\n    def __init__(\n        self,\n        path: PathLike,\n        chunk_size: int = 256 * 1024,\n        status: int = 200,\n        reason: str | None = None,\n        headers: LooseHeaders | None = None,\n    ) -> None:\n        super().__init__(status=status, reason=reason, headers=headers)\n\n        self._path = pathlib.Path(path)\n        self._chunk_size = chunk_size\n\n    def _seek_and_read(self, fobj: IO[Any], offset: int, chunk_size: int) -> bytes:\n        fobj.seek(offset)\n        return fobj.read(chunk_size)  # type: ignore[no-any-return]\n\n    async def _sendfile_fallback(\n        self, writer: AbstractStreamWriter, fobj: IO[Any], offset: int, count: int\n    ) -> AbstractStreamWriter:\n        # To keep memory usage low,fobj is transferred in chunks\n        # controlled by the constructor's chunk_size argument.\n\n        chunk_size = self._chunk_size\n        loop = asyncio.get_event_loop()\n        chunk = await loop.run_in_executor(\n            None, self._seek_and_read, fobj, offset, min(chunk_size, count)\n        )\n        while chunk:\n            await writer.write(chunk)\n            count = count - len(chunk)\n            if count <= 0:\n                break\n            chunk = await loop.run_in_executor(None, fobj.read, min(chunk_size, count))\n\n        await writer.drain()\n        return writer\n\n    async def _sendfile(\n        self, request: \"BaseRequest\", fobj: IO[Any], offset: int, count: int\n    ) -> AbstractStreamWriter:\n        writer = await super().prepare(request)\n        assert writer is not None\n\n        if NOSENDFILE or self.compression:\n            return await self._sendfile_fallback(writer, fobj, offset, count)\n\n        loop = request._loop\n        transport = request.transport\n        assert transport is not None\n\n        try:\n            await loop.sendfile(transport, fobj, offset, count)\n        except NotImplementedError:\n            return await self._sendfile_fallback(writer, fobj, offset, count)\n\n        await super().write_eof()\n        return writer\n\n    @staticmethod\n    def _etag_match(etag_value: str, etags: tuple[ETag, ...], *, weak: bool) -> bool:\n        if len(etags) == 1 and etags[0].value == ETAG_ANY:\n            return True\n        return any(\n            etag.value == etag_value for etag in etags if weak or not etag.is_weak\n        )\n\n    async def _not_modified(\n        self, request: \"BaseRequest\", etag_value: str, last_modified: float\n    ) -> AbstractStreamWriter | None:\n        self.set_status(HTTPNotModified.status_code)\n        self._length_check = False\n        self.etag = etag_value\n        self.last_modified = last_modified\n        # Delete any Content-Length headers provided by user. HTTP 304\n        # should always have empty response body\n        return await super().prepare(request)\n\n    async def _precondition_failed(\n        self, request: \"BaseRequest\"\n    ) -> AbstractStreamWriter | None:\n        self.set_status(HTTPPreconditionFailed.status_code)\n        self.content_length = 0\n        return await super().prepare(request)\n\n    def _make_response(\n        self, request: \"BaseRequest\", accept_encoding: str\n    ) -> tuple[\n        _FileResponseResult, io.BufferedReader | None, os.stat_result, str | None\n    ]:\n        \"\"\"Return the response result, io object, stat result, and encoding.\n\n        If an uncompressed file is returned, the encoding is set to\n        :py:data:`None`.\n\n        This method should be called from a thread executor\n        since it calls os.stat which may block.\n        \"\"\"\n        file_path, st, file_encoding = self._get_file_path_stat_encoding(\n            accept_encoding\n        )\n        if not file_path:\n            return _FileResponseResult.NOT_ACCEPTABLE, None, st, None\n\n        etag_value = f\"{st.st_mtime_ns:x}-{st.st_size:x}\"\n\n        # https://www.rfc-editor.org/rfc/rfc9110#section-13.1.1-2\n        if (ifmatch := request.if_match) is not None and not self._etag_match(\n            etag_value, ifmatch, weak=False\n        ):\n            return _FileResponseResult.PRE_CONDITION_FAILED, None, st, file_encoding\n\n        if (\n            (unmodsince := request.if_unmodified_since) is not None\n            and ifmatch is None\n            and st.st_mtime > unmodsince.timestamp()\n        ):\n            return _FileResponseResult.PRE_CONDITION_FAILED, None, st, file_encoding\n\n        # https://www.rfc-editor.org/rfc/rfc9110#section-13.1.2-2\n        if (ifnonematch := request.if_none_match) is not None and self._etag_match(\n            etag_value, ifnonematch, weak=True\n        ):\n            return _FileResponseResult.NOT_MODIFIED, None, st, file_encoding\n\n        if (\n            (modsince := request.if_modified_since) is not None\n            and ifnonematch is None\n            and st.st_mtime <= modsince.timestamp()\n        ):\n            return _FileResponseResult.NOT_MODIFIED, None, st, file_encoding\n\n        fobj = file_path.open(\"rb\")\n        with suppress(OSError):\n            # fstat() may not be available on all platforms\n            # Once we open the file, we want the fstat() to ensure\n            # the file has not changed between the first stat()\n            # and the open().\n            st = os.stat(fobj.fileno())\n        return _FileResponseResult.SEND_FILE, fobj, st, file_encoding\n\n    def _get_file_path_stat_encoding(\n        self, accept_encoding: str\n    ) -> tuple[pathlib.Path | None, os.stat_result, str | None]:\n        file_path = self._path\n        for file_extension, file_encoding in ENCODING_EXTENSIONS.items():\n            if file_encoding not in accept_encoding:\n                continue\n\n            compressed_path = file_path.with_suffix(file_path.suffix + file_extension)\n            with suppress(OSError):\n                # Do not follow symlinks and ignore any non-regular files.\n                st = compressed_path.lstat()\n                if S_ISREG(st.st_mode):\n                    return compressed_path, st, file_encoding\n\n        # Fallback to the uncompressed file\n        st = file_path.stat()\n        return file_path if S_ISREG(st.st_mode) else None, st, None\n\n    async def prepare(self, request: \"BaseRequest\") -> AbstractStreamWriter | None:\n        loop = asyncio.get_running_loop()\n        # Encoding comparisons should be case-insensitive\n        # https://www.rfc-editor.org/rfc/rfc9110#section-8.4.1\n        accept_encoding = request.headers.get(hdrs.ACCEPT_ENCODING, \"\").lower()\n        try:\n            response_result, fobj, st, file_encoding = await loop.run_in_executor(\n                None, self._make_response, request, accept_encoding\n            )\n        except PermissionError:\n            self.set_status(HTTPForbidden.status_code)\n            return await super().prepare(request)\n        except OSError:\n            # Most likely to be FileNotFoundError or OSError for circular\n            # symlinks in python >= 3.13, so respond with 404.\n            self.set_status(HTTPNotFound.status_code)\n            return await super().prepare(request)\n\n        # Forbid special files like sockets, pipes, devices, etc.\n        if response_result is _FileResponseResult.NOT_ACCEPTABLE:\n            self.set_status(HTTPForbidden.status_code)\n            return await super().prepare(request)\n\n        if response_result is _FileResponseResult.PRE_CONDITION_FAILED:\n            return await self._precondition_failed(request)\n\n        if response_result is _FileResponseResult.NOT_MODIFIED:\n            etag_value = f\"{st.st_mtime_ns:x}-{st.st_size:x}\"\n            last_modified = st.st_mtime\n            return await self._not_modified(request, etag_value, last_modified)\n\n        assert fobj is not None\n        try:\n            return await self._prepare_open_file(request, fobj, st, file_encoding)\n        finally:\n            # We do not await here because we do not want to wait\n            # for the executor to finish before returning the response\n            # so the connection can begin servicing another request\n            # as soon as possible.\n            close_future = loop.run_in_executor(None, fobj.close)\n            # Hold a strong reference to the future to prevent it from being\n            # garbage collected before it completes.\n            _CLOSE_FUTURES.add(close_future)\n            close_future.add_done_callback(_CLOSE_FUTURES.remove)\n\n    async def _prepare_open_file(\n        self,\n        request: \"BaseRequest\",\n        fobj: io.BufferedReader,\n        st: os.stat_result,\n        file_encoding: str | None,\n    ) -> AbstractStreamWriter | None:\n        status = self._status\n        file_size: int = st.st_size\n        file_mtime: float = st.st_mtime\n        count: int = file_size\n        start: int | None = None\n\n        if (ifrange := request.if_range) is None or file_mtime <= ifrange.timestamp():\n            # If-Range header check:\n            # condition = cached date >= last modification date\n            # return 206 if True else 200.\n            # if False:\n            #   Range header would not be processed, return 200\n            # if True but Range header missing\n            #   return 200\n            try:\n                rng = request.http_range\n                start = rng.start\n                end: int | None = rng.stop\n            except ValueError:\n                # https://tools.ietf.org/html/rfc7233:\n                # A server generating a 416 (Range Not Satisfiable) response to\n                # a byte-range request SHOULD send a Content-Range header field\n                # with an unsatisfied-range value.\n                # The complete-length in a 416 response indicates the current\n                # length of the selected representation.\n                #\n                # Will do the same below. Many servers ignore this and do not\n                # send a Content-Range header with HTTP 416\n                self._headers[hdrs.CONTENT_RANGE] = f\"bytes */{file_size}\"\n                self.set_status(HTTPRequestRangeNotSatisfiable.status_code)\n                return await super().prepare(request)\n\n            # If a range request has been made, convert start, end slice\n            # notation into file pointer offset and count\n            if start is not None:\n                if start < 0 and end is None:  # return tail of file\n                    start += file_size\n                    if start < 0:\n                        # if Range:bytes=-1000 in request header but file size\n                        # is only 200, there would be trouble without this\n                        start = 0\n                    count = file_size - start\n                else:\n                    # rfc7233:If the last-byte-pos value is\n                    # absent, or if the value is greater than or equal to\n                    # the current length of the representation data,\n                    # the byte range is interpreted as the remainder\n                    # of the representation (i.e., the server replaces the\n                    # value of last-byte-pos with a value that is one less than\n                    # the current length of the selected representation).\n                    count = (\n                        min(end if end is not None else file_size, file_size) - start\n                    )\n\n                if start >= file_size:\n                    # HTTP 416 should be returned in this case.\n                    #\n                    # According to https://tools.ietf.org/html/rfc7233:\n                    # If a valid byte-range-set includes at least one\n                    # byte-range-spec with a first-byte-pos that is less than\n                    # the current length of the representation, or at least one\n                    # suffix-byte-range-spec with a non-zero suffix-length,\n                    # then the byte-range-set is satisfiable. Otherwise, the\n                    # byte-range-set is unsatisfiable.\n                    self._headers[hdrs.CONTENT_RANGE] = f\"bytes */{file_size}\"\n                    self.set_status(HTTPRequestRangeNotSatisfiable.status_code)\n                    return await super().prepare(request)\n\n                status = HTTPPartialContent.status_code\n                # Even though you are sending the whole file, you should still\n                # return a HTTP 206 for a Range request.\n                self.set_status(status)\n\n        # If the Content-Type header is not already set, guess it based on the\n        # extension of the request path. The encoding returned by guess_type\n        #  can be ignored since the map was cleared above.\n        if hdrs.CONTENT_TYPE not in self._headers:\n            if sys.version_info >= (3, 13):\n                guesser = CONTENT_TYPES.guess_file_type\n            else:\n                guesser = CONTENT_TYPES.guess_type\n            self.content_type = guesser(self._path)[0] or FALLBACK_CONTENT_TYPE\n\n        if file_encoding:\n            self._headers[hdrs.CONTENT_ENCODING] = file_encoding\n            self._headers[hdrs.VARY] = hdrs.ACCEPT_ENCODING\n            # Disable compression if we are already sending\n            # a compressed file since we don't want to double\n            # compress.\n            self._compression = False\n\n        self.etag = f\"{st.st_mtime_ns:x}-{st.st_size:x}\"\n        self.last_modified = file_mtime\n        self.content_length = count\n\n        self._headers[hdrs.ACCEPT_RANGES] = \"bytes\"\n\n        if status == HTTPPartialContent.status_code:\n            real_start = start\n            assert real_start is not None\n            self._headers[hdrs.CONTENT_RANGE] = (\n                f\"bytes {real_start}-{real_start + count - 1}/{file_size}\"\n            )\n\n        # If we are sending 0 bytes calling sendfile() will throw a ValueError\n        if count == 0 or must_be_empty_body(request.method, status):\n            return await super().prepare(request)\n\n        # be aware that start could be None or int=0 here.\n        offset = start or 0\n\n        return await self._sendfile(request, fobj, offset, count)\n"
  },
  {
    "path": "aiohttp/web_log.py",
    "content": "import datetime\nimport functools\nimport logging\nimport os\nimport re\nimport time as time_mod\nfrom collections import namedtuple\nfrom collections.abc import Iterable\nfrom typing import Callable, ClassVar\n\nfrom .abc import AbstractAccessLogger\nfrom .web_request import BaseRequest\nfrom .web_response import StreamResponse\n\nKeyMethod = namedtuple(\"KeyMethod\", \"key method\")\n\n\nclass AccessLogger(AbstractAccessLogger):\n    \"\"\"Helper object to log access.\n\n    Usage:\n        log = logging.getLogger(\"spam\")\n        log_format = \"%a %{User-Agent}i\"\n        access_logger = AccessLogger(log, log_format)\n        access_logger.log(request, response, time)\n\n    Format:\n        %%  The percent sign\n        %a  Remote IP-address (IP-address of proxy if using reverse proxy)\n        %t  Time when the request was started to process\n        %P  The process ID of the child that serviced the request\n        %r  First line of request\n        %s  Response status code\n        %b  Size of response in bytes, including HTTP headers\n        %T  Time taken to serve the request, in seconds\n        %Tf Time taken to serve the request, in seconds with floating fraction\n            in .06f format\n        %D  Time taken to serve the request, in microseconds\n        %{FOO}i  request.headers['FOO']\n        %{FOO}o  response.headers['FOO']\n        %{FOO}e  os.environ['FOO']\n\n    \"\"\"\n\n    LOG_FORMAT_MAP = {\n        \"a\": \"remote_address\",\n        \"t\": \"request_start_time\",\n        \"P\": \"process_id\",\n        \"r\": \"first_request_line\",\n        \"s\": \"response_status\",\n        \"b\": \"response_size\",\n        \"T\": \"request_time\",\n        \"Tf\": \"request_time_frac\",\n        \"D\": \"request_time_micro\",\n        \"i\": \"request_header\",\n        \"o\": \"response_header\",\n    }\n\n    LOG_FORMAT = '%a %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\"'\n    FORMAT_RE = re.compile(r\"%(\\{([A-Za-z0-9\\-_]+)\\}([ioe])|[atPrsbOD]|Tf?)\")\n    CLEANUP_RE = re.compile(r\"(%[^s])\")\n    _FORMAT_CACHE: dict[str, tuple[str, list[KeyMethod]]] = {}\n\n    _cached_tz: ClassVar[datetime.timezone | None] = None\n    _cached_tz_expires: ClassVar[float] = 0.0\n\n    def __init__(self, logger: logging.Logger, log_format: str = LOG_FORMAT) -> None:\n        \"\"\"Initialise the logger.\n\n        logger is a logger object to be used for logging.\n        log_format is a string with apache compatible log format description.\n\n        \"\"\"\n        super().__init__(logger, log_format=log_format)\n\n        _compiled_format = AccessLogger._FORMAT_CACHE.get(log_format)\n        if not _compiled_format:\n            _compiled_format = self.compile_format(log_format)\n            AccessLogger._FORMAT_CACHE[log_format] = _compiled_format\n\n        self._log_format, self._methods = _compiled_format\n\n    def compile_format(self, log_format: str) -> tuple[str, list[KeyMethod]]:\n        \"\"\"Translate log_format into form usable by modulo formatting\n\n        All known atoms will be replaced with %s\n        Also methods for formatting of those atoms will be added to\n        _methods in appropriate order\n\n        For example we have log_format = \"%a %t\"\n        This format will be translated to \"%s %s\"\n        Also contents of _methods will be\n        [self._format_a, self._format_t]\n        These method will be called and results will be passed\n        to translated string format.\n\n        Each _format_* method receive 'args' which is list of arguments\n        given to self.log\n\n        Exceptions are _format_e, _format_i and _format_o methods which\n        also receive key name (by functools.partial)\n\n        \"\"\"\n        # list of (key, method) tuples, we don't use an OrderedDict as users\n        # can repeat the same key more than once\n        methods = list()\n\n        for atom in self.FORMAT_RE.findall(log_format):\n            if atom[1] == \"\":\n                format_key1 = self.LOG_FORMAT_MAP[atom[0]]\n                m = getattr(AccessLogger, \"_format_%s\" % atom[0])\n                key_method = KeyMethod(format_key1, m)\n            else:\n                format_key2 = (self.LOG_FORMAT_MAP[atom[2]], atom[1])\n                m = getattr(AccessLogger, \"_format_%s\" % atom[2])\n                key_method = KeyMethod(format_key2, functools.partial(m, atom[1]))\n\n            methods.append(key_method)\n\n        log_format = self.FORMAT_RE.sub(r\"%s\", log_format)\n        log_format = self.CLEANUP_RE.sub(r\"%\\1\", log_format)\n        return log_format, methods\n\n    @staticmethod\n    def _format_i(\n        key: str, request: BaseRequest, response: StreamResponse, time: float\n    ) -> str:\n        # suboptimal, make istr(key) once\n        return request.headers.get(key, \"-\")\n\n    @staticmethod\n    def _format_o(\n        key: str, request: BaseRequest, response: StreamResponse, time: float\n    ) -> str:\n        # suboptimal, make istr(key) once\n        return response.headers.get(key, \"-\")\n\n    @staticmethod\n    def _format_a(request: BaseRequest, response: StreamResponse, time: float) -> str:\n        ip = request.remote\n        return ip if ip is not None else \"-\"\n\n    @classmethod\n    def _get_local_time(cls) -> datetime.datetime:\n        if cls._cached_tz is None or time_mod.time() >= cls._cached_tz_expires:\n            gmtoff = time_mod.localtime().tm_gmtoff\n            cls._cached_tz = tz = datetime.timezone(datetime.timedelta(seconds=gmtoff))\n\n            now = datetime.datetime.now(tz)\n            # Expire at every 30 mins, as any DST change should occur at 0/30 mins past.\n            d = now + datetime.timedelta(minutes=30)\n            d = d.replace(minute=30 if d.minute >= 30 else 0, second=0, microsecond=0)\n            cls._cached_tz_expires = d.timestamp()\n            return now\n\n        return datetime.datetime.now(cls._cached_tz)\n\n    @staticmethod\n    def _format_t(request: BaseRequest, response: StreamResponse, time: float) -> str:\n        now = AccessLogger._get_local_time()\n        start_time = now - datetime.timedelta(seconds=time)\n        return start_time.strftime(\"[%d/%b/%Y:%H:%M:%S %z]\")\n\n    @staticmethod\n    def _format_P(request: BaseRequest, response: StreamResponse, time: float) -> str:\n        return \"<%s>\" % os.getpid()\n\n    @staticmethod\n    def _format_r(request: BaseRequest, response: StreamResponse, time: float) -> str:\n        return f\"{request.method} {request.path_qs} HTTP/{request.version.major}.{request.version.minor}\"\n\n    @staticmethod\n    def _format_s(request: BaseRequest, response: StreamResponse, time: float) -> int:\n        return response.status\n\n    @staticmethod\n    def _format_b(request: BaseRequest, response: StreamResponse, time: float) -> int:\n        return response.body_length\n\n    @staticmethod\n    def _format_T(request: BaseRequest, response: StreamResponse, time: float) -> str:\n        return str(round(time))\n\n    @staticmethod\n    def _format_Tf(request: BaseRequest, response: StreamResponse, time: float) -> str:\n        return \"%06f\" % time\n\n    @staticmethod\n    def _format_D(request: BaseRequest, response: StreamResponse, time: float) -> str:\n        return str(round(time * 1000000))\n\n    def _format_line(\n        self, request: BaseRequest, response: StreamResponse, time: float\n    ) -> Iterable[tuple[str, Callable[[BaseRequest, StreamResponse, float], str]]]:\n        return [(key, method(request, response, time)) for key, method in self._methods]\n\n    @property\n    def enabled(self) -> bool:\n        \"\"\"Check if logger is enabled.\"\"\"\n        # Avoid formatting the log line if it will not be emitted.\n        return self.logger.isEnabledFor(logging.INFO)\n\n    def log(self, request: BaseRequest, response: StreamResponse, time: float) -> None:\n        try:\n            fmt_info = self._format_line(request, response, time)\n\n            values = list()\n            extra = dict()\n            for key, value in fmt_info:\n                values.append(value)\n\n                if key.__class__ is str:\n                    extra[key] = value\n                else:\n                    k1, k2 = key  # type: ignore[misc]\n                    dct = extra.get(k1, {})  # type: ignore[var-annotated,has-type]\n                    dct[k2] = value  # type: ignore[index,has-type]\n                    extra[k1] = dct  # type: ignore[has-type,assignment]\n\n            self.logger.info(self._log_format % tuple(values), extra=extra)\n        except Exception:\n            self.logger.exception(\"Error in logging\")\n"
  },
  {
    "path": "aiohttp/web_middlewares.py",
    "content": "import re\nimport warnings\nfrom typing import TYPE_CHECKING, TypeVar\n\nfrom .typedefs import Handler, Middleware\nfrom .web_exceptions import HTTPMove, HTTPPermanentRedirect\nfrom .web_request import Request\nfrom .web_response import StreamResponse\nfrom .web_urldispatcher import SystemRoute\n\n__all__ = (\n    \"middleware\",\n    \"normalize_path_middleware\",\n)\n\nif TYPE_CHECKING:\n    from .web_app import Application\n\n_Func = TypeVar(\"_Func\")\n\n\nasync def _check_request_resolves(request: Request, path: str) -> tuple[bool, Request]:\n    alt_request = request.clone(rel_url=path)\n\n    match_info = await request.app.router.resolve(alt_request)\n    alt_request._match_info = match_info\n\n    if match_info.http_exception is None:\n        return True, alt_request\n\n    return False, request\n\n\ndef middleware(f: _Func) -> _Func:\n    warnings.warn(\n        \"Middleware decorator is deprecated since 4.0 \"\n        \"and its behaviour is default, \"\n        \"you can simply remove this decorator.\",\n        DeprecationWarning,\n        stacklevel=2,\n    )\n    return f\n\n\ndef normalize_path_middleware(\n    *,\n    append_slash: bool = True,\n    remove_slash: bool = False,\n    merge_slashes: bool = True,\n    redirect_class: type[HTTPMove] = HTTPPermanentRedirect,\n) -> Middleware:\n    \"\"\"Factory for producing a middleware that normalizes the path of a request.\n\n    Normalizing means:\n        - Add or remove a trailing slash to the path.\n        - Double slashes are replaced by one.\n\n    The middleware returns as soon as it finds a path that resolves\n    correctly. The order if both merge and append/remove are enabled is\n        1) merge slashes\n        2) append/remove slash\n        3) both merge slashes and append/remove slash.\n    If the path resolves with at least one of those conditions, it will\n    redirect to the new path.\n\n    Only one of `append_slash` and `remove_slash` can be enabled. If both\n    are `True` the factory will raise an assertion error\n\n    If `append_slash` is `True` the middleware will append a slash when\n    needed. If a resource is defined with trailing slash and the request\n    comes without it, it will append it automatically.\n\n    If `remove_slash` is `True`, `append_slash` must be `False`. When enabled\n    the middleware will remove trailing slashes and redirect if the resource\n    is defined\n\n    If merge_slashes is True, merge multiple consecutive slashes in the\n    path into one.\n    \"\"\"\n    correct_configuration = not (append_slash and remove_slash)\n    assert correct_configuration, \"Cannot both remove and append slash\"\n\n    async def impl(request: Request, handler: Handler) -> StreamResponse:\n        if isinstance(request.match_info.route, SystemRoute):\n            paths_to_check = []\n            if \"?\" in request.raw_path:\n                path, query = request.raw_path.split(\"?\", 1)\n                query = \"?\" + query\n            else:\n                query = \"\"\n                path = request.raw_path\n\n            if merge_slashes:\n                paths_to_check.append(re.sub(\"//+\", \"/\", path))\n            if append_slash and not request.path.endswith(\"/\"):\n                paths_to_check.append(path + \"/\")\n            if remove_slash and request.path.endswith(\"/\"):\n                paths_to_check.append(path[:-1])\n            if merge_slashes and append_slash:\n                paths_to_check.append(re.sub(\"//+\", \"/\", path + \"/\"))\n            if merge_slashes and remove_slash and path.endswith(\"/\"):\n                merged_slashes = re.sub(\"//+\", \"/\", path)\n                paths_to_check.append(merged_slashes[:-1])\n\n            for path in paths_to_check:\n                path = re.sub(\"^//+\", \"/\", path)  # SECURITY: GHSA-v6wp-4m6f-gcjg\n                resolves, request = await _check_request_resolves(request, path)\n                if resolves:\n                    raise redirect_class(request.raw_path + query)\n\n        return await handler(request)\n\n    return impl\n\n\ndef _fix_request_current_app(app: \"Application\") -> Middleware:\n    async def impl(request: Request, handler: Handler) -> StreamResponse:\n        match_info = request.match_info\n        prev = match_info.current_app\n        match_info.current_app = app\n        try:\n            return await handler(request)\n        finally:\n            match_info.current_app = prev\n\n    return impl\n"
  },
  {
    "path": "aiohttp/web_protocol.py",
    "content": "import asyncio\nimport asyncio.streams\nimport sys\nimport traceback\nfrom collections import deque\nfrom collections.abc import Awaitable, Callable, Sequence\nfrom contextlib import suppress\nfrom html import escape as html_escape\nfrom http import HTTPStatus\nfrom logging import Logger\nfrom typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar, Union, cast\n\nimport yarl\nfrom propcache import under_cached_property\n\nfrom .abc import AbstractAccessLogger, AbstractAsyncAccessLogger, AbstractStreamWriter\nfrom .base_protocol import BaseProtocol\nfrom .helpers import ceil_timeout, frozen_dataclass_decorator\nfrom .http import (\n    HttpProcessingError,\n    HttpRequestParser,\n    HttpVersion10,\n    RawRequestMessage,\n    StreamWriter,\n)\nfrom .http_exceptions import BadHttpMethod\nfrom .log import access_logger, server_logger\nfrom .streams import EMPTY_PAYLOAD, StreamReader\nfrom .tcp_helpers import tcp_keepalive\nfrom .web_exceptions import HTTPException, HTTPInternalServerError\nfrom .web_log import AccessLogger\nfrom .web_request import BaseRequest\nfrom .web_response import Response, StreamResponse\n\n__all__ = (\"RequestHandler\", \"RequestPayloadError\", \"PayloadAccessError\")\n\nif TYPE_CHECKING:\n    import ssl\n\n    from .web_server import Server\n\n\n_Request = TypeVar(\"_Request\", bound=BaseRequest)\n_RequestFactory = Callable[\n    [\n        RawRequestMessage,\n        StreamReader,\n        \"RequestHandler[_Request]\",\n        AbstractStreamWriter,\n        \"asyncio.Task[None]\",\n    ],\n    _Request,\n]\n\n_RequestHandler = Callable[[_Request], Awaitable[StreamResponse]]\n_AnyAbstractAccessLogger = Union[\n    type[AbstractAsyncAccessLogger],\n    type[AbstractAccessLogger],\n]\n\nERROR = RawRequestMessage(\n    \"UNKNOWN\",\n    \"/\",\n    HttpVersion10,\n    {},  # type: ignore[arg-type]\n    {},  # type: ignore[arg-type]\n    True,\n    None,\n    False,\n    False,\n    yarl.URL(\"/\"),\n)\n\n\nclass RequestPayloadError(Exception):\n    \"\"\"Payload parsing error.\"\"\"\n\n\nclass PayloadAccessError(Exception):\n    \"\"\"Payload was accessed after response was sent.\"\"\"\n\n\n_PAYLOAD_ACCESS_ERROR = PayloadAccessError()\n\n\nclass AccessLoggerWrapper(AbstractAsyncAccessLogger):\n    \"\"\"Wrap an AbstractAccessLogger so it behaves like an AbstractAsyncAccessLogger.\"\"\"\n\n    __slots__ = (\"access_logger\", \"_loop\")\n\n    def __init__(\n        self, access_logger: AbstractAccessLogger, loop: asyncio.AbstractEventLoop\n    ) -> None:\n        self.access_logger = access_logger\n        self._loop = loop\n        super().__init__()\n\n    async def log(\n        self, request: BaseRequest, response: StreamResponse, request_start: float\n    ) -> None:\n        self.access_logger.log(request, response, self._loop.time() - request_start)\n\n    @property\n    def enabled(self) -> bool:\n        \"\"\"Check if logger is enabled.\"\"\"\n        return self.access_logger.enabled\n\n\n@frozen_dataclass_decorator\nclass _ErrInfo:\n    status: int\n    exc: BaseException\n    message: str\n\n\n_MsgType = tuple[RawRequestMessage | _ErrInfo, StreamReader]\n\n\nclass RequestHandler(BaseProtocol, Generic[_Request]):\n    \"\"\"HTTP protocol implementation.\n\n    RequestHandler handles incoming HTTP request. It reads request line,\n    request headers and request payload and calls handle_request() method.\n    By default it always returns with 404 response.\n\n    RequestHandler handles errors in incoming request, like bad\n    status line, bad headers or incomplete payload. If any error occurs,\n    connection gets closed.\n\n    keepalive_timeout -- number of seconds before closing\n                         keep-alive connection\n\n    tcp_keepalive -- TCP keep-alive is on, default is on\n\n    logger -- custom logger object\n\n    access_log_class -- custom class for access_logger\n\n    access_log -- custom logging object\n\n    access_log_format -- access log format string\n\n    loop -- Optional event loop\n\n    max_line_size -- Optional maximum header line size\n\n    max_field_size -- Optional maximum header field size\n\n    timeout_ceil_threshold -- Optional value to specify\n                              threshold to ceil() timeout\n                              values\n\n    \"\"\"\n\n    __slots__ = (\n        \"max_field_size\",\n        \"max_headers\",\n        \"max_line_size\",\n        \"_request_count\",\n        \"_keepalive\",\n        \"_manager\",\n        \"_request_handler\",\n        \"_request_factory\",\n        \"_tcp_keepalive\",\n        \"_next_keepalive_close_time\",\n        \"_keepalive_handle\",\n        \"_keepalive_timeout\",\n        \"_lingering_time\",\n        \"_messages\",\n        \"_message_tail\",\n        \"_handler_waiter\",\n        \"_waiter\",\n        \"_task_handler\",\n        \"_upgrade\",\n        \"_payload_parser\",\n        \"_data_received_cb\",\n        \"_request_parser\",\n        \"logger\",\n        \"access_log\",\n        \"access_logger\",\n        \"_close\",\n        \"_force_close\",\n        \"_current_request\",\n        \"_timeout_ceil_threshold\",\n        \"_request_in_progress\",\n        \"_logging_enabled\",\n        \"_cache\",\n    )\n\n    def __init__(\n        self,\n        manager: \"Server[_Request]\",\n        *,\n        loop: asyncio.AbstractEventLoop,\n        # Default should be high enough that it's likely longer than a reverse proxy.\n        keepalive_timeout: float = 3630,\n        tcp_keepalive: bool = True,\n        logger: Logger = server_logger,\n        access_log_class: _AnyAbstractAccessLogger = AccessLogger,\n        access_log: Logger | None = access_logger,\n        access_log_format: str = AccessLogger.LOG_FORMAT,\n        max_line_size: int = 8190,\n        max_headers: int = 128,\n        max_field_size: int = 8190,\n        lingering_time: float = 10.0,\n        read_bufsize: int = 2**16,\n        auto_decompress: bool = True,\n        timeout_ceil_threshold: float = 5,\n    ):\n        super().__init__(loop)\n\n        # _request_count is the number of requests processed with the same connection.\n        self._request_count = 0\n        self._keepalive = False\n        self._current_request: _Request | None = None\n        self._manager: Server[_Request] | None = manager\n        self._request_handler: _RequestHandler[_Request] | None = (\n            manager.request_handler\n        )\n        self._request_factory: _RequestFactory[_Request] | None = (\n            manager.request_factory\n        )\n\n        self.max_line_size = max_line_size\n        self.max_headers = max_headers\n        self.max_field_size = max_field_size\n\n        self._tcp_keepalive = tcp_keepalive\n        # placeholder to be replaced on keepalive timeout setup\n        self._next_keepalive_close_time = 0.0\n        self._keepalive_handle: asyncio.Handle | None = None\n        self._keepalive_timeout = keepalive_timeout\n        self._lingering_time = float(lingering_time)\n\n        self._messages: deque[_MsgType] = deque()\n        self._message_tail = b\"\"\n        self._data_received_cb: Callable[[], None] | None = None\n\n        self._waiter: asyncio.Future[None] | None = None\n        self._handler_waiter: asyncio.Future[None] | None = None\n        self._task_handler: asyncio.Task[None] | None = None\n\n        self._upgrade = False\n        self._payload_parser: Any = None\n        self._request_parser: HttpRequestParser | None = HttpRequestParser(\n            self,\n            loop,\n            read_bufsize,\n            max_line_size=max_line_size,\n            max_field_size=max_field_size,\n            max_headers=max_headers,\n            payload_exception=RequestPayloadError,\n            auto_decompress=auto_decompress,\n        )\n\n        self._timeout_ceil_threshold: float = 5\n        try:\n            self._timeout_ceil_threshold = float(timeout_ceil_threshold)\n        except (TypeError, ValueError):\n            pass\n\n        self.logger = logger\n        self.access_log = access_log\n        if access_log:\n            if issubclass(access_log_class, AbstractAsyncAccessLogger):\n                self.access_logger: AbstractAsyncAccessLogger | None = (\n                    access_log_class()\n                )\n            else:\n                access_logger = access_log_class(access_log, access_log_format)\n                self.access_logger = AccessLoggerWrapper(\n                    access_logger,\n                    self._loop,\n                )\n            self._logging_enabled = self.access_logger.enabled\n        else:\n            self.access_logger = None\n            self._logging_enabled = False\n\n        self._close = False\n        self._force_close = False\n        self._request_in_progress = False\n        self._cache: dict[str, Any] = {}\n\n    def __repr__(self) -> str:\n        return \"<{} {}>\".format(\n            self.__class__.__name__,\n            \"connected\" if self.transport is not None else \"disconnected\",\n        )\n\n    @under_cached_property\n    def ssl_context(self) -> Optional[\"ssl.SSLContext\"]:\n        \"\"\"Return SSLContext if available.\"\"\"\n        return (\n            None\n            if self.transport is None\n            else self.transport.get_extra_info(\"sslcontext\")\n        )\n\n    @under_cached_property\n    def peername(\n        self,\n    ) -> str | tuple[str, int, int, int] | tuple[str, int] | None:\n        \"\"\"Return peername if available.\"\"\"\n        return (\n            None\n            if self.transport is None\n            else self.transport.get_extra_info(\"peername\")\n        )\n\n    @property\n    def keepalive_timeout(self) -> float:\n        return self._keepalive_timeout\n\n    async def shutdown(self, timeout: float | None = 15.0) -> None:\n        \"\"\"Do worker process exit preparations.\n\n        We need to clean up everything and stop accepting requests.\n        It is especially important for keep-alive connections.\n        \"\"\"\n        self._force_close = True\n\n        if self._keepalive_handle is not None:\n            self._keepalive_handle.cancel()\n\n        # Wait for graceful handler completion\n        if self._request_in_progress:\n            # The future is only created when we are shutting\n            # down while the handler is still processing a request\n            # to avoid creating a future for every request.\n            self._handler_waiter = self._loop.create_future()\n            try:\n                async with ceil_timeout(timeout):\n                    await self._handler_waiter\n            except (asyncio.CancelledError, asyncio.TimeoutError):\n                self._handler_waiter = None\n                if (\n                    sys.version_info >= (3, 11)\n                    and (task := asyncio.current_task())\n                    and task.cancelling()\n                ):\n                    raise\n        # Then cancel handler and wait\n        try:\n            async with ceil_timeout(timeout):\n                if self._current_request is not None:\n                    self._current_request._cancel(asyncio.CancelledError())\n\n                if self._task_handler is not None and not self._task_handler.done():\n                    await asyncio.shield(self._task_handler)\n        except (asyncio.CancelledError, asyncio.TimeoutError):\n            if (\n                sys.version_info >= (3, 11)\n                and (task := asyncio.current_task())\n                and task.cancelling()\n            ):\n                raise\n\n        # force-close non-idle handler\n        if self._task_handler is not None:\n            self._task_handler.cancel()\n\n        self.force_close()\n\n    def connection_made(self, transport: asyncio.BaseTransport) -> None:\n        super().connection_made(transport)\n\n        real_transport = cast(asyncio.Transport, transport)\n        if self._tcp_keepalive:\n            tcp_keepalive(real_transport)\n\n        assert self._manager is not None\n        self._manager.connection_made(self, real_transport)\n\n        loop = self._loop\n        if sys.version_info >= (3, 12):\n            task = asyncio.Task(self.start(), loop=loop, eager_start=True)\n        else:\n            task = loop.create_task(self.start())\n        self._task_handler = task\n\n    def connection_lost(self, exc: BaseException | None) -> None:\n        if self._manager is None:\n            return\n        self._manager.connection_lost(self, exc)\n\n        # Grab value before setting _manager to None.\n        handler_cancellation = self._manager.handler_cancellation\n\n        self.force_close()\n        super().connection_lost(exc)\n        self._manager = None\n        self._request_factory = None\n        self._request_handler = None\n        self._request_parser = None\n\n        if self._keepalive_handle is not None:\n            self._keepalive_handle.cancel()\n\n        if self._current_request is not None:\n            if exc is None:\n                exc = ConnectionResetError(\"Connection lost\")\n            self._current_request._cancel(exc)\n\n        if handler_cancellation and self._task_handler is not None:\n            self._task_handler.cancel()\n\n        self._task_handler = None\n\n        if self._payload_parser is not None:\n            self._payload_parser.feed_eof()\n            self._payload_parser = None\n\n    def set_parser(\n        self, parser: Any, data_received_cb: Callable[[], None] | None = None\n    ) -> None:\n        # Actual type is WebReader\n        assert self._payload_parser is None\n\n        self._payload_parser = parser\n        self._data_received_cb = data_received_cb\n\n        if self._message_tail:\n            self._payload_parser.feed_data(self._message_tail)\n            self._message_tail = b\"\"\n\n    def eof_received(self) -> None:\n        pass\n\n    def data_received(self, data: bytes) -> None:\n        if self._force_close or self._close:\n            return\n        # parse http messages\n        messages: Sequence[_MsgType]\n        if self._payload_parser is None and not self._upgrade:\n            assert self._request_parser is not None\n            try:\n                messages, upgraded, tail = self._request_parser.feed_data(data)\n            except HttpProcessingError as exc:\n                messages = [\n                    (_ErrInfo(status=400, exc=exc, message=exc.message), EMPTY_PAYLOAD)\n                ]\n                upgraded = False\n                tail = b\"\"\n\n            for msg, payload in messages or ():\n                self._request_count += 1\n                self._messages.append((msg, payload))\n\n            waiter = self._waiter\n            if messages and waiter is not None and not waiter.done():\n                # don't set result twice\n                waiter.set_result(None)\n\n            self._upgrade = upgraded\n            if upgraded and tail:\n                self._message_tail = tail\n\n        # no parser, just store\n        elif self._payload_parser is None and self._upgrade and data:\n            self._message_tail += data\n\n        # feed payload\n        elif data:\n            if self._data_received_cb is not None:\n                self._data_received_cb()\n            eof, tail = self._payload_parser.feed_data(data)\n            if eof:\n                self.close()\n\n    def keep_alive(self, val: bool) -> None:\n        \"\"\"Set keep-alive connection mode.\n\n        :param bool val: new state.\n        \"\"\"\n        self._keepalive = val\n        if self._keepalive_handle:\n            self._keepalive_handle.cancel()\n            self._keepalive_handle = None\n\n    def close(self) -> None:\n        \"\"\"Close connection.\n\n        Stop accepting new pipelining messages and close\n        connection when handlers done processing messages.\n        \"\"\"\n        self._close = True\n        if self._waiter:\n            self._waiter.cancel()\n\n    def force_close(self) -> None:\n        \"\"\"Forcefully close connection.\"\"\"\n        self._force_close = True\n        if self._waiter:\n            self._waiter.cancel()\n        if self.transport is not None:\n            self.transport.close()\n            self.transport = None\n\n    async def log_access(\n        self,\n        request: BaseRequest,\n        response: StreamResponse,\n        request_start: float | None,\n    ) -> None:\n        if self._logging_enabled and self.access_logger is not None:\n            if TYPE_CHECKING:\n                assert request_start is not None\n            await self.access_logger.log(request, response, request_start)\n\n    def log_debug(self, *args: Any, **kw: Any) -> None:\n        if self._loop.get_debug():\n            self.logger.debug(*args, **kw)\n\n    def log_exception(self, *args: Any, **kw: Any) -> None:\n        self.logger.exception(*args, **kw)\n\n    def _process_keepalive(self) -> None:\n        self._keepalive_handle = None\n        if self._force_close or not self._keepalive:\n            return\n\n        loop = self._loop\n        now = loop.time()\n        close_time = self._next_keepalive_close_time\n        if now < close_time:\n            # Keep alive close check fired too early, reschedule\n            self._keepalive_handle = loop.call_at(close_time, self._process_keepalive)\n            return\n\n        # handler in idle state\n        if self._waiter and not self._waiter.done():\n            self.force_close()\n\n    async def _handle_request(\n        self,\n        request: _Request,\n        start_time: float | None,\n        request_handler: Callable[[_Request], Awaitable[StreamResponse]],\n    ) -> tuple[StreamResponse, bool]:\n        self._request_in_progress = True\n        try:\n            try:\n                self._current_request = request\n                resp = await request_handler(request)\n            finally:\n                self._current_request = None\n        except HTTPException as exc:\n            resp = Response(\n                status=exc.status, reason=exc.reason, text=exc.text, headers=exc.headers\n            )\n            resp._cookies = exc._cookies\n            resp, reset = await self.finish_response(request, resp, start_time)\n        except asyncio.CancelledError:\n            raise\n        except asyncio.TimeoutError as exc:\n            self.log_debug(\"Request handler timed out.\", exc_info=exc)\n            resp = self.handle_error(request, 504)\n            resp, reset = await self.finish_response(request, resp, start_time)\n        except Exception as exc:\n            resp = self.handle_error(request, 500, exc)\n            resp, reset = await self.finish_response(request, resp, start_time)\n        else:\n            resp, reset = await self.finish_response(request, resp, start_time)\n        finally:\n            self._request_in_progress = False\n            if self._handler_waiter is not None:\n                self._handler_waiter.set_result(None)\n\n        return resp, reset\n\n    async def start(self) -> None:\n        \"\"\"Process incoming request.\n\n        It reads request line, request headers and request payload, then\n        calls handle_request() method. Subclass has to override\n        handle_request(). start() handles various exceptions in request\n        or response handling. Connection is being closed always unless\n        keep_alive(True) specified.\n        \"\"\"\n        loop = self._loop\n        manager = self._manager\n        assert manager is not None\n        keepalive_timeout = self._keepalive_timeout\n        resp = None\n        assert self._request_factory is not None\n        assert self._request_handler is not None\n\n        while not self._force_close:\n            if not self._messages:\n                try:\n                    # wait for next request\n                    self._waiter = loop.create_future()\n                    await self._waiter\n                finally:\n                    self._waiter = None\n\n            message, payload = self._messages.popleft()\n\n            # time is only fetched if logging is enabled as otherwise\n            # its thrown away and never used.\n            start = loop.time() if self._logging_enabled else None\n\n            manager.requests_count += 1\n            writer = StreamWriter(self, loop)\n            if not isinstance(message, _ErrInfo):\n                request_handler = self._request_handler\n            else:\n                # make request_factory work\n                request_handler = self._make_error_handler(message)\n                message = ERROR\n\n            # Important don't hold a reference to the current task\n            # as on traceback it will prevent the task from being\n            # collected and will cause a memory leak.\n            request = self._request_factory(\n                message,\n                payload,\n                self,\n                writer,\n                self._task_handler or asyncio.current_task(loop),  # type: ignore[arg-type]\n            )\n            try:\n                # a new task is used for copy context vars (#3406)\n                coro = self._handle_request(request, start, request_handler)\n                if sys.version_info >= (3, 12):\n                    task = asyncio.Task(coro, loop=loop, eager_start=True)\n                else:\n                    task = loop.create_task(coro)\n                try:\n                    resp, reset = await task\n                except ConnectionError:\n                    self.log_debug(\"Ignored premature client disconnection\")\n                    break\n\n                # Drop the processed task from asyncio.Task.all_tasks() early\n                del task\n                # https://github.com/python/mypy/issues/14309\n                if reset:  # type: ignore[possibly-undefined]\n                    self.log_debug(\"Ignored premature client disconnection 2\")\n                    break\n\n                # notify server about keep-alive\n                self._keepalive = bool(resp.keep_alive)\n\n                # check payload\n                if not payload.is_eof():\n                    lingering_time = self._lingering_time\n                    # Could be force closed while awaiting above tasks.\n                    if not self._force_close and lingering_time:  # type: ignore[redundant-expr]\n                        self.log_debug(\n                            \"Start lingering close timer for %s sec.\", lingering_time\n                        )\n\n                        now = loop.time()\n                        end_t = now + lingering_time\n\n                        try:\n                            while not payload.is_eof() and now < end_t:\n                                async with ceil_timeout(end_t - now):\n                                    # read and ignore\n                                    await payload.readany()\n                                now = loop.time()\n                        except (asyncio.CancelledError, asyncio.TimeoutError):\n                            if (\n                                sys.version_info >= (3, 11)\n                                and (t := asyncio.current_task())\n                                and t.cancelling()\n                            ):\n                                raise\n\n                    # if payload still uncompleted\n                    if not payload.is_eof() and not self._force_close:\n                        self.log_debug(\"Uncompleted request.\")\n                        self.close()\n\n                payload.set_exception(_PAYLOAD_ACCESS_ERROR)\n\n            except asyncio.CancelledError:\n                self.log_debug(\"Ignored premature client disconnection\")\n                self.force_close()\n                raise\n            except Exception as exc:\n                self.log_exception(\"Unhandled exception\", exc_info=exc)\n                self.force_close()\n            except BaseException:\n                self.force_close()\n                raise\n            finally:\n                request._task = None  # type: ignore[assignment] # Break reference cycle in case of exception\n                if self.transport is None and resp is not None:\n                    self.log_debug(\"Ignored premature client disconnection.\")\n\n            if self._keepalive and not self._close and not self._force_close:\n                # start keep-alive timer\n                close_time = loop.time() + keepalive_timeout\n                self._next_keepalive_close_time = close_time\n                if self._keepalive_handle is None:\n                    self._keepalive_handle = loop.call_at(\n                        close_time, self._process_keepalive\n                    )\n            else:\n                break\n\n        # remove handler, close transport if no handlers left\n        if not self._force_close:\n            self._task_handler = None\n            if self.transport is not None:\n                self.transport.close()\n\n    async def finish_response(\n        self, request: BaseRequest, resp: StreamResponse, start_time: float | None\n    ) -> tuple[StreamResponse, bool]:\n        \"\"\"Prepare the response and write_eof, then log access.\n\n        This has to\n        be called within the context of any exception so the access logger\n        can get exception information. Returns True if the client disconnects\n        prematurely.\n        \"\"\"\n        request._finish()\n        if self._request_parser is not None:\n            self._request_parser.set_upgraded(False)\n            self._upgrade = False\n            if self._message_tail:\n                self._request_parser.feed_data(self._message_tail)\n                self._message_tail = b\"\"\n        try:\n            prepare_meth = resp.prepare\n        except AttributeError:\n            if resp is None:\n                self.log_exception(\"Missing return statement on request handler\")  # type: ignore[unreachable]\n            else:\n                self.log_exception(\n                    f\"Web-handler should return a response instance, got {resp!r}\"\n                )\n            exc = HTTPInternalServerError()\n            resp = Response(\n                status=exc.status, reason=exc.reason, text=exc.text, headers=exc.headers\n            )\n            prepare_meth = resp.prepare\n        try:\n            await prepare_meth(request)\n            await resp.write_eof()\n        except ConnectionError:\n            await self.log_access(request, resp, start_time)\n            return resp, True\n\n        await self.log_access(request, resp, start_time)\n        return resp, False\n\n    def handle_error(\n        self,\n        request: BaseRequest,\n        status: int = 500,\n        exc: BaseException | None = None,\n        message: str | None = None,\n    ) -> StreamResponse:\n        \"\"\"Handle errors.\n\n        Returns HTTP response with specific status code. Logs additional\n        information. It always closes current connection.\n        \"\"\"\n        if self._request_count == 1 and isinstance(exc, BadHttpMethod):\n            # BadHttpMethod is common when a client sends non-HTTP\n            # or encrypted traffic to an HTTP port. This is expected\n            # to happen when connected to the public internet so we log\n            # it at the debug level as to not fill logs with noise.\n            self.logger.debug(\n                \"Error handling request from %s\", request.remote, exc_info=exc\n            )\n        else:\n            self.log_exception(\n                \"Error handling request from %s\", request.remote, exc_info=exc\n            )\n\n        # some data already got sent, connection is broken\n        if request.writer.output_size > 0:\n            raise ConnectionError(\n                \"Response is sent already, cannot send another response \"\n                \"with the error message\"\n            )\n\n        ct = \"text/plain\"\n        if status == HTTPStatus.INTERNAL_SERVER_ERROR:\n            title = f\"{HTTPStatus.INTERNAL_SERVER_ERROR.value} {HTTPStatus.INTERNAL_SERVER_ERROR.phrase}\"\n            msg = HTTPStatus.INTERNAL_SERVER_ERROR.description\n            tb = None\n            if self._loop.get_debug():\n                with suppress(Exception):\n                    tb = traceback.format_exc()\n\n            if \"text/html\" in request.headers.get(\"Accept\", \"\"):\n                if tb:\n                    tb = html_escape(tb)\n                    msg = f\"<h2>Traceback:</h2>\\n<pre>{tb}</pre>\"\n                message = (\n                    \"<html><head>\"\n                    f\"<title>{title}</title>\"\n                    f\"</head><body>\\n<h1>{title}</h1>\"\n                    f\"\\n{msg}\\n</body></html>\\n\"\n                )\n                ct = \"text/html\"\n            else:\n                if tb:\n                    msg = tb\n                message = title + \"\\n\\n\" + msg\n\n        resp = Response(status=status, text=message, content_type=ct)\n        resp.force_close()\n\n        return resp\n\n    def _make_error_handler(\n        self, err_info: _ErrInfo\n    ) -> Callable[[BaseRequest], Awaitable[StreamResponse]]:\n        async def handler(request: BaseRequest) -> StreamResponse:\n            return self.handle_error(\n                request, err_info.status, err_info.exc, err_info.message\n            )\n\n        return handler\n"
  },
  {
    "path": "aiohttp/web_request.py",
    "content": "import asyncio\nimport datetime\nimport io\nimport re\nimport socket\nimport string\nimport sys\nimport tempfile\nimport types\nfrom collections.abc import Iterator, Mapping, MutableMapping\nfrom re import Pattern\nfrom types import MappingProxyType\nfrom typing import TYPE_CHECKING, Any, Final, Optional, TypeVar, cast, overload\nfrom urllib.parse import parse_qsl\n\nfrom multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy\nfrom yarl import URL\n\nfrom . import hdrs\nfrom ._cookie_helpers import parse_cookie_header\nfrom .abc import AbstractStreamWriter\nfrom .helpers import (\n    _SENTINEL,\n    ETAG_ANY,\n    LIST_QUOTED_ETAG_RE,\n    ChainMapProxy,\n    ETag,\n    HeadersMixin,\n    RequestKey,\n    frozen_dataclass_decorator,\n    is_expected_content_type,\n    parse_http_date,\n    reify,\n    sentinel,\n    set_exception,\n)\nfrom .http_parser import RawRequestMessage\nfrom .http_writer import HttpVersion\nfrom .multipart import BodyPartReader, MultipartReader\nfrom .streams import EmptyStreamReader, StreamReader\nfrom .typedefs import (\n    DEFAULT_JSON_DECODER,\n    JSONDecoder,\n    LooseHeaders,\n    RawHeaders,\n    StrOrURL,\n)\nfrom .web_exceptions import (\n    HTTPBadRequest,\n    HTTPRequestEntityTooLarge,\n    HTTPUnsupportedMediaType,\n)\nfrom .web_response import StreamResponse\n\nif sys.version_info >= (3, 11):\n    from typing import Self\nelse:\n    Self = Any\n\n__all__ = (\"BaseRequest\", \"FileField\", \"Request\")\n\n\nif TYPE_CHECKING:\n    from .web_app import Application\n    from .web_protocol import RequestHandler\n    from .web_urldispatcher import UrlMappingMatchInfo\n\n\n_T = TypeVar(\"_T\")\n\n\n@frozen_dataclass_decorator\nclass FileField:\n    name: str\n    filename: str\n    file: io.BufferedReader\n    content_type: str\n    headers: CIMultiDictProxy[str]\n\n\n_TCHAR: Final[str] = string.digits + string.ascii_letters + r\"!#$%&'*+.^_`|~-\"\n# '-' at the end to prevent interpretation as range in a char class\n\n_TOKEN: Final[str] = rf\"[{_TCHAR}]+\"\n\n_QDTEXT: Final[str] = r\"[{}]\".format(\n    r\"\".join(chr(c) for c in (0x09, 0x20, 0x21) + tuple(range(0x23, 0x7F)))\n)\n# qdtext includes 0x5C to escape 0x5D ('\\]')\n# qdtext excludes obs-text (because obsoleted, and encoding not specified)\n\n_QUOTED_PAIR: Final[str] = r\"\\\\[\\t !-~]\"\n\n_QUOTED_STRING: Final[str] = rf'\"(?:{_QUOTED_PAIR}|{_QDTEXT})*\"'\n\n# This does not have a ReDOS/performance concern as long as it used with re.match().\n_FORWARDED_PAIR: Final[str] = rf\"({_TOKEN})=({_TOKEN}|{_QUOTED_STRING})(:\\d{{1,4}})?\"\n\n_QUOTED_PAIR_REPLACE_RE: Final[Pattern[str]] = re.compile(r\"\\\\([\\t !-~])\")\n# same pattern as _QUOTED_PAIR but contains a capture group\n\n_FORWARDED_PAIR_RE: Final[Pattern[str]] = re.compile(_FORWARDED_PAIR)\n\n############################################################\n# HTTP Request\n############################################################\n\n\nclass BaseRequest(MutableMapping[str | RequestKey[Any], Any], HeadersMixin):\n    POST_METHODS = {\n        hdrs.METH_PATCH,\n        hdrs.METH_POST,\n        hdrs.METH_PUT,\n        hdrs.METH_TRACE,\n        hdrs.METH_DELETE,\n    }\n\n    _post: MultiDictProxy[str | bytes | FileField] | None = None\n    _read_bytes: bytes | None = None\n\n    def __init__(\n        self,\n        message: RawRequestMessage,\n        payload: StreamReader,\n        protocol: \"RequestHandler[Self]\",\n        payload_writer: AbstractStreamWriter,\n        task: \"asyncio.Task[None]\",\n        loop: asyncio.AbstractEventLoop,\n        *,\n        client_max_size: int = 1024**2,\n        state: dict[RequestKey[Any] | str, Any] | None = None,\n        scheme: str | None = None,\n        host: str | None = None,\n        remote: str | None = None,\n    ) -> None:\n        self._message = message\n        self._protocol = protocol\n        self._payload_writer = payload_writer\n\n        self._payload = payload\n        self._headers: CIMultiDictProxy[str] = message.headers\n        self._method = message.method\n        self._version = message.version\n        self._cache: dict[str, Any] = {}\n        url = message.url\n        if url.absolute:\n            if scheme is not None:\n                url = url.with_scheme(scheme)\n            if host is not None:\n                url = url.with_host(host)\n            # absolute URL is given,\n            # override auto-calculating url, host, and scheme\n            # all other properties should be good\n            self._cache[\"url\"] = url\n            self._cache[\"host\"] = url.host\n            self._cache[\"scheme\"] = url.scheme\n            self._rel_url = url.relative()\n        else:\n            self._rel_url = url\n            if scheme is not None:\n                self._cache[\"scheme\"] = scheme\n            if host is not None:\n                self._cache[\"host\"] = host\n\n        self._state = {} if state is None else state\n        self._task = task\n        self._client_max_size = client_max_size\n        self._loop = loop\n\n        self._transport_sslcontext = protocol.ssl_context\n        self._transport_peername = protocol.peername\n\n        if remote is not None:\n            self._cache[\"remote\"] = remote\n\n    def clone(\n        self,\n        *,\n        method: str | _SENTINEL = sentinel,\n        rel_url: StrOrURL | _SENTINEL = sentinel,\n        headers: LooseHeaders | _SENTINEL = sentinel,\n        scheme: str | _SENTINEL = sentinel,\n        host: str | _SENTINEL = sentinel,\n        remote: str | _SENTINEL = sentinel,\n        client_max_size: int | _SENTINEL = sentinel,\n    ) -> \"BaseRequest\":\n        \"\"\"Clone itself with replacement some attributes.\n\n        Creates and returns a new instance of Request object. If no parameters\n        are given, an exact copy is returned. If a parameter is not passed, it\n        will reuse the one from the current request object.\n        \"\"\"\n        if self._read_bytes:\n            raise RuntimeError(\"Cannot clone request after reading its content\")\n\n        dct: dict[str, Any] = {}\n        if method is not sentinel:\n            dct[\"method\"] = method\n        if rel_url is not sentinel:\n            new_url: URL = URL(rel_url)\n            dct[\"url\"] = new_url\n            dct[\"path\"] = str(new_url)\n        if headers is not sentinel:\n            # a copy semantic\n            new_headers = CIMultiDictProxy(CIMultiDict(headers))\n            dct[\"headers\"] = new_headers\n            dct[\"raw_headers\"] = tuple(\n                (k.encode(\"utf-8\"), v.encode(\"utf-8\")) for k, v in new_headers.items()\n            )\n\n        message = self._message._replace(**dct)\n\n        kwargs: dict[str, str] = {}\n        if scheme is not sentinel:\n            kwargs[\"scheme\"] = scheme\n        if host is not sentinel:\n            kwargs[\"host\"] = host\n        if remote is not sentinel:\n            kwargs[\"remote\"] = remote\n        if client_max_size is sentinel:\n            client_max_size = self._client_max_size\n\n        return self.__class__(\n            message,\n            self._payload,\n            self._protocol,  # type: ignore[arg-type]\n            self._payload_writer,\n            self._task,\n            self._loop,\n            client_max_size=client_max_size,\n            state=self._state.copy(),\n            **kwargs,\n        )\n\n    @property\n    def task(self) -> \"asyncio.Task[None]\":\n        return self._task\n\n    @property\n    def protocol(self) -> \"RequestHandler[Self]\":\n        return self._protocol\n\n    @property\n    def transport(self) -> asyncio.Transport | None:\n        return self._protocol.transport\n\n    @property\n    def writer(self) -> AbstractStreamWriter:\n        return self._payload_writer\n\n    @property\n    def client_max_size(self) -> int:\n        return self._client_max_size\n\n    @reify\n    def rel_url(self) -> URL:\n        return self._rel_url\n\n    # MutableMapping API\n\n    @overload  # type: ignore[override]\n    def __getitem__(self, key: RequestKey[_T]) -> _T: ...\n\n    @overload\n    def __getitem__(self, key: str) -> Any: ...\n\n    def __getitem__(self, key: str | RequestKey[_T]) -> Any:\n        return self._state[key]\n\n    @overload  # type: ignore[override]\n    def __setitem__(self, key: RequestKey[_T], value: _T) -> None: ...\n\n    @overload\n    def __setitem__(self, key: str, value: Any) -> None: ...\n\n    def __setitem__(self, key: str | RequestKey[_T], value: Any) -> None:\n        self._state[key] = value\n\n    def __delitem__(self, key: str | RequestKey[_T]) -> None:\n        del self._state[key]\n\n    def __len__(self) -> int:\n        return len(self._state)\n\n    def __iter__(self) -> Iterator[str | RequestKey[Any]]:\n        return iter(self._state)\n\n    ########\n\n    @reify\n    def secure(self) -> bool:\n        \"\"\"A bool indicating if the request is handled with SSL.\"\"\"\n        return self.scheme == \"https\"\n\n    @reify\n    def forwarded(self) -> tuple[Mapping[str, str], ...]:\n        \"\"\"A tuple containing all parsed Forwarded header(s).\n\n        Makes an effort to parse Forwarded headers as specified by RFC 7239:\n\n        - It adds one (immutable) dictionary per Forwarded 'field-value', ie\n          per proxy. The element corresponds to the data in the Forwarded\n          field-value added by the first proxy encountered by the client. Each\n          subsequent item corresponds to those added by later proxies.\n        - It checks that every value has valid syntax in general as specified\n          in section 4: either a 'token' or a 'quoted-string'.\n        - It un-escapes found escape sequences.\n        - It does NOT validate 'by' and 'for' contents as specified in section\n          6.\n        - It does NOT validate 'host' contents (Host ABNF).\n        - It does NOT validate 'proto' contents for valid URI scheme names.\n\n        Returns a tuple containing one or more immutable dicts\n        \"\"\"\n        elems = []\n        for field_value in self._message.headers.getall(hdrs.FORWARDED, ()):\n            length = len(field_value)\n            pos = 0\n            need_separator = False\n            elem: dict[str, str] = {}\n            elems.append(types.MappingProxyType(elem))\n            while 0 <= pos < length:\n                match = _FORWARDED_PAIR_RE.match(field_value, pos)\n                if match is not None:  # got a valid forwarded-pair\n                    if need_separator:\n                        # bad syntax here, skip to next comma\n                        pos = field_value.find(\",\", pos)\n                    else:\n                        name, value, port = match.groups()\n                        if value[0] == '\"':\n                            # quoted string: remove quotes and unescape\n                            value = _QUOTED_PAIR_REPLACE_RE.sub(r\"\\1\", value[1:-1])\n                        if port:\n                            value += port\n                        elem[name.lower()] = value\n                        pos += len(match.group(0))\n                        need_separator = True\n                elif field_value[pos] == \",\":  # next forwarded-element\n                    need_separator = False\n                    elem = {}\n                    elems.append(types.MappingProxyType(elem))\n                    pos += 1\n                elif field_value[pos] == \";\":  # next forwarded-pair\n                    need_separator = False\n                    pos += 1\n                elif field_value[pos] in \" \\t\":\n                    # Allow whitespace even between forwarded-pairs, though\n                    # RFC 7239 doesn't. This simplifies code and is in line\n                    # with Postel's law.\n                    pos += 1\n                else:\n                    # bad syntax here, skip to next comma\n                    pos = field_value.find(\",\", pos)\n        return tuple(elems)\n\n    @reify\n    def scheme(self) -> str:\n        \"\"\"A string representing the scheme of the request.\n\n        Hostname is resolved in this order:\n\n        - overridden value by .clone(scheme=new_scheme) call.\n        - type of connection to peer: HTTPS if socket is SSL, HTTP otherwise.\n\n        'http' or 'https'.\n        \"\"\"\n        if self._transport_sslcontext:\n            return \"https\"\n        else:\n            return \"http\"\n\n    @reify\n    def method(self) -> str:\n        \"\"\"Read only property for getting HTTP method.\n\n        The value is upper-cased str like 'GET', 'POST', 'PUT' etc.\n        \"\"\"\n        return self._method\n\n    @reify\n    def version(self) -> HttpVersion:\n        \"\"\"Read only property for getting HTTP version of request.\n\n        Returns aiohttp.protocol.HttpVersion instance.\n        \"\"\"\n        return self._version\n\n    @reify\n    def host(self) -> str:\n        \"\"\"Hostname of the request.\n\n        Hostname is resolved in this order:\n\n        - overridden value by .clone(host=new_host) call.\n        - HOST HTTP header\n        - socket.getfqdn() value\n\n        For example, 'example.com' or 'localhost:8080'.\n\n        For historical reasons, the port number may be included.\n        \"\"\"\n        host = self._message.headers.get(hdrs.HOST)\n        if host is not None:\n            return host\n        return socket.getfqdn()\n\n    @reify\n    def remote(self) -> str | None:\n        \"\"\"Remote IP of client initiated HTTP request.\n\n        The IP is resolved in this order:\n\n        - overridden value by .clone(remote=new_remote) call.\n        - peername of opened socket\n        \"\"\"\n        if self._transport_peername is None:\n            return None\n        if isinstance(self._transport_peername, (list, tuple)):\n            return str(self._transport_peername[0])\n        return str(self._transport_peername)\n\n    @reify\n    def url(self) -> URL:\n        \"\"\"The full URL of the request.\"\"\"\n        # authority is used here because it may include the port number\n        # and we want yarl to parse it correctly\n        return URL.build(scheme=self.scheme, authority=self.host).join(self._rel_url)\n\n    @reify\n    def path(self) -> str:\n        \"\"\"The URL including *PATH INFO* without the host or scheme.\n\n        E.g., ``/app/blog``\n        \"\"\"\n        return self._rel_url.path\n\n    @reify\n    def path_qs(self) -> str:\n        \"\"\"The URL including PATH_INFO and the query string.\n\n        E.g, /app/blog?id=10\n        \"\"\"\n        return str(self._rel_url)\n\n    @reify\n    def raw_path(self) -> str:\n        \"\"\"The URL including raw *PATH INFO* without the host or scheme.\n\n        Warning, the path is unquoted and may contains non valid URL characters\n\n        E.g., ``/my%2Fpath%7Cwith%21some%25strange%24characters``\n        \"\"\"\n        return self._message.path\n\n    @reify\n    def query(self) -> MultiDictProxy[str]:\n        \"\"\"A multidict with all the variables in the query string.\"\"\"\n        return self._rel_url.query\n\n    @reify\n    def query_string(self) -> str:\n        \"\"\"The query string in the URL.\n\n        E.g., id=10\n        \"\"\"\n        return self._rel_url.query_string\n\n    @reify\n    def headers(self) -> CIMultiDictProxy[str]:\n        \"\"\"A case-insensitive multidict proxy with all headers.\"\"\"\n        return self._headers\n\n    @reify\n    def raw_headers(self) -> RawHeaders:\n        \"\"\"A sequence of pairs for all headers.\"\"\"\n        return self._message.raw_headers\n\n    @reify\n    def if_modified_since(self) -> datetime.datetime | None:\n        \"\"\"The value of If-Modified-Since HTTP header, or None.\n\n        This header is represented as a `datetime` object.\n        \"\"\"\n        return parse_http_date(self.headers.get(hdrs.IF_MODIFIED_SINCE))\n\n    @reify\n    def if_unmodified_since(self) -> datetime.datetime | None:\n        \"\"\"The value of If-Unmodified-Since HTTP header, or None.\n\n        This header is represented as a `datetime` object.\n        \"\"\"\n        return parse_http_date(self.headers.get(hdrs.IF_UNMODIFIED_SINCE))\n\n    @staticmethod\n    def _etag_values(etag_header: str) -> Iterator[ETag]:\n        \"\"\"Extract `ETag` objects from raw header.\"\"\"\n        if etag_header == ETAG_ANY:\n            yield ETag(\n                is_weak=False,\n                value=ETAG_ANY,\n            )\n        else:\n            for match in LIST_QUOTED_ETAG_RE.finditer(etag_header):\n                is_weak, value, garbage = match.group(2, 3, 4)\n                # Any symbol captured by 4th group means\n                # that the following sequence is invalid.\n                if garbage:\n                    break\n\n                yield ETag(\n                    is_weak=bool(is_weak),\n                    value=value,\n                )\n\n    @classmethod\n    def _if_match_or_none_impl(\n        cls, header_value: str | None\n    ) -> tuple[ETag, ...] | None:\n        if not header_value:\n            return None\n\n        return tuple(cls._etag_values(header_value))\n\n    @reify\n    def if_match(self) -> tuple[ETag, ...] | None:\n        \"\"\"The value of If-Match HTTP header, or None.\n\n        This header is represented as a `tuple` of `ETag` objects.\n        \"\"\"\n        return self._if_match_or_none_impl(self.headers.get(hdrs.IF_MATCH))\n\n    @reify\n    def if_none_match(self) -> tuple[ETag, ...] | None:\n        \"\"\"The value of If-None-Match HTTP header, or None.\n\n        This header is represented as a `tuple` of `ETag` objects.\n        \"\"\"\n        return self._if_match_or_none_impl(self.headers.get(hdrs.IF_NONE_MATCH))\n\n    @reify\n    def if_range(self) -> datetime.datetime | None:\n        \"\"\"The value of If-Range HTTP header, or None.\n\n        This header is represented as a `datetime` object.\n        \"\"\"\n        return parse_http_date(self.headers.get(hdrs.IF_RANGE))\n\n    @reify\n    def keep_alive(self) -> bool:\n        \"\"\"Is keepalive enabled by client?\"\"\"\n        return not self._message.should_close\n\n    @reify\n    def cookies(self) -> Mapping[str, str]:\n        \"\"\"Return request cookies.\n\n        A read-only dictionary-like object.\n        \"\"\"\n        # Use parse_cookie_header for RFC 6265 compliant Cookie header parsing\n        # that accepts special characters in cookie names (fixes #2683)\n        parsed = parse_cookie_header(self.headers.get(hdrs.COOKIE, \"\"))\n        # Extract values from Morsel objects\n        return MappingProxyType({name: morsel.value for name, morsel in parsed})\n\n    @reify\n    def http_range(self) -> \"slice[int, int, int]\":\n        \"\"\"The content of Range HTTP header.\n\n        Return a slice instance.\n\n        \"\"\"\n        rng = self._headers.get(hdrs.RANGE)\n        start, end = None, None\n        if rng is not None:\n            try:\n                pattern = r\"^bytes=(\\d*)-(\\d*)$\"\n                start, end = re.findall(pattern, rng, re.ASCII)[0]\n            except IndexError:  # pattern was not found in header\n                raise ValueError(\"range not in acceptable format\")\n\n            end = int(end) if end else None\n            start = int(start) if start else None\n\n            if start is None and end is not None:\n                # end with no start is to return tail of content\n                start = -end\n                end = None\n\n            if start is not None and end is not None:\n                # end is inclusive in range header, exclusive for slice\n                end += 1\n\n                if start >= end:\n                    raise ValueError(\"start cannot be after end\")\n\n            if start is end is None:  # No valid range supplied\n                raise ValueError(\"No start or end of range specified\")\n\n        return slice(start, end, 1)\n\n    @reify\n    def content(self) -> StreamReader:\n        \"\"\"Return raw payload stream.\"\"\"\n        return self._payload\n\n    @property\n    def can_read_body(self) -> bool:\n        \"\"\"Return True if request's HTTP BODY can be read, False otherwise.\"\"\"\n        return not self._payload.at_eof()\n\n    @reify\n    def body_exists(self) -> bool:\n        \"\"\"Return True if request has HTTP BODY, False otherwise.\"\"\"\n        return type(self._payload) is not EmptyStreamReader\n\n    async def release(self) -> None:\n        \"\"\"Release request.\n\n        Eat unread part of HTTP BODY if present.\n        \"\"\"\n        while not self._payload.at_eof():\n            await self._payload.readany()\n\n    async def read(self) -> bytes:\n        \"\"\"Read request body if present.\n\n        Returns bytes object with full request content.\n        \"\"\"\n        if self._read_bytes is None:\n            body = bytearray()\n            while True:\n                chunk = await self._payload.readany()\n                body.extend(chunk)\n                if self._client_max_size:\n                    body_size = len(body)\n                    if body_size > self._client_max_size:\n                        raise HTTPRequestEntityTooLarge(\n                            max_size=self._client_max_size, actual_size=body_size\n                        )\n                if not chunk:\n                    break\n            self._read_bytes = bytes(body)\n        return self._read_bytes\n\n    async def text(self) -> str:\n        \"\"\"Return BODY as text using encoding from .charset.\"\"\"\n        bytes_body = await self.read()\n        encoding = self.charset or \"utf-8\"\n        try:\n            return bytes_body.decode(encoding)\n        except LookupError:\n            raise HTTPUnsupportedMediaType()\n\n    async def json(\n        self,\n        *,\n        loads: JSONDecoder = DEFAULT_JSON_DECODER,\n        content_type: str | None = \"application/json\",\n    ) -> Any:\n        \"\"\"Return BODY as JSON.\"\"\"\n        body = await self.text()\n        if content_type:\n            if not is_expected_content_type(self.content_type, content_type):\n                raise HTTPBadRequest(\n                    text=(\n                        \"Attempt to decode JSON with \"\n                        \"unexpected mimetype: %s\" % self.content_type\n                    )\n                )\n\n        return loads(body)\n\n    async def multipart(self) -> MultipartReader:\n        \"\"\"Return async iterator to process BODY as multipart.\"\"\"\n        return MultipartReader(\n            self._headers,\n            self._payload,\n            max_field_size=self._protocol.max_field_size,\n            max_headers=self._protocol.max_headers,\n        )\n\n    async def post(self) -> \"MultiDictProxy[str | bytes | FileField]\":\n        \"\"\"Return POST parameters.\"\"\"\n        if self._post is not None:\n            return self._post\n        if self._method not in self.POST_METHODS:\n            self._post = MultiDictProxy(MultiDict())\n            return self._post\n\n        content_type = self.content_type\n        if content_type not in (\n            \"\",\n            \"application/x-www-form-urlencoded\",\n            \"multipart/form-data\",\n        ):\n            self._post = MultiDictProxy(MultiDict())\n            return self._post\n\n        out: MultiDict[str | bytes | FileField] = MultiDict()\n\n        if content_type == \"multipart/form-data\":\n            multipart = await self.multipart()\n            max_size = self._client_max_size\n\n            size = 0\n            while (field := await multipart.next()) is not None:\n                field_ct = field.headers.get(hdrs.CONTENT_TYPE)\n\n                if isinstance(field, BodyPartReader):\n                    if field.name is None:\n                        raise ValueError(\"Multipart field missing name.\")\n\n                    # Note that according to RFC 7578, the Content-Type header\n                    # is optional, even for files, so we can't assume it's\n                    # present.\n                    # https://tools.ietf.org/html/rfc7578#section-4.4\n                    if field.filename:\n                        # store file in temp file\n                        tmp = await self._loop.run_in_executor(\n                            None, tempfile.TemporaryFile\n                        )\n                        while chunk := await field.read_chunk(size=2**18):\n                            async for decoded_chunk in field.decode_iter(chunk):\n                                await self._loop.run_in_executor(\n                                    None, tmp.write, decoded_chunk\n                                )\n                                size += len(decoded_chunk)\n                                if 0 < max_size < size:\n                                    await self._loop.run_in_executor(None, tmp.close)\n                                    raise HTTPRequestEntityTooLarge(\n                                        max_size=max_size, actual_size=size\n                                    )\n                        await self._loop.run_in_executor(None, tmp.seek, 0)\n\n                        if field_ct is None:\n                            field_ct = \"application/octet-stream\"\n\n                        ff = FileField(\n                            field.name,\n                            field.filename,\n                            cast(io.BufferedReader, tmp),\n                            field_ct,\n                            field.headers,\n                        )\n                        out.add(field.name, ff)\n                    else:\n                        # deal with ordinary data\n                        raw_data = bytearray()\n                        while chunk := await field.read_chunk():\n                            size += len(chunk)\n                            if 0 < max_size < size:\n                                raise HTTPRequestEntityTooLarge(\n                                    max_size=max_size, actual_size=size\n                                )\n                            raw_data.extend(chunk)\n\n                        value = bytearray()\n                        # form-data doesn't support compression, so don't need to check size again.\n                        async for d in field.decode_iter(raw_data):  # type: ignore[arg-type]\n                            value.extend(d)\n\n                        if field_ct is None or field_ct.startswith(\"text/\"):\n                            charset = field.get_charset(default=\"utf-8\")\n                            out.add(field.name, value.decode(charset))\n                        else:\n                            out.add(field.name, value)  # type: ignore[arg-type]\n                else:\n                    raise ValueError(\n                        \"To decode nested multipart you need to use custom reader\",\n                    )\n        else:\n            data = await self.read()\n            if data:\n                charset = self.charset or \"utf-8\"\n                bytes_query = data.rstrip()\n                try:\n                    query = bytes_query.decode(charset)\n                except LookupError:\n                    raise HTTPUnsupportedMediaType()\n                out.extend(\n                    parse_qsl(qs=query, keep_blank_values=True, encoding=charset)\n                )\n\n        self._post = MultiDictProxy(out)\n        return self._post\n\n    def get_extra_info(self, name: str, default: Any = None) -> Any:\n        \"\"\"Extra info from protocol transport\"\"\"\n        transport = self._protocol.transport\n        if transport is None:\n            return default\n\n        return transport.get_extra_info(name, default)\n\n    def __repr__(self) -> str:\n        ascii_encodable_path = self.path.encode(\"ascii\", \"backslashreplace\").decode(\n            \"ascii\"\n        )\n        return f\"<{self.__class__.__name__} {self._method} {ascii_encodable_path} >\"\n\n    def __eq__(self, other: object) -> bool:\n        return id(self) == id(other)\n\n    def __bool__(self) -> bool:\n        return True\n\n    async def _prepare_hook(self, response: StreamResponse) -> None:\n        return\n\n    def _cancel(self, exc: BaseException) -> None:\n        set_exception(self._payload, exc)\n\n    def _finish(self) -> None:\n        if self._post is None or self.content_type != \"multipart/form-data\":\n            return\n\n        # NOTE: Release file descriptors for the\n        # NOTE: `tempfile.Temporaryfile`-created `_io.BufferedRandom`\n        # NOTE: instances of files sent within multipart request body\n        # NOTE: via HTTP POST request.\n        for file_name, file_field_object in self._post.items():\n            if isinstance(file_field_object, FileField):\n                file_field_object.file.close()\n\n\nclass Request(BaseRequest):\n\n    _match_info: Optional[\"UrlMappingMatchInfo\"] = None\n\n    def clone(\n        self,\n        *,\n        method: str | _SENTINEL = sentinel,\n        rel_url: StrOrURL | _SENTINEL = sentinel,\n        headers: LooseHeaders | _SENTINEL = sentinel,\n        scheme: str | _SENTINEL = sentinel,\n        host: str | _SENTINEL = sentinel,\n        remote: str | _SENTINEL = sentinel,\n        client_max_size: int | _SENTINEL = sentinel,\n    ) -> \"Request\":\n        ret = super().clone(\n            method=method,\n            rel_url=rel_url,\n            headers=headers,\n            scheme=scheme,\n            host=host,\n            remote=remote,\n            client_max_size=client_max_size,\n        )\n        new_ret = cast(Request, ret)\n        new_ret._match_info = self._match_info\n        return new_ret\n\n    @reify\n    def match_info(self) -> \"UrlMappingMatchInfo\":\n        \"\"\"Result of route resolving.\"\"\"\n        match_info = self._match_info\n        assert match_info is not None\n        return match_info\n\n    @property\n    def app(self) -> \"Application\":\n        \"\"\"Application instance.\"\"\"\n        match_info = self._match_info\n        assert match_info is not None\n        return match_info.current_app\n\n    @property\n    def config_dict(self) -> ChainMapProxy:\n        match_info = self._match_info\n        assert match_info is not None\n        lst = match_info.apps\n        app = self.app\n        idx = lst.index(app)\n        sublist = list(reversed(lst[: idx + 1]))\n        return ChainMapProxy(sublist)\n\n    async def _prepare_hook(self, response: StreamResponse) -> None:\n        match_info = self._match_info\n        if match_info is None:\n            return\n        for app in match_info._apps:\n            if on_response_prepare := app.on_response_prepare:\n                await on_response_prepare.send(self, response)\n"
  },
  {
    "path": "aiohttp/web_response.py",
    "content": "import asyncio\nimport datetime\nimport enum\nimport json\nimport math\nimport time\nimport warnings\nfrom collections.abc import Iterator, MutableMapping\nfrom concurrent.futures import Executor\nfrom http import HTTPStatus\nfrom typing import TYPE_CHECKING, Any, Optional, TypeVar, Union, cast, overload\n\nfrom multidict import CIMultiDict, istr\n\nfrom . import hdrs, payload\nfrom .abc import AbstractStreamWriter\nfrom .compression_utils import ZLibCompressor\nfrom .helpers import (\n    ETAG_ANY,\n    QUOTED_ETAG_RE,\n    CookieMixin,\n    ETag,\n    HeadersMixin,\n    ResponseKey,\n    must_be_empty_body,\n    parse_http_date,\n    populate_with_cookies,\n    rfc822_formatted_time,\n    sentinel,\n    should_remove_content_length,\n    validate_etag_value,\n)\nfrom .http import SERVER_SOFTWARE, HttpVersion10, HttpVersion11\nfrom .payload import Payload\nfrom .typedefs import JSONBytesEncoder, JSONEncoder, LooseHeaders\n\nREASON_PHRASES = {http_status.value: http_status.phrase for http_status in HTTPStatus}\nLARGE_BODY_SIZE = 1024**2\n\n__all__ = (\n    \"ContentCoding\",\n    \"StreamResponse\",\n    \"Response\",\n    \"json_response\",\n    \"json_bytes_response\",\n)\n\n\nif TYPE_CHECKING:\n    from .web_request import BaseRequest\n\n\n_T = TypeVar(\"_T\")\n\n\n# TODO(py311): Convert to StrEnum for wider use\nclass ContentCoding(enum.Enum):\n    # The content codings that we have support for.\n    #\n    # Additional registered codings are listed at:\n    # https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding\n    deflate = \"deflate\"\n    gzip = \"gzip\"\n    identity = \"identity\"\n\n\nCONTENT_CODINGS = {coding.value: coding for coding in ContentCoding}\n\n############################################################\n# HTTP Response classes\n############################################################\n\n\nclass StreamResponse(\n    MutableMapping[str | ResponseKey[Any], Any], HeadersMixin, CookieMixin\n):\n\n    _body: None | bytes | bytearray | Payload\n    _length_check = True\n    _body = None\n    _keep_alive: bool | None = None\n    _chunked: bool = False\n    _compression: bool = False\n    _compression_strategy: int | None = None\n    _compression_force: ContentCoding | None = None\n    _req: Optional[\"BaseRequest\"] = None\n    _payload_writer: AbstractStreamWriter | None = None\n    _eof_sent: bool = False\n    _must_be_empty_body: bool | None = None\n    _body_length = 0\n    _send_headers_immediately = True\n\n    def __init__(\n        self,\n        *,\n        status: int = 200,\n        reason: str | None = None,\n        headers: LooseHeaders | None = None,\n        _real_headers: CIMultiDict[str] | None = None,\n    ) -> None:\n        \"\"\"Initialize a new stream response object.\n\n        _real_headers is an internal parameter used to pass a pre-populated\n        headers object. It is used by the `Response` class to avoid copying\n        the headers when creating a new response object. It is not intended\n        to be used by external code.\n        \"\"\"\n        self._state: dict[str | ResponseKey[Any], Any] = {}\n\n        if _real_headers is not None:\n            self._headers = _real_headers\n        elif headers is not None:\n            self._headers: CIMultiDict[str] = CIMultiDict(headers)\n        else:\n            self._headers = CIMultiDict()\n\n        self._set_status(status, reason)\n\n    @property\n    def prepared(self) -> bool:\n        return self._eof_sent or self._payload_writer is not None\n\n    @property\n    def task(self) -> \"asyncio.Task[None] | None\":\n        if self._req:\n            return self._req.task\n        else:\n            return None\n\n    @property\n    def status(self) -> int:\n        return self._status\n\n    @property\n    def chunked(self) -> bool:\n        return self._chunked\n\n    @property\n    def compression(self) -> bool:\n        return self._compression\n\n    @property\n    def reason(self) -> str:\n        return self._reason\n\n    def set_status(\n        self,\n        status: int,\n        reason: str | None = None,\n    ) -> None:\n        assert (\n            not self.prepared\n        ), \"Cannot change the response status code after the headers have been sent\"\n        self._set_status(status, reason)\n\n    def _set_status(self, status: int, reason: str | None) -> None:\n        self._status = status\n        if reason is None:\n            reason = REASON_PHRASES.get(self._status, \"\")\n        elif \"\\r\" in reason or \"\\n\" in reason:\n            raise ValueError(\"Reason cannot contain \\\\r or \\\\n\")\n        self._reason = reason\n\n    @property\n    def keep_alive(self) -> bool | None:\n        return self._keep_alive\n\n    def force_close(self) -> None:\n        self._keep_alive = False\n\n    @property\n    def body_length(self) -> int:\n        return self._body_length\n\n    def enable_chunked_encoding(self) -> None:\n        \"\"\"Enables automatic chunked transfer encoding.\"\"\"\n        if hdrs.CONTENT_LENGTH in self._headers:\n            raise RuntimeError(\n                \"You can't enable chunked encoding when a content length is set\"\n            )\n        self._chunked = True\n\n    def enable_compression(\n        self,\n        force: ContentCoding | None = None,\n        strategy: int | None = None,\n    ) -> None:\n        \"\"\"Enables response compression encoding.\"\"\"\n        # Don't enable compression if content is already encoded.\n        # This prevents double compression and provides a safe, predictable behavior\n        # without breaking existing code that may call enable_compression() on\n        # responses that already have Content-Encoding set (e.g., FileResponse\n        # serving pre-compressed files).\n        if hdrs.CONTENT_ENCODING in self._headers:\n            return\n        self._compression = True\n        self._compression_force = force\n        self._compression_strategy = strategy\n\n    @property\n    def headers(self) -> \"CIMultiDict[str]\":\n        return self._headers\n\n    @property\n    def content_length(self) -> int | None:\n        # Just a placeholder for adding setter\n        return super().content_length\n\n    @content_length.setter\n    def content_length(self, value: int | None) -> None:\n        if value is not None:\n            value = int(value)\n            if self._chunked:\n                raise RuntimeError(\n                    \"You can't set content length when chunked encoding is enable\"\n                )\n            self._headers[hdrs.CONTENT_LENGTH] = str(value)\n        else:\n            self._headers.pop(hdrs.CONTENT_LENGTH, None)\n\n    @property\n    def content_type(self) -> str:\n        # Just a placeholder for adding setter\n        return super().content_type\n\n    @content_type.setter\n    def content_type(self, value: str) -> None:\n        self.content_type  # read header values if needed\n        self._content_type = str(value)\n        self._generate_content_type_header()\n\n    @property\n    def charset(self) -> str | None:\n        # Just a placeholder for adding setter\n        return super().charset\n\n    @charset.setter\n    def charset(self, value: str | None) -> None:\n        ctype = self.content_type  # read header values if needed\n        if ctype == \"application/octet-stream\":\n            raise RuntimeError(\n                \"Setting charset for application/octet-stream \"\n                \"doesn't make sense, setup content_type first\"\n            )\n        assert self._content_dict is not None\n        if value is None:\n            self._content_dict.pop(\"charset\", None)\n        else:\n            self._content_dict[\"charset\"] = str(value).lower()\n        self._generate_content_type_header()\n\n    @property\n    def last_modified(self) -> datetime.datetime | None:\n        \"\"\"The value of Last-Modified HTTP header, or None.\n\n        This header is represented as a `datetime` object.\n        \"\"\"\n        return parse_http_date(self._headers.get(hdrs.LAST_MODIFIED))\n\n    @last_modified.setter\n    def last_modified(\n        self, value: int | float | datetime.datetime | str | None\n    ) -> None:\n        if value is None:\n            self._headers.pop(hdrs.LAST_MODIFIED, None)\n        elif isinstance(value, (int, float)):\n            self._headers[hdrs.LAST_MODIFIED] = time.strftime(\n                \"%a, %d %b %Y %H:%M:%S GMT\", time.gmtime(math.ceil(value))\n            )\n        elif isinstance(value, datetime.datetime):\n            self._headers[hdrs.LAST_MODIFIED] = time.strftime(\n                \"%a, %d %b %Y %H:%M:%S GMT\", value.utctimetuple()\n            )\n        elif isinstance(value, str):\n            self._headers[hdrs.LAST_MODIFIED] = value\n        else:\n            msg = f\"Unsupported type for last_modified: {type(value).__name__}\"  # type: ignore[unreachable]\n            raise TypeError(msg)\n\n    @property\n    def etag(self) -> ETag | None:\n        quoted_value = self._headers.get(hdrs.ETAG)\n        if not quoted_value:\n            return None\n        elif quoted_value == ETAG_ANY:\n            return ETag(value=ETAG_ANY)\n        match = QUOTED_ETAG_RE.fullmatch(quoted_value)\n        if not match:\n            return None\n        is_weak, value = match.group(1, 2)\n        return ETag(\n            is_weak=bool(is_weak),\n            value=value,\n        )\n\n    @etag.setter\n    def etag(self, value: ETag | str | None) -> None:\n        if value is None:\n            self._headers.pop(hdrs.ETAG, None)\n        elif (isinstance(value, str) and value == ETAG_ANY) or (\n            isinstance(value, ETag) and value.value == ETAG_ANY\n        ):\n            self._headers[hdrs.ETAG] = ETAG_ANY\n        elif isinstance(value, str):\n            validate_etag_value(value)\n            self._headers[hdrs.ETAG] = f'\"{value}\"'\n        elif isinstance(value, ETag) and isinstance(value.value, str):  # type: ignore[redundant-expr]\n            validate_etag_value(value.value)\n            hdr_value = f'W/\"{value.value}\"' if value.is_weak else f'\"{value.value}\"'\n            self._headers[hdrs.ETAG] = hdr_value\n        else:\n            raise ValueError(\n                f\"Unsupported etag type: {type(value)}. \"\n                f\"etag must be str, ETag or None\"\n            )\n\n    def _generate_content_type_header(\n        self, CONTENT_TYPE: istr = hdrs.CONTENT_TYPE\n    ) -> None:\n        assert self._content_dict is not None\n        assert self._content_type is not None\n        params = \"; \".join(f\"{k}={v}\" for k, v in self._content_dict.items())\n        if params:\n            ctype = self._content_type + \"; \" + params\n        else:\n            ctype = self._content_type\n        self._headers[CONTENT_TYPE] = ctype\n\n    async def _do_start_compression(self, coding: ContentCoding) -> None:\n        if coding is ContentCoding.identity:\n            return\n        assert self._payload_writer is not None\n        self._headers[hdrs.CONTENT_ENCODING] = coding.value\n        self._payload_writer.enable_compression(\n            coding.value, self._compression_strategy\n        )\n        # Compressed payload may have different content length,\n        # remove the header\n        self._headers.popall(hdrs.CONTENT_LENGTH, None)\n\n    async def _start_compression(self, request: \"BaseRequest\") -> None:\n        if self._compression_force:\n            await self._do_start_compression(self._compression_force)\n            return\n        # Encoding comparisons should be case-insensitive\n        # https://www.rfc-editor.org/rfc/rfc9110#section-8.4.1\n        accept_encoding = request.headers.get(hdrs.ACCEPT_ENCODING, \"\").lower()\n        for value, coding in CONTENT_CODINGS.items():\n            if value in accept_encoding:\n                await self._do_start_compression(coding)\n                return\n\n    async def prepare(self, request: \"BaseRequest\") -> AbstractStreamWriter | None:\n        if self._eof_sent:\n            return None\n        if self._payload_writer is not None:\n            return self._payload_writer\n        self._must_be_empty_body = must_be_empty_body(request.method, self.status)\n        return await self._start(request)\n\n    async def _start(self, request: \"BaseRequest\") -> AbstractStreamWriter:\n        self._req = request\n        writer = self._payload_writer = request._payload_writer\n\n        await self._prepare_headers()\n        await request._prepare_hook(self)\n        await self._write_headers()\n\n        return writer\n\n    async def _prepare_headers(self) -> None:\n        request = self._req\n        assert request is not None\n        writer = self._payload_writer\n        assert writer is not None\n        keep_alive = self._keep_alive\n        if keep_alive is None:\n            keep_alive = request.keep_alive\n        self._keep_alive = keep_alive\n\n        version = request.version\n\n        headers = self._headers\n        if self._cookies:\n            populate_with_cookies(headers, self._cookies)\n\n        if self._compression:\n            await self._start_compression(request)\n\n        if self._chunked:\n            if version != HttpVersion11:\n                raise RuntimeError(\n                    \"Using chunked encoding is forbidden \"\n                    f\"for HTTP/{request.version.major}.{request.version.minor}\"\n                )\n            if not self._must_be_empty_body:\n                writer.enable_chunking()\n                headers[hdrs.TRANSFER_ENCODING] = \"chunked\"\n        elif self._length_check:  # Disabled for WebSockets\n            writer.length = self.content_length\n            if writer.length is None:\n                if version >= HttpVersion11:\n                    if not self._must_be_empty_body:\n                        writer.enable_chunking()\n                        headers[hdrs.TRANSFER_ENCODING] = \"chunked\"\n                elif not self._must_be_empty_body:\n                    keep_alive = False\n\n        # HTTP 1.1: https://tools.ietf.org/html/rfc7230#section-3.3.2\n        # HTTP 1.0: https://tools.ietf.org/html/rfc1945#section-10.4\n        if self._must_be_empty_body:\n            if hdrs.CONTENT_LENGTH in headers and should_remove_content_length(\n                request.method, self.status\n            ):\n                del headers[hdrs.CONTENT_LENGTH]\n            # https://datatracker.ietf.org/doc/html/rfc9112#section-6.1-10\n            # https://datatracker.ietf.org/doc/html/rfc9112#section-6.1-13\n            if hdrs.TRANSFER_ENCODING in headers:\n                del headers[hdrs.TRANSFER_ENCODING]\n        elif (writer.length if self._length_check else self.content_length) != 0:\n            # https://www.rfc-editor.org/rfc/rfc9110#section-8.3-5\n            headers.setdefault(hdrs.CONTENT_TYPE, \"application/octet-stream\")\n        headers.setdefault(hdrs.DATE, rfc822_formatted_time())\n        headers.setdefault(hdrs.SERVER, SERVER_SOFTWARE)\n\n        # connection header\n        if hdrs.CONNECTION not in headers:\n            if keep_alive:\n                if version == HttpVersion10:\n                    headers[hdrs.CONNECTION] = \"keep-alive\"\n            elif version == HttpVersion11:\n                headers[hdrs.CONNECTION] = \"close\"\n\n    async def _write_headers(self) -> None:\n        request = self._req\n        assert request is not None\n        writer = self._payload_writer\n        assert writer is not None\n        # status line\n        version = request.version\n        status_line = f\"HTTP/{version[0]}.{version[1]} {self._status} {self._reason}\"\n        await writer.write_headers(status_line, self._headers)\n\n        # Send headers immediately if not opted into buffering\n        if self._send_headers_immediately:\n            writer.send_headers()\n\n    async def write(\n        self, data: Union[bytes, bytearray, \"memoryview[int]\", \"memoryview[bytes]\"]\n    ) -> None:\n        assert isinstance(\n            data, (bytes, bytearray, memoryview)\n        ), \"data argument must be byte-ish (%r)\" % type(data)\n\n        if self._eof_sent:\n            raise RuntimeError(\"Cannot call write() after write_eof()\")\n        if self._payload_writer is None:\n            raise RuntimeError(\"Cannot call write() before prepare()\")\n\n        await self._payload_writer.write(data)\n\n    async def drain(self) -> None:\n        assert not self._eof_sent, \"EOF has already been sent\"\n        assert self._payload_writer is not None, \"Response has not been started\"\n        warnings.warn(\n            \"drain method is deprecated, use await resp.write()\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n        await self._payload_writer.drain()\n\n    async def write_eof(self, data: bytes = b\"\") -> None:\n        assert isinstance(\n            data, (bytes, bytearray, memoryview)\n        ), \"data argument must be byte-ish (%r)\" % type(data)\n\n        if self._eof_sent:\n            return\n\n        assert self._payload_writer is not None, \"Response has not been started\"\n\n        await self._payload_writer.write_eof(data)\n        self._eof_sent = True\n        self._req = None\n        self._body_length = self._payload_writer.output_size\n        self._payload_writer = None\n\n    def __repr__(self) -> str:\n        if self._eof_sent:\n            info = \"eof\"\n        elif self.prepared:\n            assert self._req is not None\n            info = f\"{self._req.method} {self._req.path} \"\n        else:\n            info = \"not prepared\"\n        return f\"<{self.__class__.__name__} {self.reason} {info}>\"\n\n    @overload  # type: ignore[override]\n    def __getitem__(self, key: ResponseKey[_T]) -> _T: ...\n\n    @overload\n    def __getitem__(self, key: str) -> Any: ...\n\n    def __getitem__(self, key: str | ResponseKey[_T]) -> Any:\n        return self._state[key]\n\n    @overload  # type: ignore[override]\n    def __setitem__(self, key: ResponseKey[_T], value: _T) -> None: ...\n\n    @overload\n    def __setitem__(self, key: str, value: Any) -> None: ...\n\n    def __setitem__(self, key: str | ResponseKey[_T], value: Any) -> None:\n        self._state[key] = value\n\n    def __delitem__(self, key: str | ResponseKey[_T]) -> None:\n        del self._state[key]\n\n    def __len__(self) -> int:\n        return len(self._state)\n\n    def __iter__(self) -> Iterator[str | ResponseKey[Any]]:\n        return iter(self._state)\n\n    def __hash__(self) -> int:\n        return hash(id(self))\n\n    def __eq__(self, other: object) -> bool:\n        return self is other\n\n    def __bool__(self) -> bool:\n        return True\n\n\nclass Response(StreamResponse):\n\n    _compressed_body: bytes | None = None\n    _send_headers_immediately = False\n\n    def __init__(\n        self,\n        *,\n        body: Any = None,\n        status: int = 200,\n        reason: str | None = None,\n        text: str | None = None,\n        headers: LooseHeaders | None = None,\n        content_type: str | None = None,\n        charset: str | None = None,\n        zlib_executor_size: int | None = None,\n        zlib_executor: Executor | None = None,\n    ) -> None:\n        if body is not None and text is not None:\n            raise ValueError(\"body and text are not allowed together\")\n\n        if headers is None:\n            real_headers: CIMultiDict[str] = CIMultiDict()\n        else:\n            real_headers = CIMultiDict(headers)\n\n        if content_type is not None and \"charset\" in content_type:\n            raise ValueError(\"charset must not be in content_type argument\")\n\n        if text is not None:\n            if hdrs.CONTENT_TYPE in real_headers:\n                if content_type or charset:\n                    raise ValueError(\n                        \"passing both Content-Type header and \"\n                        \"content_type or charset params \"\n                        \"is forbidden\"\n                    )\n            else:\n                # fast path for filling headers\n                if not isinstance(text, str):\n                    raise TypeError(\"text argument must be str (%r)\" % type(text))\n                if content_type is None:\n                    content_type = \"text/plain\"\n                if charset is None:\n                    charset = \"utf-8\"\n                real_headers[hdrs.CONTENT_TYPE] = content_type + \"; charset=\" + charset\n                body = text.encode(charset)\n                text = None\n        elif hdrs.CONTENT_TYPE in real_headers:\n            if content_type is not None or charset is not None:\n                raise ValueError(\n                    \"passing both Content-Type header and \"\n                    \"content_type or charset params \"\n                    \"is forbidden\"\n                )\n        elif content_type is not None:\n            if charset is not None:\n                content_type += \"; charset=\" + charset\n            real_headers[hdrs.CONTENT_TYPE] = content_type\n\n        super().__init__(status=status, reason=reason, _real_headers=real_headers)\n\n        if text is not None:\n            self.text = text\n        else:\n            self.body = body\n\n        self._zlib_executor_size = zlib_executor_size\n        self._zlib_executor = zlib_executor\n\n    @property\n    def body(self) -> bytes | bytearray | Payload | None:\n        return self._body\n\n    @body.setter\n    def body(self, body: Any) -> None:\n        if body is None:\n            self._body = None\n        elif isinstance(body, (bytes, bytearray)):\n            self._body = body\n        else:\n            try:\n                self._body = body = payload.PAYLOAD_REGISTRY.get(body)\n            except payload.LookupError:\n                raise ValueError(\"Unsupported body type %r\" % type(body))\n\n            headers = self._headers\n\n            # set content-type\n            if hdrs.CONTENT_TYPE not in headers:\n                headers[hdrs.CONTENT_TYPE] = body.content_type\n\n            # copy payload headers\n            if body.headers:\n                for key, value in body.headers.items():\n                    if key not in headers:\n                        headers[key] = value\n\n        self._compressed_body = None\n\n    @property\n    def text(self) -> str | None:\n        if self._body is None:\n            return None\n        # Note: When _body is a Payload (e.g. FilePayload), this may do blocking I/O\n        # This is generally safe as most common payloads (BytesPayload, StringPayload)\n        # don't do blocking I/O, but be careful with file-based payloads\n        return self._body.decode(self.charset or \"utf-8\")\n\n    @text.setter\n    def text(self, text: str) -> None:\n        assert isinstance(text, str), \"text argument must be str (%r)\" % type(text)\n\n        if self.content_type == \"application/octet-stream\":\n            self.content_type = \"text/plain\"\n        if self.charset is None:\n            self.charset = \"utf-8\"\n\n        self._body = text.encode(self.charset)\n        self._compressed_body = None\n\n    @property\n    def content_length(self) -> int | None:\n        if self._chunked:\n            return None\n\n        if hdrs.CONTENT_LENGTH in self._headers:\n            return int(self._headers[hdrs.CONTENT_LENGTH])\n\n        if self._compressed_body is not None:\n            # Return length of the compressed body\n            return len(self._compressed_body)\n        elif isinstance(self._body, Payload):\n            # A payload without content length, or a compressed payload\n            return None\n        elif self._body is not None:\n            return len(self._body)\n        else:\n            return 0\n\n    @content_length.setter\n    def content_length(self, value: int | None) -> None:\n        raise RuntimeError(\"Content length is set automatically\")\n\n    async def write_eof(self, data: bytes = b\"\") -> None:\n        if self._eof_sent:\n            return\n        if self._compressed_body is None:\n            body = self._body\n        else:\n            body = self._compressed_body\n        assert not data, f\"data arg is not supported, got {data!r}\"\n        assert self._req is not None\n        assert self._payload_writer is not None\n        if body is None or self._must_be_empty_body:\n            await super().write_eof()\n        elif isinstance(self._body, Payload):\n            await self._body.write(self._payload_writer)\n            await self._body.close()\n            await super().write_eof()\n        else:\n            await super().write_eof(cast(bytes, body))\n\n    async def _start(self, request: \"BaseRequest\") -> AbstractStreamWriter:\n        if hdrs.CONTENT_LENGTH in self._headers:\n            if should_remove_content_length(request.method, self.status):\n                del self._headers[hdrs.CONTENT_LENGTH]\n        elif not self._chunked:\n            if isinstance(self._body, Payload):\n                if (size := self._body.size) is not None:\n                    self._headers[hdrs.CONTENT_LENGTH] = str(size)\n            else:\n                body_len = len(self._body) if self._body else \"0\"\n                # https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6-7\n                if body_len != \"0\" or (\n                    self.status != 304 and request.method not in hdrs.METH_HEAD_ALL\n                ):\n                    self._headers[hdrs.CONTENT_LENGTH] = str(body_len)\n\n        return await super()._start(request)\n\n    async def _do_start_compression(self, coding: ContentCoding) -> None:\n        if self._chunked or isinstance(self._body, Payload):\n            return await super()._do_start_compression(coding)\n        if coding is ContentCoding.identity:\n            return\n        # Instead of using _payload_writer.enable_compression,\n        # compress the whole body\n        compressor = ZLibCompressor(\n            encoding=coding.value,\n            max_sync_chunk_size=self._zlib_executor_size,\n            executor=self._zlib_executor,\n        )\n        assert self._body is not None\n        if self._zlib_executor_size is None and len(self._body) > LARGE_BODY_SIZE:\n            warnings.warn(\n                \"Synchronous compression of large response bodies \"\n                f\"({len(self._body)} bytes) might block the async event loop. \"\n                \"Consider providing a custom value to zlib_executor_size/\"\n                \"zlib_executor response properties or disabling compression on it.\"\n            )\n        self._compressed_body = (\n            await compressor.compress(self._body) + compressor.flush()\n        )\n        self._headers[hdrs.CONTENT_ENCODING] = coding.value\n        self._headers[hdrs.CONTENT_LENGTH] = str(len(self._compressed_body))\n\n\ndef json_response(\n    data: Any = sentinel,\n    *,\n    text: str | None = None,\n    body: bytes | None = None,\n    status: int = 200,\n    reason: str | None = None,\n    headers: LooseHeaders | None = None,\n    content_type: str = \"application/json\",\n    dumps: JSONEncoder = json.dumps,\n) -> Response:\n    if data is not sentinel:\n        if text or body:\n            raise ValueError(\"only one of data, text, or body should be specified\")\n        else:\n            text = dumps(data)\n    return Response(\n        text=text,\n        body=body,\n        status=status,\n        reason=reason,\n        headers=headers,\n        content_type=content_type,\n    )\n\n\ndef json_bytes_response(\n    data: Any = sentinel,\n    *,\n    dumps: JSONBytesEncoder,\n    body: bytes | None = None,\n    status: int = 200,\n    reason: str | None = None,\n    headers: LooseHeaders | None = None,\n    content_type: str = \"application/json\",\n) -> Response:\n    \"\"\"Create a JSON response using a bytes-returning encoder.\n\n    Use this when your JSON encoder (like orjson) returns bytes\n    instead of str, avoiding the encode/decode overhead.\n    \"\"\"\n    if data is not sentinel:\n        if body is not None:\n            raise ValueError(\"only one of data or body should be specified\")\n        else:\n            body = dumps(data)\n    return Response(\n        body=body,\n        status=status,\n        reason=reason,\n        headers=headers,\n        content_type=content_type,\n    )\n"
  },
  {
    "path": "aiohttp/web_routedef.py",
    "content": "import abc\nimport dataclasses\nfrom collections.abc import Callable, Iterator, Sequence\nfrom typing import TYPE_CHECKING, Any, Union, overload\n\nfrom . import hdrs\nfrom .abc import AbstractView\nfrom .typedefs import Handler, PathLike\n\nif TYPE_CHECKING:\n    from .web_request import Request\n    from .web_response import StreamResponse\n    from .web_urldispatcher import AbstractRoute, UrlDispatcher\nelse:\n    Request = StreamResponse = UrlDispatcher = AbstractRoute = None\n\n\n__all__ = (\n    \"AbstractRouteDef\",\n    \"RouteDef\",\n    \"StaticDef\",\n    \"RouteTableDef\",\n    \"head\",\n    \"options\",\n    \"get\",\n    \"post\",\n    \"patch\",\n    \"put\",\n    \"delete\",\n    \"route\",\n    \"view\",\n    \"static\",\n)\n\n\nclass AbstractRouteDef(abc.ABC):\n    @abc.abstractmethod\n    def register(self, router: UrlDispatcher) -> list[AbstractRoute]:\n        \"\"\"Register itself into the given router.\"\"\"\n\n\n_HandlerType = Union[type[AbstractView], Handler]\n\n\n@dataclasses.dataclass(frozen=True, repr=False)\nclass RouteDef(AbstractRouteDef):\n    method: str\n    path: str\n    handler: _HandlerType\n    kwargs: dict[str, Any]\n\n    def __repr__(self) -> str:\n        info = []\n        for name, value in sorted(self.kwargs.items()):\n            info.append(f\", {name}={value!r}\")\n        return \"<RouteDef {method} {path} -> {handler.__name__!r}{info}>\".format(\n            method=self.method, path=self.path, handler=self.handler, info=\"\".join(info)\n        )\n\n    def register(self, router: UrlDispatcher) -> list[AbstractRoute]:\n        if self.method in hdrs.METH_ALL:\n            reg = getattr(router, \"add_\" + self.method.lower())\n            return [reg(self.path, self.handler, **self.kwargs)]\n        else:\n            return [\n                router.add_route(self.method, self.path, self.handler, **self.kwargs)\n            ]\n\n\n@dataclasses.dataclass(frozen=True, repr=False)\nclass StaticDef(AbstractRouteDef):\n    prefix: str\n    path: PathLike\n    kwargs: dict[str, Any]\n\n    def __repr__(self) -> str:\n        info = []\n        for name, value in sorted(self.kwargs.items()):\n            info.append(f\", {name}={value!r}\")\n        return \"<StaticDef {prefix} -> {path}{info}>\".format(\n            prefix=self.prefix, path=self.path, info=\"\".join(info)\n        )\n\n    def register(self, router: UrlDispatcher) -> list[AbstractRoute]:\n        resource = router.add_static(self.prefix, self.path, **self.kwargs)\n        routes = resource.get_info().get(\"routes\", {})\n        return list(routes.values())\n\n\ndef route(method: str, path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef:\n    return RouteDef(method, path, handler, kwargs)\n\n\ndef head(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef:\n    return route(hdrs.METH_HEAD, path, handler, **kwargs)\n\n\ndef options(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef:\n    return route(hdrs.METH_OPTIONS, path, handler, **kwargs)\n\n\ndef get(\n    path: str,\n    handler: _HandlerType,\n    *,\n    name: str | None = None,\n    allow_head: bool = True,\n    **kwargs: Any,\n) -> RouteDef:\n    return route(\n        hdrs.METH_GET, path, handler, name=name, allow_head=allow_head, **kwargs\n    )\n\n\ndef post(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef:\n    return route(hdrs.METH_POST, path, handler, **kwargs)\n\n\ndef put(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef:\n    return route(hdrs.METH_PUT, path, handler, **kwargs)\n\n\ndef patch(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef:\n    return route(hdrs.METH_PATCH, path, handler, **kwargs)\n\n\ndef delete(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef:\n    return route(hdrs.METH_DELETE, path, handler, **kwargs)\n\n\ndef view(path: str, handler: type[AbstractView], **kwargs: Any) -> RouteDef:\n    return route(hdrs.METH_ANY, path, handler, **kwargs)\n\n\ndef static(prefix: str, path: PathLike, **kwargs: Any) -> StaticDef:\n    return StaticDef(prefix, path, kwargs)\n\n\n_Deco = Callable[[_HandlerType], _HandlerType]\n\n\nclass RouteTableDef(Sequence[AbstractRouteDef]):\n    \"\"\"Route definition table\"\"\"\n\n    def __init__(self) -> None:\n        self._items: list[AbstractRouteDef] = []\n\n    def __repr__(self) -> str:\n        return f\"<RouteTableDef count={len(self._items)}>\"\n\n    @overload\n    def __getitem__(self, index: int) -> AbstractRouteDef: ...\n\n    @overload\n    def __getitem__(self, index: \"slice[int, int, int]\") -> list[AbstractRouteDef]: ...\n\n    def __getitem__(\n        self, index: Union[int, \"slice[int, int, int]\"]\n    ) -> AbstractRouteDef | list[AbstractRouteDef]:\n        return self._items[index]\n\n    def __iter__(self) -> Iterator[AbstractRouteDef]:\n        return iter(self._items)\n\n    def __len__(self) -> int:\n        return len(self._items)\n\n    def __contains__(self, item: object) -> bool:\n        return item in self._items\n\n    def route(self, method: str, path: str, **kwargs: Any) -> _Deco:\n        def inner(handler: _HandlerType) -> _HandlerType:\n            self._items.append(RouteDef(method, path, handler, kwargs))\n            return handler\n\n        return inner\n\n    def head(self, path: str, **kwargs: Any) -> _Deco:\n        return self.route(hdrs.METH_HEAD, path, **kwargs)\n\n    def get(self, path: str, **kwargs: Any) -> _Deco:\n        return self.route(hdrs.METH_GET, path, **kwargs)\n\n    def post(self, path: str, **kwargs: Any) -> _Deco:\n        return self.route(hdrs.METH_POST, path, **kwargs)\n\n    def put(self, path: str, **kwargs: Any) -> _Deco:\n        return self.route(hdrs.METH_PUT, path, **kwargs)\n\n    def patch(self, path: str, **kwargs: Any) -> _Deco:\n        return self.route(hdrs.METH_PATCH, path, **kwargs)\n\n    def delete(self, path: str, **kwargs: Any) -> _Deco:\n        return self.route(hdrs.METH_DELETE, path, **kwargs)\n\n    def options(self, path: str, **kwargs: Any) -> _Deco:\n        return self.route(hdrs.METH_OPTIONS, path, **kwargs)\n\n    def view(self, path: str, **kwargs: Any) -> _Deco:\n        return self.route(hdrs.METH_ANY, path, **kwargs)\n\n    def static(self, prefix: str, path: PathLike, **kwargs: Any) -> None:\n        self._items.append(StaticDef(prefix, path, kwargs))\n"
  },
  {
    "path": "aiohttp/web_runner.py",
    "content": "import asyncio\nimport signal\nimport socket\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Generic, TypeVar\n\nfrom yarl import URL\n\nfrom .abc import AbstractAccessLogger, AbstractStreamWriter\nfrom .http_parser import RawRequestMessage\nfrom .streams import StreamReader\nfrom .typedefs import PathLike\nfrom .web_app import Application\nfrom .web_log import AccessLogger\nfrom .web_protocol import RequestHandler\nfrom .web_request import BaseRequest, Request\nfrom .web_server import Server\n\ntry:\n    from ssl import SSLContext\nexcept ImportError:  # pragma: no cover\n    SSLContext = object  # type: ignore[misc,assignment]\n\n__all__ = (\n    \"BaseSite\",\n    \"TCPSite\",\n    \"UnixSite\",\n    \"NamedPipeSite\",\n    \"SockSite\",\n    \"BaseRunner\",\n    \"AppRunner\",\n    \"ServerRunner\",\n    \"GracefulExit\",\n)\n\n_Request = TypeVar(\"_Request\", bound=BaseRequest)\n\n\nclass GracefulExit(SystemExit):\n    code = 1\n\n\ndef _raise_graceful_exit() -> None:\n    raise GracefulExit()\n\n\nclass BaseSite(ABC):\n    __slots__ = (\"_runner\", \"_ssl_context\", \"_backlog\", \"_server\")\n\n    def __init__(\n        self,\n        runner: \"BaseRunner[Any]\",\n        *,\n        ssl_context: SSLContext | None = None,\n        backlog: int = 128,\n    ) -> None:\n        if runner.server is None:\n            raise RuntimeError(\"Call runner.setup() before making a site\")\n        self._runner = runner\n        self._ssl_context = ssl_context\n        self._backlog = backlog\n        self._server: asyncio.Server | None = None\n\n    @property\n    @abstractmethod\n    def name(self) -> str:\n        \"\"\"Return the name of the site (e.g. a URL).\"\"\"\n\n    @abstractmethod\n    async def start(self) -> None:\n        self._runner._reg_site(self)\n\n    async def stop(self) -> None:\n        self._runner._check_site(self)\n        if self._server is not None:  # Maybe not started yet\n            self._server.close()\n\n        self._runner._unreg_site(self)\n\n\nclass TCPSite(BaseSite):\n    __slots__ = (\"_host\", \"_port\", \"_bound_port\", \"_reuse_address\", \"_reuse_port\")\n\n    def __init__(\n        self,\n        runner: \"BaseRunner[Any]\",\n        host: str | None = None,\n        port: int | None = None,\n        *,\n        ssl_context: SSLContext | None = None,\n        backlog: int = 128,\n        reuse_address: bool | None = None,\n        reuse_port: bool | None = None,\n    ) -> None:\n        super().__init__(\n            runner,\n            ssl_context=ssl_context,\n            backlog=backlog,\n        )\n        self._host = host\n        if port is None:\n            port = 8443 if self._ssl_context else 8080\n        self._port = port\n        self._bound_port: int | None = None\n        self._reuse_address = reuse_address\n        self._reuse_port = reuse_port\n\n    @property\n    def port(self) -> int:\n        \"\"\"The port the server is listening on.\n\n        If the server hasn't been started yet, this returns the requested port\n        (which might be 0 for a dynamic port).\n        After the server starts, it returns the actual bound port. This is\n        especially useful when port=0 was requested, as it allows retrieving the\n        dynamically assigned port after the site has started.\n        \"\"\"\n        if self._bound_port is not None:\n            return self._bound_port\n        return self._port\n\n    @property\n    def name(self) -> str:\n        scheme = \"https\" if self._ssl_context else \"http\"\n        host = \"0.0.0.0\" if not self._host else self._host\n        return str(URL.build(scheme=scheme, host=host, port=self.port))\n\n    async def start(self) -> None:\n        await super().start()\n        loop = asyncio.get_event_loop()\n        server = self._runner.server\n        assert server is not None\n        self._server = await loop.create_server(\n            server,\n            self._host,\n            self._port,\n            ssl=self._ssl_context,\n            backlog=self._backlog,\n            reuse_address=self._reuse_address,\n            reuse_port=self._reuse_port,\n        )\n        if self._server.sockets:\n            self._bound_port = self._server.sockets[0].getsockname()[1]\n        else:\n            self._bound_port = self._port\n\n\nclass UnixSite(BaseSite):\n    __slots__ = (\"_path\",)\n\n    def __init__(\n        self,\n        runner: \"BaseRunner[Any]\",\n        path: PathLike,\n        *,\n        ssl_context: SSLContext | None = None,\n        backlog: int = 128,\n    ) -> None:\n        super().__init__(\n            runner,\n            ssl_context=ssl_context,\n            backlog=backlog,\n        )\n        self._path = path\n\n    @property\n    def name(self) -> str:\n        scheme = \"https\" if self._ssl_context else \"http\"\n        return f\"{scheme}://unix:{self._path}:\"\n\n    async def start(self) -> None:\n        await super().start()\n        loop = asyncio.get_event_loop()\n        server = self._runner.server\n        assert server is not None\n        self._server = await loop.create_unix_server(\n            server,\n            self._path,\n            ssl=self._ssl_context,\n            backlog=self._backlog,\n        )\n\n\nclass NamedPipeSite(BaseSite):\n    __slots__ = (\"_path\",)\n\n    def __init__(self, runner: \"BaseRunner[Any]\", path: str) -> None:\n        loop = asyncio.get_event_loop()\n        if not isinstance(\n            loop, asyncio.ProactorEventLoop  # type: ignore[attr-defined]\n        ):\n            raise RuntimeError(\n                \"Named Pipes only available in proactor loop under windows\"\n            )\n        super().__init__(runner)\n        self._path = path\n\n    @property\n    def name(self) -> str:\n        return self._path\n\n    async def start(self) -> None:\n        await super().start()\n        loop = asyncio.get_event_loop()\n        server = self._runner.server\n        assert server is not None\n        _server = await loop.start_serving_pipe(  # type: ignore[attr-defined]\n            server, self._path\n        )\n        self._server = _server[0]\n\n\nclass SockSite(BaseSite):\n    __slots__ = (\"_sock\", \"_name\")\n\n    def __init__(\n        self,\n        runner: \"BaseRunner[Any]\",\n        sock: socket.socket,\n        *,\n        ssl_context: SSLContext | None = None,\n        backlog: int = 128,\n    ) -> None:\n        super().__init__(\n            runner,\n            ssl_context=ssl_context,\n            backlog=backlog,\n        )\n        self._sock = sock\n        scheme = \"https\" if self._ssl_context else \"http\"\n        if hasattr(socket, \"AF_UNIX\") and sock.family == socket.AF_UNIX:\n            name = f\"{scheme}://unix:{sock.getsockname()}:\"\n        else:\n            host, port = sock.getsockname()[:2]\n            name = str(URL.build(scheme=scheme, host=host, port=port))\n        self._name = name\n\n    @property\n    def name(self) -> str:\n        return self._name\n\n    async def start(self) -> None:\n        await super().start()\n        loop = asyncio.get_event_loop()\n        server = self._runner.server\n        assert server is not None\n        self._server = await loop.create_server(\n            server, sock=self._sock, ssl=self._ssl_context, backlog=self._backlog\n        )\n\n\nclass BaseRunner(ABC, Generic[_Request]):\n    __slots__ = (\"_handle_signals\", \"_kwargs\", \"_server\", \"_sites\", \"_shutdown_timeout\")\n\n    def __init__(\n        self,\n        *,\n        handle_signals: bool = False,\n        shutdown_timeout: float = 60.0,\n        **kwargs: Any,\n    ) -> None:\n        self._handle_signals = handle_signals\n        self._kwargs = kwargs\n        self._server: Server[_Request] | None = None\n        self._sites: list[BaseSite] = []\n        self._shutdown_timeout = shutdown_timeout\n\n    @property\n    def server(self) -> Server[_Request] | None:\n        return self._server\n\n    @property\n    def addresses(self) -> list[Any]:\n        ret: list[Any] = []\n        for site in self._sites:\n            server = site._server\n            if server is not None:\n                sockets = server.sockets\n                if sockets is not None:\n                    for sock in sockets:\n                        ret.append(sock.getsockname())\n        return ret\n\n    @property\n    def sites(self) -> set[BaseSite]:\n        return set(self._sites)\n\n    async def setup(self) -> None:\n        loop = asyncio.get_event_loop()\n\n        if self._handle_signals:\n            try:\n                loop.add_signal_handler(signal.SIGINT, _raise_graceful_exit)\n                loop.add_signal_handler(signal.SIGTERM, _raise_graceful_exit)\n            except NotImplementedError:\n                # add_signal_handler is not implemented on Windows\n                pass\n\n        self._server = await self._make_server()\n\n    @abstractmethod\n    async def shutdown(self) -> None:\n        \"\"\"Call any shutdown hooks to help server close gracefully.\"\"\"\n\n    async def cleanup(self) -> None:\n        # The loop over sites is intentional, an exception on gather()\n        # leaves self._sites in unpredictable state.\n        # The loop guarantees that a site is either deleted on success or\n        # still present on failure\n        for site in list(self._sites):\n            await site.stop()\n\n        if self._server:  # If setup succeeded\n            # Yield to event loop to ensure incoming requests prior to stopping the sites\n            # have all started to be handled before we proceed to close idle connections.\n            await asyncio.sleep(0)\n            self._server.pre_shutdown()\n            await self.shutdown()\n            await self._server.shutdown(self._shutdown_timeout)\n        await self._cleanup_server()\n\n        self._server = None\n        if self._handle_signals:\n            loop = asyncio.get_running_loop()\n            try:\n                loop.remove_signal_handler(signal.SIGINT)\n                loop.remove_signal_handler(signal.SIGTERM)\n            except NotImplementedError:\n                # remove_signal_handler is not implemented on Windows\n                pass\n\n    @abstractmethod\n    async def _make_server(self) -> Server[_Request]:\n        \"\"\"Return a new server for the runner to serve requests.\"\"\"\n\n    @abstractmethod\n    async def _cleanup_server(self) -> None:\n        \"\"\"Run any cleanup steps after the server is shutdown.\"\"\"\n\n    def _reg_site(self, site: BaseSite) -> None:\n        if site in self._sites:\n            raise RuntimeError(f\"Site {site} is already registered in runner {self}\")\n        self._sites.append(site)\n\n    def _check_site(self, site: BaseSite) -> None:\n        if site not in self._sites:\n            raise RuntimeError(f\"Site {site} is not registered in runner {self}\")\n\n    def _unreg_site(self, site: BaseSite) -> None:\n        if site not in self._sites:\n            raise RuntimeError(f\"Site {site} is not registered in runner {self}\")\n        self._sites.remove(site)\n\n\nclass ServerRunner(BaseRunner[BaseRequest]):\n    \"\"\"Low-level web server runner\"\"\"\n\n    __slots__ = (\"_web_server\",)\n\n    def __init__(\n        self,\n        web_server: Server[BaseRequest],\n        *,\n        handle_signals: bool = False,\n        **kwargs: Any,\n    ) -> None:\n        super().__init__(handle_signals=handle_signals, **kwargs)\n        self._web_server = web_server\n\n    async def shutdown(self) -> None:\n        pass\n\n    async def _make_server(self) -> Server[BaseRequest]:\n        return self._web_server\n\n    async def _cleanup_server(self) -> None:\n        pass\n\n\nclass AppRunner(BaseRunner[Request]):\n    \"\"\"Web Application runner\"\"\"\n\n    __slots__ = (\"_app\",)\n\n    def __init__(\n        self,\n        app: Application,\n        *,\n        handle_signals: bool = False,\n        access_log_class: type[AbstractAccessLogger] = AccessLogger,\n        **kwargs: Any,\n    ) -> None:\n        if not isinstance(app, Application):\n            raise TypeError(\n                f\"The first argument should be web.Application instance, got {app!r}\"\n            )\n        kwargs[\"access_log_class\"] = access_log_class\n\n        if app._handler_args:\n            for k, v in app._handler_args.items():\n                kwargs[k] = v\n\n        if not issubclass(kwargs[\"access_log_class\"], AbstractAccessLogger):\n            raise TypeError(\n                \"access_log_class must be subclass of \"\n                \"aiohttp.abc.AbstractAccessLogger, got {}\".format(\n                    kwargs[\"access_log_class\"]\n                )\n            )\n\n        super().__init__(handle_signals=handle_signals, **kwargs)\n        self._app = app\n\n    @property\n    def app(self) -> Application:\n        return self._app\n\n    async def shutdown(self) -> None:\n        await self._app.shutdown()\n\n    async def _make_server(self) -> Server[Request]:\n        self._app.on_startup.freeze()\n        await self._app.startup()\n        self._app.freeze()\n\n        return Server(\n            self._app._handle,\n            request_factory=self._make_request,\n            **self._kwargs,\n        )\n\n    def _make_request(\n        self,\n        message: RawRequestMessage,\n        payload: StreamReader,\n        protocol: RequestHandler[Request],\n        writer: AbstractStreamWriter,\n        task: \"asyncio.Task[None]\",\n        _cls: type[Request] = Request,\n    ) -> Request:\n        loop = asyncio.get_running_loop()\n        return _cls(\n            message,\n            payload,\n            protocol,\n            writer,\n            task,\n            loop,\n            client_max_size=self.app._client_max_size,\n        )\n\n    async def _cleanup_server(self) -> None:\n        await self._app.cleanup()\n"
  },
  {
    "path": "aiohttp/web_server.py",
    "content": "\"\"\"Low level HTTP server.\"\"\"\n\nimport asyncio\nimport warnings\nfrom collections.abc import Awaitable, Callable\nfrom typing import Any, Generic, TypeVar, overload\n\nfrom .abc import AbstractStreamWriter\nfrom .http_parser import RawRequestMessage\nfrom .streams import StreamReader\nfrom .web_protocol import RequestHandler\nfrom .web_request import BaseRequest\nfrom .web_response import StreamResponse\n\n__all__ = (\"Server\",)\n\n_Request = TypeVar(\"_Request\", bound=BaseRequest)\n_RequestFactory = Callable[\n    [\n        RawRequestMessage,\n        StreamReader,\n        \"RequestHandler[_Request]\",\n        AbstractStreamWriter,\n        \"asyncio.Task[None]\",\n    ],\n    _Request,\n]\n\n\nclass Server(Generic[_Request]):\n    request_factory: _RequestFactory[_Request]\n\n    @overload\n    def __init__(\n        self: \"Server[BaseRequest]\",\n        handler: Callable[[_Request], Awaitable[StreamResponse]],\n        *,\n        debug: bool | None = None,\n        handler_cancellation: bool = False,\n        **kwargs: Any,  # TODO(PY311): Use Unpack to define kwargs from RequestHandler\n    ) -> None: ...\n    @overload\n    def __init__(\n        self,\n        handler: Callable[[_Request], Awaitable[StreamResponse]],\n        *,\n        request_factory: _RequestFactory[_Request] | None,\n        debug: bool | None = None,\n        handler_cancellation: bool = False,\n        **kwargs: Any,\n    ) -> None: ...\n    def __init__(\n        self,\n        handler: Callable[[_Request], Awaitable[StreamResponse]],\n        *,\n        request_factory: _RequestFactory[_Request] | None = None,\n        debug: bool | None = None,\n        handler_cancellation: bool = False,\n        **kwargs: Any,\n    ) -> None:\n        if debug is not None:\n            warnings.warn(\n                \"debug argument is no-op since 4.0 and scheduled for removal in 5.0\",\n                DeprecationWarning,\n                stacklevel=2,\n            )\n        self._loop = asyncio.get_running_loop()\n        self._connections: dict[RequestHandler[_Request], asyncio.Transport] = {}\n        self._kwargs = kwargs\n        # requests_count is the number of requests being processed by the server\n        # for the lifetime of the server.\n        self.requests_count = 0\n        self.request_handler = handler\n        self.request_factory = request_factory or self._make_request  # type: ignore[assignment]\n        self.handler_cancellation = handler_cancellation\n\n    @property\n    def connections(self) -> list[RequestHandler[_Request]]:\n        return list(self._connections.keys())\n\n    def connection_made(\n        self, handler: RequestHandler[_Request], transport: asyncio.Transport\n    ) -> None:\n        self._connections[handler] = transport\n\n    def connection_lost(\n        self, handler: RequestHandler[_Request], exc: BaseException | None = None\n    ) -> None:\n        if handler in self._connections:\n            if handler._task_handler:\n                handler._task_handler.add_done_callback(\n                    lambda f: self._connections.pop(handler, None)\n                )\n            else:\n                del self._connections[handler]\n\n    def _make_request(\n        self,\n        message: RawRequestMessage,\n        payload: StreamReader,\n        protocol: RequestHandler[BaseRequest],\n        writer: AbstractStreamWriter,\n        task: \"asyncio.Task[None]\",\n    ) -> BaseRequest:\n        return BaseRequest(message, payload, protocol, writer, task, self._loop)\n\n    def pre_shutdown(self) -> None:\n        for conn in self._connections:\n            conn.close()\n\n    async def shutdown(self, timeout: float | None = None) -> None:\n        coros = (conn.shutdown(timeout) for conn in self._connections)\n        await asyncio.gather(*coros)\n        self._connections.clear()\n\n    def __call__(self) -> RequestHandler[_Request]:\n        try:\n            return RequestHandler(self, loop=self._loop, **self._kwargs)\n        except TypeError:\n            # Failsafe creation: remove all custom handler_args\n            kwargs = {\n                k: v\n                for k, v in self._kwargs.items()\n                if k in [\"debug\", \"access_log_class\"]\n            }\n            return RequestHandler(self, loop=self._loop, **kwargs)\n"
  },
  {
    "path": "aiohttp/web_urldispatcher.py",
    "content": "import abc\nimport asyncio\nimport base64\nimport functools\nimport hashlib\nimport html\nimport inspect\nimport keyword\nimport os\nimport platform\nimport re\nimport sys\nfrom collections.abc import (\n    Awaitable,\n    Callable,\n    Container,\n    Generator,\n    Iterable,\n    Iterator,\n    Mapping,\n    Sized,\n)\nfrom pathlib import Path\nfrom re import Pattern\nfrom types import MappingProxyType\nfrom typing import TYPE_CHECKING, Any, Final, NoReturn, Optional, TypedDict, cast\n\nfrom yarl import URL\n\nfrom . import hdrs\nfrom .abc import AbstractMatchInfo, AbstractRouter, AbstractView\nfrom .helpers import DEBUG\nfrom .http import HttpVersion11\nfrom .typedefs import Handler, PathLike\nfrom .web_exceptions import (\n    HTTPException,\n    HTTPExpectationFailed,\n    HTTPForbidden,\n    HTTPMethodNotAllowed,\n    HTTPNotFound,\n)\nfrom .web_fileresponse import FileResponse\nfrom .web_request import Request\nfrom .web_response import Response, StreamResponse\nfrom .web_routedef import AbstractRouteDef\n\n__all__ = (\n    \"UrlDispatcher\",\n    \"UrlMappingMatchInfo\",\n    \"AbstractResource\",\n    \"Resource\",\n    \"PlainResource\",\n    \"DynamicResource\",\n    \"AbstractRoute\",\n    \"ResourceRoute\",\n    \"StaticResource\",\n    \"View\",\n)\n\n\nif TYPE_CHECKING:\n    from .web_app import Application\n\nCIRCULAR_SYMLINK_ERROR = (RuntimeError,) if sys.version_info < (3, 13) else ()\n\nHTTP_METHOD_RE: Final[Pattern[str]] = re.compile(\n    r\"^[0-9A-Za-z!#\\$%&'\\*\\+\\-\\.\\^_`\\|~]+$\"\n)\nROUTE_RE: Final[Pattern[str]] = re.compile(\n    r\"(\\{[_a-zA-Z][^{}]*(?:\\{[^{}]*\\}[^{}]*)*\\})\"\n)\nPATH_SEP: Final[str] = re.escape(\"/\")\n\nIS_WINDOWS: Final[bool] = platform.system() == \"Windows\"\n\n_ExpectHandler = Callable[[Request], Awaitable[StreamResponse | None]]\n_Resolve = tuple[Optional[\"UrlMappingMatchInfo\"], set[str]]\n\nhtml_escape = functools.partial(html.escape, quote=True)\n\n\nclass _InfoDict(TypedDict, total=False):\n    path: str\n\n    formatter: str\n    pattern: Pattern[str]\n\n    directory: Path\n    prefix: str\n    routes: Mapping[str, \"AbstractRoute\"]\n\n    app: \"Application\"\n\n    domain: str\n\n    rule: \"AbstractRuleMatching\"\n\n    http_exception: HTTPException\n\n\nclass AbstractResource(Sized, Iterable[\"AbstractRoute\"]):\n    def __init__(self, *, name: str | None = None) -> None:\n        self._name = name\n\n    @property\n    def name(self) -> str | None:\n        return self._name\n\n    @property\n    @abc.abstractmethod\n    def canonical(self) -> str:\n        \"\"\"Exposes the resource's canonical path.\n\n        For example '/foo/bar/{name}'\n\n        \"\"\"\n\n    @abc.abstractmethod  # pragma: no branch\n    def url_for(self, **kwargs: str) -> URL:\n        \"\"\"Construct url for resource with additional params.\"\"\"\n\n    @abc.abstractmethod  # pragma: no branch\n    async def resolve(self, request: Request) -> _Resolve:\n        \"\"\"Resolve resource.\n\n        Return (UrlMappingMatchInfo, allowed_methods) pair.\n        \"\"\"\n\n    @abc.abstractmethod\n    def add_prefix(self, prefix: str) -> None:\n        \"\"\"Add a prefix to processed URLs.\n\n        Required for subapplications support.\n        \"\"\"\n\n    @abc.abstractmethod\n    def get_info(self) -> _InfoDict:\n        \"\"\"Return a dict with additional info useful for introspection\"\"\"\n\n    def freeze(self) -> None:\n        pass\n\n    @abc.abstractmethod\n    def raw_match(self, path: str) -> bool:\n        \"\"\"Perform a raw match against path\"\"\"\n\n\nclass AbstractRoute(abc.ABC):\n    def __init__(\n        self,\n        method: str,\n        handler: Handler | type[AbstractView],\n        *,\n        expect_handler: _ExpectHandler | None = None,\n        resource: AbstractResource | None = None,\n    ) -> None:\n        if expect_handler is None:\n            expect_handler = _default_expect_handler\n\n        assert inspect.iscoroutinefunction(expect_handler) or (\n            sys.version_info < (3, 14) and asyncio.iscoroutinefunction(expect_handler)\n        ), f\"Coroutine is expected, got {expect_handler!r}\"\n\n        method = method.upper()\n        if not HTTP_METHOD_RE.match(method):\n            raise ValueError(f\"{method} is not allowed HTTP method\")\n\n        if inspect.iscoroutinefunction(handler) or (\n            sys.version_info < (3, 14) and asyncio.iscoroutinefunction(handler)\n        ):\n            pass\n        elif isinstance(handler, type) and issubclass(handler, AbstractView):\n            pass\n        else:\n            raise TypeError(\n                f\"Only async functions are allowed as web-handlers, got {handler!r}\"\n            )\n\n        self._method = method\n        self._handler = handler\n        self._expect_handler = expect_handler\n        self._resource = resource\n\n    @property\n    def method(self) -> str:\n        return self._method\n\n    @property\n    def handler(self) -> Handler:\n        return self._handler\n\n    @property\n    @abc.abstractmethod\n    def name(self) -> str | None:\n        \"\"\"Optional route's name, always equals to resource's name.\"\"\"\n\n    @property\n    def resource(self) -> AbstractResource | None:\n        return self._resource\n\n    @abc.abstractmethod\n    def get_info(self) -> _InfoDict:\n        \"\"\"Return a dict with additional info useful for introspection\"\"\"\n\n    @abc.abstractmethod  # pragma: no branch\n    def url_for(self, *args: str, **kwargs: str) -> URL:\n        \"\"\"Construct url for route with additional params.\"\"\"\n\n    async def handle_expect_header(self, request: Request) -> StreamResponse | None:\n        return await self._expect_handler(request)\n\n\nclass UrlMappingMatchInfo(dict[str, str], AbstractMatchInfo):\n\n    __slots__ = (\"_route\", \"_apps\", \"_current_app\", \"_frozen\")\n\n    def __init__(self, match_dict: dict[str, str], route: AbstractRoute) -> None:\n        super().__init__(match_dict)\n        self._route = route\n        self._apps: list[Application] = []\n        self._current_app: Application | None = None\n        self._frozen = False\n\n    @property\n    def handler(self) -> Handler:\n        return self._route.handler\n\n    @property\n    def route(self) -> AbstractRoute:\n        return self._route\n\n    @property\n    def expect_handler(self) -> _ExpectHandler:\n        return self._route.handle_expect_header\n\n    @property\n    def http_exception(self) -> HTTPException | None:\n        return None\n\n    def get_info(self) -> _InfoDict:  # type: ignore[override]\n        return self._route.get_info()\n\n    @property\n    def apps(self) -> tuple[\"Application\", ...]:\n        return tuple(self._apps)\n\n    def add_app(self, app: \"Application\") -> None:\n        if self._frozen:\n            raise RuntimeError(\"Cannot change apps stack after .freeze() call\")\n        if self._current_app is None:\n            self._current_app = app\n        self._apps.insert(0, app)\n\n    @property\n    def current_app(self) -> \"Application\":\n        app = self._current_app\n        assert app is not None\n        return app\n\n    @current_app.setter\n    def current_app(self, app: \"Application\") -> None:\n        if DEBUG:\n            if app not in self._apps:\n                raise RuntimeError(\n                    f\"Expected one of the following apps {self._apps!r}, got {app!r}\"\n                )\n        self._current_app = app\n\n    def freeze(self) -> None:\n        self._frozen = True\n\n    def __repr__(self) -> str:\n        return f\"<MatchInfo {super().__repr__()}: {self._route}>\"\n\n\nclass MatchInfoError(UrlMappingMatchInfo):\n\n    __slots__ = (\"_exception\",)\n\n    def __init__(self, http_exception: HTTPException) -> None:\n        self._exception = http_exception\n        super().__init__({}, SystemRoute(self._exception))\n\n    @property\n    def http_exception(self) -> HTTPException:\n        return self._exception\n\n    def __repr__(self) -> str:\n        return f\"<MatchInfoError {self._exception.status}: {self._exception.reason}>\"\n\n\nasync def _default_expect_handler(request: Request) -> None:\n    \"\"\"Default handler for Expect header.\n\n    Just send \"100 Continue\" to client.\n    raise HTTPExpectationFailed if value of header is not \"100-continue\"\n    \"\"\"\n    expect = request.headers.get(hdrs.EXPECT, \"\")\n    if request.version == HttpVersion11:\n        if expect.lower() == \"100-continue\":\n            await request.writer.write(b\"HTTP/1.1 100 Continue\\r\\n\\r\\n\")\n            # Reset output_size as we haven't started the main body yet.\n            request.writer.output_size = 0\n        else:\n            raise HTTPExpectationFailed(text=\"Unknown Expect: %s\" % expect)\n\n\nclass Resource(AbstractResource):\n    def __init__(self, *, name: str | None = None) -> None:\n        super().__init__(name=name)\n        self._routes: dict[str, ResourceRoute] = {}\n        self._any_route: ResourceRoute | None = None\n        self._allowed_methods: set[str] = set()\n\n    def add_route(\n        self,\n        method: str,\n        handler: type[AbstractView] | Handler,\n        *,\n        expect_handler: _ExpectHandler | None = None,\n    ) -> \"ResourceRoute\":\n        if route := self._routes.get(method, self._any_route):\n            raise RuntimeError(\n                \"Added route will never be executed, \"\n                f\"method {route.method} is already \"\n                \"registered\"\n            )\n\n        route_obj = ResourceRoute(method, handler, self, expect_handler=expect_handler)\n        self.register_route(route_obj)\n        return route_obj\n\n    def register_route(self, route: \"ResourceRoute\") -> None:\n        assert isinstance(\n            route, ResourceRoute\n        ), f\"Instance of Route class is required, got {route!r}\"\n        if route.method == hdrs.METH_ANY:\n            self._any_route = route\n        self._allowed_methods.add(route.method)\n        self._routes[route.method] = route\n\n    async def resolve(self, request: Request) -> _Resolve:\n        if (match_dict := self._match(request.rel_url.path_safe)) is None:\n            return None, set()\n        if route := self._routes.get(request.method, self._any_route):\n            return UrlMappingMatchInfo(match_dict, route), self._allowed_methods\n        return None, self._allowed_methods\n\n    @abc.abstractmethod\n    def _match(self, path: str) -> dict[str, str] | None:\n        \"\"\"Return dict of path values if path matches this resource, otherwise None.\"\"\"\n\n    def __len__(self) -> int:\n        return len(self._routes)\n\n    def __iter__(self) -> Iterator[\"ResourceRoute\"]:\n        return iter(self._routes.values())\n\n    # TODO: implement all abstract methods\n\n\nclass PlainResource(Resource):\n    def __init__(self, path: str, *, name: str | None = None) -> None:\n        super().__init__(name=name)\n        assert not path or path.startswith(\"/\")\n        self._path = path\n\n    @property\n    def canonical(self) -> str:\n        return self._path\n\n    def freeze(self) -> None:\n        if not self._path:\n            self._path = \"/\"\n\n    def add_prefix(self, prefix: str) -> None:\n        assert prefix.startswith(\"/\")\n        assert not prefix.endswith(\"/\")\n        assert len(prefix) > 1\n        self._path = prefix + self._path\n\n    def _match(self, path: str) -> dict[str, str] | None:\n        # string comparison is about 10 times faster than regexp matching\n        if self._path == path:\n            return {}\n        return None\n\n    def raw_match(self, path: str) -> bool:\n        return self._path == path\n\n    def get_info(self) -> _InfoDict:\n        return {\"path\": self._path}\n\n    def url_for(self) -> URL:  # type: ignore[override]\n        return URL.build(path=self._path, encoded=True)\n\n    def __repr__(self) -> str:\n        name = \"'\" + self.name + \"' \" if self.name is not None else \"\"\n        return f\"<PlainResource {name} {self._path}>\"\n\n\nclass DynamicResource(Resource):\n    DYN = re.compile(r\"\\{(?P<var>[_a-zA-Z][_a-zA-Z0-9]*)\\}\")\n    DYN_WITH_RE = re.compile(r\"\\{(?P<var>[_a-zA-Z][_a-zA-Z0-9]*):(?P<re>.+)\\}\")\n    GOOD = r\"[^{}/]+\"\n\n    def __init__(self, path: str, *, name: str | None = None) -> None:\n        super().__init__(name=name)\n        self._orig_path = path\n        pattern = \"\"\n        formatter = \"\"\n        for part in ROUTE_RE.split(path):\n            match = self.DYN.fullmatch(part)\n            if match:\n                pattern += \"(?P<{}>{})\".format(match.group(\"var\"), self.GOOD)\n                formatter += \"{\" + match.group(\"var\") + \"}\"\n                continue\n\n            match = self.DYN_WITH_RE.fullmatch(part)\n            if match:\n                pattern += \"(?P<{var}>{re})\".format(**match.groupdict())\n                formatter += \"{\" + match.group(\"var\") + \"}\"\n                continue\n\n            if \"{\" in part or \"}\" in part:\n                raise ValueError(f\"Invalid path '{path}'['{part}']\")\n\n            part = _requote_path(part)\n            formatter += part\n            pattern += re.escape(part)\n\n        try:\n            compiled = re.compile(pattern)\n        except re.error as exc:\n            raise ValueError(f\"Bad pattern '{pattern}': {exc}\") from None\n        assert compiled.pattern.startswith(PATH_SEP)\n        assert formatter.startswith(\"/\")\n        self._pattern = compiled\n        self._formatter = formatter\n\n    @property\n    def canonical(self) -> str:\n        return self._formatter\n\n    def add_prefix(self, prefix: str) -> None:\n        assert prefix.startswith(\"/\")\n        assert not prefix.endswith(\"/\")\n        assert len(prefix) > 1\n        self._pattern = re.compile(re.escape(prefix) + self._pattern.pattern)\n        self._formatter = prefix + self._formatter\n\n    def _match(self, path: str) -> dict[str, str] | None:\n        match = self._pattern.fullmatch(path)\n        if match is None:\n            return None\n        return {\n            key: _unquote_path_safe(value) for key, value in match.groupdict().items()\n        }\n\n    def raw_match(self, path: str) -> bool:\n        return self._orig_path == path\n\n    def get_info(self) -> _InfoDict:\n        return {\"formatter\": self._formatter, \"pattern\": self._pattern}\n\n    def url_for(self, **parts: str) -> URL:\n        url = self._formatter.format_map({k: _quote_path(v) for k, v in parts.items()})\n        return URL.build(path=url, encoded=True)\n\n    def __repr__(self) -> str:\n        name = \"'\" + self.name + \"' \" if self.name is not None else \"\"\n        return f\"<DynamicResource {name} {self._formatter}>\"\n\n\nclass PrefixResource(AbstractResource):\n    def __init__(self, prefix: str, *, name: str | None = None) -> None:\n        assert not prefix or prefix.startswith(\"/\"), prefix\n        assert prefix in (\"\", \"/\") or not prefix.endswith(\"/\"), prefix\n        super().__init__(name=name)\n        self._prefix = _requote_path(prefix)\n        self._prefix2 = self._prefix + \"/\"\n\n    @property\n    def canonical(self) -> str:\n        return self._prefix\n\n    def add_prefix(self, prefix: str) -> None:\n        assert prefix.startswith(\"/\")\n        assert not prefix.endswith(\"/\")\n        assert len(prefix) > 1\n        self._prefix = prefix + self._prefix\n        self._prefix2 = self._prefix + \"/\"\n\n    def raw_match(self, prefix: str) -> bool:\n        return False\n\n    # TODO: impl missing abstract methods\n\n\nclass StaticResource(PrefixResource):\n    VERSION_KEY = \"v\"\n\n    def __init__(\n        self,\n        prefix: str,\n        directory: PathLike,\n        *,\n        name: str | None = None,\n        expect_handler: _ExpectHandler | None = None,\n        chunk_size: int = 256 * 1024,\n        show_index: bool = False,\n        follow_symlinks: bool = False,\n        append_version: bool = False,\n    ) -> None:\n        super().__init__(prefix, name=name)\n        try:\n            directory = Path(directory).expanduser().resolve(strict=True)\n        except FileNotFoundError as error:\n            raise ValueError(f\"'{directory}' does not exist\") from error\n        if not directory.is_dir():\n            raise ValueError(f\"'{directory}' is not a directory\")\n        self._directory = directory\n        self._show_index = show_index\n        self._chunk_size = chunk_size\n        self._follow_symlinks = follow_symlinks\n        self._expect_handler = expect_handler\n        self._append_version = append_version\n\n        self._routes = {\n            \"GET\": ResourceRoute(\n                \"GET\", self._handle, self, expect_handler=expect_handler\n            ),\n            \"HEAD\": ResourceRoute(\n                \"HEAD\", self._handle, self, expect_handler=expect_handler\n            ),\n        }\n        self._allowed_methods = set(self._routes)\n\n    def url_for(  # type: ignore[override]\n        self,\n        *,\n        filename: PathLike,\n        append_version: bool | None = None,\n    ) -> URL:\n        if append_version is None:\n            append_version = self._append_version\n        filename = str(filename).lstrip(\"/\")\n\n        url = URL.build(path=self._prefix, encoded=True)\n        # filename is not encoded\n        url = url / filename\n\n        if append_version:\n            unresolved_path = self._directory.joinpath(filename)\n            try:\n                if self._follow_symlinks:\n                    normalized_path = Path(os.path.normpath(unresolved_path))\n                    normalized_path.relative_to(self._directory)\n                    filepath = normalized_path.resolve()\n                else:\n                    filepath = unresolved_path.resolve()\n                    filepath.relative_to(self._directory)\n            except (ValueError, FileNotFoundError):\n                # ValueError for case when path point to symlink\n                # with follow_symlinks is False\n                return url  # relatively safe\n            if filepath.is_file():\n                # TODO cache file content\n                # with file watcher for cache invalidation\n                with filepath.open(\"rb\") as f:\n                    file_bytes = f.read()\n                h = self._get_file_hash(file_bytes)\n                url = url.with_query({self.VERSION_KEY: h})\n                return url\n        return url\n\n    @staticmethod\n    def _get_file_hash(byte_array: bytes) -> str:\n        m = hashlib.sha256()  # todo sha256 can be configurable param\n        m.update(byte_array)\n        b64 = base64.urlsafe_b64encode(m.digest())\n        return b64.decode(\"ascii\")\n\n    def get_info(self) -> _InfoDict:\n        return {\n            \"directory\": self._directory,\n            \"prefix\": self._prefix,\n            \"routes\": self._routes,\n        }\n\n    def set_options_route(self, handler: Handler) -> None:\n        if \"OPTIONS\" in self._routes:\n            raise RuntimeError(\"OPTIONS route was set already\")\n        self._routes[\"OPTIONS\"] = ResourceRoute(\n            \"OPTIONS\", handler, self, expect_handler=self._expect_handler\n        )\n        self._allowed_methods.add(\"OPTIONS\")\n\n    async def resolve(self, request: Request) -> _Resolve:\n        path = request.rel_url.path_safe\n        method = request.method\n        # We normalise here to avoid matches that traverse below the static root.\n        # e.g. /static/../../../../home/user/webapp/static/\n        norm_path = os.path.normpath(path)\n        if IS_WINDOWS:\n            norm_path = norm_path.replace(\"\\\\\", \"/\")\n        if not norm_path.startswith(self._prefix2) and norm_path != self._prefix:\n            return None, set()\n\n        allowed_methods = self._allowed_methods\n        if method not in allowed_methods:\n            return None, allowed_methods\n\n        match_dict = {\"filename\": _unquote_path_safe(path[len(self._prefix) + 1 :])}\n        return (UrlMappingMatchInfo(match_dict, self._routes[method]), allowed_methods)\n\n    def __len__(self) -> int:\n        return len(self._routes)\n\n    def __iter__(self) -> Iterator[AbstractRoute]:\n        return iter(self._routes.values())\n\n    async def _handle(self, request: Request) -> StreamResponse:\n        filename = request.match_info[\"filename\"]\n        if Path(filename).is_absolute():\n            # filename is an absolute path e.g. //network/share or D:\\path\n            # which could be a UNC path leading to NTLM credential theft\n            raise HTTPNotFound()\n        unresolved_path = self._directory.joinpath(filename)\n        loop = asyncio.get_running_loop()\n        return await loop.run_in_executor(\n            None, self._resolve_path_to_response, unresolved_path\n        )\n\n    def _resolve_path_to_response(self, unresolved_path: Path) -> StreamResponse:\n        \"\"\"Take the unresolved path and query the file system to form a response.\"\"\"\n        # Check for access outside the root directory. For follow symlinks, URI\n        # cannot traverse out, but symlinks can. Otherwise, no access outside\n        # root is permitted.\n        try:\n            if self._follow_symlinks:\n                normalized_path = Path(os.path.normpath(unresolved_path))\n                normalized_path.relative_to(self._directory)\n                file_path = normalized_path.resolve()\n            else:\n                file_path = unresolved_path.resolve()\n                file_path.relative_to(self._directory)\n        except (ValueError, *CIRCULAR_SYMLINK_ERROR) as error:\n            # ValueError is raised for the relative check. Circular symlinks\n            # raise here on resolving for python < 3.13.\n            raise HTTPNotFound() from error\n\n        # if path is a directory, return the contents if permitted. Note the\n        # directory check will raise if a segment is not readable.\n        try:\n            if file_path.is_dir():\n                if self._show_index:\n                    return Response(\n                        text=self._directory_as_html(file_path),\n                        content_type=\"text/html\",\n                    )\n                else:\n                    raise HTTPForbidden()\n        except PermissionError as error:\n            raise HTTPForbidden() from error\n\n        # Return the file response, which handles all other checks.\n        return FileResponse(file_path, chunk_size=self._chunk_size)\n\n    def _directory_as_html(self, dir_path: Path) -> str:\n        \"\"\"returns directory's index as html.\"\"\"\n        assert dir_path.is_dir()\n\n        relative_path_to_dir = dir_path.relative_to(self._directory).as_posix()\n        index_of = f\"Index of /{html_escape(relative_path_to_dir)}\"\n        h1 = f\"<h1>{index_of}</h1>\"\n\n        index_list = []\n        dir_index = dir_path.iterdir()\n        for _file in sorted(dir_index):\n            # show file url as relative to static path\n            rel_path = _file.relative_to(self._directory).as_posix()\n            quoted_file_url = _quote_path(f\"{self._prefix}/{rel_path}\")\n\n            # if file is a directory, add '/' to the end of the name\n            if _file.is_dir():\n                file_name = f\"{_file.name}/\"\n            else:\n                file_name = _file.name\n\n            index_list.append(\n                f'<li><a href=\"{quoted_file_url}\">{html_escape(file_name)}</a></li>'\n            )\n        ul = \"<ul>\\n{}\\n</ul>\".format(\"\\n\".join(index_list))\n        body = f\"<body>\\n{h1}\\n{ul}\\n</body>\"\n\n        head_str = f\"<head>\\n<title>{index_of}</title>\\n</head>\"\n        html = f\"<html>\\n{head_str}\\n{body}\\n</html>\"\n\n        return html\n\n    def __repr__(self) -> str:\n        name = \"'\" + self.name + \"'\" if self.name is not None else \"\"\n        return f\"<StaticResource {name} {self._prefix} -> {self._directory!r}>\"\n\n\nclass PrefixedSubAppResource(PrefixResource):\n    def __init__(self, prefix: str, app: \"Application\") -> None:\n        super().__init__(prefix)\n        self._app = app\n        self._add_prefix_to_resources(prefix)\n\n    def add_prefix(self, prefix: str) -> None:\n        super().add_prefix(prefix)\n        self._add_prefix_to_resources(prefix)\n\n    def _add_prefix_to_resources(self, prefix: str) -> None:\n        router = self._app.router\n        for resource in router.resources():\n            # Since the canonical path of a resource is about\n            # to change, we need to unindex it and then reindex\n            router.unindex_resource(resource)\n            resource.add_prefix(prefix)\n            router.index_resource(resource)\n\n    def url_for(self, *args: str, **kwargs: str) -> URL:\n        raise RuntimeError(\".url_for() is not supported by sub-application root\")\n\n    def get_info(self) -> _InfoDict:\n        return {\"app\": self._app, \"prefix\": self._prefix}\n\n    async def resolve(self, request: Request) -> _Resolve:\n        match_info = await self._app.router.resolve(request)\n        match_info.add_app(self._app)\n        if isinstance(match_info.http_exception, HTTPMethodNotAllowed):\n            methods = match_info.http_exception.allowed_methods\n        else:\n            methods = set()\n        return match_info, methods\n\n    def __len__(self) -> int:\n        return len(self._app.router.routes())\n\n    def __iter__(self) -> Iterator[AbstractRoute]:\n        return iter(self._app.router.routes())\n\n    def __repr__(self) -> str:\n        return f\"<PrefixedSubAppResource {self._prefix} -> {self._app!r}>\"\n\n\nclass AbstractRuleMatching(abc.ABC):\n    @abc.abstractmethod  # pragma: no branch\n    async def match(self, request: Request) -> bool:\n        \"\"\"Return bool if the request satisfies the criteria\"\"\"\n\n    @abc.abstractmethod  # pragma: no branch\n    def get_info(self) -> _InfoDict:\n        \"\"\"Return a dict with additional info useful for introspection\"\"\"\n\n    @property\n    @abc.abstractmethod  # pragma: no branch\n    def canonical(self) -> str:\n        \"\"\"Return a str\"\"\"\n\n\nclass Domain(AbstractRuleMatching):\n    re_part = re.compile(r\"(?!-)[a-z\\d-]{1,63}(?<!-)\")\n\n    def __init__(self, domain: str) -> None:\n        super().__init__()\n        self._domain = self.validation(domain)\n\n    @property\n    def canonical(self) -> str:\n        return self._domain\n\n    def validation(self, domain: str) -> str:\n        if not isinstance(domain, str):\n            raise TypeError(\"Domain must be str\")\n        domain = domain.rstrip(\".\").lower()\n        if not domain:\n            raise ValueError(\"Domain cannot be empty\")\n        elif \"://\" in domain:\n            raise ValueError(\"Scheme not supported\")\n        url = URL(\"http://\" + domain)\n        assert url.raw_host is not None\n        if not all(self.re_part.fullmatch(x) for x in url.raw_host.split(\".\")):\n            raise ValueError(\"Domain not valid\")\n        if url.port == 80:\n            return url.raw_host\n        return f\"{url.raw_host}:{url.port}\"\n\n    async def match(self, request: Request) -> bool:\n        host = request.headers.get(hdrs.HOST)\n        if not host:\n            return False\n        return self.match_domain(host)\n\n    def match_domain(self, host: str) -> bool:\n        return host.lower() == self._domain\n\n    def get_info(self) -> _InfoDict:\n        return {\"domain\": self._domain}\n\n\nclass MaskDomain(Domain):\n    re_part = re.compile(r\"(?!-)[a-z\\d\\*-]{1,63}(?<!-)\")\n\n    def __init__(self, domain: str) -> None:\n        super().__init__(domain)\n        mask = self._domain.replace(\".\", r\"\\.\").replace(\"*\", \".*\")\n        self._mask = re.compile(mask)\n\n    @property\n    def canonical(self) -> str:\n        return self._mask.pattern\n\n    def match_domain(self, host: str) -> bool:\n        return self._mask.fullmatch(host) is not None\n\n\nclass MatchedSubAppResource(PrefixedSubAppResource):\n    def __init__(self, rule: AbstractRuleMatching, app: \"Application\") -> None:\n        AbstractResource.__init__(self)\n        self._prefix = \"\"\n        self._app = app\n        self._rule = rule\n\n    @property\n    def canonical(self) -> str:\n        return self._rule.canonical\n\n    def get_info(self) -> _InfoDict:\n        return {\"app\": self._app, \"rule\": self._rule}\n\n    async def resolve(self, request: Request) -> _Resolve:\n        if not await self._rule.match(request):\n            return None, set()\n        match_info = await self._app.router.resolve(request)\n        match_info.add_app(self._app)\n        if isinstance(match_info.http_exception, HTTPMethodNotAllowed):\n            methods = match_info.http_exception.allowed_methods\n        else:\n            methods = set()\n        return match_info, methods\n\n    def __repr__(self) -> str:\n        return f\"<MatchedSubAppResource -> {self._app!r}>\"\n\n\nclass ResourceRoute(AbstractRoute):\n    \"\"\"A route with resource\"\"\"\n\n    def __init__(\n        self,\n        method: str,\n        handler: Handler | type[AbstractView],\n        resource: AbstractResource,\n        *,\n        expect_handler: _ExpectHandler | None = None,\n    ) -> None:\n        super().__init__(\n            method, handler, expect_handler=expect_handler, resource=resource\n        )\n\n    def __repr__(self) -> str:\n        return f\"<ResourceRoute [{self.method}] {self._resource} -> {self.handler!r}\"\n\n    @property\n    def name(self) -> str | None:\n        if self._resource is None:\n            return None\n        return self._resource.name\n\n    def url_for(self, *args: str, **kwargs: str) -> URL:\n        \"\"\"Construct url for route with additional params.\"\"\"\n        assert self._resource is not None\n        return self._resource.url_for(*args, **kwargs)\n\n    def get_info(self) -> _InfoDict:\n        assert self._resource is not None\n        return self._resource.get_info()\n\n\nclass SystemRoute(AbstractRoute):\n    def __init__(self, http_exception: HTTPException) -> None:\n        super().__init__(hdrs.METH_ANY, self._handle)\n        self._http_exception = http_exception\n\n    def url_for(self, *args: str, **kwargs: str) -> URL:\n        raise RuntimeError(\".url_for() is not allowed for SystemRoute\")\n\n    @property\n    def name(self) -> str | None:\n        return None\n\n    def get_info(self) -> _InfoDict:\n        return {\"http_exception\": self._http_exception}\n\n    async def _handle(self, request: Request) -> StreamResponse:\n        raise self._http_exception\n\n    @property\n    def status(self) -> int:\n        return self._http_exception.status\n\n    @property\n    def reason(self) -> str:\n        return self._http_exception.reason\n\n    def __repr__(self) -> str:\n        return f\"<SystemRoute {self.status}: {self.reason}>\"\n\n\nclass View(AbstractView):\n    async def _iter(self) -> StreamResponse:\n        if self.request.method not in hdrs.METH_ALL:\n            self._raise_allowed_methods()\n        method: Callable[[], Awaitable[StreamResponse]] | None = getattr(\n            self, self.request.method.lower(), None\n        )\n        if method is None:\n            self._raise_allowed_methods()\n        return await method()\n\n    def __await__(self) -> Generator[None, None, StreamResponse]:\n        return self._iter().__await__()\n\n    def _raise_allowed_methods(self) -> NoReturn:\n        allowed_methods = {m for m in hdrs.METH_ALL if hasattr(self, m.lower())}\n        raise HTTPMethodNotAllowed(self.request.method, allowed_methods)\n\n\nclass ResourcesView(Sized, Iterable[AbstractResource], Container[AbstractResource]):\n    def __init__(self, resources: list[AbstractResource]) -> None:\n        self._resources = resources\n\n    def __len__(self) -> int:\n        return len(self._resources)\n\n    def __iter__(self) -> Iterator[AbstractResource]:\n        yield from self._resources\n\n    def __contains__(self, resource: object) -> bool:\n        return resource in self._resources\n\n\nclass RoutesView(Sized, Iterable[AbstractRoute], Container[AbstractRoute]):\n    def __init__(self, resources: list[AbstractResource]):\n        self._routes: list[AbstractRoute] = []\n        for resource in resources:\n            for route in resource:\n                self._routes.append(route)\n\n    def __len__(self) -> int:\n        return len(self._routes)\n\n    def __iter__(self) -> Iterator[AbstractRoute]:\n        yield from self._routes\n\n    def __contains__(self, route: object) -> bool:\n        return route in self._routes\n\n\nclass UrlDispatcher(AbstractRouter, Mapping[str, AbstractResource]):\n    NAME_SPLIT_RE = re.compile(r\"[.:-]\")\n    HTTP_NOT_FOUND = HTTPNotFound()\n\n    def __init__(self) -> None:\n        super().__init__()\n        self._resources: list[AbstractResource] = []\n        self._named_resources: dict[str, AbstractResource] = {}\n        self._resource_index: dict[str, list[AbstractResource]] = {}\n        self._matched_sub_app_resources: list[MatchedSubAppResource] = []\n\n    async def resolve(self, request: Request) -> UrlMappingMatchInfo:\n        resource_index = self._resource_index\n        allowed_methods: set[str] = set()\n\n        # MatchedSubAppResource is primarily used to match on domain names\n        # (though custom rules could match on other things). This means that\n        # the traversal algorithm below can't be applied, and that we likely\n        # need to check these first so a sub app that defines the same path\n        # as a parent app will get priority if there's a domain match.\n        #\n        # For most cases we do not expect there to be many of these since\n        # currently they are only added by `.add_domain()`.\n        for resource in self._matched_sub_app_resources:\n            match_dict, allowed = await resource.resolve(request)\n            if match_dict is not None:\n                return match_dict\n            else:\n                allowed_methods |= allowed\n\n        # Walk the url parts looking for candidates. We walk the url backwards\n        # to ensure the most explicit match is found first. If there are multiple\n        # candidates for a given url part because there are multiple resources\n        # registered for the same canonical path, we resolve them in a linear\n        # fashion to ensure registration order is respected.\n        url_part = request.rel_url.path_safe\n        while url_part:\n            for candidate in resource_index.get(url_part, ()):\n                match_dict, allowed = await candidate.resolve(request)\n                if match_dict is not None:\n                    return match_dict\n                else:\n                    allowed_methods |= allowed\n            if url_part == \"/\":\n                break\n            url_part = url_part.rpartition(\"/\")[0] or \"/\"\n\n        if allowed_methods:\n            return MatchInfoError(HTTPMethodNotAllowed(request.method, allowed_methods))\n\n        return MatchInfoError(self.HTTP_NOT_FOUND)\n\n    def __iter__(self) -> Iterator[str]:\n        return iter(self._named_resources)\n\n    def __len__(self) -> int:\n        return len(self._named_resources)\n\n    def __contains__(self, resource: object) -> bool:\n        return resource in self._named_resources\n\n    def __getitem__(self, name: str) -> AbstractResource:\n        return self._named_resources[name]\n\n    def resources(self) -> ResourcesView:\n        return ResourcesView(self._resources)\n\n    def routes(self) -> RoutesView:\n        return RoutesView(self._resources)\n\n    def named_resources(self) -> Mapping[str, AbstractResource]:\n        return MappingProxyType(self._named_resources)\n\n    def register_resource(self, resource: AbstractResource) -> None:\n        assert isinstance(\n            resource, AbstractResource\n        ), f\"Instance of AbstractResource class is required, got {resource!r}\"\n        if self.frozen:\n            raise RuntimeError(\"Cannot register a resource into frozen router.\")\n\n        name = resource.name\n\n        if name is not None:\n            parts = self.NAME_SPLIT_RE.split(name)\n            for part in parts:\n                if keyword.iskeyword(part):\n                    raise ValueError(\n                        f\"Incorrect route name {name!r}, \"\n                        \"python keywords cannot be used \"\n                        \"for route name\"\n                    )\n                if not part.isidentifier():\n                    raise ValueError(\n                        f\"Incorrect route name {name!r}, \"\n                        \"the name should be a sequence of \"\n                        \"python identifiers separated \"\n                        \"by dash, dot or column\"\n                    )\n            if name in self._named_resources:\n                raise ValueError(\n                    f\"Duplicate {name!r}, \"\n                    f\"already handled by {self._named_resources[name]!r}\"\n                )\n            self._named_resources[name] = resource\n        self._resources.append(resource)\n\n        if isinstance(resource, MatchedSubAppResource):\n            # We cannot index match sub-app resources because they have match rules\n            self._matched_sub_app_resources.append(resource)\n        else:\n            self.index_resource(resource)\n\n    def _get_resource_index_key(self, resource: AbstractResource) -> str:\n        \"\"\"Return a key to index the resource in the resource index.\"\"\"\n        if \"{\" in (index_key := resource.canonical):\n            # strip at the first { to allow for variables, and than\n            # rpartition at / to allow for variable parts in the path\n            # For example if the canonical path is `/core/locations{tail:.*}`\n            # the index key will be `/core` since index is based on the\n            # url parts split by `/`\n            index_key = index_key.partition(\"{\")[0].rpartition(\"/\")[0]\n        return index_key.rstrip(\"/\") or \"/\"\n\n    def index_resource(self, resource: AbstractResource) -> None:\n        \"\"\"Add a resource to the resource index.\"\"\"\n        resource_key = self._get_resource_index_key(resource)\n        # There may be multiple resources for a canonical path\n        # so we keep them in a list to ensure that registration\n        # order is respected.\n        self._resource_index.setdefault(resource_key, []).append(resource)\n\n    def unindex_resource(self, resource: AbstractResource) -> None:\n        \"\"\"Remove a resource from the resource index.\"\"\"\n        resource_key = self._get_resource_index_key(resource)\n        self._resource_index[resource_key].remove(resource)\n\n    def add_resource(self, path: str, *, name: str | None = None) -> Resource:\n        if path and not path.startswith(\"/\"):\n            raise ValueError(\"path should be started with / or be empty\")\n        # Reuse last added resource if path and name are the same\n        if self._resources:\n            resource = self._resources[-1]\n            if resource.name == name and resource.raw_match(path):\n                return cast(Resource, resource)\n        if not (\"{\" in path or \"}\" in path or ROUTE_RE.search(path)):\n            resource = PlainResource(path, name=name)\n            self.register_resource(resource)\n            return resource\n        resource = DynamicResource(path, name=name)\n        self.register_resource(resource)\n        return resource\n\n    def add_route(\n        self,\n        method: str,\n        path: str,\n        handler: Handler | type[AbstractView],\n        *,\n        name: str | None = None,\n        expect_handler: _ExpectHandler | None = None,\n    ) -> AbstractRoute:\n        resource = self.add_resource(path, name=name)\n        return resource.add_route(method, handler, expect_handler=expect_handler)\n\n    def add_static(\n        self,\n        prefix: str,\n        path: PathLike,\n        *,\n        name: str | None = None,\n        expect_handler: _ExpectHandler | None = None,\n        chunk_size: int = 256 * 1024,\n        show_index: bool = False,\n        follow_symlinks: bool = False,\n        append_version: bool = False,\n    ) -> StaticResource:\n        \"\"\"Add static files view.\n\n        prefix - url prefix\n        path - folder with files\n\n        \"\"\"\n        assert prefix.startswith(\"/\")\n        if prefix.endswith(\"/\"):\n            prefix = prefix[:-1]\n        resource = StaticResource(\n            prefix,\n            path,\n            name=name,\n            expect_handler=expect_handler,\n            chunk_size=chunk_size,\n            show_index=show_index,\n            follow_symlinks=follow_symlinks,\n            append_version=append_version,\n        )\n        self.register_resource(resource)\n        return resource\n\n    def add_head(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute:\n        \"\"\"Shortcut for add_route with method HEAD.\"\"\"\n        return self.add_route(hdrs.METH_HEAD, path, handler, **kwargs)\n\n    def add_options(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute:\n        \"\"\"Shortcut for add_route with method OPTIONS.\"\"\"\n        return self.add_route(hdrs.METH_OPTIONS, path, handler, **kwargs)\n\n    def add_get(\n        self,\n        path: str,\n        handler: Handler,\n        *,\n        name: str | None = None,\n        allow_head: bool = True,\n        **kwargs: Any,\n    ) -> AbstractRoute:\n        \"\"\"Shortcut for add_route with method GET.\n\n        If allow_head is true, another\n        route is added allowing head requests to the same endpoint.\n        \"\"\"\n        resource = self.add_resource(path, name=name)\n        if allow_head:\n            resource.add_route(hdrs.METH_HEAD, handler, **kwargs)\n        return resource.add_route(hdrs.METH_GET, handler, **kwargs)\n\n    def add_post(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute:\n        \"\"\"Shortcut for add_route with method POST.\"\"\"\n        return self.add_route(hdrs.METH_POST, path, handler, **kwargs)\n\n    def add_put(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute:\n        \"\"\"Shortcut for add_route with method PUT.\"\"\"\n        return self.add_route(hdrs.METH_PUT, path, handler, **kwargs)\n\n    def add_patch(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute:\n        \"\"\"Shortcut for add_route with method PATCH.\"\"\"\n        return self.add_route(hdrs.METH_PATCH, path, handler, **kwargs)\n\n    def add_delete(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute:\n        \"\"\"Shortcut for add_route with method DELETE.\"\"\"\n        return self.add_route(hdrs.METH_DELETE, path, handler, **kwargs)\n\n    def add_view(\n        self, path: str, handler: type[AbstractView], **kwargs: Any\n    ) -> AbstractRoute:\n        \"\"\"Shortcut for add_route with ANY methods for a class-based view.\"\"\"\n        return self.add_route(hdrs.METH_ANY, path, handler, **kwargs)\n\n    def freeze(self) -> None:\n        super().freeze()\n        for resource in self._resources:\n            resource.freeze()\n\n    def add_routes(self, routes: Iterable[AbstractRouteDef]) -> list[AbstractRoute]:\n        \"\"\"Append routes to route table.\n\n        Parameter should be a sequence of RouteDef objects.\n\n        Returns a list of registered AbstractRoute instances.\n        \"\"\"\n        registered_routes = []\n        for route_def in routes:\n            registered_routes.extend(route_def.register(self))\n        return registered_routes\n\n\ndef _quote_path(value: str) -> str:\n    return URL.build(path=value, encoded=False).raw_path\n\n\ndef _unquote_path_safe(value: str) -> str:\n    if \"%\" not in value:\n        return value\n    return value.replace(\"%2F\", \"/\").replace(\"%25\", \"%\")\n\n\ndef _requote_path(value: str) -> str:\n    # Quote non-ascii characters and other characters which must be quoted,\n    # but preserve existing %-sequences.\n    result = _quote_path(value)\n    if \"%\" in value:\n        result = result.replace(\"%25\", \"%\")\n    return result\n"
  },
  {
    "path": "aiohttp/web_ws.py",
    "content": "import asyncio\nimport base64\nimport binascii\nimport hashlib\nimport json\nimport sys\nfrom collections.abc import Callable, Iterable\nfrom typing import Any, Final, Generic, Literal, Union, overload\n\nfrom multidict import CIMultiDict\n\nfrom . import hdrs\nfrom ._websocket.reader import WebSocketDataQueue\nfrom ._websocket.writer import DEFAULT_LIMIT\nfrom .abc import AbstractStreamWriter\nfrom .client_exceptions import WSMessageTypeError\nfrom .helpers import (\n    calculate_timeout_when,\n    frozen_dataclass_decorator,\n    set_exception,\n    set_result,\n)\nfrom .http import (\n    WS_CLOSED_MESSAGE,\n    WS_CLOSING_MESSAGE,\n    WS_KEY,\n    WebSocketError,\n    WebSocketReader,\n    WebSocketWriter,\n    WSCloseCode,\n    WSMessageDecodeText,\n    WSMessageNoDecodeText,\n    WSMsgType,\n    ws_ext_gen,\n    ws_ext_parse,\n)\nfrom .http_websocket import _INTERNAL_RECEIVE_TYPES, WSMessageError\nfrom .log import ws_logger\nfrom .streams import EofStream\nfrom .typedefs import JSONBytesEncoder, JSONDecoder, JSONEncoder\nfrom .web_exceptions import HTTPBadRequest, HTTPException\nfrom .web_request import BaseRequest\nfrom .web_response import StreamResponse\n\nif sys.version_info >= (3, 13):\n    from typing import TypeVar\nelse:\n    from typing_extensions import TypeVar\n\nif sys.version_info >= (3, 11):\n    import asyncio as async_timeout\n    from typing import Self\nelse:\n    import async_timeout\n    from typing_extensions import Self\n\n__all__ = (\n    \"WebSocketResponse\",\n    \"WebSocketReady\",\n    \"WSMsgType\",\n)\n\nTHRESHOLD_CONNLOST_ACCESS: Final[int] = 5\n\n# TypeVar for whether text messages are decoded to str (True) or kept as bytes (False)\n_DecodeText = TypeVar(\"_DecodeText\", bound=bool, covariant=True, default=Literal[True])\n\n\n@frozen_dataclass_decorator\nclass WebSocketReady:\n    ok: bool\n    protocol: str | None\n\n    def __bool__(self) -> bool:\n        return self.ok\n\n\nclass WebSocketResponse(StreamResponse, Generic[_DecodeText]):\n\n    _length_check: bool = False\n    _ws_protocol: str | None = None\n    _writer: WebSocketWriter | None = None\n    _reader: WebSocketDataQueue | None = None\n    _closed: bool = False\n    _closing: bool = False\n    _conn_lost: int = 0\n    _close_code: int | None = None\n    _loop: asyncio.AbstractEventLoop | None = None\n    _waiting: bool = False\n    _close_wait: asyncio.Future[None] | None = None\n    _exception: BaseException | None = None\n    _heartbeat_when: float = 0.0\n    _heartbeat_cb: asyncio.TimerHandle | None = None\n    _pong_response_cb: asyncio.TimerHandle | None = None\n    _ping_task: asyncio.Task[None] | None = None\n    _need_heartbeat_reset: bool = False\n    _heartbeat_reset_handle: asyncio.Handle | None = None\n\n    def __init__(\n        self,\n        *,\n        timeout: float = 10.0,\n        receive_timeout: float | None = None,\n        autoclose: bool = True,\n        autoping: bool = True,\n        heartbeat: float | None = None,\n        protocols: Iterable[str] = (),\n        compress: bool = True,\n        max_msg_size: int = 4 * 1024 * 1024,\n        writer_limit: int = DEFAULT_LIMIT,\n        decode_text: bool = True,\n    ) -> None:\n        super().__init__(status=101)\n        self._protocols = protocols\n        self._timeout = timeout\n        self._receive_timeout = receive_timeout\n        self._autoclose = autoclose\n        self._autoping = autoping\n        self._heartbeat = heartbeat\n        if heartbeat is not None:\n            self._pong_heartbeat = heartbeat / 2.0\n        self._compress: bool | int = compress\n        self._max_msg_size = max_msg_size\n        self._writer_limit = writer_limit\n        self._decode_text = decode_text\n        self._need_heartbeat_reset = False\n        self._heartbeat_reset_handle = None\n\n    def _cancel_heartbeat(self) -> None:\n        self._cancel_pong_response_cb()\n        if self._heartbeat_reset_handle is not None:\n            self._heartbeat_reset_handle.cancel()\n            self._heartbeat_reset_handle = None\n        self._need_heartbeat_reset = False\n        if self._heartbeat_cb is not None:\n            self._heartbeat_cb.cancel()\n            self._heartbeat_cb = None\n        if self._ping_task is not None:\n            self._ping_task.cancel()\n            self._ping_task = None\n\n    def _cancel_pong_response_cb(self) -> None:\n        if self._pong_response_cb is not None:\n            self._pong_response_cb.cancel()\n            self._pong_response_cb = None\n\n    def _on_data_received(self) -> None:\n        if self._heartbeat is None or self._need_heartbeat_reset:\n            return\n        loop = self._loop\n        assert loop is not None\n        # Coalesce multiple chunks received in the same loop tick into a single\n        # heartbeat reset. Resetting immediately per chunk increases timer churn.\n        self._need_heartbeat_reset = True\n        self._heartbeat_reset_handle = loop.call_soon(self._flush_heartbeat_reset)\n\n    def _flush_heartbeat_reset(self) -> None:\n        self._heartbeat_reset_handle = None\n        if not self._need_heartbeat_reset:\n            return\n        self._reset_heartbeat()\n        self._need_heartbeat_reset = False\n\n    def _reset_heartbeat(self) -> None:\n        if self._heartbeat is None:\n            return\n        self._cancel_pong_response_cb()\n        req = self._req\n        timeout_ceil_threshold = (\n            req._protocol._timeout_ceil_threshold if req is not None else 5\n        )\n        loop = self._loop\n        assert loop is not None\n        now = loop.time()\n        when = calculate_timeout_when(now, self._heartbeat, timeout_ceil_threshold)\n        self._heartbeat_when = when\n        if self._heartbeat_cb is None:\n            # We do not cancel the previous heartbeat_cb here because\n            # it generates a significant amount of TimerHandle churn\n            # which causes asyncio to rebuild the heap frequently.\n            # Instead _send_heartbeat() will reschedule the next\n            # heartbeat if it fires too early.\n            self._heartbeat_cb = loop.call_at(when, self._send_heartbeat)\n\n    def _send_heartbeat(self) -> None:\n        self._heartbeat_cb = None\n\n        # If heartbeat reset is pending (data is being received), skip sending\n        # the ping and let the reset callback handle rescheduling the heartbeat.\n        if self._need_heartbeat_reset:\n            return\n\n        loop = self._loop\n        assert loop is not None and self._writer is not None\n        now = loop.time()\n        if now < self._heartbeat_when:\n            # Heartbeat fired too early, reschedule\n            self._heartbeat_cb = loop.call_at(\n                self._heartbeat_when, self._send_heartbeat\n            )\n            return\n\n        req = self._req\n        timeout_ceil_threshold = (\n            req._protocol._timeout_ceil_threshold if req is not None else 5\n        )\n        when = calculate_timeout_when(now, self._pong_heartbeat, timeout_ceil_threshold)\n        self._cancel_pong_response_cb()\n        self._pong_response_cb = loop.call_at(when, self._pong_not_received)\n\n        coro = self._writer.send_frame(b\"\", WSMsgType.PING)\n        if sys.version_info >= (3, 12):\n            # Optimization for Python 3.12, try to send the ping\n            # immediately to avoid having to schedule\n            # the task on the event loop.\n            ping_task = asyncio.Task(coro, loop=loop, eager_start=True)\n        else:\n            ping_task = loop.create_task(coro)\n\n        if not ping_task.done():\n            self._ping_task = ping_task\n            ping_task.add_done_callback(self._ping_task_done)\n        else:\n            self._ping_task_done(ping_task)\n\n    def _ping_task_done(self, task: \"asyncio.Task[None]\") -> None:\n        \"\"\"Callback for when the ping task completes.\"\"\"\n        if not task.cancelled() and (exc := task.exception()):\n            self._handle_ping_pong_exception(exc)\n        self._ping_task = None\n\n    def _pong_not_received(self) -> None:\n        if self._req is not None and self._req.transport is not None:\n            self._handle_ping_pong_exception(\n                asyncio.TimeoutError(\n                    f\"No PONG received after {self._pong_heartbeat} seconds\"\n                )\n            )\n\n    def _handle_ping_pong_exception(self, exc: BaseException) -> None:\n        \"\"\"Handle exceptions raised during ping/pong processing.\"\"\"\n        if self._closed:\n            return\n        self._set_closed()\n        self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE)\n        self._exception = exc\n        if self._waiting and not self._closing and self._reader is not None:\n            self._reader.feed_data(WSMessageError(data=exc, extra=None))\n\n    def _set_closed(self) -> None:\n        \"\"\"Set the connection to closed.\n\n        Cancel any heartbeat timers and set the closed flag.\n        \"\"\"\n        self._closed = True\n        self._cancel_heartbeat()\n\n    async def prepare(self, request: BaseRequest) -> AbstractStreamWriter:\n        # make pre-check to don't hide it by do_handshake() exceptions\n        if self._payload_writer is not None:\n            return self._payload_writer\n\n        protocol, writer = self._pre_start(request)\n        payload_writer = await super().prepare(request)\n        assert payload_writer is not None\n        self._post_start(request, protocol, writer)\n        await payload_writer.drain()\n        return payload_writer\n\n    def _handshake(\n        self, request: BaseRequest\n    ) -> tuple[\"CIMultiDict[str]\", str | None, int, bool]:\n        headers = request.headers\n        if \"websocket\" != headers.get(hdrs.UPGRADE, \"\").lower().strip():\n            raise HTTPBadRequest(\n                text=(\n                    f\"No WebSocket UPGRADE hdr: {headers.get(hdrs.UPGRADE)}\\n Can \"\n                    '\"Upgrade\" only to \"WebSocket\".'\n                )\n            )\n\n        if \"upgrade\" not in headers.get(hdrs.CONNECTION, \"\").lower():\n            raise HTTPBadRequest(\n                text=f\"No CONNECTION upgrade hdr: {headers.get(hdrs.CONNECTION)}\"\n            )\n\n        # find common sub-protocol between client and server\n        protocol: str | None = None\n        if hdrs.SEC_WEBSOCKET_PROTOCOL in headers:\n            req_protocols = [\n                str(proto.strip())\n                for proto in headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(\",\")\n            ]\n\n            for proto in req_protocols:\n                if proto in self._protocols:\n                    protocol = proto\n                    break\n            else:\n                # No overlap found: Return no protocol as per spec\n                ws_logger.warning(\n                    \"%s: Client protocols %r don’t overlap server-known ones %r\",\n                    request.remote,\n                    req_protocols,\n                    self._protocols,\n                )\n\n        # check supported version\n        version = headers.get(hdrs.SEC_WEBSOCKET_VERSION, \"\")\n        if version not in (\"13\", \"8\", \"7\"):\n            raise HTTPBadRequest(text=f\"Unsupported version: {version}\")\n\n        # check client handshake for validity\n        key = headers.get(hdrs.SEC_WEBSOCKET_KEY)\n        try:\n            if not key or len(base64.b64decode(key)) != 16:\n                raise HTTPBadRequest(text=f\"Handshake error: {key!r}\")\n        except binascii.Error:\n            raise HTTPBadRequest(text=f\"Handshake error: {key!r}\") from None\n\n        accept_val = base64.b64encode(\n            hashlib.sha1(key.encode() + WS_KEY).digest()\n        ).decode()\n        response_headers = CIMultiDict(\n            {\n                hdrs.UPGRADE: \"websocket\",\n                hdrs.CONNECTION: \"upgrade\",\n                hdrs.SEC_WEBSOCKET_ACCEPT: accept_val,\n            }\n        )\n\n        notakeover = False\n        compress = 0\n        if self._compress:\n            extensions = headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS)\n            # Server side always get return with no exception.\n            # If something happened, just drop compress extension\n            compress, notakeover = ws_ext_parse(extensions, isserver=True)\n            if compress:\n                enabledext = ws_ext_gen(\n                    compress=compress, isserver=True, server_notakeover=notakeover\n                )\n                response_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = enabledext\n\n        if protocol:\n            response_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = protocol\n        return (\n            response_headers,\n            protocol,\n            compress,\n            notakeover,\n        )\n\n    def _pre_start(self, request: BaseRequest) -> tuple[str | None, WebSocketWriter]:\n        self._loop = request._loop\n\n        headers, protocol, compress, notakeover = self._handshake(request)\n\n        self.set_status(101)\n        self.headers.update(headers)\n        self.force_close()\n        self._compress = compress\n        transport = request._protocol.transport\n        assert transport is not None\n        writer = WebSocketWriter(\n            request._protocol,\n            transport,\n            compress=compress,\n            notakeover=notakeover,\n            limit=self._writer_limit,\n        )\n\n        return protocol, writer\n\n    def _post_start(\n        self, request: BaseRequest, protocol: str | None, writer: WebSocketWriter\n    ) -> None:\n        self._ws_protocol = protocol\n        self._writer = writer\n\n        self._reset_heartbeat()\n\n        loop = self._loop\n        assert loop is not None\n        self._reader = WebSocketDataQueue(request._protocol, 2**16, loop=loop)\n        parser = WebSocketReader(\n            self._reader,\n            self._max_msg_size,\n            compress=bool(self._compress),\n            decode_text=self._decode_text,\n        )\n        cb = None if self._heartbeat is None else self._on_data_received\n        request.protocol.set_parser(parser, data_received_cb=cb)\n        # disable HTTP keepalive for WebSocket\n        request.protocol.keep_alive(False)\n\n    def can_prepare(self, request: BaseRequest) -> WebSocketReady:\n        if self._writer is not None:\n            raise RuntimeError(\"Already started\")\n        try:\n            _, protocol, _, _ = self._handshake(request)\n        except HTTPException:\n            return WebSocketReady(False, None)\n        else:\n            return WebSocketReady(True, protocol)\n\n    @property\n    def prepared(self) -> bool:\n        return self._writer is not None\n\n    @property\n    def closed(self) -> bool:\n        return self._closed\n\n    @property\n    def close_code(self) -> int | None:\n        return self._close_code\n\n    @property\n    def ws_protocol(self) -> str | None:\n        return self._ws_protocol\n\n    @property\n    def compress(self) -> int | bool:\n        return self._compress\n\n    def get_extra_info(self, name: str, default: Any = None) -> Any:\n        \"\"\"Get optional transport information.\n\n        If no value associated with ``name`` is found, ``default`` is returned.\n        \"\"\"\n        writer = self._writer\n        if writer is None:\n            return default\n        return writer.transport.get_extra_info(name, default)\n\n    def exception(self) -> BaseException | None:\n        return self._exception\n\n    async def ping(self, message: bytes = b\"\") -> None:\n        if self._writer is None:\n            raise RuntimeError(\"Call .prepare() first\")\n        await self._writer.send_frame(message, WSMsgType.PING)\n\n    async def pong(self, message: bytes = b\"\") -> None:\n        # unsolicited pong\n        if self._writer is None:\n            raise RuntimeError(\"Call .prepare() first\")\n        await self._writer.send_frame(message, WSMsgType.PONG)\n\n    async def send_frame(\n        self, message: bytes, opcode: WSMsgType, compress: int | None = None\n    ) -> None:\n        \"\"\"Send a frame over the websocket.\"\"\"\n        if self._writer is None:\n            raise RuntimeError(\"Call .prepare() first\")\n        await self._writer.send_frame(message, opcode, compress)\n\n    async def send_str(self, data: str, compress: int | None = None) -> None:\n        if self._writer is None:\n            raise RuntimeError(\"Call .prepare() first\")\n        if not isinstance(data, str):\n            raise TypeError(\"data argument must be str (%r)\" % type(data))\n        await self._writer.send_frame(\n            data.encode(\"utf-8\"), WSMsgType.TEXT, compress=compress\n        )\n\n    async def send_bytes(self, data: bytes, compress: int | None = None) -> None:\n        if self._writer is None:\n            raise RuntimeError(\"Call .prepare() first\")\n        if not isinstance(data, (bytes, bytearray, memoryview)):\n            raise TypeError(\"data argument must be byte-ish (%r)\" % type(data))\n        await self._writer.send_frame(data, WSMsgType.BINARY, compress=compress)\n\n    async def send_json(\n        self,\n        data: Any,\n        compress: int | None = None,\n        *,\n        dumps: JSONEncoder = json.dumps,\n    ) -> None:\n        await self.send_str(dumps(data), compress=compress)\n\n    async def send_json_bytes(\n        self,\n        data: Any,\n        compress: int | None = None,\n        *,\n        dumps: JSONBytesEncoder,\n    ) -> None:\n        \"\"\"Send JSON data using a bytes-returning encoder as a binary frame.\n\n        Use this when your JSON encoder (like orjson) returns bytes\n        instead of str, avoiding the encode/decode overhead.\n        \"\"\"\n        await self.send_bytes(dumps(data), compress=compress)\n\n    async def write_eof(self) -> None:  # type: ignore[override]\n        if self._eof_sent:\n            return\n        if self._payload_writer is None:\n            raise RuntimeError(\"Response has not been started\")\n\n        await self.close()\n        self._eof_sent = True\n\n    async def close(\n        self, *, code: int = WSCloseCode.OK, message: bytes = b\"\", drain: bool = True\n    ) -> bool:\n        \"\"\"Close websocket connection.\"\"\"\n        if self._writer is None:\n            raise RuntimeError(\"Call .prepare() first\")\n\n        if self._closed:\n            return False\n        self._set_closed()\n\n        try:\n            await self._writer.close(code, message)\n            writer = self._payload_writer\n            assert writer is not None\n            if drain:\n                await writer.drain()\n        except (asyncio.CancelledError, asyncio.TimeoutError):\n            self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE)\n            raise\n        except Exception as exc:\n            self._exception = exc\n            self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE)\n            return True\n\n        reader = self._reader\n        assert reader is not None\n        # we need to break `receive()` cycle before we can call\n        # `reader.read()` as `close()` may be called from different task\n        if self._waiting:\n            assert self._loop is not None\n            assert self._close_wait is None\n            self._close_wait = self._loop.create_future()\n            reader.feed_data(WS_CLOSING_MESSAGE)\n            await self._close_wait\n\n        if self._closing:\n            self._close_transport()\n            return True\n\n        try:\n            async with async_timeout.timeout(self._timeout):\n                while True:\n                    msg = await reader.read()\n                    if msg.type is WSMsgType.CLOSE:\n                        self._set_code_close_transport(msg.data)\n                        return True\n        except asyncio.CancelledError:\n            self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE)\n            raise\n        except Exception as exc:\n            self._exception = exc\n            self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE)\n            return True\n\n    def _set_closing(self, code: int) -> None:\n        \"\"\"Set the close code and mark the connection as closing.\"\"\"\n        self._closing = True\n        self._close_code = code\n        self._cancel_heartbeat()\n\n    def _set_code_close_transport(self, code: int) -> None:\n        \"\"\"Set the close code and close the transport.\"\"\"\n        self._close_code = code\n        self._close_transport()\n\n    def _close_transport(self) -> None:\n        \"\"\"Close the transport.\"\"\"\n        if self._req is not None and self._req.transport is not None:\n            self._req.transport.close()\n\n    @overload\n    async def receive(\n        self: \"WebSocketResponse[Literal[True]]\", timeout: float | None = None\n    ) -> WSMessageDecodeText: ...\n\n    @overload\n    async def receive(\n        self: \"WebSocketResponse[Literal[False]]\", timeout: float | None = None\n    ) -> WSMessageNoDecodeText: ...\n\n    @overload\n    async def receive(\n        self: \"WebSocketResponse[_DecodeText]\", timeout: float | None = None\n    ) -> WSMessageDecodeText | WSMessageNoDecodeText: ...\n\n    async def receive(\n        self, timeout: float | None = None\n    ) -> WSMessageDecodeText | WSMessageNoDecodeText:\n        if self._reader is None:\n            raise RuntimeError(\"Call .prepare() first\")\n\n        receive_timeout = timeout or self._receive_timeout\n        while True:\n            if self._waiting:\n                raise RuntimeError(\"Concurrent call to receive() is not allowed\")\n\n            if self._closed:\n                self._conn_lost += 1\n                if self._conn_lost >= THRESHOLD_CONNLOST_ACCESS:\n                    raise RuntimeError(\"WebSocket connection is closed.\")\n                return WS_CLOSED_MESSAGE\n            elif self._closing:\n                return WS_CLOSING_MESSAGE\n\n            try:\n                self._waiting = True\n                try:\n                    if receive_timeout:\n                        # Entering the context manager and creating\n                        # Timeout() object can take almost 50% of the\n                        # run time in this loop so we avoid it if\n                        # there is no read timeout.\n                        async with async_timeout.timeout(receive_timeout):\n                            msg = await self._reader.read()\n                    else:\n                        msg = await self._reader.read()\n                finally:\n                    self._waiting = False\n                    if self._close_wait:\n                        set_result(self._close_wait, None)\n            except asyncio.TimeoutError:\n                raise\n            except EofStream:\n                self._close_code = WSCloseCode.OK\n                await self.close()\n                return WS_CLOSED_MESSAGE\n            except WebSocketError as exc:\n                self._close_code = exc.code\n                await self.close(code=exc.code)\n                return WSMessageError(data=exc)\n            except Exception as exc:\n                self._exception = exc\n                self._set_closing(WSCloseCode.ABNORMAL_CLOSURE)\n                await self.close()\n                return WSMessageError(data=exc)\n\n            if msg.type not in _INTERNAL_RECEIVE_TYPES:\n                # If its not a close/closing/ping/pong message\n                # we can return it immediately\n                return msg\n\n            if msg.type is WSMsgType.CLOSE:\n                self._set_closing(msg.data)\n                # Could be closed while awaiting reader.\n                if not self._closed and self._autoclose:  # type: ignore[redundant-expr]\n                    # The client is likely going to close the\n                    # connection out from under us so we do not\n                    # want to drain any pending writes as it will\n                    # likely result writing to a broken pipe.\n                    await self.close(drain=False)\n            elif msg.type is WSMsgType.CLOSING:\n                self._set_closing(WSCloseCode.OK)\n            elif msg.type is WSMsgType.PING and self._autoping:\n                await self.pong(msg.data)\n                continue\n            elif msg.type is WSMsgType.PONG and self._autoping:\n                continue\n\n            return msg\n\n    @overload\n    async def receive_str(\n        self: \"WebSocketResponse[Literal[True]]\", *, timeout: float | None = None\n    ) -> str: ...\n\n    @overload\n    async def receive_str(\n        self: \"WebSocketResponse[Literal[False]]\", *, timeout: float | None = None\n    ) -> bytes: ...\n\n    @overload\n    async def receive_str(\n        self: \"WebSocketResponse[_DecodeText]\", *, timeout: float | None = None\n    ) -> str | bytes: ...\n\n    async def receive_str(self, *, timeout: float | None = None) -> str | bytes:\n        \"\"\"Receive TEXT message.\n\n        Returns str when decode_text=True (default), bytes when decode_text=False.\n        \"\"\"\n        msg = await self.receive(timeout)\n        if msg.type is not WSMsgType.TEXT:\n            raise WSMessageTypeError(\n                f\"Received message {msg.type}:{msg.data!r} is not WSMsgType.TEXT\"\n            )\n        return msg.data\n\n    async def receive_bytes(self, *, timeout: float | None = None) -> bytes:\n        msg = await self.receive(timeout)\n        if msg.type is not WSMsgType.BINARY:\n            raise WSMessageTypeError(\n                f\"Received message {msg.type}:{msg.data!r} is not WSMsgType.BINARY\"\n            )\n        return msg.data\n\n    @overload\n    async def receive_json(\n        self: \"WebSocketResponse[Literal[True]]\",\n        *,\n        loads: JSONDecoder = ...,\n        timeout: float | None = None,\n    ) -> Any: ...\n\n    @overload\n    async def receive_json(\n        self: \"WebSocketResponse[Literal[False]]\",\n        *,\n        loads: Callable[[bytes], Any] = ...,\n        timeout: float | None = None,\n    ) -> Any: ...\n\n    @overload\n    async def receive_json(\n        self: \"WebSocketResponse[_DecodeText]\",\n        *,\n        loads: JSONDecoder | Callable[[bytes], Any] = ...,\n        timeout: float | None = None,\n    ) -> Any: ...\n\n    async def receive_json(\n        self,\n        *,\n        loads: JSONDecoder | Callable[[bytes], Any] = json.loads,\n        timeout: float | None = None,\n    ) -> Any:\n        data = await self.receive_str(timeout=timeout)\n        return loads(data)  # type: ignore[arg-type]\n\n    async def write(\n        self, data: Union[bytes, bytearray, \"memoryview[int]\", \"memoryview[bytes]\"]\n    ) -> None:\n        raise RuntimeError(\"Cannot call .write() for websocket\")\n\n    def __aiter__(self) -> Self:\n        return self\n\n    @overload\n    async def __anext__(\n        self: \"WebSocketResponse[Literal[True]]\",\n    ) -> WSMessageDecodeText: ...\n\n    @overload\n    async def __anext__(\n        self: \"WebSocketResponse[Literal[False]]\",\n    ) -> WSMessageNoDecodeText: ...\n\n    @overload\n    async def __anext__(\n        self: \"WebSocketResponse[_DecodeText]\",\n    ) -> WSMessageDecodeText | WSMessageNoDecodeText: ...\n\n    async def __anext__(self) -> WSMessageDecodeText | WSMessageNoDecodeText:\n        msg = await self.receive()\n        if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED):\n            raise StopAsyncIteration\n        return msg\n\n    def _cancel(self, exc: BaseException) -> None:\n        # web_protocol calls this from connection_lost\n        # or when the server is shutting down.\n        self._closing = True\n        self._cancel_heartbeat()\n        if self._reader is not None:\n            set_exception(self._reader, exc)\n"
  },
  {
    "path": "aiohttp/worker.py",
    "content": "\"\"\"Async gunicorn worker for aiohttp.web\"\"\"\n\nimport asyncio\nimport inspect\nimport os\nimport re\nimport signal\nimport sys\nfrom types import FrameType\nfrom typing import Any, Optional\n\nfrom gunicorn.config import AccessLogFormat as GunicornAccessLogFormat\nfrom gunicorn.workers import base\n\nfrom aiohttp import web\n\nfrom .helpers import set_result\nfrom .web_app import Application\nfrom .web_log import AccessLogger\n\ntry:\n    import ssl\n\n    SSLContext = ssl.SSLContext\nexcept ImportError:  # pragma: no cover\n    ssl = None  # type: ignore[assignment]\n    SSLContext = object  # type: ignore[misc,assignment]\n\n\n__all__ = (\"GunicornWebWorker\", \"GunicornUVLoopWebWorker\")\n\n\nclass GunicornWebWorker(base.Worker):  # type: ignore[misc,no-any-unimported]\n    DEFAULT_AIOHTTP_LOG_FORMAT = AccessLogger.LOG_FORMAT\n    DEFAULT_GUNICORN_LOG_FORMAT = GunicornAccessLogFormat.default\n\n    def __init__(self, *args: Any, **kw: Any) -> None:\n        super().__init__(*args, **kw)\n\n        self._task: asyncio.Task[None] | None = None\n        self.exit_code = 0\n        self._notify_waiter: asyncio.Future[bool] | None = None\n\n    def init_process(self) -> None:\n        # create new event_loop after fork\n        self.loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(self.loop)\n\n        super().init_process()\n\n    def run(self) -> None:\n        self._task = self.loop.create_task(self._run())\n\n        try:  # ignore all finalization problems\n            self.loop.run_until_complete(self._task)\n        except Exception:\n            self.log.exception(\"Exception in gunicorn worker\")\n        self.loop.run_until_complete(self.loop.shutdown_asyncgens())\n        self.loop.close()\n\n        sys.exit(self.exit_code)\n\n    async def _run(self) -> None:\n        runner = None\n        if isinstance(self.wsgi, Application):\n            app = self.wsgi\n        elif inspect.iscoroutinefunction(self.wsgi) or (\n            sys.version_info < (3, 14) and asyncio.iscoroutinefunction(self.wsgi)\n        ):\n            wsgi = await self.wsgi()\n            if isinstance(wsgi, web.AppRunner):\n                runner = wsgi\n                app = runner.app\n            else:\n                app = wsgi\n        else:\n            raise RuntimeError(\n                \"wsgi app should be either Application or \"\n                f\"async function returning Application, got {self.wsgi}\"\n            )\n\n        if runner is None:\n            access_log = self.log.access_log if self.cfg.accesslog else None\n            runner = web.AppRunner(\n                app,\n                logger=self.log,\n                keepalive_timeout=self.cfg.keepalive,\n                access_log=access_log,\n                access_log_format=self._get_valid_log_format(\n                    self.cfg.access_log_format\n                ),\n                shutdown_timeout=self.cfg.graceful_timeout / 100 * 95,\n            )\n        await runner.setup()\n\n        ctx = self._create_ssl_context(self.cfg) if self.cfg.is_ssl else None\n\n        assert runner is not None\n        server = runner.server\n        assert server is not None\n        for sock in self.sockets:\n            site = web.SockSite(\n                runner,\n                sock,\n                ssl_context=ctx,\n            )\n            await site.start()\n\n        # If our parent changed then we shut down.\n        pid = os.getpid()\n        try:\n            while self.alive:  # type: ignore[has-type]\n                self.notify()\n\n                cnt = server.requests_count\n                if self.max_requests and cnt > self.max_requests:\n                    self.alive = False\n                    self.log.info(\"Max requests, shutting down: %s\", self)\n\n                elif pid == os.getpid() and self.ppid != os.getppid():\n                    self.alive = False\n                    self.log.info(\"Parent changed, shutting down: %s\", self)\n                else:\n                    await self._wait_next_notify()\n        except Exception:\n            pass\n\n        await runner.cleanup()\n\n    def _wait_next_notify(self) -> \"asyncio.Future[bool]\":\n        self._notify_waiter_done()\n\n        loop = self.loop\n        assert loop is not None\n        self._notify_waiter = waiter = loop.create_future()\n        self.loop.call_later(1.0, self._notify_waiter_done, waiter)\n\n        return waiter\n\n    def _notify_waiter_done(\n        self, waiter: Optional[\"asyncio.Future[bool]\"] = None\n    ) -> None:\n        if waiter is None:\n            waiter = self._notify_waiter\n        if waiter is not None:\n            set_result(waiter, True)\n\n        if waiter is self._notify_waiter:\n            self._notify_waiter = None\n\n    def init_signals(self) -> None:\n        # Set up signals through the event loop API.\n\n        self.loop.add_signal_handler(\n            signal.SIGQUIT, self.handle_quit, signal.SIGQUIT, None\n        )\n\n        self.loop.add_signal_handler(\n            signal.SIGTERM, self.handle_exit, signal.SIGTERM, None\n        )\n\n        self.loop.add_signal_handler(\n            signal.SIGINT, self.handle_quit, signal.SIGINT, None\n        )\n\n        self.loop.add_signal_handler(\n            signal.SIGWINCH, self.handle_winch, signal.SIGWINCH, None\n        )\n\n        self.loop.add_signal_handler(\n            signal.SIGUSR1, self.handle_usr1, signal.SIGUSR1, None\n        )\n\n        self.loop.add_signal_handler(\n            signal.SIGABRT, self.handle_abort, signal.SIGABRT, None\n        )\n\n        # Don't let SIGTERM and SIGUSR1 disturb active requests\n        # by interrupting system calls\n        signal.siginterrupt(signal.SIGTERM, False)\n        signal.siginterrupt(signal.SIGUSR1, False)\n        # Reset signals so Gunicorn doesn't swallow subprocess return codes\n        # See: https://github.com/aio-libs/aiohttp/issues/6130\n\n    def handle_quit(self, sig: int, frame: FrameType | None) -> None:\n        self.alive = False\n\n        # worker_int callback\n        self.cfg.worker_int(self)\n\n        # wakeup closing process\n        self._notify_waiter_done()\n\n    def handle_abort(self, sig: int, frame: FrameType | None) -> None:\n        self.alive = False\n        self.exit_code = 1\n        self.cfg.worker_abort(self)\n        sys.exit(1)\n\n    @staticmethod\n    def _create_ssl_context(cfg: Any) -> \"SSLContext\":\n        \"\"\"Creates SSLContext instance for usage in asyncio.create_server.\n\n        See ssl.SSLSocket.__init__ for more details.\n        \"\"\"\n        if ssl is None:  # pragma: no cover\n            raise RuntimeError(\"SSL is not supported.\")\n\n        ctx = ssl.SSLContext(cfg.ssl_version)\n        ctx.load_cert_chain(cfg.certfile, cfg.keyfile)\n        ctx.verify_mode = cfg.cert_reqs\n        if cfg.ca_certs:\n            ctx.load_verify_locations(cfg.ca_certs)\n        if cfg.ciphers:\n            ctx.set_ciphers(cfg.ciphers)\n        return ctx\n\n    def _get_valid_log_format(self, source_format: str) -> str:\n        if source_format == self.DEFAULT_GUNICORN_LOG_FORMAT:\n            return self.DEFAULT_AIOHTTP_LOG_FORMAT\n        elif re.search(r\"%\\([^\\)]+\\)\", source_format):\n            raise ValueError(\n                \"Gunicorn's style options in form of `%(name)s` are not \"\n                \"supported for the log formatting. Please use aiohttp's \"\n                \"format specification to configure access log formatting: \"\n                \"http://docs.aiohttp.org/en/stable/logging.html\"\n                \"#format-specification\"\n            )\n        else:\n            return source_format\n\n\nclass GunicornUVLoopWebWorker(GunicornWebWorker):\n    def init_process(self) -> None:\n        import uvloop\n\n        # Setup uvloop policy, so that every\n        # asyncio.get_event_loop() will create an instance\n        # of uvloop event loop.\n        asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())\n\n        super().init_process()\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    = -W --keep-going -n\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# User-friendly check for sphinx-build\nifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)\n$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)\nendif\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  latexpdfja to make LaTeX files and run them through platex/dvipdfmx\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  xml        to make Docutils-native XML files\"\n\t@echo \"  pseudoxml  to make pseudoxml-XML files for display purposes\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\nclean:\n\trm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/aiohttp.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/aiohttp.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/aiohttp\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/aiohttp\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\nlatexpdfja:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through platex and dvipdfmx...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n\nxml:\n\t$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml\n\t@echo\n\t@echo \"Build finished. The XML files are in $(BUILDDIR)/xml.\"\n\npseudoxml:\n\t$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml\n\t@echo\n\t@echo \"Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml.\"\n\nspelling:\n\t$(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)/spelling\n\t@echo\n\t@echo \"Build finished.\"\n"
  },
  {
    "path": "docs/_static/css/logo-adjustments.css",
    "content": ".sphinxsidebarwrapper>h1.logo {\n  display: none;\n}\n\n.sphinxsidebarwrapper>p.logo>a>img.logo {\n  width: 65%;\n}\n"
  },
  {
    "path": "docs/abc.rst",
    "content": ".. module:: aiohttp.abc\n\n.. _aiohttp-abc:\n\nAbstract Base Classes\n=====================\n\nAbstract routing\n----------------\n\naiohttp has abstract classes for managing web interfaces.\n\nThe most part of :mod:`aiohttp.web` is not intended to be inherited\nbut few of them are.\n\naiohttp.web is built on top of few concepts: *application*, *router*,\n*request* and *response*.\n\n*router* is a *pluggable* part: a library user may build a *router*\nfrom scratch, all other parts should work with new router seamlessly.\n\n:class:`aiohttp.abc.AbstractRouter` has the only mandatory method:\n:meth:`aiohttp.abc.AbstractRouter.resolve` coroutine. It must return an\n:class:`aiohttp.abc.AbstractMatchInfo` instance.\n\nIf the requested URL handler is found\n:meth:`aiohttp.abc.AbstractMatchInfo.handler` is a :term:`web-handler` for\nrequested URL and :attr:`aiohttp.abc.AbstractMatchInfo.http_exception` is ``None``.\n\nOtherwise :attr:`aiohttp.abc.AbstractMatchInfo.http_exception` is an instance of\n:exc:`~aiohttp.web.HTTPException` like *404: NotFound* or *405: Method\nNot Allowed*. :meth:`aiohttp.abc.AbstractMatchInfo.handler` raises\n:attr:`~aiohttp.abc.AbstractMatchInfo.http_exception` on call.\n\n\n.. class:: AbstractRouter\n\n   Abstract router, :class:`aiohttp.web.Application` accepts it as\n   *router* parameter and returns as\n   :attr:`aiohttp.web.Application.router`.\n\n   .. method:: resolve(request)\n      :async:\n\n      Performs URL resolving. It's an abstract method, should be\n      overridden in *router* implementation.\n\n      :param request: :class:`aiohttp.web.Request` instance for\n                      resolving, the request has\n                      :attr:`aiohttp.web.Request.match_info` equals to\n                      ``None`` at resolving stage.\n\n      :return: :class:`aiohttp.abc.AbstractMatchInfo` instance.\n\n\n.. class:: AbstractMatchInfo\n\n   Abstract *match info*, returned by :meth:`aiohttp.abc.AbstractRouter.resolve` call.\n\n   .. attribute:: http_exception\n\n      :exc:`aiohttp.web.HTTPException` if no match was found, ``None``\n      otherwise.\n\n   .. method:: handler(request)\n      :async:\n\n      Abstract method performing :term:`web-handler` processing.\n\n      :param request: :class:`aiohttp.web.Request` instance for\n                      resolving, the request has\n                      :attr:`aiohttp.web.Request.match_info` equals to\n                      ``None`` at resolving stage.\n      :return: :class:`aiohttp.web.StreamResponse` or descendants.\n\n      :raise: :class:`aiohttp.web.HTTPException` on error\n\n   .. method:: expect_handler(request)\n      :async:\n\n      Abstract method for handling *100-continue* processing.\n\n\nAbstract Class Based Views\n--------------------------\n\nFor *class based view* support aiohttp has abstract\n:class:`AbstractView` class which is *awaitable* (may be uses like\n``await Cls()`` or ``yield from Cls()`` and has a *request* as an\nattribute.\n\n.. class:: AbstractView\n\n   An abstract class, base for all *class based views* implementations.\n\n   Methods ``__iter__`` and ``__await__`` should be overridden.\n\n   .. attribute:: request\n\n      :class:`aiohttp.web.Request` instance for performing the request.\n\n\nAbstract Cookie Jar\n-------------------\n\n.. class:: AbstractCookieJar\n\n   The cookie jar instance is available as :attr:`aiohttp.ClientSession.cookie_jar`.\n\n   The jar contains :class:`~http.cookies.Morsel` items for storing\n   internal cookie data.\n\n   API provides a count of saved cookies::\n\n       len(session.cookie_jar)\n\n   These cookies may be iterated over::\n\n       for cookie in session.cookie_jar:\n           print(cookie.key)\n           print(cookie[\"domain\"])\n\n   An abstract class for cookie storage. Implements\n   :class:`collections.abc.Iterable` and\n   :class:`collections.abc.Sized`.\n\n   .. method:: update_cookies(cookies, response_url=None)\n\n      Update cookies returned by server in ``Set-Cookie`` header.\n\n      :param cookies: a :class:`collections.abc.Mapping`\n         (e.g. :class:`dict`, :class:`~http.cookies.SimpleCookie`) or\n         *iterable* of *pairs* with cookies returned by server's\n         response.\n\n      :param str response_url: URL of response, ``None`` for *shared\n         cookies*.  Regular cookies are coupled with server's URL and\n         are sent only to this server, shared ones are sent in every\n         client request.\n\n   .. method:: filter_cookies(request_url)\n\n      Return jar's cookies acceptable for URL and available in\n      ``Cookie`` header for sending client requests for given URL.\n\n      :param str response_url: request's URL for which cookies are asked.\n\n      :return: :class:`http.cookies.SimpleCookie` with filtered\n         cookies for given URL.\n\n   .. method:: clear(predicate=None)\n\n      Removes all cookies from the jar if the predicate is ``None``. Otherwise remove only those :class:`~http.cookies.Morsel` that ``predicate(morsel)`` returns ``True``.\n\n      :param predicate: callable that gets :class:`~http.cookies.Morsel` as a parameter and returns ``True`` if this :class:`~http.cookies.Morsel` must be deleted from the jar.\n\n          .. versionadded:: 3.8\n\n   .. method:: clear_domain(domain)\n\n      Remove all cookies from the jar that belongs to the specified domain or its subdomains.\n\n      :param str domain: domain for which cookies must be deleted from the jar.\n\n      .. versionadded:: 3.8\n\nAbstract Access Logger\n-------------------------------\n\n.. class:: AbstractAccessLogger\n\n   An abstract class, base for all :class:`aiohttp.web.RequestHandler`\n   ``access_logger`` implementations\n\n   Method ``log`` should be overridden.\n\n   .. method:: log(request, response, time)\n\n      :param request: :class:`aiohttp.web.Request` object.\n\n      :param response: :class:`aiohttp.web.Response` object.\n\n      :param float time: Time taken to serve the request.\n\n   .. attribute:: enabled\n\n        Return True if logger is enabled.\n\n        Override this property if logging is disabled to avoid the\n        overhead of calculating details to feed the logger.\n\n        This property may be omitted if logging is always enabled.\n\n\nAbstract Resolver\n-------------------------------\n\n.. class:: AbstractResolver\n\n   An abstract class, base for all resolver implementations.\n\n   Method ``resolve`` should be overridden.\n\n   .. method:: resolve(host, port, family)\n\n      Resolve host name to IP address.\n\n      :param str host: host name to resolve.\n\n      :param int port: port number.\n\n      :param int family: socket family.\n\n      :return: list of :class:`aiohttp.abc.ResolveResult` instances.\n\n   .. method:: close()\n\n      Release resolver.\n\n.. class:: ResolveResult\n\n   Result of host name resolution.\n\n   .. attribute:: hostname\n\n      The host name that was provided.\n\n   .. attribute:: host\n\n      The IP address that was resolved.\n\n   .. attribute:: port\n\n      The port that was resolved.\n\n   .. attribute:: family\n\n      The address family that was resolved.\n\n   .. attribute:: proto\n\n      The protocol that was resolved.\n\n   .. attribute:: flags\n\n      The flags that were resolved.\n"
  },
  {
    "path": "docs/built_with.rst",
    "content": ".. _aiohttp-built-with:\n\nBuilt with aiohttp\n==================\n\naiohttp is used to build useful libraries built on top of it,\nand there's a page dedicated to list them: :ref:`aiohttp-3rd-party`.\n\nThere are also projects that leverage the power of aiohttp to\nprovide end-user tools, like command lines or software with\nfull user interfaces.\n\nThis page aims to list those projects. If you are using aiohttp\nin your software and if it's playing a central role, you\ncan add it here in this list.\n\nYou can also add a **Built with aiohttp** link somewhere in your\nproject, pointing to `<https://github.com/aio-libs/aiohttp>`_.\n\n\n* `Pulp <https://pulpproject.org>`_ Platform for managing repositories\n  of software packages and making them available to consumers.\n* `repo-peek <https://github.com/rahulunair/repo-peek>`_ CLI tool to open a remote repo locally quickly.\n* `Molotov <http://molotov.readthedocs.io>`_ Load testing tool.\n* `Arsenic <https://github.com/hde/arsenic>`_ Async WebDriver.\n* `Home Assistant <https://home-assistant.io>`_ Home Automation Platform.\n* `Backend.AI <https://backend.ai>`_ Code execution API service.\n* `doh-proxy <https://github.com/facebookexperimental/doh-proxy>`_ DNS Over HTTPS Proxy.\n* `Mariner <https://gitlab.com/radek-sprta/mariner>`_ Command-line torrent searcher.\n* `DEEPaaS API <https://github.com/indigo-dc/DEEPaaS>`_ REST API for Machine learning, Deep learning and artificial intelligence applications.\n* `BentoML <https://github.com/bentoml/BentoML>`_ Machine Learning model serving framework\n* `salted <https://github.com/RuedigerVoigt/salted>`_ fast link check library (for HTML, Markdown, LaTeX, ...) with CLI\n* `Unofficial Tabdeal API <https://github.com/MohsenHNSJ/unofficial_tabdeal_api>`_ A package to communicate with the *Tabdeal* trading platform.\n"
  },
  {
    "path": "docs/changes.rst",
    "content": ".. _aiohttp_changes:\n\n=========\nChangelog\n=========\n\n.. only:: not is_release\n\n   To be included in v\\ |release| (if present)\n   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n   .. towncrier-draft-entries:: |release| [UNRELEASED DRAFT]\n\n   Released versions\n   ^^^^^^^^^^^^^^^^^\n\n.. include:: ../CHANGES.rst\n   :start-after: .. towncrier release notes start\n"
  },
  {
    "path": "docs/client.rst",
    "content": ".. _aiohttp-client:\n\nClient\n======\n\n.. currentmodule:: aiohttp\n\nThe page contains all information about aiohttp Client API:\n\n\n.. toctree::\n   :name: client\n   :maxdepth: 3\n\n   Quickstart <client_quickstart>\n   Advanced Usage <client_advanced>\n   Client Middleware Cookbook <client_middleware_cookbook>\n   Reference <client_reference>\n   Tracing Reference <tracing_reference>\n   The aiohttp Request Lifecycle <http_request_lifecycle>\n"
  },
  {
    "path": "docs/client_advanced.rst",
    "content": ".. currentmodule:: aiohttp\n\n.. _aiohttp-client-advanced:\n\nAdvanced Client Usage\n=====================\n\n.. _aiohttp-client-session:\n\nClient Session\n--------------\n\n:class:`ClientSession` is the heart and the main entry point for all\nclient API operations.\n\nCreate the session first, use the instance for performing HTTP\nrequests and initiating WebSocket connections.\n\nThe session contains a cookie storage and connection pool, thus\ncookies and connections are shared between HTTP requests sent by the\nsame session.\n\nCustom Request Headers\n----------------------\n\nIf you need to add HTTP headers to a request, pass them in a\n:class:`dict` to the *headers* parameter.\n\nFor example, if you want to specify the content-type directly::\n\n    url = 'http://example.com/image'\n    payload = b'GIF89a\\x01\\x00\\x01\\x00\\x00\\xff\\x00,\\x00\\x00'\n              b'\\x00\\x00\\x01\\x00\\x01\\x00\\x00\\x02\\x00;'\n    headers = {'content-type': 'image/gif'}\n\n    await session.post(url,\n                       data=payload,\n                       headers=headers)\n\nYou also can set default headers for all session requests::\n\n    headers={\"Authorization\": \"Basic bG9naW46cGFzcw==\"}\n    async with aiohttp.ClientSession(headers=headers) as session:\n        async with session.get(\"http://httpbin.org/headers\") as r:\n            json_body = await r.json()\n            assert json_body['headers']['Authorization'] == \\\n                'Basic bG9naW46cGFzcw=='\n\nTypical use case is sending JSON body. You can specify content type\ndirectly as shown above, but it is more convenient to use special keyword\n``json``::\n\n    await session.post(url, json={'example': 'text'})\n\nFor ``text/plain``::\n\n    await session.post(url, data='Привет, Мир!')\n\nAuthentication\n--------------\n\nInstead of setting the ``Authorization`` header directly,\n:class:`ClientSession` and individual request methods provide an ``auth``\nargument. An instance of :class:`BasicAuth` can be passed in like this::\n\n    auth = BasicAuth(login=\"...\", password=\"...\")\n    async with ClientSession(auth=auth) as session:\n        ...\n\nFor HTTP digest authentication, use the :class:`DigestAuthMiddleware` client middleware::\n\n    from aiohttp import ClientSession, DigestAuthMiddleware\n\n    # Create the middleware with your credentials\n    digest_auth = DigestAuthMiddleware(login=\"user\", password=\"password\")\n\n    # Pass it to the ClientSession as a tuple\n    async with ClientSession(middlewares=(digest_auth,)) as session:\n        # The middleware will automatically handle auth challenges\n        async with session.get(\"https://example.com/protected\") as resp:\n            print(await resp.text())\n\nThe :class:`DigestAuthMiddleware` implements HTTP Digest Authentication according to RFC 7616,\nproviding a more secure alternative to Basic Authentication. It supports all\nstandard hash algorithms including MD5, SHA, SHA-256, SHA-512 and their session\nvariants, as well as both 'auth' and 'auth-int' quality of protection (qop) options.\nThe middleware automatically handles the authentication flow by intercepting 401 responses\nand retrying with proper credentials.\n\nNote that if the request is redirected and the redirect URL contains\ncredentials, those credentials will supersede any previously set credentials.\nIn other words, if ``http://user@example.com`` redirects to\n``http://other_user@example.com``, the second request will be authenticated\nas ``other_user``. Providing both the ``auth`` parameter and authentication in\nthe *initial* URL will result in a :exc:`ValueError`.\n\nFor other authentication flows, the ``Authorization`` header can be set\ndirectly::\n\n    headers = {\"Authorization\": \"Bearer eyJh...0M30\"}\n    async with ClientSession(headers=headers) as session:\n        ...\n\nThe authentication header for a session may be updated as and when required.\nFor example::\n\n    session.headers[\"Authorization\"] = \"Bearer eyJh...1OH0\"\n\nNote that a *copy* of the headers dictionary is set as an attribute when\ncreating a :class:`ClientSession` instance (as a :class:`multidict.CIMultiDict`\nobject). Updating the original dictionary does not have any effect.\n\nIn cases where the authentication header value expires periodically, an\n:mod:`asyncio` task may be used to update the session's default headers in the\nbackground.\n\n.. note::\n\n   The ``Authorization`` header will be removed if you get redirected\n   to a different host or protocol, except the case when  HTTP → HTTPS\n   redirect is performed on the same host.\n\n.. versionchanged:: 4.0\n\n   Started keeping the ``Authorization`` header during HTTP → HTTPS\n   redirects when the host remains the same.\n\n.. _aiohttp-client-middleware:\n\nClient Middleware\n-----------------\n\nThe client supports middleware to intercept requests and responses. This can be\nuseful for authentication, logging, request/response modification, retries etc.\n\nFor more examples and common middleware patterns, see the :ref:`aiohttp-client-middleware-cookbook`.\n\nCreating a middleware\n^^^^^^^^^^^^^^^^^^^^^\n\nTo create a middleware, define an async function (or callable class) that accepts a request object\nand a handler function, and returns a response. Middlewares must follow the\n:type:`ClientMiddlewareType` signature::\n\n    async def auth_middleware(req: ClientRequest, handler: ClientHandlerType) -> ClientResponse:\n        req.headers[\"Authorization\"] = get_auth_header()\n        return await handler(req)\n\nUsing Middlewares\n^^^^^^^^^^^^^^^^^\n\nYou can apply middlewares to a client session or to individual requests::\n\n    # Apply to all requests in a session\n    async with ClientSession(middlewares=(my_middleware,)) as session:\n        resp = await session.get(\"http://example.com\")\n\n    # Apply to a specific request\n    async with ClientSession() as session:\n        resp = await session.get(\"http://example.com\", middlewares=(my_middleware,))\n\nMiddleware Chaining\n^^^^^^^^^^^^^^^^^^^\n\nMultiple middlewares are applied in the order they are listed::\n\n    # Middlewares are applied in order: logging -> auth -> request\n    async with ClientSession(middlewares=(logging_middleware, auth_middleware)) as session:\n        async with session.get(\"http://example.com\") as resp:\n            ...\n\nA key aspect to understand about the middleware sequence is that the execution flow follows this pattern:\n\n1. The first middleware in the list is called first and executes its code before calling the handler\n2. The handler is the next middleware in the chain (or the request handler if there are no more middlewares)\n3. When the handler returns a response, execution continues from the last middleware right after the handler call\n4. This creates a nested \"onion-like\" pattern for execution\n\nFor example, with ``middlewares=(middleware1, middleware2)``, the execution order would be:\n\n1. Enter ``middleware1`` (pre-request code)\n2. Enter ``middleware2`` (pre-request code)\n3. Execute the actual request handler\n4. Exit ``middleware2`` (post-response code)\n5. Exit ``middleware1`` (post-response code)\n\nThis flat structure means that a middleware is applied on each retry attempt inside the client's retry loop,\nnot just once before all retries. This allows middleware to modify requests freshly on each retry attempt.\n\nFor example, if we had a retry middleware and a logging middleware, and we want every retried request to be\nlogged separately, then we'd need to specify ``middlewares=(retry_mw, logging_mw)``. If we reversed the order\nto ``middlewares=(logging_mw, retry_mw)``, then we'd only log once regardless of how many retries are done.\n\n.. note::\n\n   Client middleware is a powerful feature but should be used judiciously.\n   Each middleware adds overhead to request processing. For simple use cases\n   like adding static headers, you can often use request parameters\n   (e.g., ``headers``) or session configuration instead.\n\nCustom Cookies\n--------------\n\nTo send your own cookies to the server, you can use the *cookies*\nparameter of :class:`ClientSession` constructor::\n\n    url = 'http://httpbin.org/cookies'\n    cookies = {'cookies_are': 'working'}\n    async with ClientSession(cookies=cookies) as session:\n        async with session.get(url) as resp:\n            assert await resp.json() == {\n               \"cookies\": {\"cookies_are\": \"working\"}}\n\n.. note::\n   ``httpbin.org/cookies`` endpoint returns request cookies\n   in JSON-encoded body.\n   To access session cookies see :attr:`ClientSession.cookie_jar`.\n\n:class:`~aiohttp.ClientSession` may be used for sharing cookies\nbetween multiple requests::\n\n    async with aiohttp.ClientSession() as session:\n        async with session.get(\n            \"http://httpbin.org/cookies/set?my_cookie=my_value\",\n            allow_redirects=False\n        ) as resp:\n            assert resp.cookies[\"my_cookie\"].value == \"my_value\"\n        async with session.get(\"http://httpbin.org/cookies\") as r:\n            json_body = await r.json()\n            assert json_body[\"cookies\"][\"my_cookie\"] == \"my_value\"\n\nResponse Headers and Cookies\n----------------------------\n\nWe can view the server's response :attr:`ClientResponse.headers` using\na :class:`~multidict.CIMultiDictProxy`::\n\n    assert resp.headers == {\n        'ACCESS-CONTROL-ALLOW-ORIGIN': '*',\n        'CONTENT-TYPE': 'application/json',\n        'DATE': 'Tue, 15 Jul 2014 16:49:51 GMT',\n        'SERVER': 'gunicorn/18.0',\n        'CONTENT-LENGTH': '331',\n        'CONNECTION': 'keep-alive'}\n\nThe dictionary is special, though: it's made just for HTTP\nheaders. According to `RFC 7230\n<http://tools.ietf.org/html/rfc7230#section-3.2>`_, HTTP Header names\nare case-insensitive. It also supports multiple values for the same\nkey as HTTP protocol does.\n\nSo, we can access the headers using any capitalization we want::\n\n    assert resp.headers['Content-Type'] == 'application/json'\n\n    assert resp.headers.get('content-type') == 'application/json'\n\nAll headers are converted from binary data using UTF-8 with\n``surrogateescape`` option. That works fine on most cases but\nsometimes unconverted data is needed if a server uses nonstandard\nencoding. While these headers are malformed from :rfc:`7230`\nperspective they may be retrieved by using\n:attr:`ClientResponse.raw_headers` property::\n\n    assert resp.raw_headers == (\n        (b'SERVER', b'nginx'),\n        (b'DATE', b'Sat, 09 Jan 2016 20:28:40 GMT'),\n        (b'CONTENT-TYPE', b'text/html; charset=utf-8'),\n        (b'CONTENT-LENGTH', b'12150'),\n        (b'CONNECTION', b'keep-alive'))\n\n\nIf a response contains some *HTTP Cookies*, you can quickly access them::\n\n    url = 'http://example.com/some/cookie/setting/url'\n    async with session.get(url) as resp:\n        print(resp.cookies['example_cookie_name'])\n\n.. note::\n\n   Response cookies contain only values, that were in ``Set-Cookie`` headers\n   of the **last** request in redirection chain. To gather cookies between all\n   redirection requests please use :ref:`aiohttp.ClientSession\n   <aiohttp-client-session>` object.\n\n\nRedirection History\n-------------------\n\nIf a request was redirected, it is possible to view previous responses using\nthe :attr:`~ClientResponse.history` attribute::\n\n    resp = await session.get('http://example.com/some/redirect/')\n    assert resp.status == 200\n    assert resp.url == URL('http://example.com/some/other/url/')\n    assert len(resp.history) == 1\n    assert resp.history[0].status == 301\n    assert resp.history[0].url == URL(\n        'http://example.com/some/redirect/')\n\nIf no redirects occurred or ``allow_redirects`` is set to ``False``,\nhistory will be an empty sequence.\n\n\nCookie Jar\n----------\n\n.. _aiohttp-client-cookie-safety:\n\nCookie Safety\n^^^^^^^^^^^^^\n\nBy default :class:`~aiohttp.ClientSession` uses strict version of\n:class:`aiohttp.CookieJar`. :rfc:`2109` explicitly forbids cookie\naccepting from URLs with IP address instead of DNS name\n(e.g. ``http://127.0.0.1:80/cookie``).\n\nIt's good but sometimes for testing we need to enable support for such\ncookies. It should be done by passing ``unsafe=True`` to\n:class:`aiohttp.CookieJar` constructor::\n\n\n   jar = aiohttp.CookieJar(unsafe=True)\n   session = aiohttp.ClientSession(cookie_jar=jar)\n\n\n.. _aiohttp-client-cookie-quoting-routine:\n\nCookie Quoting Routine\n^^^^^^^^^^^^^^^^^^^^^^\n\nThe client uses the :class:`~aiohttp.SimpleCookie` quoting routines\nconform to the :rfc:`2109`, which in turn references the character definitions\nfrom :rfc:`2068`. They provide a two-way quoting algorithm where any non-text\ncharacter is translated into a 4 character sequence: a forward-slash\nfollowed by the three-digit octal equivalent of the character.\nAny ``\\`` or ``\"`` is quoted with a preceding ``\\`` slash.\nBecause of the way browsers really handle cookies (as opposed to what the RFC\nsays) we also encode ``,`` and ``;``.\n\nSome backend systems does not support quoted cookies. You can skip this\nquotation routine by passing ``quote_cookie=False`` to the\n:class:`~aiohttp.CookieJar` constructor::\n\n   jar = aiohttp.CookieJar(quote_cookie=False)\n   session = aiohttp.ClientSession(cookie_jar=jar)\n\n\n.. _aiohttp-client-dummy-cookie-jar:\n\nDummy Cookie Jar\n^^^^^^^^^^^^^^^^\n\nSometimes cookie processing is not desirable. For this purpose it's\npossible to pass :class:`aiohttp.DummyCookieJar` instance into client\nsession::\n\n   jar = aiohttp.DummyCookieJar()\n   session = aiohttp.ClientSession(cookie_jar=jar)\n\n\nUploading pre-compressed data\n-----------------------------\n\nTo upload data that is already compressed before passing it to\naiohttp, call the request function with the used compression algorithm\nname (usually ``deflate`` or ``gzip``) as the value of the\n``Content-Encoding`` header::\n\n    async def my_coroutine(session, headers, my_data):\n        data = zlib.compress(my_data)\n        headers = {'Content-Encoding': 'deflate'}\n        async with session.post('http://httpbin.org/post',\n                                data=data,\n                                headers=headers)\n            pass\n\nDisabling content type validation for JSON responses\n----------------------------------------------------\n\nThe standard explicitly restricts JSON ``Content-Type`` HTTP header to\n``application/json`` or any extended form, e.g. ``application/vnd.custom-type+json``.\nUnfortunately, some servers send a wrong type, like ``text/html``.\n\nThis can be worked around in two ways:\n\n1. Pass the expected type explicitly (in this case checking will be strict, without the extended form support,\n   so ``custom/xxx+type`` won't be accepted):\n\n   ``await resp.json(content_type='custom/type')``.\n2. Disable the check entirely:\n\n   ``await resp.json(content_type=None)``.\n\n.. _aiohttp-client-tracing:\n\nClient Tracing\n--------------\n\nThe execution flow of a specific request can be followed attaching\nlisteners coroutines to the signals provided by the\n:class:`TraceConfig` instance, this instance will be used as a\nparameter for the :class:`ClientSession` constructor having as a\nresult a client that triggers the different signals supported by the\n:class:`TraceConfig`. By default any instance of\n:class:`ClientSession` class comes with the signals ability\ndisabled. The following snippet shows how the start and the end\nsignals of a request flow can be followed::\n\n    async def on_request_start(\n            session, trace_config_ctx, params):\n        print(\"Starting request\")\n\n    async def on_request_end(session, trace_config_ctx, params):\n        print(\"Ending request\")\n\n    trace_config = aiohttp.TraceConfig()\n    trace_config.on_request_start.append(on_request_start)\n    trace_config.on_request_end.append(on_request_end)\n    async with aiohttp.ClientSession(\n            trace_configs=[trace_config]) as client:\n        client.get('http://example.com/some/redirect/')\n\nThe ``trace_configs`` is a list that can contain instances of\n:class:`TraceConfig` class that allow run the signals handlers coming\nfrom different :class:`TraceConfig` instances.  The following example\nshows how two different :class:`TraceConfig` that have a different\nnature are installed to perform their job in each signal handle::\n\n    from mylib.traceconfig import AuditRequest\n    from mylib.traceconfig import XRay\n\n    async with aiohttp.ClientSession(\n            trace_configs=[AuditRequest(), XRay()]) as client:\n        client.get('http://example.com/some/redirect/')\n\n\nAll signals take as a parameters first, the :class:`ClientSession`\ninstance used by the specific request related to that signals and\nsecond, a :class:`~types.SimpleNamespace` instance called\n``trace_config_ctx``. The ``trace_config_ctx`` object can be used to\nshare the state through to the different signals that belong to the\nsame request and to the same :class:`TraceConfig` class, perhaps::\n\n    async def on_request_start(\n            session, trace_config_ctx, params):\n        trace_config_ctx.start = asyncio.get_event_loop().time()\n\n    async def on_request_end(session, trace_config_ctx, params):\n        elapsed = asyncio.get_event_loop().time() - trace_config_ctx.start\n        print(\"Request took {}\".format(elapsed))\n\n\nThe ``trace_config_ctx`` param is by default a\n:class:`~types.SimpleNamespace` that is initialized at the beginning of the\nrequest flow. However, the factory used to create this object can be\noverwritten using the ``trace_config_ctx_factory`` constructor param of\nthe :class:`TraceConfig` class.\n\nThe ``trace_request_ctx`` param can given at the beginning of the\nrequest execution, accepted by all of the HTTP verbs,  and will be\npassed as a keyword argument for the ``trace_config_ctx_factory``\nfactory. This param is useful to pass data that is only available at\nrequest time, perhaps::\n\n    async def on_request_start(\n            session, trace_config_ctx, params):\n        print(trace_config_ctx.trace_request_ctx)\n\n\n    session.get('http://example.com/some/redirect/',\n                trace_request_ctx={'foo': 'bar'})\n\n\n.. seealso:: :ref:`aiohttp-client-tracing-reference` section for\n             more information about the different signals supported.\n\nConnectors\n----------\n\nTo tweak or change *transport* layer of requests you can pass a custom\n*connector* to :class:`~aiohttp.ClientSession` and family. For example::\n\n    conn = aiohttp.TCPConnector()\n    session = aiohttp.ClientSession(connector=conn)\n\n.. note::\n\n   By default *session* object takes the ownership of the connector, among\n   other things closing the connections once the *session* is closed. If\n   you are keen on share the same *connector* through different *session*\n   instances you must give the  *connector_owner* parameter as **False**\n   for each *session* instance.\n\n.. seealso:: :ref:`aiohttp-client-reference-connectors` section for\n             more information about different connector types and\n             configuration options.\n\n\nLimiting connection pool size\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTo limit amount of simultaneously opened connections you can pass *limit*\nparameter to *connector*::\n\n    conn = aiohttp.TCPConnector(limit=30)\n\nThe example limits total amount of parallel connections to `30`.\n\nThe default is `100`.\n\nIf you explicitly want not to have limits, pass `0`. For example::\n\n    conn = aiohttp.TCPConnector(limit=0)\n\nTo limit amount of simultaneously opened connection to the same\nendpoint (``(host, port, is_ssl)`` triple) you can pass *limit_per_host*\nparameter to *connector*::\n\n    conn = aiohttp.TCPConnector(limit_per_host=30)\n\nThe example limits amount of parallel connections to the same to `30`.\n\nThe default is `0` (no limit on per host bases).\n\nTuning the DNS cache\n^^^^^^^^^^^^^^^^^^^^\n\nBy default :class:`~aiohttp.TCPConnector` comes with the DNS cache\ntable enabled, and resolutions will be cached by default for `10` seconds.\nThis behavior can be changed either to change of the TTL for a resolution,\nas can be seen in the following example::\n\n    conn = aiohttp.TCPConnector(ttl_dns_cache=300)\n\nor disabling the use of the DNS cache table, meaning that all requests will\nend up making a DNS resolution, as the following example shows::\n\n    conn = aiohttp.TCPConnector(use_dns_cache=False)\n\n\nResolving using custom nameservers\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIn order to specify the nameservers to when resolving the hostnames,\n:term:`aiodns` is required::\n\n    from aiohttp.resolver import AsyncResolver\n\n    resolver = AsyncResolver(nameservers=[\"8.8.8.8\", \"8.8.4.4\"])\n    conn = aiohttp.TCPConnector(resolver=resolver)\n\n\nUnix domain sockets\n^^^^^^^^^^^^^^^^^^^\n\nIf your HTTP server uses UNIX domain sockets you can use\n:class:`~aiohttp.UnixConnector`::\n\n  conn = aiohttp.UnixConnector(path='/path/to/socket')\n  session = aiohttp.ClientSession(connector=conn)\n\n\nCustom socket creation\n^^^^^^^^^^^^^^^^^^^^^^\n\nIf the default socket is insufficient for your use case, pass an optional\n``socket_factory`` to the :class:`~aiohttp.TCPConnector`, which implements\n:class:`SocketFactoryType`. This will be used to create all sockets for the\nlifetime of the class object. For example, we may want to change the\nconditions under which we consider a connection dead. The following would\nmake all sockets respect 9*7200 = 18 hours::\n\n  import socket\n\n  def socket_factory(addr_info):\n      family, type_, proto, _, _ = addr_info\n      sock = socket.socket(family=family, type=type_, proto=proto)\n      sock.setsockopt(socket.SOL_SOCKET,  socket.SO_KEEPALIVE,  True)\n      sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE,  7200)\n      sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT,      9)\n      return sock\n\n  conn = aiohttp.TCPConnector(socket_factory=socket_factory)\n\n``socket_factory`` may also be used for binding to the specific network\ninterface on supported platforms::\n\n  def socket_factory(addr_info):\n      family, type_, proto, _, _ = addr_info\n      sock = socket.socket(family=family, type=type_, proto=proto)\n      sock.setsockopt(\n          socket.SOL_SOCKET, socket.SO_BINDTODEVICE, b'eth0'\n      )\n      return sock\n\n  conn = aiohttp.TCPConnector(socket_factory=socket_factory)\n\n\nNamed pipes in Windows\n^^^^^^^^^^^^^^^^^^^^^^\n\nIf your HTTP server uses Named pipes you can use\n:class:`~aiohttp.NamedPipeConnector`::\n\n  conn = aiohttp.NamedPipeConnector(path=r'\\\\.\\pipe\\<name-of-pipe>')\n  session = aiohttp.ClientSession(connector=conn)\n\nIt will only work with the ProactorEventLoop\n\nSSL control for TCP sockets\n---------------------------\n\nBy default *aiohttp* uses strict checks for HTTPS protocol. Certification\nchecks can be relaxed by setting *ssl* to ``False``::\n\n  r = await session.get('https://example.com', ssl=False)\n\nIf you need to setup custom ssl parameters (use own certification\nfiles for example) you can create a :class:`ssl.SSLContext` instance and\npass it into the :meth:`ClientSession.request` methods or set it for the\nentire session with ``ClientSession(connector=TCPConnector(ssl=ssl_context))``.\n\nThere are explicit errors when ssl verification fails\n\n:class:`aiohttp.ClientConnectorSSLError`::\n\n  try:\n      await session.get('https://expired.badssl.com/')\n  except aiohttp.ClientConnectorSSLError as e:\n      assert isinstance(e, ssl.SSLError)\n\n:class:`aiohttp.ClientConnectorCertificateError`::\n\n  try:\n      await session.get('https://wrong.host.badssl.com/')\n  except aiohttp.ClientConnectorCertificateError as e:\n      assert isinstance(e, ssl.CertificateError)\n\nIf you need to skip both ssl related errors\n\n:class:`aiohttp.ClientSSLError`::\n\n  try:\n      await session.get('https://expired.badssl.com/')\n  except aiohttp.ClientSSLError as e:\n      assert isinstance(e, ssl.SSLError)\n\n  try:\n      await session.get('https://wrong.host.badssl.com/')\n  except aiohttp.ClientSSLError as e:\n      assert isinstance(e, ssl.CertificateError)\n\nExample: Use certifi\n^^^^^^^^^^^^^^^^^^^^\n\nBy default, Python uses the system CA certificates. In rare cases, these may not be\ninstalled or Python is unable to find them, resulting in a error like\n`ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate`\n\nOne way to work around this problem is to use the `certifi` package::\n\n  ssl_context = ssl.create_default_context(cafile=certifi.where())\n  async with ClientSession(connector=TCPConnector(ssl=ssl_context)) as sess:\n      ...\n\nExample: Use self-signed certificate\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf you need to verify *self-signed* certificates, you need to add a call to\n:meth:`ssl.SSLContext.load_cert_chain` with the key pair::\n\n  ssl_context = ssl.create_default_context()\n  ssl_context.load_cert_chain(\"/path/to/client/public/device.pem\",\n                              \"/path/to/client/private/device.key\")\n  async with sess.get(\"https://example.com\", ssl=ssl_context) as resp:\n      ...\n\nExample: Verify certificate fingerprint\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nYou may also verify certificates via *SHA256* fingerprint::\n\n  # Attempt to connect to https://www.python.org\n  # with a pin to a bogus certificate:\n  bad_fp = b'0'*64\n  exc = None\n  try:\n      r = await session.get('https://www.python.org',\n                            ssl=aiohttp.Fingerprint(bad_fp))\n  except aiohttp.FingerprintMismatch as e:\n      exc = e\n  assert exc is not None\n  assert exc.expected == bad_fp\n\n  # www.python.org cert's actual fingerprint\n  assert exc.got == b'...'\n\nNote that this is the fingerprint of the DER-encoded certificate.\nIf you have the certificate in PEM format, you can convert it to\nDER with e.g::\n\n   openssl x509 -in crt.pem -inform PEM -outform DER > crt.der\n\n.. note::\n\n   Tip: to convert from a hexadecimal digest to a binary byte-string,\n   you can use :func:`binascii.unhexlify`.\n\n   *ssl* parameter could be passed\n   to :class:`TCPConnector` as default, the value from\n   :meth:`ClientSession.get` and others override default.\n\n.. _aiohttp-client-proxy-support:\n\nProxy support\n-------------\n\naiohttp supports plain HTTP proxies and HTTP proxies that can be\nupgraded to HTTPS via the HTTP CONNECT method. aiohttp has a limited\nsupport for proxies that must be connected to via ``https://`` — see\nthe info box below for more details.\nTo connect, use the *proxy* parameter::\n\n   async with aiohttp.ClientSession() as session:\n       async with session.get(\"http://python.org\",\n                              proxy=\"http://proxy.com\") as resp:\n           print(resp.status)\n\nIt also supports proxy authorization::\n\n   async with aiohttp.ClientSession() as session:\n       proxy_auth = aiohttp.BasicAuth('user', 'pass')\n       async with session.get(\"http://python.org\",\n                              proxy=\"http://proxy.com\",\n                              proxy_auth=proxy_auth) as resp:\n           print(resp.status)\n\nAuthentication credentials can be passed in proxy URL::\n\n   session.get(\"http://python.org\",\n               proxy=\"http://user:pass@some.proxy.com\")\n\nAnd you may set default proxy::\n\n   proxy_auth = aiohttp.BasicAuth('user', 'pass')\n   async with aiohttp.ClientSession(proxy=\"http://proxy.com\", proxy_auth=proxy_auth) as session:\n       async with session.get(\"http://python.org\") as resp:\n           print(resp.status)\n\nContrary to the ``requests`` library, it won't read environment\nvariables by default. But you can do so by passing\n``trust_env=True`` into :class:`aiohttp.ClientSession`\nconstructor.::\n\n   async with aiohttp.ClientSession(trust_env=True) as session:\n       async with session.get(\"http://python.org\") as resp:\n           print(resp.status)\n\n.. note::\n    aiohttp uses :func:`urllib.request.getproxies`\n    for reading the proxy configuration (e.g. from the *HTTP_PROXY* etc. environment variables) and applies them for the *HTTP*, *HTTPS*, *WS* and *WSS* schemes.\n\n    Hosts defined in ``no_proxy`` will bypass the proxy.\n\nProxy credentials are given from ``~/.netrc`` file if present (see\n:class:`aiohttp.ClientSession` for more details).\n\n.. attention::\n\n   As of now (Python 3.10), support for TLS in TLS is disabled for the transports that\n   :py:mod:`asyncio` uses. If the further release of Python (say v3.11)\n   toggles one attribute, it'll *just work™*.\n\n   aiohttp v3.8 and higher is ready for this to happen and has code in\n   place supports TLS-in-TLS, hence sending HTTPS requests over HTTPS\n   proxy tunnels.\n\n   ⚠️ For as long as your Python runtime doesn't declare the support for\n   TLS-in-TLS, please don't file bugs with aiohttp but rather try to\n   help the CPython upstream enable this feature. Meanwhile, if you\n   *really* need this to work, there's a patch that may help you make\n   it happen, include it into your app's code base:\n   https://github.com/aio-libs/aiohttp/discussions/6044#discussioncomment-1432443.\n\n.. important::\n\n   When supplying a custom :py:class:`ssl.SSLContext` instance, bear in\n   mind that it will be used not only to establish a TLS session with\n   the HTTPS endpoint you're hitting but also to establish a TLS tunnel\n   to the HTTPS proxy. To avoid surprises, make sure to set up the trust\n   chain that would recognize TLS certificates used by both the endpoint\n   and the proxy.\n\n.. _aiohttp-persistent-session:\n\nPersistent session\n------------------\n\nEven though creating a session on demand seems like a tempting idea, we\nadvise against it. :class:`aiohttp.ClientSession` maintains a\nconnection pool. Contained connections can be reused if necessary to gain some\nperformance improvements. If you plan on reusing the session, a.k.a. creating\n**persistent session**, you can use either :ref:`aiohttp-web-signals` or\n:ref:`aiohttp-web-cleanup-ctx`. If possible we advise using :ref:`aiohttp-web-cleanup-ctx`,\nas it results in more compact code::\n\n    session = aiohttp.web.AppKey(\"session\", aiohttp.ClientSession)\n\n    @contextlib.asynccontextmanager\n    async def persistent_session(app):\n       app[persistent_session] = session = aiohttp.ClientSession()\n       yield\n       await session.close()\n\n    async def my_request_handler(request):\n       sess = request.app[session]\n       async with sess.get(\"http://python.org\") as resp:\n           print(resp.status)\n\n    app.cleanup_ctx.append(persistent_session)\n\n\nThis approach can be successfully used to define numerous sessions given certain\nrequirements. It benefits from having a single location where :class:`aiohttp.ClientSession`\ninstances are created and where artifacts such as :class:`aiohttp.BaseConnector`\ncan be safely shared between sessions if needed.\n\nIn the end all you have to do is to close all sessions after the `yield` statement::\n\n    async def multiple_sessions(app):\n       app[persistent_session_1] = session_1 = aiohttp.ClientSession()\n       app[persistent_session_2] = session_2 = aiohttp.ClientSession()\n       app[persistent_session_3] = session_3 = aiohttp.ClientSession()\n\n       yield\n\n       await asyncio.gather(\n           session_1.close(),\n           session_2.close(),\n           session_3.close(),\n       )\n\nGraceful Shutdown\n-----------------\n\nWhen :class:`ClientSession` closes at the end of an ``async with``\nblock (or through a direct :meth:`ClientSession.close` call), the\nunderlying connection remains open due to asyncio internal details. In\npractice, the underlying connection will close after a short\nwhile. However, if the event loop is stopped before the underlying\nconnection is closed, a ``ResourceWarning: unclosed transport``\nwarning is emitted (when warnings are enabled).\n\nTo avoid this situation, a small delay must be added before closing\nthe event loop to allow any open underlying connections to close.\n\nFor a :class:`ClientSession` without SSL, a simple zero-sleep (``await\nasyncio.sleep(0)``) will suffice::\n\n    async def read_website():\n        async with aiohttp.ClientSession() as session:\n            async with session.get('http://example.org/') as resp:\n                await resp.read()\n        # Zero-sleep to allow underlying connections to close\n        await asyncio.sleep(0)\n\nFor a :class:`ClientSession` with SSL, the application must wait a\nshort duration before closing::\n\n    ...\n    # Wait 250 ms for the underlying SSL connections to close\n    await asyncio.sleep(0.250)\n\nNote that the appropriate amount of time to wait will vary from\napplication to application.\n\nAll of this will eventually become obsolete when the asyncio internals\nare changed so that aiohttp itself can wait on the underlying\nconnection to close. Please follow issue `#1925\n<https://github.com/aio-libs/aiohttp/issues/1925>`_ for the progress\non this.\n\nHTTP Pipelining\n---------------\n\naiohttp does not support HTTP/HTTPS pipelining.\n\n\nCharacter Set Detection\n-----------------------\n\nIf you encounter a :exc:`UnicodeDecodeError` when using :meth:`ClientResponse.text`\nthis may be because the response does not include the charset needed\nto decode the body.\n\nIf you know the correct encoding for a request, you can simply specify\nthe encoding as a parameter (e.g. ``resp.text(\"windows-1252\")``).\n\nAlternatively, :class:`ClientSession` accepts a ``fallback_charset_resolver`` parameter which\ncan be used to introduce charset guessing functionality. When a charset is not found\nin the Content-Type header, this function will be called to get the charset encoding. For\nexample, this can be used with the ``chardetng_py`` library.::\n\n    from chardetng_py import detect\n\n    def charset_resolver(resp: ClientResponse, body: bytes) -> str:\n        tld = resp.url.host.rsplit(\".\", maxsplit=1)[-1]\n        return detect(body, allow_utf8=True, tld=tld.encode())\n\n    ClientSession(fallback_charset_resolver=charset_resolver)\n\nOr, if ``chardetng_py`` doesn't work for you, then ``charset-normalizer`` is another option::\n\n    from charset_normalizer import detect\n\n    ClientSession(fallback_charset_resolver=lambda r, b: detect(b)[\"encoding\"] or \"utf-8\")\n"
  },
  {
    "path": "docs/client_middleware_cookbook.rst",
    "content": ".. currentmodule:: aiohttp\n\n.. _aiohttp-client-middleware-cookbook:\n\nClient Middleware Cookbook\n==========================\n\nThis cookbook provides examples of how client middlewares can be used for common use cases.\n\nSimple Retry Middleware\n-----------------------\n\nIt's very easy to create middlewares that can retry a connection on a given condition:\n\n.. literalinclude:: code/client_middleware_cookbook.py\n   :pyobject: retry_middleware\n\n.. warning::\n\n    It is recommended to ensure loops are bounded (e.g. using a ``for`` loop) to avoid\n    creating an infinite loop.\n\nLogging to an external service\n------------------------------\n\nIf we needed to log our requests via an API call to an external server or similar, we could\ncreate a simple middleware like this:\n\n.. literalinclude:: code/client_middleware_cookbook.py\n   :pyobject: api_logging_middleware\n\n.. warning::\n\n    Using the same session from within a middleware can cause infinite recursion if\n    that request gets processed again by the middleware.\n\n    To avoid such recursion a middleware should typically make requests with\n    ``middlewares=()`` or else contain some condition to stop the request triggering\n    the same logic when it is processed again by the middleware (e.g by whitelisting\n    the API domain of the request).\n\nToken Refresh Middleware\n------------------------\n\nIf you need to refresh access tokens to continue accessing an API, this is also a good\ncandidate for a middleware. For example, you could check for a 401 response, then\nrefresh the token and retry:\n\n.. literalinclude:: code/client_middleware_cookbook.py\n   :pyobject: TokenRefresh401Middleware\n\nIf you have an expiry time for the token, you could refresh at the expiry time, to avoid the\nfailed request:\n\n.. literalinclude:: code/client_middleware_cookbook.py\n   :pyobject: TokenRefreshExpiryMiddleware\n\nOr you could even refresh preemptively in a background task to avoid any API delays. This is probably more\nefficient to implement without a middleware:\n\n.. literalinclude:: code/client_middleware_cookbook.py\n   :pyobject: token_refresh_preemptively_example\n   :lines: 2-\n   :dedent:\n\nOr combine the above approaches to create a more robust solution.\n\n.. note::\n\n    These can also be adjusted to handle proxy auth by modifying\n    :attr:`ClientRequest.proxy_headers`.\n\nServer-side Request Forgery Protection\n--------------------------------------\n\nTo provide protection against server-side request forgery, we could blacklist any internal\nIPs or domains. We could create a middleware that rejects requests made to a blacklist:\n\n.. literalinclude:: code/client_middleware_cookbook.py\n   :pyobject: ssrf_middleware\n\n.. warning::\n\n   The above example is simplified for demonstration purposes. A production-ready\n   implementation should also check IPv6 addresses (``::1``), private IP ranges,\n   link-local addresses, and other internal hostnames. Consider using a well-tested\n   library for SSRF protection in production environments.\n\nIf you know that your services correctly reject requests with an incorrect `Host` header, then\nthat may provide sufficient protection. Otherwise, we still have a concern with an attacker's\nown domain resolving to a blacklisted IP. To provide complete protection, we can also\ncreate a custom resolver:\n\n.. literalinclude:: code/client_middleware_cookbook.py\n   :pyobject: SSRFConnector\n\nUsing both of these together in a session should provide full SSRF protection.\n\n\nBest Practices\n--------------\n\n.. important::\n\n   **Request-level middlewares replace session middlewares**: When you pass ``middlewares``\n   to ``request()`` or its convenience methods (``get()``, ``post()``, etc.), it completely\n   replaces the session-level middlewares, rather than extending them. This differs from\n   other parameters like ``headers``, which are merged.\n\n   .. code-block:: python\n\n      session = ClientSession(middlewares=[middleware_session])\n\n      # Session middleware is used\n      await session.get(\"http://example.com\")\n\n      # Session middleware is NOT used, only request middleware\n      await session.get(\"http://example.com\", middlewares=[middleware_request])\n\n      # To use both, explicitly pass both\n      await session.get(\n          \"http://example.com\",\n          middlewares=[middleware_session, middleware_request]\n      )\n\n1. **Keep middleware focused**: Each middleware should have a single responsibility.\n\n2. **Order matters**: Middlewares execute in the order they're listed. Place logging first,\n   authentication before retry, etc.\n\n3. **Avoid infinite recursion**: When making HTTP requests inside middleware, either:\n\n   - Use ``middlewares=()`` to disable middleware for internal requests\n   - Check the request URL/host to skip middleware for specific endpoints\n   - Use a separate session for internal requests\n\n4. **Handle errors gracefully**: Don't let middleware errors break the request flow unless\n   absolutely necessary.\n\n5. **Use bounded loops**: Always use ``for`` loops with a maximum iteration count instead\n   of unbounded ``while`` loops to prevent infinite retries.\n\n6. **Consider performance**: Each middleware adds overhead. For simple cases like adding\n   static headers, consider using session or request parameters instead.\n\n7. **Test thoroughly**: Middleware can affect all requests in subtle ways. Test edge cases\n   like network errors, timeouts, and concurrent requests.\n\nSee Also\n--------\n\n- :ref:`aiohttp-client-middleware` - Core middleware documentation\n- :ref:`aiohttp-client-advanced` - Advanced client usage\n- :class:`DigestAuthMiddleware` - Built-in digest authentication middleware\n"
  },
  {
    "path": "docs/client_quickstart.rst",
    "content": ".. currentmodule:: aiohttp\n\n.. _aiohttp-client-quickstart:\n\n===================\n Client Quickstart\n===================\n\nEager to get started? This page gives a good introduction in how to\nget started with aiohttp client API.\n\nFirst, make sure that aiohttp is :ref:`installed\n<aiohttp-installation>` and *up-to-date*\n\nLet's get started with some simple examples.\n\n\n\nMake a Request\n==============\n\nBegin by importing the aiohttp module, and asyncio::\n\n    import aiohttp\n    import asyncio\n\nNow, let's try to get a web-page. For example let's query\n``http://httpbin.org/get``::\n\n    async def main():\n        async with aiohttp.ClientSession() as session:\n            async with session.get('http://httpbin.org/get') as resp:\n                print(resp.status)\n                print(await resp.text())\n\n    asyncio.run(main())\n\nNow, we have a :class:`ClientSession` called ``session`` and a\n:class:`ClientResponse` object called ``resp``. We can get all the\ninformation we need from the response.  The mandatory parameter of\n:meth:`ClientSession.get` coroutine is an HTTP *url* (:class:`str` or\nclass:`yarl.URL` instance).\n\nIn order to make an HTTP POST request use :meth:`ClientSession.post` coroutine::\n\n    session.post('http://httpbin.org/post', data=b'data')\n\nOther HTTP methods are available as well::\n\n    session.put('http://httpbin.org/put', data=b'data')\n    session.delete('http://httpbin.org/delete')\n    session.head('http://httpbin.org/get')\n    session.options('http://httpbin.org/get')\n    session.patch('http://httpbin.org/patch', data=b'data')\n\nTo make several requests to the same site more simple, the parameter ``base_url``\nof :class:`ClientSession` constructor can be used. For example to request different\nendpoints of ``http://httpbin.org`` can be used the following code::\n\n    async with aiohttp.ClientSession('http://httpbin.org') as session:\n        async with session.get('/get'):\n            pass\n        async with session.post('/post', data=b'data'):\n            pass\n        async with session.put('/put', data=b'data'):\n            pass\n\n.. note::\n\n   Don't create a session per request. Most likely you need a session\n   per application which performs all requests together.\n\n   More complex cases may require a session per site, e.g. one for\n   Github and other one for Facebook APIs. Anyway making a session for\n   every request is a **very bad** idea.\n\n   A session contains a connection pool inside. Connection reusage and\n   keep-alive (both are on by default) may speed up total performance.\n\n   You may find more information about creating persistent sessions\n   in :ref:`aiohttp-persistent-session`.\n\nA session context manager usage is not mandatory\nbut ``await session.close()`` method\nshould be called in this case, e.g.::\n\n    session = aiohttp.ClientSession()\n    async with session.get('...'):\n        # ...\n    await session.close()\n\n\nPassing Parameters In URLs\n==========================\n\nYou often want to send some sort of data in the URL's query string. If\nyou were constructing the URL by hand, this data would be given as key/value\npairs in the URL after a question mark, e.g. ``httpbin.org/get?key=val``.\naiohttp allows you to provide these arguments as a :class:`dict`, using the\n``params`` keyword argument. As an example, if you wanted to pass\n``key1=value1`` and ``key2=value2`` to ``httpbin.org/get``, you would use the\nfollowing code::\n\n    params = {'key1': 'value1', 'key2': 'value2'}\n    async with session.get('http://httpbin.org/get',\n                           params=params) as resp:\n        expect = 'http://httpbin.org/get?key1=value1&key2=value2'\n        assert str(resp.url) == expect\n\nYou can see that the URL has been correctly encoded by printing the URL.\n\nFor sending data with multiple values for the same key\n:class:`~multidict.MultiDict` may be used; the library support nested lists\n(``{'key': ['value1', 'value2']}``) alternative as well.\n\nIt is also possible to pass a list of 2 item tuples as parameters, in\nthat case you can specify multiple values for each key::\n\n    params = [('key', 'value1'), ('key', 'value2')]\n    async with session.get('http://httpbin.org/get',\n                           params=params) as r:\n        expect = 'http://httpbin.org/get?key=value2&key=value1'\n        assert str(r.url) == expect\n\nYou can also pass :class:`str` content as param, but beware -- content\nis not encoded by library. Note that ``+`` is not encoded::\n\n    async with session.get('http://httpbin.org/get',\n                           params='key=value+1') as r:\n            assert str(r.url) == 'http://httpbin.org/get?key=value+1'\n\n.. note::\n\n   *aiohttp* internally performs URL canonicalization before sending request.\n\n   Canonicalization encodes *host* part by :term:`IDNA` codec and applies\n   :term:`requoting` to *path* and *query* parts.\n\n   For example ``URL('http://example.com/путь/%30?a=%31')`` is converted to\n   ``URL('http://example.com/%D0%BF%D1%83%D1%82%D1%8C/0?a=1')``.\n\n   Sometimes canonicalization is not desirable if server accepts exact\n   representation and does not requote URL itself.\n\n   To disable canonicalization use ``encoded=True`` parameter for URL construction::\n\n      await session.get(\n          URL('http://example.com/%30', encoded=True))\n\n.. warning::\n\n   Passing *params* overrides ``encoded=True``, never use both options.\n\nResponse Content and Status Code\n================================\n\nWe can read the content of the server's response and its status\ncode. Consider the GitHub time-line again::\n\n    async with session.get('https://api.github.com/events') as resp:\n        print(resp.status)\n        print(await resp.text())\n\nprints out something like::\n\n    200\n    '[{\"created_at\":\"2015-06-12T14:06:22Z\",\"public\":true,\"actor\":{...\n\n``aiohttp`` automatically decodes the content from the server. You can\nspecify custom encoding for the :meth:`~ClientResponse.text` method::\n\n    await resp.text(encoding='windows-1251')\n\n\nBinary Response Content\n=======================\n\nYou can also access the response body as bytes, for non-text requests::\n\n    print(await resp.read())\n\n::\n\n    b'[{\"created_at\":\"2015-06-12T14:06:22Z\",\"public\":true,\"actor\":{...\n\nThe ``gzip`` and ``deflate`` transfer-encodings are automatically\ndecoded for you.\n\nYou can enable ``brotli`` transfer-encodings support,\njust install `Brotli <https://pypi.org/project/Brotli/>`_\nor `brotlicffi <https://pypi.org/project/brotlicffi/>`_.\n\nYou can enable ``zstd`` transfer-encodings support,\ninstall `backports.zstd <https://pypi.org/project/backports.zstd/>`_.\nIf you are using Python >= 3.14, no dependency should be required.\n\nJSON Request\n============\n\nAny of session's request methods like :func:`request`,\n:meth:`ClientSession.get`, :meth:`ClientSession.post` etc. accept\n`json` parameter::\n\n  async with aiohttp.ClientSession() as session:\n      await session.post(url, json={'test': 'object'})\n\n\nBy default session uses python's standard :mod:`json` module for\nserialization.  But it is possible to use a different\n``serializer``. :class:`ClientSession` accepts ``json_serialize`` and\n``json_serialize_bytes`` parameters::\n\n  import orjson\n\n  async with aiohttp.ClientSession(\n          json_serialize_bytes=orjson.dumps) as session:\n      await session.post(url, json={'test': 'object'})\n\n.. note::\n\n   ``orjson`` library is faster than standard :mod:`json` and is actively\n   maintained. Since ``orjson.dumps`` returns :class:`bytes`, pass it via\n   the ``json_serialize_bytes`` parameter to avoid unnecessary\n   encoding/decoding overhead.\n\nJSON Response Content\n=====================\n\nThere's also a built-in JSON decoder, in case you're dealing with JSON data::\n\n    async with session.get('https://api.github.com/events') as resp:\n        print(await resp.json())\n\nIn case that JSON decoding fails, :meth:`~ClientResponse.json` will\nraise an exception. It is possible to specify custom encoding and\ndecoder functions for the :meth:`~ClientResponse.json` call.\n\n.. note::\n\n    The methods above reads the whole response body into memory. If you are\n    planning on reading lots of data, consider using the streaming response\n    method documented below.\n\n\nStreaming Response Content\n==========================\n\nWhile methods :meth:`~ClientResponse.read`,\n:meth:`~ClientResponse.json` and :meth:`~ClientResponse.text` are very\nconvenient you should use them carefully. All these methods load the\nwhole response in memory.  For example if you want to download several\ngigabyte sized files, these methods will load all the data in\nmemory. Instead you can use the :attr:`~ClientResponse.content`\nattribute. It is an instance of the :class:`aiohttp.StreamReader`\nclass. The ``gzip`` and ``deflate`` transfer-encodings are\nautomatically decoded for you::\n\n    async with session.get('https://api.github.com/events') as resp:\n        await resp.content.read(10)\n\nIn general, however, you should use a pattern like this to save what is being\nstreamed to a file::\n\n    with open(filename, 'wb') as fd:\n        async for chunk in resp.content.iter_chunked(chunk_size):\n            fd.write(chunk)\n\nIt is not possible to use :meth:`~ClientResponse.read`,\n:meth:`~ClientResponse.json` and :meth:`~ClientResponse.text` after\nexplicit reading from :attr:`~ClientResponse.content`.\n\nMore complicated POST requests\n==============================\n\nTypically, you want to send some form-encoded data -- much like an HTML form.\nTo do this, simply pass a dictionary to the *data* argument. Your\ndictionary of data will automatically be form-encoded when the request is made::\n\n    payload = {'key1': 'value1', 'key2': 'value2'}\n    async with session.post('http://httpbin.org/post',\n                            data=payload) as resp:\n        print(await resp.text())\n\n::\n\n    {\n      ...\n      \"form\": {\n        \"key2\": \"value2\",\n        \"key1\": \"value1\"\n      },\n      ...\n    }\n\nIf you want to send data that is not form-encoded you can do it by\npassing a :class:`bytes` instead of a :class:`dict`. This data will be\nposted directly and content-type set to 'application/octet-stream' by\ndefault::\n\n    async with session.post(url, data=b'\\x00Binary-data\\x00') as resp:\n        ...\n\nIf you want to send JSON data::\n\n    async with session.post(url, json={'example': 'test'}) as resp:\n        ...\n\nTo send text with appropriate content-type just use ``data`` argument::\n\n    async with session.post(url, data='Тест') as resp:\n        ...\n\nPOST a Multipart-Encoded File\n=============================\n\nTo upload Multipart-encoded files::\n\n    url = 'http://httpbin.org/post'\n    files = {'file': open('report.xls', 'rb')}\n\n    await session.post(url, data=files)\n\nYou can set the ``filename`` and ``content_type`` explicitly::\n\n    url = 'http://httpbin.org/post'\n    data = aiohttp.FormData()\n    data.add_field('file',\n                   open('report.xls', 'rb'),\n                   filename='report.xls',\n                   content_type='application/vnd.ms-excel')\n\n    await session.post(url, data=data)\n\nIf you pass a file object as data parameter, aiohttp will stream it to\nthe server automatically. Check :class:`~aiohttp.StreamReader`\nfor supported format information.\n\n.. seealso:: :ref:`aiohttp-multipart`\n\n\nStreaming uploads\n=================\n\n:mod:`aiohttp` supports multiple types of streaming uploads, which allows you to\nsend large files without reading them into memory.\n\nAs a simple case, simply provide a file-like object for your body::\n\n    with open(\"massive-body\", \"rb\") as f:\n       await session.post(\"https://httpbin.org/post\", data=f)\n\n\nOr you can provide an *asynchronous generator*, for example to generate\ndata on the fly::\n\n  async def data_generator():\n      for i in range(10):\n          yield f\"line {i}\\n\".encode()\n\n  async with session.post(\"https://httpbin.org/post\",\n                          data=data_generator()) as resp:\n      print(await resp.text())\n\n.. warning::\n\n   Async generators and other non-rewindable data sources\n   (such as :class:`~aiohttp.StreamReader`) cannot be replayed if a\n   redirect occurs (for example, HTTP 307 or 308). If the request body\n   has already been streamed, :mod:`aiohttp` raises\n   :class:`~aiohttp.ClientPayloadError`.\n\n   If your endpoint may redirect, either:\n\n   * Pass a seekable file-like object or :class:`bytes`.\n   * Disable redirects with ``allow_redirects=False`` and handle them manually.\n\n\nBecause the :attr:`~aiohttp.ClientResponse.content` attribute is a\n:class:`~aiohttp.StreamReader` (provides async iterator protocol), you\ncan chain get and post requests together::\n\n   resp = await session.get('http://python.org')\n   await session.post('http://httpbin.org/post',\n                      data=resp.content)\n\n.. _aiohttp-client-websockets:\n\n\nWebSockets\n==========\n\n:mod:`aiohttp` works with client websockets out-of-the-box.\n\nYou have to use the :meth:`aiohttp.ClientSession.ws_connect` coroutine\nfor client websocket connection. It accepts a *url* as a first\nparameter and returns :class:`ClientWebSocketResponse`, with that\nobject you can communicate with websocket server using response's\nmethods::\n\n   async with session.ws_connect('http://example.org/ws') as ws:\n       async for msg in ws:\n           if msg.type == aiohttp.WSMsgType.TEXT:\n               if msg.data == 'close cmd':\n                   await ws.close()\n                   break\n               else:\n                   await ws.send_str(msg.data + '/answer')\n           elif msg.type == aiohttp.WSMsgType.ERROR:\n               break\n\n\nYou **must** use the only websocket task for both reading (e.g. ``await\nws.receive()`` or ``async for msg in ws:``) and writing but may have\nmultiple writer tasks which can only send data asynchronously (by\n``await ws.send_str('data')`` for example).\n\n\n.. _aiohttp-client-timeouts:\n\nTimeouts\n========\n\nTimeout settings are stored in :class:`ClientTimeout` data structure.\n\nBy default *aiohttp* uses a *total* 300 seconds (5min) timeout, it means that the\nwhole operation should finish in 5 minutes. In order to allow time for DNS fallback,\nthe default ``sock_connect`` timeout is 30 seconds.\n\nThe value could be overridden by *timeout* parameter for the session (specified in seconds)::\n\n    timeout = aiohttp.ClientTimeout(total=60)\n    async with aiohttp.ClientSession(timeout=timeout) as session:\n        ...\n\nTimeout could be overridden for a request like :meth:`ClientSession.get`::\n\n    async with session.get(url, timeout=timeout) as resp:\n        ...\n\nSupported :class:`ClientTimeout` fields are:\n\n   ``total``\n\n      The maximal number of seconds for the whole operation including connection\n      establishment, request sending and response reading.\n\n   ``connect``\n\n      The maximal number of seconds for\n      connection establishment of a new connection or\n      for waiting for a free connection from a pool if pool connection\n      limits are exceeded.\n\n   ``sock_connect``\n\n      The maximal number of seconds for connecting to a peer for a new connection, not\n      given from a pool.\n\n   ``sock_read``\n\n      The maximal number of seconds allowed for period between reading a new\n      data portion from a peer.\n\n    ``ceil_threshold``\n\n      The threshold value to trigger ceiling of absolute timeout values.\n\nAll fields are floats, ``None`` or ``0`` disables a particular timeout check, see the\n:class:`ClientTimeout` reference for defaults and additional details.\n\nThus the default timeout is::\n\n   aiohttp.ClientTimeout(total=5*60, connect=None,\n                         sock_connect=None, sock_read=None, ceil_threshold=5)\n\n.. note::\n\n   *aiohttp* **ceils** timeout if the value is equal or greater than 5\n   seconds. The timeout expires at the next integer second greater than\n   ``current_time + timeout``.\n\n   The ceiling is done for the sake of optimization, when many concurrent tasks\n   are scheduled to wake-up at the almost same but different absolute times. It\n   leads to very many event loop wakeups, which kills performance.\n\n   The optimization shifts absolute wakeup times by scheduling them to exactly\n   the same time as other neighbors, the loop wakes up once-per-second for\n   timeout expiration.\n\n   Smaller timeouts are not rounded to help testing; in the real life network\n   timeouts usually greater than tens of seconds. However, the default threshold\n   value of 5 seconds can be configured using the ``ceil_threshold`` parameter.\n"
  },
  {
    "path": "docs/client_reference.rst",
    "content": ".. _aiohttp-client-reference:\n\nClient Reference\n================\n\n.. currentmodule:: aiohttp\n\n\nClient Session\n--------------\n\nClient session is the recommended interface for making HTTP requests.\n\nSession encapsulates a *connection pool* (*connector* instance) and\nsupports keepalives by default. Unless you are connecting to a large,\nunknown number of different servers over the lifetime of your\napplication, it is suggested you use a single session for the\nlifetime of your application to benefit from connection pooling.\n\nUsage example::\n\n     import aiohttp\n     import asyncio\n\n     async def fetch(client):\n         async with client.get('http://python.org') as resp:\n             assert resp.status == 200\n             return await resp.text()\n\n     async def main():\n         async with aiohttp.ClientSession() as client:\n             html = await fetch(client)\n             print(html)\n\n     asyncio.run(main())\n\n\nThe client session supports the context manager protocol for self closing.\n\n.. class:: ClientSession(base_url=None, *, \\\n                         connector=None, cookies=None, \\\n                         headers=None, skip_auto_headers=None, \\\n                         auth=None, json_serialize=json.dumps, \\\n                         request_class=ClientRequest, \\\n                         response_class=ClientResponse, \\\n                         ws_response_class=ClientWebSocketResponse, \\\n                         version=aiohttp.HttpVersion11, \\\n                         cookie_jar=None, \\\n                         connector_owner=True, \\\n                         raise_for_status=False, \\\n                         timeout=sentinel, \\\n                         auto_decompress=True, \\\n                         trust_env=False, \\\n                         requote_redirect_url=True, \\\n                         trace_configs=None, \\\n                         middlewares=(), \\\n                         read_bufsize=2**16, \\\n                         max_line_size=8190, \\\n                         max_field_size=8190, \\\n                         max_headers=128, \\\n                         fallback_charset_resolver=lambda r, b: \"utf-8\", \\\n                         ssl_shutdown_timeout=0)\n   :canonical: aiohttp.client.ClientSession\n\n   The class for creating client sessions and making requests.\n\n\n   :param base_url: Base part of the URL (optional)\n      If set, allows to join a base part to relative URLs in request calls.\n      If the URL has a path it must have a trailing ``/`` (as in\n      https://docs.aiohttp.org/en/stable/).\n\n      Note that URL joining follows :rfc:`3986`. This means, in the most\n      common case the request URLs should have no leading slash, e.g.::\n\n        session = ClientSession(base_url=\"http://example.com/foo/\")\n\n        await session.request(\"GET\", \"bar\")\n        # request for http://example.com/foo/bar\n\n        await session.request(\"GET\", \"/bar\")\n        # request for http://example.com/bar\n\n      .. versionadded:: 3.8\n\n      .. versionchanged:: 3.12\n\n         Added support for overriding the base URL with an absolute one in client sessions.\n\n   :param aiohttp.BaseConnector connector: BaseConnector\n      sub-class instance to support connection pooling.\n\n   :param dict cookies: Cookies to send with the request (optional)\n\n   :param headers: HTTP Headers to send with every request (optional).\n\n                   May be either *iterable of key-value pairs* or\n                   :class:`~collections.abc.Mapping`\n                   (e.g. :class:`dict`,\n                   :class:`~multidict.CIMultiDict`).\n\n   :param skip_auto_headers: set of headers for which autogeneration\n      should be skipped.\n\n      *aiohttp* autogenerates headers like ``User-Agent`` or\n      ``Content-Type`` if these headers are not explicitly\n      passed. Using ``skip_auto_headers`` parameter allows to skip\n      that generation. Note that ``Content-Length`` autogeneration can't\n      be skipped.\n\n      Iterable of :class:`str` or :class:`~multidict.istr` (optional)\n\n   :param aiohttp.BasicAuth auth: an object that represents HTTP Basic\n                                  Authorization (optional). It will be included\n                                  with any request. However, if the\n                                  ``_base_url`` parameter is set, the request\n                                  URL's origin must match the base URL's origin;\n                                  otherwise, the default auth will not be\n                                  included.\n\n   :param collections.abc.Callable json_serialize: Json *serializer* callable.\n\n      By default :func:`json.dumps` function.\n\n   :param aiohttp.ClientRequest request_class: Custom class to use for client requests.\n\n   :param ClientResponse response_class: Custom class to use for client responses.\n\n   :param ClientWebSocketResponse ws_response_class: Custom class to use for websocket responses.\n\n   :param version: supported HTTP version, ``HTTP 1.1`` by default.\n\n   :param cookie_jar: Cookie Jar, :class:`~aiohttp.abc.AbstractCookieJar` instance.\n\n      By default every session instance has own private cookie jar for\n      automatic cookies processing but user may redefine this behavior\n      by providing own jar implementation.\n\n      One example is not processing cookies at all when working in\n      proxy mode.\n\n      If no cookie processing is needed, a\n      :class:`aiohttp.DummyCookieJar` instance can be\n      provided.\n\n   :param bool connector_owner:\n\n      Close connector instance on session closing.\n\n      Setting the parameter to ``False`` allows to share\n      connection pool between sessions without sharing session state:\n      cookies etc.\n\n   :param bool raise_for_status:\n\n      Automatically call :meth:`ClientResponse.raise_for_status` for\n      each response, ``False`` by default.\n\n      This parameter can be overridden when making a request, e.g.::\n\n          client_session = aiohttp.ClientSession(raise_for_status=True)\n          resp = await client_session.get(url, raise_for_status=False)\n          async with resp:\n              assert resp.status == 200\n\n      Set the parameter to ``True`` if you need ``raise_for_status``\n      for most of cases but override ``raise_for_status`` for those\n      requests where you need to handle responses with status 400 or\n      higher.\n\n   :param timeout: a :class:`ClientTimeout` settings structure, 300 seconds (5min)\n        total timeout, 30 seconds socket connect timeout by default.\n\n      .. versionadded:: 3.3\n\n      .. versionchanged:: 3.10.9\n\n         The default value for the ``sock_connect`` timeout has been changed to 30 seconds.\n\n   :param bool auto_decompress: Automatically decompress response body (``True`` by default).\n\n      .. versionadded:: 2.3\n\n   :param bool trust_env: Trust environment settings for proxy configuration if the parameter\n      is ``True`` (``False`` by default). See :ref:`aiohttp-client-proxy-support` for\n      more information.\n\n      Get proxy credentials from ``~/.netrc`` file if present.\n\n      Get HTTP Basic Auth credentials from :file:`~/.netrc` file if present.\n\n      If :envvar:`NETRC` environment variable is set, read from file specified\n      there rather than from :file:`~/.netrc`.\n\n      .. seealso::\n\n         ``.netrc`` documentation: https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html\n\n      .. versionadded:: 2.3\n\n      .. versionchanged:: 3.0\n\n         Added support for ``~/.netrc`` file.\n\n      .. versionchanged:: 3.9\n\n         Added support for reading HTTP Basic Auth credentials from :file:`~/.netrc` file.\n\n   :param bool requote_redirect_url: Apply *URL requoting* for redirection URLs if\n                                     automatic redirection is enabled (``True`` by\n                                     default).\n\n      .. versionadded:: 3.5\n\n   :param trace_configs: A list of :class:`TraceConfig` instances used for client\n                         tracing.  ``None`` (default) is used for request tracing\n                         disabling.  See :ref:`aiohttp-client-tracing-reference` for\n                         more information.\n\n   :param middlewares: A sequence of middleware instances to apply to all session requests.\n                      Each middleware must match the :type:`ClientMiddlewareType` signature.\n                      ``()`` (empty tuple, default) is used when no middleware is needed.\n                      See :ref:`aiohttp-client-middleware` for more information.\n\n      .. versionadded:: 3.12\n\n   :param int read_bufsize: Size of the read buffer (:attr:`ClientResponse.content`).\n                            64 KiB by default.\n\n      .. versionadded:: 3.7\n\n   :param int max_line_size: Maximum allowed size of lines in responses.\n\n   :param int max_field_size: Maximum allowed size of header name and value combined in responses.\n\n   :param int max_headers: Maximum number of headers and trailers combined in responses.\n\n   :param Callable[[ClientResponse,bytes],str] fallback_charset_resolver:\n      A :term:`callable` that accepts a :class:`ClientResponse` and the\n      :class:`bytes` contents, and returns a :class:`str` which will be used as\n      the encoding parameter to :meth:`bytes.decode()`.\n\n      This function will be called when the charset is not known (e.g. not specified in the\n      Content-Type header). The default function simply defaults to ``utf-8``.\n\n      .. versionadded:: 3.8.6\n\n   :param float ssl_shutdown_timeout: **(DEPRECATED)** This parameter is deprecated\n      and will be removed in aiohttp 4.0. Grace period for SSL shutdown handshake on\n      TLS connections when the connector is closed (``0`` seconds by default).\n      By default (``0``), SSL connections are aborted immediately when the\n      connector is closed, without performing the shutdown handshake. During\n      normal operation, SSL connections use Python's default SSL shutdown\n      behavior. Setting this to a positive value (e.g., ``0.1``) will perform\n      a graceful shutdown when closing the connector, notifying the remote\n      peer which can help prevent \"connection reset\" errors at the cost of\n      additional cleanup time. This timeout is passed to the underlying\n      :class:`TCPConnector` when one is created automatically.\n      Note: On Python versions prior to 3.11, only a value of ``0`` is supported;\n      other values will trigger a warning.\n\n      .. versionadded:: 3.12.5\n\n      .. versionchanged:: 3.12.11\n         Changed default from ``0.1`` to ``0`` to abort SSL connections\n         immediately when the connector is closed. Added support for\n         ``ssl_shutdown_timeout=0`` on all Python versions. A :exc:`RuntimeWarning`\n         is issued when non-zero values are passed on Python < 3.11.\n\n      .. deprecated:: 3.12.11\n         This parameter is deprecated and will be removed in aiohttp 4.0.\n\n   .. attribute:: closed\n\n      ``True`` if the session has been closed, ``False`` otherwise.\n\n      A read-only property.\n\n   .. attribute:: connector\n\n      :class:`aiohttp.BaseConnector` derived instance used\n      for the session.\n\n      A read-only property.\n\n   .. attribute:: cookie_jar\n\n      The session cookies, :class:`~aiohttp.abc.AbstractCookieJar` instance.\n\n      Gives access to cookie jar's content and modifiers.\n\n      A read-only property.\n\n   .. attribute:: requote_redirect_url\n\n      aiohttp re quote's redirect urls by default, but some servers\n      require exact url from location header. To disable *re-quote* system\n      set :attr:`requote_redirect_url` attribute to ``False``.\n\n      .. versionadded:: 2.1\n\n      .. note:: This parameter affects all subsequent requests.\n\n      .. deprecated:: 3.5\n\n         The attribute modification is deprecated.\n\n   .. attribute:: loop\n\n      A loop instance used for session creation.\n\n      A read-only property.\n\n      .. deprecated:: 3.5\n\n   .. attribute:: timeout\n\n      Default client timeouts, :class:`ClientTimeout` instance.  The value can\n      be tuned by passing *timeout* parameter to :class:`ClientSession`\n      constructor.\n\n      .. versionadded:: 3.7\n\n   .. attribute:: headers\n\n      HTTP Headers that sent with every request\n\n      May be either *iterable of key-value pairs* or\n      :class:`~collections.abc.Mapping`\n      (e.g. :class:`dict`,\n      :class:`~multidict.CIMultiDict`).\n\n      .. versionadded:: 3.7\n\n   .. attribute:: skip_auto_headers\n\n      Set of headers for which autogeneration skipped.\n\n      :class:`frozenset` of :class:`str` or :class:`~multidict.istr` (optional)\n\n      .. versionadded:: 3.7\n\n   .. attribute:: auth\n\n      An object that represents HTTP Basic Authorization.\n\n      :class:`~aiohttp.BasicAuth` (optional)\n\n      .. versionadded:: 3.7\n\n   .. attribute:: json_serialize\n\n      Json serializer callable.\n\n      By default :func:`json.dumps` function.\n\n      .. versionadded:: 3.7\n\n   .. attribute:: connector_owner\n\n      Should connector be closed on session closing\n\n      :class:`bool` (optional)\n\n      .. versionadded:: 3.7\n\n   .. attribute:: raise_for_status\n\n      Should :meth:`ClientResponse.raise_for_status` be called for each response\n\n      Either :class:`bool` or :class:`collections.abc.Callable`\n\n      .. versionadded:: 3.7\n\n   .. attribute:: auto_decompress\n\n      Should the body response be automatically decompressed\n\n      :class:`bool` default is ``True``\n\n      .. versionadded:: 3.7\n\n   .. attribute:: trust_env\n\n      Trust environment settings for proxy configuration\n      or ~/.netrc file if present. See :ref:`aiohttp-client-proxy-support` for\n      more information.\n\n      :class:`bool` default is ``False``\n\n      .. versionadded:: 3.7\n\n   .. attribute:: trace_configs\n\n      A list of :class:`TraceConfig` instances used for client\n      tracing.  ``None`` (default) is used for request tracing\n      disabling.  See :ref:`aiohttp-client-tracing-reference` for more information.\n\n      .. versionadded:: 3.7\n\n   .. method:: request(method, url, *, params=None, data=None, json=None,\\\n                         cookies=None, headers=None, skip_auto_headers=None, \\\n                         auth=None, allow_redirects=True,\\\n                         max_redirects=10,\\\n                         compress=None, chunked=None, expect100=False, raise_for_status=None,\\\n                         read_until_eof=True, \\\n                         proxy=None, proxy_auth=None,\\\n                         timeout=sentinel, ssl=True, \\\n                         server_hostname=None, \\\n                         proxy_headers=None, \\\n                         trace_request_ctx=None, \\\n                         middlewares=None, \\\n                         read_bufsize=None, \\\n                         auto_decompress=None, \\\n                         max_line_size=None, \\\n                         max_field_size=None, \\\n                         max_headers=None)\n      :async:\n      :noindexentry:\n\n      Performs an asynchronous HTTP request. Returns a response object that\n      should be used as an async context manager.\n\n      :param str method: HTTP method\n\n      :param url: Request URL, :class:`~yarl.URL` or :class:`str` that will\n                  be encoded with :class:`~yarl.URL` (see :class:`~yarl.URL`\n                  to skip encoding).\n\n      :param params: Mapping, iterable of tuple of *key*/*value* pairs or\n                     string to be sent as parameters in the query\n                     string of the new request. Ignored for subsequent\n                     redirected requests (optional)\n\n                     Allowed values are:\n\n                     - :class:`collections.abc.Mapping` e.g. :class:`dict`,\n                       :class:`multidict.MultiDict` or\n                       :class:`multidict.MultiDictProxy`\n                     - :class:`collections.abc.Iterable` e.g. :class:`tuple` or\n                       :class:`list`\n                     - :class:`str` with preferably url-encoded content\n                       (**Warning:** content will not be encoded by *aiohttp*)\n\n      :param data: The data to send in the body of the request. This can be a\n                   :class:`FormData` object or anything that can be passed into\n                   :class:`FormData`, e.g. a dictionary, bytes, or file-like object.\n                   (optional)\n\n      :param json: Any json compatible python object\n                   (optional). *json* and *data* parameters could not\n                   be used at the same time.\n\n      :param dict cookies: HTTP Cookies to send with\n                           the request (optional)\n\n         Global session cookies and the explicitly set cookies will be merged\n         when sending the request.\n\n         .. versionadded:: 3.5\n\n      :param dict headers: HTTP Headers to send with\n                           the request (optional)\n\n      :param skip_auto_headers: set of headers for which autogeneration\n         should be skipped.\n\n         *aiohttp* autogenerates headers like ``User-Agent`` or\n         ``Content-Type`` if these headers are not explicitly\n         passed. Using ``skip_auto_headers`` parameter allows to skip\n         that generation.\n\n         Iterable of :class:`str` or :class:`~multidict.istr`\n         (optional)\n\n      :param aiohttp.BasicAuth auth: an object that represents HTTP\n                                     Basic Authorization (optional)\n\n      :param bool allow_redirects: Whether to process redirects or not.\n         When ``True``, redirects are followed (up to ``max_redirects`` times)\n         and logged into :attr:`ClientResponse.history` and ``trace_configs``.\n         When ``False``, the original response is returned.\n         ``True`` by default (optional).\n\n      :param int max_redirects: Maximum number of redirects to follow.\n         :exc:`TooManyRedirects` is raised if the number is exceeded.\n         Ignored when ``allow_redirects=False``.\n         ``10`` by default.\n\n      :param bool compress: Set to ``True`` if request has to be compressed\n         with deflate encoding. If `compress` can not be combined\n         with a *Content-Encoding* and *Content-Length* headers.\n         ``None`` by default (optional).\n\n      :param int chunked: Enable chunked transfer encoding.\n         It is up to the developer\n         to decide how to chunk data streams. If chunking is enabled, aiohttp\n         encodes the provided chunks in the \"Transfer-encoding: chunked\" format.\n         If *chunked* is set, then the *Transfer-encoding* and *content-length*\n         headers are disallowed. ``None`` by default (optional).\n\n      :param bool expect100: Expect 100-continue response from server.\n                             ``False`` by default (optional).\n\n      :param bool raise_for_status: Automatically call :meth:`ClientResponse.raise_for_status` for\n                                    response if set to ``True``.\n                                    If set to ``None`` value from ``ClientSession`` will be used.\n                                    ``None`` by default (optional).\n\n          .. versionadded:: 3.4\n\n      :param bool read_until_eof: Read response until EOF if response\n                                  does not have Content-Length header.\n                                  ``True`` by default (optional).\n\n      :param proxy: Proxy URL, :class:`str` or :class:`~yarl.URL` (optional)\n\n      :param aiohttp.BasicAuth proxy_auth: an object that represents proxy HTTP\n                                           Basic Authorization (optional)\n\n      :param int timeout: override the session's timeout.\n\n         .. versionchanged:: 3.3\n\n            The parameter is :class:`ClientTimeout` instance,\n            :class:`float` is still supported for sake of backward\n            compatibility.\n\n            If :class:`float` is passed it is a *total* timeout (in seconds).\n\n      :param ssl: SSL validation mode. ``True`` for default SSL check\n                  (:func:`ssl.create_default_context` is used),\n                  ``False`` for skip SSL certificate validation,\n                  :class:`aiohttp.Fingerprint` for fingerprint\n                  validation, :class:`ssl.SSLContext` for custom SSL\n                  certificate validation.\n\n                  Supersedes *verify_ssl*, *ssl_context* and\n                  *fingerprint* parameters.\n\n         .. versionadded:: 3.0\n\n      :param str server_hostname: Sets or overrides the host name that the\n         target server's certificate will be matched against.\n\n         See :py:meth:`asyncio.loop.create_connection` for more information.\n\n         .. versionadded:: 3.9\n\n      :param collections.abc.Mapping proxy_headers: HTTP headers to send to the proxy if the\n         parameter proxy has been provided.\n\n         .. versionadded:: 2.3\n\n      :param trace_request_ctx: Object used to give as a kw param for each new\n        :class:`TraceConfig` object instantiated,\n        used to give information to the\n        tracers that is only available at request time.\n\n         .. versionadded:: 3.0\n\n      :param middlewares: A sequence of middleware instances to apply to this request only.\n                         Each middleware must match the :type:`ClientMiddlewareType` signature.\n                         ``None`` by default which uses session middlewares.\n                         See :ref:`aiohttp-client-middleware` for more information.\n\n         .. versionadded:: 3.12\n\n      :param int read_bufsize: Size of the read buffer (:attr:`ClientResponse.content`).\n                              ``None`` by default,\n                              it means that the session global value is used.\n\n          .. versionadded:: 3.7\n\n      :param bool auto_decompress: Automatically decompress response body.\n         Overrides :attr:`ClientSession.auto_decompress`.\n         May be used to enable/disable auto decompression on a per-request basis.\n\n      :param int max_line_size: Maximum allowed size of lines in responses.\n\n      :param int max_field_size: Maximum allowed size of header name and value combined in responses.\n\n      :param int max_headers: Maximum number of headers and trailers combined in responses.\n\n      :return ClientResponse: a :class:`client response <ClientResponse>`\n         object.\n\n   .. method:: get(url, *, allow_redirects=True, **kwargs)\n      :async:\n\n      Perform a ``GET`` request. Returns an async context manager.\n\n      In order to modify inner\n      :meth:`request<aiohttp.ClientSession.request>`\n      parameters, provide `kwargs`.\n\n      :param url: Request URL, :class:`str` or :class:`~yarl.URL`\n\n      :param bool allow_redirects: Whether to process redirects or not.\n         When ``True``, redirects are followed and logged into\n         :attr:`ClientResponse.history`.\n         When ``False``, the original response is returned.\n         ``True`` by default (optional).\n\n      :return ClientResponse: a :class:`client response\n                              <ClientResponse>` object.\n\n   .. method:: post(url, *, data=None, **kwargs)\n      :async:\n\n      Perform a ``POST`` request. Returns an async context manager.\n\n      In order to modify inner\n      :meth:`request<aiohttp.ClientSession.request>`\n      parameters, provide `kwargs`.\n\n\n      :param url: Request URL, :class:`str` or :class:`~yarl.URL`\n\n      :param data: Data to send in the body of the request; see\n                   :meth:`request<aiohttp.ClientSession.request>`\n                   for details (optional)\n\n      :return ClientResponse: a :class:`client response\n                              <ClientResponse>` object.\n\n   .. method:: put(url, *, data=None, **kwargs)\n      :async:\n\n      Perform a ``PUT`` request. Returns an async context manager.\n\n      In order to modify inner\n      :meth:`request<aiohttp.ClientSession.request>`\n      parameters, provide `kwargs`.\n\n\n      :param url: Request URL, :class:`str` or :class:`~yarl.URL`\n\n      :param data: Data to send in the body of the request; see\n                   :meth:`request<aiohttp.ClientSession.request>`\n                   for details (optional)\n\n      :return ClientResponse: a :class:`client response\n                              <ClientResponse>` object.\n\n   .. method:: delete(url, **kwargs)\n      :async:\n\n      Perform a ``DELETE`` request. Returns an async context manager.\n\n      In order to modify inner\n      :meth:`request<aiohttp.ClientSession.request>`\n      parameters, provide `kwargs`.\n\n      :param url: Request URL, :class:`str` or :class:`~yarl.URL`\n\n      :return ClientResponse: a :class:`client response\n                              <ClientResponse>` object.\n\n   .. method:: head(url, *, allow_redirects=False, **kwargs)\n      :async:\n\n      Perform a ``HEAD`` request. Returns an async context manager.\n\n      In order to modify inner\n      :meth:`request<aiohttp.ClientSession.request>`\n      parameters, provide `kwargs`.\n\n      :param url: Request URL, :class:`str` or :class:`~yarl.URL`\n\n      :param bool allow_redirects: Whether to process redirects or not.\n         When ``True``, redirects are followed and logged into\n         :attr:`ClientResponse.history`.\n         When ``False``, the original response is returned.\n         ``False`` by default (optional).\n\n      :return ClientResponse: a :class:`client response\n                              <ClientResponse>` object.\n\n   .. method:: options(url, *, allow_redirects=True, **kwargs)\n      :async:\n\n      Perform an ``OPTIONS`` request. Returns an async context manager.\n\n      In order to modify inner\n      :meth:`request<aiohttp.ClientSession.request>`\n      parameters, provide `kwargs`.\n\n\n      :param url: Request URL, :class:`str` or :class:`~yarl.URL`\n\n      :param bool allow_redirects: Whether to process redirects or not.\n         When ``True``, redirects are followed and logged into\n         :attr:`ClientResponse.history`.\n         When ``False``, the original response is returned.\n         ``True`` by default (optional).\n\n      :return ClientResponse: a :class:`client response\n                              <ClientResponse>` object.\n\n   .. method:: patch(url, *, data=None, **kwargs)\n      :async:\n\n      Perform a ``PATCH`` request. Returns an async context manager.\n\n      In order to modify inner\n      :meth:`request<aiohttp.ClientSession.request>`\n      parameters, provide `kwargs`.\n\n      :param url: Request URL, :class:`str` or :class:`~yarl.URL`\n\n      :param data: Data to send in the body of the request; see\n                   :meth:`request<aiohttp.ClientSession.request>`\n                   for details (optional)\n\n      :return ClientResponse: a :class:`client response\n                              <ClientResponse>` object.\n\n   .. method:: ws_connect(url, *, method='GET', \\\n                            protocols=(), \\\n                            timeout=sentinel,\\\n                            auth=None,\\\n                            autoclose=True,\\\n                            autoping=True,\\\n                            heartbeat=None,\\\n                            origin=None, \\\n                            params=None, \\\n                            headers=None, \\\n                            proxy=None, proxy_auth=None, ssl=True, \\\n                            verify_ssl=None, fingerprint=None, \\\n                            ssl_context=None, proxy_headers=None, \\\n                            compress=0, max_msg_size=4194304, \\\n                            decode_text=True)\n      :async:\n\n      Create a websocket connection. Returns a\n      :class:`ClientWebSocketResponse` async context manager object.\n\n      :param url: Websocket server url, :class:`~yarl.URL` or :class:`str` that\n                  will be encoded with :class:`~yarl.URL` (see :class:`~yarl.URL`\n                  to skip encoding).\n\n      :param tuple protocols: Websocket protocols\n\n      :param timeout: a :class:`ClientWSTimeout` timeout for websocket.\n                      By default, the value\n                      `ClientWSTimeout(ws_receive=None, ws_close=10.0)` is used\n                      (``10.0`` seconds for the websocket to close).\n                      ``None`` means no timeout will be used.\n\n      :param aiohttp.BasicAuth auth: an object that represents HTTP\n                                     Basic Authorization (optional)\n\n      :param bool autoclose: Automatically close websocket connection on close\n                             message from server. If *autoclose* is False\n                             then close procedure has to be handled manually.\n                             ``True`` by default\n\n      :param bool autoping: automatically send *pong* on *ping*\n                            message from server. ``True`` by default\n\n      :param float heartbeat: Send *ping* message every *heartbeat*\n                              seconds and wait *pong* response, if\n                              *pong* response is not received then\n                              close connection. The timer is reset on any\n                              inbound data reception (coalesced per event loop\n                              iteration). (optional)\n\n      :param str origin: Origin header to send to server(optional)\n\n      :param params: Mapping, iterable of tuple of *key*/*value* pairs or\n                     string to be sent as parameters in the query\n                     string of the new request. Ignored for subsequent\n                     redirected requests (optional)\n\n                     Allowed values are:\n\n                     - :class:`collections.abc.Mapping` e.g. :class:`dict`,\n                       :class:`multidict.MultiDict` or\n                       :class:`multidict.MultiDictProxy`\n                     - :class:`collections.abc.Iterable` e.g. :class:`tuple` or\n                       :class:`list`\n                     - :class:`str` with preferably url-encoded content\n                       (**Warning:** content will not be encoded by *aiohttp*)\n\n      :param dict headers: HTTP Headers to send with\n                           the request (optional)\n\n      :param str proxy: Proxy URL, :class:`str` or :class:`~yarl.URL` (optional)\n\n      :param aiohttp.BasicAuth proxy_auth: an object that represents proxy HTTP\n                                           Basic Authorization (optional)\n\n      :param ssl: SSL validation mode. ``True`` for default SSL check\n                  (:func:`ssl.create_default_context` is used),\n                  ``False`` for skip SSL certificate validation,\n                  :class:`aiohttp.Fingerprint` for fingerprint\n                  validation, :class:`ssl.SSLContext` for custom SSL\n                  certificate validation.\n\n                  Supersedes *verify_ssl*, *ssl_context* and\n                  *fingerprint* parameters.\n\n         .. versionadded:: 3.0\n\n      :param bool verify_ssl: Perform SSL certificate validation for\n         *HTTPS* requests (enabled by default). May be disabled to\n         skip validation for sites with invalid certificates.\n\n         .. versionadded:: 2.3\n\n         .. deprecated:: 3.0\n\n            Use ``ssl=False``\n\n      :param bytes fingerprint: Pass the SHA256 digest of the expected\n         certificate in DER format to verify that the certificate the\n         server presents matches. Useful for `certificate pinning\n         <https://en.wikipedia.org/wiki/HTTP_Public_Key_Pinning>`_.\n\n         Note: use of MD5 or SHA1 digests is insecure and deprecated.\n\n         .. versionadded:: 2.3\n\n         .. deprecated:: 3.0\n\n            Use ``ssl=aiohttp.Fingerprint(digest)``\n\n      :param ssl.SSLContext ssl_context: ssl context used for processing\n         *HTTPS* requests (optional).\n\n         *ssl_context* may be used for configuring certification\n         authority channel, supported SSL options etc.\n\n         .. versionadded:: 2.3\n\n         .. deprecated:: 3.0\n\n            Use ``ssl=ssl_context``\n\n      :param dict proxy_headers: HTTP headers to send to the proxy if the\n         parameter proxy has been provided.\n\n         .. versionadded:: 2.3\n\n      :param int compress: Enable Per-Message Compress Extension support.\n                           0 for disable, 9 to 15 for window bit support.\n                           Default value is 0.\n\n         .. versionadded:: 2.3\n\n      :param int max_msg_size: maximum size of read websocket message,\n                               4 MB by default. To disable the size\n                               limit use ``0``.\n\n         .. versionadded:: 3.3\n\n      :param str method: HTTP method to establish WebSocket connection,\n                         ``'GET'`` by default.\n\n         .. versionadded:: 3.5\n\n      :param bool decode_text: If ``True`` (default), TEXT messages are\n                               decoded to strings. If ``False``, TEXT messages\n                               are returned as raw bytes, which can improve\n                               performance when using JSON parsers like\n                               ``orjson`` that accept bytes directly.\n\n         .. versionadded:: 3.14\n\n\n   .. method:: close()\n      :async:\n\n      Close underlying connector.\n\n      Release all acquired resources.\n\n   .. method:: detach()\n\n      Detach connector from session without closing the former.\n\n      Session is switched to closed state anyway.\n\n\n\nBasic API\n---------\n\nWhile we encourage :class:`ClientSession` usage we also provide simple\ncoroutines for making HTTP requests.\n\nBasic API is good for performing simple HTTP requests without\nkeepaliving, cookies and complex connection stuff like properly configured SSL\ncertification chaining.\n\n\n.. function:: request(method, url, *, params=None, data=None, \\\n                        json=None,\\\n                        cookies=None, headers=None, skip_auto_headers=None, auth=None, \\\n                        allow_redirects=True, max_redirects=10, \\\n                        compress=False, chunked=None, expect100=False, raise_for_status=None, \\\n                        read_until_eof=True, \\\n                        proxy=None, proxy_auth=None, \\\n                        timeout=sentinel, ssl=True, \\\n                        server_hostname=None, \\\n                        proxy_headers=None, \\\n                        trace_request_ctx=None, \\\n                        read_bufsize=None, \\\n                        auto_decompress=None, \\\n                        max_line_size=None, \\\n                        max_field_size=None, \\\n                        max_headers=None, \\\n                        version=aiohttp.HttpVersion11, \\\n                        connector=None)\n   :canonical: aiohttp.client.request\n   :async:\n\n   Asynchronous context manager for performing an asynchronous HTTP\n   request. Returns a :class:`ClientResponse` response object. Use as\n   an async context manager.\n\n   :param str method: HTTP method\n\n   :param url: Request URL, :class:`~yarl.URL` or :class:`str` that will\n               be encoded with :class:`~yarl.URL` (see :class:`~yarl.URL`\n               to skip encoding).\n\n   :param params: Mapping, iterable of tuple of *key*/*value* pairs or\n                  string to be sent as parameters in the query\n                  string of the new request. Ignored for subsequent\n                  redirected requests (optional)\n\n                  Allowed values are:\n\n                  - :class:`collections.abc.Mapping` e.g. :class:`dict`,\n                     :class:`multidict.MultiDict` or\n                     :class:`multidict.MultiDictProxy`\n                  - :class:`collections.abc.Iterable` e.g. :class:`tuple` or\n                     :class:`list`\n                  - :class:`str` with preferably url-encoded content\n                     (**Warning:** content will not be encoded by *aiohttp*)\n\n   :param data: The data to send in the body of the request. This can be a\n                :class:`FormData` object or anything that can be passed into\n                :class:`FormData`, e.g. a dictionary, bytes, or file-like object.\n                (optional)\n\n   :param json: Any json compatible python object (optional). *json* and *data*\n                parameters could not be used at the same time.\n\n   :param dict cookies: HTTP Cookies to send with the request (optional)\n\n   :param dict headers: HTTP Headers to send with the request (optional)\n\n   :param skip_auto_headers: set of headers for which autogeneration\n      should be skipped.\n\n      *aiohttp* autogenerates headers like ``User-Agent`` or\n      ``Content-Type`` if these headers are not explicitly\n      passed. Using ``skip_auto_headers`` parameter allows to skip\n      that generation.\n\n      Iterable of :class:`str` or :class:`~multidict.istr`\n      (optional)\n\n   :param aiohttp.BasicAuth auth: an object that represents HTTP Basic\n                                  Authorization (optional)\n\n   :param bool allow_redirects: Whether to process redirects or not.\n      When ``True``, redirects are followed (up to ``max_redirects`` times)\n      and logged into :attr:`ClientResponse.history` and ``trace_configs``.\n      When ``False``, the original response is returned.\n      ``True`` by default (optional).\n\n   :param int max_redirects: Maximum number of redirects to follow.\n      :exc:`TooManyRedirects` is raised if the number is exceeded.\n      Ignored when ``allow_redirects=False``.\n      ``10`` by default.\n\n   :param bool compress: Set to ``True`` if request has to be compressed\n                         with deflate encoding. If `compress` can not be combined\n                         with a *Content-Encoding* and *Content-Length* headers.\n                         ``None`` by default (optional).\n\n   :param int chunked: Enables chunked transfer encoding.\n      It is up to the developer\n      to decide how to chunk data streams. If chunking is enabled, aiohttp\n      encodes the provided chunks in the \"Transfer-encoding: chunked\" format.\n      If *chunked* is set, then the *Transfer-encoding* and *content-length*\n      headers are disallowed. ``None`` by default (optional).\n\n   :param bool expect100: Expect 100-continue response from server.\n                          ``False`` by default (optional).\n\n   :param bool raise_for_status: Automatically call\n                                 :meth:`ClientResponse.raise_for_status`\n                                 for response if set to ``True``.  If\n                                 set to ``None`` value from\n                                 ``ClientSession`` will be used.\n                                 ``None`` by default (optional).\n\n      .. versionadded:: 3.4\n\n   :param bool read_until_eof: Read response until EOF if response\n                               does not have Content-Length header.\n                               ``True`` by default (optional).\n\n   :param proxy: Proxy URL, :class:`str` or :class:`~yarl.URL` (optional)\n\n   :param aiohttp.BasicAuth proxy_auth: an object that represents proxy HTTP\n                                        Basic Authorization (optional)\n\n   :param timeout: a :class:`ClientTimeout` settings structure, 300 seconds (5min)\n        total timeout, 30 seconds socket connect timeout by default.\n\n   :param ssl: SSL validation mode. ``True`` for default SSL check\n               (:func:`ssl.create_default_context` is used),\n               ``False`` for skip SSL certificate validation,\n               :class:`aiohttp.Fingerprint` for fingerprint\n               validation, :class:`ssl.SSLContext` for custom SSL\n               certificate validation.\n\n               Supersedes *verify_ssl*, *ssl_context* and\n               *fingerprint* parameters.\n\n   :param str server_hostname: Sets or overrides the host name that the\n      target server's certificate will be matched against.\n\n      See :py:meth:`asyncio.loop.create_connection`\n      for more information.\n\n   :param collections.abc.Mapping proxy_headers: HTTP headers to send to the proxy\n      if the parameter proxy has been provided.\n\n   :param trace_request_ctx: Object used to give as a kw param for each new\n      :class:`TraceConfig` object instantiated,\n      used to give information to the\n      tracers that is only available at request time.\n\n   :param int read_bufsize: Size of the read buffer (:attr:`ClientResponse.content`).\n                            ``None`` by default,\n                            it means that the session global value is used.\n\n      .. versionadded:: 3.7\n\n   :param bool auto_decompress: Automatically decompress response body.\n      May be used to enable/disable auto decompression on a per-request basis.\n\n   :param int max_line_size: Maximum allowed size of lines in responses.\n\n   :param int max_field_size: Maximum allowed size of header name and value combined in responses.\n\n   :param int max_headers: Maximum number of headers and trailers combined in responses.\n\n   :param aiohttp.protocol.HttpVersion version: Request HTTP version,\n      ``HTTP 1.1`` by default. (optional)\n\n   :param aiohttp.BaseConnector connector: BaseConnector sub-class\n      instance to support connection pooling. (optional)\n\n   :return ClientResponse: a :class:`client response <ClientResponse>` object.\n\n   Usage::\n\n      import aiohttp\n\n      async def fetch():\n          async with aiohttp.request('GET',\n                  'http://python.org/') as resp:\n              assert resp.status == 200\n              print(await resp.text())\n\n\n.. _aiohttp-client-reference-connectors:\n\nConnectors\n----------\n\nConnectors are transports for aiohttp client API.\n\nThere are standard connectors:\n\n1. :class:`TCPConnector` for regular *TCP sockets* (both *HTTP* and\n   *HTTPS* schemes supported).\n2. :class:`UnixConnector` for connecting via UNIX socket (it's used mostly for\n   testing purposes).\n\nAll connector classes should be derived from :class:`BaseConnector`.\n\nBy default all *connectors* support *keep-alive connections* (behavior\nis controlled by *force_close* constructor's parameter).\n\n\n.. class:: BaseConnector(*, keepalive_timeout=15, \\\n                         force_close=False, limit=100, limit_per_host=0, \\\n                         enable_cleanup_closed=False, loop=None)\n   :canonical: aiohttp.connector.BaseConnector\n\n   Base class for all connectors.\n\n   :param float keepalive_timeout: timeout for connection reusing\n                                   after releasing (optional). Values\n                                   ``0``. For disabling *keep-alive*\n                                   feature use ``force_close=True``\n                                   flag.\n\n   :param int limit: total number simultaneous connections. If *limit* is\n                     ``0`` the connector has no limit (default: 100).\n\n   :param int limit_per_host: limit simultaneous connections to the same\n      endpoint.  Endpoints are the same if they are\n      have equal ``(host, port, is_ssl)`` triple.\n      If *limit* is ``0`` the connector has no limit (default: 0).\n\n   :param bool force_close: close underlying sockets after\n                            connection releasing (optional).\n\n   :param bool enable_cleanup_closed: some SSL servers do not properly complete\n      SSL shutdown process, in that case asyncio leaks SSL connections.\n      If this parameter is set to True, aiohttp additionally aborts underlining\n      transport after 2 seconds. It is off by default.\n\n      For Python version 3.12.7+, or 3.13.1 and later,\n      this parameter is ignored because the asyncio SSL connection\n      leak is fixed in these versions of Python.\n\n\n   :param loop: :ref:`event loop<asyncio-event-loop>`\n      used for handling connections.\n      If param is ``None``, :func:`asyncio.get_event_loop`\n      is used for getting default event loop.\n\n      .. deprecated:: 2.0\n\n   .. attribute:: closed\n\n      Read-only property, ``True`` if connector is closed.\n\n   .. attribute:: force_close\n\n      Read-only property, ``True`` if connector should ultimately\n      close connections on releasing.\n\n   .. attribute:: limit\n\n      The total number for simultaneous connections.\n      If limit is 0 the connector has no limit. The default limit size is 100.\n\n   .. attribute:: limit_per_host\n\n      The limit for simultaneous connections to the same\n      endpoint.\n\n      Endpoints are the same if they are have equal ``(host, port,\n      is_ssl)`` triple.\n\n      If *limit_per_host* is ``0`` the connector has no limit per host.\n\n      Read-only property.\n\n   .. method:: close()\n      :async:\n\n      Close all opened connections.\n\n   .. method:: connect(request)\n      :async:\n\n      Get a free connection from pool or create new one if connection\n      is absent in the pool.\n\n      The call may be paused if :attr:`limit` is exhausted until used\n      connections returns to pool.\n\n      :param aiohttp.ClientRequest request: request object\n                                                   which is connection\n                                                   initiator.\n\n      :return: :class:`Connection` object.\n\n   .. method:: _create_connection(req)\n      :async:\n\n      Abstract method for actual connection establishing, should be\n      overridden in subclasses.\n\n\n.. py:class:: AddrInfoType\n\n   Refer to :py:data:`aiohappyeyeballs.AddrInfoType` for more info.\n\n.. warning::\n\n   Be sure to use ``aiohttp.AddrInfoType`` rather than\n   ``aiohappyeyeballs.AddrInfoType`` to avoid import breakage, as\n   it is likely to be removed from :mod:`aiohappyeyeballs` in the\n   future.\n\n\n.. py:class:: SocketFactoryType\n\n   Refer to :py:data:`aiohappyeyeballs.SocketFactoryType` for more info.\n\n.. warning::\n\n   Be sure to use ``aiohttp.SocketFactoryType`` rather than\n   ``aiohappyeyeballs.SocketFactoryType`` to avoid import breakage,\n   as it is likely to be removed from :mod:`aiohappyeyeballs` in the\n   future.\n\n\n.. class:: TCPConnector(*, ssl=True, verify_ssl=True, fingerprint=None, \\\n                 use_dns_cache=True, ttl_dns_cache=10, \\\n                 family=0, ssl_context=None, local_addr=None, \\\n                 resolver=None, keepalive_timeout=sentinel, \\\n                 force_close=False, limit=100, limit_per_host=0, \\\n                 enable_cleanup_closed=False, timeout_ceil_threshold=5, \\\n                 happy_eyeballs_delay=0.25, interleave=None, loop=None, \\\n                 socket_factory=None, ssl_shutdown_timeout=0)\n   :canonical: aiohttp.connector.TCPConnector\n\n   Connector for working with *HTTP* and *HTTPS* via *TCP* sockets.\n\n   The most common transport. When you don't know what connector type\n   to use, use a :class:`TCPConnector` instance.\n\n   :class:`TCPConnector` inherits from :class:`BaseConnector`.\n\n   Constructor accepts all parameters suitable for\n   :class:`BaseConnector` plus several TCP-specific ones:\n\n      :param ssl: SSL validation mode. ``True`` for default SSL check\n                  (:func:`ssl.create_default_context` is used),\n                  ``False`` for skip SSL certificate validation,\n                  :class:`aiohttp.Fingerprint` for fingerprint\n                  validation, :class:`ssl.SSLContext` for custom SSL\n                  certificate validation.\n\n                  Supersedes *verify_ssl*, *ssl_context* and\n                  *fingerprint* parameters.\n\n         .. versionadded:: 3.0\n\n   :param bool verify_ssl: perform SSL certificate validation for\n      *HTTPS* requests (enabled by default). May be disabled to\n      skip validation for sites with invalid certificates.\n\n      .. deprecated:: 2.3\n\n         Pass *verify_ssl* to ``ClientSession.get()`` etc.\n\n   :param bytes fingerprint: pass the SHA256 digest of the expected\n      certificate in DER format to verify that the certificate the\n      server presents matches. Useful for `certificate pinning\n      <https://en.wikipedia.org/wiki/HTTP_Public_Key_Pinning>`_.\n\n      Note: use of MD5 or SHA1 digests is insecure and deprecated.\n\n      .. deprecated:: 2.3\n\n         Pass *verify_ssl* to ``ClientSession.get()`` etc.\n\n   :param bool use_dns_cache: use internal cache for DNS lookups, ``True``\n      by default.\n\n      Enabling an option *may* speedup connection\n      establishing a bit but may introduce some\n      *side effects* also.\n\n   :param int ttl_dns_cache: expire after some seconds the DNS entries, ``None``\n      means cached forever. By default 10 seconds (optional).\n\n      In some environments the IP addresses related to a specific HOST can\n      change after a specific time. Use this option to keep the DNS cache\n      updated refreshing each entry after N seconds.\n\n   :param int limit: total number simultaneous connections. If *limit* is\n                     ``0`` the connector has no limit (default: 100).\n\n   :param int limit_per_host: limit simultaneous connections to the same\n      endpoint.  Endpoints are the same if they are\n      have equal ``(host, port, is_ssl)`` triple.\n      If *limit* is ``0`` the connector has no limit (default: 0).\n\n   :param aiohttp.abc.AbstractResolver resolver: custom resolver\n      instance to use.  ``aiohttp.DefaultResolver`` by\n      default (asynchronous if ``aiodns>=1.1`` is installed).\n\n      Custom resolvers allow to resolve hostnames differently than the\n      way the host is configured.\n\n      The resolver is ``aiohttp.ThreadedResolver`` by default,\n      asynchronous version is pretty robust but might fail in\n      very rare cases.\n\n   :param int family: TCP socket family, both IPv4 and IPv6 by default.\n                      For *IPv4* only use :data:`socket.AF_INET`,\n                      for  *IPv6* only -- :data:`socket.AF_INET6`.\n\n                      *family* is ``0`` by default, that means both\n                      IPv4 and IPv6 are accepted. To specify only\n                      concrete version please pass\n                      :data:`socket.AF_INET` or\n                      :data:`socket.AF_INET6` explicitly.\n\n   :param ssl.SSLContext ssl_context: SSL context used for processing\n      *HTTPS* requests (optional).\n\n      *ssl_context* may be used for configuring certification\n      authority channel, supported SSL options etc.\n\n   :param tuple local_addr: tuple of ``(local_host, local_port)`` used to bind\n      socket locally if specified.\n\n   :param bool force_close: close underlying sockets after\n                            connection releasing (optional).\n\n   :param bool enable_cleanup_closed: Some ssl servers do not properly complete\n      SSL shutdown process, in that case asyncio leaks SSL connections.\n      If this parameter is set to True, aiohttp additionally aborts underlining\n      transport after 2 seconds. It is off by default.\n\n   :param float happy_eyeballs_delay: The amount of time in seconds to wait for a\n      connection attempt to complete, before starting the next attempt in parallel.\n      This is the “Connection Attempt Delay” as defined in RFC 8305. To disable\n      Happy Eyeballs, set this to ``None``. The default value recommended by the\n      RFC is 0.25 (250 milliseconds).\n\n        .. versionadded:: 3.10\n\n   :param int interleave: controls address reordering when a host name resolves\n      to multiple IP addresses. If ``0`` or unspecified, no reordering is done, and\n      addresses are tried in the order returned by the resolver. If a positive\n      integer is specified, the addresses are interleaved by address family, and\n      the given integer is interpreted as “First Address Family Count” as defined\n      in RFC 8305. The default is ``0`` if happy_eyeballs_delay is not specified, and\n      ``1`` if it is.\n\n        .. versionadded:: 3.10\n\n   :param SocketFactoryType socket_factory: This function takes an\n      :py:data:`AddrInfoType` and is used in lieu of\n      :py:func:`socket.socket` when creating TCP connections.\n\n        .. versionadded:: 3.12\n\n   :param float ssl_shutdown_timeout: **(DEPRECATED)** This parameter is deprecated\n      and will be removed in aiohttp 4.0. Grace period for SSL shutdown on TLS\n      connections when the connector is closed (``0`` seconds by default).\n      By default (``0``), SSL connections are aborted immediately when the\n      connector is closed, without performing the shutdown handshake. During\n      normal operation, SSL connections use Python's default SSL shutdown\n      behavior. Setting this to a positive value (e.g., ``0.1``) will perform\n      a graceful shutdown when closing the connector, notifying the remote\n      server which can help prevent \"connection reset\" errors at the cost of\n      additional cleanup time. Note: On Python versions prior to 3.11, only\n      a value of ``0`` is supported; other values will trigger a warning.\n\n        .. versionadded:: 3.12.5\n\n        .. versionchanged:: 3.12.11\n           Changed default from ``0.1`` to ``0`` to abort SSL connections\n           immediately when the connector is closed. Added support for\n           ``ssl_shutdown_timeout=0`` on all Python versions. A :exc:`RuntimeWarning`\n           is issued when non-zero values are passed on Python < 3.11.\n\n        .. deprecated:: 3.12.11\n           This parameter is deprecated and will be removed in aiohttp 4.0.\n\n   .. attribute:: family\n\n      *TCP* socket family e.g. :data:`socket.AF_INET` or\n      :data:`socket.AF_INET6`\n\n      Read-only property.\n\n   .. attribute:: dns_cache\n\n      Use quick lookup in internal *DNS* cache for host names if ``True``.\n\n      Read-only :class:`bool` property.\n\n   .. attribute:: cached_hosts\n\n      The cache of resolved hosts if :attr:`dns_cache` is enabled.\n\n      Read-only :class:`types.MappingProxyType` property.\n\n   .. method:: clear_dns_cache(self, host=None, port=None)\n\n      Clear internal *DNS* cache.\n\n      Remove specific entry if both *host* and *port* are specified,\n      clear all cache otherwise.\n\n\n.. class:: UnixConnector(path, *, conn_timeout=None, \\\n                         keepalive_timeout=30, limit=100, \\\n                         force_close=False, loop=None)\n   :canonical: aiohttp.connector.UnixConnector\n\n   Unix socket connector.\n\n   Use :class:`UnixConnector` for sending *HTTP/HTTPS* requests\n   through *UNIX Sockets* as underlying transport.\n\n   UNIX sockets are handy for writing tests and making very fast\n   connections between processes on the same host.\n\n   :class:`UnixConnector` is inherited from :class:`BaseConnector`.\n\n    Usage::\n\n       conn = UnixConnector(path='/path/to/socket')\n       session = ClientSession(connector=conn)\n       async with session.get('http://python.org') as resp:\n           ...\n\n   Constructor accepts all parameters suitable for\n   :class:`BaseConnector` plus UNIX-specific one:\n\n   :param str path: Unix socket path\n\n\n   .. attribute:: path\n\n      Path to *UNIX socket*, read-only :class:`str` property.\n\n\n.. class:: Connection\n   :canonical: aiohttp.connector.Connection\n\n   Encapsulates single connection in connector object.\n\n   End user should never create :class:`Connection` instances manually\n   but get it by :meth:`BaseConnector.connect` coroutine.\n\n   .. attribute:: closed\n\n      :class:`bool` read-only property, ``True`` if connection was\n      closed, released or detached.\n\n   .. attribute:: loop\n\n      Event loop used for connection\n\n      .. deprecated:: 3.5\n\n   .. attribute:: transport\n\n      Connection transport\n\n   .. method:: close()\n\n      Close connection with forcibly closing underlying socket.\n\n   .. method:: release()\n\n      Release connection back to connector.\n\n      Underlying socket is not closed, the connection may be reused\n      later if timeout (30 seconds by default) for connection was not\n      expired.\n\n\nResponse object\n---------------\n\n.. class:: ClientResponse\n   :canonical: aiohttp.client_reqrep.ClientResponse\n\n   Client response returned by :meth:`aiohttp.ClientSession.request` and family.\n\n   User never creates the instance of ClientResponse class but gets it\n   from API calls.\n\n   :class:`ClientResponse` supports async context manager protocol, e.g.::\n\n       resp = await client_session.get(url)\n       async with resp:\n           assert resp.status == 200\n\n   After exiting from ``async with`` block response object will be\n   *released* (see :meth:`release` method).\n\n   .. attribute:: version\n\n      Response's version, :class:`~aiohttp.protocol.HttpVersion` instance.\n\n   .. attribute:: status\n\n      HTTP status code of response (:class:`int`), e.g. ``200``.\n\n   .. attribute:: reason\n\n      HTTP status reason of response (:class:`str`), e.g. ``\"OK\"``.\n\n   .. attribute:: ok\n\n      Boolean representation of HTTP status code (:class:`bool`).\n      ``True`` if ``status`` is less than ``400``; otherwise, ``False``.\n\n   .. attribute:: method\n\n      Request's method (:class:`str`).\n\n   .. attribute:: url\n\n      URL of request (:class:`~yarl.URL`).\n\n   .. attribute:: real_url\n\n      Unmodified URL of request with URL fragment unstripped (:class:`~yarl.URL`).\n\n      .. versionadded:: 3.2\n\n   .. attribute:: connection\n\n      :class:`Connection` used for handling response.\n\n   .. attribute:: content\n\n      Payload stream, which contains response's BODY (:class:`StreamReader`).\n      It supports various reading methods depending on the expected format.\n      When chunked transfer encoding is used by the server, allows retrieving\n      the actual http chunks.\n\n      Reading from the stream may raise\n      :exc:`aiohttp.ClientPayloadError` if the response object is\n      closed before response receives all data or in case if any\n      transfer encoding related errors like malformed chunked\n      encoding of broken compression data.\n\n   .. attribute:: cookies\n\n      HTTP cookies of response (*Set-Cookie* HTTP header,\n      :class:`~http.cookies.SimpleCookie`).\n\n      .. note::\n\n         Since :class:`~http.cookies.SimpleCookie` uses cookie name as the\n         key, cookies with the same name but different domains or paths will\n         be overwritten. Only the last cookie with a given name will be\n         accessible via this attribute.\n\n         To access all cookies, including duplicates with the same name,\n         use :meth:`response.headers.getall('Set-Cookie') <multidict.MultiDictProxy.getall>`.\n\n         The session's cookie jar will correctly store all cookies, even if\n         they are not accessible via this attribute.\n\n   .. attribute:: headers\n\n      A case-insensitive multidict proxy with HTTP headers of\n      response, :class:`~multidict.CIMultiDictProxy`.\n\n   .. attribute:: raw_headers\n\n      Unmodified HTTP headers of response as unconverted bytes, a sequence of\n      ``(key, value)`` pairs.\n\n   .. attribute:: links\n\n      Link HTTP header parsed into a :class:`~multidict.MultiDictProxy`.\n\n      For each link, key is link param `rel` when it exists, or link url as\n      :class:`str` otherwise, and value is :class:`~multidict.MultiDictProxy`\n      of link params and url at key `url` as :class:`~yarl.URL` instance.\n\n      .. versionadded:: 3.2\n\n   .. attribute:: content_type\n\n      Read-only property with *content* part of *Content-Type* header.\n\n      .. note::\n\n         Returns ``'application/octet-stream'`` if no Content-Type header\n         is present or the value contains invalid syntax according to\n         :rfc:`9110`. To see the original header check\n         ``resp.headers[\"Content-Type\"]``.\n\n         To make sure Content-Type header is not present in\n         the server reply, use :attr:`headers` or :attr:`raw_headers`, e.g.\n         ``'Content-Type' not in resp.headers``.\n\n   .. attribute:: charset\n\n      Read-only property that specifies the *encoding* for the request's BODY.\n\n      The value is parsed from the *Content-Type* HTTP header.\n\n      Returns :class:`str` like ``'utf-8'`` or ``None`` if no *Content-Type*\n      header present in HTTP headers or it has no charset information.\n\n   .. attribute:: content_disposition\n\n      Read-only property that specified the *Content-Disposition* HTTP header.\n\n      Instance of :class:`ContentDisposition` or ``None`` if no *Content-Disposition*\n      header present in HTTP headers.\n\n   .. attribute:: history\n\n      A :class:`~collections.abc.Sequence` of :class:`ClientResponse`\n      objects of preceding requests (earliest request first) if there were\n      redirects, an empty sequence otherwise.\n\n   .. method:: close()\n\n      Close response and underlying connection.\n\n      For :term:`keep-alive` support see :meth:`release`.\n\n   .. method:: read()\n      :async:\n\n      Read the whole response's body as :class:`bytes`.\n\n      Close underlying connection if data reading gets an error,\n      release connection otherwise.\n\n      Raise an :exc:`aiohttp.ClientResponseError` if the data can't\n      be read.\n\n      :return bytes: read *BODY*.\n\n      .. seealso:: :meth:`close`, :meth:`release`.\n\n   .. method:: release()\n\n      It is not required to call `release` on the response\n      object. When the client fully receives the payload, the\n      underlying connection automatically returns back to pool. If the\n      payload is not fully read, the connection is closed\n\n   .. method:: raise_for_status()\n\n      Raise an :exc:`aiohttp.ClientResponseError` if the response\n      status is 400 or higher.\n\n      Do nothing for success responses (less than 400).\n\n   .. method:: text(encoding=None)\n      :async:\n\n      Read response's body and return decoded :class:`str` using\n      specified *encoding* parameter.\n\n      If *encoding* is ``None`` content encoding is determined from the\n      Content-Type header, or using the ``fallback_charset_resolver`` function.\n\n      Close underlying connection if data reading gets an error,\n      release connection otherwise.\n\n      :param str encoding: text encoding used for *BODY* decoding, or\n                           ``None`` for encoding autodetection\n                           (default).\n\n\n      :raises: :exc:`UnicodeDecodeError` if decoding fails. See also\n               :meth:`get_encoding`.\n\n      :return str: decoded *BODY*\n\n   .. method:: json(*, encoding=None, loads=json.loads, \\\n                      content_type='application/json')\n      :async:\n\n      Read response's body as *JSON*, return :class:`dict` using\n      specified *encoding* and *loader*. If data is not still available\n      a ``read`` call will be done.\n\n      If response's `content-type` does not match `content_type` parameter\n      :exc:`aiohttp.ContentTypeError` get raised.\n      To disable content type check pass ``None`` value.\n\n      :param str encoding: text encoding used for *BODY* decoding, or\n                           ``None`` for encoding autodetection\n                           (default).\n\n                           By the standard JSON encoding should be\n                           ``UTF-8`` but practice beats purity: some\n                           servers return non-UTF\n                           responses. Autodetection works pretty fine\n                           anyway.\n\n      :param collections.abc.Callable loads: :term:`callable` used for loading *JSON*\n                             data, :func:`json.loads` by default.\n\n      :param str content_type: specify response's content-type, if content type\n         does not match raise :exc:`aiohttp.ClientResponseError`.\n         To disable `content-type` check, pass ``None`` as value.\n         (default: `application/json`).\n\n      :return: *BODY* as *JSON* data parsed by *loads* parameter or\n               ``None`` if *BODY* is empty or contains white-spaces only.\n\n   .. attribute:: request_info\n\n       A :class:`typing.NamedTuple` with request URL and headers from :class:`~aiohttp.ClientRequest`\n       object, :class:`aiohttp.RequestInfo` instance.\n\n   .. method:: get_encoding()\n\n      Retrieve content encoding using ``charset`` info in ``Content-Type`` HTTP header.\n      If no charset is present or the charset is not understood by Python, the\n      ``fallback_charset_resolver`` function associated with the ``ClientSession`` is called.\n\n      .. versionadded:: 3.0\n\n\nClientWebSocketResponse\n-----------------------\n\nTo connect to a websocket server :func:`aiohttp.ws_connect` or\n:meth:`aiohttp.ClientSession.ws_connect` coroutines should be used, do\nnot create an instance of class :class:`ClientWebSocketResponse`\nmanually.\n\n.. class:: ClientWebSocketResponse()\n   :canonical: aiohttp.client_ws.ClientWebSocketResponse\n\n   Class for handling client-side websockets.\n\n   .. attribute:: closed\n\n      Read-only property, ``True`` if :meth:`close` has been called or\n      :const:`~aiohttp.WSMsgType.CLOSE` message has been received from peer.\n\n   .. attribute:: protocol\n\n      Websocket *subprotocol* chosen after :meth:`start` call.\n\n      May be ``None`` if server and client protocols are\n      not overlapping.\n\n   .. method:: get_extra_info(name, default=None)\n\n      Reads optional extra information from the connection's transport.\n      If no value associated with ``name`` is found, ``default`` is returned.\n\n      See :meth:`asyncio.BaseTransport.get_extra_info`\n\n      :param str name: The key to look up in the transport extra information.\n\n      :param default: Default value to be used when no value for ``name`` is\n                      found (default is ``None``).\n\n   .. method:: exception()\n\n      Returns exception if any occurs or returns None.\n\n   .. method:: ping(message=b'')\n      :async:\n\n      Send :const:`~aiohttp.WSMsgType.PING` to peer.\n\n      :param message: optional payload of *ping* message,\n                      :class:`str` (converted to *UTF-8* encoded bytes)\n                      or :class:`bytes`.\n\n      .. versionchanged:: 3.0\n\n         The method is converted into :term:`coroutine`\n\n   .. method:: pong(message=b'')\n      :async:\n\n      Send :const:`~aiohttp.WSMsgType.PONG` to peer.\n\n      :param message: optional payload of *pong* message,\n                      :class:`str` (converted to *UTF-8* encoded bytes)\n                      or :class:`bytes`.\n\n      .. versionchanged:: 3.0\n\n         The method is converted into :term:`coroutine`\n\n   .. method:: send_str(data, compress=None)\n      :async:\n\n      Send *data* to peer as :const:`~aiohttp.WSMsgType.TEXT` message.\n\n      :param str data: data to send.\n\n      :param int compress: sets specific level of compression for\n                           single message,\n                           ``None`` for not overriding per-socket setting.\n\n      :raise TypeError: if data is not :class:`str`\n\n      .. versionchanged:: 3.0\n\n         The method is converted into :term:`coroutine`,\n         *compress* parameter added.\n\n   .. method:: send_bytes(data, compress=None)\n      :async:\n\n      Send *data* to peer as :const:`~aiohttp.WSMsgType.BINARY` message.\n\n      :param data: data to send.\n\n      :param int compress: sets specific level of compression for\n                           single message,\n                           ``None`` for not overriding per-socket setting.\n\n      :raise TypeError: if data is not :class:`bytes`,\n                        :class:`bytearray` or :class:`memoryview`.\n\n      .. versionchanged:: 3.0\n\n         The method is converted into :term:`coroutine`,\n         *compress* parameter added.\n\n   .. method:: send_json(data, compress=None, *, dumps=json.dumps)\n      :async:\n\n      Send *data* to peer as JSON string.\n\n      :param data: data to send.\n\n      :param int compress: sets specific level of compression for\n                           single message,\n                           ``None`` for not overriding per-socket setting.\n\n      :param collections.abc.Callable dumps: any :term:`callable` that accepts an object and\n                             returns a JSON string\n                             (:func:`json.dumps` by default).\n\n      :raise RuntimeError: if connection is not started or closing\n\n      :raise ValueError: if data is not serializable object\n\n      :raise TypeError: if value returned by ``dumps(data)`` is not\n                        :class:`str`\n\n      .. versionchanged:: 3.0\n\n         The method is converted into :term:`coroutine`,\n         *compress* parameter added.\n\n   .. method:: send_json_bytes(data, compress=None, *, dumps)\n      :async:\n\n      Send *data* to peer as a JSON binary frame using a bytes-returning encoder.\n\n      :param data: data to send.\n\n      :param int compress: sets specific level of compression for\n                           single message,\n                           ``None`` for not overriding per-socket setting.\n\n      :param collections.abc.Callable dumps: any :term:`callable` that accepts an object and\n                             returns JSON as :class:`bytes`\n                             (e.g. ``orjson.dumps``).\n\n      :raise RuntimeError: if connection is not started or closing\n\n      :raise ValueError: if data is not serializable object\n\n      :raise TypeError: if value returned by ``dumps(data)`` is not\n                        :class:`bytes`\n\n   .. method:: send_frame(message, opcode, compress=None)\n      :async:\n\n      Send a :const:`~aiohttp.WSMsgType` message *message* to peer.\n\n      This method is low-level and should be used with caution as it\n      only accepts bytes which must conform to the correct message type\n      for *message*.\n\n      It is recommended to use the :meth:`send_str`, :meth:`send_bytes`\n      or :meth:`send_json` methods instead of this method.\n\n      The primary use case for this method is to send bytes that are\n      have already been encoded without having to decode and\n      re-encode them.\n\n      :param bytes message: message to send.\n\n      :param ~aiohttp.WSMsgType opcode: opcode of the message.\n\n      :param int compress: sets specific level of compression for\n                           single message,\n                           ``None`` for not overriding per-socket setting.\n\n      .. versionadded:: 3.11\n\n   .. method:: close(*, code=WSCloseCode.OK, message=b'')\n      :async:\n\n      A :ref:`coroutine<coroutine>` that initiates closing handshake by sending\n      :const:`~aiohttp.WSMsgType.CLOSE` message. It waits for\n      close response from server. To add a timeout to `close()` call\n      just wrap the call with `asyncio.wait()` or `asyncio.wait_for()`.\n\n      :param int code: closing code. See also :class:`~aiohttp.WSCloseCode`.\n\n      :param message: optional payload of *close* message,\n         :class:`str` (converted to *UTF-8* encoded bytes) or :class:`bytes`.\n\n   .. method:: receive()\n      :async:\n\n      A :ref:`coroutine<coroutine>` that waits upcoming *data*\n      message from peer and returns it.\n\n      The coroutine implicitly handles\n      :const:`~aiohttp.WSMsgType.PING`,\n      :const:`~aiohttp.WSMsgType.PONG` and\n      :const:`~aiohttp.WSMsgType.CLOSE` without returning the\n      message.\n\n      It process *ping-pong game* and performs *closing handshake* internally.\n\n      :return: :class:`~aiohttp.WSMessage`\n\n   .. method:: receive_str()\n      :async:\n\n      A :ref:`coroutine<coroutine>` that calls :meth:`receive` but\n      also asserts the message type is\n      :const:`~aiohttp.WSMsgType.TEXT`.\n\n      :return str: peer's message content.\n\n      :raise aiohttp.WSMessageTypeError: if message is not :const:`~aiohttp.WSMsgType.TEXT`.\n\n   .. method:: receive_bytes()\n      :async:\n\n      A :ref:`coroutine<coroutine>` that calls :meth:`receive` but\n      also asserts the message type is\n      :const:`~aiohttp.WSMsgType.BINARY`.\n\n      :return bytes: peer's message content.\n\n      :raise aiohttp.WSMessageTypeError: if message is not :const:`~aiohttp.WSMsgType.BINARY`.\n\n   .. method:: receive_json(*, loads=json.loads)\n      :async:\n\n      A :ref:`coroutine<coroutine>` that calls :meth:`receive_str` and loads\n      the JSON string to a Python dict.\n\n      :param collections.abc.Callable loads: any :term:`callable` that accepts\n                              :class:`str` and returns :class:`dict`\n                              with parsed JSON (:func:`json.loads` by\n                              default).\n\n      :return dict: loaded JSON content\n\n      :raise TypeError: if message is :const:`~aiohttp.WSMsgType.BINARY`.\n      :raise ValueError: if message is not valid JSON.\n\nClientRequest\n-------------\n\n.. class:: ClientRequest\n   :canonical: aiohttp.client_reqrep.ClientRequest\n\n   Represents an HTTP request to be sent by the client.\n\n   This object encapsulates all the details of an HTTP request before it is sent.\n   It is primarily used within client middleware to inspect or modify requests.\n\n   .. note::\n\n      You typically don't create ``ClientRequest`` instances directly. They are\n      created internally by :class:`ClientSession` methods and passed to middleware.\n\n   For more information about using middleware, see :ref:`aiohttp-client-middleware`.\n\n   .. attribute:: body\n      :type: Payload | Literal[b\"\"]\n\n      The request body payload (defaults to ``b\"\"`` if no body passed).\n\n      .. danger::\n\n         **DO NOT set this attribute directly!** Direct assignment will cause resource\n         leaks. Always use :meth:`update_body` instead:\n\n         .. code-block:: python\n\n            # WRONG - This will leak resources!\n            request.body = b\"new data\"\n\n            # CORRECT - Use update_body\n            await request.update_body(b\"new data\")\n\n         Setting body directly bypasses cleanup of the previous payload, which can\n         leave file handles open, streams unclosed, and buffers unreleased.\n\n         Additionally, setting body directly must be done from within an event loop\n         and is not thread-safe. Setting body outside of an event loop may raise\n         RuntimeError when closing file-based payloads.\n\n   .. attribute:: chunked\n      :type: bool | None\n\n      Whether to use chunked transfer encoding:\n\n      - ``True``: Use chunked encoding\n      - ``False``: Don't use chunked encoding\n      - ``None``: Automatically determine based on body\n\n   .. attribute:: compress\n      :type: str | None\n\n      The compression encoding for the request body. Common values include\n      ``'gzip'`` and ``'deflate'``, but any string value is technically allowed.\n      ``None`` means no compression.\n\n   .. attribute:: headers\n      :type: multidict.CIMultiDict\n\n      The HTTP headers that will be sent with the request. This is a case-insensitive\n      multidict that can be modified by middleware.\n\n      .. code-block:: python\n\n         # Add or modify headers\n         request.headers['X-Custom-Header'] = 'value'\n         request.headers['User-Agent'] = 'MyApp/1.0'\n\n   .. attribute:: is_ssl\n      :type: bool\n\n      ``True`` if the request uses a secure scheme (e.g., HTTPS, WSS), ``False`` otherwise.\n\n   .. attribute:: method\n      :type: str\n\n      The HTTP method of the request (e.g., ``'GET'``, ``'POST'``, ``'PUT'``, etc.).\n\n   .. attribute:: original_url\n      :type: yarl.URL\n\n      The original URL passed to the request method, including any fragment.\n      This preserves the exact URL as provided by the user.\n\n   .. attribute:: proxy\n      :type: yarl.URL | None\n\n      The proxy URL if the request will be sent through a proxy, ``None`` otherwise.\n\n   .. attribute:: proxy_headers\n      :type: multidict.CIMultiDict | None\n\n      Headers to be sent to the proxy server (e.g., ``Proxy-Authorization``).\n      Only set when :attr:`proxy` is not ``None``.\n\n   .. attribute:: response_class\n      :type: type[ClientResponse]\n\n      The class to use for creating the response object. Defaults to\n      :class:`ClientResponse` but can be customized for special handling.\n\n   .. attribute:: server_hostname\n      :type: str | None\n\n      Override the hostname for SSL certificate verification. Useful when\n      connecting through proxies or to IP addresses.\n\n   .. attribute:: session\n      :type: ClientSession\n\n      The client session that created this request. Useful for accessing\n      session-level configuration or making additional requests within middleware.\n\n      .. warning::\n         Be careful when making requests with the same session inside middleware\n         to avoid infinite recursion. Use ``middlewares=()`` parameter when needed.\n\n   .. attribute:: ssl\n      :type: ssl.SSLContext | bool | Fingerprint\n\n      SSL validation configuration for this request:\n\n      - ``True``: Use default SSL verification\n      - ``False``: Skip SSL verification\n      - :class:`ssl.SSLContext`: Custom SSL context\n      - :class:`Fingerprint`: Verify specific certificate fingerprint\n\n   .. attribute:: url\n      :type: yarl.URL\n\n      The target URL of the request with the fragment (``#...``) part stripped.\n      This is the actual URL that will be used for the connection.\n\n      .. note::\n         To access the original URL with fragment, use :attr:`original_url`.\n\n   .. attribute:: version\n      :type: HttpVersion\n\n      The HTTP version to use for the request (e.g., ``HttpVersion(1, 1)`` for HTTP/1.1).\n\n   .. method:: update_body(body)\n\n      Update the request body and close any existing payload to prevent resource leaks.\n\n      **This is the ONLY correct way to modify a request body.** Never set the\n      :attr:`body` attribute directly.\n\n      This method is particularly useful in middleware when you need to modify the\n      request body after the request has been created but before it's sent.\n\n      :param body: The new body content. Can be:\n\n                   - ``bytes``/``bytearray``: Raw binary data\n                   - ``str``: Text data (encoded using charset from Content-Type)\n                   - :class:`FormData`: Form data encoded as multipart/form-data\n                   - :class:`Payload`: A pre-configured payload object\n                   - ``AsyncIterable[bytes]``: Async iterable of bytes chunks\n                   - File-like object: Will be read and sent as binary data\n                   - ``None``: Clears the body\n\n      .. code-block:: python\n\n         async def middleware(request, handler):\n             # Modify request body in middleware\n             if request.method == 'POST':\n                 # CORRECT: Always use update_body\n                 await request.update_body(b'{\"modified\": true}')\n\n                 # WRONG: Never set body directly!\n                 # request.body = b'{\"modified\": true}'  # This leaks resources!\n\n             # Or add authentication data to form\n             if isinstance(request.body, FormData):\n                 form = FormData()\n                 # Copy existing fields and add auth token\n                 form.add_field('auth_token', 'secret123')\n                 await request.update_body(form)\n\n             return await handler(request)\n\n      .. note::\n\n         This method is async because it may need to close file handles or\n         other resources associated with the previous payload. Always await\n         this method to ensure proper cleanup.\n\n      .. danger::\n\n         **Never set :attr:`ClientRequest.body` directly!** Direct assignment will cause resource\n         leaks. Always use this method instead. Setting the body attribute directly:\n\n         - Bypasses cleanup of the previous payload\n         - Leaves file handles and streams open\n         - Can cause memory leaks\n         - May result in unexpected behavior with async iterables\n\n      .. warning::\n\n         When updating the body, ensure that the Content-Type header is\n         appropriate for the new body content. The Content-Length header\n         will be updated automatically. When using :class:`FormData` or\n         :class:`Payload` objects, headers are updated automatically,\n         but you may need to set Content-Type manually for raw bytes or text.\n\n         It is not recommended to change the payload type in middleware. If the\n         body was already set (e.g., as bytes), it's best to keep the same type\n         rather than converting it (e.g., to str) as this may result in unexpected\n         behavior.\n\n      .. versionadded:: 3.12\n\n\n\nUtilities\n---------\n\n\n.. class:: ClientTimeout(*, total=None, connect=None, \\\n                         sock_connect=None, sock_read=None)\n   :canonical: aiohttp.client.ClientTimeout\n\n   A data class for client timeout settings.\n\n   See :ref:`aiohttp-client-timeouts` for usage examples.\n\n   .. attribute:: total\n\n      Total number of seconds for the whole request.\n\n      :class:`float`, ``None`` by default.\n\n   .. attribute:: connect\n\n      Maximal number of seconds for acquiring a connection from pool.  The time\n      consists connection establishment for a new connection or\n      waiting for a free connection from a pool if pool connection\n      limits are exceeded.\n\n      For pure socket connection establishment time use\n      :attr:`sock_connect`.\n\n      :class:`float`, ``None`` by default.\n\n   .. attribute:: sock_connect\n\n      Maximal number of seconds for connecting to a peer for a new connection, not\n      given from a pool.  See also :attr:`connect`.\n\n      :class:`float`, ``None`` by default.\n\n   .. attribute:: sock_read\n\n      Maximal number of seconds for reading a portion of data from a peer.\n\n      :class:`float`, ``None`` by default.\n\n\n.. class:: ClientWSTimeout(*, ws_receive=None, ws_close=None)\n   :canonical: aiohttp.client_ws.ClientWSTimeout\n\n   A data class for websocket client timeout settings.\n\n   .. attribute:: ws_receive\n\n      A timeout for websocket to receive a complete message.\n\n      :class:`float`, ``None`` by default.\n\n   .. attribute:: ws_close\n\n      A timeout for the websocket to close.\n\n      :class:`float`, ``10.0`` by default.\n\n\n   .. note::\n\n      Timeouts of 5 seconds or more are rounded for scheduling on the next\n      second boundary (an absolute time where microseconds part is zero) for the\n      sake of performance.\n\n      E.g., assume a timeout is ``10``, absolute time when timeout should expire\n      is ``loop.time() + 5``, and it points to ``12345.67 + 10`` which is equal\n      to ``12355.67``.\n\n      The absolute time for the timeout cancellation is ``12356``.\n\n      It leads to grouping all close scheduled timeout expirations to exactly\n      the same time to reduce amount of loop wakeups.\n\n      .. versionchanged:: 3.7\n\n         Rounding to the next seconds boundary is disabled for timeouts smaller\n         than 5 seconds for the sake of easy debugging.\n\n         In turn, tiny timeouts can lead to significant performance degradation\n         on production environment.\n\n\n.. class:: ETag(name, is_weak=False)\n   :canonical: aiohttp.helpers.ETag\n\n   Represents `ETag` identifier.\n\n   .. attribute:: value\n\n      Value of corresponding etag without quotes.\n\n   .. attribute:: is_weak\n\n      Flag indicates that etag is weak (has `W/` prefix).\n\n   .. versionadded:: 3.8\n\n\n.. class:: ContentDisposition\n   :canonical: aiohttp.client_reqrep.ContentDisposition\n\n    A data class to represent the Content-Disposition header,\n    available as :attr:`ClientResponse.content_disposition` attribute.\n\n    .. attribute:: type\n\n    A :class:`str` instance. Value of Content-Disposition header\n    itself, e.g. ``attachment``.\n\n    .. attribute:: filename\n\n    A :class:`str` instance. Content filename extracted from\n    parameters. May be ``None``.\n\n    .. attribute:: parameters\n\n    Read-only mapping contains all parameters.\n\n\n.. class:: RequestInfo()\n   :canonical: aiohttp.client_reqrep.RequestInfo\n\n   A :class:`typing.NamedTuple` with request URL and headers from :class:`~aiohttp.ClientRequest`\n   object, available as :attr:`ClientResponse.request_info` attribute.\n\n   .. attribute:: url\n\n      Requested *url*, :class:`yarl.URL` instance.\n\n   .. attribute:: method\n\n      Request HTTP method like ``'GET'`` or ``'POST'``, :class:`str`.\n\n   .. attribute:: headers\n\n      HTTP headers for request, :class:`multidict.CIMultiDict` instance.\n\n   .. attribute:: real_url\n\n      Requested *url* with URL fragment unstripped, :class:`yarl.URL` instance.\n\n      .. versionadded:: 3.2\n\n\n\n.. class:: BasicAuth(login, password='', encoding='latin1')\n   :canonical: aiohttp.helpers.BasicAuth\n\n   HTTP basic authentication helper.\n\n   :param str login: login\n   :param str password: password\n   :param str encoding: encoding (``'latin1'`` by default)\n\n\n   Should be used for specifying authorization data in client API,\n   e.g. *auth* parameter for :meth:`ClientSession.request() <aiohttp.ClientSession.request>`.\n\n\n   .. classmethod:: decode(auth_header, encoding='latin1')\n\n      Decode HTTP basic authentication credentials.\n\n      :param str auth_header:  The ``Authorization`` header to decode.\n      :param str encoding: (optional) encoding ('latin1' by default)\n\n      :return:  decoded authentication data, :class:`BasicAuth`.\n\n   .. classmethod:: from_url(url)\n\n      Constructed credentials info from url's *user* and *password*\n      parts.\n\n      :return: credentials data, :class:`BasicAuth` or ``None`` is\n                credentials are not provided.\n\n      .. versionadded:: 2.3\n\n   .. method:: encode()\n\n      Encode credentials into string suitable for ``Authorization``\n      header etc.\n\n      :return: encoded authentication data, :class:`str`.\n\n\n.. class:: DigestAuthMiddleware(login, password, *, preemptive=True)\n   :canonical: aiohttp.client_middleware_digest_auth.DigestAuthMiddleware\n\n   HTTP digest authentication client middleware.\n\n   :param str login: login\n   :param str password: password\n   :param bool preemptive: Enable preemptive authentication (default: ``True``)\n\n   This middleware supports HTTP digest authentication with both `auth` and\n   `auth-int` quality of protection (qop) modes, and a variety of hashing algorithms.\n\n   It automatically handles the digest authentication handshake by:\n\n   - Parsing 401 Unauthorized responses with `WWW-Authenticate: Digest` headers\n   - Generating appropriate `Authorization: Digest` headers on retry\n   - Maintaining nonce counts and challenge data per request\n   - When ``preemptive=True``, reusing authentication credentials for subsequent\n     requests to the same protection space (following RFC 7616 Section 3.6)\n\n   **Preemptive Authentication**\n\n   By default (``preemptive=True``), the middleware remembers successful authentication\n   challenges and automatically includes the Authorization header in subsequent requests\n   to the same protection space. This behavior:\n\n   - Improves server efficiency by avoiding extra round trips\n   - Matches how modern web browsers handle digest authentication\n   - Follows the recommendation in RFC 7616 Section 3.6\n\n   The server may still respond with a 401 status and ``stale=true`` if the nonce\n   has expired, in which case the middleware will automatically retry with the new nonce.\n\n   To disable preemptive authentication and require a 401 challenge for every request,\n   set ``preemptive=False``::\n\n       # Default behavior - preemptive auth enabled\n       digest_auth_middleware = DigestAuthMiddleware(login=\"user\", password=\"pass\")\n\n       # Disable preemptive auth - always wait for 401 challenge\n       digest_auth_middleware = DigestAuthMiddleware(login=\"user\", password=\"pass\",\n                                                      preemptive=False)\n\n   Usage::\n\n       digest_auth_middleware = DigestAuthMiddleware(login=\"user\", password=\"pass\")\n       async with ClientSession(middlewares=(digest_auth_middleware,)) as session:\n           async with session.get(\"http://protected.example.com\") as resp:\n               # The middleware automatically handles the digest auth handshake\n               assert resp.status == 200\n\n           # Subsequent requests include auth header preemptively\n           async with session.get(\"http://protected.example.com/other\") as resp:\n               assert resp.status == 200  # No 401 round trip needed\n\n   .. versionadded:: 3.12\n   .. versionchanged:: 3.12.8\n      Added ``preemptive`` parameter to enable/disable preemptive authentication.\n\n\n.. class:: CookieJar(*, unsafe=False, quote_cookie=True, treat_as_secure_origin = [])\n   :canonical: aiohttp.cookiejar.CookieJar\n\n   The cookie jar instance is available as :attr:`ClientSession.cookie_jar`.\n\n   The jar contains :class:`~http.cookies.Morsel` items for storing\n   internal cookie data.\n\n   API provides a count of saved cookies::\n\n       len(session.cookie_jar)\n\n   These cookies may be iterated over::\n\n       for cookie in session.cookie_jar:\n           print(cookie.key)\n           print(cookie[\"domain\"])\n\n   The class implements :class:`collections.abc.Iterable`,\n   :class:`collections.abc.Sized` and\n   :class:`aiohttp.abc.AbstractCookieJar` interfaces.\n\n   Implements cookie storage adhering to RFC 6265.\n\n   :param bool unsafe: (optional) Whether to accept cookies from IPs.\n\n   :param bool quote_cookie: (optional) Whether to quote cookies according to\n                             :rfc:`2109`.  Some backend systems\n                             (not compatible with RFC mentioned above)\n                             does not support quoted cookies.\n\n      .. versionadded:: 3.7\n\n   :param treat_as_secure_origin: (optional) Mark origins as secure\n                                  for cookies marked as Secured. Possible types are\n\n                                  Possible types are:\n\n                                  - :class:`tuple` or :class:`list` of\n                                    :class:`str` or :class:`yarl.URL`\n                                  - :class:`str`\n                                  - :class:`yarl.URL`\n\n      .. versionadded:: 3.8\n\n   .. method:: update_cookies(cookies, response_url=None)\n\n      Update cookies returned by server in ``Set-Cookie`` header.\n\n      :param cookies: a :class:`collections.abc.Mapping`\n         (e.g. :class:`dict`, :class:`~http.cookies.SimpleCookie`) or\n         *iterable* of *pairs* with cookies returned by server's\n         response.\n\n      :param ~yarl.URL response_url: URL of response, ``None`` for *shared\n         cookies*.  Regular cookies are coupled with server's URL and\n         are sent only to this server, shared ones are sent in every\n         client request.\n\n   .. method:: filter_cookies(request_url)\n\n      Return jar's cookies acceptable for URL and available in\n      ``Cookie`` header for sending client requests for given URL.\n\n      :param ~yarl.URL response_url: request's URL for which cookies are asked.\n\n      :return: :class:`http.cookies.SimpleCookie` with filtered\n         cookies for given URL.\n\n   .. method:: save(file_path)\n\n      Write a JSON representation of cookies into the file\n      at provided path.\n\n      .. versionchanged:: 3.14\n\n         Previously used pickle format. Now uses JSON for safe\n         serialization.\n\n      :param file_path: Path to file where cookies will be serialized,\n          :class:`str` or :class:`pathlib.Path` instance.\n\n   .. method:: load(file_path)\n\n      Load cookies from the file at provided path. Tries JSON format\n      first, then falls back to legacy pickle format (using a restricted\n      unpickler that only allows cookie-related types) for backward\n      compatibility with existing cookie files.\n\n      .. versionchanged:: 3.14\n\n         Now loads JSON format by default. Falls back to restricted\n         pickle for files saved by older versions.\n\n      :param file_path: Path to file from where cookies will be\n           imported, :class:`str` or :class:`pathlib.Path` instance.\n\n   .. method:: clear(predicate=None)\n\n      Removes all cookies from the jar if the predicate is ``None``. Otherwise remove only those :class:`~http.cookies.Morsel` that ``predicate(morsel)`` returns ``True``.\n\n      :param predicate: callable that gets :class:`~http.cookies.Morsel` as a parameter and returns ``True`` if this :class:`~http.cookies.Morsel` must be deleted from the jar.\n\n          .. versionadded:: 4.0\n\n   .. method:: clear_domain(domain)\n\n      Remove all cookies from the jar that belongs to the specified domain or its subdomains.\n\n      :param str domain: domain for which cookies must be deleted from the jar.\n\n      .. versionadded:: 4.0\n\n\n.. class:: DummyCookieJar(*, loop=None)\n   :canonical: aiohttp.cookiejar.DummyCookieJar\n\n   Dummy cookie jar which does not store cookies but ignores them.\n\n   Could be useful e.g. for web crawlers to iterate over Internet\n   without blowing up with saved cookies information.\n\n   To install dummy cookie jar pass it into session instance::\n\n      jar = aiohttp.DummyCookieJar()\n      session = aiohttp.ClientSession(cookie_jar=DummyCookieJar())\n\n\n.. class:: Fingerprint(digest)\n   :canonical: aiohttp.client_reqrep.Fingerprint\n\n   Fingerprint helper for checking SSL certificates by *SHA256* digest.\n\n   :param bytes digest: *SHA256* digest for certificate in DER-encoded\n                        binary form (see\n                        :meth:`ssl.SSLSocket.getpeercert`).\n\n   To check fingerprint pass the object into :meth:`ClientSession.get`\n   call, e.g.::\n\n      import hashlib\n\n      with open(path_to_cert, 'rb') as f:\n          digest = hashlib.sha256(f.read()).digest()\n\n      await session.get(url, ssl=aiohttp.Fingerprint(digest))\n\n   .. versionadded:: 3.0\n\n.. function:: set_zlib_backend(lib)\n   :canonical: aiohttp.compression_utils.set_zlib_backend\n\n   Sets the compression backend for zlib-based operations.\n\n   This function allows you to override the default zlib backend\n   used internally by passing a module that implements the standard\n   compression interface.\n\n   The module should implement at minimum the exact interface offered by the\n   latest version of zlib.\n\n   :param types.ModuleType lib: A module that implements the zlib-compatible compression API.\n\n   Example usage::\n\n      import zlib_ng.zlib_ng as zng\n      import aiohttp\n\n      aiohttp.set_zlib_backend(zng)\n\n   .. note:: aiohttp has been tested internally with :mod:`zlib`, :mod:`zlib_ng.zlib_ng`, and :mod:`isal.isal_zlib`.\n\n   .. versionadded:: 3.12\n\nFormData\n^^^^^^^^\n\nA :class:`FormData` object contains the form data and also handles\nencoding it into a body that is either ``multipart/form-data`` or\n``application/x-www-form-urlencoded``. ``multipart/form-data`` is\nused if at least one field is an :class:`io.IOBase` object or was\nadded with at least one optional argument to :meth:`add_field<aiohttp.FormData.add_field>`\n(``content_type``, ``filename``, or ``content_transfer_encoding``).\nOtherwise, ``application/x-www-form-urlencoded`` is used.\n\n:class:`FormData` instances are callable and return a :class:`aiohttp.payload.Payload`\non being called.\n\n.. class:: FormData(fields, quote_fields=True, charset=None)\n   :canonical: aiohttp.formdata.FormData\n\n   Helper class for multipart/form-data and application/x-www-form-urlencoded body generation.\n\n   :param fields: A container for the key/value pairs of this form.\n\n                  Possible types are:\n\n                  - :class:`dict`\n                  - :class:`tuple` or :class:`list`\n                  - :class:`io.IOBase`, e.g. a file-like object\n                  - :class:`multidict.MultiDict` or :class:`multidict.MultiDictProxy`\n\n                  If it is a :class:`tuple` or :class:`list`, it must be a valid argument\n                  for :meth:`add_fields<aiohttp.FormData.add_fields>`.\n\n                  For :class:`dict`, :class:`multidict.MultiDict`, and :class:`multidict.MultiDictProxy`,\n                  the keys and values must be valid `name` and `value` arguments to\n                  :meth:`add_field<aiohttp.FormData.add_field>`, respectively.\n\n   .. method:: add_field(name, value, content_type=None, filename=None,\\\n                         content_transfer_encoding=None)\n\n      Add a field to the form.\n\n      :param str name: Name of the field\n\n      :param value: Value of the field\n\n                    Possible types are:\n\n                    - :class:`str`\n                    - :class:`bytes`, :class:`bytearray`, or :class:`memoryview`\n                    - :class:`io.IOBase`, e.g. a file-like object\n\n      :param str content_type: The field's content-type header (optional)\n\n      :param str filename: The field's filename (optional)\n\n                           If this is not set and ``value`` is a :class:`bytes`, :class:`bytearray`,\n                           or :class:`memoryview` object, the `name` argument is used as the filename\n                           unless ``content_transfer_encoding`` is specified.\n\n                           If ``filename`` is not set and ``value`` is an :class:`io.IOBase`\n                           object, the filename is extracted from the object if possible.\n\n      :param str content_transfer_encoding: The field's content-transfer-encoding\n                                            header (optional)\n\n   .. method:: add_fields(fields)\n\n      Add one or more fields to the form.\n\n      :param fields: An iterable containing:\n\n                     - :class:`io.IOBase`, e.g. a file-like object\n                     - :class:`multidict.MultiDict` or :class:`multidict.MultiDictProxy`\n                     - :class:`tuple` or :class:`list` of length two, containing a name-value pair\n\nClient exceptions\n-----------------\n\nException hierarchy has been significantly modified in version\n2.0. aiohttp defines only exceptions that covers connection handling\nand server response misbehaviors.  For developer specific mistakes,\naiohttp uses python standard exceptions like :exc:`ValueError` or\n:exc:`TypeError`.\n\nReading a response content may raise a :exc:`ClientPayloadError`\nexception. This exception indicates errors specific to the payload\nencoding. Such as invalid compressed data, malformed chunked-encoded\nchunks or not enough data that satisfy the content-length header.\n\nAll exceptions are available as members of *aiohttp* module.\n\n.. exception:: ClientError\n   :canonical: aiohttp.client_exceptions.ClientError\n\n   Base class for all client specific exceptions.\n\n   Derived from :exc:`Exception`\n\n\n.. class:: ClientPayloadError\n   :canonical: aiohttp.client_exceptions.ClientPayloadError\n\n   This exception can only be raised while reading the response\n   payload if one of these errors occurs:\n\n   1. invalid compression\n   2. malformed chunked encoding\n   3. not enough data that satisfy ``Content-Length`` HTTP header.\n\n   Derived from :exc:`ClientError`\n\n.. exception:: InvalidURL\n   :canonical: aiohttp.client_exceptions.InvalidURL\n\n   URL used for fetching is malformed, e.g. it does not contain host\n   part.\n\n   Derived from :exc:`ClientError` and :exc:`ValueError`\n\n   .. attribute:: url\n\n      Invalid URL, :class:`yarl.URL` instance.\n\n    .. attribute:: description\n\n      Invalid URL description, :class:`str` instance or :data:`None`.\n\n.. exception:: InvalidUrlClientError\n   :canonical: aiohttp.client_exceptions.InvalidUrlClientError\n\n   Base class for all errors related to client url.\n\n   Derived from :exc:`InvalidURL`\n\n.. exception:: RedirectClientError\n   :canonical: aiohttp.client_exceptions.RedirectClientError\n\n   Base class for all errors related to client redirects.\n\n   Derived from :exc:`ClientError`\n\n.. exception:: NonHttpUrlClientError\n   :canonical: aiohttp.client_exceptions.NonHttpUrlClientError\n\n   Base class for all errors related to non http client urls.\n\n   Derived from :exc:`ClientError`\n\n.. exception:: InvalidUrlRedirectClientError\n   :canonical: aiohttp.client_exceptions.InvalidUrlRedirectClientError\n\n   Redirect URL is malformed, e.g. it does not contain host part.\n\n   Derived from :exc:`InvalidUrlClientError` and :exc:`RedirectClientError`\n\n.. exception:: NonHttpUrlRedirectClientError\n   :canonical: aiohttp.client_exceptions.NonHttpUrlRedirectClientError\n\n   Redirect URL does not contain http schema.\n\n   Derived from :exc:`RedirectClientError` and :exc:`NonHttpUrlClientError`\n\nResponse errors\n^^^^^^^^^^^^^^^\n\n.. exception:: ClientResponseError\n   :canonical: aiohttp.client_exceptions.ClientResponseError\n\n   These exceptions could happen after we get response from server.\n\n   Derived from :exc:`ClientError`\n\n   .. attribute:: request_info\n\n      Instance of :class:`RequestInfo` object, contains information\n      about request.\n\n   .. attribute:: status\n\n      HTTP status code of response (:class:`int`), e.g. ``400``.\n\n   .. attribute:: message\n\n      Message of response (:class:`str`), e.g. ``\"OK\"``.\n\n   .. attribute:: headers\n\n      Headers in response, a list of pairs.\n\n   .. attribute:: history\n\n      History from failed response, if available, else empty tuple.\n\n      A :class:`tuple` of :class:`ClientResponse` objects used for\n      handle redirection responses.\n\n   .. attribute:: code\n\n      HTTP status code of response (:class:`int`), e.g. ``400``.\n\n      .. deprecated:: 3.1\n\n\n.. class:: ContentTypeError\n   :canonical: aiohttp.client_exceptions.ContentTypeError\n\n   Invalid content type.\n\n   Derived from :exc:`ClientResponseError`\n\n   .. versionadded:: 2.3\n\n\n.. class:: TooManyRedirects\n   :canonical: aiohttp.client_exceptions.TooManyRedirects\n\n   Client was redirected too many times.\n\n   Maximum number of redirects can be configured by using\n   parameter ``max_redirects`` in :meth:`request<aiohttp.ClientSession.request>`.\n\n   Derived from :exc:`ClientResponseError`\n\n   .. versionadded:: 3.2\n\n\n.. class:: WSServerHandshakeError\n   :canonical: aiohttp.client_exceptions.WSServerHandshakeError\n\n   Web socket server response error.\n\n   Derived from :exc:`ClientResponseError`\n\n.. exception:: WSMessageTypeError\n   :canonical: aiohttp.client_exceptions.WSMessageTypeError\n\n   Received WebSocket message of unexpected type\n\n   Derived from :exc:`TypeError`\n\nConnection errors\n^^^^^^^^^^^^^^^^^\n\n.. class:: ClientConnectionError\n   :canonical: aiohttp.client_exceptions.ClientConnectionError\n\n   These exceptions related to low-level connection problems.\n\n   Derived from :exc:`ClientError`\n\n.. class:: ClientConnectionResetError\n   :canonical: aiohttp.client_exceptions.ClientConnectionResetError\n\n   Derived from :exc:`ClientConnectionError` and :exc:`ConnectionResetError`\n\n.. class:: ClientOSError\n   :canonical: aiohttp.client_exceptions.ClientOSError\n\n   Subset of connection errors that are initiated by an :exc:`OSError`\n   exception.\n\n   Derived from :exc:`ClientConnectionError` and :exc:`OSError`\n\n.. class:: ClientConnectorError\n   :canonical: aiohttp.client_exceptions.ClientConnectorError\n\n   Connector related exceptions.\n\n   Derived from :exc:`ClientOSError`\n\n.. class:: ClientConnectorDNSError\n   :canonical: aiohttp.client_exceptions.ClientConnectorDNSError\n\n   DNS resolution error.\n\n   Derived from :exc:`ClientConnectorError`\n\n.. class:: ClientProxyConnectionError\n   :canonical: aiohttp.client_exceptions.ClientProxyConnectionError\n\n   Derived from :exc:`ClientConnectorError`\n\n.. class:: ClientSSLError\n   :canonical: aiohttp.client_exceptions.ClientSSLError\n\n   Derived from :exc:`ClientConnectorError`\n\n.. class:: ClientConnectorSSLError\n   :canonical: aiohttp.client_exceptions.ClientConnectorSSLError\n\n   Response ssl error.\n\n   Derived from :exc:`ClientSSLError` and :exc:`ssl.SSLError`\n\n.. class:: ClientConnectorCertificateError\n   :canonical: aiohttp.client_exceptions.ClientConnectorCertificateError\n\n   Response certificate error.\n\n   Derived from :exc:`ClientSSLError` and :exc:`ssl.CertificateError`\n\n.. class:: UnixClientConnectorError\n   :canonical: aiohttp.client_exceptions.UnixClientConnectorError\n\n   Derived from :exc:`ClientConnectorError`\n\n.. class:: ServerConnectionError\n   :canonical: aiohttp.client_exceptions.ServerConnectionError\n\n   Derived from :exc:`ClientConnectionError`\n\n.. class:: ServerDisconnectedError\n   :canonical: aiohttp.client_exceptions.ServerDisconnectedError\n\n   Server disconnected.\n\n   Derived from :exc:`~aiohttp.ServerConnectionError`\n\n   .. attribute:: message\n\n      Partially parsed HTTP message (optional).\n\n\n.. class:: ServerFingerprintMismatch\n   :canonical: aiohttp.client_exceptions.ServerFingerprintMismatch\n\n   Server fingerprint mismatch.\n\n   Derived from :exc:`ServerConnectionError`\n\n.. class:: ServerTimeoutError\n   :canonical: aiohttp.client_exceptions.ServerTimeoutError\n\n   Server operation timeout: read timeout, etc.\n\n   To catch all timeouts, including the ``total`` timeout, use\n   :exc:`asyncio.TimeoutError`.\n\n   Derived from :exc:`ServerConnectionError` and :exc:`asyncio.TimeoutError`\n\n.. class:: ConnectionTimeoutError\n   :canonical: aiohttp.client_exceptions.ConnectionTimeoutError\n\n   Connection timeout on ``connect`` and ``sock_connect`` timeouts.\n\n   Derived from :exc:`ServerTimeoutError`\n\n.. class:: SocketTimeoutError\n   :canonical: aiohttp.client_exceptions.SocketTimeoutError\n\n   Reading from socket timeout on ``sock_read`` timeout.\n\n   Derived from :exc:`ServerTimeoutError`\n\nHierarchy of exceptions\n^^^^^^^^^^^^^^^^^^^^^^^\n\n* :exc:`ClientError`\n\n  * :exc:`ClientConnectionError`\n\n    * :exc:`ClientConnectionResetError`\n\n    * :exc:`ClientOSError`\n\n      * :exc:`ClientConnectorError`\n\n        * :exc:`ClientProxyConnectionError`\n\n        * :exc:`ClientConnectorDNSError`\n\n        * :exc:`ClientSSLError`\n\n          * :exc:`ClientConnectorCertificateError`\n\n          * :exc:`ClientConnectorSSLError`\n\n        * :exc:`UnixClientConnectorError`\n\n    * :exc:`ServerConnectionError`\n\n      * :exc:`ServerDisconnectedError`\n\n      * :exc:`ServerFingerprintMismatch`\n\n      * :exc:`ServerTimeoutError`\n\n        * :exc:`ConnectionTimeoutError`\n\n        * :exc:`SocketTimeoutError`\n\n  * :exc:`ClientPayloadError`\n\n  * :exc:`ClientResponseError`\n\n    * :exc:`~aiohttp.ClientHttpProxyError`\n\n    * :exc:`ContentTypeError`\n\n    * :exc:`TooManyRedirects`\n\n    * :exc:`WSServerHandshakeError`\n\n  * :exc:`InvalidURL`\n\n    * :exc:`InvalidUrlClientError`\n\n      * :exc:`InvalidUrlRedirectClientError`\n\n  * :exc:`NonHttpUrlClientError`\n\n    * :exc:`NonHttpUrlRedirectClientError`\n\n  * :exc:`RedirectClientError`\n\n    * :exc:`InvalidUrlRedirectClientError`\n\n    * :exc:`NonHttpUrlRedirectClientError`\n\n\nClient Types\n------------\n\n.. type:: ClientMiddlewareType\n\n   Type alias for client middleware functions. Middleware functions must have this signature::\n\n      Callable[\n          [ClientRequest, ClientHandlerType],\n          Awaitable[ClientResponse]\n      ]\n\n.. type:: ClientHandlerType\n\n   Type alias for client request handler functions::\n\n      Callable[[ClientRequest], Awaitable[ClientResponse]]\n"
  },
  {
    "path": "docs/code/client_middleware_cookbook.py",
    "content": "\"\"\"This is a collection of semi-complete examples that get included into the cookbook page.\"\"\"\n\nimport asyncio\nimport logging\nimport time\nfrom collections.abc import AsyncIterator, Sequence\nfrom contextlib import asynccontextmanager, suppress\n\nfrom aiohttp import (\n    ClientError,\n    ClientHandlerType,\n    ClientRequest,\n    ClientResponse,\n    ClientSession,\n    TCPConnector,\n)\nfrom aiohttp.abc import ResolveResult\nfrom aiohttp.tracing import Trace\n\n\nclass SSRFError(ClientError):\n    \"\"\"A request was made to a blacklisted host.\"\"\"\n\n\nasync def retry_middleware(\n    req: ClientRequest, handler: ClientHandlerType\n) -> ClientResponse:\n    for _ in range(3):  # Try up to 3 times\n        resp = await handler(req)\n        if resp.ok:\n            return resp\n    return resp  # type: ignore[possibly-undefined]\n\n\nasync def api_logging_middleware(\n    req: ClientRequest, handler: ClientHandlerType\n) -> ClientResponse:\n    # We use middlewares=() to avoid infinite recursion.\n    async with req.session.post(\"/log\", data=req.url.host, middlewares=()) as resp:\n        if not resp.ok:\n            logging.warning(\"Log endpoint failed\")\n\n    return await handler(req)\n\n\nclass TokenRefresh401Middleware:\n    def __init__(self, refresh_token: str, access_token: str):\n        self.access_token = access_token\n        self.refresh_token = refresh_token\n        self.lock = asyncio.Lock()\n\n    async def __call__(\n        self, req: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        for _ in range(2):  # Retry at most one time\n            token = self.access_token\n            req.headers[\"Authorization\"] = f\"Bearer {token}\"\n            resp = await handler(req)\n            if resp.status != 401:\n                return resp\n            async with self.lock:\n                if token != self.access_token:  # Already refreshed\n                    continue\n                url = \"https://api.example/refresh\"\n                async with req.session.post(url, data=self.refresh_token) as resp:\n                    # Add error handling as needed\n                    data = await resp.json()\n                    self.access_token = data[\"access_token\"]\n        return resp  # type: ignore[possibly-undefined]\n\n\nclass TokenRefreshExpiryMiddleware:\n    def __init__(self, refresh_token: str):\n        self.access_token = \"\"\n        self.expires_at = 0\n        self.refresh_token = refresh_token\n        self.lock = asyncio.Lock()\n\n    async def __call__(\n        self, req: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        if self.expires_at <= time.time():\n            token = self.access_token\n            async with self.lock:\n                if token == self.access_token:  # Still not refreshed\n                    url = \"https://api.example/refresh\"\n                    async with req.session.post(url, data=self.refresh_token) as resp:\n                        # Add error handling as needed\n                        data = await resp.json()\n                        self.access_token = data[\"access_token\"]\n                        self.expires_at = data[\"expires_at\"]\n\n        req.headers[\"Authorization\"] = f\"Bearer {self.access_token}\"\n        return await handler(req)\n\n\nasync def token_refresh_preemptively_example() -> None:\n    async def set_token(session: ClientSession, event: asyncio.Event) -> None:\n        while True:\n            async with session.post(\"/refresh\") as resp:\n                token = await resp.json()\n                session.headers[\"Authorization\"] = f\"Bearer {token['auth']}\"\n                event.set()\n                await asyncio.sleep(token[\"valid_duration\"])\n\n    @asynccontextmanager\n    async def auto_refresh_client() -> AsyncIterator[ClientSession]:\n        async with ClientSession() as session:\n            ready = asyncio.Event()\n            t = asyncio.create_task(set_token(session, ready))\n            await ready.wait()\n            yield session\n            t.cancel()\n            with suppress(asyncio.CancelledError):\n                await t\n\n    async with auto_refresh_client() as sess:\n        ...\n\n\nasync def ssrf_middleware(\n    req: ClientRequest, handler: ClientHandlerType\n) -> ClientResponse:\n    # WARNING: This is a simplified example for demonstration purposes only.\n    # A complete implementation should also check:\n    # - IPv6 loopback (::1)\n    # - Private IP ranges (10.x.x.x, 192.168.x.x, 172.16-31.x.x)\n    # - Link-local addresses (169.254.x.x, fe80::/10)\n    # - Other internal hostnames and aliases\n    if req.url.host in {\"127.0.0.1\", \"localhost\"}:\n        raise SSRFError(req.url.host)\n    return await handler(req)\n\n\nclass SSRFConnector(TCPConnector):\n    async def _resolve_host(\n        self, host: str, port: int, traces: Sequence[Trace] | None = None\n    ) -> list[ResolveResult]:\n        res = await super()._resolve_host(host, port, traces)\n        # WARNING: This is a simplified example - should also check ::1, private ranges, etc.\n        if any(r[\"host\"] in {\"127.0.0.1\"} for r in res):\n            raise SSRFError()\n        return res\n"
  },
  {
    "path": "docs/conf.py",
    "content": "#!/usr/bin/env python3\n#\n# aiohttp documentation build configuration file, created by\n# sphinx-quickstart on Wed Mar  5 12:35:35 2014.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport os\nimport re\nfrom pathlib import Path\n\nPROJECT_ROOT_DIR = Path(__file__).parents[1].resolve()\nIS_RELEASE_ON_RTD = (\n    os.getenv(\"READTHEDOCS\", \"False\") == \"True\"\n    and os.environ[\"READTHEDOCS_VERSION_TYPE\"] == \"tag\"\n)\nif IS_RELEASE_ON_RTD:\n    tags.add(\"is_release\")\n\n_docs_path = os.path.dirname(__file__)\n_version_path = os.path.abspath(\n    os.path.join(_docs_path, \"..\", \"aiohttp\", \"__init__.py\")\n)\nwith open(_version_path, encoding=\"latin1\") as fp:\n    try:\n        _version_info = re.search(\n            r'^__version__ = \"'\n            r\"(?P<major>\\d+)\"\n            r\"\\.(?P<minor>\\d+)\"\n            r\"\\.(?P<patch>\\d+)\"\n            r'(?P<tag>.*)?\"$',\n            fp.read(),\n            re.M,\n        ).groupdict()\n    except IndexError:\n        raise RuntimeError(\"Unable to determine version.\")\n\n\n# -- General configuration ------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n    # stdlib-party extensions:\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.extlinks\",\n    \"sphinx.ext.graphviz\",\n    \"sphinx.ext.intersphinx\",\n    \"sphinx.ext.viewcode\",\n    # Third-party extensions:\n    \"sphinxcontrib.towncrier.ext\",  # provides `towncrier-draft-entries` directive\n]\n\n\ntry:\n    import sphinxcontrib.spelling  # noqa\n\n    extensions.append(\"sphinxcontrib.spelling\")\nexcept ImportError:\n    pass\n\n\nintersphinx_mapping = {\n    \"pytest\": (\"http://docs.pytest.org/en/latest/\", None),\n    \"python\": (\"http://docs.python.org/3\", None),\n    \"multidict\": (\"https://multidict.readthedocs.io/en/stable/\", None),\n    \"propcache\": (\"https://propcache.aio-libs.org/en/stable\", None),\n    \"yarl\": (\"https://yarl.readthedocs.io/en/stable/\", None),\n    \"aiosignal\": (\"https://aiosignal.readthedocs.io/en/stable/\", None),\n    \"aiohttpjinja2\": (\"https://aiohttp-jinja2.readthedocs.io/en/stable/\", None),\n    \"aiohttpremotes\": (\"https://aiohttp-remotes.readthedocs.io/en/stable/\", None),\n    \"aiohttpsession\": (\"https://aiohttp-session.readthedocs.io/en/stable/\", None),\n    \"aiohttpdemos\": (\"https://aiohttp-demos.readthedocs.io/en/latest/\", None),\n    \"aiojobs\": (\"https://aiojobs.readthedocs.io/en/stable/\", None),\n    \"aiohappyeyeballs\": (\"https://aiohappyeyeballs.readthedocs.io/en/latest/\", None),\n    \"isal\": (\"https://python-isal.readthedocs.io/en/stable/\", None),\n    \"zlib_ng\": (\"https://python-zlib-ng.readthedocs.io/en/stable/\", None),\n}\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# The suffix of source filenames.\nsource_suffix = \".rst\"\n\n# The encoding of source files.\n# source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# -- Project information -----------------------------------------------------\n\ngithub_url = \"https://github.com\"\ngithub_repo_org = \"aio-libs\"\ngithub_repo_name = \"aiohttp\"\ngithub_repo_slug = f\"{github_repo_org}/{github_repo_name}\"\ngithub_repo_url = f\"{github_url}/{github_repo_slug}\"\ngithub_sponsors_url = f\"{github_url}/sponsors\"\n\nproject = github_repo_name\ncopyright = f\"{project} contributors\"\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = \"{major}.{minor}\".format(**_version_info)\n# The full version, including alpha/beta/rc tags.\nrelease = \"{major}.{minor}.{patch}{tag}\".format(**_version_info)\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n# language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n# today = ''\n# Else, today_fmt is used as the format for a strftime call.\n# today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = [\"_build\"]\n\n# The reST default role (used for this markup: `text`) to use for all\n# documents.\n# default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n# add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n# add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n# show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\n# pygments_style = 'sphinx'\n\n# The default language to highlight source code in.\nhighlight_language = \"python3\"\n\n# A list of ignored prefixes for module index sorting.\n# modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built documents.\n# keep_warnings = False\n\n\n# -- Extension configuration -------------------------------------------------\n\n# -- Options for extlinks extension ---------------------------------------\nextlinks = {\n    \"issue\": (f\"{github_repo_url}/issues/%s\", \"#%s\"),\n    \"pr\": (f\"{github_repo_url}/pull/%s\", \"PR #%s\"),\n    \"commit\": (f\"{github_repo_url}/commit/%s\", \"%s\"),\n    \"gh\": (f\"{github_url}/%s\", \"GitHub: %s\"),\n    \"user\": (f\"{github_sponsors_url}/%s\", \"@%s\"),\n}\n\n# -- Options for HTML output ----------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = \"aiohttp_theme\"\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\nhtml_theme_options = {\n    \"description\": \"Async HTTP client/server for asyncio and Python\",\n    \"canonical_url\": \"http://docs.aiohttp.org/en/stable/\",\n    \"github_user\": github_repo_org,\n    \"github_repo\": github_repo_name,\n    \"github_button\": True,\n    \"github_type\": \"star\",\n    \"github_banner\": True,\n    \"badges\": [\n        {\n            \"image\": f\"{github_repo_url}/workflows/CI/badge.svg\",\n            \"target\": f\"{github_repo_url}/actions?query=workflow%3ACI\",\n            \"height\": \"20\",\n            \"alt\": \"Azure Pipelines CI status\",\n        },\n        {\n            \"image\": f\"https://codecov.io/github/{github_repo_slug}/coverage.svg?branch=master\",\n            \"target\": f\"https://codecov.io/github/{github_repo_slug}\",\n            \"height\": \"20\",\n            \"alt\": \"Code coverage status\",\n        },\n        {\n            \"image\": f\"https://badge.fury.io/py/{project}.svg\",\n            \"target\": f\"https://badge.fury.io/py/{project}\",\n            \"height\": \"20\",\n            \"alt\": \"Latest PyPI package version\",\n        },\n        {\n            \"image\": \"https://badges.gitter.im/Join%20Chat.svg\",\n            \"target\": f\"https://gitter.im/{github_repo_org}/Lobby\",\n            \"height\": \"20\",\n            \"alt\": \"Chat on Gitter\",\n        },\n    ],\n}\n\nhtml_css_files = [\n    \"css/logo-adjustments.css\",\n]\n\n# Add any paths that contain custom themes here, relative to this directory.\n# html_theme_path = [alabaster.get_path()]\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n# html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n# html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\nhtml_logo = \"aiohttp-plain.svg\"\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\nhtml_favicon = \"favicon.ico\"\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = [\"_static\"]\n\n# Add any extra paths that contain custom files (such as robots.txt or\n# .htaccess) here, relative to this directory. These files are copied\n# directly to the root of the documentation.\n# html_extra_path = []\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n# html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n# html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\nhtml_sidebars = {\n    \"**\": [\n        \"about.html\",\n        \"navigation.html\",\n        \"searchbox.html\",\n    ]\n}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n# html_additional_pages = {}\n\n# If false, no module index is generated.\n# html_domain_indices = True\n\n# If false, no index is generated.\n# html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n# html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n# html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n# html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n# html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n# html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n# html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = f\"{project}doc\"\n\n\n# -- Options for LaTeX output ---------------------------------------------\n\nlatex_elements = {\n    # The paper size ('letterpaper' or 'a4paper').\n    # 'papersize': 'letterpaper',\n    # The font size ('10pt', '11pt' or '12pt').\n    # 'pointsize': '10pt',\n    # Additional stuff for the LaTeX preamble.\n    # 'preamble': '',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n    (\n        \"index\",\n        f\"{project}.tex\",\n        f\"{project} Documentation\",\n        f\"{project} contributors\",\n        \"manual\",\n    ),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n# latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n# latex_use_parts = False\n\n# If true, show page references after internal links.\n# latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n# latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n# latex_appendices = []\n\n# If false, no module index is generated.\n# latex_domain_indices = True\n\n\n# -- Options for manual page output ---------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [(\"index\", project, f\"{project} Documentation\", [project], 1)]\n\n# If true, show URL addresses after external links.\n# man_show_urls = False\n\n\n# -- Options for Texinfo output -------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    (\n        \"index\",\n        project,\n        f\"{project} Documentation\",\n        \"Aiohttp contributors\",\n        project,\n        \"One line description of project.\",\n        \"Miscellaneous\",\n    ),\n]\n\n# Documents to append as an appendix to all manuals.\n# texinfo_appendices = []\n\n# If false, no module index is generated.\n# texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n# texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n# texinfo_no_detailmenu = False\n\n\n# -------------------------------------------------------------------------\nnitpicky = True\nnitpick_ignore = [\n    (\"py:mod\", \"aiohttp\"),  # undocumented, no `.. currentmodule:: aiohttp` in docs\n    (\"py:class\", \"aiohttp.SimpleCookie\"),  # undocumented\n    (\"py:class\", \"aiohttp.web.RequestHandler\"),  # undocumented\n    (\"py:class\", \"aiohttp.NamedPipeConnector\"),  # undocumented\n    (\"py:class\", \"aiohttp.protocol.HttpVersion\"),  # undocumented\n    (\"py:class\", \"HttpVersion\"),  # undocumented\n    (\"py:class\", \"aiohttp.payload.Payload\"),  # undocumented\n    (\"py:class\", \"Payload\"),  # undocumented\n    (\"py:class\", \"aiohttp.resolver.AsyncResolver\"),  # undocumented\n    (\"py:class\", \"aiohttp.resolver.ThreadedResolver\"),  # undocumented\n    (\"py:func\", \"aiohttp.ws_connect\"),  # undocumented\n    (\"py:meth\", \"start\"),  # undocumented\n    (\"py:exc\", \"aiohttp.ClientHttpProxyError\"),  # undocumented\n    (\"py:class\", \"asyncio.AbstractServer\"),  # undocumented\n    (\"py:mod\", \"aiohttp.test_tools\"),  # undocumented\n    (\"py:class\", \"list of pairs\"),  # undocumented\n    (\"py:class\", \"aiohttp.protocol.HttpVersion\"),  # undocumented\n    (\"py:meth\", \"aiohttp.ClientSession.request\"),  # undocumented\n    (\"py:class\", \"aiohttp.StreamWriter\"),  # undocumented\n    (\"py:attr\", \"aiohttp.StreamResponse.body\"),  # undocumented\n    (\"py:class\", \"aiohttp.payload.StringPayload\"),  # undocumented\n    (\"py:meth\", \"aiohttp.web.Application.copy\"),  # undocumented\n    (\"py:meth\", \"asyncio.AbstractEventLoop.create_server\"),  # undocumented\n    (\"py:data\", \"aiohttp.log.server_logger\"),  # undocumented\n    (\"py:data\", \"aiohttp.log.access_logger\"),  # undocumented\n    (\"py:data\", \"aiohttp.helpers.AccessLogger\"),  # undocumented\n    (\"py:attr\", \"helpers.AccessLogger.LOG_FORMAT\"),  # undocumented\n    (\"py:meth\", \"aiohttp.web.AbstractRoute.url\"),  # undocumented\n    (\"py:class\", \"aiohttp.web.MatchedSubAppResource\"),  # undocumented\n    (\"py:attr\", \"body\"),  # undocumented\n    (\"py:class\", \"socket.socket\"),  # undocumented\n    (\"py:func\", \"socket.socket\"),  # undocumented\n    (\"py:class\", \"socket.AddressFamily\"),  # undocumented\n    (\"py:obj\", \"logging.DEBUG\"),  # undocumented\n    (\"py:class\", \"aiohttp.abc.AbstractAsyncAccessLogger\"),  # undocumented\n    (\"py:meth\", \"aiohttp.web.Response.write_eof\"),  # undocumented\n    (\"py:meth\", \"aiohttp.payload.Payload.set_content_disposition\"),  # undocumented\n    (\"py:class\", \"cgi.FieldStorage\"),  # undocumented\n    (\"py:meth\", \"aiohttp.web.UrlDispatcher.register_resource\"),  # undocumented\n    (\"py:func\", \"aiohttp_debugtoolbar.setup\"),  # undocumented\n    (\"py:class\", \"socket.SocketKind\"),  # undocumented\n]\n\n# -- Options for towncrier_draft extension -----------------------------------\n\ntowncrier_draft_autoversion_mode = \"draft\"  # or: 'sphinx-version', 'sphinx-release'\ntowncrier_draft_include_empty = True\ntowncrier_draft_working_directory = PROJECT_ROOT_DIR\n# Not yet supported: towncrier_draft_config_path = 'pyproject.toml'  # relative to cwd\n"
  },
  {
    "path": "docs/contributing-admins.rst",
    "content": ":orphan:\n\nInstructions for aiohttp admins\n===============================\n\nThis page is intended to document certain processes for admins of the aiohttp repository.\nFor regular contributors, return to :doc:`contributing`.\n\n.. contents::\n   :local:\n\nCreating a new release\n----------------------\n\n.. note:: The example commands assume that ``origin`` refers to the ``aio-libs`` repository.\n\nTo create a new release:\n\n#. Start on the branch for the release you are planning (e.g. ``3.8`` for v3.8.6): ``git checkout 3.8 && git pull``\n#. Update the version number in ``__init__.py``.\n#. Run ``towncrier``.\n#. Check and cleanup the changes in ``CHANGES.rst``.\n#. Checkout a new branch: e.g. ``git checkout -b release/v3.8.6``\n#. Commit and create a PR. Verify the changelog and release notes look good on Read the Docs. Once PR is merged, continue.\n#. Go back to the release branch: e.g. ``git checkout 3.8 && git pull``\n#. Add a tag: e.g. ``git tag -a v3.8.6 -m 'Release 3.8.6' -s``\n#. Push the tag: e.g. ``git push origin v3.8.6``\n#. Monitor CI to ensure release process completes without errors.\n\nOnce released, we need to complete some cleanup steps (no further steps are needed for\nnon-stable releases though). If doing a patch release, we need to do the below steps twice,\nfirst merge into the newer release branch (e.g. 3.8 into 3.9) and then to master\n(e.g. 3.9 into master). If a new minor release, then just merge to master.\n\n#. Switch to target branch: e.g. ``git checkout 3.9 && git pull``\n#. Start a merge: e.g. ``git merge 3.8 --no-commit --no-ff --gpg-sign``\n#. Carefully review the changes and revert anything that should not be included (most\n   things outside the changelog).\n#. To ensure change fragments are cleaned up properly, run: ``python tools/cleanup_changes.py``\n#. Commit the merge (must be a normal merge commit, not squashed).\n#. Push the branch directly to Github (because a PR would get squashed). When pushing,\n   you may get a rejected message. Follow these steps to resolve:\n\n  #. Checkout to a new branch and push: e.g. ``git checkout -b do-not-merge && git push``\n  #. Open a *draft* PR with a title of 'DO NOT MERGE'.\n  #. Once the CI has completed on that branch, you should be able to switch back and push\n     the target branch (as tests have passed on the merge commit now).\n  #. This should automatically consider the PR merged and delete the temporary branch.\n\nBack on the original release branch, bump the version number and append ``.dev0`` in ``__init__.py``.\n\nPost the release announcement to social media:\n - BlueSky: https://bsky.app/profile/aiohttp.org and re-post to https://bsky.app/profile/aio-libs.org\n - Mastodon: https://fosstodon.org/@aiohttp and re-post to https://fosstodon.org/@aio_libs\n\nIf doing a minor release:\n\n#. Create a new release branch for future features to go to: e.g. ``git checkout -b 3.10 3.9 && git push``\n#. Update both ``target-branch`` backports for Dependabot to reference the new branch name in ``.github/dependabot.yml``.\n#. Delete the older backport label (e.g. backport-3.8): https://github.com/aio-libs/aiohttp/labels\n#. Add a new backport label (e.g. backport-3.10).\n"
  },
  {
    "path": "docs/contributing.rst",
    "content": ".. _aiohttp-contributing:\n\nContributing\n============\n\n(:doc:`contributing-admins`)\n\nInstructions for contributors\n-----------------------------\n\nIn order to make a clone of the GitHub_ repo: open the link and press the \"Fork\" button on the upper-right menu of the web page.\n\nI hope everybody knows how to work with git and github nowadays :)\n\nWorkflow is pretty straightforward:\n\n  0. Make sure you are reading the latest version of this document.\n     It can be found in the GitHub_ repo in the ``docs`` subdirectory.\n\n  1. Clone the GitHub_ repo using the ``--recurse-submodules`` argument\n\n  2. Setup your machine with the required development environment\n\n  3. Make a change\n\n  4. Make sure all tests passed\n\n  5. Add a file into the ``CHANGES`` folder (see `Changelog update`_ for how).\n\n  6. Commit changes to your own aiohttp clone\n\n  7. Make a pull request from the github page of your clone against the master branch\n\n  8. Optionally make backport Pull Request(s) for landing a bug fix into released aiohttp versions.\n\n.. note::\n\n   The project uses *Squash-and-Merge* strategy for *GitHub Merge* button.\n\n   Basically it means that there is **no need to rebase** a Pull Request against\n   *master* branch. Just ``git merge`` *master* into your working copy (a fork) if\n   needed. The Pull Request is automatically squashed into the single commit\n   once the PR is accepted.\n\n.. note::\n\n   GitHub issue and pull request threads are automatically locked when there has\n   not been any recent activity for one year.  Please open a `new issue\n   <https://github.com/aio-libs/aiohttp/issues/new>`_ for related bugs.\n\n   If you feel like there are important points in the locked discussions,\n   please include those excerpts into that new issue.\n\n\nPreconditions for running aiohttp test suite\n--------------------------------------------\n\nWe expect you to use a python virtual environment to run our tests.\n\nThere are several ways to make a virtual environment.\n\nIf you like to use *virtualenv* please run:\n\n.. code-block:: shell\n\n   $ cd aiohttp\n   $ virtualenv --python=`which python3` venv\n   $ . venv/bin/activate\n\nFor standard python *venv*:\n\n.. code-block:: shell\n\n   $ cd aiohttp\n   $ python3 -m venv venv\n   $ . venv/bin/activate\n\nFor *virtualenvwrapper*:\n\n.. code-block:: shell\n\n   $ cd aiohttp\n   $ mkvirtualenv --python=`which python3` aiohttp\n\nThere are other tools like *pyvenv* but you know the rule of thumb now: create a python3 virtual environment and activate it.\n\nAfter that please install libraries required for development:\n\n.. code-block:: shell\n\n   $ make install-dev\n\n.. note::\n\n  For now, the development tooling depends on ``make`` and assumes an Unix OS If you wish to contribute to aiohttp from a Windows machine, the easiest way is probably to `configure the WSL <https://docs.microsoft.com/en-us/windows/wsl/install-win10>`_ so you can use the same instructions. If it's not possible for you or if it doesn't work, please contact us so we can find a solution together.\n\nInstall pre-commit hooks:\n\n.. code-block:: shell\n\n   $ pre-commit install\n\n.. warning::\n\n  If you plan to use temporary ``print()``, ``pdb`` or ``ipdb`` within the test suite, execute it with ``-s``:\n\n  .. code-block:: shell\n\n     $ pytest tests -s\n\n  in order to run the tests without output capturing.\n\nCongratulations, you are ready to run the test suite!\n\n.. include:: ../vendor/README.rst\n\n\nRun autoformatter\n-----------------\n\nThe project uses black_ + isort_ formatters to keep the source code style.\nPlease run `make fmt` after every change before starting tests.\n\n  .. code-block:: shell\n\n     $ make fmt\n\n\nRun aiohttp test suite\n----------------------\n\nAfter all the preconditions are met you can run tests typing the next\ncommand:\n\n.. code-block:: shell\n\n   $ make test\n\nThe command at first will run the *linters* (sorry, we don't accept\npull requests with pyflakes, black, isort, or mypy errors).\n\nOn *lint* success the tests will be run.\n\nPlease take a look on the produced output.\n\nAny extra texts (print statements and so on) should be removed.\n\nCode coverage\n-------------\n\nWe use *codecov.io* as an indispensable tool for analyzing our coverage\nresults. Visit https://codecov.io/gh/aio-libs/aiohttp to see coverage\nreports for the master branch, history, pull requests etc.\n\nWe'll use an example from a real PR to demonstrate how we use this.\nOnce the tests run in a PR, you'll see a comment posted by *codecov*.\nThe most important thing to check here is whether there are any new\nmissed or partial lines in the report:\n\n.. image:: _static/img/contributing-cov-comment.svg\n\nHere, the PR has introduced 1 miss and 2 partials. Now we\nclick the link in the comment header to open the full report:\n\n.. image:: _static/img/contributing-cov-header.svg\n   :alt: Codecov report\n\nNow, if we look through the diff under 'Files changed' we find one of\nour partials:\n\n.. image:: _static/img/contributing-cov-partial.svg\n   :alt: A while loop with partial coverage.\n\nIn this case, the while loop is never skipped in our tests. This is\nprobably not worth writing a test for (and may be a situation that is\nimpossible to trigger anyway), so we leave this alone.\n\nWe're still missing a partial and a miss, so we switch to the\n'Indirect changes' tab and take a look through the diff there. This\ntime we find the remaining 2 lines:\n\n.. image:: _static/img/contributing-cov-miss.svg\n   :alt: An if statement that isn't covered anymore.\n\nAfter reviewing the PR, we find that this code is no longer needed as\nthe changes mean that this method will never be called under those\nconditions. Thanks to this report, we were able to remove some\nredundant code from a performance-critical part of our codebase (this\ncheck would have been run, probably multiple times, for every single\nincoming request).\n\n.. tip::\n   Sometimes the diff on *codecov.io* doesn't make sense. This is usually\n   caused by the branch being out of sync with master. Try merging\n   master into the branch and it will likely fix the issue. Failing\n   that, try checking coverage locally as described in the next section.\n\nOther tools\n-----------\n\nThe browser extension https://docs.codecov.io/docs/browser-extension\nis also a useful tool for analyzing the coverage directly from *Files\nChanged* tab on the *GitHub Pull Request* review page.\n\n\nYou can also produce coverage reports locally with ``make cov-dev``\nor just adding ``--cov-report=html`` to ``pytest``.\n\nThis will run the test suite and collect coverage information. Once\nfinished, coverage results can be view by opening:\n```console\n$ python -m webbrowser -n file://\"$(pwd)\"/htmlcov/index.html\n```\n\nDocumentation\n-------------\n\nWe encourage documentation improvements.\n\nPlease before making a Pull Request about documentation changes run:\n\n.. code-block:: shell\n\n   $ make doc\n\nOnce it finishes it will output the index html page\n``open file:///.../aiohttp/docs/_build/html/index.html``.\n\nGo to the link and make sure your doc changes looks good.\n\nSpell checking\n--------------\n\nWe use ``pyenchant`` and ``sphinxcontrib-spelling`` for running spell\nchecker for documentation:\n\n.. code-block:: shell\n\n   $ make doc-spelling\n\nUnfortunately there are problems with running spell checker on MacOS X.\n\nTo run spell checker on Linux box you should install it first:\n\n.. code-block:: shell\n\n   $ sudo apt-get install enchant\n   $ pip install sphinxcontrib-spelling\n\n\nPreparing a pull request\n------------------------\n\nWhen making a pull request, please include a short summary of the changes\nand a reference to any issue tickets that the PR is intended to solve.\nAll PRs with code changes should include tests. All changes should\ninclude a changelog entry.\n\n\nChangelog update\n----------------\n\n.. include:: ../CHANGES/README.rst\n\n\nMaking a pull request\n---------------------\n\nAfter finishing all steps make a GitHub_ Pull Request with *master* base branch.\n\n\nBackporting\n-----------\n\nAll Pull Requests are created against *master* git branch.\n\nIf the Pull Request is not a new functionality but bug fixing\n*backport* to maintenance branch would be desirable.\n\n*aiohttp* project committer may ask for making a *backport* of the PR\ninto maintained branch(es), in this case he or she adds a github label\nlike *needs backport to 3.1*.\n\n*Backporting* is performed *after* main PR merging into master.\n Please do the following steps:\n\n1. Find *Pull Request's commit* for cherry-picking.\n\n   *aiohttp* does *squashing* PRs on merging, so open your PR page on\n   github and scroll down to message like ``asvetlov merged commit\n   f7b8921 into master 9 days ago``.  ``f7b8921`` is the required commit number.\n\n2. Run `cherry_picker\n   <https://github.com/python/core-workflow/tree/master/cherry_picker>`_\n   tool for making backport PR (the tool is already pre-installed from\n   ``./requirements/dev.txt``), e.g. ``cherry_picker f7b8921 3.1``.\n\n3. In case of conflicts fix them and continue cherry-picking by\n   ``cherry_picker --continue``.\n\n   ``cherry_picker --abort`` stops the process.\n\n   ``cherry_picker --status`` shows current cherry-picking status\n   (like ``git status``)\n\n4. After all conflicts are done the tool opens a New Pull Request page\n   in a browser with pre-filed information.  Create a backport Pull\n   Request and wait for review/merging.\n\n5. *aiohttp* *committer* should remove *backport Git label* after\n   merging the backport.\n\nHow to become an aiohttp committer\n----------------------------------\n\nContribute!\n\nThe easiest way is providing Pull Requests for issues in our bug\ntracker.  But if you have a great idea for the library improvement\n-- please make an issue and Pull Request.\n\n\n\nThe rules for committers are simple:\n\n1. No wild commits! Everything should go through PRs.\n2. Take a part in reviews. It's very important part of maintainer's activity.\n3. Pickup issues created by others, especially if they are simple.\n4. Keep test suite comprehensive. In practice it means leveling up\n   coverage. 97% is not bad but we wish to have 100% someday. Well, 99%\n   is good target too.\n5. Don't hesitate to improve our docs. Documentation is a very important\n   thing, it's the key for project success. The documentation should\n   not only cover our public API but help newbies to start using the\n   project and shed a light on non-obvious gotchas.\n\n\n\nAfter positive answer aiohttp committer creates an issue on github\nwith the proposal for nomination.  If the proposal will collect only\npositive votes and no strong objection -- you'll be a new member in\nour team.\n\n\n.. _GitHub: https://github.com/aio-libs/aiohttp\n\n.. _ipdb: https://pypi.python.org/pypi/ipdb\n\n.. _black: https://pypi.python.org/pypi/black\n\n.. _isort: https://pypi.python.org/pypi/isort\n"
  },
  {
    "path": "docs/deployment.rst",
    "content": ".. _aiohttp-deployment:\n\n=================\nServer Deployment\n=================\n\nThere are several options for aiohttp server deployment:\n\n* Standalone server\n\n* Running a pool of backend servers behind of :term:`nginx`, HAProxy\n  or other *reverse proxy server*\n\n* Using :term:`gunicorn` behind of *reverse proxy*\n\nEvery method has own benefits and disadvantages.\n\n\n.. _aiohttp-deployment-standalone:\n\nStandalone\n==========\n\nJust call :func:`aiohttp.web.run_app` function passing\n:class:`aiohttp.web.Application` instance.\n\n\nThe method is very simple and could be the best solution in some\ntrivial cases. But it does not utilize all CPU cores.\n\nFor running multiple aiohttp server instances use *reverse proxies*.\n\n.. _aiohttp-deployment-nginx-supervisord:\n\nNginx+supervisord\n=================\n\nRunning aiohttp servers behind :term:`nginx` makes several advantages.\n\nFirst, nginx is the perfect frontend server. It may prevent many\nattacks based on malformed http protocol etc.\n\nSecond, running several aiohttp instances behind nginx allows to\nutilize all CPU cores.\n\nThird, nginx serves static files much faster than built-in aiohttp\nstatic file support.\n\nBut this way requires more complex configuration.\n\nNginx configuration\n--------------------\n\nHere is short example of an Nginx configuration file.\nIt does not cover all available Nginx options.\n\nFor full details, read `Nginx tutorial\n<https://www.nginx.com/resources/admin-guide/>`_ and `official Nginx\ndocumentation\n<http://nginx.org/en/docs/http/ngx_http_proxy_module.html>`_.\n\nFirst configure HTTP server itself:\n\n.. code-block:: nginx\n\n   http {\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_redirect off;\n         proxy_buffering off;\n         proxy_pass http://aiohttp;\n       }\n\n       location /static {\n         # path for static files\n         root /path/to/app/static;\n       }\n\n     }\n   }\n\nThis config listens on port ``80`` for a server named ``example.com``\nand redirects everything to the ``aiohttp`` backend group.\n\nAlso it serves static files from ``/path/to/app/static`` path as\n``example.com/static``.\n\nNext we need to configure *aiohttp upstream group*:\n\n.. code-block:: nginx\n\n   http {\n     upstream aiohttp {\n       # fail_timeout=0 means we always retry an upstream even if it failed\n       # to return a good HTTP response\n\n       # Unix domain servers\n       server unix:/tmp/example_1.sock fail_timeout=0;\n       server unix:/tmp/example_2.sock fail_timeout=0;\n       server unix:/tmp/example_3.sock fail_timeout=0;\n       server unix:/tmp/example_4.sock fail_timeout=0;\n\n       # Unix domain sockets are used in this example due to their high performance,\n       # but TCP/IP sockets could be used instead:\n       # server 127.0.0.1:8081 fail_timeout=0;\n       # server 127.0.0.1:8082 fail_timeout=0;\n       # server 127.0.0.1:8083 fail_timeout=0;\n       # server 127.0.0.1:8084 fail_timeout=0;\n     }\n   }\n\nAll HTTP requests for ``http://example.com`` except ones for\n``http://example.com/static`` will be redirected to ``example1.sock``,\n``example2.sock``, ``example3.sock`` or ``example4.sock``\nbackend servers. By default, Nginx uses round-robin algorithm for backend\nselection.\n\n.. note::\n\n   Nginx is not the only existing *reverse proxy server*, but it's the most\n   popular one.  Alternatives like HAProxy may be used as well.\n\nSupervisord\n-----------\n\nAfter configuring Nginx we need to start our aiohttp backends. It's best\nto use some tool for starting them automatically after a system reboot\nor backend crash.\n\nThere are many ways to do it: Supervisord, Upstart, Systemd,\nGaffer, Circus, Runit etc.\n\nHere we'll use `Supervisord <http://supervisord.org/>`_ as an example:\n\n.. code-block:: cfg\n\n   [program:aiohttp]\n   numprocs = 4\n   numprocs_start = 1\n   process_name = example_%(process_num)s\n\n   ; Unix socket paths are specified by command line.\n   command=/path/to/aiohttp_example.py --path=/tmp/example_%(process_num)s.sock\n\n   ; We can just as easily pass TCP port numbers:\n   ; command=/path/to/aiohttp_example.py --port=808%(process_num)s\n\n   user=nobody\n   autostart=true\n   autorestart=true\n\naiohttp server\n--------------\n\nThe last step is preparing the aiohttp server to work with supervisord.\n\nAssuming we have properly configured :class:`aiohttp.web.Application`\nand port is specified by command line, the task is trivial:\n\n.. code-block:: python3\n\n   # aiohttp_example.py\n   import argparse\n   from aiohttp import web\n\n   parser = argparse.ArgumentParser(description=\"aiohttp server example\")\n   parser.add_argument('--path')\n   parser.add_argument('--port')\n\n\n   if __name__ == '__main__':\n       app = web.Application()\n       # configure app\n\n       args = parser.parse_args()\n       web.run_app(app, path=args.path, port=args.port)\n\nFor real use cases we perhaps need to configure other things like\nlogging etc., but it's out of scope of the topic.\n\n\n.. _aiohttp-deployment-gunicorn:\n\nNginx+Gunicorn\n==============\n\naiohttp can be deployed using `Gunicorn\n<http://docs.gunicorn.org/en/latest/index.html>`_, which is based on a\npre-fork worker model.  Gunicorn launches your app as worker processes\nfor handling incoming requests.\n\nAs opposed to deployment with :ref:`bare Nginx\n<aiohttp-deployment-nginx-supervisord>`, this solution does not need to\nmanually run several aiohttp processes and use a tool like supervisord\nto monitor them. But nothing is free: running aiohttp\napplication under gunicorn is slightly slower.\n\n\nPrepare environment\n-------------------\n\nYou first need to setup your deployment environment. This example is\nbased on `Ubuntu <https://www.ubuntu.com/>`_ 16.04.\n\nCreate a directory for your application::\n\n  >> mkdir myapp\n  >> cd myapp\n\nCreate a Python virtual environment::\n\n  >> python3 -m venv venv\n  >> source venv/bin/activate\n\nNow that the virtual environment is ready, we'll proceed to install\naiohttp and gunicorn::\n\n  >> pip install gunicorn\n  >> pip install aiohttp\n\n\nApplication\n-----------\n\nLets write a simple application, which we will save to file. We'll\nname this file *my_app_module.py*::\n\n   from aiohttp import web\n\n   async def index(request):\n       return web.Response(text=\"Welcome home!\")\n\n\n   my_web_app = web.Application()\n   my_web_app.router.add_get('/', index)\n\n\nApplication factory\n-------------------\n\nAs an option an entry point could be a coroutine that accepts no\nparameters and returns an application instance::\n\n   from aiohttp import web\n\n   async def index(request):\n       return web.Response(text=\"Welcome home!\")\n\n\n   async def my_web_app():\n       app = web.Application()\n       app.router.add_get('/', index)\n       return app\n\n\nStart Gunicorn\n--------------\n\nWhen `Running Gunicorn\n<http://docs.gunicorn.org/en/latest/run.html>`_, you provide the name\nof the module, i.e. *my_app_module*, and the name of the app or\napplication factory, i.e. *my_web_app*, along with other `Gunicorn\nSettings <http://docs.gunicorn.org/en/latest/settings.html>`_ provided\nas command line flags or in your config file.\n\nIn this case, we will use:\n\n* the ``--bind`` flag to set the server's socket address;\n* the ``--worker-class`` flag to tell Gunicorn that we want to use a\n  custom worker subclass instead of one of the Gunicorn default worker\n  types;\n* you may also want to use the ``--workers`` flag to tell Gunicorn how\n  many worker processes to use for handling requests. (See the\n  documentation for recommendations on `How Many Workers?\n  <http://docs.gunicorn.org/en/latest/design.html#how-many-workers>`_)\n* you may also want to use the ``--accesslog`` flag to enable the access\n  log to be populated. (See :ref:`logging <gunicorn-accesslog>` for more information.)\n\nThe custom worker subclass is defined in ``aiohttp.GunicornWebWorker``::\n\n  >> gunicorn my_app_module:my_web_app --bind localhost:8080 --worker-class aiohttp.GunicornWebWorker\n  [2017-03-11 18:27:21 +0000] [1249] [INFO] Starting gunicorn 19.7.1\n  [2017-03-11 18:27:21 +0000] [1249] [INFO] Listening at: http://127.0.0.1:8080 (1249)\n  [2017-03-11 18:27:21 +0000] [1249] [INFO] Using worker: aiohttp.worker.GunicornWebWorker\n  [2015-03-11 18:27:21 +0000] [1253] [INFO] Booting worker with pid: 1253\n\nGunicorn is now running and ready to serve requests to your app's\nworker processes.\n\n.. note::\n\n   If you want to use an alternative asyncio event loop\n   `uvloop <https://github.com/MagicStack/uvloop>`_, you can use the\n   ``aiohttp.GunicornUVLoopWebWorker`` worker class.\n\nProxy through NGINX\n----------------------\n\nWe can proxy our gunicorn workers through NGINX with a configuration like this:\n\n.. code-block:: nginx\n\n    worker_processes 1;\n    user nobody nogroup;\n    events {\n        worker_connections 1024;\n    }\n    http {\n        ## Main Server Block\n        server {\n            ## Open by default.\n            listen                80 default_server;\n            server_name           main;\n            client_max_body_size  200M;\n\n            ## Main site location.\n            location / {\n                proxy_pass                          http://127.0.0.1:8080;\n                proxy_set_header                    Host $host;\n                proxy_set_header X-Forwarded-Host   $server_name;\n                proxy_set_header X-Real-IP          $remote_addr;\n            }\n        }\n    }\n\nSince gunicorn listens for requests at our localhost address on port 8080, we can\nuse the `proxy_pass <https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass>`_\ndirective to send web traffic to our workers. If everything is configured correctly,\nwe should reach our application at the ip address of our web server.\n\nProxy through NGINX + SSL\n----------------------------\n\nHere is an example NGINX configuration setup to accept SSL connections:\n\n.. code-block:: nginx\n\n    worker_processes 1;\n    user nobody nogroup;\n    events {\n        worker_connections 1024;\n    }\n    http {\n        ## SSL Redirect\n        server {\n            listen 80       default;\n            return 301      https://$host$request_uri;\n        }\n\n        ## Main Server Block\n        server {\n            # Open by default.\n            listen                443 ssl default_server;\n            listen                [::]:443 ssl default_server;\n            server_name           main;\n            client_max_body_size  200M;\n\n            ssl_certificate       /etc/secrets/cert.pem;\n            ssl_certificate_key   /etc/secrets/key.pem;\n\n            ## Main site location.\n            location / {\n                proxy_pass                          http://127.0.0.1:8080;\n                proxy_set_header                    Host $host;\n                proxy_set_header X-Forwarded-Host   $server_name;\n                proxy_set_header X-Real-IP          $remote_addr;\n            }\n        }\n    }\n\n\nThe first server block accepts regular http connections on port 80 and redirects\nthem to our secure SSL connection. The second block matches our previous example\nexcept we need to change our open port to https and specify where our SSL\ncertificates are being stored with the ``ssl_certificate`` and ``ssl_certificate_key``\ndirectives.\n\nDuring development, you may want to `create your own self-signed certificates for testing purposes <https://www.digitalocean.com/community/tutorials/how-to-create-a-self-signed-ssl-certificate-for-nginx-in-ubuntu-18-04>`_\nand use another service like `Let's Encrypt <https://letsencrypt.org/>`_ when you\nare ready to move to production.\n\nMore information\n----------------\n\nSee the `official documentation\n<http://docs.gunicorn.org/en/latest/deploy.html>`_ for more\ninformation about suggested nginx configuration. You can also find out more about\n`configuring for secure https connections as well. <https://nginx.org/en/docs/http/configuring_https_servers.html>`_\n\nLogging configuration\n---------------------\n\n``aiohttp`` and ``gunicorn`` use different format for specifying access log.\n\nBy default aiohttp uses own defaults::\n\n   '%a %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\"'\n\nFor more information please read :ref:`Format Specification for Access\nLog <aiohttp-logging-access-log-format-spec>`.\n\n\nProxy through Apache at your own risk\n-------------------------------------\nIssues have been reported using Apache2 in front of aiohttp server:\n`#2687 Intermittent 502 proxy errors when running behind Apache <https://github.com/aio-libs/aiohttp/issues/2687>`.\n"
  },
  {
    "path": "docs/essays.rst",
    "content": "Essays\n======\n\n\n.. toctree::\n\n   new_router\n   whats_new_1_1\n   migration_to_2xx\n   whats_new_3_0\n"
  },
  {
    "path": "docs/external.rst",
    "content": "Who uses aiohttp?\n=================\n\nThe list of *aiohttp* users: both libraries, big projects and web sites.\n\nPlease don't hesitate to add your awesome project to the list by\nmaking a Pull Request on GitHub_.\n\nIf you like the project -- please go to GitHub_ and press *Star* button!\n\n\n.. toctree::\n\n   third_party\n   built_with\n   powered_by\n\n.. _GitHub: https://github.com/aio-libs/aiohttp\n"
  },
  {
    "path": "docs/faq.rst",
    "content": "FAQ\n===\n\n.. contents::\n   :local:\n\nAre there plans for an @app.route decorator like in Flask?\n----------------------------------------------------------\n\nAs of aiohttp 2.3, :class:`~aiohttp.web.RouteTableDef` provides an API\nsimilar to Flask's ``@app.route``. See\n:ref:`aiohttp-web-alternative-routes-definition`.\n\nUnlike Flask's ``@app.route``, :class:`~aiohttp.web.RouteTableDef`\ndoes not require an ``app`` in the module namespace (which often leads\nto circular imports).\n\nInstead, a :class:`~aiohttp.web.RouteTableDef` is decoupled from an application instance::\n\n   routes = web.RouteTableDef()\n\n   @routes.get('/get')\n   async def handle_get(request):\n       ...\n\n\n   @routes.post('/post')\n   async def handle_post(request):\n       ...\n\n   app.router.add_routes(routes)\n\n\nDoes aiohttp have a concept like Flask's \"blueprint\" or Django's \"app\"?\n-----------------------------------------------------------------------\n\nIf you're writing a large application, you may want to consider\nusing :ref:`nested applications <aiohttp-web-nested-applications>`, which\nare similar to Flask's \"blueprints\" or Django's \"apps\".\n\nSee: :ref:`aiohttp-web-nested-applications`.\n\n\nHow do I create a route that matches urls with a given prefix?\n--------------------------------------------------------------\n\nYou can do something like the following: ::\n\n    app.router.add_route('*', '/path/to/{tail:.+}', sink_handler)\n\nThe first argument, ``*``,  matches any HTTP method\n(*GET, POST, OPTIONS*, etc). The second argument matches URLS with the desired prefix.\nThe third argument is the handler function.\n\n\nWhere do I put my database connection so handlers can access it?\n----------------------------------------------------------------\n\n:class:`aiohttp.web.Application` object supports the :class:`dict`\ninterface and provides a place to store your database connections or any\nother resource you want to share between handlers.\n::\n\n    db_key = web.AppKey(\"db_key\", DB)\n\n    async def go(request):\n        db = request.app[db_key]\n        cursor = await db.cursor()\n        await cursor.execute('SELECT 42')\n        # ...\n        return web.Response(status=200, text='ok')\n\n\n    async def init_app():\n        app = Application()\n        db = await create_connection(user='user', password='123')\n        app[db_key] = db\n        app.router.add_get('/', go)\n        return app\n\n\nHow can middleware store data for web handlers to use?\n------------------------------------------------------\n\nBoth :class:`aiohttp.web.Request`  and :class:`aiohttp.web.Application`\nsupport the :class:`dict` interface.\n\nTherefore, data may be stored inside a request object. ::\n\n    request_id_key = web.RequestKey(\"request_id_key\", str)\n\n    @web.middleware\n    async def request_id_middleware(request, handler):\n        request[request_id_key] = \"some_request_id\"\n        return await handler(request)\n\n    async def handler(request):\n        request_id = request[request_id_key]\n\nSee https://github.com/aio-libs/aiohttp_session code for an example.\nThe ``aiohttp_session.get_session(request)`` method uses ``SESSION_KEY``\nfor saving request-specific session information.\n\nAs of aiohttp 3.0, all response objects are dict-like structures as\nwell.\n\n\n.. _aiohttp_faq_parallel_event_sources:\n\nCan a handler receive incoming events from different sources in parallel?\n-------------------------------------------------------------------------\n\nYes.\n\nAs an example, we may have two event sources:\n\n   1. WebSocket for events from an end user\n\n   2. Redis PubSub for events from other parts of the application\n\nThe most native way to handle this is to create a separate task for\nPubSub handling.\n\nParallel :meth:`aiohttp.web.WebSocketResponse.receive` calls are forbidden;\na single task should perform WebSocket reading.\nHowever, other tasks may use the same WebSocket object for sending data to\npeers. ::\n\n    async def handler(request):\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        task = asyncio.create_task(\n            read_subscription(ws, request.app[redis_key]))\n        try:\n            async for msg in ws:\n                # handle incoming messages\n                # use ws.send_str() to send data back\n                ...\n\n        finally:\n            task.cancel()\n\n    async def read_subscription(ws, redis):\n        channel, = await redis.subscribe('channel:1')\n\n        try:\n            async for msg in channel.iter():\n                answer = process_the_message(msg)  # your function here\n                await ws.send_str(answer)\n        finally:\n            await redis.unsubscribe('channel:1')\n\n\n.. _aiohttp_faq_terminating_websockets:\n\nHow do I programmatically close a WebSocket server-side?\n--------------------------------------------------------\n\nLet's say we have an application with two endpoints:\n\n\n   1. ``/echo`` a WebSocket echo server that authenticates the user\n   2. ``/logout_user`` that, when invoked, closes all open\n      WebSockets for that user.\n\nOne simple solution is to keep a shared registry of WebSocket\nresponses for a user in the :class:`aiohttp.web.Application` instance\nand call :meth:`aiohttp.web.WebSocketResponse.close` on all of them in\n``/logout_user`` handler::\n\n    async def echo_handler(request):\n\n        ws = web.WebSocketResponse()\n        user_id = authenticate_user(request)\n        await ws.prepare(request)\n        request.app[websockets_key][user_id].add(ws)\n        try:\n            async for msg in ws:\n                ws.send_str(msg.data)\n        finally:\n            request.app[websockets_key][user_id].remove(ws)\n\n        return ws\n\n\n    async def logout_handler(request):\n\n        user_id = authenticate_user(request)\n\n        ws_closers = [ws.close()\n                      for ws in request.app[websockets_key][user_id]\n                      if not ws.closed]\n\n        # Watch out, this will keep us from returning the response\n        # until all are closed\n        ws_closers and await asyncio.gather(*ws_closers)\n\n        return web.Response(text='OK')\n\n\n    def main():\n        loop = asyncio.get_event_loop()\n        app = web.Application()\n        app.router.add_route('GET', '/echo', echo_handler)\n        app.router.add_route('POST', '/logout', logout_handler)\n        app[websockets_key] = defaultdict(set)\n        web.run_app(app, host='localhost', port=8080)\n\n\nHow do I make a request from a specific IP address?\n---------------------------------------------------\n\nIf your system has several IP interfaces, you may choose one which will\nbe used used to bind a socket locally::\n\n    conn = aiohttp.TCPConnector(local_addr=('127.0.0.1', 0))\n    async with aiohttp.ClientSession(connector=conn) as session:\n        ...\n\n.. seealso:: :class:`aiohttp.TCPConnector` and ``local_addr`` parameter.\n\n\nWhat is the API stability and deprecation policy?\n-------------------------------------------------\n\n*aiohttp* follows strong `Semantic Versioning <https://semver.org>`_ (SemVer).\n\nObsolete attributes and methods are marked as *deprecated* in the\ndocumentation and raise :class:`DeprecationWarning` upon usage.\n\nAssume aiohttp ``X.Y.Z`` where ``X`` is major version,\n``Y`` is minor version and ``Z`` is bugfix number.\n\nFor example, if the latest released version is ``aiohttp==3.0.6``:\n\n``3.0.7`` fixes some bugs but have no new features.\n\n``3.1.0`` introduces new features and can deprecate some API but never\nremove it, also all bug fixes from previous release are merged.\n\n``4.0.0`` removes all deprecations collected from ``3.Y`` versions\n**except** deprecations from the **last** ``3.Y`` release. These\ndeprecations will be removed by ``5.0.0``.\n\nUnfortunately we may have to break these rules when a **security\nvulnerability** is found.\nIf a security problem cannot be fixed without breaking backward\ncompatibility, a bugfix release may break compatibility. This is unlikely, but\npossible.\n\nAll backward incompatible changes are explicitly marked in\n:ref:`the changelog <aiohttp_changes>`.\n\n\nHow do I enable gzip compression globally for my entire application?\n--------------------------------------------------------------------\n\nIt's impossible. Choosing what to compress and what not to compress\nis a tricky matter.\n\nIf you need global compression, write a custom middleware. Or\nenable compression in NGINX (you are deploying aiohttp behind reverse\nproxy, right?).\n\n\nHow do I manage a ClientSession within a web server?\n----------------------------------------------------\n\n:class:`aiohttp.ClientSession` should be created once for the lifetime\nof the server in order to benefit from connection pooling.\n\nSessions save cookies internally. If you don't need cookie processing,\nuse :class:`aiohttp.DummyCookieJar`. If you need separate cookies\nfor different http calls but process them in logical chains, use a single\n:class:`aiohttp.TCPConnector` with separate\nclient sessions and ``connector_owner=False``.\n\n\nHow do I access database connections from a subapplication?\n-----------------------------------------------------------\n\nRestricting access from subapplication to main (or outer) app is a\ndeliberate choice.\n\nA subapplication is an isolated unit by design. If you need to share a\ndatabase object, do it explicitly::\n\n   subapp[db_key] = mainapp[db_key]\n   mainapp.add_subapp(\"/prefix\", subapp)\n\nThis can also be done from a :ref:`cleanup context<aiohttp-web-cleanup-ctx>`::\n\n   @contextlib.asynccontextmanager\n   async def db_context(app: web.Application) -> AsyncIterator[None]:\n      async with create_db() as db:\n         mainapp[db_key] = mainapp[subapp_key][db_key] = db\n         yield\n\n   mainapp[subapp_key] = subapp\n   mainapp.add_subapp(\"/prefix\", subapp)\n   mainapp.cleanup_ctx.append(db_context)\n\n\nHow do I perform operations in a request handler after sending the response?\n----------------------------------------------------------------------------\n\nMiddlewares can be written to handle post-response operations, but\nthey run after every request. You can explicitly send the response by\ncalling :meth:`aiohttp.web.Response.write_eof`, which starts sending\nbefore the handler returns, giving you a chance to execute follow-up\noperations::\n\n    def ping_handler(request):\n        \"\"\"Send PONG and increase DB counter.\"\"\"\n\n        # explicitly send the response\n        resp = web.json_response({'message': 'PONG'})\n        await resp.prepare(request)\n        await resp.write_eof()\n\n        # increase the pong count\n        request.app[db_key].inc_pong()\n\n        return resp\n\nA :class:`aiohttp.web.Response` object must be returned. This is\nrequired by aiohttp web contracts, even though the response has\nalready been sent.\n\n\nHow do I make sure my custom middleware response will behave correctly?\n------------------------------------------------------------------------\n\nSometimes your middleware handlers might need to send a custom response.\nThis is just fine as long as you always create a new\n:class:`aiohttp.web.Response` object when required.\n\nThe response object is a Finite State Machine. Once it has been dispatched\nby the server, it will reach its final state and cannot be used again.\n\nThe following middleware will make the server hang, once it serves the second\nresponse::\n\n    from aiohttp import web\n\n    def misbehaved_middleware():\n        # don't do this!\n        cached = web.Response(status=200, text='Hi, I am cached!')\n\n        async def middleware(request, handler):\n            # ignoring response for the sake of this example\n            _res = handler(request)\n            return cached\n\n        return middleware\n\nThe rule of thumb is *one request, one response*.\n\n\nWhy is creating a ClientSession outside of an event loop dangerous?\n-------------------------------------------------------------------\n\nShort answer is: life-cycle of all asyncio objects should be shorter\nthan life-cycle of event loop.\n\nFull explanation is longer.  All asyncio object should be correctly\nfinished/disconnected/closed before event loop shutdown.  Otherwise\nuser can get unexpected behavior. In the best case it is a warning\nabout unclosed resource, in the worst case the program just hangs,\nawaiting for coroutine is never resumed etc.\n\nConsider the following code from ``mod.py``::\n\n    import aiohttp\n\n    session = aiohttp.ClientSession()\n\n    async def fetch(url):\n        async with session.get(url) as resp:\n            return await resp.text()\n\nThe session grabs current event loop instance and stores it in a\nprivate variable.\n\nThe main module imports the module and installs ``uvloop`` (an\nalternative fast event loop implementation).\n\n``main.py``::\n\n    import asyncio\n    import uvloop\n    import mod\n\n    asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())\n    asyncio.run(main())\n\nThe code is broken: ``session`` is bound to default ``asyncio`` loop\non import time but the loop is changed **after the import** by\n``set_event_loop()``.  As result ``fetch()`` call hangs.\n\n\nTo avoid import dependency hell *aiohttp* encourages creation of\n``ClientSession`` from async function.  The same policy works for\n``web.Application`` too.\n\nAnother use case is unit test writing.  Very many test libraries\n(*aiohttp test tools* first) creates a new loop instance for every\ntest function execution.  It's done for sake of tests isolation.\nOtherwise pending activity (timers, network packets etc.) from\nprevious test may interfere with current one producing very cryptic\nand unstable test failure.\n\nNote: *class variables* are hidden globals actually. The following\ncode has the same problem as ``mod.py`` example, ``session`` variable\nis the hidden global object::\n\n    class A:\n        session = aiohttp.ClientSession()\n\n        async def fetch(self, url):\n            async with session.get(url) as resp:\n                return await resp.text()\n"
  },
  {
    "path": "docs/glossary.rst",
    "content": ".. _aiohttp-glossary:\n\n\n==========\n Glossary\n==========\n\n.. if you add new entries, keep the alphabetical sorting!\n\n.. glossary::\n   :sorted:\n\n   aiodns\n\n      DNS resolver for asyncio.\n\n      https://pypi.python.org/pypi/aiodns\n\n   asyncio\n\n      The library for writing single-threaded concurrent code using\n      coroutines, multiplexing I/O access over sockets and other\n      resources, running network clients and servers, and other\n      related primitives.\n\n      Reference implementation of :pep:`3156`\n\n      https://pypi.python.org/pypi/asyncio/\n\n   Brotli\n\n      Brotli is a generic-purpose lossless compression algorithm that\n      compresses data using a combination of a modern variant\n      of the LZ77 algorithm, Huffman coding and second order context modeling,\n      with a compression ratio comparable to the best currently available\n      general-purpose compression methods. It is similar in speed with deflate\n      but offers more dense compression.\n\n      The specification of the Brotli Compressed Data Format is defined :rfc:`7932`\n\n      https://pypi.org/project/Brotli/\n\n   brotlicffi\n\n      An alternative implementation of :term:`Brotli` built using the CFFI\n      library. This implementation supports PyPy correctly.\n\n      https://pypi.org/project/brotlicffi/\n\n   callable\n\n      Any object that can be called. Use :func:`callable` to check\n      that.\n\n   gunicorn\n\n       Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for\n       UNIX.\n\n       http://gunicorn.org/\n\n   IDNA\n\n       An Internationalized Domain Name in Applications (IDNA) is an\n       industry standard for encoding Internet Domain Names that contain in\n       whole or in part, in a language-specific script or alphabet,\n       such as Arabic, Chinese, Cyrillic, Tamil, Hebrew or the Latin\n       alphabet-based characters with diacritics or ligatures, such as\n       French. These writing systems are encoded by computers in\n       multi-byte Unicode. Internationalized domain names are stored\n       in the Domain Name System as ASCII strings using Punycode\n       transcription.\n\n   keep-alive\n\n       A technique for communicating between HTTP client and server\n       when connection is not closed after sending response but kept\n       open for sending next request through the same socket.\n\n       It makes communication faster by getting rid of connection\n       establishment for every request.\n\n\n\n   nginx\n\n      Nginx [engine x] is an HTTP and reverse proxy server, a mail\n      proxy server, and a generic TCP/UDP proxy server.\n\n      https://nginx.org/en/\n\n   percent-encoding\n\n      A mechanism for encoding information in a Uniform Resource\n      Locator (URL) if URL parts don't fit in safe characters space.\n\n   requests\n\n      Currently the most popular synchronous library to make\n      HTTP requests in Python.\n\n      https://requests.readthedocs.io\n\n   requoting\n\n      Applying :term:`percent-encoding` to non-safe symbols and decode\n      percent encoded safe symbols back.\n\n      According to :rfc:`3986` allowed path symbols are::\n\n         allowed       = unreserved / pct-encoded / sub-delims\n                         / \":\" / \"@\" / \"/\"\n\n         pct-encoded   = \"%\" HEXDIG HEXDIG\n\n         unreserved    = ALPHA / DIGIT / \"-\" / \".\" / \"_\" / \"~\"\n\n         sub-delims    = \"!\" / \"$\" / \"&\" / \"'\" / \"(\" / \")\"\n                         / \"*\" / \"+\" / \",\" / \";\" / \"=\"\n\n   resource\n\n      A concept reflects the HTTP **path**, every resource corresponds\n      to *URI*.\n\n      May have a unique name.\n\n      Contains :term:`route`\\'s for different HTTP methods.\n\n   route\n\n       A part of :term:`resource`, resource's *path* coupled with HTTP method.\n\n   web-handler\n\n       An endpoint that returns HTTP response.\n\n   websocket\n\n       A protocol providing full-duplex communication channels over a\n       single TCP connection. The WebSocket protocol was standardized\n       by the IETF as :rfc:`6455`\n\n   yarl\n\n      A library for operating with URL objects.\n\n      https://pypi.python.org/pypi/yarl\n\n\nEnvironment Variables\n=====================\n\n.. envvar:: AIOHTTP_NO_EXTENSIONS\n\n   If set to a non-empty value while building from source, aiohttp will be built without speedups\n   written as C extensions. This option is primarily useful for debugging.\n\n.. envvar:: AIOHTTP_USE_SYSTEM_DEPS\n\n   If set to a non-empty value while building from source, aiohttp will be built against\n   the system installation of llhttp rather than the vendored library. This option is primarily\n   meant to be used by downstream redistributors.\n\n.. envvar:: NETRC\n\n   If set, HTTP Basic Auth will be read from the file pointed to by this environment variable,\n   rather than from :file:`~/.netrc`.\n\n   .. seealso::\n\n      ``.netrc`` documentation: https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html\n"
  },
  {
    "path": "docs/http_request_lifecycle.rst",
    "content": "\n\n.. _aiohttp-request-lifecycle:\n\n\nThe aiohttp Request Lifecycle\n=============================\n\n\nWhy is aiohttp client API that way?\n--------------------------------------\n\n\nThe first time you use aiohttp, you'll notice that a simple HTTP request is performed not with one, but with up to three steps:\n\n\n.. code-block:: python\n\n\n    async with aiohttp.ClientSession() as session:\n        async with session.get('http://python.org') as response:\n            print(await response.text())\n\n\nIt's especially unexpected when coming from other libraries such as the very popular :term:`requests`, where the \"hello world\" looks like this:\n\n\n.. code-block:: python\n\n\n    response = requests.get('http://python.org')\n    print(response.text)\n\n\nSo why is the aiohttp snippet so verbose?\n\n\nBecause aiohttp is asynchronous, its API is designed to make the most out of non-blocking network operations. In code like this, requests will block three times, and does it transparently, while aiohttp gives the event loop three opportunities to switch context:\n\n\n- When doing the ``.get()``, both libraries send a GET request to the remote server. For aiohttp, this means asynchronous I/O, which is marked here with an ``async with`` that gives you the guarantee that not only it doesn't block, but that it's cleanly finalized.\n- When doing ``response.text`` in requests, you just read an attribute. The call to ``.get()`` already preloaded and decoded the entire response payload, in a blocking manner. aiohttp loads only the headers when ``.get()`` is executed, letting you decide to pay the cost of loading the body afterward, in a second asynchronous operation. Hence the ``await response.text()``.\n- ``async with aiohttp.ClientSession()`` does not perform I/O when entering the block, but at the end of it, it will ensure all remaining resources are closed correctly. Again, this is done asynchronously and must be marked as such. The session is also a performance tool, as it manages a pool of connections for you, allowing you to reuse them instead of opening and closing a new one at each request. You can even `manage the pool size by passing a connector object <client_advanced.html#limiting-connection-pool-size>`_.\n\nUsing a session as a best practice\n-----------------------------------\n\nThe requests library does in fact also provides a session system. Indeed, it lets you do:\n\n.. code-block:: python\n\n    with requests.Session() as session:\n        response = session.get('http://python.org')\n        print(response.text)\n\nIt's just not the default behavior, nor is it advertised early in the documentation. Because of this, most users take a hit in performance, but can quickly start hacking. And for requests, it's an understandable trade-off, since its goal is to be \"HTTP for humans\" and simplicity has always been more important than performance in this context.\n\nHowever, if one uses aiohttp, one chooses asynchronous programming, a paradigm that makes the opposite trade-off: more verbosity for better performance. And so the library default behavior reflects this, encouraging you to use performant best practices from the start.\n\nHow to use the ClientSession ?\n-------------------------------\n\nBy default the :class:`aiohttp.ClientSession` object will hold a connector with a maximum of 100 connections, putting the rest in a queue. This is quite a big number, this means you must be connected to a hundred different servers (not pages!) concurrently before even having to consider if your task needs resource adjustment.\n\nIn fact, you can picture the session object as a user starting and closing a browser: it wouldn't make sense to do that every time you want to load a new tab.\n\nSo you are expected to reuse a session object and make many requests from it. For most scripts and average-sized software, this means you can create a single session, and reuse it for the entire execution of the program. You can even pass the session around as a parameter in functions. For example, the typical \"hello world\":\n\n.. code-block:: python\n\n    import aiohttp\n    import asyncio\n\n    async def main():\n        async with aiohttp.ClientSession() as session:\n            async with session.get('http://python.org') as response:\n                html = await response.text()\n                print(html)\n\n    asyncio.run(main())\n\n\nCan become this:\n\n\n.. code-block:: python\n\n    import aiohttp\n    import asyncio\n\n    async def fetch(session, url):\n        async with session.get(url) as response:\n            return await response.text()\n\n    async def main():\n        async with aiohttp.ClientSession() as session:\n            html = await fetch(session, 'http://python.org')\n            print(html)\n\n    asyncio.run(main())\n\nOn more complex code bases, you can even create a central registry to hold the session object from anywhere in the code, or a higher level ``Client`` class that holds a reference to it.\n\nWhen to create more than one session object then? It arises when you want more granularity with your resources management:\n\n- you want to group connections by a common configuration. e.g: sessions can set cookies, headers, timeout values, etc. that are shared for all connections they hold.\n- you need several threads and want to avoid sharing a mutable object between them.\n- you want several connection pools to benefit from different queues and assign priorities. e.g: one session never uses the queue and is for high priority requests, the other one has a small concurrency limit and a very long queue, for non important requests.\n"
  },
  {
    "path": "docs/index.rst",
    "content": ".. aiohttp documentation master file, created by\n   sphinx-quickstart on Wed Mar  5 12:35:35 2014.\n   You can adapt this file completely to your liking, but it should at least\n   contain the root `toctree` directive.\n\n==================\nWelcome to AIOHTTP\n==================\n\nAsynchronous HTTP Client/Server for :term:`asyncio` and Python.\n\nCurrent version is |release|.\n\n.. _GitHub: https://github.com/aio-libs/aiohttp\n\n\nKey Features\n============\n\n- Supports both :ref:`aiohttp-client` and :ref:`HTTP Server <aiohttp-web>`.\n- Supports both :ref:`Server WebSockets <aiohttp-web-websockets>` and\n  :ref:`Client WebSockets <aiohttp-client-websockets>` out-of-the-box\n  without the Callback Hell.\n- Web-server has :ref:`aiohttp-web-middlewares`,\n  :ref:`aiohttp-web-signals` and pluggable routing.\n- Client supports :ref:`middleware <aiohttp-client-middleware>` for\n  customizing request/response processing.\n\n.. _aiohttp-installation:\n\nLibrary Installation\n====================\n\n.. code-block:: bash\n\n   $ pip install aiohttp\n\nFor speeding up DNS resolving by client API you may install\n:term:`aiodns` as well.\nThis option is highly recommended:\n\n.. code-block:: bash\n\n   $ pip install aiodns\n\nInstalling all speedups in one command\n--------------------------------------\n\nThe following will get you ``aiohttp`` along with :term:`aiodns` and ``Brotli`` in one\nbundle.\nNo need to type separate commands anymore!\n\n.. code-block:: bash\n\n   $ pip install aiohttp[speedups]\n\nGetting Started\n===============\n\nClient example\n--------------\n\n.. code-block:: python\n\n  import aiohttp\n  import asyncio\n\n  async def main():\n\n      async with aiohttp.ClientSession() as session:\n          async with session.get('http://python.org') as response:\n\n              print(\"Status:\", response.status)\n              print(\"Content-type:\", response.headers['content-type'])\n\n              html = await response.text()\n              print(\"Body:\", html[:15], \"...\")\n\n  asyncio.run(main())\n\nThis prints:\n\n.. code-block:: text\n\n    Status: 200\n    Content-type: text/html; charset=utf-8\n    Body: <!doctype html> ...\n\nComing from :term:`requests` ? Read :ref:`why we need so many lines <aiohttp-request-lifecycle>`.\n\nServer example:\n----------------\n\n.. code-block:: python\n\n    from aiohttp import web\n\n    async def handle(request):\n        name = request.match_info.get('name', \"Anonymous\")\n        text = \"Hello, \" + name\n        return web.Response(text=text)\n\n    app = web.Application()\n    app.add_routes([web.get('/', handle),\n                    web.get('/{name}', handle)])\n\n    if __name__ == '__main__':\n        web.run_app(app)\n\n\nFor more information please visit :ref:`aiohttp-client` and\n:ref:`aiohttp-web` pages.\n\nDevelopment mode\n================\n\nWhen writing your code, we recommend enabling Python's\n`development mode <https://docs.python.org/3/library/devmode.html>`_\n(``python -X dev``). In addition to the extra features enabled for asyncio, aiohttp\nwill:\n\n- Use a strict parser in the client code (which can help detect malformed responses\n  from a server).\n- Enable some additional checks (resulting in warnings in certain situations).\n\nWhat's new in aiohttp 3?\n========================\n\nGo to :ref:`aiohttp_whats_new_3_0` page for aiohttp 3.0 major release\nchanges.\n\n\nTutorial\n========\n\n:ref:`Polls tutorial <aiohttpdemos:aiohttp-demos-polls-beginning>`\n\n\nSource code\n===========\n\nThe project is hosted on GitHub_\n\nPlease feel free to file an issue on the `bug tracker\n<https://github.com/aio-libs/aiohttp/issues>`_ if you have found a bug\nor have some suggestion in order to improve the library.\n\n\nDependencies\n============\n\n- *multidict*\n- *yarl*\n\n- *Optional* :term:`aiodns` for fast DNS resolving. The\n  library is highly recommended.\n\n  .. code-block:: bash\n\n     $ pip install aiodns\n\n- *Optional* :term:`Brotli` or :term:`brotlicffi` for brotli (:rfc:`7932`)\n  client compression support.\n\n  .. code-block:: bash\n\n     $ pip install Brotli\n\n\nCommunication channels\n======================\n\n*aio-libs Discussions*: https://github.com/aio-libs/aiohttp/discussions\n\nFeel free to post your questions and ideas here.\n\n*Matrix*: `#aio-libs:matrix.org <https://matrix.to/#/#aio-libs:matrix.org>`_\n\nWe support `Stack Overflow\n<https://stackoverflow.com/questions/tagged/aiohttp>`_.\nPlease add *aiohttp* tag to your question there.\n\nContributing\n============\n\nPlease read the :ref:`instructions for contributors<aiohttp-contributing>`\nbefore making a Pull Request.\n\n\nAuthors and License\n===================\n\nThe ``aiohttp`` package is written mostly by Nikolay Kim and Andrew Svetlov.\n\nIt's *Apache 2* licensed and freely available.\n\nFeel free to improve this package and send a pull request to GitHub_.\n\n\n.. _aiohttp-backward-compatibility-policy:\n\nPolicy for Backward Incompatible Changes\n========================================\n\n*aiohttp* keeps backward compatibility.\n\nWhen a new release is published that deprecates a *Public API* (method, class,\nfunction argument, etc.), the library will guarantee its usage for at least\na year and half from the date of release.\n\nDeprecated APIs are reflected in their documentation, and their use will raise\n:exc:`DeprecationWarning`.\n\nHowever, if there is a strong reason, we may be forced to break this guarantee.\nThe most likely reason would be a critical bug, such as a security issue, which\ncannot be solved without a major API change. We are working hard to keep these\nbreaking changes as rare as possible.\n\n\nTable Of Contents\n=================\n\n.. toctree::\n   :name: mastertoc\n   :maxdepth: 2\n\n   client\n   web\n   utilities\n   faq\n   misc\n   external\n   contributing\n"
  },
  {
    "path": "docs/logging.rst",
    "content": ".. currentmodule:: aiohttp\n\n.. _aiohttp-logging:\n\nLogging\n=======\n\n*aiohttp* uses standard :mod:`logging` for tracking the\nlibrary activity.\n\nWe have the following loggers enumerated by names:\n\n- ``'aiohttp.access'``\n- ``'aiohttp.client'``\n- ``'aiohttp.internal'``\n- ``'aiohttp.server'``\n- ``'aiohttp.web'``\n- ``'aiohttp.websocket'``\n\nYou may subscribe to these loggers for getting logging messages.  The\npage does not provide instructions for logging subscribing while the\nmost friendly method is :func:`logging.config.dictConfig` for\nconfiguring whole loggers in your application.\n\nLogging does not work out of the box. It requires at least minimal ``'logging'``\nconfiguration.\nExample of minimal working logger setup::\n\n  import logging\n  from aiohttp import web\n\n  app = web.Application()\n  logging.basicConfig(level=logging.DEBUG)\n  web.run_app(app, port=5000)\n\n.. versionadded:: 4.0.0\n\nAccess logs\n-----------\n\nAccess logs are enabled by default. If the `debug` flag is set, and the default\nlogger ``'aiohttp.access'`` is used, access logs will be output to\n:obj:`~sys.stderr` if no handlers are attached.\nFurthermore, if the default logger has no log level set, the log level will be\nset to :obj:`logging.DEBUG`.\n\nThis logging may be controlled by :meth:`aiohttp.web.AppRunner` and\n:func:`aiohttp.web.run_app`.\n\nTo override the default logger, pass an instance of :class:`logging.Logger` to\noverride the default logger.\n\n.. note::\n\n   Use ``web.run_app(app, access_log=None)`` to disable access logs.\n\n\nIn addition, *access_log_format* may be used to specify the log format.\n\n.. _aiohttp-logging-access-log-format-spec:\n\nFormat specification\n^^^^^^^^^^^^^^^^^^^^\n\nThe library provides custom micro-language to specifying info about\nrequest and response:\n\n+--------------+---------------------------------------------------------+\n| Option       | Meaning                                                 |\n+==============+=========================================================+\n| ``%%``       | The percent sign                                        |\n+--------------+---------------------------------------------------------+\n| ``%a``       | Remote IP-address                                       |\n|              | (IP-address of proxy if using reverse proxy)            |\n+--------------+---------------------------------------------------------+\n| ``%t``       | Time when the request was started to process            |\n+--------------+---------------------------------------------------------+\n| ``%P``       | The process ID of the child that serviced the request   |\n+--------------+---------------------------------------------------------+\n| ``%r``       | First line of request                                   |\n+--------------+---------------------------------------------------------+\n| ``%s``       | Response status code                                    |\n+--------------+---------------------------------------------------------+\n| ``%b``       | Size of response in bytes, including HTTP headers       |\n+--------------+---------------------------------------------------------+\n| ``%T``       | The time taken to serve the request, in seconds         |\n+--------------+---------------------------------------------------------+\n| ``%Tf``      | The time taken to serve the request, in seconds         |\n|              | with fraction in %.06f format                           |\n+--------------+---------------------------------------------------------+\n| ``%D``       | The time taken to serve the request, in microseconds    |\n+--------------+---------------------------------------------------------+\n| ``%{FOO}i``  | ``request.headers['FOO']``                              |\n+--------------+---------------------------------------------------------+\n| ``%{FOO}o``  | ``response.headers['FOO']``                             |\n+--------------+---------------------------------------------------------+\n\nThe default access log format is::\n\n   '%a %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\"'\n\n.. versionadded:: 2.3.0\n\n*access_log_class* introduced.\n\nExample of a drop-in replacement for the default access logger::\n\n  from aiohttp.abc import AbstractAccessLogger\n\n  class AccessLogger(AbstractAccessLogger):\n\n      def log(self, request, response, time):\n          self.logger.info(f'{request.remote} '\n                           f'\"{request.method} {request.path} '\n                           f'done in {time}s: {response.status}')\n\n      @property\n      def enabled(self):\n          \"\"\"Return True if logger is enabled.\n\n          Override this property if logging is disabled to avoid the\n          overhead of calculating details to feed the logger.\n\n          This property may be omitted if logging is always enabled.\n          \"\"\"\n          return self.logger.isEnabledFor(logging.INFO)\n\n.. versionadded:: 4.0.0\n\n\n``AccessLogger.log()`` can now access any exception raised while processing\nthe request with ``sys.exc_info()``.\n\n\n.. versionadded:: 4.0.0\n\n\nIf your logging needs to perform IO you can instead inherit from\n:class:`aiohttp.abc.AbstractAsyncAccessLogger`::\n\n\n  from aiohttp.abc import AbstractAsyncAccessLogger\n\n  class AccessLogger(AbstractAsyncAccessLogger):\n\n      async def log(self, request, response, time):\n          logging_service = request.app['logging_service']\n          await logging_service.log(f'{request.remote} '\n                                    f'\"{request.method} {request.path} '\n                                    f'done in {time}s: {response.status}')\n\n      @property\n      def enabled(self) -> bool:\n          \"\"\"Return True if logger is enabled.\n\n          Override this property if logging is disabled to avoid the\n          overhead of calculating details to feed the logger.\n          \"\"\"\n          return self.logger.isEnabledFor(logging.INFO)\n\n\nThis also allows access to the results of coroutines on the ``request`` and\n``response``, e.g. ``request.text()``.\n\n.. _gunicorn-accesslog:\n\nGunicorn access logs\n^^^^^^^^^^^^^^^^^^^^\nWhen `Gunicorn <https://gunicorn.org>`_ is used for\n:ref:`deployment <aiohttp-deployment-gunicorn>`, its default access log format\nwill be automatically replaced with the default aiohttp's access log format.\n\nIf Gunicorn's option access_logformat_ is\nspecified explicitly, it should use aiohttp's format specification.\n\nGunicorn's access log works only if accesslog_ is specified explicitly in your\nconfig or as a command line option.\nThis configuration can be either a path or ``'-'``. If the application uses\na custom logging setup intercepting the ``'gunicorn.access'`` logger,\naccesslog_ should be set to ``'-'`` to prevent Gunicorn to create an empty\naccess log file upon every startup.\n\nError logs\n----------\n\n:mod:`aiohttp.web` uses a logger named ``'aiohttp.server'`` to store errors\ngiven on web requests handling.\n\nThis log is enabled by default.\n\nTo use a different logger name, pass *logger* (:class:`logging.Logger`\ninstance) to the :meth:`aiohttp.web.AppRunner` constructor.\n\n\n.. _access_logformat:\n    http://docs.gunicorn.org/en/stable/settings.html#access-log-format\n\n.. _accesslog:\n    http://docs.gunicorn.org/en/stable/settings.html#accesslog\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\r\n\r\nREM Command file for Sphinx documentation\r\n\r\nif \"%SPHINXBUILD%\" == \"\" (\r\n\tset SPHINXBUILD=sphinx-build\r\n)\r\nset BUILDDIR=_build\r\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .\r\nset I18NSPHINXOPTS=%SPHINXOPTS% .\r\nif NOT \"%PAPER%\" == \"\" (\r\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\r\n\tset I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%\r\n)\r\n\r\nif \"%1\" == \"\" goto help\r\n\r\nif \"%1\" == \"help\" (\r\n\t:help\r\n\techo.Please use `make ^<target^>` where ^<target^> is one of\r\n\techo.  html       to make standalone HTML files\r\n\techo.  dirhtml    to make HTML files named index.html in directories\r\n\techo.  singlehtml to make a single large HTML file\r\n\techo.  pickle     to make pickle files\r\n\techo.  json       to make JSON files\r\n\techo.  htmlhelp   to make HTML files and a HTML help project\r\n\techo.  qthelp     to make HTML files and a qthelp project\r\n\techo.  devhelp    to make HTML files and a Devhelp project\r\n\techo.  epub       to make an epub\r\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\r\n\techo.  text       to make text files\r\n\techo.  man        to make manual pages\r\n\techo.  texinfo    to make Texinfo files\r\n\techo.  gettext    to make PO message catalogs\r\n\techo.  changes    to make an overview over all changed/added/deprecated items\r\n\techo.  xml        to make Docutils-native XML files\r\n\techo.  pseudoxml  to make pseudoxml-XML files for display purposes\r\n\techo.  linkcheck  to check all external links for integrity\r\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"clean\" (\r\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\r\n\tdel /q /s %BUILDDIR%\\*\r\n\tgoto end\r\n)\r\n\r\n\r\n%SPHINXBUILD% 2> nul\r\nif errorlevel 9009 (\r\n\techo.\r\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\r\n\techo.installed, then set the SPHINXBUILD environment variable to point\r\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\r\n\techo.may add the Sphinx directory to PATH.\r\n\techo.\r\n\techo.If you don't have Sphinx installed, grab it from\r\n\techo.http://sphinx-doc.org/\r\n\texit /b 1\r\n)\r\n\r\nif \"%1\" == \"html\" (\r\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"dirhtml\" (\r\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"singlehtml\" (\r\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"pickle\" (\r\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can process the pickle files.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"json\" (\r\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can process the JSON files.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"htmlhelp\" (\r\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can run HTML Help Workshop with the ^\r\n.hhp project file in %BUILDDIR%/htmlhelp.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"qthelp\" (\r\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\r\n.qhcp project file in %BUILDDIR%/qthelp, like this:\r\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\aiohttp.qhcp\r\n\techo.To view the help file:\r\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\aiohttp.ghc\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"devhelp\" (\r\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"epub\" (\r\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latex\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latexpdf\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tcd %BUILDDIR%/latex\r\n\tmake all-pdf\r\n\tcd %BUILDDIR%/..\r\n\techo.\r\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latexpdfja\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tcd %BUILDDIR%/latex\r\n\tmake all-pdf-ja\r\n\tcd %BUILDDIR%/..\r\n\techo.\r\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"text\" (\r\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The text files are in %BUILDDIR%/text.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"man\" (\r\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"texinfo\" (\r\n\t%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"gettext\" (\r\n\t%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The message catalogs are in %BUILDDIR%/locale.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"changes\" (\r\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.The overview file is in %BUILDDIR%/changes.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"linkcheck\" (\r\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Link check complete; look for any errors in the above output ^\r\nor in %BUILDDIR%/linkcheck/output.txt.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"doctest\" (\r\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Testing of doctests in the sources finished, look at the ^\r\nresults in %BUILDDIR%/doctest/output.txt.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"xml\" (\r\n\t%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The XML files are in %BUILDDIR%/xml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"pseudoxml\" (\r\n\t%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.\r\n\tgoto end\r\n)\r\n\r\n:end\r\n"
  },
  {
    "path": "docs/migration_to_2xx.rst",
    "content": ".. _aiohttp-migration:\n\nMigration to 2.x\n================\n\nClient\n------\n\nchunking\n^^^^^^^^\n\naiohttp does not support custom chunking sizes. It is up to the developer\nto decide how to chunk data streams. If chunking is enabled, aiohttp\nencodes the provided chunks in the \"Transfer-encoding: chunked\" format.\n\naiohttp does not enable chunked encoding automatically even if a\n*transfer-encoding* header is supplied: *chunked* has to be set\nexplicitly. If *chunked* is set, then the *Transfer-encoding* and\n*content-length* headers are disallowed.\n\ncompression\n^^^^^^^^^^^\n\nCompression has to be enabled explicitly with the *compress* parameter.\nIf compression is enabled, adding a *content-encoding* header is not allowed.\nCompression also enables the *chunked* transfer-encoding.\nCompression can not be combined with a *Content-Length* header.\n\n\nClient Connector\n^^^^^^^^^^^^^^^^\n\n1. By default a connector object manages a total number of concurrent\n   connections.  This limit was a per host rule in version 1.x. In\n   2.x, the `limit` parameter defines how many concurrent connection\n   connector can open and a new `limit_per_host` parameter defines the\n   limit per host. By default there is no per-host limit.\n2. BaseConnector.close is now a normal function as opposed to\n   coroutine in version 1.x\n3. BaseConnector.conn_timeout was moved to ClientSession\n\n\nClientResponse.release\n^^^^^^^^^^^^^^^^^^^^^^\n\nInternal implementation was significantly redesigned. It is not\nrequired to call `release` on the response object. When the client\nfully receives the payload, the underlying connection automatically\nreturns back to pool. If the payload is not fully read, the connection\nis closed\n\n\nClient exceptions\n^^^^^^^^^^^^^^^^^\n\nException hierarchy has been significantly modified. aiohttp now defines only\nexceptions that covers connection handling and server response misbehaviors.\nFor developer specific mistakes, aiohttp uses python standard exceptions\nlike ValueError or TypeError.\n\nReading a response content may raise a ClientPayloadError\nexception. This exception indicates errors specific to the payload\nencoding. Such as invalid compressed data, malformed chunked-encoded\nchunks or not enough data that satisfy the content-length header.\n\nAll exceptions are moved from `aiohttp.errors` module to top level\n`aiohttp` module.\n\nNew hierarchy of exceptions:\n\n* `ClientError` - Base class for all client specific exceptions\n\n  - `ClientResponseError` - exceptions that could happen after we get\n    response from server\n\n    * `WSServerHandshakeError` - web socket server response error\n\n      - `ClientHttpProxyError` - proxy response\n\n  - `ClientConnectionError` - exceptions related to low-level\n    connection problems\n\n    * `ClientOSError` - subset of connection errors that are initiated\n      by an OSError exception\n\n      - `ClientConnectorError` - connector related exceptions\n\n        * `ClientProxyConnectionError` - proxy connection initialization error\n\n          - `ServerConnectionError` - server connection related errors\n\n        * `ServerDisconnectedError` - server disconnected\n\n        * `ServerTimeoutError` - server operation timeout, (read timeout, etc)\n\n        * `ServerFingerprintMismatch` - server fingerprint mismatch\n\n  - `ClientPayloadError` - This exception can only be raised while\n    reading the response payload if one of these errors occurs:\n    invalid compression, malformed chunked encoding or not enough data\n    that satisfy content-length header.\n\n\nClient payload (form-data)\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTo unify form-data/payload handling a new `Payload` system was\nintroduced. It handles customized handling of existing types and\nprovide implementation for user-defined types.\n\n1. FormData.__call__ does not take an encoding arg anymore\n   and its return value changes from an iterator or bytes to a Payload instance.\n   aiohttp provides payload adapters for some standard types like `str`, `byte`,\n   `io.IOBase`, `StreamReader` or `DataQueue`.\n\n2. a generator is not supported as data provider anymore, `streamer`\n   can be used instead.  For example, to upload data from file::\n\n     @aiohttp.streamer\n     def file_sender(writer, file_name=None):\n           with open(file_name, 'rb') as f:\n               chunk = f.read(2**16)\n               while chunk:\n                   yield from writer.write(chunk)\n                   chunk = f.read(2**16)\n\n     # Then you can use `file_sender` like this:\n\n     async with session.post('http://httpbin.org/post',\n                             data=file_sender(file_name='huge_file')) as resp:\n            print(await resp.text())\n\n\nVarious\n^^^^^^^\n\n1. the `encoding` parameter is deprecated in `ClientSession.request()`.\n   Payload encoding is controlled at the payload level.\n   It is possible to specify an encoding for each payload instance.\n\n2. the `version` parameter is removed in `ClientSession.request()`\n   client version can be specified in the `ClientSession` constructor.\n\n3. `aiohttp.MsgType` dropped, use `aiohttp.WSMsgType` instead.\n\n4. `ClientResponse.url` is an instance of `yarl.URL` class (`url_obj`\n   is deprecated)\n\n5. `ClientResponse.raise_for_status()` raises\n   :exc:`aiohttp.ClientResponseError` exception\n\n6. `ClientResponse.json()` is strict about response's content type. if\n   content type does not match, it raises\n   :exc:`aiohttp.ClientResponseError` exception.  To disable content\n   type check you can pass ``None`` as `content_type` parameter.\n\n\n\n\nServer\n------\n\nServerHttpProtocol and low-level details\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nInternal implementation was significantly redesigned to provide\nbetter performance and support HTTP pipelining.\nServerHttpProtocol is dropped, implementation is merged with RequestHandler\na lot of low-level api's are dropped.\n\n\nApplication\n^^^^^^^^^^^\n\n1. Constructor parameter `loop` is deprecated. Loop is get configured by application runner,\n   `run_app` function for any of gunicorn workers.\n\n2. `Application.router.add_subapp` is dropped, use `Application.add_subapp` instead\n\n3. `Application.finished` is dropped, use `Application.cleanup` instead\n\n\nWebRequest and WebResponse\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n1. the `GET` and `POST` attributes no longer exist. Use the `query` attribute instead of `GET`\n\n2. Custom chunking size is not support `WebResponse.chunked` - developer is\n   responsible for actual chunking.\n\n3. Payloads are supported as body. So it is possible to use client response's content\n   object as body parameter for `WebResponse`\n\n4. `FileSender` api is dropped, it is replaced with more general `FileResponse` class::\n\n     async def handle(request):\n         return web.FileResponse('path-to-file.txt')\n\n5. `WebSocketResponse.protocol` is renamed to `WebSocketResponse.ws_protocol`.\n   `WebSocketResponse.protocol` is instance of `RequestHandler` class.\n\n\n\nRequestPayloadError\n^^^^^^^^^^^^^^^^^^^\n\nReading request's payload may raise a `RequestPayloadError` exception. The behavior is similar\nto `ClientPayloadError`.\n\n\nWSGI\n^^^^\n\n*WSGI* support has been dropped, as well as gunicorn wsgi support. We still provide default and uvloop gunicorn workers for `web.Application`\n"
  },
  {
    "path": "docs/misc.rst",
    "content": ".. _aiohttp-misc:\n\nMiscellaneous\n=============\n\nHelpful pages.\n\n.. toctree::\n   :name: misc\n\n   essays\n   glossary\n\n.. toctree::\n   :titlesonly:\n\n   changes\n\nIndices and tables\n------------------\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n"
  },
  {
    "path": "docs/multipart.rst",
    "content": ".. currentmodule:: aiohttp\n\n.. _aiohttp-multipart:\n\nWorking with Multipart\n======================\n\n``aiohttp`` supports a full featured multipart reader and writer. Both\nare designed with streaming processing in mind to avoid unwanted\nfootprint which may be significant if you're dealing with large\npayloads, but this also means that most I/O operation are only\npossible to be executed a single time.\n\nReading Multipart Responses\n---------------------------\n\nAssume you made a request, as usual, and want to process the response multipart\ndata::\n\n    async with aiohttp.request(...) as resp:\n        pass\n\nFirst, you need to wrap the response with a\n:meth:`MultipartReader.from_response`. This needs to keep the implementation of\n:class:`MultipartReader` separated from the response and the connection routines\nwhich makes it more portable::\n\n    reader = aiohttp.MultipartReader.from_response(resp)\n\nLet's assume with this response you'd received some JSON document and multiple\nfiles for it, but you don't need all of them, just a specific one.\n\nSo first you need to enter into a loop where the multipart body will\nbe processed::\n\n    metadata = None\n    filedata = None\n    while True:\n        part = await reader.next()\n\nThe returned type depends on what the next part is: if it's a simple body part\nthen you'll get :class:`BodyPartReader` instance here, otherwise, it will\nbe another :class:`MultipartReader` instance for the nested multipart. Remember,\nthat multipart format is recursive and supports multiple levels of nested body\nparts. When there are no more parts left to fetch, ``None`` value will be\nreturned - that's the signal to break the loop::\n\n    if part is None:\n        break\n\nBoth :class:`BodyPartReader` and :class:`MultipartReader` provides access to\nbody part headers: this allows you to filter parts by their attributes::\n\n    if part.headers[aiohttp.hdrs.CONTENT_TYPE] == 'application/json':\n        metadata = await part.json()\n        continue\n\nNeither :class:`BodyPartReader` nor :class:`MultipartReader` instances\nread the whole body part data without explicitly asking for.\n:class:`BodyPartReader` provides a set of helpers methods\nto fetch popular content types in friendly way:\n\n- :meth:`BodyPartReader.text` for plain text data;\n- :meth:`BodyPartReader.json` for JSON;\n- :meth:`BodyPartReader.form` for `application/www-urlform-encode`\n\nEach of these methods automatically recognizes if content is compressed by\nusing `gzip` and `deflate` encoding (while it respects `identity` one), or if\ntransfer encoding is base64 or `quoted-printable` - in each case the result\nwill get automatically decoded. But in case you need to access to raw binary\ndata as it is, there are :meth:`BodyPartReader.read` and\n:meth:`BodyPartReader.read_chunk` coroutine methods as well to read raw binary\ndata as it is all-in-single-shot or by chunks respectively.\n\nWhen you have to deal with multipart files, the :attr:`BodyPartReader.filename`\nproperty comes to help. It's a very smart helper which handles\n`Content-Disposition` handler right and extracts the right filename attribute\nfrom it::\n\n    if part.filename != 'secret.txt':\n        continue\n\nIf current body part does not matches your expectation and you want to skip it\n- just continue a loop to start a next iteration of it. Here is where magic\nhappens. Before fetching the next body part ``await reader.next()`` it\nensures that the previous one was read completely. If it was not, all its content\nsends to the void in term to fetch the next part. So you don't have to care\nabout cleanup routines while you're within a loop.\n\nOnce you'd found a part for the file you'd searched for, just read it. Let's\nhandle it as it is without applying any decoding magic::\n\n    filedata = await part.read(decode=False)\n\nLater you may decide to decode the data. It's still simple and possible\nto do::\n\n    filedata = part.decode(filedata)\n\nOnce you are done with multipart processing, just break a loop::\n\n    break\n\n\nSending Multipart Requests\n--------------------------\n\n:class:`MultipartWriter` provides an interface to build multipart payload from\nthe Python data and serialize it into chunked binary stream. Since multipart\nformat is recursive and supports deeply nesting, you can use ``with`` statement\nto design your multipart data closer to how it will be::\n\n    with aiohttp.MultipartWriter('mixed') as mpwriter:\n        ...\n        with aiohttp.MultipartWriter('related') as subwriter:\n            ...\n        mpwriter.append(subwriter)\n\n        with aiohttp.MultipartWriter('related') as subwriter:\n            ...\n            with aiohttp.MultipartWriter('related') as subsubwriter:\n                ...\n            subwriter.append(subsubwriter)\n        mpwriter.append(subwriter)\n\n        with aiohttp.MultipartWriter('related') as subwriter:\n            ...\n        mpwriter.append(subwriter)\n\nThe :meth:`MultipartWriter.append` is used to join new body parts into a\nsingle stream. It accepts various inputs and determines what default headers\nshould be used for.\n\nFor text data default `Content-Type` is :mimetype:`text/plain; charset=utf-8`::\n\n    mpwriter.append('hello')\n\nFor binary data :mimetype:`application/octet-stream` is used::\n\n    mpwriter.append(b'aiohttp')\n\nYou can always override these default by passing your own headers with\nthe second argument::\n\n    mpwriter.append(io.BytesIO(b'GIF89a...'),\n                    {'CONTENT-TYPE': 'image/gif'})\n\nFor file objects `Content-Type` will be determined by using Python's\nmod:`mimetypes` module and additionally `Content-Disposition` header\nwill include the file's basename::\n\n    part = root.append(open(__file__, 'rb'))\n\nIf you want to send a file with a different name, just handle the\n:class:`~aiohttp.payload.Payload` instance which :meth:`MultipartWriter.append` will\nalways return and set `Content-Disposition` explicitly by using\nthe :meth:`Payload.set_content_disposition() <aiohttp.payload.Payload.set_content_disposition>` helper::\n\n    part.set_content_disposition('attachment', filename='secret.txt')\n\nAdditionally, you may want to set other headers here::\n\n    part.headers[aiohttp.hdrs.CONTENT_ID] = 'X-12345'\n\nIf you'd set `Content-Encoding`, it will be automatically applied to the\ndata on serialization (see below)::\n\n    part.headers[aiohttp.hdrs.CONTENT_ENCODING] = 'gzip'\n\nThere are also :meth:`MultipartWriter.append_json` and\n:meth:`MultipartWriter.append_form` helpers which are useful to work with JSON\nand form urlencoded data, so you don't have to encode it every time manually::\n\n    mpwriter.append_json({'test': 'passed'})\n    mpwriter.append_form([('key', 'value')])\n\nWhen it's done, to make a request just pass a root :class:`MultipartWriter`\ninstance as :meth:`aiohttp.ClientSession.request` ``data`` argument::\n\n    await session.post('http://example.com', data=mpwriter)\n\nBehind the scenes :meth:`MultipartWriter.write` will yield chunks of every\npart and if body part has `Content-Encoding` or `Content-Transfer-Encoding`\nthey will be applied on streaming content.\n\nPlease note, that on :meth:`MultipartWriter.write` all the file objects\nwill be read until the end and there is no way to repeat a request without\nrewinding their pointers to the start.\n\nExample MJPEG Streaming ``multipart/x-mixed-replace``. By default\n:meth:`MultipartWriter.write` appends closing ``--boundary--`` and breaks your\ncontent. Providing `close_boundary = False` prevents this.::\n\n    my_boundary = 'some-boundary'\n    response = web.StreamResponse(\n        status=200,\n        reason='OK',\n        headers={\n            'Content-Type': 'multipart/x-mixed-replace;boundary={}'.format(my_boundary)\n        }\n    )\n    while True:\n        frame = get_jpeg_frame()\n        with MultipartWriter('image/jpeg', boundary=my_boundary) as mpwriter:\n            mpwriter.append(frame, {\n                'Content-Type': 'image/jpeg'\n            })\n            await mpwriter.write(response, close_boundary=False)\n        await response.drain()\n\nHacking Multipart\n-----------------\n\nThe Internet is full of terror and sometimes you may find a server which\nimplements multipart support in strange ways when an oblivious solution\ndoes not work.\n\nFor instance, is server used :class:`cgi.FieldStorage` then you have\nto ensure that no body part contains a `Content-Length` header::\n\n    for part in mpwriter:\n        part.headers.pop(aiohttp.hdrs.CONTENT_LENGTH, None)\n\nOn the other hand, some server may require to specify `Content-Length` for the\nwhole multipart request. `aiohttp` does not do that since it sends multipart\nusing chunked transfer encoding by default. To overcome this issue, you have\nto serialize a :class:`MultipartWriter` by our own in the way to calculate its\nsize::\n\n    class Writer:\n        def __init__(self):\n            self.buffer = bytearray()\n\n        async def write(self, data):\n            self.buffer.extend(data)\n\n    writer = Writer()\n    await mpwriter.write(writer)\n    await aiohttp.post('http://example.com',\n                       data=writer.buffer, headers=mpwriter.headers)\n\nSometimes the server response may not be well formed: it may or may not\ncontains nested parts. For instance, we request a resource which returns\nJSON documents with the files attached to it. If the document has any\nattachments, they are returned as a nested multipart.\nIf it has not it responds as plain body parts:\n\n.. code-block:: none\n\n    CONTENT-TYPE: multipart/mixed; boundary=--:\n\n    --:\n    CONTENT-TYPE: application/json\n\n    {\"_id\": \"foo\"}\n    --:\n    CONTENT-TYPE: multipart/related; boundary=----:\n\n    ----:\n    CONTENT-TYPE: application/json\n\n    {\"_id\": \"bar\"}\n    ----:\n    CONTENT-TYPE: text/plain\n    CONTENT-DISPOSITION: attachment; filename=bar.txt\n\n    bar! bar! bar!\n    ----:--\n    --:\n    CONTENT-TYPE: application/json\n\n    {\"_id\": \"boo\"}\n    --:\n    CONTENT-TYPE: multipart/related; boundary=----:\n\n    ----:\n    CONTENT-TYPE: application/json\n\n    {\"_id\": \"baz\"}\n    ----:\n    CONTENT-TYPE: text/plain\n    CONTENT-DISPOSITION: attachment; filename=baz.txt\n\n    baz! baz! baz!\n    ----:--\n    --:--\n\nReading such kind of data in single stream is possible, but is not clean at\nall::\n\n    result = []\n    while True:\n        part = await reader.next()\n\n        if part is None:\n            break\n\n        if isinstance(part, aiohttp.MultipartReader):\n            # Fetching files\n            while True:\n                filepart = await part.next()\n                if filepart is None:\n                    break\n                result[-1].append((await filepart.read()))\n\n        else:\n            # Fetching document\n            result.append([(await part.json())])\n\nLet's hack a reader in the way to return pairs of document and reader of the\nrelated files on each iteration::\n\n    class PairsMultipartReader(aiohttp.MultipartReader):\n\n        # keep reference on the original reader\n        multipart_reader_cls = aiohttp.MultipartReader\n\n        async def next(self):\n            \"\"\"Emits a tuple of document object (:class:`dict`) and multipart\n            reader of the followed attachments (if any).\n\n            :rtype: tuple\n            \"\"\"\n            reader = await super().next()\n\n            if self._at_eof:\n                return None, None\n\n            if isinstance(reader, self.multipart_reader_cls):\n                part = await reader.next()\n                doc = await part.json()\n            else:\n                doc = await reader.json()\n\n            return doc, reader\n\nAnd this gives us a more cleaner solution::\n\n    reader = PairsMultipartReader.from_response(resp)\n    result = []\n    while True:\n        doc, files_reader = await reader.next()\n\n        if doc is None:\n            break\n\n        files = []\n        while True:\n            filepart = await files_reader.next()\n            if file.part is None:\n                break\n            files.append((await filepart.read()))\n\n        result.append((doc, files))\n\n.. seealso:: :ref:`aiohttp-multipart-reference`\n"
  },
  {
    "path": "docs/multipart_reference.rst",
    "content": ".. currentmodule:: aiohttp\n\n.. _aiohttp-multipart-reference:\n\nMultipart reference\n===================\n\n.. class:: MultipartResponseWrapper(resp, stream)\n   :canonical: aiohttp.multipart.MultipartResponseWrapper\n\n   Wrapper around the :class:`MultipartReader` to take care about\n   underlying connection and close it when it needs in.\n\n\n   .. method:: at_eof()\n\n      Returns ``True`` when all response data had been read.\n\n      :rtype: bool\n\n   .. method:: next()\n      :async:\n\n      Emits next multipart reader object.\n\n   .. method:: release()\n      :async:\n\n      Releases the connection gracefully, reading all the content\n      to the void.\n\n\n.. class:: BodyPartReader(boundary, headers, content)\n   :canonical: aiohttp.multipart.BodyPartReader\n\n   Multipart reader for single body part.\n\n   .. method:: read(*, decode=False)\n      :async:\n\n      Reads body part data.\n\n      :param bool decode: Decodes data following by encoding method\n                          from ``Content-Encoding`` header. If it\n                          missed data remains untouched\n\n      :rtype: bytearray\n\n   .. method:: read_chunk(size=chunk_size)\n      :async:\n\n      Reads body part content chunk of the specified size.\n\n      :param int size: chunk size\n\n      :rtype: bytearray\n\n   .. method:: readline()\n      :async:\n\n      Reads body part by line by line.\n\n      :rtype: bytearray\n\n   .. method:: release()\n      :async:\n\n      Like :meth:`read`, but reads all the data to the void.\n\n      :rtype: None\n\n   .. method:: text(*, encoding=None)\n      :async:\n\n      Like :meth:`read`, but assumes that body part contains text data.\n\n      :param str encoding: Custom text encoding. Overrides specified\n                           in charset param of ``Content-Type`` header\n\n      :rtype: str\n\n   .. method:: json(*, encoding=None)\n      :async:\n\n      Like :meth:`read`, but assumes that body parts contains JSON data.\n\n      :param str encoding: Custom JSON encoding. Overrides specified\n                           in charset param of ``Content-Type`` header\n\n   .. method:: form(*, encoding=None)\n      :async:\n\n      Like :meth:`read`, but assumes that body parts contains form\n      urlencoded data.\n\n      :param str encoding: Custom form encoding. Overrides specified\n                           in charset param of ``Content-Type`` header\n\n   .. method:: at_eof()\n\n      Returns ``True`` if the boundary was reached or ``False`` otherwise.\n\n      :rtype: bool\n\n   .. method:: decode(data)\n\n      Decodes data synchronously according the specified ``Content-Encoding``\n      or ``Content-Transfer-Encoding`` headers value.\n\n      Supports ``gzip``, ``deflate`` and ``identity`` encodings for\n      ``Content-Encoding`` header.\n\n      Supports ``base64``, ``quoted-printable``, ``binary`` encodings for\n      ``Content-Transfer-Encoding`` header.\n\n      :param bytearray data: Data to decode.\n\n      :raises: :exc:`RuntimeError` - if encoding is unknown.\n\n      :rtype: bytes\n\n      .. note::\n\n         For large payloads, consider using :meth:`decode_iter` instead\n         to avoid blocking the event loop during decompression.\n\n   .. method:: decode_iter(data)\n      :async:\n\n      Decodes data asynchronously according the specified ``Content-Encoding``\n      or ``Content-Transfer-Encoding`` headers value.\n\n      This is an async iterator and will return decoded data in chunks. This\n      can be used to avoid loading large payloads into memory.\n\n      This method offloads decompression to an executor for large payloads\n      to avoid blocking the event loop.\n\n      Supports ``gzip``, ``deflate`` and ``identity`` encodings for\n      ``Content-Encoding`` header.\n\n      Supports ``base64``, ``quoted-printable``, ``binary`` encodings for\n      ``Content-Transfer-Encoding`` header.\n\n      :param bytearray data: Data to decode.\n\n      :raises: :exc:`RuntimeError` - if encoding is unknown.\n\n      :rtype: bytes\n\n      .. versionadded:: 3.13.4\n\n   .. method:: get_charset(default=None)\n\n      Returns charset parameter from ``Content-Type`` header or default.\n\n   .. attribute:: name\n\n      A field *name* specified in ``Content-Disposition`` header or ``None``\n      if missed or header is malformed.\n\n      Readonly :class:`str` property.\n\n   .. attribute:: filename\n\n      A field *filename* specified in ``Content-Disposition`` header or ``None``\n      if missed or header is malformed.\n\n      Readonly :class:`str` property.\n\n\n.. class:: MultipartReader(headers, content)\n   :canonical: aiohttp.multipart.MultipartReader\n\n   Multipart body reader.\n\n   .. classmethod:: from_response(cls, response)\n\n      Constructs reader instance from HTTP response.\n\n      :param response: :class:`~aiohttp.ClientResponse` instance\n\n   .. method:: at_eof()\n\n      Returns ``True`` if the final boundary was reached or\n      ``False`` otherwise.\n\n      :rtype: bool\n\n   .. method:: next()\n      :async:\n\n      Emits the next multipart body part.\n\n   .. method:: release()\n      :async:\n\n      Reads all the body parts to the void till the final boundary.\n\n   .. method:: fetch_next_part()\n      :async:\n\n      Returns the next body part reader.\n\n\n.. class:: MultipartWriter(subtype='mixed', boundary=None, close_boundary=True)\n   :canonical: aiohttp.multipart.MultipartWriter\n\n   Multipart body writer.\n\n   ``boundary`` may be an ASCII-only string.\n\n   .. attribute:: boundary\n\n      The string (:class:`str`) representation of the boundary.\n\n      .. versionchanged:: 3.0\n\n         Property type was changed from :class:`bytes` to :class:`str`.\n\n   .. method:: append(obj, headers=None)\n\n      Append an object to writer.\n\n   .. method:: append_payload(payload)\n\n      Adds a new body part to multipart writer.\n\n   .. method:: append_json(obj, headers=None)\n\n      Helper to append JSON part.\n\n   .. method:: append_form(obj, headers=None)\n\n      Helper to append form urlencoded part.\n\n   .. attribute:: size\n\n      Size of the payload.\n\n   .. method:: write(writer, close_boundary=True)\n      :async:\n\n      Write body.\n\n      :param bool close_boundary: The (:class:`bool`) that will emit\n                                  boundary closing. You may want to disable\n                                  when streaming (``multipart/x-mixed-replace``)\n\n      .. versionadded:: 3.4\n\n         Support ``close_boundary`` argument.\n"
  },
  {
    "path": "docs/new_router.rst",
    "content": ".. _aiohttp-router-refactoring-021:\n\nRouter refactoring in 0.21\n==========================\n\nRationale\n---------\n\nFirst generation (v1) of router has mapped ``(method, path)`` pair to\n:term:`web-handler`.  Mapping is named **route**. Routes used to have\nunique names if any.\n\nThe main mistake with the design is coupling the **route** to\n``(method, path)`` pair while really URL construction operates with\n**resources** (**location** is a synonym). HTTP method is not part of URI\nbut applied on sending HTTP request only.\n\nHaving different **route names** for the same path is confusing. Moreover\n**named routes** constructed for the same path should have unique\nnon overlapping names which is cumbersome is certain situations.\n\nFrom other side sometimes it's desirable to bind several HTTP methods\nto the same web handler. For *v1* router it can be solved by passing '*'\nas HTTP method. Class based views require '*' method also usually.\n\n\nImplementation\n--------------\n\nThe change introduces **resource** as first class citizen::\n\n   resource = router.add_resource('/path/{to}', name='name')\n\n*Resource* has a **path** (dynamic or constant) and optional **name**.\n\nThe name is **unique** in router context.\n\n*Resource* has **routes**.\n\n*Route* corresponds to *HTTP method* and :term:`web-handler` for the method::\n\n   route = resource.add_route('GET', handler)\n\nUser still may use wildcard for accepting all HTTP methods (maybe we\nwill add something like ``resource.add_wildcard(handler)`` later).\n\nSince **names** belongs to **resources** now ``app.router['name']``\nreturns a **resource** instance instead of :class:`aiohttp.web.AbstractRoute`.\n\n**resource** has ``.url()`` method, so\n``app.router['name'].url(parts={'a': 'b'}, query={'arg': 'param'})``\nstill works as usual.\n\n\nThe change allows to rewrite static file handling and implement nested\napplications as well.\n\nDecoupling of *HTTP location* and *HTTP method* makes life easier.\n\nBackward compatibility\n----------------------\n\nThe refactoring is 99% compatible with previous implementation.\n\n99% means all example and the most of current code works without\nmodifications but we have subtle API backward incompatibles.\n\n``app.router['name']`` returns a :class:`aiohttp.web.AbstractResource`\ninstance instead of :class:`aiohttp.web.AbstractRoute` but resource has the\nsame ``resource.url(...)`` most useful method, so end user should feel no\ndifference.\n\n``route.match(...)`` is **not** supported anymore, use\n:meth:`aiohttp.web.AbstractResource.resolve` instead.\n\n``app.router.add_route(method, path, handler, name='name')`` now is just\nshortcut for::\n\n    resource = app.router.add_resource(path, name=name)\n    route = resource.add_route(method, handler)\n    return route\n\n``app.router.register_route(...)`` is still supported, it creates\n``aiohttp.web.ResourceAdapter`` for every call (but it's deprecated now).\n"
  },
  {
    "path": "docs/powered_by.rst",
    "content": ".. _aiohttp-powered-by:\n\nPowered by aiohttp\n==================\n\nWeb sites powered by aiohttp.\n\nFeel free to fork documentation on github, add a link to your site and\nmake a Pull Request!\n\n* `Farmer Business Network <https://www.farmersbusinessnetwork.com>`_\n* `Home Assistant <https://home-assistant.io>`_\n* `KeepSafe <https://www.getkeepsafe.com/>`_\n* `Skyscanner Hotels <https://www.skyscanner.net/hotels>`_\n* `Ocean S.A. <https://ocean.io/>`_\n* `GNS3 <http://gns3.com>`_\n* `TutorCruncher socket\n  <https://tutorcruncher.com/features/tutorcruncher-socket/>`_\n* `Eyepea - Custom telephony solutions <http://www.eyepea.eu>`_\n* `ALLOcloud - Telephony in the cloud <https://www.allocloud.com>`_\n* `helpmanual - comprehensive help and man page database\n  <https://helpmanual.io/>`_\n* `bedevere <https://github.com/python/bedevere>`_ - CPython's GitHub\n  bot, helps maintain and identify issues with a CPython pull request.\n* `miss-islington <https://github.com/python/miss-islington>`_ -\n  CPython's GitHub bot, backports and merge CPython's pull requests\n* `noa technologies - Bike-sharing management platform\n  <https://noa.one/>`_ - SSE endpoint, pushes real time updates of\n  bikes location.\n* `Wargaming: World of Tanks <https://worldoftanks.ru/>`_\n* `Yandex <https://yandex.ru>`_\n* `Rambler <https://rambler.ru>`_\n* `Escargot <https://escargot.log1p.xyz>`_ - Chat server\n* `Prom.ua <https://prom.ua/>`_ - Online trading platform\n* `globo.com <https://www.globo.com/>`_ - (some parts) Brazilian largest media portal\n* `Glose <https://www.glose.com/>`_ - Social reader for E-Books\n* `Emoji Generator <https://emoji-gen.ninja>`_ - Text icon generator\n* `SerpsBot Google Search API <https://serpsbot.com>`_ - SerpsBot Google Search API\n* `PyChess <https://www.pychess.org>`_ - Chess variant server\n"
  },
  {
    "path": "docs/spelling_wordlist.txt",
    "content": "abc\naddons\naiodns\naioes\naiohttp\naiohttpdemo\naiohttp’s\naiopg\nal\nalives\napi\napi’s\napp\napp’s\napps\narg\nargs\narmv\nArsenic\nasync\nasyncio\nasyncpg\nasynctest\nattrs\nauth\nautocalculated\nautodetection\nautoformatter\nautoformatters\nautogenerates\nautogeneration\nawaitable\nbackoff\nbackend\nbackends\nbackport\nBackport\nBackporting\nbackports\nBaseEventLoop\nbasename\nBasicAuth\nbehaviour\nBodyPartReader\nboolean\nbotocore\nbrotli\nBrotli\nbrotlicffi\nbrotlipy\nbugfix\nbugfixes\nBugfixes\nbuiltin\nBytesIO\ncallables\ncancelled\ncanonicalization\ncanonicalize\ncchardet\ncChardet\nceil\nchangelog\nChangelog\nchardet\nChardet\ncharset\ncharsetdetect\nchunked\nchunking\nCIMultiDict\nClientSession\ncls\ncmd\ncodebase\ncodec\nCodings\ncommitter\ncommitters\nconfig\nConfig\nconfigs\nconjunction\ncontextmanager\nCookieJar\ncoroutine\nCoroutine\ncoroutines\ncpu\nCPython\ncss\nctor\nCtrl\ncython\nCython\nCythonize\ncythonized\nde\ndeduplicate\ndefs\nDependabot\ndeprecations\ndeserialization\nDER\ndev\nDev\ndict\nDict\nDiscord\ndjango\nDjango\ndns\nDNSResolver\ndocstring\ndocstrings\nDoS\ndownstreams\nDup\nelasticsearch\nencodings\nenv\nenviron\neof\nepoll\net\netag\nETag\nexpirations\nFacebook\nfacto\nfallback\nfallbacks\nfilename\nfinalizers\nformatter\nformatters\nfrontend\ngetall\ngethostbyname\ngithub\ngoogle\ngunicorn\ngunicorn’s\ngzipped\nhackish\nhighlevel\nhostnames\nHTTPException\nHttpProcessingError\nhttpretty\nhttps\nhostname\nimpl\nincapsulates\nIndices\ninfos\ninitializer\ninline\nintaking\nio\nIoT\nip\nIP\nipdb\nipv\nIPv\nish\nisort\niterable\niterables\njavascript\nJinja\njitter\njson\nkeepalive\nkeepalived\nkeepalives\nkeepaliving\nkib\nKiB\nkwarg\nkwargs\nlatin\nlifecycle\nlinux\nllhttp\nlocalhost\nLocator\nlogin\nlookup\nlookups\nlossless\nlowercased\nMako\nmanylinux\nmetadata\nMiB\nmicroservice\nmiddleware\nmiddlewares\nmiltidict\nmisbehaviors\nMixcloud\nMongo\nmsg\nMsgType\nmulti\nmultidict\nmultidict’s\nmultidicts\nMultidicts\nmultipart\nMultipart\nmusllinux\nmypy\nNagle\nNagle's\nNFS\nnamedtuple\nnameservers\nnamespace\nnetrc\nnginx\nNginx\nNikolay\nnoop\nnormalizer\nnowait\nOAuth\nOnline\noptimizations\norjson\nos\noutcoming\nOverridable\nPaolini\nparam\nparams\nparsers\npathlib\npayloads\npeername\nperformant\npickleable\nping\npipelining\npluggable\nplugin\npoller\npong\nPostgres\npre\npreloaded\nproactor\nprogrammatically\nproxied\nPRs\npubsub\nPunycode\npy\npydantic\npyenv\npyflakes\npyright\npytest\nPytest\nqop\nQuickstart\nquickstart\nquote’s\nrc\nreadline\nreadonly\nreadpayload\nrebase\nredirections\nRedis\nrefactor\nrefactored\nrefactoring\nreferenceable\nregex\nregexps\nregexs\nreloader\nrenderer\nrenderers\nrepo\nrepr\nrepr’s\nRequestContextManager\nrequest’s\nRequest’s\nrequote\nrequoting\nresolvehost\nresolvers\nreusage\nreuseconn\nriscv64\nRunit\nruntime\nruntimes\nsa\nSatisfiable\nscalability\nschemas\nseekable\nsendfile\nserializable\nserializer\nshourtcuts\nskipuntil\nSkyscanner\nSocketSocketTransport\nssl\nSSLContext\nstartup\nstateful\nstorages\nsubapplication\nsubclassed\nsubclasses\nsubdirectory\nsubmodules\nsubpackage\nsubprotocol\nsubprotocols\nsubtype\nsupervisord\nSupervisord\nSvetlov\nsymlink\nsymlinks\nsyscall\nsyscalls\nSystemd\ntarball\nTCP\nteardown\nTeardown\nTestClient\nTestsuite\nTf\ntimestamps\nTLS\ntmp\ntmpdir\ntoolbar\ntoplevel\ntowncrier\ntp\ntuples\nUI\nun\nunawaited\nunclosed\nundercounting\nunescaped\nunhandled\nunicode\nunittest\nUnittest\nunpickler\nuntrusted\nunix\nunobvious\nunsets\nunstripped\nuntyped\nuppercased\nupstr\nurl\nurldispatcher\nurlencoded\nurl’s\nurls\nutf\nutils\nuvloop\nuWSGI\nvcvarsall\nvendored\nvendoring\nwaituntil\nwakeup\nwakeups\nwebapp\nwebsocket\nwebsocket’s\nwebsockets\nWebsockets\nwildcard\nWinloop\nWorkflow\nws\nwsgi\nwss\nwww\nxxx\nyarl\nzlib\nzstandard\nzstd\n"
  },
  {
    "path": "docs/streams.rst",
    "content": ".. currentmodule:: aiohttp\n\n.. _aiohttp-streams:\n\nStreaming API\n=============\n\n\n``aiohttp`` uses streams for retrieving *BODIES*:\n:attr:`aiohttp.web.BaseRequest.content` and\n:attr:`aiohttp.ClientResponse.content` are properties with stream API.\n\n\n.. class:: StreamReader\n   :canonical: aiohttp.streams.StreamReader\n\n   The reader from incoming stream.\n\n   User should never instantiate streams manually but use existing\n   :attr:`aiohttp.web.BaseRequest.content` and\n   :attr:`aiohttp.ClientResponse.content` properties for accessing raw\n   BODY data.\n\nReading Attributes and Methods\n------------------------------\n\n.. method:: StreamReader.read(n=-1)\n      :async:\n\n   Read up to a maximum of *n* bytes. If *n* is not provided, or set to ``-1``,\n   read until EOF and return all read bytes.\n\n   When *n* is provided, data will be returned as soon as it is available.\n   Therefore it will return less than *n* bytes if there are less than *n*\n   bytes in the buffer.\n\n   If the EOF was received and the internal buffer is empty, return an\n   empty bytes object.\n\n   :param int n: maximum number of bytes to read, ``-1`` for the whole stream.\n\n   :return bytes: the given data\n\n.. method:: StreamReader.readany()\n      :async:\n\n   Read next data portion for the stream.\n\n   Returns immediately if internal buffer has a data.\n\n   :return bytes: the given data\n\n.. method:: StreamReader.readexactly(n)\n      :async:\n\n   Read exactly *n* bytes.\n\n   Raise an :exc:`asyncio.IncompleteReadError` if the end of the\n   stream is reached before *n* can be read, the\n   :attr:`asyncio.IncompleteReadError.partial` attribute of the\n   exception contains the partial read bytes.\n\n   :param int n: how many bytes to read.\n\n   :return bytes: the given data\n\n\n.. method:: StreamReader.readline()\n      :async:\n\n   Read one line, where “line” is a sequence of bytes ending\n   with ``\\n``.\n\n   If EOF is received, and ``\\n`` was not found, the method will\n   return the partial read bytes.\n\n   If the EOF was received and the internal buffer is empty, return an\n   empty bytes object.\n\n   :return bytes: the given line\n\n.. method:: StreamReader.readuntil(separator=\"\\n\")\n      :async:\n\n   Read until separator, where `separator` is a sequence of bytes.\n\n   If EOF is received, and `separator` was not found, the method will\n   return the partial read bytes.\n\n   If the EOF was received and the internal buffer is empty, return an\n   empty bytes object.\n\n   .. versionadded:: 3.8\n\n   :return bytes: the given data\n\n.. method:: StreamReader.readchunk()\n      :async:\n\n   Read a chunk of data as it was received by the server.\n\n   Returns a tuple of (data, end_of_HTTP_chunk).\n\n   When chunked transfer encoding is used, end_of_HTTP_chunk is a :class:`bool`\n   indicating if the end of the data corresponds to the end of a HTTP chunk,\n   otherwise it is always ``False``.\n\n   :return tuple[bytes, bool]: a chunk of data and a :class:`bool` that is ``True``\n                               when the end of the returned chunk corresponds\n                               to the end of a HTTP chunk.\n\n\n.. attribute:: StreamReader.total_raw_bytes\n\n   The number of bytes of raw data downloaded (before decompression).\n\n   Readonly :class:`int` property.\n\n\nAsynchronous Iteration Support\n------------------------------\n\n\nStream reader supports asynchronous iteration over BODY.\n\nBy default it iterates over lines::\n\n   async for line in response.content:\n       print(line)\n\nAlso there are methods for iterating over data chunks with maximum\nsize limit and over any available data.\n\n.. method:: StreamReader.iter_chunked(n)\n   :async:\n\n   Iterates over data chunks with maximum size limit::\n\n      async for data in response.content.iter_chunked(1024):\n          print(data)\n\n   To get chunks that are exactly *n* bytes, you could use the\n   `asyncstdlib.itertools <https://asyncstdlib.readthedocs.io/en/stable/source/api/itertools.html>`_\n   module::\n\n      chunks = batched(chain.from_iterable(response.content.iter_chunked(n)), n)\n      async for data in chunks:\n          print(data)\n\n.. method:: StreamReader.iter_any()\n   :async:\n\n   Iterates over data chunks in order of intaking them into the stream::\n\n      async for data in response.content.iter_any():\n          print(data)\n\n.. method:: StreamReader.iter_chunks()\n   :async:\n\n   Iterates over data chunks as received from the server::\n\n      async for data, _ in response.content.iter_chunks():\n          print(data)\n\n   If chunked transfer encoding is used, the original http chunks formatting\n   can be retrieved by reading the second element of returned tuples::\n\n      buffer = b\"\"\n\n      async for data, end_of_http_chunk in response.content.iter_chunks():\n          buffer += data\n          if end_of_http_chunk:\n              print(buffer)\n              buffer = b\"\"\n\n\nHelpers\n-------\n\n.. method:: StreamReader.exception()\n\n   Get the exception occurred on data reading.\n\n.. method:: is_eof()\n\n   Return ``True`` if EOF was reached.\n\n   Internal buffer may be not empty at the moment.\n\n   .. seealso::\n\n      :meth:`StreamReader.at_eof`\n\n.. method:: StreamReader.at_eof()\n\n   Return ``True`` if the buffer is empty and EOF was reached.\n\n.. method:: StreamReader.read_nowait(n=None)\n\n   Returns data from internal buffer if any, empty bytes object otherwise.\n\n   Raises :exc:`RuntimeError` if other coroutine is waiting for stream.\n\n\n   :param int n: how many bytes to read, ``-1`` for the whole internal\n                 buffer.\n\n   :return bytes: the given data\n\n.. method:: StreamReader.unread_data(data)\n\n   Rollback reading some data from stream, inserting it to buffer head.\n\n   :param bytes data: data to push back into the stream.\n\n   .. warning:: The method does not wake up waiters.\n\n      E.g. :meth:`~StreamReader.read` will not be resumed.\n\n\n.. method:: wait_eof()\n      :async:\n\n   Wait for EOF. The given data may be accessible by upcoming read calls.\n"
  },
  {
    "path": "docs/structures.rst",
    "content": ".. currentmodule:: aiohttp\n\n\n.. _aiohttp-structures:\n\n\nCommon data structures\n======================\n\nCommon data structures used by *aiohttp* internally.\n\n\nFrozenList\n----------\n\nA list-like structure which implements\n:class:`collections.abc.MutableSequence`.\n\nThe list is *mutable* unless :meth:`FrozenList.freeze` is called,\nafter that the list modification raises :exc:`RuntimeError`.\n\n\n.. class:: FrozenList(items)\n\n   Construct a new *non-frozen* list from *items* iterable.\n\n   The list implements all :class:`collections.abc.MutableSequence`\n   methods plus two additional APIs.\n\n   .. attribute:: frozen\n\n      A read-only property, ``True`` is the list is *frozen*\n      (modifications are forbidden).\n\n   .. method:: freeze()\n\n      Freeze the list. There is no way to *thaw* it back.\n\n\nChainMapProxy\n-------------\n\nAn *immutable* version of :class:`collections.ChainMap`.  Internally\nthe proxy is a list of mappings (dictionaries), if the requested key\nis not present in the first mapping the second is looked up and so on.\n\nThe class supports :class:`collections.abc.Mapping` interface.\n\n.. class:: ChainMapProxy(maps)\n\n   Create a new chained mapping proxy from a list of mappings (*maps*).\n\n   .. versionadded:: 3.2\n"
  },
  {
    "path": "docs/testing.rst",
    "content": ".. module:: aiohttp.test_utils\n\n.. _aiohttp-testing:\n\nTesting\n=======\n\nTesting aiohttp web servers\n---------------------------\n\naiohttp provides plugin for *pytest* making writing web server tests\nextremely easy, it also provides :ref:`test framework agnostic\nutilities <aiohttp-testing-framework-agnostic-utilities>` for testing\nwith other frameworks such as :ref:`unittest\n<aiohttp-testing-unittest-example>`.\n\nBefore starting to write your tests, you may also be interested on\nreading :ref:`how to write testable\nservices<aiohttp-testing-writing-testable-services>` that interact\nwith the loop.\n\n\nFor using pytest plugin please install pytest-aiohttp_ library:\n\n.. code-block:: shell\n\n   $ pip install pytest-aiohttp\n\nIf you don't want to install *pytest-aiohttp* for some reason you may\ninsert ``pytest_plugins = 'aiohttp.pytest_plugin'`` line into\n``conftest.py`` instead for the same functionality.\n\n\n\nThe Test Client and Servers\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n*aiohttp* test utils provides a scaffolding for testing aiohttp-based\nweb servers.\n\nThey consist of two parts: running test server and making HTTP\nrequests to this server.\n\n:class:`~aiohttp.test_utils.TestServer` runs :class:`aiohttp.web.Application`\nbased server, :class:`~aiohttp.test_utils.RawTestServer` starts\n:class:`aiohttp.web.Server` low level server.\n\nFor performing HTTP requests to these servers you have to create a\ntest client: :class:`~aiohttp.test_utils.TestClient` instance.\n\nThe client incapsulates :class:`aiohttp.ClientSession` by providing\nproxy methods to the client for common operations such as\n*ws_connect*, *get*, *post*, etc.\n\n\n\nPytest\n~~~~~~\n\n.. currentmodule:: pytest_aiohttp\n\nThe :data:`aiohttp_client` fixture available from pytest-aiohttp_ plugin\nallows you to create a client to make requests to test your app.\n\nTo run these examples, you need to use `--asyncio-mode=auto` or add to your\npytest config file::\n\n    asyncio_mode = auto\n\nA simple test would be::\n\n    from aiohttp import web\n\n    async def hello(request):\n        return web.Response(text='Hello, world')\n\n    async def test_hello(aiohttp_client):\n        app = web.Application()\n        app.router.add_get('/', hello)\n        client = await aiohttp_client(app)\n        resp = await client.get('/')\n        assert resp.status == 200\n        text = await resp.text()\n        assert 'Hello, world' in text\n\n\nIt also provides access to the app instance allowing tests to check the state\nof the app. Tests can be made even more succinct with a fixture to create an\napp test client::\n\n    import pytest\n    from aiohttp import web\n\n    value = web.AppKey(\"value\", str)\n\n\n    async def previous(request):\n        if request.method == 'POST':\n            request.app[value] = (await request.post())['value']\n            return web.Response(body=b'thanks for the data')\n        return web.Response(\n            body='value: {}'.format(request.app[value]).encode('utf-8'))\n\n    @pytest.fixture\n    async def cli(aiohttp_client):\n        app = web.Application()\n        app.router.add_get('/', previous)\n        app.router.add_post('/', previous)\n        return await aiohttp_client(app)\n\n    async def test_set_value(cli):\n        resp = await cli.post('/', data={'value': 'foo'})\n        assert resp.status == 200\n        assert await resp.text() == 'thanks for the data'\n        assert cli.server.app[value] == 'foo'\n\n    async def test_get_value(cli):\n        cli.server.app[value] = 'bar'\n        resp = await cli.get('/')\n        assert resp.status == 200\n        assert await resp.text() == 'value: bar'\n\n\nPytest tooling has the following fixtures:\n\n.. data:: aiohttp_server(app, *, port=None, **kwargs)\n\n   A fixture factory that creates\n   :class:`~aiohttp.test_utils.TestServer`::\n\n      async def test_f(aiohttp_server):\n          app = web.Application()\n          # fill route table\n\n          server = await aiohttp_server(app)\n\n   The server will be destroyed on exit from test function.\n\n   *app* is the :class:`aiohttp.web.Application` used\n                           to start server.\n\n   *port* optional, port the server is run at, if\n   not provided a random unused port is used.\n\n   .. versionadded:: 3.0\n\n   *kwargs* are parameters passed to\n                  :meth:`aiohttp.web.AppRunner`\n\n   .. versionchanged:: 3.0\n   .. deprecated:: 3.2\n\n      The fixture was renamed from ``test_server`` to ``aiohttp_server``.\n\n\n.. data:: aiohttp_client(app, server_kwargs=None, **kwargs)\n          aiohttp_client(server, **kwargs)\n          aiohttp_client(raw_server, **kwargs)\n\n   A fixture factory that creates\n   :class:`~aiohttp.test_utils.TestClient` for access to tested server::\n\n      async def test_f(aiohttp_client):\n          app = web.Application()\n          # fill route table\n\n          client = await aiohttp_client(app)\n          resp = await client.get('/')\n\n   *client* and responses are cleaned up after test function finishing.\n\n   The fixture accepts :class:`aiohttp.web.Application`,\n   :class:`aiohttp.test_utils.TestServer` or\n   :class:`aiohttp.test_utils.RawTestServer` instance.\n\n   *server_kwargs* are parameters passed to the test server if an app\n   is passed, else ignored.\n\n   *kwargs* are parameters passed to\n   :class:`aiohttp.test_utils.TestClient` constructor.\n\n   .. versionchanged:: 3.0\n\n      The fixture was renamed from ``test_client`` to ``aiohttp_client``.\n\n.. data:: aiohttp_raw_server(handler, *, port=None, **kwargs)\n\n   A fixture factory that creates\n   :class:`~aiohttp.test_utils.RawTestServer` instance from given web\n   handler.::\n\n      async def test_f(aiohttp_raw_server, aiohttp_client):\n\n          async def handler(request):\n              return web.Response(text=\"OK\")\n\n          raw_server = await aiohttp_raw_server(handler)\n          client = await aiohttp_client(raw_server)\n          resp = await client.get('/')\n\n   *handler* should be a coroutine which accepts a request and returns\n   response, e.g.\n\n   *port* optional, port the server is run at, if\n   not provided a random unused port is used.\n\n   .. versionadded:: 3.0\n\n.. data:: aiohttp_unused_port()\n\n   Function to return an unused port number for IPv4 TCP protocol::\n\n      async def test_f(aiohttp_client, aiohttp_unused_port):\n          port = aiohttp_unused_port()\n          app = web.Application()\n          # fill route table\n\n          client = await aiohttp_client(app, server_kwargs={'port': port})\n          ...\n\n   .. versionchanged:: 3.0\n\n      The fixture was renamed from ``unused_port`` to ``aiohttp_unused_port``.\n\n.. data:: aiohttp_client_cls\n\n   A fixture for passing custom :class:`~aiohttp.test_utils.TestClient` implementations::\n\n      class MyClient(TestClient):\n          async def login(self, *, user, pw):\n              payload = {\"username\": user, \"password\": pw}\n              return await self.post(\"/login\", json=payload)\n\n      @pytest.fixture\n      def aiohttp_client_cls():\n          return MyClient\n\n      def test_login(aiohttp_client):\n          app = web.Application()\n          client = await aiohttp_client(app)\n          await client.login(user=\"admin\", pw=\"s3cr3t\")\n\n   If you want to switch between different clients in tests, you can use\n   the usual ``pytest`` machinery. Example with using test markers::\n\n      class RESTfulClient(TestClient):\n          ...\n\n      class GraphQLClient(TestClient):\n          ...\n\n      @pytest.fixture\n      def aiohttp_client_cls(request):\n          if request.node.get_closest_marker('rest') is not None:\n              return RESTfulClient\n          if request.node.get_closest_marker('graphql') is not None:\n              return GraphQLClient\n          return TestClient\n\n\n      @pytest.mark.rest\n      async def test_rest(aiohttp_client) -> None:\n          client: RESTfulClient = await aiohttp_client(Application())\n          ...\n\n\n      @pytest.mark.graphql\n      async def test_graphql(aiohttp_client) -> None:\n          client: GraphQLClient = await aiohttp_client(Application())\n          ...\n\n\n.. _aiohttp-testing-unittest-example:\n\n.. _aiohttp-testing-unittest-style:\n\nUnittest\n~~~~~~~~\n\n.. currentmodule:: aiohttp.test_utils\n\n\nTo test applications with the standard library's unittest or unittest-based\nfunctionality, the AioHTTPTestCase is provided::\n\n    from aiohttp.test_utils import AioHTTPTestCase\n    from aiohttp import web\n\n    class MyAppTestCase(AioHTTPTestCase):\n\n        async def get_application(self):\n            \"\"\"\n            Override the get_app method to return your application.\n            \"\"\"\n            async def hello(request):\n                return web.Response(text='Hello, world')\n\n            app = web.Application()\n            app.router.add_get('/', hello)\n            return app\n\n        async def test_example(self):\n            async with self.client.request(\"GET\", \"/\") as resp:\n                self.assertEqual(resp.status, 200)\n                text = await resp.text()\n            self.assertIn(\"Hello, world\", text)\n\n.. class:: AioHTTPTestCase\n\n    A base class to allow for unittest web applications using aiohttp.\n\n    Derived from :class:`unittest.IsolatedAsyncioTestCase`\n\n    See :class:`unittest.TestCase` and :class:`unittest.IsolatedAsyncioTestCase`\n    for inherited methods and behavior.\n\n    This class additionally provides the following:\n\n    .. attribute:: client\n\n       an aiohttp test client, :class:`TestClient` instance.\n\n    .. attribute:: server\n\n       an aiohttp test server, :class:`TestServer` instance.\n\n       .. versionadded:: 2.3\n\n    .. attribute:: app\n\n       The application returned by :meth:`~aiohttp.test_utils.AioHTTPTestCase.get_application`\n       (:class:`aiohttp.web.Application` instance).\n\n    .. method:: get_client()\n      :async:\n\n       This async method can be overridden to return the :class:`TestClient`\n       object used in the test.\n\n       :return: :class:`TestClient` instance.\n\n       .. versionadded:: 2.3\n\n    .. method:: get_server()\n      :async:\n\n       This async method can be overridden to return the :class:`TestServer`\n       object used in the test.\n\n       :return: :class:`TestServer` instance.\n\n       .. versionadded:: 2.3\n\n    .. method:: get_application()\n      :async:\n\n       This async method should be overridden\n       to return the :class:`aiohttp.web.Application`\n       object to test.\n\n       :return: :class:`aiohttp.web.Application` instance.\n\n    .. method:: asyncSetUp()\n      :async:\n\n       This async method can be overridden to execute asynchronous code during\n       the ``setUp`` stage of the ``TestCase``::\n\n           async def asyncSetUp(self):\n               await super().asyncSetUp()\n               await foo()\n\n       .. versionadded:: 2.3\n\n       .. versionchanged:: 3.8\n\n          ``await super().asyncSetUp()`` call is required.\n\n    .. method:: asyncTearDown()\n      :async:\n\n       This async method can be overridden to execute asynchronous code during\n       the ``tearDown`` stage of the ``TestCase``::\n\n           async def asyncTearDown(self):\n               await super().asyncTearDown()\n               await foo()\n\n       .. versionadded:: 2.3\n\n       .. versionchanged:: 3.8\n\n          ``await super().asyncTearDown()`` call is required.\n\nFaking request object\n^^^^^^^^^^^^^^^^^^^^^\n\naiohttp provides test utility for creating fake\n:class:`aiohttp.web.Request` objects:\n:func:`aiohttp.test_utils.make_mocked_request`, it could be useful in\ncase of simple unit tests, like handler tests, or simulate error\nconditions that hard to reproduce on real server::\n\n    from aiohttp import web\n    from aiohttp.test_utils import make_mocked_request\n\n    def handler(request):\n        assert request.headers.get('token') == 'x'\n        return web.Response(body=b'data')\n\n    def test_handler():\n        req = make_mocked_request('GET', '/', headers={'token': 'x'})\n        resp = handler(req)\n        assert resp.body == b'data'\n\n.. warning::\n\n   We don't recommend to apply\n   :func:`~aiohttp.test_utils.make_mocked_request` everywhere for\n   testing web-handler's business object -- please use test client and\n   real networking via 'localhost' as shown in examples before.\n\n   :func:`~aiohttp.test_utils.make_mocked_request` exists only for\n   testing complex cases (e.g. emulating network errors) which\n   are extremely hard or even impossible to test by conventional\n   way.\n\n\n.. function:: make_mocked_request(method, path, headers=None, *, \\\n                                  version=HttpVersion(1, 1), \\\n                                  closing=False, \\\n                                  app=None, \\\n                                  match_info=sentinel, \\\n                                  reader=sentinel, \\\n                                  writer=sentinel, \\\n                                  transport=sentinel, \\\n                                  payload=sentinel, \\\n                                  sslcontext=None, \\\n                                  loop=...)\n\n   Creates mocked web.Request testing purposes.\n\n   Useful in unit tests, when spinning full web server is overkill or\n   specific conditions and errors are hard to trigger.\n\n   :param method: str, that represents HTTP method, like; GET, POST.\n   :type method: str\n\n   :param path: str, The URL including *PATH INFO* without the host or scheme\n   :type path: str\n\n   :param headers: mapping containing the headers. Can be anything accepted\n       by the multidict.CIMultiDict constructor.\n   :type headers: dict, multidict.CIMultiDict, list of tuple(str, str)\n\n   :param match_info: mapping containing the info to match with url parameters.\n   :type match_info: dict\n\n   :param version: namedtuple with encoded HTTP version\n   :type version: aiohttp.protocol.HttpVersion\n\n   :param closing: flag indicates that connection should be closed after\n       response.\n   :type closing: bool\n\n   :param app: the aiohttp.web application attached for fake request\n   :type app: aiohttp.web.Application\n\n   :param writer: object for managing outcoming data\n   :type writer: aiohttp.StreamWriter\n\n   :param transport: asyncio transport instance\n   :type transport: asyncio.Transport\n\n   :param payload: raw payload reader object\n   :type  payload: aiohttp.StreamReader\n\n   :param sslcontext: ssl.SSLContext object, for HTTPS connection\n   :type sslcontext: ssl.SSLContext\n\n   :param loop: An event loop instance, mocked loop by default.\n   :type loop: :class:`asyncio.AbstractEventLoop`\n\n   :return: :class:`aiohttp.web.Request` object.\n\n   .. versionadded:: 2.3\n      *match_info* parameter.\n\n.. _aiohttp-testing-writing-testable-services:\n\n.. _aiohttp-testing-framework-agnostic-utilities:\n\n\nFramework Agnostic Utilities\n----------------------------\n\nHigh level test creation::\n\n    from aiohttp.test_utils import TestClient, TestServer\n    from aiohttp import request\n\n    async def test():\n        app = _create_example_app()\n        async with TestClient(TestServer(app)) as client:\n\n            async def test_get_route():\n                nonlocal client\n                resp = await client.get(\"/\")\n                assert resp.status == 200\n                text = await resp.text()\n                assert \"Hello, world\" in text\n\n            await test_get_route()\n\n\nIf it's preferred to handle the creation / teardown on a more granular\nbasis, the TestClient object can be used directly::\n\n    from aiohttp.test_utils import TestClient, TestServer\n\n    async def test():\n        app = _create_example_app()\n        client = TestClient(TestServer(app))\n        await client.start_server()\n        root = \"http://127.0.0.1:{}\".format(port)\n\n        async def test_get_route():\n            resp = await client.get(\"/\")\n            assert resp.status == 200\n            text = await resp.text()\n            assert \"Hello, world\" in text\n\n        await test_get_route()\n        await client.close()\n\n\nA full list of the utilities provided can be found at the\n:data:`api reference <aiohttp.test_utils>`\n\n\nTesting API Reference\n---------------------\n\nTest server\n~~~~~~~~~~~\n\nRuns given :class:`aiohttp.web.Application` instance on random TCP port.\n\nAfter creation the server is not started yet, use\n:meth:`~aiohttp.test_utils.BaseTestServer.start_server` for actual server\nstarting and :meth:`~aiohttp.test_utils.BaseTestServer.close` for\nstopping/cleanup.\n\nTest server usually works in conjunction with\n:class:`aiohttp.test_utils.TestClient` which provides handy client methods\nfor accessing to the server.\n\n.. class:: BaseTestServer(*, scheme='http', host='127.0.0.1', port=None, socket_factory=get_port_socket)\n\n   Base class for test servers.\n\n   :param str scheme: HTTP scheme, non-protected ``\"http\"`` by default.\n\n   :param str host: a host for TCP socket, IPv4 *local host*\n      (``'127.0.0.1'``) by default.\n\n   :param int port: optional port for TCP socket, if not provided a\n      random unused port is used.\n\n      .. versionadded:: 3.0\n\n   :param collections.abc.Callable[[str,int,socket.AddressFamily],socket.socket] socket_factory: optional\n                          Factory to create a socket for the server.\n                          By default creates a TCP socket and binds it\n                          to ``host`` and ``port``.\n\n      .. versionadded:: 3.8\n\n   .. attribute:: scheme\n\n      A *scheme* for tested application, ``'http'`` for non-protected\n      run and ``'https'`` for TLS encrypted server.\n\n   .. attribute:: host\n\n      *host* used to start a test server.\n\n   .. attribute:: port\n\n      *port* used to start the test server.\n\n   .. attribute:: handler\n\n      :class:`aiohttp.web.Server` used for HTTP requests serving.\n\n   .. attribute:: server\n\n      :class:`asyncio.AbstractServer` used for managing accepted connections.\n\n   .. attribute:: socket_factory\n\n      *socket_factory* used to create and bind a server socket.\n\n      .. versionadded:: 3.8\n\n   .. method:: start_server(**kwargs)\n      :async:\n\n      Start a test server.\n\n   .. method:: close()\n      :async:\n\n      Stop and finish executed test server.\n\n   .. method:: make_url(path)\n\n      Return an *absolute* :class:`~yarl.URL` for given *path*.\n\n\n.. class:: RawTestServer(handler, *, scheme=\"http\", host='127.0.0.1')\n\n   Low-level test server (derived from :class:`BaseTestServer`).\n\n   :param handler: a coroutine for handling web requests. The\n                   handler should accept\n                   :class:`aiohttp.web.BaseRequest` and return a\n                   response instance,\n                   e.g. :class:`~aiohttp.web.StreamResponse` or\n                   :class:`~aiohttp.web.Response`.\n\n                   The handler could raise\n                   :class:`~aiohttp.web.HTTPException` as a signal for\n                   non-200 HTTP response.\n\n   :param str scheme: HTTP scheme, non-protected ``\"http\"`` by default.\n\n   :param str host: a host for TCP socket, IPv4 *local host*\n      (``'127.0.0.1'``) by default.\n\n   :param int port: optional port for TCP socket, if not provided a\n      random unused port is used.\n\n      .. versionadded:: 3.0\n\n\n.. class:: TestServer(app, *, scheme=\"http\", host='127.0.0.1')\n\n   Test server (derived from :class:`BaseTestServer`) for starting\n   :class:`~aiohttp.web.Application`.\n\n   :param app: :class:`aiohttp.web.Application` instance to run.\n\n   :param str scheme: HTTP scheme, non-protected ``\"http\"`` by default.\n\n   :param str host: a host for TCP socket, IPv4 *local host*\n      (``'127.0.0.1'``) by default.\n\n   :param int port: optional port for TCP socket, if not provided a\n      random unused port is used.\n\n      .. versionadded:: 3.0\n\n   .. attribute:: app\n\n      :class:`aiohttp.web.Application` instance to run.\n\n\nTest Client\n~~~~~~~~~~~\n\n.. class:: TestClient(app_or_server, *, \\\n                      scheme='http', host='127.0.0.1', \\\n                      cookie_jar=None, **kwargs)\n\n   A test client used for making calls to tested server.\n\n   :param app_or_server: :class:`BaseTestServer` instance for making\n                         client requests to it.\n\n                         In order to pass an :class:`aiohttp.web.Application`\n                         you need to convert it first to :class:`TestServer`\n                         first with ``TestServer(app)``.\n\n   :param cookie_jar: an optional :class:`aiohttp.CookieJar` instance,\n                      may be useful with\n                      ``CookieJar(unsafe=True, treat_as_secure_origin=\"http://127.0.0.1\")``\n                      option.\n\n   :param str scheme: HTTP scheme, non-protected ``\"http\"`` by default.\n\n   :param str host: a host for TCP socket, IPv4 *local host*\n      (``'127.0.0.1'``) by default.\n\n   .. attribute:: scheme\n\n      A *scheme* for tested application, ``'http'`` for non-protected\n      run and ``'https'`` for TLS encrypted server.\n\n   .. attribute:: host\n\n      *host* used to start a test server.\n\n   .. attribute:: port\n\n      *port* used to start the server\n\n   .. attribute:: server\n\n      :class:`BaseTestServer` test server instance used in conjunction\n      with client.\n\n   .. attribute:: app\n\n      An alias for ``self.server.app``. return ``None`` if\n      ``self.server`` is not :class:`TestServer`\n      instance(e.g. :class:`RawTestServer` instance for test low-level server).\n\n   .. attribute:: session\n\n      An internal :class:`aiohttp.ClientSession`.\n\n      Unlike the methods on the :class:`TestClient`, client session\n      requests do not automatically include the host in the url\n      queried, and will require an absolute path to the resource.\n\n   .. method:: start_server(**kwargs)\n      :async:\n\n      Start a test server.\n\n   .. method:: close()\n      :async:\n\n      Stop and finish executed test server.\n\n   .. method:: make_url(path)\n\n      Return an *absolute* :class:`~yarl.URL` for given *path*.\n\n   .. method:: request(method, path, *args, **kwargs)\n      :async:\n\n      Routes a request to tested http server.\n\n      The interface is identical to\n      :meth:`aiohttp.ClientSession.request`, except the loop kwarg is\n      overridden by the instance used by the test server.\n\n   .. method:: get(path, *args, **kwargs)\n      :async:\n\n      Perform an HTTP GET request.\n\n   .. method:: post(path, *args, **kwargs)\n      :async:\n\n      Perform an HTTP POST request.\n\n   .. method:: options(path, *args, **kwargs)\n      :async:\n\n      Perform an HTTP OPTIONS request.\n\n   .. method:: head(path, *args, **kwargs)\n      :async:\n\n      Perform an HTTP HEAD request.\n\n   .. method:: put(path, *args, **kwargs)\n      :async:\n\n      Perform an HTTP PUT request.\n\n   .. method:: patch(path, *args, **kwargs)\n      :async:\n\n      Perform an HTTP PATCH request.\n\n   .. method:: delete(path, *args, **kwargs)\n      :async:\n\n      Perform an HTTP DELETE request.\n\n   .. method:: ws_connect(path, *args, **kwargs)\n      :async:\n\n      Initiate websocket connection.\n\n      The api corresponds to :meth:`aiohttp.ClientSession.ws_connect`.\n\n\nUtilities\n~~~~~~~~~\n\n.. function:: unused_port()\n\n   Return an unused port number for IPv4 TCP protocol.\n\n   :return int: ephemeral port number which could be reused by test server.\n\n.. function:: loop_context(loop_factory=<function asyncio.new_event_loop>)\n\n   A contextmanager that creates an event_loop, for test purposes.\n\n   Handles the creation and cleanup of a test loop.\n\n.. function:: setup_test_loop(loop_factory=<function asyncio.new_event_loop>)\n\n   Create and return an :class:`asyncio.AbstractEventLoop` instance.\n\n   The caller should also call teardown_test_loop, once they are done\n   with the loop.\n\n   .. note::\n\n      As side effect the function changes asyncio *default loop* by\n      :func:`asyncio.set_event_loop` call.\n\n      Previous default loop is not restored.\n\n      It should not be a problem for test suite: every test expects a\n      new test loop instance anyway.\n\n   .. versionchanged:: 3.1\n\n      The function installs a created event loop as *default*.\n\n.. function:: teardown_test_loop(loop)\n\n   Teardown and cleanup an event_loop created by setup_test_loop.\n\n   :param loop: the loop to teardown\n   :type loop: asyncio.AbstractEventLoop\n\n\n\n.. _pytest: http://pytest.org/latest/\n.. _pytest-aiohttp: https://pypi.python.org/pypi/pytest-aiohttp\n"
  },
  {
    "path": "docs/third_party.rst",
    "content": ".. _aiohttp-3rd-party:\n\nThird-Party libraries\n=====================\n\n\naiohttp is not just a library for making HTTP requests and creating web\nservers.\n\nIt is the foundation for libraries built *on top* of aiohttp.\n\nThis page is a list of these tools.\n\nPlease feel free to add your open source library if it's not listed\nyet by making a pull request to https://github.com/aio-libs/aiohttp/\n\n* Why would you want to include your awesome library in this list?\n\n* Because the list increases your library visibility. People\n  will have an easy way to find it.\n\n\nOfficially supported\n--------------------\n\nThis list contains libraries which are supported by the *aio-libs* team\nand located on https://github.com/aio-libs\n\n\naiohttp extensions\n^^^^^^^^^^^^^^^^^^\n\n- `aiohttp-apischema <https://github.com/aio-libs/aiohttp-apischema>`_\n  provides automatic API schema generation and validation of user input\n  for :mod:`aiohttp.web`.\n\n- `aiohttp-session <https://github.com/aio-libs/aiohttp-session>`_\n  provides sessions for :mod:`aiohttp.web`.\n\n- `aiohttp-debugtoolbar <https://github.com/aio-libs/aiohttp-debugtoolbar>`_\n  is a library for *debug toolbar* support for :mod:`aiohttp.web`.\n\n- `aiohttp-security <https://github.com/aio-libs/aiohttp-security>`_\n  auth and permissions for :mod:`aiohttp.web`.\n\n- `aiohttp-devtools <https://github.com/aio-libs/aiohttp-devtools>`_\n  provides development tools for :mod:`aiohttp.web` applications.\n\n- `aiohttp-cors <https://github.com/aio-libs/aiohttp-cors>`_ CORS\n  support for aiohttp.\n\n- `aiohttp-sse <https://github.com/aio-libs/aiohttp-sse>`_ Server-sent\n  events support for aiohttp.\n\n- `pytest-aiohttp <https://github.com/aio-libs/pytest-aiohttp>`_\n  pytest plugin for aiohttp support.\n\n- `aiohttp-mako <https://github.com/aio-libs/aiohttp-mako>`_ Mako\n  template renderer for aiohttp.web.\n\n- `aiohttp-jinja2 <https://github.com/aio-libs/aiohttp-jinja2>`_ Jinja2\n  template renderer for aiohttp.web.\n\n- `aiozipkin <https://github.com/aio-libs/aiozipkin>`_ distributed\n  tracing instrumentation for `aiohttp` client and server.\n\nDatabase drivers\n^^^^^^^^^^^^^^^^\n\n- `aiopg <https://github.com/aio-libs/aiopg>`_ PostgreSQL async driver.\n\n- `aiomysql <https://github.com/aio-libs/aiomysql>`_ MySQL async driver.\n\n- `aioredis <https://github.com/aio-libs/aioredis>`_ Redis async driver.\n\nOther tools\n^^^^^^^^^^^\n\n- `aiodocker <https://github.com/aio-libs/aiodocker>`_ Python Docker\n  API client based on asyncio and aiohttp.\n\n- `aiobotocore <https://github.com/aio-libs/aiobotocore>`_ asyncio\n  support for botocore library using aiohttp.\n\n\nApproved third-party libraries\n------------------------------\n\nThese libraries are not part of ``aio-libs`` but they have proven to be very\nwell written and highly recommended for usage.\n\n- `uvloop <https://github.com/MagicStack/uvloop>`_ Ultra fast\n  implementation of asyncio event loop on top of ``libuv``.\n\n  We highly recommend to use this instead of standard ``asyncio``.\n\nDatabase drivers\n^^^^^^^^^^^^^^^^\n\n- `asyncpg <https://github.com/MagicStack/asyncpg>`_ Another\n  PostgreSQL async driver. It's much faster than ``aiopg`` but is\n  not a drop-in replacement -- the API is different. But, please take\n  a look at it -- the driver is incredibly fast.\n\nOpenAPI / Swagger extensions\n----------------------------\n\nExtensions bringing `OpenAPI <https://swagger.io/docs/specification/about>`_\nsupport to aiohttp web servers.\n\n- `aiohttp-apispec <https://github.com/maximdanilchenko/aiohttp-apispec>`_\n  Build and document REST APIs with ``aiohttp`` and ``apispec``.\n\n- `aiohttp_apiset <https://github.com/aamalev/aiohttp_apiset>`_\n  Package to build routes using swagger specification.\n\n- `aiohttp-pydantic <https://github.com/Maillol/aiohttp-pydantic>`_\n  An ``aiohttp.View`` to validate the HTTP request's body, query-string, and\n  headers regarding function annotations and generate OpenAPI doc.\n\n- `aiohttp-swagger <https://github.com/cr0hn/aiohttp-swagger>`_\n  Swagger API Documentation builder for aiohttp server.\n\n- `aiohttp-swagger3 <https://github.com/hh-h/aiohttp-swagger3>`_\n  Library for Swagger documentation builder and validating aiohttp requests\n  using swagger specification 3.0.\n\n- `aiohttp-swaggerify <https://github.com/dchaplinsky/aiohttp_swaggerify>`_\n  Library to automatically generate swagger2.0 definition for aiohttp endpoints.\n\n- `aio-openapi <https://github.com/quantmind/aio-openapi>`_\n  Asynchronous web middleware for aiohttp and serving Rest APIs with OpenAPI v3\n  specification and with optional PostgreSQL database bindings.\n\n- `rororo <https://github.com/playpauseandstop/rororo>`_\n  Implement ``aiohttp.web`` OpenAPI 3 server applications with schema first\n  approach.\n\nOthers\n------\n\nHere is a list of other known libraries that do not belong in the former categories.\n\nWe cannot vouch for the quality of these libraries, use them at your own risk.\n\nPlease add your library reference here first and after some time\nask to raise the status.\n\n- `pytest-aiohttp-client <https://github.com/sivakov512/pytest-aiohttp-client>`_\n  Pytest fixture with simpler api, payload decoding and status code assertions.\n\n- `python-proxy-headers <https://github.com/proxymesh/python-proxy-headers>`_\n  provides ``aiohttp_proxy`` extension for receiving custom response headers from a proxy server\n\n- `octomachinery <https://octomachinery.dev>`_ A framework for developing\n  GitHub Apps and GitHub Actions.\n\n- `aiomixcloud <https://github.com/amikrop/aiomixcloud>`_\n  Mixcloud API wrapper for Python and Async IO.\n\n- `aiohttp-cache <https://github.com/cr0hn/aiohttp-cache>`_ A cache\n  system for aiohttp server.\n\n- `aiocache <https://github.com/argaen/aiocache>`_ Caching for asyncio\n  with multiple backends (framework agnostic)\n\n- `gain <https://github.com/gaojiuli/gain>`_ Web crawling framework\n  based on asyncio for everyone.\n\n- `aiohttp-validate <https://github.com/dchaplinsky/aiohttp_validate>`_\n  Simple library that helps you validate your API endpoints requests/responses with json schema.\n\n- `raven-aiohttp <https://github.com/getsentry/raven-aiohttp>`_ An\n  aiohttp transport for raven-python (Sentry client).\n\n- `webargs <https://github.com/sloria/webargs>`_ A friendly library\n  for parsing HTTP request arguments, with built-in support for\n  popular web frameworks, including Flask, Django, Bottle, Tornado,\n  Pyramid, webapp2, Falcon, and aiohttp.\n\n- `aiohttpretty\n  <https://github.com/CenterForOpenScience/aiohttpretty>`_ A simple\n  asyncio compatible httpretty mock using aiohttp.\n\n- `aioresponses <https://github.com/pnuckowski/aioresponses>`_ a\n  helper for mock/fake web requests in python aiohttp package.\n\n- `aiohttp-transmute\n  <https://github.com/toumorokoshi/aiohttp-transmute>`_ A transmute\n  implementation for aiohttp.\n\n- `aiohttp-login <https://github.com/imbolc/aiohttp-login>`_\n  Registration and authorization (including social) for aiohttp\n  applications.\n\n- `aiohttp_utils <https://github.com/sloria/aiohttp_utils>`_ Handy\n  utilities for building aiohttp.web applications.\n\n- `aiohttpproxy <https://github.com/jmehnle/aiohttpproxy>`_ Simple\n  aiohttp HTTP proxy.\n\n- `aiohttp_traversal <https://github.com/zzzsochi/aiohttp_traversal>`_\n  Traversal based router for aiohttp.web.\n\n- `aiohttp_autoreload\n  <https://github.com/anti1869/aiohttp_autoreload>`_ Makes aiohttp\n  server auto-reload on source code change.\n\n- `gidgethub <https://github.com/brettcannon/gidgethub>`_ An async\n  GitHub API library for Python.\n\n- `aiohttp-rpc <https://github.com/expert-m/aiohttp-rpc>`_ A simple\n  JSON-RPC for aiohttp.\n\n- `aiohttp_jrpc <https://github.com/zloidemon/aiohttp_jrpc>`_ aiohttp\n  JSON-RPC service.\n\n- `fbemissary <https://github.com/cdunklau/fbemissary>`_ A bot\n  framework for the Facebook Messenger platform, built on asyncio and\n  aiohttp.\n\n- `aioslacker <https://github.com/wikibusiness/aioslacker>`_ slacker\n  wrapper for asyncio.\n\n- `aioreloader <https://github.com/and800/aioreloader>`_ Port of\n  tornado reloader to asyncio.\n\n- `aiohttp_babel <https://github.com/jie/aiohttp_babel>`_ Babel\n  localization support for aiohttp.\n\n- `python-mocket <https://github.com/mindflayer/python-mocket>`_ a\n  socket mock framework - for all kinds of socket animals, web-clients\n  included.\n\n- `aioraft <https://github.com/lisael/aioraft>`_ asyncio RAFT\n  algorithm based on aiohttp.\n\n- `home-assistant <https://github.com/home-assistant/home-assistant>`_\n  Open-source home automation platform running on Python 3.\n\n- `discord.py <https://github.com/Rapptz/discord.py>`_ Discord client library.\n\n- `aiogram <https://github.com/aiogram/aiogram>`_\n  A fully asynchronous library for Telegram Bot API written with asyncio and aiohttp.\n\n- `aiohttp-graphql <https://github.com/graphql-python/aiohttp-graphql>`_\n  GraphQL and GraphIQL interface for aiohttp.\n\n- `aiohttp-sentry <https://github.com/underyx/aiohttp-sentry>`_\n  An aiohttp middleware for reporting errors to Sentry.\n\n- `aiohttp-datadog <https://github.com/underyx/aiohttp-datadog>`_\n  An aiohttp middleware for reporting metrics to DataDog.\n\n- `async-v20 <https://github.com/jamespeterschinner/async_v20>`_\n  Asynchronous FOREX client for OANDA's v20 API.\n\n- `aiohttp-jwt <https://github.com/hzlmn/aiohttp-jwt>`_\n  An aiohttp middleware for JWT(JSON Web Token) support.\n\n- `AWS Xray Python SDK <https://github.com/aws/aws-xray-sdk-python>`_\n  Native tracing support for Aiohttp applications.\n\n- `GINO <https://github.com/fantix/gino>`_\n  An asyncio ORM on top of SQLAlchemy core, delivered with an aiohttp extension.\n\n- `New Relic <https://github.com/newrelic/newrelic-quickstarts/tree/main/quickstarts/python/aiohttp>`_ An aiohttp middleware for reporting your `Python application performance <https://newrelic.com/instant-observability/aiohttp>`_ metrics to New Relic.\n\n- `eider-py <https://github.com/eider-rpc/eider-py>`_ Python implementation of\n  the `Eider RPC protocol <http://eider.readthedocs.io/>`_.\n\n- `asynapplicationinsights\n  <https://github.com/RobertoPrevato/asynapplicationinsights>`_ A client for\n  `Azure Application Insights\n  <https://azure.microsoft.com/en-us/services/application-insights/>`_\n  implemented using ``aiohttp`` client, including a middleware for ``aiohttp``\n  servers to collect web apps telemetry.\n\n- `aiogmaps <https://github.com/hzlmn/aiogmaps>`_\n  Asynchronous client for Google Maps API Web Services.\n\n- `DBGR <https://github.com/JakubTesarek/dbgr>`_\n  Terminal based tool to test and debug HTTP APIs with ``aiohttp``.\n\n- `aiohttp-middlewares <https://github.com/playpauseandstop/aiohttp-middlewares>`_\n  Collection of useful middlewares for ``aiohttp.web`` applications.\n\n- `aiohttp-tus <https://github.com/pylotcode/aiohttp-tus>`_\n  `tus.io <https://tus.io>`_ protocol implementation for ``aiohttp.web``\n  applications.\n\n- `aiohttp-sse-client <https://github.com/rtfol/aiohttp-sse-client>`_\n  A Server-Sent Event python client base on aiohttp.\n\n- `aiohttp-retry <https://github.com/inyutin/aiohttp_retry>`_\n  Wrapper for aiohttp client for retrying requests.\n\n- `aiohttp-socks <https://github.com/romis2012/aiohttp-socks>`_\n  SOCKS proxy connector for aiohttp.\n\n- `aiohttp-catcher <https://github.com/yuvalherziger/aiohttp-catcher>`_\n  An aiohttp middleware library for centralized error handling in aiohttp servers.\n\n- `rsocket <https://github.com/rsocket/rsocket-py>`_\n  Python implementation of `RSocket protocol <https://rsocket.io>`_.\n\n- `nacl_middleware <https://github.com/CosmicDNA/nacl_middleware>`_\n  An aiohttp middleware library for asymmetric encryption of data transmitted via http and/or websocket connections.\n\n- `aiohttp-asgi-connector <https://github.com/thearchitector/aiohttp-asgi-connector>`_\n  An aiohttp connector for using a ``ClientSession`` to interface directly with separate ASGI applications.\n\n- `aiohttp-openmetrics <https://github.com/jelmer/aiohttp-openmetrics>`_\n  An aiohttp middleware for exposing Prometheus metrics.\n\n- `wireup <https://github.com/maldoinc/wireup>`_\n  Performant, concise, and easy-to-use dependency injection container.\n"
  },
  {
    "path": "docs/tracing_reference.rst",
    "content": ".. currentmodule:: aiohttp\n\n.. _aiohttp-client-tracing-reference:\n\nTracing Reference\n=================\n\n.. versionadded:: 3.0\n\nA reference for client tracing API.\n\n.. seealso:: :ref:`aiohttp-client-tracing` for tracing usage instructions.\n\n\nRequest life cycle\n------------------\n\nA request goes through the following stages and corresponding fallbacks.\n\n\nOverview\n^^^^^^^^\n\n.. graphviz::\n\n   digraph {\n\n     start[shape=point, xlabel=\"start\", width=\"0.1\"];\n     redirect[shape=box];\n     end[shape=point, xlabel=\"end  \", width=\"0.1\"];\n     exception[shape=oval];\n\n     acquire_connection[shape=box];\n     headers_received[shape=box];\n     headers_sent[shape=box];\n     chunk_sent[shape=box];\n     chunk_received[shape=box];\n\n     start -> acquire_connection;\n     acquire_connection -> headers_sent;\n     headers_sent -> headers_received;\n     headers_sent -> chunk_sent;\n     chunk_sent -> chunk_sent;\n     chunk_sent -> headers_received;\n     headers_received -> chunk_received;\n     chunk_received -> chunk_received;\n     chunk_received -> end;\n     headers_received -> redirect;\n     headers_received -> end;\n     redirect -> headers_sent;\n     chunk_received -> exception;\n     chunk_sent -> exception;\n     headers_sent -> exception;\n\n   }\n\n.. list-table::\n   :header-rows: 1\n\n   * - Name\n     - Description\n   * - start\n     - on_request_start\n   * - redirect\n     - on_request_redirect\n   * - acquire_connection\n     - Connection acquiring\n   * - headers_received\n     -\n   * - exception\n     - on_request_exception\n   * - end\n     - on_request_end\n   * - headers_sent\n     - on_request_headers_sent\n   * - chunk_sent\n     - on_request_chunk_sent\n   * - chunk_received\n     - on_response_chunk_received\n\nConnection acquiring\n^^^^^^^^^^^^^^^^^^^^\n\n.. graphviz::\n\n   digraph {\n\n     begin[shape=point, xlabel=\"begin\", width=\"0.1\"];\n     end[shape=point, xlabel=\"end \", width=\"0.1\"];\n     exception[shape=oval];\n\n     queued_start[shape=box];\n     queued_end[shape=box];\n     create_start[shape=box];\n     create_end[shape=box];\n     reuseconn[shape=box];\n     resolve_dns[shape=box];\n     sock_connect[shape=box];\n\n     begin -> reuseconn;\n     begin -> create_start;\n     create_start -> resolve_dns;\n     resolve_dns -> exception;\n     resolve_dns -> sock_connect;\n     sock_connect -> exception;\n     sock_connect -> create_end -> end;\n     begin -> queued_start;\n     queued_start -> queued_end;\n     queued_end -> reuseconn;\n     queued_end -> create_start;\n     reuseconn -> end;\n\n   }\n\n.. list-table::\n   :header-rows: 1\n\n   * - Name\n     - Description\n   * - begin\n     -\n   * - end\n     -\n   * - queued_start\n     - on_connection_queued_start\n   * - create_start\n     - on_connection_create_start\n   * - reuseconn\n     - on_connection_reuseconn\n   * - queued_end\n     - on_connection_queued_end\n   * - create_end\n     - on_connection_create_end\n   * - exception\n     - Exception raised\n   * - resolve_dns\n     - DNS resolving\n   * - sock_connect\n     - Connection establishment\n\nDNS resolving\n^^^^^^^^^^^^^\n\n.. graphviz::\n\n   digraph {\n\n     begin[shape=point, xlabel=\"begin\", width=\"0.1\"];\n     end[shape=point, xlabel=\"end\", width=\"0.1\"];\n     exception[shape=oval];\n\n     resolve_start[shape=box];\n     resolve_end[shape=box];\n     cache_hit[shape=box];\n     cache_miss[shape=box];\n\n     begin -> cache_hit -> end;\n     begin -> cache_miss -> resolve_start;\n     resolve_start -> resolve_end -> end;\n     resolve_start -> exception;\n\n   }\n\n.. list-table::\n   :header-rows: 1\n\n   * - Name\n     - Description\n   * - begin\n     -\n   * - end\n     -\n   * - exception\n     - Exception raised\n   * - resolve_end\n     - on_dns_resolvehost_end\n   * - resolve_start\n     - on_dns_resolvehost_start\n   * - cache_hit\n     - on_dns_cache_hit\n   * - cache_miss\n     - on_dns_cache_miss\n\nClasses\n-------\n\n.. class:: TraceConfig(trace_config_ctx_factory=SimpleNamespace)\n   :canonical: aiohttp.tracing.TraceConfig\n\n   Trace config is the configuration object used to trace requests\n   launched by a :class:`ClientSession` object using different events\n   related to different parts of the request flow.\n\n   :param trace_config_ctx_factory: factory used to create trace contexts,\n      default class used :class:`types.SimpleNamespace`\n\n   .. method:: trace_config_ctx(trace_request_ctx=None)\n\n      :param trace_request_ctx: Will be used to pass as a kw for the\n        ``trace_config_ctx_factory``.\n\n      Build a new trace context from the config.\n\n   Every signal handler should have the following signature::\n\n      async def on_signal(session, context, params): ...\n\n   where ``session`` is :class:`ClientSession` instance, ``context`` is an\n   object returned by :meth:`trace_config_ctx` call and ``params`` is a\n   data class with signal parameters. The type of ``params`` depends on\n   subscribed signal and described below.\n\n   .. attribute:: on_request_start\n\n      Property that gives access to the signals that will be executed\n      when a request starts.\n\n      ``params`` is :class:`aiohttp.TraceRequestStartParams` instance.\n\n   .. attribute:: on_request_chunk_sent\n\n\n      Property that gives access to the signals that will be executed\n      when a chunk of request body is sent.\n\n      ``params`` is :class:`aiohttp.TraceRequestChunkSentParams` instance.\n\n      .. versionadded:: 3.1\n\n   .. attribute:: on_response_chunk_received\n\n\n      Property that gives access to the signals that will be executed\n      when a chunk of response body is received.\n\n      ``params`` is :class:`aiohttp.TraceResponseChunkReceivedParams` instance.\n\n      .. versionadded:: 3.1\n\n   .. attribute:: on_request_redirect\n\n      Property that gives access to the signals that will be executed when a\n      redirect happens during a request flow.\n\n      ``params`` is :class:`aiohttp.TraceRequestRedirectParams` instance.\n\n   .. attribute:: on_request_end\n\n      Property that gives access to the signals that will be executed when a\n      request ends.\n\n      ``params`` is :class:`aiohttp.TraceRequestEndParams` instance.\n\n   .. attribute:: on_request_exception\n\n      Property that gives access to the signals that will be executed when a\n      request finishes with an exception.\n\n      ``params`` is :class:`aiohttp.TraceRequestExceptionParams` instance.\n\n   .. attribute:: on_connection_queued_start\n\n      Property that gives access to the signals that will be executed when a\n      request has been queued waiting for an available connection.\n\n      ``params`` is :class:`aiohttp.TraceConnectionQueuedStartParams`\n      instance.\n\n   .. attribute:: on_connection_queued_end\n\n      Property that gives access to the signals that will be executed when a\n      request that was queued already has an available connection.\n\n      ``params`` is :class:`aiohttp.TraceConnectionQueuedEndParams`\n      instance.\n\n   .. attribute:: on_connection_create_start\n\n      Property that gives access to the signals that will be executed when a\n      request creates a new connection.\n\n      ``params`` is :class:`aiohttp.TraceConnectionCreateStartParams`\n      instance.\n\n   .. attribute:: on_connection_create_end\n\n      Property that gives access to the signals that will be executed when a\n      request that created a new connection finishes its creation.\n\n      ``params`` is :class:`aiohttp.TraceConnectionCreateEndParams`\n      instance.\n\n   .. attribute:: on_connection_reuseconn\n\n      Property that gives access to the signals that will be executed when a\n      request reuses a connection.\n\n      ``params`` is :class:`aiohttp.TraceConnectionReuseconnParams`\n      instance.\n\n   .. attribute:: on_dns_resolvehost_start\n\n      Property that gives access to the signals that will be executed when a\n      request starts to resolve the domain related with the request.\n\n      ``params`` is :class:`aiohttp.TraceDnsResolveHostStartParams`\n      instance.\n\n   .. attribute:: on_dns_resolvehost_end\n\n      Property that gives access to the signals that will be executed when a\n      request finishes to resolve the domain related with the request.\n\n      ``params`` is :class:`aiohttp.TraceDnsResolveHostEndParams` instance.\n\n   .. attribute:: on_dns_cache_hit\n\n      Property that gives access to the signals that will be executed when a\n      request was able to use a cached DNS resolution for the domain related\n      with the request.\n\n      ``params`` is :class:`aiohttp.TraceDnsCacheHitParams` instance.\n\n   .. attribute:: on_dns_cache_miss\n\n      Property that gives access to the signals that will be executed when a\n      request was not able to use a cached DNS resolution for the domain related\n      with the request.\n\n      ``params`` is :class:`aiohttp.TraceDnsCacheMissParams` instance.\n\n   .. attribute:: on_request_headers_sent\n\n      Property that gives access to the signals that will be executed\n      when request headers are sent.\n\n      ``params`` is :class:`aiohttp.TraceRequestHeadersSentParams` instance.\n\n      .. versionadded:: 3.8\n\n\n.. class:: TraceRequestStartParams\n   :canonical: aiohttp.tracing.TraceRequestStartParams\n\n   See :attr:`TraceConfig.on_request_start` for details.\n\n   .. attribute:: method\n\n       Method that will be used  to make the request.\n\n   .. attribute:: url\n\n       URL that will be used  for the request.\n\n   .. attribute:: headers\n\n       Headers that will be used for the request, can be mutated.\n\n\n.. class:: TraceRequestChunkSentParams\n   :canonical: aiohttp.tracing.TraceRequestChunkSentParams\n\n   .. versionadded:: 3.1\n\n   See :attr:`TraceConfig.on_request_chunk_sent` for details.\n\n   .. attribute:: method\n\n       Method that will be used  to make the request.\n\n   .. attribute:: url\n\n       URL that will be used  for the request.\n\n   .. attribute:: chunk\n\n       Bytes of chunk sent\n\n\n.. class:: TraceResponseChunkReceivedParams\n   :canonical: aiohttp.tracing.TraceResponseChunkReceivedParams\n\n   .. versionadded:: 3.1\n\n   See :attr:`TraceConfig.on_response_chunk_received` for details.\n\n   .. attribute:: method\n\n       Method that will be used  to make the request.\n\n   .. attribute:: url\n\n       URL that will be used  for the request.\n\n   .. attribute:: chunk\n\n       Bytes of chunk received\n\n\n.. class:: TraceRequestEndParams\n   :canonical: aiohttp.tracing.TraceRequestEndParams\n\n   See :attr:`TraceConfig.on_request_end` for details.\n\n   .. attribute:: method\n\n       Method used to make the request.\n\n   .. attribute:: url\n\n       URL used for the request.\n\n   .. attribute:: headers\n\n       Headers used for the request.\n\n   .. attribute:: response\n\n       Response :class:`ClientResponse`.\n\n\n.. class:: TraceRequestExceptionParams\n   :canonical: aiohttp.tracing.TraceRequestExceptionParams\n\n   See :attr:`TraceConfig.on_request_exception` for details.\n\n   .. attribute:: method\n\n       Method used to make the request.\n\n   .. attribute:: url\n\n       URL used for the request.\n\n   .. attribute:: headers\n\n       Headers used for the request.\n\n   .. attribute:: exception\n\n       Exception raised during the request.\n\n\n.. class:: TraceRequestRedirectParams\n   :canonical: aiohttp.tracing.TraceRequestRedirectParams\n\n   See :attr:`TraceConfig.on_request_redirect` for details.\n\n   .. attribute:: method\n\n       Method used to get this redirect request.\n\n   .. attribute:: url\n\n       URL used for this redirect request.\n\n   .. attribute:: headers\n\n       Headers used for this redirect.\n\n   .. attribute:: response\n\n       Response :class:`ClientResponse` got from the redirect.\n\n\n.. class:: TraceConnectionQueuedStartParams\n   :canonical: aiohttp.tracing.TraceConnectionQueuedStartParams\n\n   See :attr:`TraceConfig.on_connection_queued_start` for details.\n\n   There are no attributes right now.\n\n\n.. class:: TraceConnectionQueuedEndParams\n   :canonical: aiohttp.tracing.TraceConnectionQueuedEndParams\n\n   See :attr:`TraceConfig.on_connection_queued_end` for details.\n\n   There are no attributes right now.\n\n\n.. class:: TraceConnectionCreateStartParams\n   :canonical: aiohttp.tracing.TraceConnectionCreateStartParams\n\n   See :attr:`TraceConfig.on_connection_create_start` for details.\n\n   There are no attributes right now.\n\n\n.. class:: TraceConnectionCreateEndParams\n   :canonical: aiohttp.tracing.TraceConnectionCreateEndParams\n\n   See :attr:`TraceConfig.on_connection_create_end` for details.\n\n   There are no attributes right now.\n\n\n.. class:: TraceConnectionReuseconnParams\n   :canonical: aiohttp.tracing.TraceConnectionReuseconnParams\n\n   See :attr:`TraceConfig.on_connection_reuseconn` for details.\n\n   There are no attributes right now.\n\n\n.. class:: TraceDnsResolveHostStartParams\n   :canonical: aiohttp.tracing.TraceDnsResolveHostStartParams\n\n   See :attr:`TraceConfig.on_dns_resolvehost_start` for details.\n\n   .. attribute:: host\n\n       Host that will be resolved.\n\n\n.. class:: TraceDnsResolveHostEndParams\n   :canonical: aiohttp.tracing.TraceDnsResolveHostEndParams\n\n   See :attr:`TraceConfig.on_dns_resolvehost_end` for details.\n\n   .. attribute:: host\n\n       Host that has been resolved.\n\n\n.. class:: TraceDnsCacheHitParams\n   :canonical: aiohttp.tracing.TraceDnsCacheHitParams\n\n   See :attr:`TraceConfig.on_dns_cache_hit` for details.\n\n   .. attribute:: host\n\n       Host found in the cache.\n\n\n.. class:: TraceDnsCacheMissParams\n   :canonical: aiohttp.tracing.TraceDnsCacheMissParams\n\n   See :attr:`TraceConfig.on_dns_cache_miss` for details.\n\n   .. attribute:: host\n\n       Host didn't find the cache.\n\n\n.. class:: TraceRequestHeadersSentParams\n   :canonical: aiohttp.tracing.TraceRequestHeadersSentParams\n\n   See :attr:`TraceConfig.on_request_headers_sent` for details.\n\n   .. versionadded:: 3.8\n\n   .. attribute:: method\n\n       Method that will be used to make the request.\n\n   .. attribute:: url\n\n       URL that will be used for the request.\n\n   .. attribute:: headers\n\n       Headers that will be used for the request.\n"
  },
  {
    "path": "docs/utilities.rst",
    "content": ".. currentmodule:: aiohttp\n\n.. _aiohttp-utilities:\n\nUtilities\n=========\n\nMiscellaneous API Shared between Client And Server.\n\n.. toctree::\n   :name: utilities\n   :maxdepth: 2\n\n   abc\n   multipart\n   multipart_reference\n   streams\n   structures\n   websocket_utilities\n"
  },
  {
    "path": "docs/web.rst",
    "content": ".. _aiohttp-web:\n\nServer\n======\n\n.. module:: aiohttp.web\n\nThe page contains all information about aiohttp Server API:\n\n\n.. toctree::\n   :name: server\n   :maxdepth: 3\n\n   Tutorial <https://demos.aiohttp.org>\n   Quickstart <web_quickstart>\n   Advanced Usage <web_advanced>\n   Low Level <web_lowlevel>\n   Reference <web_reference>\n   Web Exceptions <web_exceptions>\n   Logging <logging>\n   Testing <testing>\n   Deployment <deployment>\n"
  },
  {
    "path": "docs/web_advanced.rst",
    "content": ".. currentmodule:: aiohttp.web\n\n.. _aiohttp-web-advanced:\n\nWeb Server Advanced\n===================\n\nUnicode support\n---------------\n\n*aiohttp* does :term:`requoting` of incoming request path.\n\nUnicode (non-ASCII) symbols are processed transparently on both *route\nadding* and *resolving* (internally everything is converted to\n:term:`percent-encoding` form by :term:`yarl` library).\n\nBut in case of custom regular expressions for\n:ref:`aiohttp-web-variable-handler` please take care that URL is\n*percent encoded*: if you pass Unicode patterns they don't match to\n*requoted* path.\n\n.. _aiohttp-web-peer-disconnection:\n\nPeer disconnection\n------------------\n\n*aiohttp* has 2 approaches to handling client disconnections.\nIf you are familiar with asyncio, or scalability is a concern for\nyour application, we recommend using the handler cancellation method.\n\nRaise on read/write (default)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen a client peer is gone, a subsequent reading or writing raises :exc:`OSError`\nor a more specific exception like :exc:`ConnectionResetError`.\n\nThis behavior is similar to classic WSGI frameworks like Flask and Django.\n\nThe reason for disconnection varies; it can be a network issue or explicit\nsocket closing on the peer side without reading the full server response.\n\n*aiohttp* handles disconnection properly but you can handle it explicitly, e.g.::\n\n   async def handler(request):\n       try:\n           text = await request.text()\n       except OSError:\n           # disconnected\n\n.. _web-handler-cancellation:\n\nWeb handler cancellation\n^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis method can be enabled using the ``handler_cancellation`` parameter\nto :func:`run_app`.\n\nWhen a client disconnects, the web handler task will be cancelled. This\nis recommended as it can reduce the load on your server when there is no\nclient to receive a response. It can also help make your application\nmore resilient to DoS attacks (by requiring an attacker to keep a\nconnection open in order to waste server resources).\n\nThis behavior is very different from classic WSGI frameworks like\nFlask and Django. It requires a reasonable level of asyncio knowledge to\nuse correctly without causing issues in your code. We provide some\nexamples here to help understand the complexity and methods\nneeded to deal with them.\n\n.. warning::\n\n   :term:`web-handler` execution could be canceled on every ``await`` or\n   ``async with`` if client drops connection without reading entire response's BODY.\n\nSometimes it is a desirable behavior: on processing ``GET`` request the\ncode might fetch data from a database or other web resource, the\nfetching is potentially slow.\n\nCanceling this fetch is a good idea: the client dropped the connection\nalready, so there is no reason to waste time and resources (memory etc)\nby getting data from a DB without any chance to send it back to the client.\n\nBut sometimes the cancellation is bad: on ``POST`` requests very often\nit is needed to save data to a DB regardless of connection closing.\n\nCancellation prevention could be implemented in several ways:\n\n* Applying :func:`aiojobs.aiohttp.shield` to a coroutine that saves data.\n* Using aiojobs_ or another third party library to run a task in the background.\n\n:func:`aiojobs.aiohttp.shield` can work well. The only disadvantage is you\nneed to split the web handler into two async functions: one for the handler\nitself and another for protected code.\n\n.. warning::\n\n   We don't recommend using :func:`asyncio.shield` for this because the shielded\n   task cannot be tracked by the application and therefore there is a risk that\n   the task will get cancelled during application shutdown. The function provided\n   by aiojobs_ operates in the same way except the inner task will be tracked\n   by the Scheduler and will get waited on during the cleanup phase.\n\nFor example the following snippet is not safe::\n\n   from aiojobs.aiohttp import shield\n\n   async def handler(request):\n       await shield(request, write_to_redis(request))\n       await shield(request, write_to_postgres(request))\n       return web.Response(text=\"OK\")\n\nCancellation might occur while saving data in REDIS, so the\n``write_to_postgres`` function will not be called, potentially\nleaving your data in an inconsistent state.\n\nInstead, you would need to write something like::\n\n   async def write_data(request):\n       await write_to_redis(request)\n       await write_to_postgres(request)\n\n   async def handler(request):\n       await shield(request, write_data(request))\n       return web.Response(text=\"OK\")\n\nAlternatively, if you want to spawn a task without waiting for\nits completion, you can use aiojobs_ which provides an API for\nspawning new background jobs. It stores all scheduled activity in\ninternal data structures and can terminate them gracefully::\n\n   from aiojobs.aiohttp import setup, spawn\n\n   async def handler(request):\n       await spawn(request, write_data())\n       return web.Response()\n\n   app = web.Application()\n   setup(app)\n   app.router.add_get(\"/\", handler)\n\n.. warning::\n\n   Don't use :func:`asyncio.create_task` for this. All tasks\n   should be awaited at some point in your code (``aiojobs`` handles\n   this for you), otherwise you will hide legitimate exceptions\n   and result in warnings being emitted.\n\n   A good case for using :func:`asyncio.create_task` is when\n   you want to run something while you are processing other data,\n   but still want to ensure the task is complete before returning::\n\n       async def handler(request):\n           t = asyncio.create_task(get_some_data())\n           ...  # Do some other things, while data is being fetched.\n           data = await t\n           return web.Response(text=data)\n\nOne more approach would be to use :func:`aiojobs.aiohttp.atomic`\ndecorator to execute the entire handler as a new job. Essentially\nrestoring the default disconnection behavior only for specific handlers::\n\n   from aiojobs.aiohttp import atomic\n\n   @atomic\n   async def handler(request):\n       await write_to_db()\n       return web.Response()\n\n   app = web.Application()\n   setup(app)\n   app.router.add_post(\"/\", handler)\n\nIt prevents all of the ``handler`` async function from cancellation,\nso ``write_to_db`` will never be interrupted.\n\n.. _aiojobs: http://aiojobs.readthedocs.io/en/latest/\n\nPassing a coroutine into run_app and Gunicorn\n---------------------------------------------\n\n:func:`run_app` accepts either application instance or a coroutine for\nmaking an application. The coroutine based approach allows to perform\nasync IO before making an app::\n\n   async def app_factory():\n       await pre_init()\n       app = web.Application()\n       app.router.add_get(...)\n       return app\n\n   web.run_app(app_factory())\n\nGunicorn worker supports a factory as well. For Gunicorn the factory\nshould accept zero parameters::\n\n   async def my_web_app():\n       app = web.Application()\n       app.router.add_get(...)\n       return app\n\nStart gunicorn:\n\n.. code-block:: shell\n\n   $ gunicorn my_app_module:my_web_app --bind localhost:8080 --worker-class aiohttp.GunicornWebWorker\n\n.. versionadded:: 3.1\n\nCustom Routing Criteria\n-----------------------\n\nSometimes you need to register :ref:`handlers <aiohttp-web-handler>` on\nmore complex criteria than simply a *HTTP method* and *path* pair.\n\nAlthough :class:`UrlDispatcher` does not support any extra criteria, routing\nbased on custom conditions can be accomplished by implementing a second layer\nof routing in your application.\n\nThe following example shows custom routing based on the *HTTP Accept* header::\n\n   class AcceptChooser:\n\n       def __init__(self):\n           self._accepts = {}\n\n       async def do_route(self, request):\n           for accept in request.headers.getall('ACCEPT', []):\n               acceptor = self._accepts.get(accept)\n               if acceptor is not None:\n                   return (await acceptor(request))\n           raise HTTPNotAcceptable()\n\n       def reg_acceptor(self, accept, handler):\n           self._accepts[accept] = handler\n\n\n   async def handle_json(request):\n       # do json handling\n\n   async def handle_xml(request):\n       # do xml handling\n\n   chooser = AcceptChooser()\n   app.add_routes([web.get('/', chooser.do_route)])\n\n   chooser.reg_acceptor('application/json', handle_json)\n   chooser.reg_acceptor('application/xml', handle_xml)\n\n.. _aiohttp-web-static-file-handling:\n\nStatic file handling\n--------------------\n\nThe best way to handle static files (images, JavaScripts, CSS files\netc.) is using `Reverse Proxy`_ like `nginx`_ or `CDN`_ services.\n\n.. _Reverse Proxy: https://en.wikipedia.org/wiki/Reverse_proxy\n.. _nginx: https://nginx.org/\n.. _CDN: https://en.wikipedia.org/wiki/Content_delivery_network\n\nBut for development it's very convenient to handle static files by\naiohttp server itself.\n\nTo do it just register a new static route by\n:meth:`RouteTableDef.static` or :func:`static` calls::\n\n   app.add_routes([web.static('/prefix', path_to_static_folder)])\n\n   routes.static('/prefix', path_to_static_folder)\n\nWhen a directory is accessed within a static route then the server responses\nto client with ``HTTP/403 Forbidden`` by default. Displaying folder index\ninstead could be enabled with ``show_index`` parameter set to ``True``::\n\n   web.static('/prefix', path_to_static_folder, show_index=True)\n\nWhen a symlink that leads outside the static directory is accessed, the server\nresponds to the client with ``HTTP/404 Not Found`` by default. To allow the server to\nfollow symlinks that lead outside the static root, the parameter ``follow_symlinks``\nshould be set to ``True``::\n\n   web.static('/prefix', path_to_static_folder, follow_symlinks=True)\n\n.. caution::\n\n   Enabling ``follow_symlinks`` can be a security risk, and may lead to\n   a directory transversal attack. You do NOT need this option to follow symlinks\n   which point to somewhere else within the static directory, this option is only\n   used to break out of the security sandbox. Enabling this option is highly\n   discouraged, and only expected to be used for edge cases in a local\n   development setting where remote users do not have access to the server.\n\nWhen you want to enable cache busting,\nparameter ``append_version`` can be set to ``True``\n\nCache busting is the process of appending some form of file version hash\nto the filename of resources like JavaScript and CSS files.\nThe performance advantage of doing this is that we can tell the browser\nto cache these files indefinitely without worrying about the client not getting\nthe latest version when the file changes::\n\n   web.static('/prefix', path_to_static_folder, append_version=True)\n\nTemplate Rendering\n------------------\n\n:mod:`aiohttp.web` does not support template rendering out-of-the-box.\n\nHowever, there is a third-party library, :mod:`aiohttp_jinja2`, which is\nsupported by the *aiohttp* authors.\n\nUsing it is rather simple. First, setup a *jinja2 environment* with a call\nto :func:`aiohttp_jinja2.setup`::\n\n    app = web.Application()\n    aiohttp_jinja2.setup(app,\n        loader=jinja2.FileSystemLoader('/path/to/templates/folder'))\n\nAfter that you may use the template engine in your\n:ref:`handlers <aiohttp-web-handler>`. The most convenient way is to simply\nwrap your handlers with the  :func:`aiohttp_jinja2.template` decorator::\n\n    @aiohttp_jinja2.template('tmpl.jinja2')\n    async def handler(request):\n        return {'name': 'Andrew', 'surname': 'Svetlov'}\n\nIf you prefer the `Mako`_ template engine, please take a look at the\n`aiohttp_mako`_ library.\n\n.. warning::\n\n   :func:`aiohttp_jinja2.template` should be applied **before**\n   :meth:`RouteTableDef.get` decorator and family, e.g. it must be\n   the *first* (most *down* decorator in the chain)::\n\n\n      @routes.get('/path')\n      @aiohttp_jinja2.template('tmpl.jinja2')\n      async def handler(request):\n          return {'name': 'Andrew', 'surname': 'Svetlov'}\n\n\n.. _Mako: http://www.makotemplates.org/\n\n.. _aiohttp_mako: https://github.com/aio-libs/aiohttp_mako\n\n\n.. _aiohttp-web-websocket-read-same-task:\n\nReading from the same task in WebSockets\n----------------------------------------\n\nReading from the *WebSocket* (``await ws.receive()``) **must only** be\ndone inside the request handler *task*; however, writing\n(``ws.send_str(...)``) to the *WebSocket*, closing (``await\nws.close()``) and canceling the handler task may be delegated to other\ntasks. See also :ref:`FAQ section\n<aiohttp_faq_terminating_websockets>`.\n\n:mod:`aiohttp.web` creates an implicit :class:`asyncio.Task` for\nhandling every incoming request.\n\n.. note::\n\n   While :mod:`aiohttp.web` itself only supports *WebSockets* without\n   downgrading to *LONG-POLLING*, etc., our team supports SockJS_, an\n   aiohttp-based library for implementing SockJS-compatible server\n   code.\n\n.. _SockJS: https://github.com/aio-libs/sockjs\n\n\n.. warning::\n\n   Parallel reads from websocket are forbidden, there is no\n   possibility to call :meth:`WebSocketResponse.receive`\n   from two tasks.\n\n   See :ref:`FAQ section <aiohttp_faq_parallel_event_sources>` for\n   instructions how to solve the problem.\n\n\n.. _aiohttp-web-data-sharing:\n\nData Sharing aka No Singletons Please\n-------------------------------------\n\n:mod:`aiohttp.web` discourages the use of *global variables*, aka *singletons*.\nEvery variable should have its own context that is *not global*.\n\nGlobal variables are generally considered bad practice due to the complexity\nthey add in keeping track of state changes to variables.\n\n*aiohttp* does not use globals by design, which will reduce the number of bugs\nand/or unexpected behaviors for its users. For example, an i18n translated string\nbeing written for one request and then being served to another.\n\nSo, :class:`Application` and :class:`Request`\nsupport a :class:`collections.abc.MutableMapping` interface (i.e. they are\ndict-like objects), allowing them to be used as data stores.\n\n\n.. _aiohttp-web-data-sharing-app-config:\n\nApplication's config\n^^^^^^^^^^^^^^^^^^^^\n\nFor storing *global-like* variables, feel free to save them in an\n:class:`Application` instance::\n\n    app['my_private_key'] = data\n\nand get it back in the :term:`web-handler`::\n\n    async def handler(request):\n        data = request.app['my_private_key']\n\nRather than using :class:`str` keys, we recommend using :class:`AppKey`.\nThis is required for type safety (e.g. when checking with mypy)::\n\n    my_private_key = web.AppKey(\"my_private_key\", str)\n    app[my_private_key] = data\n\n    async def handler(request: web.Request):\n        data = request.app[my_private_key]\n        # reveal_type(data) -> str\n\nIn case of :ref:`nested applications\n<aiohttp-web-nested-applications>` the desired lookup strategy could\nbe the following:\n\n1. Search the key in the current nested application.\n2. If the key is not found continue searching in the parent application(s).\n\nFor this please use :attr:`Request.config_dict` read-only property::\n\n    async def handler(request):\n        data = request.config_dict[my_private_key]\n\nThe app object can be used in this way to reuse a database connection or anything\nelse needed throughout the application.\n\nSee this reference section for more detail: :ref:`aiohttp-web-app-and-router`.\n\nRequest's storage\n^^^^^^^^^^^^^^^^^\n\nVariables that are only needed for the lifetime of a :class:`Request`, can be\nstored in a :class:`Request`. Similarly to :class:`Application`, :class:`RequestKey`\ninstances or strings can be used as keys::\n\n    my_private_key = web.RequestKey(\"my_private_key\", str)\n\n    async def handler(request):\n      request[my_private_key] = \"data\"\n      ...\n\nThis is mostly useful for :ref:`aiohttp-web-middlewares` and\n:ref:`aiohttp-web-signals` handlers to store data for further processing by the\nnext handlers in the chain.\n\nResponse's storage\n^^^^^^^^^^^^^^^^^^\n\n:class:`StreamResponse` and :class:`Response` objects\nalso support :class:`collections.abc.MutableMapping` interface. This is useful\nwhen you want to share data with signals and middlewares once all the work in\nthe handler is done::\n\n    my_metric_key = web.ResponseKey(\"my_metric_key\", int)\n\n    async def handler(request):\n      [ do all the work ]\n      response[my_metric_key] = 123\n      return response\n\n\nNaming hint\n^^^^^^^^^^^\n\nTo avoid clashing with other *aiohttp* users and third-party libraries, please\nchoose a unique key name for storing data.\n\nIf your code is published on PyPI, then the project name is most likely unique\nand safe to use as the key.\nOtherwise, something based on your company name/url would be satisfactory (i.e.\n``org.company.app``).\n\n\n.. _aiohttp-web-contextvars:\n\n\nContextVars support\n-------------------\n\nAsyncio has :mod:`Context Variables <contextvars>` as a context-local storage\n(a generalization of thread-local concept that works with asyncio tasks also).\n\n\n*aiohttp* server supports it in the following way:\n\n* A server inherits the current task's context used when creating it.\n  :func:`aiohttp.web.run_app()` runs a task for handling all underlying jobs running\n  the app, but alternatively :ref:`aiohttp-web-app-runners` can be used.\n\n* Application initialization / finalization events (:attr:`Application.cleanup_ctx`,\n  :attr:`Application.on_startup` and :attr:`Application.on_shutdown`,\n  :attr:`Application.on_cleanup`) are executed inside the same context.\n\n  E.g. all context modifications made on application startup are visible on teardown.\n\n* On every request handling *aiohttp* creates a context copy. :term:`web-handler` has\n  all variables installed on initialization stage. But the context modification made by\n  a handler or middleware is invisible to another HTTP request handling call.\n\nAn example of context vars usage::\n\n    from contextvars import ContextVar\n\n    from aiohttp import web\n\n    VAR = ContextVar('VAR', default='default')\n\n\n    async def coro():\n        return VAR.get()\n\n\n    async def handler(request):\n        var = VAR.get()\n        VAR.set('handler')\n        ret = await coro()\n        return web.Response(text='\\n'.join([var,\n                                            ret]))\n\n\n    async def on_startup(app):\n        print('on_startup', VAR.get())\n        VAR.set('on_startup')\n\n\n    async def on_cleanup(app):\n        print('on_cleanup', VAR.get())\n        VAR.set('on_cleanup')\n\n\n    async def init():\n        print('init', VAR.get())\n        VAR.set('init')\n        app = web.Application()\n        app.router.add_get('/', handler)\n\n        app.on_startup.append(on_startup)\n        app.on_cleanup.append(on_cleanup)\n        return app\n\n\n    web.run_app(init())\n    print('done', VAR.get())\n\n.. versionadded:: 3.5\n\n\n.. _aiohttp-web-middlewares:\n\nMiddlewares\n-----------\n\n:mod:`aiohttp.web` provides a powerful mechanism for customizing\n:ref:`request handlers<aiohttp-web-handler>` via *middlewares*.\n\nA *middleware* is a coroutine that can modify either the request or\nresponse. For example, here's a simple *middleware* which appends\n``' wink'`` to the response::\n\n    from aiohttp import web\n    from typing import Callable, Awaitable\n\n    async def middleware(\n        request: web.Request,\n        handler: Callable[[web.Request], Awaitable[web.StreamResponse]]\n    ) -> web.StreamResponse:\n        resp = await handler(request)\n        resp.text = resp.text + ' wink'\n        return resp\n\n.. warning::\n\n   As of version ``4.0.0`` \"new-style\" middleware is default and the\n   ``@middleware`` decorator is not required (and is deprecated), you can\n   simply remove the decorator. \"Old-style\" middleware (a coroutine which\n   returned a coroutine) is no longer supported.\n\n.. note::\n\n   The example won't work with streamed responses or websockets\n\nEvery *middleware* should accept two parameters, a :class:`request\n<Request>` instance and a *handler*, and return the response or raise\nan exception. If the exception is not an instance of\n:exc:`HTTPException` it is converted to ``500``\n:exc:`HTTPInternalServerError` after processing the\nmiddlewares chain.\n\n.. warning::\n\n   Second argument should be named *handler* exactly.\n\nWhen creating an :class:`Application`, these *middlewares* are passed to\nthe keyword-only ``middlewares`` parameter::\n\n   app = web.Application(middlewares=[middleware_1,\n                                      middleware_2])\n\nInternally, a single :ref:`request handler <aiohttp-web-handler>` is constructed\nby applying the middleware chain to the original handler in reverse order,\nand is called by the :class:`~aiohttp.web.RequestHandler` as a regular *handler*.\n\nSince *middlewares* are themselves coroutines, they may perform extra\n``await`` calls when creating a new handler, e.g. call database etc.\n\n*Middlewares* usually call the handler, but they may choose to ignore it,\ne.g. displaying *403 Forbidden page* or raising :exc:`HTTPForbidden` exception\nif the user does not have permissions to access the underlying resource.\nThey may also render errors raised by the handler, perform some pre- or\npost-processing like handling *CORS* and so on.\n\nThe following code demonstrates middlewares execution order::\n\n   from aiohttp import web\n   from typing import Callable, Awaitable\n\n   async def test(request: web.Request) -> web.Response:\n       print('Handler function called')\n       return web.Response(text=\"Hello\")\n\n   async def middleware1(\n       request: web.Request,\n       handler: Callable[[web.Request], Awaitable[web.StreamResponse]]\n   ) -> web.StreamResponse:\n       print('Middleware 1 called')\n       response = await handler(request)\n       print('Middleware 1 finished')\n       return response\n\n   async def middleware2(\n       request: web.Request,\n       handler: Callable[[web.Request], Awaitable[web.StreamResponse]]\n   ) -> web.StreamResponse:\n       print('Middleware 2 called')\n       response = await handler(request)\n       print('Middleware 2 finished')\n       return response\n\n\n   app = web.Application(middlewares=[middleware1, middleware2])\n   app.router.add_get('/', test)\n   web.run_app(app)\n\nProduced output::\n\n   Middleware 1 called\n   Middleware 2 called\n   Handler function called\n   Middleware 2 finished\n   Middleware 1 finished\n\nRequest Body Stream Consumption\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. warning::\n\n   When middleware reads the request body (using :meth:`~aiohttp.web.BaseRequest.read`,\n   :meth:`~aiohttp.web.BaseRequest.text`, :meth:`~aiohttp.web.BaseRequest.json`, or\n   :meth:`~aiohttp.web.BaseRequest.post`), the body stream is consumed. However, these\n   high-level methods cache their result, so subsequent calls from the handler or other\n   middleware will return the same cached value.\n\n   The important distinction is:\n\n   - High-level methods (:meth:`~aiohttp.web.BaseRequest.read`, :meth:`~aiohttp.web.BaseRequest.text`,\n     :meth:`~aiohttp.web.BaseRequest.json`, :meth:`~aiohttp.web.BaseRequest.post`) cache their\n     results internally, so they can be called multiple times and will return the same value.\n   - Direct stream access via :attr:`~aiohttp.web.BaseRequest.content` does NOT have this\n     caching behavior. Once you read from ``request.content`` directly (e.g., using\n     ``await request.content.read()``), subsequent reads will return empty bytes.\n\nConsider this middleware that logs request bodies::\n\n    from aiohttp import web\n    from typing import Callable, Awaitable\n\n    async def logging_middleware(\n        request: web.Request,\n        handler: Callable[[web.Request], Awaitable[web.StreamResponse]]\n    ) -> web.StreamResponse:\n        # This consumes the request body stream\n        body = await request.text()\n        print(f\"Request body: {body}\")\n        return await handler(request)\n\n    async def handler(request: web.Request) -> web.Response:\n        # This will return the same value that was read in the middleware\n        # (i.e., the cached result, not an empty string)\n        body = await request.text()\n        return web.Response(text=f\"Received: {body}\")\n\nIn contrast, when accessing the stream directly (not recommended in middleware)::\n\n    async def stream_middleware(\n        request: web.Request,\n        handler: Callable[[web.Request], Awaitable[web.StreamResponse]]\n    ) -> web.StreamResponse:\n        # Reading directly from the stream - this consumes it!\n        data = await request.content.read()\n        print(f\"Stream data: {data}\")\n        return await handler(request)\n\n    async def handler(request: web.Request) -> web.Response:\n        # This will return empty bytes because the stream was already consumed\n        data = await request.content.read()\n        # data will be b'' (empty bytes)\n\n        # However, high-level methods would still work if called for the first time:\n        # body = await request.text()  # This would read from internal cache if available\n        return web.Response(text=f\"Received: {data}\")\n\nWhen working with raw stream data that needs to be shared between middleware and handlers::\n\n    raw_body_key = web.RequestKey(\"raw_body_key\", bytes)\n\n    async def stream_parsing_middleware(\n        request: web.Request,\n        handler: Callable[[web.Request], Awaitable[web.StreamResponse]]\n    ) -> web.StreamResponse:\n        # Read stream once and store the data\n        raw_data = await request.content.read()\n        request[raw_body_key] = raw_data\n        return await handler(request)\n\n    async def handler(request: web.Request) -> web.Response:\n        # Access the stored data instead of reading the stream again\n        raw_data = request.get(raw_body_key, b'')\n        return web.Response(body=raw_data)\n\nExample\n^^^^^^^\n\nA common use of middlewares is to implement custom error pages.  The following\nexample will render 404 errors using a JSON response, as might be appropriate\na JSON REST service::\n\n    from aiohttp import web\n\n    async def error_middleware(request, handler):\n        try:\n            response = await handler(request)\n            if response.status != 404:\n                return response\n            message = response.message\n        except web.HTTPException as ex:\n            if ex.status != 404:\n                raise\n            message = ex.reason\n        return web.json_response({'error': message})\n\n    app = web.Application(middlewares=[error_middleware])\n\n\nMiddleware Factory\n^^^^^^^^^^^^^^^^^^\n\nA *middleware factory* is a function that creates a middleware with passed\narguments. For example, here's a trivial *middleware factory*::\n\n    def middleware_factory(text):\n        async def sample_middleware(request, handler):\n            resp = await handler(request)\n            resp.text = resp.text + text\n            return resp\n        return sample_middleware\n\nNote that in contrast to regular middlewares, a middleware factory should\nreturn the function, not the value. So when passing a middleware factory\nto the app you actually need to call it::\n\n    app = web.Application(middlewares=[middleware_factory(' wink')])\n\n.. _aiohttp-web-signals:\n\nSignals\n-------\n\nAlthough :ref:`middlewares <aiohttp-web-middlewares>` can customize\n:ref:`request handlers<aiohttp-web-handler>` before or after a :class:`Response`\nhas been prepared, they can't customize a :class:`Response` **while** it's\nbeing prepared. For this :mod:`aiohttp.web` provides *signals*.\n\nFor example, a middleware can only change HTTP headers for *unprepared*\nresponses (see :meth:`StreamResponse.prepare`), but sometimes we\nneed a hook for changing HTTP headers for streamed responses and WebSockets.\nThis can be accomplished by subscribing to the\n:attr:`Application.on_response_prepare` signal, which is called after default\nheaders have been computed and directly before headers are sent::\n\n    async def on_prepare(request, response):\n        response.headers['My-Header'] = 'value'\n\n    app.on_response_prepare.append(on_prepare)\n\n\nAdditionally, the :attr:`Application.on_startup` and\n:attr:`Application.on_cleanup` signals can be subscribed to for\napplication component setup and tear down accordingly.\n\nThe following example will properly initialize and dispose an asyncpg connection\nengine::\n\n    from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine\n\n    pg_engine = web.AppKey(\"pg_engine\", AsyncEngine)\n\n    async def create_pg(app):\n        app[pg_engine] = await create_async_engine(\n            \"postgresql+asyncpg://postgre:@localhost:5432/postgre\"\n        )\n\n    async def dispose_pg(app):\n        await app[pg_engine].dispose()\n\n    app.on_startup.append(create_pg)\n    app.on_cleanup.append(dispose_pg)\n\n\nSignal handlers should not return a value but may modify incoming mutable\nparameters.\n\nSignal handlers will be run sequentially, in order they were\nadded. All handlers must be asynchronous since *aiohttp* 3.0.\n\n.. _aiohttp-web-cleanup-ctx:\n\nCleanup Context\n---------------\n\nBare :attr:`Application.on_startup` / :attr:`Application.on_cleanup`\npair still has a pitfall: signals handlers are independent on each other.\n\nE.g. we have ``[create_pg, create_redis]`` in *startup* signal and\n``[dispose_pg, dispose_redis]`` in *cleanup*.\n\nIf, for example, ``create_pg(app)`` call fails ``create_redis(app)``\nis not called. But on application cleanup both ``dispose_pg(app)`` and\n``dispose_redis(app)`` are still called: *cleanup signal* has no\nknowledge about startup/cleanup pairs and their execution state.\n\n\nThe solution is :attr:`Application.cleanup_ctx` usage::\n\n    @contextlib.asynccontextmanager\n    async def pg_engine(app: web.Application):\n        app[pg_engine] = await create_async_engine(\n            \"postgresql+asyncpg://postgre:@localhost:5432/postgre\"\n        )\n        yield\n        await app[pg_engine].dispose()\n\n    app.cleanup_ctx.append(pg_engine)\n\nThe attribute is a list of *asynchronous generators*, a code *before*\n``yield`` is an initialization stage (called on *startup*), a code\n*after* ``yield`` is executed on *cleanup*. The generator must have only\none ``yield``.\n\n*aiohttp* guarantees that *cleanup code* is called if and only if\n*startup code* was successfully finished.\n\n.. versionadded:: 3.1\n\n.. _aiohttp-web-nested-applications:\n\nNested applications\n-------------------\n\nSub applications are designed for solving the problem of the big\nmonolithic code base.\nLet's assume we have a project with own business logic and tools like\nadministration panel and debug toolbar.\n\nAdministration panel is a separate application by its own nature but all\ntoolbar URLs are served by prefix like ``/admin``.\n\nThus we'll create a totally separate application named ``admin`` and\nconnect it to main app with prefix by\n:meth:`Application.add_subapp`::\n\n   admin = web.Application()\n   # setup admin routes, signals and middlewares\n\n   app.add_subapp('/admin/', admin)\n\nMiddlewares and signals from ``app`` and ``admin`` are chained.\n\nIt means that if URL is ``'/admin/something'`` middlewares from\n``app`` are applied first and ``admin.middlewares`` are the next in\nthe call chain.\n\nThe same is going for\n:attr:`Application.on_response_prepare` signal -- the\nsignal is delivered to both top level ``app`` and ``admin`` if\nprocessing URL is routed to ``admin`` sub-application.\n\nCommon signals like :attr:`Application.on_startup`,\n:attr:`Application.on_shutdown` and\n:attr:`Application.on_cleanup` are delivered to all\nregistered sub-applications. The passed parameter is sub-application\ninstance, not top-level application.\n\n\nThird level sub-applications can be nested into second level ones --\nthere are no limitation for nesting level.\n\nUrl reversing for sub-applications should generate urls with proper prefix.\n\nBut for getting URL sub-application's router should be used::\n\n   admin = web.Application()\n   admin.add_routes([web.get('/resource', handler, name='name')])\n\n   app.add_subapp('/admin/', admin)\n\n   url = admin.router['name'].url_for()\n\nThe generated ``url`` from example will have a value\n``URL('/admin/resource')``.\n\nIf main application should do URL reversing for sub-application it could\nuse the following explicit technique::\n\n   admin = web.Application()\n   admin_key = web.AppKey('admin_key', web.Application)\n   admin.add_routes([web.get('/resource', handler, name='name')])\n\n   app.add_subapp('/admin/', admin)\n   app[admin_key] = admin\n\n   async def handler(request: web.Request):  # main application's handler\n       admin = request.app[admin_key]\n       url = admin.router['name'].url_for()\n\n.. _aiohttp-web-expect-header:\n\n*Expect* Header\n---------------\n\n:mod:`aiohttp.web` supports *Expect* header. By default it sends\n``HTTP/1.1 100 Continue`` line to client, or raises\n:exc:`HTTPExpectationFailed` if header value is not equal to\n\"100-continue\". It is possible to specify custom *Expect* header\nhandler on per route basis. This handler gets called if *Expect*\nheader exist in request after receiving all headers and before\nprocessing application's :ref:`aiohttp-web-middlewares` and\nroute handler. Handler can return *None*, in that case the request\nprocessing continues as usual. If handler returns an instance of class\n:class:`StreamResponse`, *request handler* uses it as response. Also\nhandler can raise a subclass of :exc:`HTTPException`. In this case all\nfurther processing will not happen and client will receive appropriate\nhttp response.\n\n.. note::\n    A server that does not understand or is unable to comply with any of the\n    expectation values in the Expect field of a request MUST respond with\n    appropriate error status. The server MUST respond with a 417\n    (Expectation Failed) status if any of the expectations cannot be met or,\n    if there are other problems with the request, some other 4xx status.\n\n    http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.20\n\nIf all checks pass, the custom handler *must* write a *HTTP/1.1 100 Continue*\nstatus code before returning.\n\nThe following example shows how to setup a custom handler for the *Expect*\nheader::\n\n   async def check_auth(request):\n       if request.version != aiohttp.HttpVersion11:\n           return\n\n       if request.headers.get('EXPECT') != '100-continue':\n           raise HTTPExpectationFailed(text=\"Unknown Expect: %s\" % expect)\n\n       if request.headers.get('AUTHORIZATION') is None:\n           raise HTTPForbidden()\n\n       request.transport.write(b\"HTTP/1.1 100 Continue\\r\\n\\r\\n\")\n\n   async def hello(request):\n       return web.Response(body=b\"Hello, world\")\n\n   app = web.Application()\n   app.add_routes([web.add_get('/', hello, expect_handler=check_auth)])\n\n.. _aiohttp-web-custom-resource:\n\nCustom resource implementation\n------------------------------\n\nTo register custom resource use :meth:`~aiohttp.web.UrlDispatcher.register_resource`.\nResource instance must implement `AbstractResource` interface.\n\n.. _aiohttp-web-app-runners:\n\nApplication runners\n-------------------\n\n:func:`run_app` provides a simple *blocking* API for running an\n:class:`Application`.\n\nFor starting the application *asynchronously* or serving on multiple\nHOST/PORT :class:`AppRunner` exists.\n\nThe simple startup code for serving HTTP site on ``'localhost'``, port\n``8080`` looks like::\n\n    runner = web.AppRunner(app)\n    await runner.setup()\n    site = web.TCPSite(runner, 'localhost', 8080)\n    await site.start()\n\n    while True:\n        await asyncio.sleep(3600)  # sleep forever\n\nTo stop serving call :meth:`AppRunner.cleanup`::\n\n    await runner.cleanup()\n\n.. versionadded:: 3.0\n\n.. _aiohttp-web-graceful-shutdown:\n\nGraceful shutdown\n------------------\n\nStopping *aiohttp web server* by just closing all connections is not\nalways satisfactory.\n\nWhen aiohttp is run with :func:`run_app`, it will attempt a graceful shutdown\nby following these steps (if using a :ref:`runner <aiohttp-web-app-runners>`,\nthen calling :meth:`AppRunner.cleanup` will perform these steps, excluding\nstep 7).\n\n1. Stop each site listening on sockets, so new connections will be rejected.\n2. Close idle keep-alive connections (and set active ones to close upon completion).\n3. Call the :attr:`Application.on_shutdown` signal. This should be used to shutdown\n   long-lived connections, such as websockets (see below).\n4. Wait a short time for running handlers to complete. This allows any pending handlers\n   to complete successfully. The timeout can be adjusted with ``shutdown_timeout``\n   in :func:`run_app`.\n5. Close any remaining connections and cancel their handlers. It will wait on the\n   canceling handlers for a short time, again adjustable with ``shutdown_timeout``.\n6. Call the :attr:`Application.on_cleanup` signal. This should be used to cleanup any\n   resources (such as DB connections). This includes completing the\n   :ref:`cleanup contexts<aiohttp-web-cleanup-ctx>` which may be used to ensure\n   background tasks are completed successfully (see\n   :ref:`handler cancellation<web-handler-cancellation>` or aiojobs_ for examples).\n7. Cancel any remaining tasks and wait on them to complete.\n\nWebsocket shutdown\n^^^^^^^^^^^^^^^^^^\n\nOne problem is if the application supports :term:`websockets <websocket>` or\n*data streaming* it most likely has open connections at server shutdown time.\n\nThe *library* has no knowledge how to close them gracefully but a developer can\nhelp by registering an :attr:`Application.on_shutdown` signal handler.\n\nA developer should keep a list of opened connections\n(:class:`Application` is a good candidate).\n\nThe following :term:`websocket` snippet shows an example of a websocket handler::\n\n    from aiohttp import web\n    import weakref\n\n    app = web.Application()\n    websockets = web.AppKey(\"websockets\", weakref.WeakSet)\n    app[websockets] = weakref.WeakSet()\n\n    async def websocket_handler(request):\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        request.app[websockets].add(ws)\n        try:\n            async for msg in ws:\n                ...\n        finally:\n            request.app[websockets].discard(ws)\n\n        return ws\n\nThen the signal handler may look like::\n\n    from aiohttp import WSCloseCode\n\n    async def on_shutdown(app):\n        for ws in set(app[websockets]):\n            await ws.close(code=WSCloseCode.GOING_AWAY, message=\"Server shutdown\")\n\n    app.on_shutdown.append(on_shutdown)\n\n.. _aiohttp-web-ceil-absolute-timeout:\n\nCeil of absolute timeout value\n------------------------------\n\n*aiohttp* **ceils** internal timeout values if the value is equal or\ngreater than 5 seconds. The timeout expires at the next integer second\ngreater than ``current_time + timeout``.\n\nMore details about ceiling absolute timeout values is available here\n:ref:`aiohttp-client-timeouts`.\n\nThe default threshold can be configured at :class:`aiohttp.web.Application`\nlevel using the ``handler_args`` parameter.\n\n.. code-block:: python3\n\n    app = web.Application(handler_args={\"timeout_ceil_threshold\": 1})\n\n.. _aiohttp-web-background-tasks:\n\nBackground tasks\n-----------------\n\nSometimes there's a need to perform some asynchronous operations just\nafter application start-up.\n\nEven more, in some sophisticated systems there could be a need to run some\nbackground tasks in the event loop along with the application's request\nhandler. Such as listening to message queue or other network message/event\nsources (e.g. ZeroMQ, Redis Pub/Sub, AMQP, etc.) to react to received messages\nwithin the application.\n\nFor example the background task could listen to ZeroMQ on\n``zmq.SUB`` socket, process and forward retrieved messages to\nclients connected via WebSocket that are stored somewhere in the\napplication (e.g. in the ``application['websockets']`` list).\n\nTo run such short and long running background tasks aiohttp provides an\nability to register :attr:`Application.on_startup` signal handler(s) that\nwill run along with the application's request handler.\n\nFor example there's a need to run one quick task and two long running\ntasks that will live till the application is alive. The appropriate\nbackground tasks could be registered as an :attr:`Application.on_startup`\nsignal handler or :attr:`Application.cleanup_ctx` as shown in the example\nbelow::\n\n  async def listen_to_redis(app: web.Application):\n      client = redis.from_url(\"redis://localhost:6379\")\n      channel = \"news\"\n      async with client.pubsub() as pubsub:\n          await pubsub.subscribe(channel)\n          while True:\n              msg = await pubsub.get_message(ignore_subscribe_messages=True)\n              if msg is not None:\n                  for ws in app[\"websockets\"]:\n                      await ws.send_str(\"{}: {}\".format(channel, msg))\n\n\n  @contextlib.asynccontextmanager\n  async def background_tasks(app):\n      app[redis_listener] = asyncio.create_task(listen_to_redis(app))\n\n      yield\n\n      app[redis_listener].cancel()\n      with contextlib.suppress(asyncio.CancelledError):\n          await app[redis_listener]\n\n\n  app = web.Application()\n  redis_listener = web.AppKey(\"redis_listener\", asyncio.Task[None])\n  app.cleanup_ctx.append(background_tasks)\n  web.run_app(app)\n\n\nThe task ``listen_to_redis`` will run forever.\nTo shut it down correctly :attr:`Application.on_cleanup` signal handler\nmay be used to send a cancellation to it.\n\n.. _aiohttp-web-complex-applications:\n\nComplex Applications\n^^^^^^^^^^^^^^^^^^^^\n\nSometimes aiohttp is not the sole part of an application and additional\ntasks/processes may need to be run alongside the aiohttp :class:`Application`.\n\nGenerally, the best way to achieve this is to use :func:`aiohttp.web.run_app`\nas the entry point for the program. Other tasks can then be run via\n:attr:`Application.startup` and :attr:`Application.on_cleanup`. By having the\n:class:`Application` control the lifecycle of the entire program, the code\nwill be more robust and ensure that the tasks are started and stopped along\nwith the application.\n\nFor example, running a long-lived task alongside the :class:`Application`\ncan be done with a :ref:`aiohttp-web-cleanup-ctx` function like::\n\n\n  @contextlib.asynccontextmanager\n  async def run_other_task(_app):\n      task = asyncio.create_task(other_long_task())\n\n      yield\n\n      task.cancel()\n      with suppress(asyncio.CancelledError):\n          await task  # Ensure any exceptions etc. are raised.\n\n  app.cleanup_ctx.append(run_other_task)\n\n\nOr a separate process can be run with something like::\n\n\n  @contextlib.asynccontextmanager\n  async def run_process(_app):\n      proc = await asyncio.create_subprocess_exec(path)\n\n      yield\n\n      if proc.returncode is None:\n          proc.terminate()\n      await proc.wait()\n\n  app.cleanup_ctx.append(run_process)\n\n\nHandling error pages\n--------------------\n\nPages like *404 Not Found* and *500 Internal Error* could be handled\nby custom middleware, see :ref:`polls demo <aiohttpdemos:aiohttp-demos-polls-middlewares>`\nfor example.\n\n.. _aiohttp-web-forwarded-support:\n\nDeploying behind a Proxy\n------------------------\n\nAs discussed in :ref:`aiohttp-deployment` the preferable way is\ndeploying *aiohttp* web server behind a *Reverse Proxy Server* like\n:term:`nginx` for production usage.\n\nIn this way properties like :attr:`BaseRequest.scheme`\n:attr:`BaseRequest.host` and :attr:`BaseRequest.remote` are\nincorrect.\n\nReal values should be given from proxy server, usually either\n``Forwarded`` or old-fashion ``X-Forwarded-For``,\n``X-Forwarded-Host``, ``X-Forwarded-Proto`` HTTP headers are used.\n\n*aiohttp* does not take *forwarded* headers into account by default\nbecause it produces *security issue*: HTTP client might add these\nheaders too, pushing non-trusted data values.\n\nThat's why *aiohttp server* should setup *forwarded* headers in custom\nmiddleware in tight conjunction with *reverse proxy configuration*.\n\nFor changing :attr:`BaseRequest.scheme` :attr:`BaseRequest.host`\n:attr:`BaseRequest.remote` and :attr:`BaseRequest.client_max_size`\nthe middleware might use :meth:`BaseRequest.clone`.\n\n.. seealso::\n\n   https://github.com/aio-libs/aiohttp-remotes provides secure helpers\n   for modifying *scheme*, *host* and *remote* attributes according\n   to ``Forwarded`` and ``X-Forwarded-*`` HTTP headers.\n\nCORS support\n------------\n\n:mod:`aiohttp.web` itself does not support `Cross-Origin Resource\nSharing <https://en.wikipedia.org/wiki/Cross-origin_resource_sharing>`_, but\nthere is an aiohttp plugin for it:\n`aiohttp-cors <https://github.com/aio-libs/aiohttp-cors>`_.\n\n\nDebug Toolbar\n-------------\n\n`aiohttp-debugtoolbar`_ is a very useful library that provides a\ndebugging toolbar while you're developing an :mod:`aiohttp.web`\napplication.\n\nInstall it with ``pip``:\n\n.. code-block:: shell\n\n    $ pip install aiohttp_debugtoolbar\n\n\nJust call :func:`aiohttp_debugtoolbar.setup`::\n\n    import aiohttp_debugtoolbar\n    from aiohttp_debugtoolbar import toolbar_middleware_factory\n\n    app = web.Application()\n    aiohttp_debugtoolbar.setup(app)\n\nThe toolbar is ready to use. Enjoy!!!\n\n.. _aiohttp-debugtoolbar: https://github.com/aio-libs/aiohttp_debugtoolbar\n\n\nDev Tools\n---------\n\n`aiohttp-devtools`_ provides a couple of tools to simplify development of\n:mod:`aiohttp.web` applications.\n\n\nInstall with ``pip``:\n\n.. code-block:: shell\n\n    $ pip install aiohttp-devtools\n\n``adev runserver`` provides a development server with auto-reload,\nlive-reload, static file serving.\n\nDocumentation and a complete tutorial of creating and running an app\nlocally are available at `aiohttp-devtools`_.\n\n.. _aiohttp-devtools: https://github.com/aio-libs/aiohttp-devtools\n"
  },
  {
    "path": "docs/web_exceptions.rst",
    "content": ".. currentmodule:: aiohttp.web\n\n.. _aiohttp-web-exceptions:\n\nWeb Server Exceptions\n=====================\n\nOverview\n--------\n\n:mod:`aiohttp.web` defines a set of exceptions for every *HTTP status code*.\n\nEach exception is a subclass of :exc:`HTTPException` and relates to a single\nHTTP status code::\n\n    async def handler(request):\n        raise aiohttp.web.HTTPFound('/redirect')\n\nEach exception class has a status code according to :rfc:`2068`:\ncodes with 100-300 are not really errors; 400s are client errors,\nand 500s are server errors.\n\nHTTP Exception hierarchy chart::\n\n   Exception\n     HTTPException\n       HTTPSuccessful\n         * 200 - HTTPOk\n         * 201 - HTTPCreated\n         * 202 - HTTPAccepted\n         * 203 - HTTPNonAuthoritativeInformation\n         * 204 - HTTPNoContent\n         * 205 - HTTPResetContent\n         * 206 - HTTPPartialContent\n       HTTPRedirection\n         * 304 - HTTPNotModified\n         HTTPMove\n           * 300 - HTTPMultipleChoices\n           * 301 - HTTPMovedPermanently\n           * 302 - HTTPFound\n           * 303 - HTTPSeeOther\n           * 305 - HTTPUseProxy\n           * 307 - HTTPTemporaryRedirect\n           * 308 - HTTPPermanentRedirect\n       HTTPError\n         HTTPClientError\n           * 400 - HTTPBadRequest\n           * 401 - HTTPUnauthorized\n           * 402 - HTTPPaymentRequired\n           * 403 - HTTPForbidden\n           * 404 - HTTPNotFound\n           * 405 - HTTPMethodNotAllowed\n           * 406 - HTTPNotAcceptable\n           * 407 - HTTPProxyAuthenticationRequired\n           * 408 - HTTPRequestTimeout\n           * 409 - HTTPConflict\n           * 410 - HTTPGone\n           * 411 - HTTPLengthRequired\n           * 412 - HTTPPreconditionFailed\n           * 413 - HTTPRequestEntityTooLarge\n           * 414 - HTTPRequestURITooLong\n           * 415 - HTTPUnsupportedMediaType\n           * 416 - HTTPRequestRangeNotSatisfiable\n           * 417 - HTTPExpectationFailed\n           * 421 - HTTPMisdirectedRequest\n           * 422 - HTTPUnprocessableEntity\n           * 424 - HTTPFailedDependency\n           * 426 - HTTPUpgradeRequired\n           * 428 - HTTPPreconditionRequired\n           * 429 - HTTPTooManyRequests\n           * 431 - HTTPRequestHeaderFieldsTooLarge\n           * 451 - HTTPUnavailableForLegalReasons\n         HTTPServerError\n           * 500 - HTTPInternalServerError\n           * 501 - HTTPNotImplemented\n           * 502 - HTTPBadGateway\n           * 503 - HTTPServiceUnavailable\n           * 504 - HTTPGatewayTimeout\n           * 505 - HTTPVersionNotSupported\n           * 506 - HTTPVariantAlsoNegotiates\n           * 507 - HTTPInsufficientStorage\n           * 510 - HTTPNotExtended\n           * 511 - HTTPNetworkAuthenticationRequired\n\nAll HTTP exceptions have the same constructor signature::\n\n    HTTPNotFound(*, headers=None, reason=None,\n                 text=None, content_type=None)\n\nIf not directly specified, *headers* will be added to the *default\nresponse headers*.\n\nClasses :exc:`HTTPMultipleChoices`, :exc:`HTTPMovedPermanently`,\n:exc:`HTTPFound`, :exc:`HTTPSeeOther`, :exc:`HTTPUseProxy`,\n:exc:`HTTPTemporaryRedirect` have the following constructor signature::\n\n    HTTPFound(location, *,headers=None, reason=None,\n              text=None, content_type=None)\n\nwhere *location* is value for *Location HTTP header*.\n\n:exc:`HTTPMethodNotAllowed` is constructed by providing the incoming\nunsupported method and list of allowed methods::\n\n    HTTPMethodNotAllowed(method, allowed_methods, *,\n                         headers=None, reason=None,\n                         text=None, content_type=None)\n\n:exc:`HTTPUnavailableForLegalReasons` should be constructed with a ``link``\nto yourself (as the entity implementing the blockage), and an explanation for\nthe block included in ``text``.::\n\n    HTTPUnavailableForLegalReasons(link, *,\n                                   headers=None, reason=None,\n                                   text=None, content_type=None)\n\nBase HTTP Exception\n-------------------\n\n.. exception:: HTTPException(*, headers=None, reason=None, text=None, \\\n                             content_type=None)\n   :canonical: aiohttp.web_exceptions.HTTPException\n\n   The base class for HTTP server exceptions. Inherited from :exc:`Exception`.\n\n   :param headers: HTTP headers (:class:`~collections.abc.Mapping`)\n\n   :param str reason: an optional custom HTTP reason. aiohttp uses *default reason\n                      string* if not specified.\n\n   :param str text: an optional text used in response body. If not specified *default\n                    text* is constructed from status code and reason, e.g. `\"404: Not\n                    Found\"`.\n\n   :param str content_type: an optional Content-Type, `\"text/plain\"` by default.\n\n   .. attribute:: status\n\n      HTTP status code for the exception, :class:`int`\n\n   .. attribute:: reason\n\n      HTTP status reason for the exception, :class:`str`\n\n   .. attribute:: text\n\n      HTTP status reason for the exception, :class:`str` or ``None``\n      for HTTP exceptions without body, e.g. \"204 No Content\"\n\n   .. attribute:: headers\n\n      HTTP headers for the exception, :class:`multidict.CIMultiDict`\n\n   .. attribute:: cookies\n\n      An instance of :class:`http.cookies.SimpleCookie` for *outgoing* cookies.\n\n      .. versionadded:: 4.0\n\n   .. method:: set_cookie(name, value, *, path='/', expires=None, \\\n                          domain=None, max_age=None, \\\n                          secure=None, httponly=None, version=None, \\\n                          samesite=None)\n\n      Convenient way for setting :attr:`cookies`, allows to specify\n      some additional properties like *max_age* in a single call.\n\n      .. versionadded:: 4.0\n\n      :param str name: cookie name\n\n      :param str value: cookie value (will be converted to\n                        :class:`str` if value has another type).\n\n      :param expires: expiration date (optional)\n\n      :param str domain: cookie domain (optional)\n\n      :param int max_age: defines the lifetime of the cookie, in\n                          seconds.  The delta-seconds value is a\n                          decimal non- negative integer.  After\n                          delta-seconds seconds elapse, the client\n                          should discard the cookie.  A value of zero\n                          means the cookie should be discarded\n                          immediately.  (optional)\n\n      :param str path: specifies the subset of URLs to\n                       which this cookie applies. (optional, ``'/'`` by default)\n\n      :param bool secure: attribute (with no value) directs\n                          the user agent to use only (unspecified)\n                          secure means to contact the origin server\n                          whenever it sends back this cookie.\n                          The user agent (possibly under the user's\n                          control) may determine what level of\n                          security it considers appropriate for\n                          \"secure\" cookies.  The *secure* should be\n                          considered security advice from the server\n                          to the user agent, indicating that it is in\n                          the session's interest to protect the cookie\n                          contents. (optional)\n\n      :param bool httponly: ``True`` if the cookie HTTP only (optional)\n\n      :param int version: a decimal integer, identifies to which\n                          version of the state management\n                          specification the cookie\n                          conforms. (Optional, *version=1* by default)\n\n      :param str samesite: Asserts that a cookie must not be sent with\n         cross-origin requests, providing some protection\n         against cross-site request forgery attacks.\n         Generally the value should be one of: ``None``,\n         ``Lax`` or ``Strict``. (optional)\n\n      .. warning::\n\n         In HTTP version 1.1, ``expires`` was deprecated and replaced with\n         the easier-to-use ``max-age``, but Internet Explorer (IE6, IE7,\n         and IE8) **does not** support ``max-age``.\n\n   .. method:: del_cookie(name, *, path='/', domain=None)\n\n      Deletes cookie.\n\n      .. versionadded:: 4.0\n\n      :param str name: cookie name\n\n      :param str domain: optional cookie domain\n\n      :param str path: optional cookie path, ``'/'`` by default\n\n\nSuccessful Exceptions\n---------------------\n\nHTTP exceptions for status code in range 200-299. They are not *errors* but special\nclasses reflected in exceptions hierarchy. E.g. ``raise web.HTTPNoContent`` may look\nstrange a little but the construction is absolutely legal.\n\n.. exception:: HTTPSuccessful\n   :canonical: aiohttp.web_exceptions.HTTPSuccessful\n\n   A base class for the category, a subclass of :exc:`HTTPException`.\n\n.. exception:: HTTPOk\n   :canonical: aiohttp.web_exceptions.HTTPOk\n\n   An exception for *200 OK*, a subclass of :exc:`HTTPSuccessful`.\n\n.. exception:: HTTPCreated\n   :canonical: aiohttp.web_exceptions.HTTPCreated\n\n   An exception for *201 Created*, a subclass of :exc:`HTTPSuccessful`.\n\n.. exception:: HTTPAccepted\n   :canonical: aiohttp.web_exceptions.HTTPAccepted\n\n   An exception for *202 Accepted*, a subclass of :exc:`HTTPSuccessful`.\n\n.. exception:: HTTPNonAuthoritativeInformation\n   :canonical: aiohttp.web_exceptions.HTTPNonAuthoritativeInformation\n\n   An exception for *203 Non-Authoritative Information*, a subclass of\n   :exc:`HTTPSuccessful`.\n\n.. exception:: HTTPNoContent\n   :canonical: aiohttp.web_exceptions.HTTPNoContent\n\n   An exception for *204 No Content*, a subclass of :exc:`HTTPSuccessful`.\n\n   Has no HTTP body.\n\n.. exception:: HTTPResetContent\n   :canonical: aiohttp.web_exceptions.HTTPResetContent\n\n   An exception for *205 Reset Content*, a subclass of :exc:`HTTPSuccessful`.\n\n   Has no HTTP body.\n\n.. exception:: HTTPPartialContent\n   :canonical: aiohttp.web_exceptions.HTTPPartialContent\n\n   An exception for *206 Partial Content*, a subclass of :exc:`HTTPSuccessful`.\n\nRedirections\n------------\n\nHTTP exceptions for status code in range 300-399, e.g. ``raise\nweb.HTTPMovedPermanently(location='/new/path')``.\n\n.. exception:: HTTPRedirection\n   :canonical: aiohttp.web_exceptions.HTTPRedirection\n\n   A base class for the category, a subclass of :exc:`HTTPException`.\n\n.. exception:: HTTPMove(location, *, headers=None, reason=None, text=None, \\\n                        content_type=None)\n   :canonical: aiohttp.web_exceptions.HTTPMove\n\n   A base class for redirections with implied *Location* header,\n   all redirections except :exc:`HTTPNotModified`.\n\n   :param location: a :class:`yarl.URL` or :class:`str` used for *Location* HTTP\n                    header.\n\n   For other arguments see :exc:`HTTPException` constructor.\n\n   .. attribute:: location\n\n      A *Location* HTTP header value, :class:`yarl.URL`.\n\n.. exception:: HTTPMultipleChoices\n   :canonical: aiohttp.web_exceptions.HTTPMultipleChoices\n\n   An exception for *300 Multiple Choices*, a subclass of :exc:`HTTPMove`.\n\n.. exception:: HTTPMovedPermanently\n   :canonical: aiohttp.web_exceptions.HTTPMovedPermanently\n\n   An exception for *301 Moved Permanently*, a subclass of :exc:`HTTPMove`.\n\n.. exception:: HTTPFound\n   :canonical: aiohttp.web_exceptions.HTTPFound\n\n   An exception for *302 Found*, a subclass of :exc:`HTTPMove`.\n\n.. exception:: HTTPSeeOther\n   :canonical: aiohttp.web_exceptions.HTTPSeeOther\n\n   An exception for *303 See Other*, a subclass of :exc:`HTTPMove`.\n\n.. exception:: HTTPNotModified\n   :canonical: aiohttp.web_exceptions.HTTPNotModified\n\n   An exception for *304 Not Modified*, a subclass of :exc:`HTTPRedirection`.\n\n   Has no HTTP body.\n\n.. exception:: HTTPUseProxy\n   :canonical: aiohttp.web_exceptions.HTTPUseProxy\n\n   An exception for *305 Use Proxy*, a subclass of :exc:`HTTPMove`.\n\n.. exception:: HTTPTemporaryRedirect\n   :canonical: aiohttp.web_exceptions.HTTPTemporaryRedirect\n\n   An exception for *307 Temporary Redirect*, a subclass of :exc:`HTTPMove`.\n\n.. exception:: HTTPPermanentRedirect\n   :canonical: aiohttp.web_exceptions.HTTPPermanentRedirect\n\n   An exception for *308 Permanent Redirect*, a subclass of :exc:`HTTPMove`.\n\n\nClient Errors\n-------------\n\nHTTP exceptions for status code in range 400-499, e.g. ``raise web.HTTPNotFound()``.\n\n.. exception:: HTTPClientError\n   :canonical: aiohttp.web_exceptions.HTTPClientError\n\n   A base class for the category, a subclass of :exc:`HTTPException`.\n\n.. exception:: HTTPBadRequest\n   :canonical: aiohttp.web_exceptions.HTTPBadRequest\n\n   An exception for *400 Bad Request*, a subclass of :exc:`HTTPClientError`.\n\n.. exception:: HTTPUnauthorized\n   :canonical: aiohttp.web_exceptions.HTTPUnauthorized\n\n   An exception for *401 Unauthorized*, a subclass of :exc:`HTTPClientError`.\n\n.. exception:: HTTPPaymentRequired\n   :canonical: aiohttp.web_exceptions.HTTPPaymentRequired\n\n   An exception for *402 Payment Required*, a subclass of\n   :exc:`HTTPClientError`.\n\n.. exception:: HTTPForbidden\n   :canonical: aiohttp.web_exceptions.HTTPForbidden\n\n   An exception for *403 Forbidden*, a subclass of :exc:`HTTPClientError`.\n\n.. exception:: HTTPNotFound\n   :canonical: aiohttp.web_exceptions.HTTPNotFound\n\n   An exception for *404 Not Found*, a subclass of :exc:`HTTPClientError`.\n\n.. exception:: HTTPMethodNotAllowed(method, allowed_methods, *, \\\n                                    headers=None, reason=None, text=None, \\\n                                    content_type=None)\n   :canonical: aiohttp.web_exceptions.HTTPMethodNotAllowed\n\n   An exception for *405 Method Not Allowed*, a subclass of\n   :exc:`HTTPClientError`.\n\n   :param str method: requested but not allowed HTTP method.\n\n   :param allowed_methods: an iterable of allowed HTTP methods (:class:`str`),\n                           *Allow* HTTP header is constructed from\n                           the sequence separated by comma.\n\n   For other arguments see :exc:`HTTPException` constructor.\n\n   .. attribute:: allowed_methods\n\n      A set of allowed HTTP methods.\n\n   .. attribute:: method\n\n      Requested but not allowed HTTP method.\n\n.. exception:: HTTPNotAcceptable\n   :canonical: aiohttp.web_exceptions.HTTPNotAcceptable\n\n   An exception for *406 Not Acceptable*, a subclass of :exc:`HTTPClientError`.\n\n.. exception:: HTTPProxyAuthenticationRequired\n   :canonical: aiohttp.web_exceptions.HTTPProxyAuthenticationRequired\n\n   An exception for *407 Proxy Authentication Required*, a subclass of\n   :exc:`HTTPClientError`.\n\n.. exception:: HTTPRequestTimeout\n   :canonical: aiohttp.web_exceptions.HTTPRequestTimeout\n\n   An exception for *408 Request Timeout*, a subclass of :exc:`HTTPClientError`.\n\n.. exception:: HTTPConflict\n   :canonical: aiohttp.web_exceptions.HTTPConflict\n\n   An exception for *409 Conflict*, a subclass of :exc:`HTTPClientError`.\n\n.. exception:: HTTPGone\n   :canonical: aiohttp.web_exceptions.HTTPGone\n\n   An exception for *410 Gone*, a subclass of :exc:`HTTPClientError`.\n\n.. exception:: HTTPLengthRequired\n   :canonical: aiohttp.web_exceptions.HTTPLengthRequired\n\n   An exception for *411 Length Required*, a subclass of :exc:`HTTPClientError`.\n\n.. exception:: HTTPPreconditionFailed\n   :canonical: aiohttp.web_exceptions.HTTPPreconditionFailed\n\n   An exception for *412 Precondition Failed*, a subclass of\n   :exc:`HTTPClientError`.\n\n.. exception:: HTTPRequestEntityTooLarge(max_size, actual_size, **kwargs)\n   :canonical: aiohttp.web_exceptions.HTTPRequestEntityTooLarge\n\n   An exception for *413 Entity Too Large*, a subclass of :exc:`HTTPClientError`.\n\n   :param int max_size: Maximum allowed request body size\n\n   :param int actual_size: Actual received size\n\n   For other acceptable parameters see :exc:`HTTPException` constructor.\n\n.. exception:: HTTPRequestURITooLong\n   :canonical: aiohttp.web_exceptions.HTTPRequestURITooLong\n\n   An exception for *414 URI is too long*, a subclass of :exc:`HTTPClientError`.\n\n.. exception:: HTTPUnsupportedMediaType\n   :canonical: aiohttp.web_exceptions.HTTPUnsupportedMediaType\n\n   An exception for *415 Entity body in unsupported format*, a subclass of\n   :exc:`HTTPClientError`.\n\n.. exception:: HTTPRequestRangeNotSatisfiable\n   :canonical: aiohttp.web_exceptions.HTTPRequestRangeNotSatisfiable\n\n   An exception for *416 Cannot satisfy request range*, a subclass of\n   :exc:`HTTPClientError`.\n\n.. exception:: HTTPExpectationFailed\n   :canonical: aiohttp.web_exceptions.HTTPExpectationFailed\n\n   An exception for *417 Expect condition could not be satisfied*, a subclass of\n   :exc:`HTTPClientError`.\n\n.. exception:: HTTPMisdirectedRequest\n   :canonical: aiohttp.web_exceptions.HTTPMisdirectedRequest\n\n   An exception for *421 Misdirected Request*, a subclass of :exc:`HTTPClientError`.\n\n.. exception:: HTTPUnprocessableEntity\n   :canonical: aiohttp.web_exceptions.HTTPUnprocessableEntity\n\n   An exception for *422 Unprocessable Entity*, a subclass of :exc:`HTTPClientError`.\n\n.. exception:: HTTPFailedDependency\n   :canonical: aiohttp.web_exceptions.HTTPFailedDependency\n\n   An exception for *424 Failed Dependency*, a subclass of :exc:`HTTPClientError`.\n\n.. exception:: HTTPUpgradeRequired\n   :canonical: aiohttp.web_exceptions.HTTPUpgradeRequired\n\n   An exception for *426 Upgrade Required*, a subclass of :exc:`HTTPClientError`.\n\n.. exception:: HTTPPreconditionRequired\n   :canonical: aiohttp.web_exceptions.HTTPPreconditionRequired\n\n   An exception for *428 Precondition Required*, a subclass of :exc:`HTTPClientError`.\n\n.. exception:: HTTPTooManyRequests\n   :canonical: aiohttp.web_exceptions.HTTPTooManyRequests\n\n   An exception for *429 Too Many Requests*, a subclass of :exc:`HTTPClientError`.\n\n.. exception:: HTTPRequestHeaderFieldsTooLarge\n   :canonical: aiohttp.web_exceptions.HTTPRequestHeaderFieldsTooLarge\n\n   An exception for *431 Requests Header Fields Too Large*, a subclass of\n   :exc:`HTTPClientError`.\n\n.. exception:: HTTPUnavailableForLegalReasons(link, *, \\\n                                              headers=None, \\\n                                              reason=None, \\\n                                              text=None, \\\n                                              content_type=None)\n   :canonical: aiohttp.web_exceptions.HTTPUnavailableForLegalReasons\n\n\n   An exception for *451 Unavailable For Legal Reasons*, a subclass of\n   :exc:`HTTPClientError`.\n\n   :param link: A link to yourself (as the entity implementing the blockage),\n                :class:`str`, :class:`~yarl.URL` or ``None``.\n\n   For other parameters see :exc:`HTTPException` constructor.\n   A reason for the block should be included in ``text``.\n\n   .. attribute:: link\n\n      A :class:`~yarl.URL` link to the entity implementing the blockage or ``None``,\n      read-only property.\n\n\nServer Errors\n-------------\n\nHTTP exceptions for status code in range 500-599, e.g. ``raise web.HTTPBadGateway()``.\n\n\n.. exception:: HTTPServerError\n   :canonical: aiohttp.web_exceptions.HTTPServerError\n\n   A base class for the category, a subclass of :exc:`HTTPException`.\n\n.. exception:: HTTPInternalServerError\n   :canonical: aiohttp.web_exceptions.HTTPInternalServerError\n\n   An exception for *500 Server got itself in trouble*, a subclass of\n   :exc:`HTTPServerError`.\n\n.. exception:: HTTPNotImplemented\n   :canonical: aiohttp.web_exceptions.HTTPNotImplemented\n\n   An exception for *501 Server does not support this operation*, a subclass of\n   :exc:`HTTPServerError`.\n\n.. exception:: HTTPBadGateway\n   :canonical: aiohttp.web_exceptions.HTTPBadGateway\n\n   An exception for *502 Invalid responses from another server/proxy*, a\n   subclass of :exc:`HTTPServerError`.\n\n.. exception:: HTTPServiceUnavailable\n   :canonical: aiohttp.web_exceptions.HTTPServiceUnavailable\n\n   An exception for *503 The server cannot process the request due to a high\n   load*, a subclass of :exc:`HTTPServerError`.\n\n.. exception:: HTTPGatewayTimeout\n   :canonical: aiohttp.web_exceptions.HTTPGatewayTimeout\n\n   An exception for *504 The gateway server did not receive a timely response*,\n   a subclass of :exc:`HTTPServerError`.\n\n.. exception:: HTTPVersionNotSupported\n   :canonical: aiohttp.web_exceptions.HTTPVersionNotSupported\n\n   An exception for *505 Cannot fulfill request*, a subclass of :exc:`HTTPServerError`.\n\n.. exception:: HTTPVariantAlsoNegotiates\n   :canonical: aiohttp.web_exceptions.HTTPVariantAlsoNegotiates\n\n   An exception for *506 Variant Also Negotiates*, a subclass of :exc:`HTTPServerError`.\n\n.. exception:: HTTPInsufficientStorage\n   :canonical: aiohttp.web_exceptions.HTTPInsufficientStorage\n\n   An exception for *507 Insufficient Storage*, a subclass of :exc:`HTTPServerError`.\n\n.. exception:: HTTPNotExtended\n   :canonical: aiohttp.web_exceptions.HTTPNotExtended\n\n   An exception for *510 Not Extended*, a subclass of :exc:`HTTPServerError`.\n\n.. exception:: HTTPNetworkAuthenticationRequired\n   :canonical: aiohttp.web_exceptions.HTTPNetworkAuthenticationRequired\n\n   An exception for *511 Network Authentication Required*, a subclass of\n   :exc:`HTTPServerError`.\n"
  },
  {
    "path": "docs/web_lowlevel.rst",
    "content": ".. currentmodule:: aiohttp.web\n\n.. _aiohttp-web-lowlevel:\n\nLow Level Server\n================\n\n\nThis topic describes :mod:`aiohttp.web` based *low level* API.\n\nAbstract\n--------\n\nSometimes users don't need high-level concepts introduced in\n:ref:`aiohttp-web`: applications, routers, middlewares and signals.\n\nAll that may be needed is supporting an asynchronous callable which accepts a\nrequest and returns a response object.\n\nThis is done by introducing :class:`aiohttp.web.Server` class which\nserves a *protocol factory* role for\n:meth:`asyncio.loop.create_server` and bridges data\nstream to *web handler* and sends result back.\n\n\nLow level *web handler* should accept the single :class:`BaseRequest`\nparameter and performs one of the following actions:\n\n  1. Return a :class:`Response` with the whole HTTP body stored in memory.\n\n  2. Create a :class:`StreamResponse`, send headers by\n     :meth:`StreamResponse.prepare` call, send data chunks by\n     :meth:`StreamResponse.write` and return finished response.\n\n  3. Raise :class:`HTTPException` derived exception (see\n     :ref:`aiohttp-web-exceptions` section).\n\n     All other exceptions not derived from :class:`HTTPException`\n     leads to *500 Internal Server Error* response.\n\n  4. Initiate and process Web-Socket connection by\n     :class:`WebSocketResponse` using (see :ref:`aiohttp-web-websockets`).\n\n\nRun a Basic Low-Level Server\n----------------------------\n\nThe following code demonstrates very trivial usage example::\n\n   import asyncio\n   from aiohttp import web\n\n\n   async def handler(request):\n       return web.Response(text=\"OK\")\n\n\n   async def main():\n       server = web.Server(handler)\n       runner = web.ServerRunner(server)\n       await runner.setup()\n       site = web.TCPSite(runner, 'localhost', 8080)\n       await site.start()\n\n       print(\"======= Serving on http://127.0.0.1:8080/ ======\")\n\n       # pause here for very long time by serving HTTP requests and\n       # waiting for keyboard interruption\n       await asyncio.sleep(100*3600)\n\n\n   asyncio.run(main())\n\n\nIn the snippet we have ``handler`` which returns a regular\n:class:`Response` with ``\"OK\"`` in BODY.\n\nThis *handler* is processed by ``server`` (:class:`Server` which acts\nas *protocol factory*).  Network communication is created by\n:ref:`runners API <aiohttp-web-app-runners-reference>` to serve\n``http://127.0.0.1:8080/``.\n\nThe handler should process every request for every *path*, e.g.\n``GET``, ``POST``, Web-Socket.\n\nThe example is very basic: it always return ``200 OK`` response, real\nlife code is much more complex usually.\n"
  },
  {
    "path": "docs/web_quickstart.rst",
    "content": ".. currentmodule:: aiohttp.web\n\n.. _aiohttp-web-quickstart:\n\nWeb Server Quickstart\n=====================\n\nRun a Simple Web Server\n-----------------------\n\nIn order to implement a web server, first create a\n:ref:`request handler <aiohttp-web-handler>`.\n\nA request handler must be a :ref:`coroutine <coroutine>` that\naccepts a :class:`Request` instance as its only parameter and returns a\n:class:`Response` instance::\n\n   from aiohttp import web\n\n   async def hello(request):\n       return web.Response(text=\"Hello, world\")\n\nNext, create an :class:`Application` instance and register the\nrequest handler on a particular *HTTP method* and *path*::\n\n   app = web.Application()\n   app.add_routes([web.get('/', hello)])\n\nAfter that, run the application by :func:`run_app` call::\n\n   web.run_app(app)\n\nThat's it. Now, head over to ``http://localhost:8080/`` to see the results.\n\nAlternatively if you prefer *route decorators* create a *route table*\nand register a :term:`web-handler`::\n\n   routes = web.RouteTableDef()\n\n   @routes.get('/')\n   async def hello(request):\n       return web.Response(text=\"Hello, world\")\n\n   app = web.Application()\n   app.add_routes(routes)\n   web.run_app(app)\n\nBoth ways essentially do the same work, the difference is only in your\ntaste: do you prefer *Django style* with famous ``urls.py`` or *Flask*\nwith shiny route decorators.\n\n*aiohttp* server documentation uses both ways in code snippets to\nemphasize their equality, switching from one style to another is very\ntrivial.\n\n.. note::\n   You can get a powerful aiohttp template by running one command.\n   To do this, simply use our `boilerplate for quick start with aiohttp\n   <https://create-aio-app.readthedocs.io/pages/aiohttp_quick_start.html>`_.\n\n\n.. seealso::\n\n   :ref:`aiohttp-web-graceful-shutdown` section explains what :func:`run_app`\n   does and how to implement complex server initialization/finalization\n   from scratch.\n\n   :ref:`aiohttp-web-app-runners` for more handling more complex cases\n   like *asynchronous* web application serving and multiple hosts\n   support.\n\n.. _aiohttp-web-cli:\n\nCommand Line Interface (CLI)\n----------------------------\n:mod:`aiohttp.web` implements a basic CLI for quickly serving an\n:class:`Application` in *development* over TCP/IP:\n\n.. code-block:: shell\n\n    $ python -m aiohttp.web -H localhost -P 8080 package.module:init_func\n\n``package.module:init_func`` should be an importable :term:`callable` that\naccepts a list of any non-parsed command-line arguments and returns an\n:class:`Application` instance after setting it up::\n\n    def init_func(argv):\n        app = web.Application()\n        app.router.add_get(\"/\", index_handler)\n        return app\n\n\n.. note::\n   For local development we typically recommend using\n   `aiohttp-devtools <https://github.com/aio-libs/aiohttp-devtools>`_.\n\n.. _aiohttp-web-handler:\n\nHandler\n-------\n\nA request handler must be a :ref:`coroutine<coroutine>` that accepts a\n:class:`Request` instance as its only argument and returns a\n:class:`StreamResponse` derived (e.g. :class:`Response`) instance::\n\n   async def handler(request):\n       return web.Response()\n\nHandlers are setup to handle requests by registering them with the\n:meth:`Application.add_routes` on a particular route (*HTTP method* and\n*path* pair) using helpers like :func:`get` and\n:func:`post`::\n\n   app.add_routes([web.get('/', handler),\n                   web.post('/post', post_handler),\n                   web.put('/put', put_handler)])\n\nOr use *route decorators*::\n\n    routes = web.RouteTableDef()\n\n    @routes.get('/')\n    async def get_handler(request):\n        ...\n\n    @routes.post('/post')\n    async def post_handler(request):\n        ...\n\n    @routes.put('/put')\n    async def put_handler(request):\n        ...\n\n    app.add_routes(routes)\n\n\nWildcard *HTTP method* is also supported by :func:`route` or\n:meth:`RouteTableDef.route`, allowing a handler to serve incoming\nrequests on a *path* having **any** *HTTP method*::\n\n  app.add_routes([web.route('*', '/path', all_handler)])\n\nThe *HTTP method* can be queried later in the request handler using the\n:attr:`aiohttp.web.BaseRequest.method` property.\n\nBy default endpoints added with ``GET`` method will accept\n``HEAD`` requests and return the same response headers as they would\nfor a ``GET`` request. You can also deny ``HEAD`` requests on a route::\n\n   web.get('/', handler, allow_head=False)\n\nHere ``handler`` won't be called on ``HEAD`` request and the server\nwill respond with ``405: Method Not Allowed``.\n\n.. seealso::\n\n   :ref:`aiohttp-web-peer-disconnection` section explains how handlers\n   behave when a client connection drops and ways to optimize handling\n   of this.\n\n.. _aiohttp-web-resource-and-route:\n\nResources and Routes\n--------------------\n\nInternally routes are served by :attr:`Application.router`\n(:class:`UrlDispatcher` instance).\n\nThe *router* is a list of *resources*.\n\nResource is an entry in *route table* which corresponds to requested URL.\n\nResource in turn has at least one *route*.\n\nRoute corresponds to handling *HTTP method* by calling *web handler*.\n\nThus when you add a *route* the *resource* object is created under the hood.\n\nThe library implementation **merges** all subsequent route additions\nfor the same path adding the only resource for all HTTP methods.\n\nConsider two examples::\n\n   app.add_routes([web.get('/path1', get_1),\n                   web.post('/path1', post_1),\n                   web.get('/path2', get_2),\n                   web.post('/path2', post_2)]\n\nand::\n\n   app.add_routes([web.get('/path1', get_1),\n                   web.get('/path2', get_2),\n                   web.post('/path2', post_2),\n                   web.post('/path1', post_1)]\n\nFirst one is *optimized*. You have got the idea.\n\n.. _aiohttp-web-variable-handler:\n\nVariable Resources\n^^^^^^^^^^^^^^^^^^\n\nResource may have *variable path* also. For instance, a resource with\nthe path ``'/a/{name}/c'`` would match all incoming requests with\npaths such as ``'/a/b/c'``, ``'/a/1/c'``, and ``'/a/etc/c'``.\n\nA variable *part* is specified in the form ``{identifier}``, where the\n``identifier`` can be used later in a\n:ref:`request handler <aiohttp-web-handler>` to access the matched value for\nthat *part*. This is done by looking up the ``identifier`` in the\n:attr:`Request.match_info` mapping::\n\n   @routes.get('/{name}')\n   async def variable_handler(request):\n       return web.Response(\n           text=\"Hello, {}\".format(request.match_info['name']))\n\nBy default, each *part* matches the regular expression ``[^{}/]+``.\n\nYou can also specify a custom regex in the form ``{identifier:regex}``::\n\n   web.get(r'/{name:\\d+}', handler)\n\n\n.. _aiohttp-web-named-routes:\n\nReverse URL Constructing using Named Resources\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nRoutes can also be given a *name*::\n\n   @routes.get('/root', name='root')\n   async def handler(request):\n       ...\n\nWhich can then be used to access and build a *URL* for that resource later (e.g.\nin a :ref:`request handler <aiohttp-web-handler>`)::\n\n   url = request.app.router['root'].url_for().with_query({\"a\": \"b\", \"c\": \"d\"})\n   assert url == URL('/root?a=b&c=d')\n\nA more interesting example is building *URLs* for :ref:`variable\nresources <aiohttp-web-variable-handler>`::\n\n   app.router.add_resource(r'/{user}/info', name='user-info')\n\n\nIn this case you can also pass in the *parts* of the route::\n\n   url = request.app.router['user-info'].url_for(user='john_doe')\n   url_with_qs = url.with_query(\"a=b\")\n   assert url_with_qs == '/john_doe/info?a=b'\n\n\nOrganizing Handlers in Classes\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAs discussed above, :ref:`handlers <aiohttp-web-handler>` can be first-class\ncoroutines::\n\n   async def hello(request):\n       return web.Response(text=\"Hello, world\")\n\n   app.router.add_get('/', hello)\n\nBut sometimes it's convenient to group logically similar handlers into a Python\n*class*.\n\nSince :mod:`aiohttp.web` does not dictate any implementation details,\napplication developers can organize handlers in classes if they so wish::\n\n   class Handler:\n\n       def __init__(self):\n           pass\n\n       async def handle_intro(self, request):\n           return web.Response(text=\"Hello, world\")\n\n       async def handle_greeting(self, request):\n           name = request.match_info.get('name', \"Anonymous\")\n           txt = \"Hello, {}\".format(name)\n           return web.Response(text=txt)\n\n   handler = Handler()\n   app.add_routes([web.get('/intro', handler.handle_intro),\n                   web.get('/greet/{name}', handler.handle_greeting)])\n\n\n.. _aiohttp-web-class-based-views:\n\nClass Based Views\n^^^^^^^^^^^^^^^^^\n\n:mod:`aiohttp.web` has support for *class based views*.\n\nYou can derive from :class:`View` and define methods for handling http\nrequests::\n\n   class MyView(web.View):\n       async def get(self):\n           return await get_resp(self.request)\n\n       async def post(self):\n           return await post_resp(self.request)\n\nHandlers should be coroutines accepting *self* only and returning\nresponse object as regular :term:`web-handler`. Request object can be\nretrieved by :attr:`View.request` property.\n\nAfter implementing the view (``MyView`` from example above) should be\nregistered in application's router::\n\n   app.add_routes([web.view('/path/to', MyView)])\n\nor::\n\n   @routes.view('/path/to')\n   class MyView(web.View):\n       ...\n\nor::\n\n   app.router.add_route('*', '/path/to', MyView)\n\nExample will process GET and POST requests for */path/to* but raise\n*405 Method not allowed* exception for unimplemented HTTP methods.\n\nResource Views\n^^^^^^^^^^^^^^\n\n*All* registered resources in a router can be viewed using the\n:meth:`UrlDispatcher.resources` method::\n\n   for resource in app.router.resources():\n       print(resource)\n\nA *subset* of the resources that were registered with a *name* can be\nviewed using the :meth:`UrlDispatcher.named_resources` method::\n\n   for name, resource in app.router.named_resources().items():\n       print(name, resource)\n\n\n.. _aiohttp-web-alternative-routes-definition:\n\nAlternative ways for registering routes\n---------------------------------------\n\nCode examples shown above use *imperative* style for adding new\nroutes: they call ``app.router.add_get(...)`` etc.\n\nThere are two alternatives: route tables and route decorators.\n\nRoute tables look like Django way::\n\n   async def handle_get(request):\n       ...\n\n\n   async def handle_post(request):\n       ...\n\n   app.router.add_routes([web.get('/get', handle_get),\n                          web.post('/post', handle_post),\n\n\nThe snippet calls :meth:`~aiohttp.web.UrlDispatcher.add_routes` to\nregister a list of *route definitions* (:class:`aiohttp.web.RouteDef`\ninstances) created by :func:`aiohttp.web.get` or\n:func:`aiohttp.web.post` functions.\n\n.. seealso:: :ref:`aiohttp-web-route-def` reference.\n\nRoute decorators are closer to Flask approach::\n\n   routes = web.RouteTableDef()\n\n   @routes.get('/get')\n   async def handle_get(request):\n       ...\n\n\n   @routes.post('/post')\n   async def handle_post(request):\n       ...\n\n   app.router.add_routes(routes)\n\nIt is also possible to use decorators with class-based views::\n\n   routes = web.RouteTableDef()\n\n   @routes.view(\"/view\")\n   class MyView(web.View):\n       async def get(self):\n           ...\n\n       async def post(self):\n           ...\n\n   app.router.add_routes(routes)\n\nThe example creates a :class:`aiohttp.web.RouteTableDef` container first.\n\nThe container is a list-like object with additional decorators\n:meth:`aiohttp.web.RouteTableDef.get`,\n:meth:`aiohttp.web.RouteTableDef.post` etc. for registering new\nroutes.\n\nAfter filling the container\n:meth:`~aiohttp.web.UrlDispatcher.add_routes` is used for adding\nregistered *route definitions* into application's router.\n\n.. seealso:: :ref:`aiohttp-web-route-table-def` reference.\n\nAll tree ways (imperative calls, route tables and decorators) are\nequivalent, you could use what do you prefer or even mix them on your\nown.\n\n.. versionadded:: 2.3\n\n\nJSON Response\n-------------\n\nIt is a common case to return JSON data in response, :mod:`aiohttp.web`\nprovides a shortcut for returning JSON -- :func:`aiohttp.web.json_response`::\n\n   async def handler(request):\n       data = {'some': 'data'}\n       return web.json_response(data)\n\nThe shortcut method returns :class:`aiohttp.web.Response` instance\nso you can for example set cookies before returning it from handler.\n\n\nUser Sessions\n-------------\n\nOften you need a container for storing user data across requests. The concept\nis usually called a *session*.\n\n:mod:`aiohttp.web` has no built-in concept of a *session*, however, there is a\nthird-party library, :mod:`aiohttp_session`, that adds *session* support::\n\n    import asyncio\n    import time\n    import base64\n    from cryptography import fernet\n    from aiohttp import web\n    from aiohttp_session import setup, get_session, session_middleware\n    from aiohttp_session.cookie_storage import EncryptedCookieStorage\n\n    async def handler(request):\n        session = await get_session(request)\n\n        last_visit = session.get(\"last_visit\")\n        session[\"last_visit\"] = time.time()\n        text = \"Last visited: {}\".format(last_visit)\n\n        return web.Response(text=text)\n\n    async def make_app():\n        app = web.Application()\n        # secret_key must be 32 url-safe base64-encoded bytes\n        fernet_key = fernet.Fernet.generate_key()\n        secret_key = base64.urlsafe_b64decode(fernet_key)\n        setup(app, EncryptedCookieStorage(secret_key))\n        app.add_routes([web.get('/', handler)])\n        return app\n\n    web.run_app(make_app())\n\n\n.. _aiohttp-web-forms:\n\nHTTP Forms\n----------\n\nHTTP Forms are supported out of the box.\n\nIf form's method is ``\"GET\"`` (``<form method=\"get\">``) use\n:attr:`aiohttp.web.BaseRequest.query` for getting form data.\n\nTo access form data with ``\"POST\"`` method use\n:meth:`aiohttp.web.BaseRequest.post` or :meth:`aiohttp.web.BaseRequest.multipart`.\n\n:meth:`aiohttp.web.BaseRequest.post` accepts both\n``'application/x-www-form-urlencoded'`` and ``'multipart/form-data'``\nform's data encoding (e.g. ``<form enctype=\"multipart/form-data\">``).\nIt stores files data in temporary directory. If `client_max_size` is\nspecified `post` raises `ValueError` exception.\nFor efficiency use :meth:`aiohttp.web.BaseRequest.multipart`, It is especially effective\nfor uploading large files (:ref:`aiohttp-web-file-upload`).\n\nValues submitted by the following form:\n\n.. code-block:: html\n\n   <form action=\"/login\" method=\"post\" accept-charset=\"utf-8\"\n         enctype=\"application/x-www-form-urlencoded\">\n\n       <label for=\"login\">Login</label>\n       <input id=\"login\" name=\"login\" type=\"text\" value=\"\" autofocus/>\n       <label for=\"password\">Password</label>\n       <input id=\"password\" name=\"password\" type=\"password\" value=\"\"/>\n\n       <input type=\"submit\" value=\"login\"/>\n   </form>\n\ncould be accessed as::\n\n    async def do_login(request):\n        data = await request.post()\n        login = data['login']\n        password = data['password']\n\n\n.. _aiohttp-web-file-upload:\n\nFile Uploads\n------------\n\n:mod:`aiohttp.web` has built-in support for handling files uploaded from the\nbrowser.\n\nFirst, make sure that the HTML ``<form>`` element has its *enctype* attribute\nset to ``enctype=\"multipart/form-data\"``. As an example, here is a form that\naccepts an MP3 file:\n\n.. code-block:: html\n\n   <form action=\"/store/mp3\" method=\"post\" accept-charset=\"utf-8\"\n         enctype=\"multipart/form-data\">\n\n       <label for=\"mp3\">Mp3</label>\n       <input id=\"mp3\" name=\"mp3\" type=\"file\" value=\"\"/>\n\n       <input type=\"submit\" value=\"submit\"/>\n   </form>\n\nThen, in the :ref:`request handler <aiohttp-web-handler>` you can access the\nfile input field as a :class:`FileField` instance. :class:`FileField` is simply\na container for the file as well as some of its metadata::\n\n    async def store_mp3_handler(request):\n\n        # WARNING: don't do that if you plan to receive large files!\n        data = await request.post()\n\n        mp3 = data['mp3']\n\n        # .filename contains the name of the file in string format.\n        filename = mp3.filename\n\n        # .file contains the actual file data that needs to be stored somewhere.\n        mp3_file = data['mp3'].file\n\n        content = mp3_file.read()\n\n        return web.Response(body=content,\n                            headers=MultiDict(\n                                {'CONTENT-DISPOSITION': mp3_file}))\n\n\nYou might have noticed a big warning in the example above. The general issue is\nthat :meth:`aiohttp.web.BaseRequest.post` reads the whole payload in memory,\nresulting in possible\n:abbr:`OOM (Out Of Memory)` errors. To avoid this, for multipart uploads, you\nshould use :meth:`aiohttp.web.BaseRequest.multipart` which returns a :ref:`multipart reader\n<aiohttp-multipart>`::\n\n    async def store_mp3_handler(request):\n\n        reader = await request.multipart()\n\n        # /!\\ Don't forget to validate your inputs /!\\\n\n        # reader.next() will `yield` the fields of your form\n\n        field = await reader.next()\n        assert field.name == 'name'\n        name = await field.read(decode=True)\n\n        field = await reader.next()\n        assert field.name == 'mp3'\n        filename = field.filename\n        # You cannot rely on Content-Length if transfer is chunked.\n        size = 0\n        with open(os.path.join('/spool/yarrr-media/mp3/', filename), 'wb') as f:\n            while True:\n                chunk = await field.read_chunk()  # 8192 bytes by default.\n                if not chunk:\n                    break\n                size += len(chunk)\n                f.write(chunk)\n\n        return web.Response(text='{} sized of {} successfully stored'\n                                 ''.format(filename, size))\n\n.. _aiohttp-web-websockets:\n\nWebSockets\n----------\n\n:mod:`aiohttp.web` supports *WebSockets* out-of-the-box.\n\nTo setup a *WebSocket*, create a :class:`WebSocketResponse` in a\n:ref:`request handler <aiohttp-web-handler>` and then use it to communicate\nwith the peer::\n\n    async def websocket_handler(request):\n\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        async for msg in ws:\n            # ws.__next__() automatically terminates the loop\n            # after ws.close() or ws.exception() is called\n            if msg.type == aiohttp.WSMsgType.TEXT:\n                if msg.data == 'close':\n                    await ws.close()\n                else:\n                    await ws.send_str(msg.data + '/answer')\n            elif msg.type == aiohttp.WSMsgType.ERROR:\n                print('ws connection closed with exception %s' %\n                      ws.exception())\n\n        print('websocket connection closed')\n\n        return ws\n\nThe handler should be registered as HTTP GET processor::\n\n    app.add_routes([web.get('/ws', websocket_handler)])\n\n.. _aiohttp-web-redirects:\n\nRedirects\n---------\n\nTo redirect user to another endpoint - raise :class:`HTTPFound` with\nan absolute URL, relative URL or view name (the argument from router)::\n\n    raise web.HTTPFound('/redirect')\n\nThe following example shows redirect to view named 'login' in routes::\n\n    async def handler(request):\n        location = request.app.router['login'].url_for()\n        raise web.HTTPFound(location=location)\n\n    router.add_get('/handler', handler)\n    router.add_get('/login', login_handler, name='login')\n\nExample with login validation::\n\n    @aiohttp_jinja2.template('login.html')\n    async def login(request):\n\n        if request.method == 'POST':\n            form = await request.post()\n            error = validate_login(form)\n            if error:\n                return {'error': error}\n            else:\n                # login form is valid\n                location = request.app.router['index'].url_for()\n                raise web.HTTPFound(location=location)\n\n        return {}\n\n    app.router.add_get('/', index, name='index')\n    app.router.add_get('/login', login, name='login')\n    app.router.add_post('/login', login, name='login')\n"
  },
  {
    "path": "docs/web_reference.rst",
    "content": ".. currentmodule:: aiohttp.web\n\n.. _aiohttp-web-reference:\n\nServer Reference\n================\n\n.. _aiohttp-web-request:\n\n\nRequest and Base Request\n------------------------\n\nThe Request object contains all the information about an incoming HTTP request.\n\n:class:`BaseRequest` is used for :ref:`Low-Level\nServers<aiohttp-web-lowlevel>` (which have no applications, routers,\nsignals and middlewares). :class:`Request` has an :attr:`Request.app`\nand :attr:`Request.match_info` attributes.\n\nA :class:`BaseRequest` / :class:`Request` are :obj:`dict` like objects,\nallowing them to be used for :ref:`sharing\ndata<aiohttp-web-data-sharing>` among :ref:`aiohttp-web-middlewares`\nand :ref:`aiohttp-web-signals` handlers.\n\n.. class:: BaseRequest\n   :canonical: aiohttp.web_request.BaseRequest\n\n   .. attribute:: version\n\n      *HTTP version* of request, Read-only property.\n\n      Returns :class:`aiohttp.protocol.HttpVersion` instance.\n\n   .. attribute:: method\n\n      *HTTP method*, read-only property.\n\n      The value is upper-cased :class:`str` like ``\"GET\"``,\n      ``\"POST\"``, ``\"PUT\"`` etc.\n\n   .. attribute:: url\n\n      A :class:`~yarl.URL` instance with absolute URL to resource\n      (*scheme*, *host* and *port* are included).\n\n      .. note::\n\n         In case of malformed request (e.g. without ``\"HOST\"`` HTTP\n         header) the absolute url may be unavailable.\n\n   .. attribute:: rel_url\n\n      A :class:`~yarl.URL` instance with relative URL to resource\n      (contains *path*, *query* and *fragment* parts only, *scheme*,\n      *host* and *port* are excluded).\n\n      The property is equal to ``.url.relative()`` but is always present.\n\n      .. seealso::\n\n         A note from :attr:`url`.\n\n   .. attribute:: scheme\n\n      A string representing the scheme of the request.\n\n      The scheme is ``'https'`` if transport for request handling is\n      *SSL*, ``'http'`` otherwise.\n\n      The value could be overridden by :meth:`~BaseRequest.clone`.\n\n      Read-only :class:`str` property.\n\n      .. versionchanged:: 2.3\n\n         *Forwarded* and *X-Forwarded-Proto* are not used anymore.\n\n         Call ``.clone(scheme=new_scheme)`` for setting up the value\n         explicitly.\n\n      .. seealso:: :ref:`aiohttp-web-forwarded-support`\n\n   .. attribute:: secure\n\n      Shorthand for ``request.url.scheme == 'https'``\n\n      Read-only :class:`bool` property.\n\n      .. seealso:: :attr:`scheme`\n\n   .. attribute:: forwarded\n\n      A tuple containing all parsed Forwarded header(s).\n\n      Makes an effort to parse Forwarded headers as specified by :rfc:`7239`:\n\n      - It adds one (immutable) dictionary per Forwarded ``field-value``, i.e.\n        per proxy. The element corresponds to the data in the Forwarded\n        ``field-value`` added by the first proxy encountered by the client.\n        Each subsequent item corresponds to those added by later proxies.\n      - It checks that every value has valid syntax in general as specified\n        in :rfc:`7239#section-4`: either a ``token`` or a ``quoted-string``.\n      - It un-escapes ``quoted-pairs``.\n      - It does NOT validate 'by' and 'for' contents as specified in\n        :rfc:`7239#section-6`.\n      - It does NOT validate ``host`` contents (Host ABNF).\n      - It does NOT validate ``proto`` contents for valid URI scheme names.\n\n      Returns a tuple containing one or more ``MappingProxy`` objects\n\n      .. seealso:: :attr:`scheme`\n\n      .. seealso:: :attr:`host`\n\n   .. attribute:: host\n\n      Host name of the request, resolved in this order:\n\n      - Overridden value by :meth:`~BaseRequest.clone` call.\n      - *Host* HTTP header\n      - :func:`socket.getfqdn`\n\n      Read-only :class:`str` property.\n\n      .. versionchanged:: 2.3\n\n         *Forwarded* and *X-Forwarded-Host* are not used anymore.\n\n         Call ``.clone(host=new_host)`` for setting up the value\n         explicitly.\n\n      .. seealso:: :ref:`aiohttp-web-forwarded-support`\n\n   .. attribute:: remote\n\n      Originating IP address of a client initiated HTTP request.\n\n      The IP is resolved through the following headers, in this order:\n\n      - Overridden value by :meth:`~BaseRequest.clone` call.\n      - Peer name of opened socket.\n\n      Read-only :class:`str` property.\n\n      Call ``.clone(remote=new_remote)`` for setting up the value\n      explicitly.\n\n      .. versionadded:: 2.3\n\n      .. seealso:: :ref:`aiohttp-web-forwarded-support`\n\n   .. attribute:: client_max_size\n\n      The maximum size of the request body.\n\n      The value could be overridden by :meth:`~BaseRequest.clone`.\n\n      Read-only :class:`int` property.\n\n   .. attribute:: path_qs\n\n      The URL including PATH_INFO and the query string. e.g.,\n      ``/app/blog?id=10``\n\n      Read-only :class:`str` property.\n\n   .. attribute:: path\n\n      The URL including *PATH INFO* without the host or scheme. e.g.,\n      ``/app/blog``. The path is URL-decoded. For raw path info see\n      :attr:`raw_path`.\n\n      Read-only :class:`str` property.\n\n   .. attribute:: raw_path\n\n      The URL including raw *PATH INFO* without the host or scheme.\n      Warning, the path may be URL-encoded and may contain invalid URL\n      characters, e.g.\n      ``/my%2Fpath%7Cwith%21some%25strange%24characters``.\n\n      For URL-decoded version please take a look on :attr:`path`.\n\n      Read-only :class:`str` property.\n\n   .. attribute:: query\n\n      A multidict with all the variables in the query string.\n\n      Read-only :class:`~multidict.MultiDictProxy` lazy property.\n\n   .. attribute:: query_string\n\n      The query string in the URL, e.g., ``id=10``\n\n      Read-only :class:`str` property.\n\n   .. attribute:: headers\n\n      A case-insensitive multidict proxy with all headers.\n\n      Read-only :class:`~multidict.CIMultiDictProxy` property.\n\n   .. attribute:: raw_headers\n\n      HTTP headers of response as unconverted bytes, a sequence of\n      ``(key, value)`` pairs.\n\n   .. attribute:: keep_alive\n\n      ``True`` if keep-alive connection enabled by HTTP client and\n      protocol version supports it, otherwise ``False``.\n\n      Read-only :class:`bool` property.\n\n   .. attribute:: transport\n\n      A :ref:`transport<asyncio-transport>` used to process request.\n      Read-only property.\n\n      The property can be used, for example, for getting IP address of\n      client's peer::\n\n         peername = request.transport.get_extra_info('peername')\n         if peername is not None:\n             host, port = peername\n\n   .. attribute:: cookies\n\n      A read-only dictionary-like object containing the request's cookies.\n\n      Read-only :class:`~types.MappingProxyType` property.\n\n   .. attribute:: content\n\n      A :class:`~aiohttp.StreamReader` instance,\n      input stream for reading request's *BODY*.\n\n      Read-only property.\n\n   .. attribute:: body_exists\n\n      Return ``True`` if request has *HTTP BODY*, ``False`` otherwise.\n\n      Read-only :class:`bool` property.\n\n      .. versionadded:: 2.3\n\n   .. attribute:: can_read_body\n\n      Return ``True`` if request's *HTTP BODY* can be read, ``False`` otherwise.\n\n      Read-only :class:`bool` property.\n\n      .. versionadded:: 2.3\n\n   .. attribute:: content_type\n\n      Read-only property with *content* part of *Content-Type* header.\n\n      Returns :class:`str` like ``'text/html'``\n\n      .. note::\n\n         Returns value is ``'application/octet-stream'`` if no\n         Content-Type header present in HTTP headers according to\n         :rfc:`2616`\n\n   .. attribute:: charset\n\n      Read-only property that specifies the *encoding* for the request's BODY.\n\n      The value is parsed from the *Content-Type* HTTP header.\n\n      Returns :class:`str` like ``'utf-8'`` or ``None`` if\n      *Content-Type* has no charset information.\n\n   .. attribute:: content_length\n\n      Read-only property that returns length of the request's BODY.\n\n      The value is parsed from the *Content-Length* HTTP header.\n\n      Returns :class:`int` or ``None`` if *Content-Length* is absent.\n\n   .. attribute:: http_range\n\n      Read-only property that returns information about *Range* HTTP header.\n\n      Returns a :class:`slice` where ``.start`` is *left inclusive\n      bound*, ``.stop`` is *right exclusive bound* and ``.step`` is\n      ``1``.\n\n      The property might be used in two manners:\n\n      1. Attribute-access style (example assumes that both left and\n         right borders are set, the real logic for case of open bounds\n         is more complex)::\n\n            rng = request.http_range\n            with open(filename, 'rb') as f:\n                f.seek(rng.start)\n                return f.read(rng.stop-rng.start)\n\n      2. Slice-style::\n\n            return buffer[request.http_range]\n\n   .. attribute:: if_modified_since\n\n      Read-only property that returns the date specified in the\n      *If-Modified-Since* header.\n\n      Returns :class:`datetime.datetime` or ``None`` if\n      *If-Modified-Since* header is absent or is not a valid\n      HTTP date.\n\n   .. attribute:: if_unmodified_since\n\n      Read-only property that returns the date specified in the\n      *If-Unmodified-Since* header.\n\n      Returns :class:`datetime.datetime` or ``None`` if\n      *If-Unmodified-Since* header is absent or is not a valid\n      HTTP date.\n\n      .. versionadded:: 3.1\n\n   .. attribute:: if_match\n\n      Read-only property that returns :class:`~aiohttp.ETag` objects specified\n      in the *If-Match* header.\n\n      Returns :class:`tuple` of :class:`~aiohttp.ETag` or ``None`` if\n      *If-Match* header is absent.\n\n      .. versionadded:: 3.8\n\n   .. attribute:: if_none_match\n\n      Read-only property that returns :class:`~aiohttp.ETag` objects specified\n      *If-None-Match* header.\n\n      Returns :class:`tuple` of :class:`~aiohttp.ETag` or ``None`` if\n      *If-None-Match* header is absent.\n\n      .. versionadded:: 3.8\n\n   .. attribute:: if_range\n\n      Read-only property that returns the date specified in the\n      *If-Range* header.\n\n      Returns :class:`datetime.datetime` or ``None`` if\n      *If-Range* header is absent or is not a valid\n      HTTP date.\n\n      .. versionadded:: 3.1\n\n   .. method:: clone(*, method=..., rel_url=..., headers=...)\n\n      Clone itself with replacement some attributes.\n\n      Creates and returns a new instance of Request object. If no parameters\n      are given, an exact copy is returned. If a parameter is not passed, it\n      will reuse the one from the current request object.\n\n      :param str method: http method\n\n      :param rel_url: url to use, :class:`str` or :class:`~yarl.URL`\n\n      :param headers: :class:`~multidict.CIMultiDict` or compatible\n                      headers container.\n\n      :return: a cloned :class:`Request` instance.\n\n   .. method:: get_extra_info(name, default=None)\n\n      Reads extra information from the protocol's transport.\n      If no value associated with ``name`` is found, ``default`` is returned.\n\n      See :meth:`asyncio.BaseTransport.get_extra_info`\n\n      :param str name: The key to look up in the transport extra information.\n\n      :param default: Default value to be used when no value for ``name`` is\n                      found (default is ``None``).\n\n      .. versionadded:: 3.7\n\n   .. method:: read()\n      :async:\n\n      Read request body, returns :class:`bytes` object with body content.\n\n      .. note::\n\n         The method **does** store read data internally, subsequent\n         :meth:`~aiohttp.web.BaseRequest.read` call will return the same value.\n\n   .. method:: text()\n      :async:\n\n      Read request body, decode it using :attr:`charset` encoding or\n      ``UTF-8`` if no encoding was specified in *MIME-type*.\n\n      Returns :class:`str` with body content.\n\n      .. note::\n\n         The method **does** store read data internally, subsequent\n         :meth:`~aiohttp.web.BaseRequest.text` call will return the same value.\n\n   .. method:: json(*, loads=json.loads, \\\n                    content_type='application/json')\n      :async:\n\n      Read request body decoded as *json*. If request's content-type does not\n      match `content_type` parameter, :exc:`aiohttp.web.HTTPBadRequest` get raised.\n      To disable content type check pass ``None`` value.\n\n      :param collections.abc.Callable loads: any :term:`callable` that accepts\n                              :class:`str` and returns :class:`dict`\n                              with parsed JSON (:func:`json.loads` by\n                              default).\n      :param str content_type: expected value of Content-Type header or ``None``\n                              ('application/json' by default)\n\n      .. note::\n\n         The method **does** store read data internally, subsequent\n         :meth:`~aiohttp.web.BaseRequest.json` call will return the same value.\n\n\n   .. method:: multipart()\n      :async:\n\n      Returns :class:`aiohttp.MultipartReader` which processes\n      incoming *multipart* request.\n\n      The method is just a boilerplate :ref:`coroutine <coroutine>`\n      implemented as::\n\n         async def multipart(self, *, reader=aiohttp.multipart.MultipartReader):\n             return reader(self.headers, self._payload)\n\n      This method is a coroutine for consistency with the else reader methods.\n\n      .. warning::\n\n         The method **does not** store read data internally. That means once\n         you exhausts multipart reader, you cannot get the request payload one\n         more time.\n\n      .. seealso:: :ref:`aiohttp-multipart`\n\n      .. versionchanged:: 3.4\n\n         Dropped *reader* parameter.\n\n   .. method:: post()\n      :async:\n\n      A :ref:`coroutine <coroutine>` that reads POST parameters from\n      request body.\n\n      Returns :class:`~multidict.MultiDictProxy` instance filled\n      with parsed data.\n\n      If :attr:`method` is not *POST*, *PUT*, *PATCH*, *TRACE* or *DELETE* or\n      :attr:`content_type` is not empty or\n      *application/x-www-form-urlencoded* or *multipart/form-data*\n      returns empty multidict.\n\n      .. note::\n\n         The method **does** store read data internally, subsequent\n         :meth:`~aiohttp.web.BaseRequest.post` call will return the same value.\n\n   .. method:: release()\n      :async:\n\n      Release request.\n\n      Eat unread part of HTTP BODY if present.\n\n      .. note::\n\n          User code may never call :meth:`~aiohttp.web.BaseRequest.release`, all\n          required work will be processed by :mod:`aiohttp.web`\n          internal machinery.\n\n.. class:: Request\n   :canonical: aiohttp.web_request.Request\n\n   A request used for receiving request's information by *web handler*.\n\n   Every :ref:`handler<aiohttp-web-handler>` accepts a request\n   instance as the first positional parameter.\n\n   The class in derived from :class:`BaseRequest`, shares all parent's\n   attributes and methods but has a couple of additional properties:\n\n   .. attribute:: match_info\n\n      Read-only property with :class:`~aiohttp.abc.AbstractMatchInfo`\n      instance for result of route resolving.\n\n      .. note::\n\n         Exact type of property depends on used router.  If\n         ``app.router`` is :class:`UrlDispatcher` the property contains\n         :class:`UrlMappingMatchInfo` instance.\n\n   .. attribute:: app\n\n      An :class:`Application` instance used to call :ref:`request handler\n      <aiohttp-web-handler>`, Read-only property.\n\n   .. attribute:: config_dict\n\n      A :class:`aiohttp.ChainMapProxy` instance for mapping all properties\n      from the current application returned by :attr:`app` property\n      and all its parents.\n\n      .. seealso:: :ref:`aiohttp-web-data-sharing-app-config`\n\n      .. versionadded:: 3.2\n\n   .. note::\n\n      You should never create the :class:`Request` instance manually\n      -- :mod:`aiohttp.web` does it for you. But\n      :meth:`~BaseRequest.clone` may be used for cloning *modified*\n      request copy with changed *path*, *method* etc.\n\n\n.. class:: RequestKey(name, t)\n   :canonical: aiohttp.helpers.RequestKey\n\n   Keys for use in :class:`Request`.\n\n   See :class:`AppKey` for more details.\n\n\n\n\n.. _aiohttp-web-response:\n\n\nResponse classes\n----------------\n\nFor now, :mod:`aiohttp.web` has three classes for the *HTTP response*:\n:class:`StreamResponse`, :class:`Response` and :class:`FileResponse`.\n\nUsually you need to use the second one. :class:`StreamResponse` is\nintended for streaming data, while :class:`Response` contains *HTTP\nBODY* as an attribute and sends own content as single piece with the\ncorrect *Content-Length HTTP header*.\n\nFor sake of design decisions :class:`Response` is derived from\n:class:`StreamResponse` parent class.\n\nThe response supports *keep-alive* handling out-of-the-box if\n*request* supports it.\n\nYou can disable *keep-alive* by :meth:`~StreamResponse.force_close` though.\n\nThe common case for sending an answer from\n:ref:`web-handler<aiohttp-web-handler>` is returning a\n:class:`Response` instance::\n\n   async def handler(request):\n       return Response(text=\"All right!\")\n\nResponse classes are :obj:`dict` like objects,\nallowing them to be used for :ref:`sharing\ndata<aiohttp-web-data-sharing>` among :ref:`aiohttp-web-middlewares`\nand :ref:`aiohttp-web-signals` handlers::\n\n   resp['key'] = value\n\n.. versionadded:: 3.0\n\n   Dict-like interface support.\n\n\n.. class:: StreamResponse(*, status=200, reason=None)\n   :canonical: aiohttp.web_response.StreamResponse\n\n   The base class for the *HTTP response* handling.\n\n   Contains methods for setting *HTTP response headers*, *cookies*,\n   *response status code*, writing *HTTP response BODY* and so on.\n\n   The most important thing you should know about *response* --- it\n   is *Finite State Machine*.\n\n   That means you can do any manipulations with *headers*, *cookies*\n   and *status code* only before :meth:`prepare` coroutine is called.\n\n   Once you call :meth:`prepare` any change of\n   the *HTTP header* part will raise :exc:`RuntimeError` exception.\n\n   Any :meth:`write` call after :meth:`write_eof` is also forbidden.\n\n   :param int status: HTTP status code, ``200`` by default.\n\n   :param str reason: HTTP reason. If param is ``None`` reason will be\n                      calculated basing on *status*\n                      parameter. Otherwise pass :class:`str` with\n                      arbitrary *status* explanation..\n\n   .. attribute:: prepared\n\n      Read-only :class:`bool` property, ``True`` if :meth:`prepare` has\n      been called, ``False`` otherwise.\n\n   .. attribute:: task\n\n      A task that serves HTTP request handling.\n\n      May be useful for graceful shutdown of long-running requests\n      (streaming, long polling or web-socket).\n\n   .. attribute:: status\n\n      Read-only property for *HTTP response status code*, :class:`int`.\n\n      ``200`` (OK) by default.\n\n   .. attribute:: reason\n\n      Read-only property for *HTTP response reason*, :class:`str`.\n\n   .. method:: set_status(status, reason=None)\n\n      Set :attr:`status` and :attr:`reason`.\n\n      *reason* value is auto calculated if not specified (``None``).\n\n   .. attribute:: keep_alive\n\n      Read-only property, copy of :attr:`aiohttp.web.BaseRequest.keep_alive` by default.\n\n      Can be switched to ``False`` by :meth:`force_close` call.\n\n   .. method:: force_close\n\n      Disable :attr:`keep_alive` for connection. There are no ways to\n      enable it back.\n\n   .. attribute:: compression\n\n      Read-only :class:`bool` property, ``True`` if compression is enabled.\n\n      ``False`` by default.\n\n      .. seealso:: :meth:`enable_compression`\n\n   .. method:: enable_compression(force=None, strategy=None)\n\n      Enable compression.\n\n      When *force* is unset compression encoding is selected based on\n      the request's *Accept-Encoding* header.\n\n      *Accept-Encoding* is not checked if *force* is set to a\n      :class:`ContentCoding`.\n\n      *strategy* accepts a :mod:`zlib` compression strategy.\n      See :func:`zlib.compressobj` for possible values, or refer to the\n      docs for the zlib of your using, should you use :func:`aiohttp.set_zlib_backend`\n      to change zlib backend. If ``None``, the default value adopted by\n      your zlib backend will be used where applicable.\n\n      .. seealso:: :attr:`compression`\n\n   .. attribute:: chunked\n\n      Read-only property, indicates if chunked encoding is on.\n\n      Can be enabled by :meth:`enable_chunked_encoding` call.\n\n      .. seealso:: :attr:`enable_chunked_encoding`\n\n   .. method:: enable_chunked_encoding()\n\n      Enables :attr:`chunked` encoding for response. There are no ways to\n      disable it back. With enabled :attr:`chunked` encoding each :meth:`write`\n      operation encoded in separate chunk.\n\n      .. warning:: chunked encoding can be enabled for ``HTTP/1.1`` only.\n\n                   Setting up both :attr:`content_length` and chunked\n                   encoding is mutually exclusive.\n\n      .. seealso:: :attr:`chunked`\n\n   .. attribute:: headers\n\n      :class:`~multidict.CIMultiDict` instance\n      for *outgoing* *HTTP headers*.\n\n   .. attribute:: cookies\n\n      An instance of :class:`http.cookies.SimpleCookie` for *outgoing* cookies.\n\n      .. warning::\n\n         Direct setting up *Set-Cookie* header may be overwritten by\n         explicit calls to cookie manipulation.\n\n         We are encourage using of :attr:`cookies` and\n         :meth:`set_cookie`, :meth:`del_cookie` for cookie\n         manipulations.\n\n   .. method:: set_cookie(name, value, *, path='/', expires=None, \\\n                          domain=None, max_age=None, \\\n                          secure=None, httponly=None, samesite=None, \\\n                          partitioned=None)\n\n      Convenient way for setting :attr:`cookies`, allows to specify\n      some additional properties like *max_age* in a single call.\n\n      :param str name: cookie name\n\n      :param str value: cookie value (will be converted to\n                        :class:`str` if value has another type).\n\n      :param expires: expiration date (optional)\n\n      :param str domain: cookie domain (optional)\n\n      :param int max_age: defines the lifetime of the cookie, in\n                          seconds.  The delta-seconds value is a\n                          decimal non- negative integer.  After\n                          delta-seconds seconds elapse, the client\n                          should discard the cookie.  A value of zero\n                          means the cookie should be discarded\n                          immediately.  (optional)\n\n      :param str path: specifies the subset of URLs to\n                       which this cookie applies. (optional, ``'/'`` by default)\n\n      :param bool secure: attribute (with no value) directs\n                          the user agent to use only (unspecified)\n                          secure means to contact the origin server\n                          whenever it sends back this cookie.\n                          The user agent (possibly under the user's\n                          control) may determine what level of\n                          security it considers appropriate for\n                          \"secure\" cookies.  The *secure* should be\n                          considered security advice from the server\n                          to the user agent, indicating that it is in\n                          the session's interest to protect the cookie\n                          contents. (optional)\n\n      :param bool httponly: ``True`` if the cookie HTTP only (optional)\n\n      :param str samesite: Asserts that a cookie must not be sent with\n         cross-origin requests, providing some protection\n         against cross-site request forgery attacks.\n         Generally the value should be one of: ``None``,\n         ``Lax`` or ``Strict``. (optional)\n\n            .. versionadded:: 3.7\n\n      :param bool partitioned: ``True`` to set a partitioned cookie.\n         Available in Python 3.14+. (optional)\n\n            .. versionadded:: 3.12\n\n   .. method:: del_cookie(name, *, path='/', domain=None)\n\n      Deletes cookie.\n\n      :param str name: cookie name\n\n      :param str domain: optional cookie domain\n\n      :param str path: optional cookie path, ``'/'`` by default\n\n   .. attribute:: content_length\n\n      *Content-Length* for outgoing response.\n\n   .. attribute:: content_type\n\n      *Content* part of *Content-Type* for outgoing response.\n\n   .. attribute:: charset\n\n      *Charset* aka *encoding* part of *Content-Type* for outgoing response.\n\n      The value converted to lower-case on attribute assigning.\n\n   .. attribute:: last_modified\n\n      *Last-Modified* header for outgoing response.\n\n      This property accepts raw :class:`str` values,\n      :class:`datetime.datetime` objects, Unix timestamps specified\n      as an :class:`int` or a :class:`float` object, and the\n      value ``None`` to unset the header.\n\n   .. attribute:: etag\n\n      *ETag* header for outgoing response.\n\n      This property accepts raw :class:`str` values, :class:`~aiohttp.ETag`\n      objects and the value ``None`` to unset the header.\n\n      In case of :class:`str` input, etag is considered as strong by default.\n\n      **Do not** use double quotes ``\"`` in the etag value,\n      they will be added automatically.\n\n      .. versionadded:: 3.8\n\n   .. method:: prepare(request)\n      :async:\n\n      :param aiohttp.web.Request request: HTTP request object, that the\n                                          response answers.\n\n      Send *HTTP header*. You should not change any header data after\n      calling this method.\n\n      The coroutine calls :attr:`~aiohttp.web.Application.on_response_prepare`\n      signal handlers after default headers have been computed and directly\n      before headers are sent.\n\n   .. method:: write(data)\n      :async:\n\n      Send byte-ish data as the part of *response BODY*::\n\n          await resp.write(data)\n\n      :meth:`prepare` must be invoked before the call.\n\n      Raises :exc:`TypeError` if data is not :class:`bytes`,\n      :class:`bytearray` or :class:`memoryview` instance.\n\n      Raises :exc:`RuntimeError` if :meth:`prepare` has not been called.\n\n      Raises :exc:`RuntimeError` if :meth:`write_eof` has been called.\n\n   .. method:: write_eof()\n      :async:\n\n      A :ref:`coroutine<coroutine>` *may* be called as a mark of the\n      *HTTP response* processing finish.\n\n      *Internal machinery* will call this method at the end of\n      the request processing if needed.\n\n      After :meth:`write_eof` call any manipulations with the *response*\n      object are forbidden.\n\n\n.. class:: Response(*, body=None, status=200, reason=None, text=None, \\\n                    headers=None, content_type=None, charset=None, \\\n                    zlib_executor_size=sentinel, zlib_executor=None)\n   :canonical: aiohttp.web_response.Response\n\n   The most usable response class, inherited from :class:`StreamResponse`.\n\n   Accepts *body* argument for setting the *HTTP response BODY*.\n\n   The actual :attr:`body` sending happens in overridden\n   :meth:`~StreamResponse.write_eof`.\n\n   :param bytes body: response's BODY\n\n   :param int status: HTTP status code, 200 OK by default.\n\n   :param collections.abc.Mapping headers: HTTP headers that should be added to\n                           response's ones.\n\n   :param str text: response's BODY\n\n   :param str content_type: response's content type. ``'text/plain'``\n                       if *text* is passed also,\n                       ``'application/octet-stream'`` otherwise.\n\n   :param str charset: response's charset. ``'utf-8'`` if *text* is\n                       passed also, ``None`` otherwise.\n\n   :param int zlib_executor_size: length in bytes which will trigger zlib compression\n                            of body to happen in an executor\n\n      .. versionadded:: 3.5\n\n   :param int zlib_executor: executor to use for zlib compression\n\n      .. versionadded:: 3.5\n\n\n   .. attribute:: body\n\n      Read-write attribute for storing response's content aka BODY,\n      :class:`bytes`.\n\n      Assigning :class:`str` to :attr:`body` will make the :attr:`body`\n      type of :class:`aiohttp.payload.StringPayload`, which tries to encode\n      the given data based on *Content-Type* HTTP header, while defaulting\n      to ``UTF-8``.\n\n   .. attribute:: text\n\n      Read-write attribute for storing response's\n      :attr:`~aiohttp.StreamResponse.body`, represented as :class:`str`.\n\n\n.. class:: FileResponse(*, path, chunk_size=256*1024, status=200, reason=None, headers=None)\n   :canonical: aiohttp.web_fileresponse.FileResponse\n\n   The response class used to send files, inherited from :class:`StreamResponse`.\n\n   Supports the ``Content-Range`` and ``If-Range`` HTTP Headers in requests.\n\n   The actual :attr:`body` sending happens in overridden :meth:`~StreamResponse.prepare`.\n\n   :param path: Path to file. Accepts both :class:`str` and :class:`pathlib.Path`.\n   :param int chunk_size: Chunk size in bytes which will be passed into\n                          :meth:`io.RawIOBase.read` in the event that the\n                          ``sendfile`` system call is not supported.\n\n   :param int status: HTTP status code, ``200`` by default.\n\n   :param str reason: HTTP reason. If param is ``None`` reason will be\n                      calculated basing on *status*\n                      parameter. Otherwise pass :class:`str` with\n                      arbitrary *status* explanation..\n\n   :param collections.abc.Mapping headers: HTTP headers that should be added to\n                           response's ones. The ``Content-Type`` response header\n                           will be overridden if provided.\n\n\n.. class:: WebSocketResponse(*, timeout=10.0, receive_timeout=None, \\\n                             autoclose=True, autoping=True, heartbeat=None, \\\n                             protocols=(), compress=True, max_msg_size=4194304, \\\n                             writer_limit=65536, decode_text=True)\n   :canonical: aiohttp.web_ws.WebSocketResponse\n\n   Class for handling server-side websockets, inherited from\n   :class:`StreamResponse`.\n\n   After starting (by :meth:`prepare` call) the response you\n   cannot use :meth:`~StreamResponse.write` method but should to\n   communicate with websocket client by :meth:`send_str`,\n   :meth:`receive` and others.\n\n   To enable back-pressure from slow websocket clients treat methods\n   :meth:`ping`, :meth:`pong`, :meth:`send_str`,\n   :meth:`send_bytes`, :meth:`send_json`, :meth:`send_frame` as coroutines.\n   By default write buffer size is set to 64k.\n\n   :param bool autoping: Automatically send\n                         :const:`~aiohttp.WSMsgType.PONG` on\n                         :const:`~aiohttp.WSMsgType.PING`\n                         message from client, and handle\n                         :const:`~aiohttp.WSMsgType.PONG`\n                         responses from client.\n                         Note that server does not send\n                         :const:`~aiohttp.WSMsgType.PING`\n                         requests, you need to do this explicitly\n                         using :meth:`ping` method.\n\n   :param float heartbeat: Send `ping` message every `heartbeat`\n                           seconds and wait `pong` response, close\n                           connection if `pong` response is not\n                           received. The timer is reset on any inbound data\n                           reception (coalesced per event loop iteration).\n\n   :param float timeout: Timeout value for the ``close``\n                         operation. After sending the close websocket message,\n                         ``close`` waits for ``timeout`` seconds for a response.\n                         Default value is ``10.0`` (10 seconds for ``close``\n                         operation)\n\n   :param float receive_timeout: Timeout value for `receive`\n                                 operations.  Default value is :data:`None`\n                                 (no timeout for receive operation)\n\n   :param bool compress: Enable per-message deflate extension support.\n                          :data:`False` for disabled, default value is :data:`True`.\n\n   :param int max_msg_size: maximum size of read websocket message, 4\n                            MB by default. To disable the size limit use ``0``.\n\n      .. versionadded:: 3.3\n\n   :param bool autoclose: Close connection when the client sends\n                           a :const:`~aiohttp.WSMsgType.CLOSE` message,\n                           ``True`` by default. If set to ``False``,\n                           the connection is not closed and the\n                           caller is responsible for calling\n                           ``request.transport.close()`` to avoid\n                           leaking resources.\n\n   :param int writer_limit: maximum size of write buffer, 64 KB by default.\n                            Once the buffer is full, the websocket will pause\n                            to drain the buffer.\n\n      .. versionadded:: 3.11\n\n   :param bool decode_text: If ``True`` (default), TEXT messages are\n                            decoded to strings. If ``False``, TEXT messages\n                            are returned as raw bytes, which can improve\n                            performance when using JSON parsers like\n                            ``orjson`` that accept bytes directly.\n\n      .. versionadded:: 3.14\n\n   The class supports ``async for`` statement for iterating over\n   incoming messages::\n\n      ws = web.WebSocketResponse()\n      await ws.prepare(request)\n\n          async for msg in ws:\n              print(msg.data)\n\n\n   .. method:: prepare(request)\n      :async:\n\n      Starts websocket. After the call you can use websocket methods.\n\n      :param aiohttp.web.Request request: HTTP request object, that the\n                                          response answers.\n\n\n      :raises HTTPException: if websocket handshake has failed.\n\n   .. method:: can_prepare(request)\n\n      Performs checks for *request* data to figure out if websocket\n      can be started on the request.\n\n      If :meth:`can_prepare` call is success then :meth:`prepare` will\n      success too.\n\n      :param aiohttp.web.Request request: HTTP request object, that the\n                                          response answers.\n\n      :return: :class:`WebSocketReady` instance.\n\n               :attr:`WebSocketReady.ok` is\n               ``True`` on success, :attr:`WebSocketReady.protocol` is\n               websocket subprotocol which is passed by client and\n               accepted by server (one of *protocols* sequence from\n               :class:`WebSocketResponse` ctor).\n               :attr:`WebSocketReady.protocol` may be ``None`` if\n               client and server subprotocols are not overlapping.\n\n      .. note:: The method never raises exception.\n\n   .. attribute:: closed\n\n      Read-only property, ``True`` if connection has been closed or in process\n      of closing.\n      :const:`~aiohttp.WSMsgType.CLOSE` message has been received from peer.\n\n   .. attribute:: prepared\n\n      Read-only :class:`bool` property, ``True`` if :meth:`prepare` has\n      been called, ``False`` otherwise.\n\n   .. attribute:: close_code\n\n      Read-only property, close code from peer. It is set to ``None`` on\n      opened connection.\n\n   .. attribute:: ws_protocol\n\n      Websocket *subprotocol* chosen after :meth:`start` call.\n\n      May be ``None`` if server and client protocols are\n      not overlapping.\n\n   .. method:: get_extra_info(name, default=None)\n\n      Reads optional extra information from the writer's transport.\n      If no value associated with ``name`` is found, ``default`` is returned.\n\n      See :meth:`asyncio.BaseTransport.get_extra_info`\n\n      :param str name: The key to look up in the transport extra information.\n\n      :param default: Default value to be used when no value for ``name`` is\n                      found (default is ``None``).\n\n   .. method:: exception()\n\n      Returns last occurred exception or None.\n\n   .. method:: ping(message=b'')\n      :async:\n\n      Send :const:`~aiohttp.WSMsgType.PING` to peer.\n\n      :param message: optional payload of *ping* message,\n                      :class:`str` (converted to *UTF-8* encoded bytes)\n                      or :class:`bytes`.\n\n      :raise RuntimeError: if the connections is not started.\n\n      :raise aiohttp.ClientConnectionResetError: if the connection is closing.\n\n      .. versionchanged:: 3.0\n\n         The method is converted into :term:`coroutine`\n\n   .. method:: pong(message=b'')\n      :async:\n\n      Send *unsolicited* :const:`~aiohttp.WSMsgType.PONG` to peer.\n\n      :param message: optional payload of *pong* message,\n                      :class:`str` (converted to *UTF-8* encoded bytes)\n                      or :class:`bytes`.\n\n      :raise RuntimeError: if the connections is not started.\n\n      :raise aiohttp.ClientConnectionResetError: if the connection is closing.\n\n      .. versionchanged:: 3.0\n\n         The method is converted into :term:`coroutine`\n\n   .. method:: send_str(data, compress=None)\n      :async:\n\n      Send *data* to peer as :const:`~aiohttp.WSMsgType.TEXT` message.\n\n      :param str data: data to send.\n\n      :param int compress: sets specific level of compression for\n                           single message,\n                           ``None`` for not overriding per-socket setting.\n\n      :raise RuntimeError: if the connection is not started.\n\n      :raise TypeError: if data is not :class:`str`\n\n      :raise aiohttp.ClientConnectionResetError: if the connection is closing.\n\n      .. versionchanged:: 3.0\n\n         The method is converted into :term:`coroutine`,\n         *compress* parameter added.\n\n   .. method:: send_bytes(data, compress=None)\n      :async:\n\n      Send *data* to peer as :const:`~aiohttp.WSMsgType.BINARY` message.\n\n      :param data: data to send.\n\n      :param int compress: sets specific level of compression for\n                           single message,\n                           ``None`` for not overriding per-socket setting.\n\n      :raise RuntimeError: if the connection is not started.\n\n      :raise TypeError: if data is not :class:`bytes`,\n                        :class:`bytearray` or :class:`memoryview`.\n\n      :raise aiohttp.ClientConnectionResetError: if the connection is closing.\n\n      .. versionchanged:: 3.0\n\n         The method is converted into :term:`coroutine`,\n         *compress* parameter added.\n\n   .. method:: send_json(data, compress=None, *, dumps=json.dumps)\n      :async:\n\n      Send *data* to peer as JSON string.\n\n      :param data: data to send.\n\n      :param int compress: sets specific level of compression for\n                           single message,\n                           ``None`` for not overriding per-socket setting.\n\n      :param collections.abc.Callable dumps: any :term:`callable` that accepts an object and\n                             returns a JSON string\n                             (:func:`json.dumps` by default).\n\n      :raise RuntimeError: if the connection is not started.\n\n      :raise ValueError: if data is not serializable object\n\n      :raise TypeError: if value returned by ``dumps`` param is not :class:`str`\n\n      :raise aiohttp.ClientConnectionResetError: if the connection is closing.\n\n      .. versionchanged:: 3.0\n\n         The method is converted into :term:`coroutine`,\n         *compress* parameter added.\n\n   .. method:: send_json_bytes(data, compress=None, *, dumps)\n      :async:\n\n      Send *data* to peer as a JSON binary frame using a bytes-returning encoder.\n\n      :param data: data to send.\n\n      :param int compress: sets specific level of compression for\n                           single message,\n                           ``None`` for not overriding per-socket setting.\n\n      :param collections.abc.Callable dumps: any :term:`callable` that accepts an object and\n                             returns JSON as :class:`bytes`\n                             (e.g. ``orjson.dumps``).\n\n      :raise RuntimeError: if the connection is not started.\n\n      :raise ValueError: if data is not serializable object\n\n      :raise TypeError: if value returned by ``dumps`` param is not :class:`bytes`\n\n   .. method:: send_frame(message, opcode, compress=None)\n      :async:\n\n      Send a :const:`~aiohttp.WSMsgType` message *message* to peer.\n\n      This method is low-level and should be used with caution as it\n      only accepts bytes which must conform to the correct message type\n      for *message*.\n\n      It is recommended to use the :meth:`send_str`, :meth:`send_bytes`\n      or :meth:`send_json` methods instead of this method.\n\n      The primary use case for this method is to send bytes that are\n      have already been encoded without having to decode and\n      re-encode them.\n\n      :param bytes message: message to send.\n\n      :param ~aiohttp.WSMsgType opcode: opcode of the message.\n\n      :param int compress: sets specific level of compression for\n                           single message,\n                           ``None`` for not overriding per-socket setting.\n\n      :raise RuntimeError: if the connection is not started.\n\n      :raise aiohttp.ClientConnectionResetError: if the connection is closing.\n\n      .. versionadded:: 3.11\n\n   .. method:: close(*, code=WSCloseCode.OK, message=b'', drain=True)\n      :async:\n\n      A :ref:`coroutine<coroutine>` that initiates closing\n      handshake by sending :const:`~aiohttp.WSMsgType.CLOSE` message.\n\n      It is safe to call `close()` from different task.\n\n      :param int code: closing code. See also :class:`~aiohttp.WSCloseCode`.\n\n      :param message: optional payload of *close* message,\n                      :class:`str` (converted to *UTF-8* encoded bytes)\n                      or :class:`bytes`.\n\n      :param bool drain: drain outgoing buffer before closing connection.\n\n      :raise RuntimeError: if connection is not started\n\n   .. method:: receive(timeout=None)\n      :async:\n\n      A :ref:`coroutine<coroutine>` that waits upcoming *data*\n      message from peer and returns it.\n\n      The coroutine implicitly handles\n      :const:`~aiohttp.WSMsgType.PING`,\n      :const:`~aiohttp.WSMsgType.PONG` and\n      :const:`~aiohttp.WSMsgType.CLOSE` without returning the\n      message.\n\n      It process *ping-pong game* and performs *closing handshake* internally.\n\n      .. note::\n\n         Can only be called by the request handling task.\n\n      :param timeout: timeout for `receive` operation.\n\n         timeout value overrides response`s receive_timeout attribute.\n\n      :return: :class:`~aiohttp.WSMessage`\n\n      :raise RuntimeError: if connection is not started\n\n      :raise asyncio.TimeoutError: if timeout expires before receiving a message\n\n   .. method:: receive_str(*, timeout=None)\n      :async:\n\n      A :ref:`coroutine<coroutine>` that calls :meth:`receive` but\n      also asserts the message type is :const:`~aiohttp.WSMsgType.TEXT`.\n\n      .. note::\n\n         Can only be called by the request handling task.\n\n      :param timeout: timeout for `receive` operation.\n\n         timeout value overrides response`s receive_timeout attribute.\n\n      :return str: peer's message content.\n\n      :raise aiohttp.WSMessageTypeError: if message is not :const:`~aiohttp.WSMsgType.TEXT`.\n\n      :raise asyncio.TimeoutError: if timeout expires before receiving a message\n\n   .. method:: receive_bytes(*, timeout=None)\n      :async:\n\n      A :ref:`coroutine<coroutine>` that calls :meth:`receive` but\n      also asserts the message type is\n      :const:`~aiohttp.WSMsgType.BINARY`.\n\n      .. note::\n\n         Can only be called by the request handling task.\n\n      :param timeout: timeout for `receive` operation.\n\n         timeout value overrides response`s receive_timeout attribute.\n\n      :return bytes: peer's message content.\n\n      :raise aiohttp.WSMessageTypeError: if message is not :const:`~aiohttp.WSMsgType.BINARY`.\n\n      :raise asyncio.TimeoutError: if timeout expires before receiving a message\n\n   .. method:: receive_json(*, loads=json.loads, timeout=None)\n      :async:\n\n      A :ref:`coroutine<coroutine>` that calls :meth:`receive_str` and loads the\n      JSON string to a Python dict.\n\n      .. note::\n\n         Can only be called by the request handling task.\n\n      :param collections.abc.Callable loads: any :term:`callable` that accepts\n                              :class:`str` and returns :class:`dict`\n                              with parsed JSON (:func:`json.loads` by\n                              default).\n\n      :param timeout: timeout for `receive` operation.\n\n         timeout value overrides response`s receive_timeout attribute.\n\n      :return dict: loaded JSON content\n\n      :raise TypeError: if message is :const:`~aiohttp.WSMsgType.BINARY`.\n      :raise ValueError: if message is not valid JSON.\n      :raise asyncio.TimeoutError: if timeout expires before receiving a message\n\n\n.. seealso:: :ref:`WebSockets handling<aiohttp-web-websockets>`\n\n\n.. class:: WebSocketReady\n   :canonical: aiohttp.web_ws.WebSocketReady\n\n   A named tuple for returning result from\n   :meth:`WebSocketResponse.can_prepare`.\n\n   Has :class:`bool` check implemented, e.g.::\n\n       if not await ws.can_prepare(...):\n           cannot_start_websocket()\n\n   .. attribute:: ok\n\n      ``True`` if websocket connection can be established, ``False``\n      otherwise.\n\n\n   .. attribute:: protocol\n\n      :class:`str` represented selected websocket sub-protocol.\n\n   .. seealso:: :meth:`WebSocketResponse.can_prepare`\n\n\n.. function:: json_response([data], *, text=None, body=None, \\\n                            status=200, reason=None, headers=None, \\\n                            content_type='application/json', \\\n                            dumps=json.dumps)\n   :canonical: aiohttp.web_response.json_response\n\nReturn :class:`Response` with predefined ``'application/json'``\ncontent type and *data* encoded by ``dumps`` parameter\n(:func:`json.dumps` by default).\n\n\n.. function:: json_bytes_response([data], *, dumps, body=None, \\\n                                  status=200, reason=None, headers=None, \\\n                                  content_type='application/json')\n\nReturn :class:`Response` with predefined ``'application/json'``\ncontent type and *data* encoded by ``dumps`` parameter\nwhich must return :class:`bytes` directly (e.g. ``orjson.dumps``).\n\nUse this when your JSON encoder returns :class:`bytes` instead of :class:`str`,\navoiding the :class:`str`-to-:class:`bytes` encoding overhead.\n\n\n.. class:: ResponseKey(name, t)\n   :canonical: aiohttp.helpers.ResponseKey\n\n   Keys for use in :class:`Response`.\n\n   See :class:`AppKey` for more details.\n\n\n\n\n.. _aiohttp-web-app-and-router:\n\nApplication and Router\n----------------------\n\n\n.. class:: Application(*, logger=<default>, middlewares=(), \\\n                       handler_args=None, client_max_size=1024**2, \\\n                       debug=...)\n   :canonical: aiohttp.web_app.Application\n\n   Application is a synonym for web-server.\n\n   To get a fully working example, you have to make an *application*, register\n   supported urls in the *router* and pass it to :func:`aiohttp.web.run_app`\n   or :class:`aiohttp.web.AppRunner`.\n\n   *Application* contains a *router* instance and a list of callbacks that\n   will be called during application finishing.\n\n   This class is a :obj:`dict`-like object, so you can use it for\n   :ref:`sharing data<aiohttp-web-data-sharing>` globally by storing arbitrary\n   properties for later access from a :ref:`handler<aiohttp-web-handler>` via the\n   :attr:`Request.app` property::\n\n       app = Application()\n       database = AppKey(\"database\", AsyncEngine)\n       app[database] = await create_async_engine(db_url)\n\n       async def handler(request):\n           async with request.app[database].begin() as conn:\n               await conn.execute(\"DELETE * FROM table\")\n\n   Although it` is a :obj:`dict`-like object, it can't be duplicated like one\n   using :meth:`~aiohttp.web.Application.copy`.\n\n   The class inherits :class:`dict`.\n\n   :param logger: :class:`logging.Logger` instance for storing application logs.\n\n                  By default the value is ``logging.getLogger(\"aiohttp.web\")``\n\n   :param middlewares: :class:`list` of middleware factories, see\n                       :ref:`aiohttp-web-middlewares` for details.\n\n   :param handler_args: dict-like object that overrides keyword arguments of\n                        :class:`AppRunner` constructor.\n\n   :param client_max_size: client's maximum size in a request, in\n                           bytes.  If a POST request exceeds this\n                           value, it raises an\n                           `HTTPRequestEntityTooLarge` exception.\n\n   :param debug: Switches debug mode.\n\n      .. deprecated:: 3.5\n\n         The argument does nothing starting from 4.0,\n         use asyncio :ref:`asyncio-debug-mode` instead.\n\n\n   .. attribute:: router\n\n      Read-only property that returns *router instance*.\n\n   .. attribute:: logger\n\n      :class:`logging.Logger` instance for storing application logs.\n\n   .. attribute:: debug\n\n      Boolean value indicating whether the debug mode is turned on or off.\n\n      .. deprecated:: 3.5\n\n         Use asyncio :ref:`asyncio-debug-mode` instead.\n\n   .. attribute:: on_response_prepare\n\n      A :class:`~aiosignal.Signal` that is fired near the end\n      of :meth:`StreamResponse.prepare` with parameters *request* and\n      *response*. It can be used, for example, to add custom headers to each\n      response, or to modify the default headers computed by the application,\n      directly before sending the headers to the client.\n\n      Signal handlers should have the following signature::\n\n          async def on_prepare(request, response):\n              pass\n\n      .. note::\n\n         The headers are written immediately after these callbacks are run.\n         Therefore, if you modify the content of the response, you may need to\n         adjust the `Content-Length` header or similar to match. Aiohttp will\n         not make any updates to the headers from this point.\n\n   .. attribute:: on_startup\n\n      A :class:`~aiosignal.Signal` that is fired on application start-up.\n\n      Subscribers may use the signal to run background tasks in the event\n      loop along with the application's request handler just after the\n      application start-up.\n\n      Signal handlers should have the following signature::\n\n          async def on_startup(app):\n              pass\n\n      .. seealso:: :ref:`aiohttp-web-signals` and :ref:`aiohttp-web-cleanup-ctx`.\n\n   .. attribute:: on_shutdown\n\n      A :class:`~aiosignal.Signal` that is fired on application shutdown.\n\n      Subscribers may use the signal for gracefully closing long running\n      connections, e.g. websockets and data streaming.\n\n      Signal handlers should have the following signature::\n\n          async def on_shutdown(app):\n              pass\n\n      It's up to end user to figure out which :term:`web-handler`\\s\n      are still alive and how to finish them properly.\n\n      We suggest keeping a list of long running handlers in\n      :class:`Application` dictionary.\n\n      .. seealso:: :ref:`aiohttp-web-graceful-shutdown` and :attr:`on_cleanup`.\n\n   .. attribute:: on_cleanup\n\n      A :class:`~aiosignal.Signal` that is fired on application cleanup.\n\n      Subscribers may use the signal for gracefully closing\n      connections to database server etc.\n\n      Signal handlers should have the following signature::\n\n          async def on_cleanup(app):\n              pass\n\n      .. seealso:: :ref:`aiohttp-web-signals` and :attr:`on_shutdown`.\n\n   .. attribute:: cleanup_ctx\n\n      A list of *context generators* for *startup*/*cleanup* handling.\n\n      Signal handlers should have the following signature::\n\n          @contextlib.asynccontextmanager\n          async def context(app: web.Application) -> AsyncIterator[None]:\n              # do startup stuff\n              yield\n              # do cleanup\n\n      .. versionadded:: 3.1\n\n      .. seealso:: :ref:`aiohttp-web-cleanup-ctx`.\n\n   .. method:: add_subapp(prefix, subapp)\n\n      Register nested sub-application under given path *prefix*.\n\n      In resolving process if request's path starts with *prefix* then\n      further resolving is passed to *subapp*.\n\n      :param str prefix: path's prefix for the resource.\n\n      :param Application subapp: nested application attached under *prefix*.\n\n      :returns: a :class:`PrefixedSubAppResource` instance.\n\n   .. method:: add_domain(domain, subapp)\n\n      Register nested sub-application that serves\n      the domain name or domain name mask.\n\n      In resolving process if request.headers['host']\n      matches the pattern *domain* then\n      further resolving is passed to *subapp*.\n\n      .. warning::\n\n         Registering many domains using this method may cause performance\n         issues with handler routing. If you have a substantial number of\n         applications for different domains, you may want to consider\n         using a reverse proxy (such as Nginx) to handle routing to\n         different apps, rather that registering them as sub-applications.\n\n      :param str domain: domain or mask of domain for the resource.\n\n      :param Application subapp: nested application.\n\n      :returns: a :class:`~aiohttp.web.MatchedSubAppResource` instance.\n\n   .. method:: add_routes(routes_table)\n\n      Register route definitions from *routes_table*.\n\n      The table is a :class:`list` of :class:`RouteDef` items or\n      :class:`RouteTableDef`.\n\n      :returns: :class:`list` of registered :class:`AbstractRoute` instances.\n\n      The method is a shortcut for\n      ``app.router.add_routes(routes_table)``, see also\n      :meth:`UrlDispatcher.add_routes`.\n\n      .. versionadded:: 3.1\n\n      .. versionchanged:: 3.7\n\n         Return value updated from ``None`` to :class:`list` of\n         :class:`AbstractRoute` instances.\n\n   .. method:: startup()\n      :async:\n\n      A :ref:`coroutine<coroutine>` that will be called along with the\n      application's request handler.\n\n      The purpose of the method is calling :attr:`on_startup` signal\n      handlers.\n\n   .. method:: shutdown()\n      :async:\n\n      A :ref:`coroutine<coroutine>` that should be called on\n      server stopping but before :meth:`cleanup`.\n\n      The purpose of the method is calling :attr:`on_shutdown` signal\n      handlers.\n\n   .. method:: cleanup()\n      :async:\n\n      A :ref:`coroutine<coroutine>` that should be called on\n      server stopping but after :meth:`shutdown`.\n\n      The purpose of the method is calling :attr:`on_cleanup` signal\n      handlers.\n\n   .. note::\n\n      Application object has :attr:`router` attribute but has no\n      ``add_route()`` method. The reason is: we want to support\n      different router implementations (even maybe not url-matching\n      based but traversal ones).\n\n      For sake of that fact we have very trivial ABC for\n      :class:`~aiohttp.abc.AbstractRouter`: it should have only\n      :meth:`aiohttp.abc.AbstractRouter.resolve` coroutine.\n\n      No methods for adding routes or route reversing (getting URL by\n      route name). All those are router implementation details (but,\n      sure, you need to deal with that methods after choosing the\n      router for your application).\n\n\n.. class:: AppKey(name, t)\n   :canonical: aiohttp.helpers.AppKey\n\n   This class should be used for the keys in :class:`Application`. They\n   provide a type-safe alternative to `str` keys when checking your code\n   with a type checker (e.g. mypy). They also avoid name clashes with keys\n   from different libraries etc.\n\n   :param name: A name to help with debugging. This should be the same as\n                the variable name (much like how :class:`typing.TypeVar`\n                is used).\n\n   :param t: The type that should be used for the value in the dict (e.g.\n             `str`, `Iterator[int]` etc.)\n\n\n.. class:: Server\n   :canonical: aiohttp.web_server.Server\n\n   A protocol factory compatible with\n   :meth:`~asyncio.AbstractEventLoop.create_server`.\n\n   The class is responsible for creating HTTP protocol\n   objects that can handle HTTP connections.\n\n   .. attribute:: connections\n\n      List of all currently opened connections.\n\n   .. attribute:: requests_count\n\n      Amount of processed requests.\n\n   .. method:: Server.shutdown(timeout)\n      :async:\n\n      A :ref:`coroutine<coroutine>` that should be called to close all opened\n      connections.\n\n\n.. class:: UrlDispatcher()\n   :canonical: aiohttp.web_urldispatcher.UrlDispatcher\n\n   For dispatching URLs to :ref:`handlers<aiohttp-web-handler>`\n   :mod:`aiohttp.web` uses *routers*, which is any object that implements\n   :class:`~aiohttp.abc.AbstractRouter` interface.\n\n   This class is a straightforward url-matching router, implementing\n   :class:`collections.abc.Mapping` for access to *named routes*.\n\n   :class:`Application` uses this class as\n   :meth:`~aiohttp.web.Application.router` by default.\n\n   Before running an :class:`Application` you should fill *route\n   table* first by calling :meth:`add_route` and :meth:`add_static`.\n\n   :ref:`Handler<aiohttp-web-handler>` lookup is performed by iterating on\n   added *routes* in FIFO order. The first matching *route* will be used\n   to call the corresponding *handler*.\n\n   If during route creation you specify *name* parameter the result is a\n   *named route*.\n\n   A *named route* can be retrieved by a ``app.router[name]`` call, checking for\n   existence can be done with ``name in app.router`` etc.\n\n   .. seealso:: :ref:`Route classes <aiohttp-web-route>`\n\n   .. method:: add_resource(path, *, name=None)\n\n      Append a :term:`resource` to the end of route table.\n\n      *path* may be either *constant* string like ``'/a/b/c'`` or\n      *variable rule* like ``'/a/{var}'`` (see\n      :ref:`handling variable paths <aiohttp-web-variable-handler>`)\n\n      :param str path: resource path spec.\n\n      :param str name: optional resource name.\n\n      :return: created resource instance (:class:`PlainResource` or\n               :class:`DynamicResource`).\n\n   .. method:: add_route(method, path, handler, *, \\\n                         name=None, expect_handler=None)\n\n      Append :ref:`handler<aiohttp-web-handler>` to the end of route table.\n\n      *path* may be either *constant* string like ``'/a/b/c'`` or\n       *variable rule* like ``'/a/{var}'`` (see\n       :ref:`handling variable paths <aiohttp-web-variable-handler>`)\n\n      Pay attention please: *handler* must be a coroutine.\n\n      :param str method: HTTP method for route. Should be one of\n                         ``'GET'``, ``'POST'``, ``'PUT'``,\n                         ``'DELETE'``, ``'PATCH'``, ``'HEAD'``,\n                         ``'OPTIONS'`` or ``'*'`` for any method.\n\n                         The parameter is case-insensitive, e.g. you\n                         can push ``'get'`` as well as ``'GET'``.\n\n      :param str path: route path. Should be started with slash (``'/'``).\n\n      :param collections.abc.Callable handler: route handler.\n\n      :param str name: optional route name.\n\n      :param collections.abc.Coroutine expect_handler: optional *expect* header handler.\n\n      :returns: new :class:`AbstractRoute` instance.\n\n   .. method:: add_routes(routes_table)\n\n      Register route definitions from *routes_table*.\n\n      The table is a :class:`list` of :class:`RouteDef` items or\n      :class:`RouteTableDef`.\n\n      :returns: :class:`list` of registered :class:`AbstractRoute` instances.\n\n      .. versionadded:: 2.3\n\n      .. versionchanged:: 3.7\n\n         Return value updated from ``None`` to :class:`list` of\n         :class:`AbstractRoute` instances.\n\n   .. method:: add_get(path, handler, *, name=None, allow_head=True, **kwargs)\n\n      Shortcut for adding a GET handler. Calls the :meth:`add_route` with \\\n      ``method`` equals to ``'GET'``.\n\n      If *allow_head* is ``True`` (default) the route for method HEAD\n      is added with the same handler as for GET.\n\n      If *name* is provided the name for HEAD route is suffixed with\n      ``'-head'``. For example ``router.add_get(path, handler,\n      name='route')`` call adds two routes: first for GET with name\n      ``'route'`` and second for HEAD with name ``'route-head'``.\n\n   .. method:: add_post(path, handler, **kwargs)\n\n      Shortcut for adding a POST handler. Calls the :meth:`add_route` with \\\n\n\n      ``method`` equals to ``'POST'``.\n\n   .. method:: add_head(path, handler, **kwargs)\n\n      Shortcut for adding a HEAD handler. Calls the :meth:`add_route` with \\\n      ``method`` equals to ``'HEAD'``.\n\n   .. method:: add_put(path, handler, **kwargs)\n\n      Shortcut for adding a PUT handler. Calls the :meth:`add_route` with \\\n      ``method`` equals to ``'PUT'``.\n\n   .. method:: add_patch(path, handler, **kwargs)\n\n      Shortcut for adding a PATCH handler. Calls the :meth:`add_route` with \\\n      ``method`` equals to ``'PATCH'``.\n\n   .. method:: add_delete(path, handler, **kwargs)\n\n      Shortcut for adding a DELETE handler. Calls the :meth:`add_route` with \\\n      ``method`` equals to ``'DELETE'``.\n\n   .. method:: add_view(path, handler, **kwargs)\n\n      Shortcut for adding a class-based view handler. Calls the \\\n      :meth:`add_route` with ``method`` equals to ``'*'``.\n\n      .. versionadded:: 3.0\n\n   .. method:: add_static(prefix, path, *, name=None, expect_handler=None, \\\n                          chunk_size=256*1024, \\\n                          response_factory=StreamResponse, \\\n                          show_index=False, \\\n                          follow_symlinks=False, \\\n                          append_version=False)\n\n      Adds a router and a handler for returning static files.\n\n      Useful for serving static content like images, javascript and css files.\n\n      On platforms that support it, the handler will transfer files more\n      efficiently using the ``sendfile`` system call.\n\n      In some situations it might be necessary to avoid using the ``sendfile``\n      system call even if the platform supports it. This can be accomplished by\n      by setting environment variable ``AIOHTTP_NOSENDFILE=1``.\n\n      If a Brotli or gzip compressed version of the static content exists at\n      the requested path with the ``.br`` or ``.gz`` extension, it will be used\n      for the response. Brotli will be preferred over gzip if both files exist.\n\n      .. warning::\n\n         Use :meth:`add_static` for development only. In production,\n         static content should be processed by web servers like *nginx*\n         or *apache*. Such web servers will be able to provide significantly\n         better performance and security for static assets. Several past security\n         vulnerabilities in aiohttp only affected applications using\n         :meth:`add_static`.\n\n      :param str prefix: URL path prefix for handled static files\n\n      :param path: path to the folder in file system that contains\n                   handled static files, :class:`str` or :class:`pathlib.Path`.\n\n      :param str name: optional route name.\n\n      :param collections.abc.Coroutine expect_handler: optional *expect* header handler.\n\n      :param int chunk_size: size of single chunk for file\n                             downloading, 256Kb by default.\n\n                             Increasing *chunk_size* parameter to,\n                             say, 1Mb may increase file downloading\n                             speed but consumes more memory.\n\n      :param bool show_index: flag for allowing to show indexes of a directory,\n                              by default it's not allowed and HTTP/403 will\n                              be returned on directory access.\n\n      :param bool follow_symlinks: flag for allowing to follow symlinks that lead\n                              outside the static root directory, by default it's not allowed and\n                              HTTP/404 will be returned on access.  Enabling ``follow_symlinks``\n                              can be a security risk, and may lead to a directory transversal attack.\n                              You do NOT need this option to follow symlinks which point to somewhere\n                              else within the static directory, this option is only used to break out\n                              of the security sandbox. Enabling this option is highly discouraged,\n                              and only expected to be used for edge cases in a local development\n                              setting where remote users do not have access to the server.\n\n      :param bool append_version: flag for adding file version (hash)\n                              to the url query string, this value will\n                              be used as default when you call to\n                              :meth:`~aiohttp.web.AbstractRoute.url` and\n                              :meth:`~aiohttp.web.AbstractRoute.url_for` methods.\n\n\n      :returns: new :class:`~aiohttp.web.AbstractRoute` instance.\n\n   .. method:: resolve(request)\n      :async:\n\n      A :ref:`coroutine<coroutine>` that returns\n      :class:`~aiohttp.abc.AbstractMatchInfo` for *request*.\n\n      The method never raises exception, but returns\n      :class:`~aiohttp.abc.AbstractMatchInfo` instance with:\n\n      1. :attr:`~aiohttp.abc.AbstractMatchInfo.http_exception` assigned to\n         :exc:`HTTPException` instance.\n      2. :meth:`~aiohttp.abc.AbstractMatchInfo.handler` which raises\n         :exc:`HTTPNotFound` or :exc:`HTTPMethodNotAllowed` on handler's\n         execution if there is no registered route for *request*.\n\n         *Middlewares* can process that exceptions to render\n         pretty-looking error page for example.\n\n      Used by internal machinery, end user unlikely need to call the method.\n\n      .. note:: The method uses :attr:`aiohttp.web.BaseRequest.raw_path` for pattern\n         matching against registered routes.\n\n   .. method:: resources()\n\n      The method returns a *view* for *all* registered resources.\n\n      The view is an object that allows to:\n\n      1. Get size of the router table::\n\n           len(app.router.resources())\n\n      2. Iterate over registered resources::\n\n           for resource in app.router.resources():\n               print(resource)\n\n      3. Make a check if the resources is registered in the router table::\n\n           route in app.router.resources()\n\n   .. method:: routes()\n\n      The method returns a *view* for *all* registered routes.\n\n   .. method:: named_resources()\n\n      Returns a :obj:`dict`-like :class:`types.MappingProxyType` *view* over\n      *all* named **resources**.\n\n      The view maps every named resource's **name** to the\n      :class:`AbstractResource` instance. It supports the usual\n      :obj:`dict`-like operations, except for any mutable operations\n      (i.e. it's **read-only**)::\n\n          len(app.router.named_resources())\n\n          for name, resource in app.router.named_resources().items():\n              print(name, resource)\n\n          \"name\" in app.router.named_resources()\n\n          app.router.named_resources()[\"name\"]\n\n\n.. _aiohttp-web-resource:\n\nResource\n^^^^^^^^\n\nDefault router :class:`UrlDispatcher` operates with :term:`resource`\\s.\n\nResource is an item in *routing table* which has a *path*, an optional\nunique *name* and at least one :term:`route`.\n\n:term:`web-handler` lookup is performed in the following way:\n\n1. The router splits the URL and checks the index from longest to shortest.\n   For example, '/one/two/three' will first check the index for\n   '/one/two/three', then '/one/two' and finally '/'.\n2. If the URL part is found in the index, the list of routes for\n   that URL part is iterated over. If a route matches to requested HTTP\n   method (or ``'*'`` wildcard) the route's handler is used as the chosen\n   :term:`web-handler`. The lookup is finished.\n3. If the route is not found in the index, the router tries to find\n   the route in the list of :class:`~aiohttp.web.MatchedSubAppResource`,\n   (current only created from :meth:`~aiohttp.web.Application.add_domain`),\n   and will iterate over the list of\n   :class:`~aiohttp.web.MatchedSubAppResource` in a linear fashion\n   until a match is found.\n4. If no *resource* / *route* pair was found, the *router*\n   returns the special :class:`~aiohttp.abc.AbstractMatchInfo`\n   instance with :attr:`aiohttp.abc.AbstractMatchInfo.http_exception` is not ``None``\n   but :exc:`HTTPException` with  either *HTTP 404 Not Found* or\n   *HTTP 405 Method Not Allowed* status code.\n   Registered :meth:`~aiohttp.abc.AbstractMatchInfo.handler` raises this exception on call.\n\nFixed paths are preferred over variable paths. For example,\nif you have two routes ``/a/b`` and ``/a/{name}``, then the first\nroute will always be preferred over the second one.\n\nIf there are multiple dynamic paths with the same fixed prefix,\nthey will be resolved in order of registration.\n\nFor example, if you have two dynamic routes that are prefixed\nwith the fixed ``/users`` path such as ``/users/{x}/{y}/z`` and\n``/users/{x}/y/z``, the first one will be preferred over the\nsecond one.\n\nUser should never instantiate resource classes but give it by\n:meth:`UrlDispatcher.add_resource` call.\n\nAfter that he may add a :term:`route` by calling :meth:`Resource.add_route`.\n\n:meth:`UrlDispatcher.add_route` is just shortcut for::\n\n   router.add_resource(path).add_route(method, handler)\n\nResource with a *name* is called *named resource*.\nThe main purpose of *named resource* is constructing URL by route name for\npassing it into *template engine* for example::\n\n   url = app.router['resource_name'].url_for().with_query({'a': 1, 'b': 2})\n\nResource classes hierarchy::\n\n   AbstractResource\n     Resource\n       PlainResource\n       DynamicResource\n     PrefixResource\n       StaticResource\n       PrefixedSubAppResource\n          MatchedSubAppResource\n\n\n.. class:: AbstractResource\n   :canonical: aiohttp.web_urldispatcher.AbstractResource\n\n   A base class for all resources.\n\n   Inherited from :class:`collections.abc.Sized` and\n   :class:`collections.abc.Iterable`.\n\n   ``len(resource)`` returns amount of :term:`route`\\s belongs to the resource,\n   ``for route in resource`` allows to iterate over these routes.\n\n   .. attribute:: name\n\n      Read-only *name* of resource or ``None``.\n\n   .. attribute:: canonical\n\n      Read-only *canonical path* associate with the resource. For example\n      ``/path/to`` or ``/path/{to}``\n\n      .. versionadded:: 3.3\n\n   .. method:: resolve(request)\n      :async:\n\n      Resolve resource by finding appropriate :term:`web-handler` for\n      ``(method, path)`` combination.\n\n      :return: (*match_info*, *allowed_methods*) pair.\n\n               *allowed_methods* is a :class:`set` or HTTP methods accepted by\n               resource.\n\n               *match_info* is either :class:`UrlMappingMatchInfo` if\n               request is resolved or ``None`` if no :term:`route` is\n               found.\n\n   .. method:: get_info()\n\n      A resource description, e.g. ``{'path': '/path/to'}`` or\n      ``{'formatter': '/path/{to}', 'pattern':\n      re.compile(r'^/path/(?P<to>[a-zA-Z][_a-zA-Z0-9]+)$``\n\n   .. method:: url_for(*args, **kwargs)\n\n      Construct an URL for route with additional params.\n\n      *args* and **kwargs** depend on a parameters list accepted by\n      inherited resource class.\n\n      :return: :class:`~yarl.URL` -- resulting URL instance.\n\n\n.. class:: Resource\n   :canonical: aiohttp.web_urldispatcher.Resource\n\n   A base class for new-style resources, inherits :class:`AbstractResource`.\n\n\n   .. method:: add_route(method, handler, *, expect_handler=None)\n\n      Add a :term:`web-handler` to resource.\n\n      :param str method: HTTP method for route. Should be one of\n                         ``'GET'``, ``'POST'``, ``'PUT'``,\n                         ``'DELETE'``, ``'PATCH'``, ``'HEAD'``,\n                         ``'OPTIONS'`` or ``'*'`` for any method.\n\n                         The parameter is case-insensitive, e.g. you\n                         can push ``'get'`` as well as ``'GET'``.\n\n                         The method should be unique for resource.\n\n      :param collections.abc.Callable handler: route handler.\n\n      :param collections.abc.Coroutine expect_handler: optional *expect* header handler.\n\n      :returns: new :class:`ResourceRoute` instance.\n\n\n.. class:: PlainResource\n   :canonical: aiohttp.web_urldispatcher.PlainResource\n\n   A resource, inherited from :class:`Resource`.\n\n   The class corresponds to resources with plain-text matching,\n   ``'/path/to'`` for example.\n\n   .. attribute:: canonical\n\n      Read-only *canonical path* associate with the resource. Returns the path\n      used to create the PlainResource. For example ``/path/to``\n\n      .. versionadded:: 3.3\n\n   .. method:: url_for()\n\n      Returns a :class:`~yarl.URL` for the resource.\n\n\n.. class:: DynamicResource\n   :canonical: aiohttp.web_urldispatcher.DynamicResource\n\n   A resource, inherited from :class:`Resource`.\n\n   The class corresponds to resources with\n   :ref:`variable <aiohttp-web-variable-handler>` matching,\n   e.g. ``'/path/{to}/{param}'`` etc.\n\n   .. attribute:: canonical\n\n      Read-only *canonical path* associate with the resource. Returns the\n      formatter obtained from the path used to create the DynamicResource.\n      For example, from a path ``/get/{num:^\\d+}``, it returns ``/get/{num}``\n\n      .. versionadded:: 3.3\n\n   .. method:: url_for(**params)\n\n      Returns a :class:`~yarl.URL` for the resource.\n\n      :param params: -- a variable substitutions for dynamic resource.\n\n         E.g. for ``'/path/{to}/{param}'`` pattern the method should\n         be called as ``resource.url_for(to='val1', param='val2')``\n\n\n.. class:: StaticResource\n   :canonical: aiohttp.web_urldispatcher.StaticResource\n\n   A resource, inherited from :class:`Resource`.\n\n   The class corresponds to resources for :ref:`static file serving\n   <aiohttp-web-static-file-handling>`.\n\n   .. attribute:: canonical\n\n      Read-only *canonical path* associate with the resource. Returns the prefix\n      used to create the StaticResource. For example ``/prefix``\n\n      .. versionadded:: 3.3\n\n   .. method:: url_for(filename, append_version=None)\n\n      Returns a :class:`~yarl.URL` for file path under resource prefix.\n\n      :param filename: -- a file name substitution for static file handler.\n\n         Accepts both :class:`str` and :class:`pathlib.Path`.\n\n         E.g. an URL for ``'/prefix/dir/file.txt'`` should\n         be generated as ``resource.url_for(filename='dir/file.txt')``\n\n      :param bool append_version: -- a flag for adding file version\n                                  (hash) to the url query string for\n                                  cache boosting\n\n         By default has value from a constructor (``False`` by default)\n         When set to ``True`` - ``v=FILE_HASH`` query string param will be added\n         When set to ``False`` has no impact\n\n         if file not found has no impact\n\n\n.. class:: PrefixedSubAppResource\n   :canonical: aiohttp.web_urldispatcher.PrefixedSubAppResource\n\n   A resource for serving nested applications. The class instance is\n   returned by :class:`~aiohttp.web.Application.add_subapp` call.\n\n   .. attribute:: canonical\n\n      Read-only *canonical path* associate with the resource. Returns the\n      prefix used to create the PrefixedSubAppResource.\n      For example ``/prefix``\n\n      .. versionadded:: 3.3\n\n   .. method:: url_for(**kwargs)\n\n      The call is not allowed, it raises :exc:`RuntimeError`.\n\n\n.. _aiohttp-web-route:\n\nRoute\n^^^^^\n\nRoute has *HTTP method* (wildcard ``'*'`` is an option),\n:term:`web-handler` and optional *expect handler*.\n\nEvery route belong to some resource.\n\nRoute classes hierarchy::\n\n   AbstractRoute\n     ResourceRoute\n     SystemRoute\n\n:class:`ResourceRoute` is the route used for resources,\n:class:`SystemRoute` serves URL resolving errors like *404 Not Found*\nand *405 Method Not Allowed*.\n\n.. class:: AbstractRoute\n   :canonical: aiohttp.web_urldispatcher.AbstractRoute\n\n   Base class for routes served by :class:`UrlDispatcher`.\n\n   .. attribute:: method\n\n      HTTP method handled by the route, e.g. *GET*, *POST* etc.\n\n   .. attribute:: handler\n\n      :ref:`handler<aiohttp-web-handler>` that processes the route.\n\n   .. attribute:: name\n\n      Name of the route, always equals to name of resource which owns the route.\n\n   .. attribute:: resource\n\n      Resource instance which holds the route, ``None`` for\n      :class:`SystemRoute`.\n\n   .. method:: url_for(*args, **kwargs)\n\n      Abstract method for constructing url handled by the route.\n\n      Actually it's a shortcut for ``route.resource.url_for(...)``.\n\n   .. method:: handle_expect_header(request)\n      :async:\n\n      ``100-continue`` handler.\n\n.. class:: ResourceRoute\n   :canonical: aiohttp.web_urldispatcher.ResourceRoute\n\n   The route class for handling different HTTP methods for :class:`Resource`.\n\n\n.. class:: SystemRoute\n   :canonical: aiohttp.web_urldispatcher.SystemRoute\n\n   The route class for handling URL resolution errors like like *404 Not Found*\n   and *405 Method Not Allowed*.\n\n   .. attribute:: status\n\n      HTTP status code\n\n   .. attribute:: reason\n\n      HTTP status reason\n\n\n.. _aiohttp-web-route-def:\n\n\nRouteDef and StaticDef\n^^^^^^^^^^^^^^^^^^^^^^\n\nRoute definition, a description for not registered yet route.\n\nCould be used for filing route table by providing a list of route\ndefinitions (Django style).\n\nThe definition is created by functions like :func:`get` or\n:func:`post`, list of definitions could be added to router by\n:meth:`UrlDispatcher.add_routes` call::\n\n   from aiohttp import web\n\n   async def handle_get(request):\n       ...\n\n\n   async def handle_post(request):\n       ...\n\n   app.router.add_routes([web.get('/get', handle_get),\n                          web.post('/post', handle_post),\n\n.. class:: AbstractRouteDef\n   :canonical: aiohttp.web_routedef.AbstractRouteDef\n\n   A base class for route definitions.\n\n   Inherited from :class:`abc.ABC`.\n\n   .. versionadded:: 3.1\n\n   .. method:: register(router)\n\n      Register itself into :class:`UrlDispatcher`.\n\n      Abstract method, should be overridden by subclasses.\n\n      :returns: :class:`list` of registered :class:`AbstractRoute` objects.\n\n      .. versionchanged:: 3.7\n\n         Return value updated from ``None`` to :class:`list` of\n         :class:`AbstractRoute` instances.\n\n\n.. class:: RouteDef\n   :canonical: aiohttp.web_routedef.RouteDef\n\n   A definition of not registered yet route.\n\n   Implements :class:`AbstractRouteDef`.\n\n   .. versionadded:: 2.3\n\n   .. versionchanged:: 3.1\n\n      The class implements :class:`AbstractRouteDef` interface.\n\n   .. attribute:: method\n\n      HTTP method (``GET``, ``POST`` etc.)  (:class:`str`).\n\n   .. attribute:: path\n\n      Path to resource, e.g. ``/path/to``. Could contain ``{}``\n      brackets for :ref:`variable resources\n      <aiohttp-web-variable-handler>` (:class:`str`).\n\n   .. attribute:: handler\n\n      An async function to handle HTTP request.\n\n   .. attribute:: kwargs\n\n      A :class:`dict` of additional arguments.\n\n\n.. class:: StaticDef\n   :canonical: aiohttp.web_routedef.StaticDef\n\n   A definition of static file resource.\n\n   Implements :class:`AbstractRouteDef`.\n\n   .. versionadded:: 3.1\n\n   .. attribute:: prefix\n\n      A prefix used for static file handling, e.g. ``/static``.\n\n   .. attribute:: path\n\n      File system directory to serve, :class:`str` or\n      :class:`pathlib.Path`\n      (e.g. ``'/home/web-service/path/to/static'``.\n\n   .. attribute:: kwargs\n\n      A :class:`dict` of additional arguments, see\n      :meth:`UrlDispatcher.add_static` for a list of supported\n      options.\n\n\n.. function:: get(path, handler, *, name=None, allow_head=True, \\\n              expect_handler=None)\n   :canonical: aiohttp.web_routedef.get\n\n   Return :class:`RouteDef` for processing ``GET`` requests. See\n   :meth:`UrlDispatcher.add_get` for information about parameters.\n\n   .. versionadded:: 2.3\n\n.. function:: post(path, handler, *, name=None, expect_handler=None)\n   :canonical: aiohttp.web_routedef.post\n\n   Return :class:`RouteDef` for processing ``POST`` requests. See\n   :meth:`UrlDispatcher.add_post` for information about parameters.\n\n   .. versionadded:: 2.3\n\n.. function:: head(path, handler, *, name=None, expect_handler=None)\n   :canonical: aiohttp.web_routedef.head\n\n   Return :class:`RouteDef` for processing ``HEAD`` requests. See\n   :meth:`UrlDispatcher.add_head` for information about parameters.\n\n   .. versionadded:: 2.3\n\n.. function:: put(path, handler, *, name=None, expect_handler=None)\n   :canonical: aiohttp.web_routedef.put\n\n   Return :class:`RouteDef` for processing ``PUT`` requests. See\n   :meth:`UrlDispatcher.add_put` for information about parameters.\n\n   .. versionadded:: 2.3\n\n.. function:: patch(path, handler, *, name=None, expect_handler=None)\n   :canonical: aiohttp.web_routedef.patch\n\n   Return :class:`RouteDef` for processing ``PATCH`` requests. See\n   :meth:`UrlDispatcher.add_patch` for information about parameters.\n\n   .. versionadded:: 2.3\n\n.. function:: delete(path, handler, *, name=None, expect_handler=None)\n   :canonical: aiohttp.web_routedef.delete\n\n   Return :class:`RouteDef` for processing ``DELETE`` requests. See\n   :meth:`UrlDispatcher.add_delete` for information about parameters.\n\n   .. versionadded:: 2.3\n\n.. function:: view(path, handler, *, name=None, expect_handler=None)\n   :canonical: aiohttp.web_routedef.view\n\n   Return :class:`RouteDef` for processing ``ANY`` requests. See\n   :meth:`UrlDispatcher.add_view` for information about parameters.\n\n   .. versionadded:: 3.0\n\n.. function:: static(prefix, path, *, name=None, expect_handler=None, \\\n                     chunk_size=256*1024, \\\n                     show_index=False, follow_symlinks=False, \\\n                     append_version=False)\n   :canonical: aiohttp.web_routedef.static\n\n   Return :class:`StaticDef` for processing static files.\n\n   See :meth:`UrlDispatcher.add_static` for information\n   about supported parameters.\n\n   .. versionadded:: 3.1\n\n.. function:: route(method, path, handler, *, name=None, expect_handler=None)\n   :canonical: aiohttp.web_routedef.route\n\n   Return :class:`RouteDef` for processing requests that decided by\n   ``method``. See :meth:`UrlDispatcher.add_route` for information\n   about parameters.\n\n   .. versionadded:: 2.3\n\n\n.. _aiohttp-web-route-table-def:\n\nRouteTableDef\n^^^^^^^^^^^^^\n\nA routes table definition used for describing routes by decorators\n(Flask style)::\n\n   from aiohttp import web\n\n   routes = web.RouteTableDef()\n\n   @routes.get('/get')\n   async def handle_get(request):\n       ...\n\n\n   @routes.post('/post')\n   async def handle_post(request):\n       ...\n\n   app.router.add_routes(routes)\n\n\n   @routes.view(\"/view\")\n   class MyView(web.View):\n       async def get(self):\n           ...\n\n       async def post(self):\n           ...\n\n.. class:: RouteTableDef()\n   :canonical: aiohttp.web_routedef.RouteTableDef\n\n   A sequence of :class:`RouteDef` instances (implements\n   :class:`collections.abc.Sequence` protocol).\n\n   In addition to all standard :class:`list` methods the class\n   provides also methods like ``get()`` and ``post()`` for adding new\n   route definition.\n\n   .. versionadded:: 2.3\n\n   .. decoratormethod:: get(path, *, allow_head=True, \\\n                            name=None, expect_handler=None)\n\n      Add a new :class:`RouteDef` item for registering ``GET`` web-handler.\n\n      See :meth:`UrlDispatcher.add_get` for information about parameters.\n\n   .. decoratormethod:: post(path, *, name=None, expect_handler=None)\n\n      Add a new :class:`RouteDef` item for registering ``POST`` web-handler.\n\n      See :meth:`UrlDispatcher.add_post` for information about parameters.\n\n   .. decoratormethod:: head(path, *, name=None, expect_handler=None)\n\n      Add a new :class:`RouteDef` item for registering ``HEAD`` web-handler.\n\n      See :meth:`UrlDispatcher.add_head` for information about parameters.\n\n   .. decoratormethod:: put(path, *, name=None, expect_handler=None)\n\n      Add a new :class:`RouteDef` item for registering ``PUT`` web-handler.\n\n      See :meth:`UrlDispatcher.add_put` for information about parameters.\n\n   .. decoratormethod:: patch(path, *, name=None, expect_handler=None)\n\n      Add a new :class:`RouteDef` item for registering ``PATCH`` web-handler.\n\n      See :meth:`UrlDispatcher.add_patch` for information about parameters.\n\n   .. decoratormethod:: delete(path, *, name=None, expect_handler=None)\n\n      Add a new :class:`RouteDef` item for registering ``DELETE`` web-handler.\n\n      See :meth:`UrlDispatcher.add_delete` for information about parameters.\n\n   .. decoratormethod:: view(path, *, name=None, expect_handler=None)\n\n      Add a new :class:`RouteDef` item for registering ``ANY`` methods\n      against a class-based view.\n\n      See :meth:`UrlDispatcher.add_view` for information about parameters.\n\n      .. versionadded:: 3.0\n\n   .. method:: static(prefix, path, *, name=None, expect_handler=None, \\\n                      chunk_size=256*1024, \\\n                      show_index=False, follow_symlinks=False, \\\n                      append_version=False)\n\n\n      Add a new :class:`StaticDef` item for registering static files processor.\n\n      See :meth:`UrlDispatcher.add_static` for information about\n      supported parameters.\n\n      .. versionadded:: 3.1\n\n   .. decoratormethod:: route(method, path, *, name=None, expect_handler=None)\n\n      Add a new :class:`RouteDef` item for registering a web-handler\n      for arbitrary HTTP method.\n\n      See :meth:`UrlDispatcher.add_route` for information about parameters.\n\n\nMatchInfo\n^^^^^^^^^\n\nAfter route matching web application calls found handler if any.\n\nMatching result can be accessible from handler as\n:attr:`Request.match_info` attribute.\n\nIn general the result may be any object derived from\n:class:`~aiohttp.abc.AbstractMatchInfo` (:class:`UrlMappingMatchInfo` for default\n:class:`UrlDispatcher` router).\n\n.. class:: UrlMappingMatchInfo\n   :canonical: aiohttp.web_urldispatcher.UrlMappingMatchInfo\n\n   Inherited from :class:`dict` and :class:`~aiohttp.abc.AbstractMatchInfo`. Dict\n   items are filled by matching info and is :term:`resource`\\-specific.\n\n   .. attribute:: expect_handler\n\n      A coroutine for handling ``100-continue``.\n\n   .. attribute:: handler\n\n      A coroutine for handling request.\n\n   .. attribute:: route\n\n      :class:`AbstractRoute` instance for url matching.\n\n\nView\n^^^^\n\n.. class:: View(request)\n   :canonical: aiohttp.web_urldispatcher.View\n\n   Inherited from :class:`~aiohttp.abc.AbstractView`.\n\n   Base class for class based views. Implementations should derive from\n   :class:`View` and override methods for handling HTTP verbs like\n   ``get()`` or ``post()``::\n\n       class MyView(View):\n\n           async def get(self):\n               resp = await get_response(self.request)\n               return resp\n\n           async def post(self):\n               resp = await post_response(self.request)\n               return resp\n\n       app.router.add_view('/view', MyView)\n\n   The view raises *405 Method Not allowed*\n   (:class:`HTTPMethodNotAllowed`) if requested web verb is not\n   supported.\n\n   :param request: instance of :class:`Request` that has initiated a view\n                   processing.\n\n\n   .. attribute:: request\n\n      Request sent to view's constructor, read-only property.\n\n\n   Overridable coroutine methods: ``connect()``, ``delete()``,\n   ``get()``, ``head()``, ``options()``, ``patch()``, ``post()``,\n   ``put()``, ``trace()``.\n\n.. seealso:: :ref:`aiohttp-web-class-based-views`\n\n\n.. _aiohttp-web-app-runners-reference:\n\nRunning Applications\n--------------------\n\nTo start web application there is ``AppRunner`` and site classes.\n\nRunner is a storage for running application, sites are for running\napplication on specific TCP or Unix socket, e.g.::\n\n    runner = web.AppRunner(app)\n    await runner.setup()\n    site = web.TCPSite(runner, 'localhost', 8080)\n    await site.start()\n    # wait for finish signal\n    await runner.cleanup()\n\n\n.. versionadded:: 3.0\n\n   :class:`AppRunner` / :class:`ServerRunner` and :class:`TCPSite` /\n   :class:`UnixSite` / :class:`SockSite` are added in aiohttp 3.0\n\n\n.. class:: BaseRunner\n   :canonical: aiohttp.web_runner.BaseRunner\n\n   A base class for runners. Use :class:`AppRunner` for serving\n   :class:`Application`, :class:`ServerRunner` for low-level\n   :class:`Server`.\n\n   .. attribute:: server\n\n      Low-level web :class:`Server` for handling HTTP requests,\n      read-only attribute.\n\n   .. attribute:: addresses\n\n      A  :class:`list` of served sockets addresses.\n\n      See :meth:`socket.getsockname() <socket.socket.getsockname>` for items type.\n\n      .. versionadded:: 3.3\n\n   .. attribute:: sites\n\n      A read-only :class:`set` of served sites (:class:`TCPSite` /\n      :class:`UnixSite` / :class:`NamedPipeSite` / :class:`SockSite` instances).\n\n   .. method:: setup()\n      :async:\n\n      Initialize the server. Should be called before adding sites.\n\n   .. method:: cleanup()\n      :async:\n\n      Stop handling all registered sites and cleanup used resources.\n\n\n.. class:: AppRunner(app, *, handle_signals=False, **kwargs)\n   :canonical: aiohttp.web_runner.AppRunner\n\n   A runner for :class:`Application`. Used with conjunction with sites\n   to serve on specific port.\n\n   Inherited from :class:`BaseRunner`.\n\n   :param Application app: web application instance to serve.\n\n   :param bool handle_signals: add signal handlers for\n                               :data:`signal.SIGINT` and\n                               :data:`signal.SIGTERM` (``False`` by\n                               default). These handlers will raise\n                               :exc:`GracefulExit`.\n\n   :param kwargs: named parameters to pass into\n                  web protocol.\n\n   Supported *kwargs*:\n\n   :param bool tcp_keepalive: Enable TCP Keep-Alive. Default: ``True``.\n   :param int keepalive_timeout: Number of seconds before closing Keep-Alive\n        connection. Default: ``3630`` seconds (when deployed behind a reverse proxy\n        it's important for this value to be higher than the proxy's timeout. To avoid\n        race conditions we always want the proxy to close the connection).\n   :param logger: Custom logger object. Default:\n        :data:`aiohttp.log.server_logger`.\n   :param access_log: Custom logging object. Default:\n        :data:`aiohttp.log.access_logger`.\n   :param access_log_class: Class for `access_logger`. Default:\n        :data:`aiohttp.helpers.AccessLogger`.\n        Must to be a subclass of :class:`aiohttp.abc.AbstractAccessLogger`.\n   :param str access_log_format: Access log format string. Default:\n        :attr:`helpers.AccessLogger.LOG_FORMAT`.\n   :param int max_line_size: Optional maximum header line size. Default:\n        ``8190``.\n   :param int max_field_size: Optional maximum header combined name and value size. Default:\n        ``8190``.\n   :param int max_headers: Optional maximum number of headers and trailers combined. Default:\n        ``128``.\n\n   :param float lingering_time: Maximum time during which the server\n        reads and ignores additional data coming from the client when\n        lingering close is on.  Use ``0`` to disable lingering on\n        server channel closing.\n   :param int read_bufsize: Size of the read buffer (:attr:`BaseRequest.content`).\n                            ``None`` by default,\n                            it means that the session global value is used.\n\n      .. versionadded:: 3.7\n   :param bool auto_decompress: Automatically decompress request body,\n      ``True`` by default.\n\n      .. versionadded:: 3.8\n\n\n\n   .. attribute:: app\n\n      Read-only attribute for accessing to :class:`Application` served\n      instance.\n\n   .. method:: setup()\n      :async:\n\n      Initialize application. Should be called before adding sites.\n\n      The method calls :attr:`Application.on_startup` registered signals.\n\n   .. method:: cleanup()\n      :async:\n\n      Stop handling all registered sites and cleanup used resources.\n\n      :attr:`Application.on_shutdown` and\n      :attr:`Application.on_cleanup` signals are called internally.\n\n\n.. class:: ServerRunner(web_server, *, handle_signals=False, **kwargs)\n   :canonical: aiohttp.web_runner.ServerRunner\n\n   A runner for low-level :class:`Server`. Used with conjunction with sites\n   to serve on specific port.\n\n   Inherited from :class:`BaseRunner`.\n\n   :param Server web_server: low-level web server instance to serve.\n\n   :param bool handle_signals: add signal handlers for\n                               :data:`signal.SIGINT` and\n                               :data:`signal.SIGTERM` (``False`` by\n                               default). These handlers will raise\n                               :exc:`GracefulExit`.\n\n   :param kwargs: named parameters to pass into\n                  web protocol.\n\n   .. seealso::\n\n      :ref:`aiohttp-web-lowlevel` demonstrates low-level server usage\n\n.. class:: BaseSite\n   :canonical: aiohttp.web_runner.BaseSite\n\n   An abstract class for handled sites.\n\n   .. attribute:: name\n\n      An identifier for site, read-only :class:`str` property. Could\n      be a handled URL or UNIX socket path.\n\n   .. method:: start()\n      :async:\n\n      Start handling a site.\n\n   .. method:: stop()\n      :async:\n\n      Stop handling a site.\n\n\n.. class:: TCPSite(runner, host=None, port=None, *, \\\n                   shutdown_timeout=60.0, ssl_context=None, \\\n                   backlog=128, reuse_address=None, \\\n                   reuse_port=None)\n   :canonical: aiohttp.web_runner.TCPSite\n\n   Serve a runner on TCP socket.\n\n   :param runner: a runner to serve.\n\n   :param str host: HOST to listen on, all interfaces if ``None`` (default).\n\n   :param int port: PORT to listen on, ``8080`` if ``None`` (default).\n                    Use ``0`` to let the OS assign a free ephemeral port\n                    (see :attr:`port`).\n\n   :param float shutdown_timeout: a timeout used for both waiting on pending\n                                  tasks before application shutdown and for\n                                  closing opened connections on\n                                  :meth:`BaseSite.stop` call.\n\n   :param ssl_context: a :class:`ssl.SSLContext` instance for serving\n                       SSL/TLS secure server, ``None`` for plain HTTP\n                       server (default).\n\n   :param int backlog: a number of unaccepted connections that the\n                       system will allow before refusing new\n                       connections, see :meth:`socket.socket.listen` for details.\n\n                       ``128`` by default.\n\n   :param bool reuse_address: tells the kernel to reuse a local socket in\n                              TIME_WAIT state, without waiting for its\n                              natural timeout to expire. If not specified\n                              will automatically be set to True on UNIX.\n\n   :param bool reuse_port: tells the kernel to allow this endpoint to be\n                           bound to the same port as other existing\n                           endpoints are bound to, so long as they all set\n                           this flag when being created. This option is not\n                           supported on Windows.\n\n   .. attribute:: port\n\n      Read-only. The actual port number the server is bound to, only\n      guaranteed to be correct after the site has been started.\n\n\n.. class:: UnixSite(runner, path, *, \\\n                   shutdown_timeout=60.0, ssl_context=None, \\\n                   backlog=128)\n   :canonical: aiohttp.web_runner.UnixSite\n\n   Serve a runner on UNIX socket.\n\n   :param runner: a runner to serve.\n\n   :param str path: PATH to UNIX socket to listen.\n\n   :param float shutdown_timeout: a timeout used for both waiting on pending\n                                  tasks before application shutdown and for\n                                  closing opened connections on\n                                  :meth:`BaseSite.stop` call.\n\n   :param ssl_context: a :class:`ssl.SSLContext` instance for serving\n                       SSL/TLS secure server, ``None`` for plain HTTP\n                       server (default).\n\n   :param int backlog: a number of unaccepted connections that the\n                       system will allow before refusing new\n                       connections, see :meth:`socket.socket.listen` for details.\n\n                       ``128`` by default.\n\n.. class:: NamedPipeSite(runner, path, *, shutdown_timeout=60.0)\n   :canonical: aiohttp.web_runner.NamedPipeSite\n\n   Serve a runner on Named Pipe in Windows.\n\n   :param runner: a runner to serve.\n\n   :param str path: PATH of named pipe to listen.\n\n   :param float shutdown_timeout: a timeout used for both waiting on pending\n                                  tasks before application shutdown and for\n                                  closing opened connections on\n                                  :meth:`BaseSite.stop` call.\n\n.. class:: SockSite(runner, sock, *, \\\n                   shutdown_timeout=60.0, ssl_context=None, \\\n                   backlog=128)\n   :canonical: aiohttp.web_runner.SockSite\n\n   Serve a runner on UNIX socket.\n\n   :param runner: a runner to serve.\n\n   :param sock: A :ref:`socket instance <socket-objects>` to listen to.\n\n   :param float shutdown_timeout: a timeout used for both waiting on pending\n                                  tasks before application shutdown and for\n                                  closing opened connections on\n                                  :meth:`BaseSite.stop` call.\n\n   :param ssl_context: a :class:`ssl.SSLContext` instance for serving\n                       SSL/TLS secure server, ``None`` for plain HTTP\n                       server (default).\n\n   :param int backlog: a number of unaccepted connections that the\n                       system will allow before refusing new\n                       connections, see :meth:`socket.socket.listen` for details.\n\n                       ``128`` by default.\n\n.. exception:: GracefulExit\n   :canonical: aiohttp.web_runner.GracefulExit\n\n   Raised by signal handlers for :data:`signal.SIGINT` and :data:`signal.SIGTERM`\n   defined in :class:`AppRunner` and :class:`ServerRunner`\n   when ``handle_signals`` is set to ``True``.\n\n   Inherited from :exc:`SystemExit`,\n   which exits with error code ``1`` if not handled.\n\n\nUtilities\n---------\n\n.. class:: FileField\n   :canonical: aiohttp.web_request.FileField\n\n   A :mod:`dataclass <dataclasses>` instance that is returned as\n   multidict value by :meth:`aiohttp.web.BaseRequest.post` if field is uploaded file.\n\n   .. attribute:: name\n\n      Field name\n\n   .. attribute:: filename\n\n      File name as specified by uploading (client) side.\n\n   .. attribute:: file\n\n      An :class:`io.IOBase` instance with content of uploaded file.\n\n   .. attribute:: content_type\n\n      *MIME type* of uploaded file, ``'text/plain'`` by default.\n\n   .. seealso:: :ref:`aiohttp-web-file-upload`\n\n\n.. function:: run_app(app, *, debug=False, host=None, port=None, \\\n                      path=None, sock=None, shutdown_timeout=60.0, \\\n                      keepalive_timeout=3630, ssl_context=None, \\\n                      print=print, backlog=128, \\\n                      access_log_class=aiohttp.helpers.AccessLogger, \\\n                      access_log_format=aiohttp.helpers.AccessLogger.LOG_FORMAT, \\\n                      access_log=aiohttp.log.access_logger, \\\n                      handle_signals=True, \\\n                      reuse_address=None, \\\n                      reuse_port=None, \\\n                      handler_cancellation=False, \\\n\t\t\t\t\t  **kwargs)\n\n   A high-level function for running an application, serving it until\n   keyboard interrupt and performing a\n   :ref:`aiohttp-web-graceful-shutdown`.\n\n   This is a high-level function very similar to :func:`asyncio.run` and\n   should be used as the main entry point for an application. The\n   :class:`Application` object essentially becomes our `main()` function.\n   If additional tasks need to be run in parallel, see\n   :ref:`aiohttp-web-complex-applications`.\n\n   The server will listen on any host or Unix domain socket path you supply.\n   If no hosts or paths are supplied, or only a port is supplied, a TCP server\n   listening on 0.0.0.0 (all hosts) will be launched.\n\n   Distributing HTTP traffic to multiple hosts or paths on the same\n   application process provides no performance benefit as the requests are\n   handled on the same event loop. See :doc:`deployment` for ways of\n   distributing work for increased performance.\n\n   :param app: :class:`Application` instance to run or a *coroutine*\n               that returns an application.\n\n   :param bool debug: enable :ref:`asyncio debug mode <asyncio-debug-mode>` if ``True``.\n\n   :param str host: TCP/IP host or a sequence of hosts for HTTP server.\n                    Default is ``'0.0.0.0'`` if *port* has been specified\n                    or if *path* is not supplied.\n\n   :param int port: TCP/IP port for HTTP server. Default is ``8080`` for plain\n                    text HTTP and ``8443`` for HTTP via SSL (when\n                    *ssl_context* parameter is specified).\n\n   :param path: file system path for HTTP server Unix domain socket.\n                    A sequence of file system paths can be used to bind\n                    multiple domain sockets. Listening on Unix domain\n                    sockets is not supported by all operating systems,\n                    :class:`str`, :class:`pathlib.Path` or an iterable of these.\n\n   :param socket.socket sock: a preexisting socket object to accept connections on.\n                       A sequence of socket objects can be passed.\n\n   :param int shutdown_timeout: a delay to wait for graceful server\n                                shutdown before disconnecting all\n                                open client sockets hard way.\n\n                                This is used as a delay to wait for\n                                pending tasks to complete and then\n                                again to close any pending connections.\n\n                                A system with properly\n                                :ref:`aiohttp-web-graceful-shutdown`\n                                implemented never waits for the second\n                                timeout but closes a server in a few\n                                milliseconds.\n\n   :param float keepalive_timeout: a delay before a TCP connection is\n                                   closed after a HTTP request. The delay\n                                   allows for reuse of a TCP connection.\n\n                                   When deployed behind a reverse proxy\n                                   it's important for this value to be\n                                   higher than the proxy's timeout. To avoid\n                                   race conditions, we always want the proxy\n                                   to handle connection closing.\n\n      .. versionadded:: 3.8\n\n   :param ssl_context: :class:`ssl.SSLContext` for HTTPS server,\n                       ``None`` for HTTP connection.\n\n   :param print: a callable compatible with :func:`print`. May be used\n                 to override STDOUT output or suppress it. Passing `None`\n                 disables output.\n\n   :param int backlog: the number of unaccepted connections that the\n                       system will allow before refusing new\n                       connections (``128`` by default).\n\n   :param access_log_class: class for `access_logger`. Default:\n                            :data:`aiohttp.helpers.AccessLogger`.\n                            Must to be a subclass of :class:`aiohttp.abc.AbstractAccessLogger`.\n\n   :param access_log: :class:`logging.Logger` instance used for saving\n                      access logs. Use ``None`` for disabling logs for\n                      sake of speedup.\n\n   :param access_log_format: access log format, see\n                             :ref:`aiohttp-logging-access-log-format-spec`\n                             for details.\n\n   :param bool handle_signals: override signal TERM handling to gracefully\n                               exit the application.\n\n   :param bool reuse_address: tells the kernel to reuse a local socket in\n                              TIME_WAIT state, without waiting for its\n                              natural timeout to expire. If not specified\n                              will automatically be set to True on UNIX.\n\n   :param bool reuse_port: tells the kernel to allow this endpoint to be\n                           bound to the same port as other existing\n                           endpoints are bound to, so long as they all set\n                           this flag when being created. This option is not\n                           supported on Windows.\n\n   :param bool handler_cancellation: cancels the web handler task if the client\n                                     drops the connection. This is recommended\n                                     if familiar with asyncio behavior or\n                                     scalability is a concern.\n                                     :ref:`aiohttp-web-peer-disconnection`\n\n   :param kwargs: additional named parameters to pass into\n                  :class:`AppRunner` constructor.\n\n   .. versionadded:: 3.0\n\n      Support *access_log_class* parameter.\n\n      Support *reuse_address*, *reuse_port* parameter.\n\n   .. versionadded:: 3.1\n\n      Accept a coroutine as *app* parameter.\n\n   .. versionadded:: 3.9\n\n      Support handler_cancellation parameter (this was the default behavior\n      in aiohttp <3.7).\n\nConstants\n---------\n\n.. class:: ContentCoding\n   :canonical: aiohttp.web_response.ContentCoding\n\n   An :class:`enum.Enum` class of available Content Codings.\n\n   .. attribute:: deflate\n\n      *DEFLATE compression*\n\n   .. attribute:: gzip\n\n      *GZIP compression*\n\n   .. attribute:: identity\n\n      *no compression*\n\n\nMiddlewares\n-----------\n\n.. function:: normalize_path_middleware(*, \\\n                                        append_slash=True, \\\n                                        remove_slash=False, \\\n                                        merge_slashes=True, \\\n                                        redirect_class=HTTPPermanentRedirect)\n   :canonical: aiohttp.web_middlewares.normalize_path_middleware\n\n   Middleware factory which produces a middleware that normalizes\n   the path of a request. By normalizing it means:\n\n     - Add or remove a trailing slash to the path.\n     - Double slashes are replaced by one.\n\n   The middleware returns as soon as it finds a path that resolves\n   correctly. The order if both merge and append/remove are enabled is:\n\n     1. *merge_slashes*\n     2. *append_slash* or *remove_slash*\n     3. both *merge_slashes* and *append_slash* or *remove_slash*\n\n   If the path resolves with at least one of those conditions, it will\n   redirect to the new path.\n\n   Only one of *append_slash* and *remove_slash* can be enabled. If both are\n   ``True`` the factory will raise an ``AssertionError``\n\n   If *append_slash* is ``True`` the middleware will append a slash when\n   needed. If a resource is defined with trailing slash and the request\n   comes without it, it will append it automatically.\n\n   If *remove_slash* is ``True``, *append_slash* must be ``False``. When enabled\n   the middleware will remove trailing slashes and redirect if the resource is\n   defined.\n\n   If *merge_slashes* is ``True``, merge multiple consecutive slashes in the\n   path into one.\n\n   .. versionadded:: 3.4\n\n      Support for *remove_slash*\n"
  },
  {
    "path": "docs/websocket_utilities.rst",
    "content": ".. currentmodule:: aiohttp\n\n\nWebSocket utilities\n===================\n\n.. class:: WSCloseCode\n   :canonical: aiohttp._websocket.models.WSCloseCode\n\n    An :class:`~enum.IntEnum` for keeping close message code.\n\n    .. attribute:: OK\n\n       A normal closure, meaning that the purpose for\n       which the connection was established has been fulfilled.\n\n    .. attribute:: GOING_AWAY\n\n       An endpoint is \"going away\", such as a server\n       going down or a browser having navigated away from a page.\n\n    .. attribute:: PROTOCOL_ERROR\n\n       An endpoint is terminating the connection due\n       to a protocol error.\n\n    .. attribute:: UNSUPPORTED_DATA\n\n       An endpoint is terminating the connection\n       because it has received a type of data it cannot accept (e.g., an\n       endpoint that understands only text data MAY send this if it\n       receives a binary message).\n\n    .. attribute:: INVALID_TEXT\n\n       An endpoint is terminating the connection\n       because it has received data within a message that was not\n       consistent with the type of the message (e.g., non-UTF-8 :rfc:`3629`\n       data within a text message).\n\n    .. attribute:: POLICY_VIOLATION\n\n       An endpoint is terminating the connection because it has\n       received a message that violates its policy.  This is a generic\n       status code that can be returned when there is no other more\n       suitable status code (e.g.,\n       :attr:`~aiohttp.WSCloseCode.UNSUPPORTED_DATA` or\n       :attr:`~aiohttp.WSCloseCode.MESSAGE_TOO_BIG`) or if there is a need to\n       hide specific details about the policy.\n\n    .. attribute:: MESSAGE_TOO_BIG\n\n       An endpoint is terminating the connection\n       because it has received a message that is too big for it to\n       process.\n\n    .. attribute:: MANDATORY_EXTENSION\n\n       An endpoint (client) is terminating the\n       connection because it has expected the server to negotiate one or\n       more extension, but the server did not return them in the response\n       message of the WebSocket handshake.  The list of extensions that\n       are needed should appear in the /reason/ part of the Close frame.\n       Note that this status code is not used by the server, because it\n       can fail the WebSocket handshake instead.\n\n    .. attribute:: INTERNAL_ERROR\n\n       A server is terminating the connection because\n       it encountered an unexpected condition that prevented it from\n       fulfilling the request.\n\n    .. attribute:: SERVICE_RESTART\n\n       The service is restarted. a client may reconnect, and if it\n       chooses to do, should reconnect using a randomized delay of 5-30s.\n\n    .. attribute:: TRY_AGAIN_LATER\n\n       The service is experiencing overload. A client should only\n       connect to a different IP (when there are multiple for the\n       target) or reconnect to the same IP upon user action.\n\n    .. attribute:: ABNORMAL_CLOSURE\n\n       Used to indicate that a connection was closed abnormally\n       (that is, with no close frame being sent) when a status code\n       is expected.\n\n    .. attribute:: BAD_GATEWAY\n\n       The server was acting as a gateway or proxy and received\n       an invalid response from the upstream server.\n       This is similar to 502 HTTP Status Code.\n\n\n.. class:: WSMsgType\n   :canonical: aiohttp._websocket.models.WSMsgType\n\n   An :class:`~enum.IntEnum` for describing :class:`WSMessage` type.\n\n   .. attribute:: CONTINUATION\n\n      A mark for continuation frame, user will never get the message\n      with this type.\n\n   .. attribute:: TEXT\n\n      Text message, the value has :class:`str` type.\n\n   .. attribute:: BINARY\n\n      Binary message, the value has :class:`bytes` type.\n\n   .. attribute:: PING\n\n      Ping frame (sent by client peer).\n\n   .. attribute:: PONG\n\n      Pong frame, answer on ping. Sent by server peer.\n\n   .. attribute:: CLOSE\n\n      Close frame.\n\n   .. attribute:: CLOSED FRAME\n\n      Actually not frame but a flag indicating that websocket was\n      closed.\n\n   .. attribute:: ERROR\n\n      Actually not frame but a flag indicating that websocket was\n      received an error.\n\n\n.. class:: WSMessage\n\n   Websocket message, returned by ``.receive()`` calls. This is actually defined as a\n   :class:`typing.Union` of different message types. All messages are a\n   :func:`collections.namedtuple` with the below attributes.\n\n   .. attribute:: data\n\n      Message payload.\n\n      1. :class:`str` for :attr:`WSMsgType.TEXT` messages.\n\n      2. :class:`bytes` for :attr:`WSMsgType.BINARY` messages.\n\n      3. :class:`int` (see :class:`WSCloseCode` for common codes)\n         for :attr:`WSMsgType.CLOSE` messages.\n\n      4. :class:`bytes` for :attr:`WSMsgType.PING` messages.\n\n      5. :class:`bytes` for :attr:`WSMsgType.PONG` messages.\n\n      6. :class:`Exception` for :attr:`WSMsgType.ERROR` messages.\n\n   .. attribute:: extra\n\n      Additional info, :class:`str` if provided, otherwise defaults to ``None``.\n\n      Makes sense only for :attr:`WSMsgType.CLOSE` messages, contains\n      optional message description.\n\n   .. attribute:: type\n\n      Message type, :class:`WSMsgType` instance.\n\n   .. method:: json(*, loads=json.loads)\n\n      Returns parsed JSON data (the method is only present on :attr:`WSMsgType.TEXT`\n      and :attr:`WSMsgType.BINARY` messages).\n\n      :param loads: optional JSON decoder function.\n"
  },
  {
    "path": "docs/whats_new_1_1.rst",
    "content": "=========================\nWhat's new in aiohttp 1.1\n=========================\n\n\nYARL and URL encoding\n======================\n\nSince aiohttp 1.1 the library uses :term:`yarl` for URL processing.\n\nNew API\n-------\n\n:class:`yarl.URL` gives handy methods for URL operations etc.\n\nClient API still accepts :class:`str` everywhere *url* is used,\ne.g. ``session.get('http://example.com')`` works as well as\n``session.get(yarl.URL('http://example.com'))``.\n\nInternal API has been switched to :class:`yarl.URL`.\n:class:`aiohttp.CookieJar` accepts :class:`~yarl.URL` instances only.\n\nOn server side has added :attr:`aiohttp.web.BaseRequest.url` and\n:attr:`aiohttp.web.BaseRequest.rel_url` properties for representing relative and\nabsolute request's URL.\n\nURL using is the recommended way, already existed properties for\nretrieving URL parts are deprecated and will be eventually removed.\n\nRedirection web exceptions accepts :class:`yarl.URL` as *location*\nparameter. :class:`str` is still supported and will be supported forever.\n\nReverse URL processing for *router* has been changed.\n\nThe main API is ``aiohttp.web.Request.url_for``\nwhich returns a :class:`yarl.URL` instance for named resource. It\ndoes not support *query args* but adding *args* is trivial:\n``request.url_for('named_resource', param='a').with_query(arg='val')``.\n\nThe method returns a *relative* URL, absolute URL may be constructed by\n``request.url.join(request.url_for(...)`` call.\n\n\nURL encoding\n------------\n\nYARL encodes all non-ASCII symbols on :class:`yarl.URL` creation.\n\nThus ``URL('https://www.python.org/путь')`` becomes\n``'https://www.python.org/%D0%BF%D1%83%D1%82%D1%8C'``.\n\nOn filling route table it's possible to use both non-ASCII and percent\nencoded paths::\n\n   app.router.add_get('/путь', handler)\n\nand::\n\n   app.router.add_get('/%D0%BF%D1%83%D1%82%D1%8C', handler)\n\nare the same. Internally ``'/путь'`` is converted into\npercent-encoding representation.\n\nRoute matching also accepts both URL forms: raw and encoded by\nconverting the route pattern to *canonical* (encoded) form on route\nregistration.\n\n\nSub-Applications\n================\n\nSub applications are designed for solving the problem of the big\nmonolithic code base.\nLet's assume we have a project with own business logic and tools like\nadministration panel and debug toolbar.\n\nAdministration panel is a separate application by its own nature but all\ntoolbar URLs are served by prefix like ``/admin``.\n\nThus we'll create a totally separate application named ``admin`` and\nconnect it to main app with prefix::\n\n   admin = web.Application()\n   # setup admin routes, signals and middlewares\n\n   app.add_subapp('/admin/', admin)\n\nMiddlewares and signals from ``app`` and ``admin`` are chained.\n\nIt means that if URL is ``'/admin/something'`` middlewares from\n``app`` are applied first and ``admin.middlewares`` are the next in\nthe call chain.\n\nThe same is going for\n:attr:`~aiohttp.web.Application.on_response_prepare` signal -- the\nsignal is delivered to both top level ``app`` and ``admin`` if\nprocessing URL is routed to ``admin`` sub-application.\n\nCommon signals like :attr:`~aiohttp.web.Application.on_startup`,\n:attr:`~aiohttp.web.Application.on_shutdown` and\n:attr:`~aiohttp.web.Application.on_cleanup` are delivered to all\nregistered sub-applications. The passed parameter is sub-application\ninstance, not top-level application.\n\n\nThird level sub-applications can be nested into second level ones --\nthere are no limitation for nesting level.\n\n\nUrl reversing\n-------------\n\nUrl reversing for sub-applications should generate urls with proper prefix.\n\nBut for getting URL sub-application's router should be used::\n\n   admin = web.Application()\n   admin.add_get('/resource', handler, name='name')\n\n   app.add_subapp('/admin/', admin)\n\n   url = admin.router['name'].url_for()\n\nThe generated ``url`` from example will have a value\n``URL('/admin/resource')``.\n\nApplication freezing\n====================\n\nApplication can be used either as main app (``app.make_handler()``) or as\nsub-application -- not both cases at the same time.\n\nAfter connecting application by ``.add_subapp()`` call or starting\nserving web-server as toplevel application the application is\n**frozen**.\n\nIt means that registering new routes, signals and middlewares is\nforbidden.  Changing state (``app['name'] = 'value'``) of frozen application is\ndeprecated and will be eventually removed.\n"
  },
  {
    "path": "docs/whats_new_3_0.rst",
    "content": ".. _aiohttp_whats_new_3_0:\n\n=========================\nWhat's new in aiohttp 3.0\n=========================\n\nasync/await everywhere\n======================\n\nThe main change is dropping ``yield from`` support and using\n``async``/``await`` everywhere. Farewell, Python 3.4.\n\nThe minimal supported Python version is **3.5.3** now.\n\nWhy not *3.5.0*?  Because *3.5.3* has a crucial change:\n:func:`asyncio.get_event_loop()` returns the running loop instead of\n*default*, which may be different, e.g.::\n\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(f())\n\nNote, :func:`asyncio.set_event_loop` was not called and default loop\nis not equal to actually executed one.\n\nApplication Runners\n===================\n\nPeople constantly asked about ability to run aiohttp servers together\nwith other asyncio code, but :func:`aiohttp.web.run_app` is blocking\nsynchronous call.\n\naiohttp had support for starting the application without ``run_app`` but the API\nwas very low-level and cumbersome.\n\nNow application runners solve the task in a few lines of code, see\n:ref:`aiohttp-web-app-runners` for details.\n\nClient Tracing\n==============\n\nOther long awaited feature is tracing client request life cycle to\nfigure out when and why client request spends a time waiting for\nconnection establishment, getting server response headers etc.\n\nNow it is possible by registering special signal handlers on every\nrequest processing stage.  :ref:`aiohttp-client-tracing` provides more\ninfo about the feature.\n\nHTTPS support\n=============\n\nUnfortunately asyncio has a bug with checking SSL certificates for\nnon-ASCII site DNS names, e.g. `https://историк.рф <https://историк.рф>`_ or\n`https://雜草工作室.香港 <https://雜草工作室.香港>`_.\n\nThe bug has been fixed in upcoming Python 3.7 only (the change\nrequires breaking backward compatibility in :mod:`ssl` API).\n\naiohttp installs a fix for older Python versions (3.5 and 3.6).\n\n\nDropped obsolete API\n====================\n\nA switch to new major version is a great chance for dropping already\ndeprecated features.\n\nThe release dropped a lot, see :ref:`aiohttp_changes` for details.\n\nAll removals was already marked as deprecated or related to very low\nlevel implementation details.\n\nIf user code did not raise :exc:`DeprecationWarning` it is compatible\nwith aiohttp 3.0 most likely.\n\n\nSummary\n=======\n\nEnjoy aiohttp 3.0 release!\n\nThe full change log is here: :ref:`aiohttp_changes`.\n"
  },
  {
    "path": "examples/background_tasks.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Example of aiohttp.web.Application.on_startup signal handler\"\"\"\n\nimport asyncio\nfrom collections.abc import AsyncIterator\nfrom contextlib import asynccontextmanager, suppress\n\nimport valkey.asyncio as valkey\n\nfrom aiohttp import web\n\nvalkey_listener = web.AppKey(\"valkey_listener\", asyncio.Task[None])\nwebsockets = web.AppKey(\"websockets\", list[web.WebSocketResponse])\n\n\nasync def websocket_handler(request: web.Request) -> web.StreamResponse:\n    ws = web.WebSocketResponse()\n    await ws.prepare(request)\n    request.app[websockets].append(ws)\n    try:\n        async for msg in ws:\n            print(msg)\n            await asyncio.sleep(1)\n    finally:\n        request.app[websockets].remove(ws)\n    return ws\n\n\nasync def on_shutdown(app: web.Application) -> None:\n    for ws in app[websockets]:\n        await ws.close(code=999, message=b\"Server shutdown\")\n\n\nasync def listen_to_valkey(app: web.Application) -> None:\n    r = valkey.Valkey(host=\"localhost\", port=6379, decode_responses=True)\n    channel = \"news\"\n    async with r.pubsub() as sub:\n        await sub.subscribe(channel)\n        async for msg in sub.listen():\n            if msg[\"type\"] != \"message\":\n                continue\n            # Forward message to all connected websockets:\n            for ws in app[websockets]:\n                await ws.send_str(f\"{channel}: {msg}\")\n            print(f\"message in {channel}: {msg}\")\n\n\n@asynccontextmanager\nasync def background_tasks(app: web.Application) -> AsyncIterator[None]:\n    app[valkey_listener] = asyncio.create_task(listen_to_valkey(app))\n\n    yield\n\n    print(\"cleanup background tasks...\")\n    app[valkey_listener].cancel()\n    with suppress(asyncio.CancelledError):\n        await app[valkey_listener]\n\n\ndef init() -> web.Application:\n    app = web.Application()\n    l: list[web.WebSocketResponse] = []\n    app[websockets] = l\n    app.router.add_get(\"/news\", websocket_handler)\n    app.cleanup_ctx.append(background_tasks)\n    app.on_shutdown.append(on_shutdown)\n    return app\n\n\nweb.run_app(init())\n"
  },
  {
    "path": "examples/basic_auth_middleware.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nExample of using basic authentication middleware with aiohttp client.\n\nThis example shows how to implement a middleware that automatically adds\nBasic Authentication headers to all requests. The middleware encodes the\nusername and password in base64 format as required by the HTTP Basic Auth\nspecification.\n\nThis example includes a test server that validates basic auth credentials.\n\"\"\"\n\nimport asyncio\nimport base64\nimport binascii\nimport logging\n\nfrom aiohttp import (\n    ClientHandlerType,\n    ClientRequest,\n    ClientResponse,\n    ClientSession,\n    hdrs,\n    web,\n)\n\nlogging.basicConfig(level=logging.DEBUG)\n_LOGGER = logging.getLogger(__name__)\n\n\nclass BasicAuthMiddleware:\n    \"\"\"Middleware that adds Basic Authentication to all requests.\"\"\"\n\n    def __init__(self, username: str, password: str) -> None:\n        self.username = username\n        self.password = password\n        self._auth_header = self._encode_credentials()\n\n    def _encode_credentials(self) -> str:\n        \"\"\"Encode username and password to base64.\"\"\"\n        credentials = f\"{self.username}:{self.password}\"\n        encoded = base64.b64encode(credentials.encode()).decode()\n        return f\"Basic {encoded}\"\n\n    async def __call__(\n        self,\n        request: ClientRequest,\n        handler: ClientHandlerType,\n    ) -> ClientResponse:\n        \"\"\"Add Basic Auth header to the request.\"\"\"\n        # Only add auth if not already present\n        if hdrs.AUTHORIZATION not in request.headers:\n            request.headers[hdrs.AUTHORIZATION] = self._auth_header\n\n        # Proceed with the request\n        return await handler(request)\n\n\nclass TestServer:\n    \"\"\"Test server for basic auth endpoints.\"\"\"\n\n    async def handle_basic_auth(self, request: web.Request) -> web.Response:\n        \"\"\"Handle basic auth validation.\"\"\"\n        # Get expected credentials from path\n        expected_user = request.match_info[\"user\"]\n        expected_pass = request.match_info[\"pass\"]\n\n        # Check if Authorization header is present\n        auth_header = request.headers.get(hdrs.AUTHORIZATION, \"\")\n\n        if not auth_header.startswith(\"Basic \"):\n            return web.Response(\n                status=401,\n                text=\"Unauthorized\",\n                headers={hdrs.WWW_AUTHENTICATE: 'Basic realm=\"test\"'},\n            )\n\n        # Decode the credentials\n        encoded_creds = auth_header[6:]  # Remove \"Basic \"\n        try:\n            decoded = base64.b64decode(encoded_creds).decode()\n            username, password = decoded.split(\":\", 1)\n        except (ValueError, binascii.Error):\n            return web.Response(\n                status=401,\n                text=\"Invalid credentials format\",\n                headers={hdrs.WWW_AUTHENTICATE: 'Basic realm=\"test\"'},\n            )\n\n        # Validate credentials\n        if username != expected_user or password != expected_pass:\n            return web.Response(\n                status=401,\n                text=\"Invalid username or password\",\n                headers={hdrs.WWW_AUTHENTICATE: 'Basic realm=\"test\"'},\n            )\n\n        return web.json_response({\"authenticated\": True, \"user\": username})\n\n    async def handle_protected_resource(self, request: web.Request) -> web.Response:\n        \"\"\"A protected resource that requires any valid auth.\"\"\"\n        auth_header = request.headers.get(hdrs.AUTHORIZATION, \"\")\n\n        if not auth_header.startswith(\"Basic \"):\n            return web.Response(\n                status=401,\n                text=\"Authentication required\",\n                headers={hdrs.WWW_AUTHENTICATE: 'Basic realm=\"protected\"'},\n            )\n\n        return web.json_response(\n            {\n                \"message\": \"Access granted to protected resource\",\n                \"auth_provided\": True,\n            }\n        )\n\n\nasync def run_test_server() -> web.AppRunner:\n    \"\"\"Run a simple test server with basic auth endpoints.\"\"\"\n    app = web.Application()\n    server = TestServer()\n\n    app.router.add_get(\"/basic-auth/{user}/{pass}\", server.handle_basic_auth)\n    app.router.add_get(\"/protected\", server.handle_protected_resource)\n\n    runner = web.AppRunner(app)\n    await runner.setup()\n    site = web.TCPSite(runner, \"localhost\", 8080)\n    await site.start()\n    return runner\n\n\nasync def run_tests() -> None:\n    \"\"\"Run all basic auth middleware tests.\"\"\"\n    # Create middleware instance\n    auth_middleware = BasicAuthMiddleware(\"user\", \"pass\")\n\n    # Use middleware in session\n    async with ClientSession(middlewares=(auth_middleware,)) as session:\n        # Test 1: Correct credentials endpoint\n        print(\"=== Test 1: Correct credentials ===\")\n        async with session.get(\"http://localhost:8080/basic-auth/user/pass\") as resp:\n            _LOGGER.info(\"Status: %s\", resp.status)\n\n            if resp.status == 200:\n                data = await resp.json()\n                _LOGGER.info(\"Response: %s\", data)\n                print(\"Authentication successful!\")\n                print(f\"Authenticated: {data.get('authenticated')}\")\n                print(f\"User: {data.get('user')}\")\n            else:\n                print(\"Authentication failed!\")\n                print(f\"Status: {resp.status}\")\n                text = await resp.text()\n                print(f\"Response: {text}\")\n\n        # Test 2: Wrong credentials endpoint\n        print(\"\\n=== Test 2: Wrong credentials endpoint ===\")\n        async with session.get(\"http://localhost:8080/basic-auth/other/secret\") as resp:\n            if resp.status == 401:\n                print(\"Authentication failed as expected (wrong credentials)\")\n                text = await resp.text()\n                print(f\"Response: {text}\")\n            else:\n                print(f\"Unexpected status: {resp.status}\")\n\n        # Test 3: Protected resource\n        print(\"\\n=== Test 3: Access protected resource ===\")\n        async with session.get(\"http://localhost:8080/protected\") as resp:\n            if resp.status == 200:\n                data = await resp.json()\n                print(\"Successfully accessed protected resource!\")\n                print(f\"Response: {data}\")\n            else:\n                print(f\"Failed to access protected resource: {resp.status}\")\n\n\nasync def main() -> None:\n    # Start test server\n    server = await run_test_server()\n\n    try:\n        await run_tests()\n    finally:\n        await server.cleanup()\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "examples/cli_app.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nExample of serving an Application using the `aiohttp.web` CLI.\n\nServe this app using::\n\n    $ python -m aiohttp.web -H localhost -P 8080 --repeat 10 cli_app:init \\\n    > \"Hello World\"\n\nHere ``--repeat`` & ``\"Hello World\"`` are application specific command-line\narguments. `aiohttp.web` only parses & consumes the command-line arguments it\nneeds (i.e. ``-H``, ``-P`` & ``entry-func``) and passes on any additional\narguments to the `cli_app:init` function for processing.\n\"\"\"\n\nfrom argparse import ArgumentParser, Namespace\nfrom collections.abc import Sequence\n\nfrom aiohttp import web\n\nargs_key = web.AppKey(\"args_key\", Namespace)\n\n\nasync def display_message(req: web.Request) -> web.StreamResponse:\n    args = req.app[args_key]\n    text = \"\\n\".join([args.message] * args.repeat)\n    return web.Response(text=text)\n\n\ndef init(argv: Sequence[str] | None) -> web.Application:\n    arg_parser = ArgumentParser(\n        prog=\"aiohttp.web ...\", description=\"Application CLI\", add_help=False\n    )\n\n    # Positional argument\n    arg_parser.add_argument(\"message\", help=\"message to print\")\n\n    # Optional argument\n    arg_parser.add_argument(\n        \"--repeat\", help=\"number of times to repeat message\", type=int, default=\"1\"\n    )\n\n    # Avoid conflict with -h from `aiohttp.web` CLI parser\n    arg_parser.add_argument(\n        \"--app-help\", help=\"show this message and exit\", action=\"help\"\n    )\n\n    args = arg_parser.parse_args(argv)\n\n    app = web.Application()\n    app[args_key] = args\n    app.router.add_get(\"/\", display_message)\n\n    return app\n"
  },
  {
    "path": "examples/client_auth.py",
    "content": "#!/usr/bin/env python3\nimport asyncio\n\nimport aiohttp\n\n\nasync def fetch(session: aiohttp.ClientSession) -> None:\n    print(\"Query http://httpbin.org/basic-auth/andrew/password\")\n    async with session.get(\"http://httpbin.org/basic-auth/andrew/password\") as resp:\n        print(resp.status)\n        body = await resp.text()\n        print(body)\n\n\nasync def go() -> None:\n    async with aiohttp.ClientSession(\n        auth=aiohttp.BasicAuth(\"andrew\", \"password\")\n    ) as session:\n        await fetch(session)\n\n\nloop = asyncio.get_event_loop()\nloop.run_until_complete(go())\n"
  },
  {
    "path": "examples/client_json.py",
    "content": "#!/usr/bin/env python3\nimport asyncio\n\nimport aiohttp\n\n\nasync def fetch(session: aiohttp.ClientSession) -> None:\n    print(\"Query http://httpbin.org/get\")\n    async with session.get(\"http://httpbin.org/get\") as resp:\n        print(resp.status)\n        data = await resp.json()\n        print(data)\n\n\nasync def go() -> None:\n    async with aiohttp.ClientSession() as session:\n        await fetch(session)\n\n\nloop = asyncio.get_event_loop()\nloop.run_until_complete(go())\nloop.close()\n"
  },
  {
    "path": "examples/client_ws.py",
    "content": "#!/usr/bin/env python3\n\"\"\"websocket cmd client for web_ws.py example.\"\"\"\n\nimport argparse\nimport asyncio\nimport sys\nfrom contextlib import suppress\n\nimport aiohttp\n\n\nasync def start_client(url: str) -> None:\n    name = input(\"Please enter your name: \")\n\n    async def dispatch(ws: aiohttp.ClientWebSocketResponse) -> None:\n        while True:\n            msg = await ws.receive()\n\n            if msg.type is aiohttp.WSMsgType.TEXT:\n                print(\"Text: \", msg.data.strip())\n            elif msg.type is aiohttp.WSMsgType.BINARY:\n                print(\"Binary: \", msg.data)\n            elif msg.type is aiohttp.WSMsgType.PING:\n                await ws.pong()\n            elif msg.type is aiohttp.WSMsgType.PONG:\n                print(\"Pong received\")\n            else:\n                if msg.type is aiohttp.WSMsgType.CLOSE:\n                    await ws.close()\n                elif msg.type is aiohttp.WSMsgType.ERROR:\n                    print(\"Error during receive %s\" % ws.exception())\n                elif msg.type is aiohttp.WSMsgType.CLOSED:\n                    pass\n\n                break\n\n    async with aiohttp.ClientSession() as session:\n        async with session.ws_connect(url, autoclose=False, autoping=False) as ws:\n            # send request\n            dispatch_task = asyncio.create_task(dispatch(ws))\n\n            # Exit with Ctrl+D\n            while line := await asyncio.to_thread(sys.stdin.readline):\n                await ws.send_str(name + \": \" + line)\n\n            dispatch_task.cancel()\n            with suppress(asyncio.CancelledError):\n                await dispatch_task\n\n\nARGS = argparse.ArgumentParser(\n    description=\"websocket console client for wssrv.py example.\"\n)\nARGS.add_argument(\n    \"--host\", action=\"store\", dest=\"host\", default=\"127.0.0.1\", help=\"Host name\"\n)\nARGS.add_argument(\n    \"--port\", action=\"store\", dest=\"port\", default=8080, type=int, help=\"Port number\"\n)\n\nif __name__ == \"__main__\":\n    args = ARGS.parse_args()\n    if \":\" in args.host:\n        args.host, port = args.host.split(\":\", 1)\n        args.port = int(port)\n\n    url = f\"http://{args.host}:{args.port}\"\n\n    asyncio.run(start_client(url))\n"
  },
  {
    "path": "examples/combined_middleware.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nExample of combining multiple middleware with aiohttp client.\n\nThis example shows how to chain multiple middleware together to create\na powerful request pipeline. Middleware are applied in order, demonstrating\nhow logging, authentication, and retry logic can work together.\n\nThe order of middleware matters:\n1. Logging (outermost) - logs all attempts including retries\n2. Authentication - adds auth headers before retry logic\n3. Retry (innermost) - retries requests on failure\n\"\"\"\n\nimport asyncio\nimport base64\nimport binascii\nimport logging\nimport time\nfrom http import HTTPStatus\nfrom typing import TYPE_CHECKING\n\nfrom aiohttp import (\n    ClientHandlerType,\n    ClientRequest,\n    ClientResponse,\n    ClientSession,\n    hdrs,\n    web,\n)\n\nlogging.basicConfig(\n    level=logging.INFO, format=\"%(asctime)s - %(name)s - %(levelname)s - %(message)s\"\n)\n_LOGGER = logging.getLogger(__name__)\n\n\nclass LoggingMiddleware:\n    \"\"\"Middleware that logs request timing and response status.\"\"\"\n\n    async def __call__(\n        self,\n        request: ClientRequest,\n        handler: ClientHandlerType,\n    ) -> ClientResponse:\n        start_time = time.monotonic()\n\n        # Log request\n        _LOGGER.info(\"[REQUEST] %s %s\", request.method, request.url)\n\n        # Execute request\n        response = await handler(request)\n\n        # Log response\n        duration = time.monotonic() - start_time\n        _LOGGER.info(\n            \"[RESPONSE] %s in %.2fs - Status: %s\",\n            request.url.path,\n            duration,\n            response.status,\n        )\n\n        return response\n\n\nclass BasicAuthMiddleware:\n    \"\"\"Middleware that adds Basic Authentication to all requests.\"\"\"\n\n    def __init__(self, username: str, password: str) -> None:\n        self.username = username\n        self.password = password\n        self._auth_header = self._encode_credentials()\n\n    def _encode_credentials(self) -> str:\n        \"\"\"Encode username and password to base64.\"\"\"\n        credentials = f\"{self.username}:{self.password}\"\n        encoded = base64.b64encode(credentials.encode()).decode()\n        return f\"Basic {encoded}\"\n\n    async def __call__(\n        self,\n        request: ClientRequest,\n        handler: ClientHandlerType,\n    ) -> ClientResponse:\n        \"\"\"Add Basic Auth header to the request.\"\"\"\n        # Only add auth if not already present\n        if hdrs.AUTHORIZATION not in request.headers:\n            request.headers[hdrs.AUTHORIZATION] = self._auth_header\n            _LOGGER.debug(\"Added Basic Auth header\")\n\n        # Proceed with the request\n        return await handler(request)\n\n\nDEFAULT_RETRY_STATUSES: set[HTTPStatus] = {\n    HTTPStatus.TOO_MANY_REQUESTS,\n    HTTPStatus.INTERNAL_SERVER_ERROR,\n    HTTPStatus.BAD_GATEWAY,\n    HTTPStatus.SERVICE_UNAVAILABLE,\n    HTTPStatus.GATEWAY_TIMEOUT,\n}\n\n\nclass RetryMiddleware:\n    \"\"\"Middleware that retries failed requests with exponential backoff.\"\"\"\n\n    def __init__(\n        self,\n        max_retries: int = 3,\n        retry_statuses: set[HTTPStatus] | None = None,\n        initial_delay: float = 1.0,\n        backoff_factor: float = 2.0,\n    ) -> None:\n        self.max_retries = max_retries\n        self.retry_statuses = retry_statuses or DEFAULT_RETRY_STATUSES\n        self.initial_delay = initial_delay\n        self.backoff_factor = backoff_factor\n\n    async def __call__(\n        self,\n        request: ClientRequest,\n        handler: ClientHandlerType,\n    ) -> ClientResponse:\n        \"\"\"Execute request with retry logic.\"\"\"\n        last_response: ClientResponse | None = None\n        delay = self.initial_delay\n\n        for attempt in range(self.max_retries + 1):\n            if attempt > 0:\n                _LOGGER.info(\n                    \"Retrying request (attempt %s/%s)\",\n                    attempt + 1,\n                    self.max_retries + 1,\n                )\n\n            # Execute the request\n            response = await handler(request)\n            last_response = response\n\n            # Check if we should retry\n            if response.status not in self.retry_statuses:\n                return response\n\n            # Don't retry if we've exhausted attempts\n            if attempt >= self.max_retries:\n                _LOGGER.warning(\"Max retries exceeded\")\n                return response\n\n            # Wait before retrying\n            _LOGGER.debug(\"Waiting %ss before retry...\", delay)\n            await asyncio.sleep(delay)\n            delay *= self.backoff_factor\n\n        if TYPE_CHECKING:\n            assert last_response is not None  # Always set since we loop at least once\n        return last_response\n\n\nclass TestServer:\n    \"\"\"Test server with stateful endpoints for middleware testing.\"\"\"\n\n    def __init__(self) -> None:\n        self.flaky_counter = 0\n        self.protected_counter = 0\n\n    async def handle_protected(self, request: web.Request) -> web.Response:\n        \"\"\"Protected endpoint that requires authentication and is flaky on first attempt.\"\"\"\n        auth_header = request.headers.get(hdrs.AUTHORIZATION, \"\")\n\n        if not auth_header.startswith(\"Basic \"):\n            return web.Response(\n                status=401,\n                text=\"Unauthorized\",\n                headers={hdrs.WWW_AUTHENTICATE: 'Basic realm=\"test\"'},\n            )\n\n        # Decode the credentials\n        encoded_creds = auth_header[6:]  # Remove \"Basic \"\n        try:\n            decoded = base64.b64decode(encoded_creds).decode()\n            username, password = decoded.split(\":\", 1)\n        except (ValueError, binascii.Error):\n            return web.Response(\n                status=401,\n                text=\"Invalid credentials format\",\n                headers={hdrs.WWW_AUTHENTICATE: 'Basic realm=\"test\"'},\n            )\n\n        # Validate credentials\n        if username != \"user\" or password != \"pass\":\n            return web.Response(status=401, text=\"Invalid credentials\")\n\n        # Fail with 500 on first attempt to test retry + auth combination\n        self.protected_counter += 1\n        if self.protected_counter == 1:\n            return web.Response(\n                status=500, text=\"Internal server error (first attempt)\"\n            )\n\n        return web.json_response(\n            {\n                \"message\": \"Access granted\",\n                \"user\": username,\n                \"resource\": \"protected data\",\n            }\n        )\n\n    async def handle_flaky(self, request: web.Request) -> web.Response:\n        \"\"\"Endpoint that fails a few times before succeeding.\"\"\"\n        self.flaky_counter += 1\n\n        # Fail the first 2 requests, succeed on the 3rd\n        if self.flaky_counter <= 2:\n            return web.Response(\n                status=503,\n                text=f\"Service temporarily unavailable (attempt {self.flaky_counter})\",\n            )\n\n        # Reset counter and return success\n        self.flaky_counter = 0\n        return web.json_response(\n            {\n                \"message\": \"Success after retries!\",\n                \"data\": \"Important information retrieved\",\n            }\n        )\n\n    async def handle_always_fail(self, request: web.Request) -> web.Response:\n        \"\"\"Endpoint that always returns an error.\"\"\"\n        return web.Response(status=500, text=\"Internal server error\")\n\n    async def handle_status(self, request: web.Request) -> web.Response:\n        \"\"\"Return the status code specified in the path.\"\"\"\n        status = int(request.match_info[\"status\"])\n        return web.Response(status=status, text=f\"Status: {status}\")\n\n\nasync def run_test_server() -> web.AppRunner:\n    \"\"\"Run a test server with various endpoints.\"\"\"\n    app = web.Application()\n    server = TestServer()\n\n    app.router.add_get(\"/protected\", server.handle_protected)\n    app.router.add_get(\"/flaky\", server.handle_flaky)\n    app.router.add_get(\"/always-fail\", server.handle_always_fail)\n    app.router.add_get(\"/status/{status}\", server.handle_status)\n\n    runner = web.AppRunner(app)\n    await runner.setup()\n    site = web.TCPSite(runner, \"localhost\", 8080)\n    await site.start()\n    return runner\n\n\nasync def run_tests() -> None:\n    \"\"\"Run all the middleware tests.\"\"\"\n    # Create middleware instances\n    logging_middleware = LoggingMiddleware()\n    auth_middleware = BasicAuthMiddleware(\"user\", \"pass\")\n    retry_middleware = RetryMiddleware(max_retries=2, initial_delay=0.5)\n\n    # Combine middleware - order matters!\n    # Applied in order: logging -> auth -> retry -> request\n    async with ClientSession(\n        middlewares=(logging_middleware, auth_middleware, retry_middleware)\n    ) as session:\n\n        print(\n            \"=== Test 1: Protected endpoint with auth (fails once, then succeeds) ===\"\n        )\n        print(\"This tests retry + auth working together...\")\n        async with session.get(\"http://localhost:8080/protected\") as resp:\n            if resp.status == 200:\n                data = await resp.json()\n                print(f\"Success after retry! Response: {data}\")\n            else:\n                print(f\"Failed with status: {resp.status}\")\n\n        print(\"\\n=== Test 2: Flaky endpoint (fails twice, then succeeds) ===\")\n        print(\"Watch the logs to see retries in action...\")\n        async with session.get(\"http://localhost:8080/flaky\") as resp:\n            if resp.status == 200:\n                data = await resp.json()\n                print(f\"Success after retries! Response: {data}\")\n            else:\n                text = await resp.text()\n                print(f\"Failed with status {resp.status}: {text}\")\n\n        print(\"\\n=== Test 3: Always failing endpoint ===\")\n        async with session.get(\"http://localhost:8080/always-fail\") as resp:\n            print(f\"Final status after retries: {resp.status}\")\n\n        print(\"\\n=== Test 4: Non-retryable status (404) ===\")\n        async with session.get(\"http://localhost:8080/status/404\") as resp:\n            print(f\"Status: {resp.status} (no retries for 404)\")\n\n        # Test without middleware for comparison\n        print(\"\\n=== Test 5: Request without middleware ===\")\n        print(\"Making a request to protected endpoint without middleware...\")\n        async with session.get(\n            \"http://localhost:8080/protected\", middlewares=()\n        ) as resp:\n            print(f\"Status without middleware: {resp.status}\")\n            if resp.status == 401:\n                print(\"Failed as expected - no auth header added\")\n\n\nasync def main() -> None:\n    # Start test server\n    server = await run_test_server()\n\n    try:\n        await run_tests()\n\n    finally:\n        await server.cleanup()\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "examples/curl.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport asyncio\nimport sys\n\nimport aiohttp\n\n\nasync def curl(url: str) -> None:\n    async with aiohttp.ClientSession() as session:\n        async with session.request(\"GET\", url) as response:\n            print(repr(response))\n            chunk = await response.content.read()\n            print(\"Downloaded: %s\" % len(chunk))\n\n\nif __name__ == \"__main__\":\n    ARGS = argparse.ArgumentParser(description=\"GET url example\")\n    ARGS.add_argument(\"url\", nargs=1, metavar=\"URL\", help=\"URL to download\")\n    ARGS.add_argument(\n        \"--iocp\",\n        default=False,\n        action=\"store_true\",\n        help=\"Use ProactorEventLoop on Windows\",\n    )\n    options = ARGS.parse_args()\n\n    if options.iocp and sys.platform == \"win32\":\n        from asyncio import events, windows_events\n\n        # https://github.com/python/mypy/issues/12286\n        el = windows_events.ProactorEventLoop()  # type: ignore[attr-defined]\n        events.set_event_loop(el)\n\n    loop = asyncio.get_event_loop()\n    loop.run_until_complete(curl(options.url[0]))\n"
  },
  {
    "path": "examples/digest_auth_qop_auth.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nExample of using digest authentication middleware with aiohttp client.\n\nThis example shows how to use the DigestAuthMiddleware from\naiohttp.client_middleware_digest_auth to authenticate with a server\nthat requires digest authentication with different qop options.\n\nIn this case, it connects to httpbin.org's digest auth endpoint.\n\"\"\"\n\nimport asyncio\nfrom itertools import product\n\nfrom yarl import URL\n\nfrom aiohttp import ClientSession\nfrom aiohttp.client_middleware_digest_auth import DigestAuthMiddleware\n\n# Define QOP options available\nQOP_OPTIONS = [\"auth\", \"auth-int\"]\n\n# Algorithms supported by httpbin.org\nALGORITHMS = [\"MD5\", \"SHA-256\", \"SHA-512\"]\n\n# Username and password for testing\nUSERNAME = \"my\"\nPASSWORD = \"dog\"\n\n# All combinations of QOP options and algorithms\nTEST_COMBINATIONS = list(product(QOP_OPTIONS, ALGORITHMS))\n\n\nasync def main() -> None:\n    # Create a DigestAuthMiddleware instance with appropriate credentials\n    digest_auth = DigestAuthMiddleware(login=USERNAME, password=PASSWORD)\n\n    # Create a client session with the digest auth middleware\n    async with ClientSession(middlewares=(digest_auth,)) as session:\n        # Test each combination of QOP and algorithm\n        for qop, algorithm in TEST_COMBINATIONS:\n            print(f\"\\n\\n=== Testing with qop={qop}, algorithm={algorithm} ===\\n\")\n\n            url = URL(\n                f\"https://httpbin.org/digest-auth/{qop}/{USERNAME}/{PASSWORD}/{algorithm}\"\n            )\n\n            async with session.get(url) as resp:\n                print(f\"Status: {resp.status}\")\n                print(f\"Headers: {resp.headers}\")\n\n                # Parse the JSON response\n                json_response = await resp.json()\n                print(f\"Response: {json_response}\")\n\n                # Verify authentication was successful\n                if resp.status == 200:\n                    print(\"\\nAuthentication successful!\")\n                    print(f\"Authenticated user: {json_response.get('user')}\")\n                    print(\n                        f\"Authentication method: {json_response.get('authenticated')}\"\n                    )\n                else:\n                    print(\"\\nAuthentication failed.\")\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "examples/fake_server.py",
    "content": "#!/usr/bin/env python3\nimport asyncio\nimport pathlib\nimport socket\nimport ssl\n\nfrom aiohttp import ClientSession, TCPConnector, web\nfrom aiohttp.abc import AbstractResolver, ResolveResult\nfrom aiohttp.resolver import DefaultResolver\n\n\nclass FakeResolver(AbstractResolver):\n    _LOCAL_HOST = {0: \"127.0.0.1\", socket.AF_INET: \"127.0.0.1\", socket.AF_INET6: \"::1\"}\n\n    def __init__(self, fakes: dict[str, int]) -> None:\n        \"\"\"fakes -- dns -> port dict\"\"\"\n        self._fakes = fakes\n        self._resolver = DefaultResolver()\n\n    async def resolve(\n        self,\n        host: str,\n        port: int = 0,\n        family: socket.AddressFamily = socket.AF_INET,\n    ) -> list[ResolveResult]:\n        fake_port = self._fakes.get(host)\n        if fake_port is not None:\n            return [\n                {\n                    \"hostname\": host,\n                    \"host\": self._LOCAL_HOST[family],\n                    \"port\": fake_port,\n                    \"family\": family,\n                    \"proto\": 0,\n                    \"flags\": socket.AI_NUMERICHOST,\n                }\n            ]\n        else:\n            return await self._resolver.resolve(host, port, family)\n\n    async def close(self) -> None:\n        await self._resolver.close()\n\n\nclass FakeFacebook:\n    def __init__(self) -> None:\n        self.app = web.Application()\n        self.app.router.add_routes(\n            [\n                web.get(\"/v2.7/me\", self.on_me),\n                web.get(\"/v2.7/me/friends\", self.on_my_friends),\n            ]\n        )\n        self.runner = web.AppRunner(self.app)\n        here = pathlib.Path(__file__)\n        ssl_cert = here.parent / \"server.crt\"\n        ssl_key = here.parent / \"server.key\"\n        self.ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)\n        self.ssl_context.load_cert_chain(str(ssl_cert), str(ssl_key))\n\n    async def start(self) -> dict[str, int]:\n        await self.runner.setup()\n        site = web.TCPSite(\n            self.runner, \"127.0.0.1\", port=0, ssl_context=self.ssl_context\n        )\n        await site.start()\n        return {\"graph.facebook.com\": site.port}\n\n    async def stop(self) -> None:\n        await self.runner.cleanup()\n\n    async def on_me(self, request: web.Request) -> web.StreamResponse:\n        return web.json_response({\"name\": \"John Doe\", \"id\": \"12345678901234567\"})\n\n    async def on_my_friends(self, request: web.Request) -> web.StreamResponse:\n        return web.json_response(\n            {\n                \"data\": [\n                    {\"name\": \"Bill Doe\", \"id\": \"233242342342\"},\n                    {\"name\": \"Mary Doe\", \"id\": \"2342342343222\"},\n                    {\"name\": \"Alex Smith\", \"id\": \"234234234344\"},\n                ],\n                \"paging\": {\n                    \"cursors\": {\n                        \"before\": \"QVFIUjRtc2c5NEl0ajN\",\n                        \"after\": \"QVFIUlpFQWM0TmVuaDRad0dt\",\n                    },\n                    \"next\": (\n                        \"https://graph.facebook.com/v2.7/12345678901234567/\"\n                        \"friends?access_token=EAACEdEose0cB\"\n                    ),\n                },\n                \"summary\": {\"total_count\": 3},\n            }\n        )\n\n\nasync def main() -> None:\n    token = \"ER34gsSGGS34XCBKd7u\"\n\n    fake_facebook = FakeFacebook()\n    info = await fake_facebook.start()\n    resolver = FakeResolver(info)\n    connector = TCPConnector(resolver=resolver, ssl=False)\n\n    async with ClientSession(connector=connector) as session:\n        async with session.get(\n            \"https://graph.facebook.com/v2.7/me\", params={\"access_token\": token}\n        ) as resp:\n            print(await resp.json())\n\n        async with session.get(\n            \"https://graph.facebook.com/v2.7/me/friends\", params={\"access_token\": token}\n        ) as resp:\n            print(await resp.json())\n\n    await fake_facebook.stop()\n\n\nasyncio.run(main())\n"
  },
  {
    "path": "examples/logging_middleware.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nExample of using logging middleware with aiohttp client.\n\nThis example shows how to implement a middleware that logs request timing\nand response status. This is useful for debugging, monitoring, and\nunderstanding the flow of HTTP requests in your application.\n\nThis example includes a test server with various endpoints.\n\"\"\"\n\nimport asyncio\nimport json\nimport logging\nimport time\nfrom collections.abc import Coroutine\nfrom typing import Any\n\nfrom aiohttp import ClientHandlerType, ClientRequest, ClientResponse, ClientSession, web\n\nlogging.basicConfig(\n    level=logging.DEBUG, format=\"%(asctime)s - %(name)s - %(levelname)s - %(message)s\"\n)\n_LOGGER = logging.getLogger(__name__)\n\n\nclass LoggingMiddleware:\n    \"\"\"Middleware that logs request timing and response status.\"\"\"\n\n    async def __call__(\n        self,\n        request: ClientRequest,\n        handler: ClientHandlerType,\n    ) -> ClientResponse:\n        start_time = time.monotonic()\n\n        # Log request\n        _LOGGER.info(\"[REQUEST] %s %s\", request.method, request.url)\n        if request.headers:\n            _LOGGER.debug(\"[REQUEST HEADERS] %s\", request.headers)\n\n        # Execute request\n        response = await handler(request)\n\n        # Log response\n        duration = time.monotonic() - start_time\n        _LOGGER.info(\n            \"[RESPONSE] %s %s - Status: %s - Duration: %.3fs\",\n            request.method,\n            request.url,\n            response.status,\n            duration,\n        )\n        _LOGGER.debug(\"[RESPONSE HEADERS] %s\", response.headers)\n\n        return response\n\n\nclass TestServer:\n    \"\"\"Test server for logging middleware demo.\"\"\"\n\n    async def handle_hello(self, request: web.Request) -> web.Response:\n        \"\"\"Simple hello endpoint.\"\"\"\n        name = request.match_info.get(\"name\", \"World\")\n        return web.json_response({\"message\": f\"Hello, {name}!\"})\n\n    async def handle_slow(self, request: web.Request) -> web.Response:\n        \"\"\"Endpoint that simulates slow response.\"\"\"\n        delay = float(request.match_info.get(\"delay\", 1))\n        await asyncio.sleep(delay)\n        return web.json_response({\"message\": \"Slow response completed\", \"delay\": delay})\n\n    async def handle_error(self, request: web.Request) -> web.Response:\n        \"\"\"Endpoint that returns an error.\"\"\"\n        status = int(request.match_info.get(\"status\", 500))\n        return web.Response(status=status, text=f\"Error response with status {status}\")\n\n    async def handle_json_data(self, request: web.Request) -> web.Response:\n        \"\"\"Endpoint that echoes JSON data.\"\"\"\n        try:\n            data = await request.json()\n            return web.json_response({\"echo\": data, \"received_at\": time.time()})\n        except json.JSONDecodeError:\n            return web.json_response({\"error\": \"Invalid JSON\"}, status=400)\n\n\nasync def run_test_server() -> web.AppRunner:\n    \"\"\"Run a simple test server.\"\"\"\n    app = web.Application()\n    server = TestServer()\n\n    app.router.add_get(\"/hello\", server.handle_hello)\n    app.router.add_get(\"/hello/{name}\", server.handle_hello)\n    app.router.add_get(\"/slow/{delay}\", server.handle_slow)\n    app.router.add_get(\"/error/{status}\", server.handle_error)\n    app.router.add_post(\"/echo\", server.handle_json_data)\n\n    runner = web.AppRunner(app)\n    await runner.setup()\n    site = web.TCPSite(runner, \"localhost\", 8080)\n    await site.start()\n    return runner\n\n\nasync def run_tests() -> None:\n    \"\"\"Run all the middleware tests.\"\"\"\n    # Create logging middleware\n    logging_middleware = LoggingMiddleware()\n\n    # Use middleware in session\n    async with ClientSession(middlewares=(logging_middleware,)) as session:\n        # Test 1: Simple GET request\n        print(\"\\n=== Test 1: Simple GET request ===\")\n        async with session.get(\"http://localhost:8080/hello\") as resp:\n            data = await resp.json()\n            print(f\"Response: {data}\")\n\n        # Test 2: GET with parameter\n        print(\"\\n=== Test 2: GET with parameter ===\")\n        async with session.get(\"http://localhost:8080/hello/Alice\") as resp:\n            data = await resp.json()\n            print(f\"Response: {data}\")\n\n        # Test 3: Slow request\n        print(\"\\n=== Test 3: Slow request (2 seconds) ===\")\n        async with session.get(\"http://localhost:8080/slow/2\") as resp:\n            data = await resp.json()\n            print(f\"Response: {data}\")\n\n        # Test 4: Error response\n        print(\"\\n=== Test 4: Error response ===\")\n        async with session.get(\"http://localhost:8080/error/404\") as resp:\n            text = await resp.text()\n            print(f\"Response: {text}\")\n\n        # Test 5: POST with JSON data\n        print(\"\\n=== Test 5: POST with JSON data ===\")\n        payload = {\"name\": \"Bob\", \"age\": 30, \"city\": \"New York\"}\n        async with session.post(\"http://localhost:8080/echo\", json=payload) as resp:\n            data = await resp.json()\n            print(f\"Response: {data}\")\n\n        # Test 6: Multiple concurrent requests\n        print(\"\\n=== Test 6: Multiple concurrent requests ===\")\n        coros: list[Coroutine[Any, Any, ClientResponse]] = []\n        for i in range(3):\n            coro = session.get(f\"http://localhost:8080/hello/User{i}\")\n            coros.append(coro)\n\n        responses = await asyncio.gather(*coros)\n        for i, resp in enumerate(responses):\n            async with resp:\n                data = await resp.json()\n                print(f\"Concurrent request {i}: {data}\")\n\n\nasync def main() -> None:\n    # Start test server\n    server = await run_test_server()\n\n    try:\n        await run_tests()\n\n    finally:\n        # Cleanup server\n        await server.cleanup()\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "examples/lowlevel_srv.py",
    "content": "import asyncio\n\nfrom aiohttp import web, web_request\n\n\nasync def handler(request: web_request.BaseRequest) -> web.StreamResponse:\n    return web.Response(text=\"OK\")\n\n\nasync def main(loop: asyncio.AbstractEventLoop) -> None:\n    server = web.Server(handler)\n    await loop.create_server(server, \"127.0.0.1\", 8080)\n    print(\"======= Serving on http://127.0.0.1:8080/ ======\")\n\n    # pause here for very long time by serving HTTP requests and\n    # waiting for keyboard interruption\n    await asyncio.sleep(100 * 3600)\n\n\nloop = asyncio.get_event_loop()\n\ntry:\n    loop.run_until_complete(main(loop))\nexcept KeyboardInterrupt:\n    pass\nloop.close()\n"
  },
  {
    "path": "examples/retry_middleware.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nExample of using retry middleware with aiohttp client.\n\nThis example shows how to implement a middleware that automatically retries\nfailed requests with exponential backoff. The middleware can be configured\nwith custom retry statuses, maximum retries, and backoff parameters.\n\nThis example includes a test server that simulates various HTTP responses\nand can return different status codes on sequential requests.\n\"\"\"\n\nimport asyncio\nimport logging\nfrom http import HTTPStatus\nfrom typing import TYPE_CHECKING\n\nfrom aiohttp import ClientHandlerType, ClientRequest, ClientResponse, ClientSession, web\n\nlogging.basicConfig(level=logging.INFO)\n_LOGGER = logging.getLogger(__name__)\n\nDEFAULT_RETRY_STATUSES: set[HTTPStatus] = {\n    HTTPStatus.TOO_MANY_REQUESTS,\n    HTTPStatus.INTERNAL_SERVER_ERROR,\n    HTTPStatus.BAD_GATEWAY,\n    HTTPStatus.SERVICE_UNAVAILABLE,\n    HTTPStatus.GATEWAY_TIMEOUT,\n}\n\n\nclass RetryMiddleware:\n    \"\"\"Middleware that retries failed requests with exponential backoff.\"\"\"\n\n    def __init__(\n        self,\n        max_retries: int = 3,\n        retry_statuses: set[HTTPStatus] | None = None,\n        initial_delay: float = 1.0,\n        backoff_factor: float = 2.0,\n    ) -> None:\n        self.max_retries = max_retries\n        self.retry_statuses = retry_statuses or DEFAULT_RETRY_STATUSES\n        self.initial_delay = initial_delay\n        self.backoff_factor = backoff_factor\n\n    async def __call__(\n        self,\n        request: ClientRequest,\n        handler: ClientHandlerType,\n    ) -> ClientResponse:\n        \"\"\"Execute request with retry logic.\"\"\"\n        last_response: ClientResponse | None = None\n        delay = self.initial_delay\n\n        for attempt in range(self.max_retries + 1):\n            if attempt > 0:\n                _LOGGER.info(\n                    \"Retrying request to %s (attempt %s/%s)\",\n                    request.url,\n                    attempt + 1,\n                    self.max_retries + 1,\n                )\n\n            # Execute the request\n            response = await handler(request)\n            last_response = response\n\n            # Check if we should retry\n            if response.status not in self.retry_statuses:\n                return response\n\n            # Don't retry if we've exhausted attempts\n            if attempt >= self.max_retries:\n                _LOGGER.warning(\n                    \"Max retries (%s) exceeded for %s\", self.max_retries, request.url\n                )\n                return response\n\n            # Wait before retrying\n            _LOGGER.debug(\"Waiting %ss before retry...\", delay)\n            await asyncio.sleep(delay)\n            delay *= self.backoff_factor\n\n        # Return the last response\n        if TYPE_CHECKING:\n            assert last_response is not None  # Always set since we loop at least once\n        return last_response\n\n\nclass TestServer:\n    \"\"\"Test server with stateful endpoints for retry testing.\"\"\"\n\n    def __init__(self) -> None:\n        self.request_counters: dict[str, int] = {}\n        self.status_sequences: dict[str, list[int]] = {\n            \"eventually-ok\": [500, 503, 502, 200],  # Fails 3 times, then succeeds\n            \"always-error\": [500, 500, 500, 500],  # Always fails\n            \"immediate-ok\": [200],  # Succeeds immediately\n            \"flaky\": [503, 200],  # Fails once, then succeeds\n        }\n\n    async def handle_status(self, request: web.Request) -> web.Response:\n        \"\"\"Return the status code specified in the path.\"\"\"\n        status = int(request.match_info[\"status\"])\n        return web.Response(status=status, text=f\"Status: {status}\")\n\n    async def handle_status_sequence(self, request: web.Request) -> web.Response:\n        \"\"\"Return different status codes on sequential requests.\"\"\"\n        path = request.path\n\n        # Initialize counter for this path if needed\n        if path not in self.request_counters:\n            self.request_counters[path] = 0\n\n        # Get the status sequence for this path\n        sequence_name = request.match_info[\"name\"]\n        if sequence_name not in self.status_sequences:\n            return web.Response(status=404, text=\"Sequence not found\")\n\n        sequence = self.status_sequences[sequence_name]\n\n        # Get the current status based on request count\n        count = self.request_counters[path]\n        if count < len(sequence):\n            status = sequence[count]\n        else:\n            # After sequence ends, always return the last status\n            status = sequence[-1]\n\n        # Increment counter for next request\n        self.request_counters[path] += 1\n\n        return web.Response(\n            status=status, text=f\"Request #{count + 1}: Status {status}\"\n        )\n\n    async def handle_delay(self, request: web.Request) -> web.Response:\n        \"\"\"Delay response by specified seconds.\"\"\"\n        delay = float(request.match_info[\"delay\"])\n        await asyncio.sleep(delay)\n        return web.json_response({\"delay\": delay, \"message\": \"Response after delay\"})\n\n    async def handle_reset(self, request: web.Request) -> web.Response:\n        \"\"\"Reset request counters.\"\"\"\n        self.request_counters = {}\n        return web.Response(text=\"Counters reset\")\n\n\nasync def run_test_server() -> web.AppRunner:\n    \"\"\"Run a simple test server.\"\"\"\n    app = web.Application()\n    server = TestServer()\n\n    app.router.add_get(\"/status/{status}\", server.handle_status)\n    app.router.add_get(\"/sequence/{name}\", server.handle_status_sequence)\n    app.router.add_get(\"/delay/{delay}\", server.handle_delay)\n    app.router.add_post(\"/reset\", server.handle_reset)\n\n    runner = web.AppRunner(app)\n    await runner.setup()\n    site = web.TCPSite(runner, \"localhost\", 8080)\n    await site.start()\n    return runner\n\n\nasync def run_tests() -> None:\n    \"\"\"Run all retry middleware tests.\"\"\"\n    # Create retry middleware with custom settings\n    retry_middleware = RetryMiddleware(\n        max_retries=3,\n        retry_statuses=DEFAULT_RETRY_STATUSES,\n        initial_delay=0.5,\n        backoff_factor=2.0,\n    )\n\n    async with ClientSession(middlewares=(retry_middleware,)) as session:\n        # Reset counters before tests\n        await session.post(\"http://localhost:8080/reset\")\n\n        # Test 1: Request that succeeds immediately\n        print(\"=== Test 1: Immediate success ===\")\n        async with session.get(\"http://localhost:8080/sequence/immediate-ok\") as resp:\n            text = await resp.text()\n            print(f\"Final status: {resp.status}\")\n            print(f\"Response: {text}\")\n            print(\"Success - no retries needed\\n\")\n\n        # Test 2: Request that eventually succeeds after retries\n        print(\"=== Test 2: Eventually succeeds (500->503->502->200) ===\")\n        async with session.get(\"http://localhost:8080/sequence/eventually-ok\") as resp:\n            text = await resp.text()\n            print(f\"Final status: {resp.status}\")\n            print(f\"Response: {text}\")\n            if resp.status == 200:\n                print(\"Success after retries!\\n\")\n            else:\n                print(\"Failed after retries\\n\")\n\n        # Test 3: Request that always fails\n        print(\"=== Test 3: Always fails (500->500->500->500) ===\")\n        async with session.get(\"http://localhost:8080/sequence/always-error\") as resp:\n            text = await resp.text()\n            print(f\"Final status: {resp.status}\")\n            print(f\"Response: {text}\")\n            print(\"Failed after exhausting all retries\\n\")\n\n        # Test 4: Flaky service (fails once then succeeds)\n        print(\"=== Test 4: Flaky service (503->200) ===\")\n        await session.post(\"http://localhost:8080/reset\")  # Reset counters\n        async with session.get(\"http://localhost:8080/sequence/flaky\") as resp:\n            text = await resp.text()\n            print(f\"Final status: {resp.status}\")\n            print(f\"Response: {text}\")\n            print(\"Success after one retry!\\n\")\n\n        # Test 5: Non-retryable status\n        print(\"=== Test 5: Non-retryable status (404) ===\")\n        async with session.get(\"http://localhost:8080/status/404\") as resp:\n            print(f\"Final status: {resp.status}\")\n            print(\"Failed immediately - not a retryable status\\n\")\n\n        # Test 6: Delayed response\n        print(\"=== Test 6: Testing with delay endpoint ===\")\n        try:\n            async with session.get(\"http://localhost:8080/delay/0.5\") as resp:\n                print(f\"Status: {resp.status}\")\n                data = await resp.json()\n                print(f\"Response received after delay: {data}\\n\")\n        except asyncio.TimeoutError:\n            print(\"Request timed out\\n\")\n\n\nasync def main() -> None:\n    # Start test server\n    server = await run_test_server()\n\n    try:\n        await run_tests()\n    finally:\n        await server.cleanup()\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "examples/server.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDADCCAegCCQCgevpPMuTTLzANBgkqhkiG9w0BAQsFADBCMQswCQYDVQQGEwJV\nQTEQMA4GA1UECAwHVWtyYWluZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ\ndHkgTHRkMB4XDTE2MDgwNzIzMTMwOFoXDTI2MDgwNTIzMTMwOFowQjELMAkGA1UE\nBhMCVUExEDAOBgNVBAgMB1VrcmFpbmUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp\ndHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOUgkn3j\nX/sdg6GGueGDHCM+snIUVY3fM6D4jXjyBhnT3TqKG1lJwCGYR11AD+2SJYppU+w4\nQaF6YZwMeZBKy+mVQ9+CrVYyKQE7j9H8XgNEHV9BQzoragT8lia8eC5aOQzUeX8A\nxCSSbsnyT/X+S1IKdd0txLOeZOD6pWwJoc3dpDELglk2b1tzhyN2GjQv3aRHj55P\nx7127MeZyRXwODFpXrpbnwih4OqkA4EYtmqFbZttGEzMhd4Y5mkbyuRbGM+IE99o\nQJMvnIkjAfUo0aKnDrcAIkWCkwLIci9TIG6u3R1P2Tn+HYVntzQZ4BnxanbFNQ5S\n9ARd3529EmO3BzUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAXyiw1+YUnTEDI3C/\nvq1Vn9pnwZALVQPiPlTqEGkl/nbq0suMmeZZG7pwrOJp3wr+sGwRAv9sPTro6srf\nVj12wTo4LrTRKEDuS+AUJl0Mut7cPGIUKo+MGeZmmnDjMqcjljN3AO47ef4eWYo5\nXGe4r4NDABEk5auOD/vQW5IiIMdmWsaMJ+0mZNpAV2NhAD/6ia28VvSL/yuaNqDW\nTYTUYHWLH08H6M6qrQ7FdoIDyYR5siqBukQzeqlnuq45bQ3ViYttNIkzZN4jbWJV\n/MFYLuJQ/fNoalDIC+ec0EIa9NbrfpoocJ8h6HlmWOqkES4QpBSOrkVid64Cdy3P\nJgiEWg==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "examples/server.csr",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIChzCCAW8CAQAwQjELMAkGA1UEBhMCVUExEDAOBgNVBAgMB1VrcmFpbmUxITAf\nBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBAOUgkn3jX/sdg6GGueGDHCM+snIUVY3fM6D4jXjyBhnT\n3TqKG1lJwCGYR11AD+2SJYppU+w4QaF6YZwMeZBKy+mVQ9+CrVYyKQE7j9H8XgNE\nHV9BQzoragT8lia8eC5aOQzUeX8AxCSSbsnyT/X+S1IKdd0txLOeZOD6pWwJoc3d\npDELglk2b1tzhyN2GjQv3aRHj55Px7127MeZyRXwODFpXrpbnwih4OqkA4EYtmqF\nbZttGEzMhd4Y5mkbyuRbGM+IE99oQJMvnIkjAfUo0aKnDrcAIkWCkwLIci9TIG6u\n3R1P2Tn+HYVntzQZ4BnxanbFNQ5S9ARd3529EmO3BzUCAwEAAaAAMA0GCSqGSIb3\nDQEBCwUAA4IBAQDO/PSd29KgisTdGXhntg7yBEhBAjsDW7uQCrdrPSZtFyN6wUHy\n/1yrrWe56ZuW8jpuP5tG0eTZ+0bT2RXIRot8a2Cc3eBhpoe8M3d84yXjKAoHutGE\n5IK+TViQdvT3pT3a7pTmjlf8Ojq9tx+U2ckiz8Ccnjd9yM47M9NgMhrS1aBpVZSt\ngOD+zzrqMML4xks9id94H7bi9Tgs3AbEJIyDpBpoK6i4OvK7KTidCngCg80qmdTy\nbcScLapoy1Ped2BKKuxWdOOlP+mDJatc/pcfBLE13AncQjJgMerS9M5RWCBjmRow\nA+aB6fBEU8bOTrqCryfBeTiV6xzyDDcIXtc6\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "examples/server.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA5SCSfeNf+x2DoYa54YMcIz6ychRVjd8zoPiNePIGGdPdOoob\nWUnAIZhHXUAP7ZIlimlT7DhBoXphnAx5kErL6ZVD34KtVjIpATuP0fxeA0QdX0FD\nOitqBPyWJrx4Llo5DNR5fwDEJJJuyfJP9f5LUgp13S3Es55k4PqlbAmhzd2kMQuC\nWTZvW3OHI3YaNC/dpEePnk/HvXbsx5nJFfA4MWleulufCKHg6qQDgRi2aoVtm20Y\nTMyF3hjmaRvK5FsYz4gT32hAky+ciSMB9SjRoqcOtwAiRYKTAshyL1Mgbq7dHU/Z\nOf4dhWe3NBngGfFqdsU1DlL0BF3fnb0SY7cHNQIDAQABAoIBAG9BJ6B03VADfrzZ\nvDwh+3Gpqd/2u6wNqvYIejk123yDATLBiJIMW3x0goJm7tT+V7gjeJqEnmmYEPlC\nnWxQxT6AOdq3iw8FgB+XGjhuAAA5/MEZ4VjHZ81QEGBytzBaosT2DqB6cMMJTz5D\nqEvb1Brb9WsWJCLLUFRloBkbfDOG9lMvt34ixYTTmqjsVj5WByD5BhzKH51OJ72L\n00IYpvrsEOtSev1hNV4199CHPYE90T/YQVooRBiHtTcfN+/KNVJu6Rf/zcaJ3WMS\n1l3MBI8HwMimjKKkbddpoMHyFMtSNmS9Yq+4a9w7XZo1F5rt88hYSCtAF8HRAarX\n0VBCJmkCgYEA9HenBBnmfDoN857femzoTHdWQQrZQ4YPAKHvKPlcgudizE5tQbs0\niTpwm+IsecgJS2Rio7zY+P7A5nKFz3N5c0IX3smYo0J2PoakkLAm25KMxFZYBuz4\nMFWVdfByAU7d28BdNfyOVbA2kU2eal9lJ0yPLpMLbH8+bbvw5uBS808CgYEA7++p\nftwib3DvKWMpl6G5eA1C2xprdbE0jm2fSr3LYp/vZ4QN2V6kK2YIlyUqQvhYCnxX\noIP3v2MWDRHKKwJtBWR4+t23PaDaSXS2Ifm0qhRxwSm/oqpAJQXbR7VzxXp4/4FP\n1SgkLe51bubc4h+cDngqBLcplCanvj52CqhqzDsCgYAEIhG8zANNjl22BLWaiETV\nJh9bMifCMH4IcLRuaOjbfbX55kmKlvOobkiBGi3OUUd28teIFSVF8GiqfL0uaLFg\n9XkZ1yaxe+or3HLjz1aY171xhFQwqcj4aDoCqHIE+6Rclr/8raxqXnRNuJY5DivT\nokO5cdr7lpsjl83W2WwNmQKBgCPXi1xWChbXqgJmu8nY8NnMMVaFpdPY+t7j5U3G\n+GDtP1gZU/BKwP9yqInblWqXqp82X+isjg/a/2pIZAj0vdB2Z9Qh1sOwCau7cZG1\nuZVGpI+UavojsJ1XOKCHrJmtZ/HTIVfYPT9XRdehSRHGYwuOS8iUi/ODqr8ymXOS\nIRINAoGBAMEmhTihgFz6Y8ezRK3QTubguehHZG1zIvtgVhOk+8hRUTSJPI9nBJPC\n4gOZsPx4g2oLK6PiudPR79bhxRxPACCMnXkdwZ/8FaIdmvRHsWVs8T80wID0wthI\nr5hW4uqi9CcKZrGWH7mx9cVJktspeGUczvKyzNMfCaojwzA/49Z1\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "examples/server_simple.py",
    "content": "# server_simple.py\nfrom aiohttp import web\n\n\nasync def handle(request: web.Request) -> web.StreamResponse:\n    name = request.match_info.get(\"name\", \"Anonymous\")\n    text = \"Hello, \" + name\n    return web.Response(text=text)\n\n\nasync def wshandle(request: web.Request) -> web.StreamResponse:\n    ws = web.WebSocketResponse()\n    await ws.prepare(request)\n\n    async for msg in ws:\n        if msg.type is web.WSMsgType.TEXT:\n            await ws.send_str(f\"Hello, {msg.data}\")\n        elif msg.type is web.WSMsgType.BINARY:\n            await ws.send_bytes(msg.data)\n        elif msg.type is web.WSMsgType.CLOSE:\n            break\n\n    return ws\n\n\napp = web.Application()\napp.add_routes(\n    [web.get(\"/\", handle), web.get(\"/echo\", wshandle), web.get(\"/{name}\", handle)]\n)\n\nweb.run_app(app)\n"
  },
  {
    "path": "examples/static_files.py",
    "content": "#!/usr/bin/env python3\nimport pathlib\n\nfrom aiohttp import web\n\napp = web.Application()\napp.router.add_static(\"/\", pathlib.Path(__file__).parent, show_index=True)\n\nweb.run_app(app)\n"
  },
  {
    "path": "examples/token_refresh_middleware.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nExample of using token refresh middleware with aiohttp client.\n\nThis example shows how to implement a middleware that handles JWT token\nrefresh automatically. The middleware:\n- Adds bearer tokens to requests\n- Detects when tokens are expired\n- Automatically refreshes tokens when needed\n- Handles concurrent requests during token refresh\n\nThis example includes a test server that simulates a JWT auth system.\nNote: This is a simplified example for demonstration purposes.\nIn production, use proper JWT libraries and secure token storage.\n\"\"\"\n\nimport asyncio\nimport hashlib\nimport json\nimport logging\nimport secrets\nimport time\nfrom collections.abc import Coroutine\nfrom http import HTTPStatus\nfrom typing import TYPE_CHECKING, Any\n\nfrom aiohttp import (\n    ClientHandlerType,\n    ClientRequest,\n    ClientResponse,\n    ClientSession,\n    hdrs,\n    web,\n)\n\nlogging.basicConfig(level=logging.INFO)\n_LOGGER = logging.getLogger(__name__)\n\n\nclass TokenRefreshMiddleware:\n    \"\"\"Middleware that handles JWT token refresh automatically.\"\"\"\n\n    def __init__(self, token_endpoint: str, refresh_token: str) -> None:\n        self.token_endpoint = token_endpoint\n        self.refresh_token = refresh_token\n        self.access_token: str | None = None\n        self.token_expires_at: float | None = None\n        self._refresh_lock = asyncio.Lock()\n\n    async def _refresh_access_token(self, session: ClientSession) -> str:\n        \"\"\"Refresh the access token using the refresh token.\"\"\"\n        async with self._refresh_lock:\n            # Check if another coroutine already refreshed the token\n            if (\n                self.token_expires_at\n                and time.time() < self.token_expires_at\n                and self.access_token\n            ):\n                _LOGGER.debug(\"Token already refreshed by another request\")\n                return self.access_token\n\n            _LOGGER.info(\"Refreshing access token...\")\n\n            # Make refresh request without middleware to avoid recursion\n            async with session.post(\n                self.token_endpoint,\n                json={\"refresh_token\": self.refresh_token},\n                middlewares=(),  # Disable middleware for this request\n            ) as resp:\n                resp.raise_for_status()\n                data = await resp.json()\n\n                if \"access_token\" not in data:\n                    raise ValueError(\"No access_token in refresh response\")\n\n                self.access_token = data[\"access_token\"]\n                # Token expires in 5 minutes for demo, refresh 30 seconds early\n                expires_in = data.get(\"expires_in\", 300)\n                self.token_expires_at = time.time() + expires_in - 30\n\n                _LOGGER.info(\n                    \"Token refreshed successfully, expires in %s seconds\", expires_in\n                )\n                if TYPE_CHECKING:\n                    assert self.access_token is not None  # Just assigned above\n                return self.access_token\n\n    async def __call__(\n        self,\n        request: ClientRequest,\n        handler: ClientHandlerType,\n    ) -> ClientResponse:\n        \"\"\"Add auth token to request, refreshing if needed.\"\"\"\n        # Skip token for refresh endpoint to avoid recursion\n        if str(request.url).endswith(\"/token/refresh\"):\n            return await handler(request)\n\n        # Refresh token if needed\n        if not self.access_token or (\n            self.token_expires_at and time.time() >= self.token_expires_at\n        ):\n            await self._refresh_access_token(request.session)\n\n        # Add token to request\n        request.headers[hdrs.AUTHORIZATION] = f\"Bearer {self.access_token}\"\n        _LOGGER.debug(\"Added Bearer token to request\")\n\n        # Execute request\n        response = await handler(request)\n\n        # If we get 401, try refreshing token once\n        if response.status == HTTPStatus.UNAUTHORIZED:\n            _LOGGER.info(\"Got 401, attempting token refresh...\")\n            await self._refresh_access_token(request.session)\n            request.headers[hdrs.AUTHORIZATION] = f\"Bearer {self.access_token}\"\n            response = await handler(request)\n\n        return response\n\n\nclass TestServer:\n    \"\"\"Test server with JWT-like token authentication.\"\"\"\n\n    def __init__(self) -> None:\n        self.tokens_db: dict[str, dict[str, str | float]] = {}\n        self.refresh_tokens_db: dict[str, dict[str, str | float]] = {\n            # Hash of refresh token -> user data\n            hashlib.sha256(b\"demo_refresh_token_12345\").hexdigest(): {\n                \"user_id\": \"user123\",\n                \"username\": \"testuser\",\n                \"issued_at\": time.time(),\n            }\n        }\n\n    def generate_access_token(self) -> str:\n        \"\"\"Generate a secure random access token.\"\"\"\n        return secrets.token_urlsafe(32)\n\n    async def _process_token_refresh(self, data: dict[str, str]) -> web.Response:\n        \"\"\"Process the token refresh request.\"\"\"\n        refresh_token = data.get(\"refresh_token\")\n\n        if not refresh_token:\n            return web.json_response({\"error\": \"refresh_token required\"}, status=400)\n\n        # Hash the refresh token to look it up\n        refresh_token_hash = hashlib.sha256(refresh_token.encode()).hexdigest()\n\n        if refresh_token_hash not in self.refresh_tokens_db:\n            return web.json_response({\"error\": \"Invalid refresh token\"}, status=401)\n\n        user_data = self.refresh_tokens_db[refresh_token_hash]\n\n        # Generate new access token\n        access_token = self.generate_access_token()\n        expires_in = 300  # 5 minutes for demo\n\n        # Store the access token with expiry\n        token_hash = hashlib.sha256(access_token.encode()).hexdigest()\n        self.tokens_db[token_hash] = {\n            \"user_id\": user_data[\"user_id\"],\n            \"username\": user_data[\"username\"],\n            \"expires_at\": time.time() + expires_in,\n            \"issued_at\": time.time(),\n        }\n\n        # Clean up expired tokens periodically\n        current_time = time.time()\n        self.tokens_db = {\n            k: v\n            for k, v in self.tokens_db.items()\n            if isinstance(v[\"expires_at\"], float) and v[\"expires_at\"] > current_time\n        }\n\n        return web.json_response(\n            {\n                \"access_token\": access_token,\n                \"token_type\": \"Bearer\",\n                \"expires_in\": expires_in,\n            }\n        )\n\n    async def handle_token_refresh(self, request: web.Request) -> web.Response:\n        \"\"\"Handle token refresh requests.\"\"\"\n        try:\n            data = await request.json()\n            return await self._process_token_refresh(data)\n        except json.JSONDecodeError:\n            return web.json_response({\"error\": \"Invalid request\"}, status=400)\n\n    async def verify_bearer_token(\n        self, request: web.Request\n    ) -> dict[str, str | float] | None:\n        \"\"\"Verify bearer token and return user data if valid.\"\"\"\n        auth_header = request.headers.get(hdrs.AUTHORIZATION, \"\")\n\n        if not auth_header.startswith(\"Bearer \"):\n            return None\n\n        token = auth_header[7:]  # Remove \"Bearer \"\n        token_hash = hashlib.sha256(token.encode()).hexdigest()\n\n        # Check if token exists and is not expired\n        if token_hash in self.tokens_db:\n            token_data = self.tokens_db[token_hash]\n            if (\n                isinstance(token_data[\"expires_at\"], float)\n                and token_data[\"expires_at\"] > time.time()\n            ):\n                return token_data\n\n        return None\n\n    async def handle_protected_resource(self, request: web.Request) -> web.Response:\n        \"\"\"Protected endpoint that requires valid bearer token.\"\"\"\n        user_data = await self.verify_bearer_token(request)\n\n        if not user_data:\n            return web.json_response({\"error\": \"Invalid or expired token\"}, status=401)\n\n        return web.json_response(\n            {\n                \"message\": \"Access granted to protected resource\",\n                \"user\": user_data[\"username\"],\n                \"data\": \"Secret information\",\n            }\n        )\n\n    async def handle_user_info(self, request: web.Request) -> web.Response:\n        \"\"\"Another protected endpoint.\"\"\"\n        user_data = await self.verify_bearer_token(request)\n\n        if not user_data:\n            return web.json_response({\"error\": \"Invalid or expired token\"}, status=401)\n\n        return web.json_response(\n            {\n                \"user_id\": user_data[\"user_id\"],\n                \"username\": user_data[\"username\"],\n                \"email\": f\"{user_data['username']}@example.com\",\n                \"roles\": [\"user\", \"admin\"],\n            }\n        )\n\n\nasync def run_test_server() -> web.AppRunner:\n    \"\"\"Run a test server with JWT auth endpoints.\"\"\"\n    test_server = TestServer()\n    app = web.Application()\n    app.router.add_post(\"/token/refresh\", test_server.handle_token_refresh)\n    app.router.add_get(\"/api/protected\", test_server.handle_protected_resource)\n    app.router.add_get(\"/api/user\", test_server.handle_user_info)\n\n    runner = web.AppRunner(app)\n    await runner.setup()\n    site = web.TCPSite(runner, \"localhost\", 8080)\n    await site.start()\n    return runner\n\n\nasync def run_tests() -> None:\n    \"\"\"Run all token refresh middleware tests.\"\"\"\n    # Create token refresh middleware\n    # In a real app, this refresh token would be securely stored\n    token_middleware = TokenRefreshMiddleware(\n        token_endpoint=\"http://localhost:8080/token/refresh\",\n        refresh_token=\"demo_refresh_token_12345\",\n    )\n\n    async with ClientSession(middlewares=(token_middleware,)) as session:\n        print(\"=== Test 1: First request (will trigger token refresh) ===\")\n        async with session.get(\"http://localhost:8080/api/protected\") as resp:\n            if resp.status == 200:\n                data = await resp.json()\n                print(f\"Success! Response: {data}\")\n            else:\n                print(f\"Failed with status: {resp.status}\")\n\n        print(\"\\n=== Test 2: Second request (uses cached token) ===\")\n        async with session.get(\"http://localhost:8080/api/user\") as resp:\n            if resp.status == 200:\n                data = await resp.json()\n                print(f\"User info: {data}\")\n            else:\n                print(f\"Failed with status: {resp.status}\")\n\n        print(\"\\n=== Test 3: Multiple concurrent requests ===\")\n        print(\"(Should only refresh token once)\")\n        coros: list[Coroutine[Any, Any, ClientResponse]] = []\n        for i in range(3):\n            coro = session.get(\"http://localhost:8080/api/protected\")\n            coros.append(coro)\n\n        responses = await asyncio.gather(*coros)\n        for i, resp in enumerate(responses):\n            async with resp:\n                if resp.status == 200:\n                    print(f\"Request {i + 1}: Success\")\n                else:\n                    print(f\"Request {i + 1}: Failed with {resp.status}\")\n\n        print(\"\\n=== Test 4: Simulate token expiry ===\")\n        # For demo purposes, force token expiry\n        token_middleware.token_expires_at = time.time() - 1\n\n        print(\"Token expired, next request should trigger refresh...\")\n        async with session.get(\"http://localhost:8080/api/protected\") as resp:\n            if resp.status == 200:\n                data = await resp.json()\n                print(f\"Success after token refresh! Response: {data}\")\n            else:\n                print(f\"Failed with status: {resp.status}\")\n\n        print(\"\\n=== Test 5: Request without middleware (no auth) ===\")\n        # Make a request without any middleware to show the difference\n        async with session.get(\n            \"http://localhost:8080/api/protected\",\n            middlewares=(),  # Bypass all middleware for this request\n        ) as resp:\n            print(f\"Status: {resp.status}\")\n            if resp.status == 401:\n                error = await resp.json()\n                print(f\"Failed as expected without auth: {error}\")\n\n\nasync def main() -> None:\n    # Start test server\n    server = await run_test_server()\n\n    try:\n        await run_tests()\n    finally:\n        await server.cleanup()\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "examples/web_classview.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Example for aiohttp.web class based views.\"\"\"\n\nimport functools\nimport json\n\nfrom aiohttp import web\n\n\nclass MyView(web.View):\n    async def get(self) -> web.StreamResponse:\n        return web.json_response(\n            {\n                \"method\": self.request.method,\n                \"args\": dict(self.request.rel_url.query),\n                \"headers\": dict(self.request.headers),\n            },\n            dumps=functools.partial(json.dumps, indent=4),\n        )\n\n    async def post(self) -> web.StreamResponse:\n        data = await self.request.post()\n        return web.json_response(\n            {\n                \"method\": self.request.method,\n                \"data\": dict(data),\n                \"headers\": dict(self.request.headers),\n            },\n            dumps=functools.partial(json.dumps, indent=4),\n        )\n\n\nasync def index(request: web.Request) -> web.StreamResponse:\n    txt = \"\"\"\n      <html>\n        <head>\n          <title>Class based view example</title>\n        </head>\n        <body>\n          <h1>Class based view example</h1>\n          <ul>\n            <li><a href=\"/\">/</a> This page\n            <li><a href=\"/get\">/get</a> Returns GET data.\n            <li><a href=\"/post\">/post</a> Returns POST data.\n          </ul>\n        </body>\n      </html>\n    \"\"\"\n    return web.Response(text=txt, content_type=\"text/html\")\n\n\ndef init() -> web.Application:\n    app = web.Application()\n    app.router.add_get(\"/\", index)\n    app.router.add_get(\"/get\", MyView)\n    app.router.add_post(\"/post\", MyView)\n    return app\n\n\nweb.run_app(init())\n"
  },
  {
    "path": "examples/web_cookies.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Example for aiohttp.web basic server with cookies.\"\"\"\n\nfrom pprint import pformat\nfrom typing import NoReturn\n\nfrom aiohttp import web\n\ntmpl = \"\"\"\\\n<html>\n    <body>\n        <a href=\"/login\">Login</a><br/>\n        <a href=\"/logout\">Logout</a><br/>\n        <pre>{}</pre>\n    </body>\n</html>\"\"\"\n\n\nasync def root(request: web.Request) -> web.StreamResponse:\n    resp = web.Response(content_type=\"text/html\")\n    resp.text = tmpl.format(pformat(request.cookies))\n    return resp\n\n\nasync def login(request: web.Request) -> NoReturn:\n    exc = web.HTTPFound(location=\"/\")\n    exc.set_cookie(\"AUTH\", \"secret\")\n    raise exc\n\n\nasync def logout(request: web.Request) -> NoReturn:\n    exc = web.HTTPFound(location=\"/\")\n    exc.del_cookie(\"AUTH\")\n    raise exc\n\n\ndef init() -> web.Application:\n    app = web.Application()\n    app.router.add_get(\"/\", root)\n    app.router.add_get(\"/login\", login)\n    app.router.add_get(\"/logout\", logout)\n    return app\n\n\nweb.run_app(init())\n"
  },
  {
    "path": "examples/web_rewrite_headers_middleware.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Example for rewriting response headers by middleware.\"\"\"\n\nfrom aiohttp import web\nfrom aiohttp.typedefs import Handler\n\n\nasync def handler(request: web.Request) -> web.StreamResponse:\n    return web.Response(text=\"Everything is fine\")\n\n\nasync def middleware(request: web.Request, handler: Handler) -> web.StreamResponse:\n    try:\n        response = await handler(request)\n    except web.HTTPException as exc:\n        raise exc\n    if not response.prepared:\n        response.headers[\"SERVER\"] = \"Secured Server Software\"\n    return response\n\n\ndef init() -> web.Application:\n    app = web.Application(middlewares=[middleware])\n    app.router.add_get(\"/\", handler)\n    return app\n\n\nweb.run_app(init())\n"
  },
  {
    "path": "examples/web_srv.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Example for aiohttp.web basic server.\"\"\"\n\nimport textwrap\n\nfrom aiohttp import web\n\n\nasync def intro(request: web.Request) -> web.StreamResponse:\n    txt = textwrap.dedent(\"\"\"\\\n        Type {url}/hello/John  {url}/simple or {url}/change_body\n        in browser url bar\n    \"\"\").format(url=\"127.0.0.1:8080\")\n    binary = txt.encode(\"utf8\")\n    resp = web.StreamResponse()\n    resp.content_length = len(binary)\n    resp.content_type = \"text/plain\"\n    await resp.prepare(request)\n    await resp.write(binary)\n    return resp\n\n\nasync def simple(request: web.Request) -> web.StreamResponse:\n    return web.Response(text=\"Simple answer\")\n\n\nasync def change_body(request: web.Request) -> web.StreamResponse:\n    resp = web.Response()\n    resp.body = b\"Body changed\"\n    resp.content_type = \"text/plain\"\n    return resp\n\n\nasync def hello(request: web.Request) -> web.StreamResponse:\n    resp = web.StreamResponse()\n    name = request.match_info.get(\"name\", \"Anonymous\")\n    answer = (\"Hello, \" + name).encode(\"utf8\")\n    resp.content_length = len(answer)\n    resp.content_type = \"text/plain\"\n    await resp.prepare(request)\n    await resp.write(answer)\n    await resp.write_eof()\n    return resp\n\n\ndef init() -> web.Application:\n    app = web.Application()\n    app.router.add_get(\"/\", intro)\n    app.router.add_get(\"/simple\", simple)\n    app.router.add_get(\"/change_body\", change_body)\n    app.router.add_get(\"/hello/{name}\", hello)\n    app.router.add_get(\"/hello\", hello)\n    return app\n\n\nweb.run_app(init())\n"
  },
  {
    "path": "examples/web_srv_route_deco.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Example for aiohttp.web basic server with decorator definition for routes.\"\"\"\n\nimport textwrap\n\nfrom aiohttp import web\n\nroutes = web.RouteTableDef()\n\n\n@routes.get(\"/\")\nasync def intro(request: web.Request) -> web.StreamResponse:\n    txt = textwrap.dedent(\"\"\"\\\n        Type {url}/hello/John  {url}/simple or {url}/change_body\n        in browser url bar\n    \"\"\").format(url=\"127.0.0.1:8080\")\n    binary = txt.encode(\"utf8\")\n    resp = web.StreamResponse()\n    resp.content_length = len(binary)\n    resp.content_type = \"text/plain\"\n    await resp.prepare(request)\n    await resp.write(binary)\n    return resp\n\n\n@routes.get(\"/simple\")\nasync def simple(request: web.Request) -> web.StreamResponse:\n    return web.Response(text=\"Simple answer\")\n\n\n@routes.get(\"/change_body\")\nasync def change_body(request: web.Request) -> web.StreamResponse:\n    resp = web.Response()\n    resp.body = b\"Body changed\"\n    resp.content_type = \"text/plain\"\n    return resp\n\n\n@routes.get(\"/hello\")\nasync def hello(request: web.Request) -> web.StreamResponse:\n    resp = web.StreamResponse()\n    name = request.match_info.get(\"name\", \"Anonymous\")\n    answer = (\"Hello, \" + name).encode(\"utf8\")\n    resp.content_length = len(answer)\n    resp.content_type = \"text/plain\"\n    await resp.prepare(request)\n    await resp.write(answer)\n    await resp.write_eof()\n    return resp\n\n\ndef init() -> web.Application:\n    app = web.Application()\n    app.router.add_routes(routes)\n    return app\n\n\nweb.run_app(init())\n"
  },
  {
    "path": "examples/web_srv_route_table.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Example for aiohttp.web basic server with table definition for routes.\"\"\"\n\nimport textwrap\n\nfrom aiohttp import web\n\n\nasync def intro(request: web.Request) -> web.StreamResponse:\n    txt = textwrap.dedent(\"\"\"\\\n        Type {url}/hello/John  {url}/simple or {url}/change_body\n        in browser url bar\n    \"\"\").format(url=\"127.0.0.1:8080\")\n    binary = txt.encode(\"utf8\")\n    resp = web.StreamResponse()\n    resp.content_length = len(binary)\n    resp.content_type = \"text/plain\"\n    await resp.prepare(request)\n    await resp.write(binary)\n    return resp\n\n\nasync def simple(request: web.Request) -> web.StreamResponse:\n    return web.Response(text=\"Simple answer\")\n\n\nasync def change_body(request: web.Request) -> web.StreamResponse:\n    resp = web.Response()\n    resp.body = b\"Body changed\"\n    resp.content_type = \"text/plain\"\n    return resp\n\n\nasync def hello(request: web.Request) -> web.StreamResponse:\n    resp = web.StreamResponse()\n    name = request.match_info.get(\"name\", \"Anonymous\")\n    answer = (\"Hello, \" + name).encode(\"utf8\")\n    resp.content_length = len(answer)\n    resp.content_type = \"text/plain\"\n    await resp.prepare(request)\n    await resp.write(answer)\n    await resp.write_eof()\n    return resp\n\n\ndef init() -> web.Application:\n    app = web.Application()\n    app.router.add_routes(\n        [\n            web.get(\"/\", intro),\n            web.get(\"/simple\", simple),\n            web.get(\"/change_body\", change_body),\n            web.get(\"/hello/{name}\", hello),\n            web.get(\"/hello\", hello),\n        ]\n    )\n    return app\n\n\nweb.run_app(init())\n"
  },
  {
    "path": "examples/web_ws.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Example for aiohttp.web websocket server.\"\"\"\n\n# The extra strict mypy settings are here to help test that `Application[AppKey()]`\n# syntax is working correctly. A regression will cause mypy to raise an error.\n# mypy: disallow-any-expr, disallow-any-unimported, disallow-subclassing-any\n\nimport os\n\nfrom aiohttp import web\n\nWS_FILE = os.path.join(os.path.dirname(__file__), \"websocket.html\")\nsockets = web.AppKey(\"sockets\", list[web.WebSocketResponse])\n\n\nasync def wshandler(request: web.Request) -> web.WebSocketResponse | web.Response:\n    resp = web.WebSocketResponse()\n    available = resp.can_prepare(request)\n    if not available:\n        with open(WS_FILE, \"rb\") as fp:\n            return web.Response(body=fp.read(), content_type=\"text/html\")\n\n    await resp.prepare(request)\n\n    await resp.send_str(\"Welcome!!!\")\n\n    try:\n        print(\"Someone joined.\")\n        for ws in request.app[sockets]:\n            await ws.send_str(\"Someone joined\")\n        request.app[sockets].append(resp)\n\n        async for msg in resp:\n            if msg.type is web.WSMsgType.TEXT:\n                for ws in request.app[sockets]:\n                    if ws is not resp:\n                        await ws.send_str(msg.data)\n            else:\n                return resp\n        return resp\n\n    finally:\n        request.app[sockets].remove(resp)\n        print(\"Someone disconnected.\")\n        for ws in request.app[sockets]:\n            await ws.send_str(\"Someone disconnected.\")\n\n\nasync def on_shutdown(app: web.Application) -> None:\n    for ws in app[sockets]:\n        await ws.close()\n\n\ndef init() -> web.Application:\n    app = web.Application()\n    l: list[web.WebSocketResponse] = []\n    app[sockets] = l\n    app.router.add_get(\"/\", wshandler)\n    app.on_shutdown.append(on_shutdown)\n    return app\n\n\nweb.run_app(init())\n"
  },
  {
    "path": "examples/websocket.html",
    "content": "<!DOCTYPE html>\n<meta charset=\"utf-8\" />\n<html>\n<head>\n  <script language=\"javascript\" type=\"text/javascript\">\n    var socket = null;\n\n    function log(msg) {\n        const logElem = document.getElementById(\"log\");\n        const p = document.createElement(\"p\");\n        p.textContent = msg;\n        logElem.appendChild(p);\n        logElem.scroll(0, logElem.scrollHeight);\n    }\n\n    function connect() {\n        disconnect();\n        socket = new WebSocket(document.getElementById(\"wsUri\").value);\n        log(\"Connecting...\");\n        socket.addEventListener(\"open\", function() {\n            log(\"Connected.\");\n            update_ui();\n        });\n        socket.addEventListener(\"message\", function(e) {\n            log(\"Received: \" + e.data);\n        });\n        socket.addEventListener(\"close\", function() {\n            log(\"Disconnected.\");\n            socket = null;\n            update_ui();\n        });\n    }\n\n    function disconnect() {\n        if (socket !== null) {\n            log(\"Disconnecting...\");\n            socket.close();\n            socket = null;\n            update_ui();\n        }\n    }\n\n    function update_ui() {\n        const status = document.getElementById(\"status\");\n        const connect = document.getElementById(\"connect\");\n        if (socket === null) {\n            status.innerText = \"disconnected\";\n            connect.innerText = \"Connect\";\n        } else {\n            status.innerText = \"connected (\" + socket.protocol + \")\";\n            connect.innerText = \"Disconnect\";\n        }\n    }\n\n    window.addEventListener(\"DOMContentLoaded\", function() {\n        const protocol = (window.location.protocol==\"https:\" && \"wss://\" || \"ws://\");\n        document.getElementById(\"wsUri\").value = protocol + (window.location.host || \"localhost:8080\");\n\n        document.getElementById(\"connect\").addEventListener(\"click\", function() {\n            if (socket == null) {\n                connect();\n            } else {\n                disconnect();\n            }\n            update_ui();\n            return false;\n        });\n\n        document.getElementById(\"send\").addEventListener(\"click\", function() {\n            const text = document.getElementById(\"text\");\n            log(\"Sending: \" + text.value);\n            socket.send(text.value);\n            text.value = \"\";\n            text.focus();\n            return false;\n        });\n\n        document.getElementById(\"text\").addEventListener(\"keyup\", function(e) {\n            if (e.keyCode === 13) {\n                document.getElementById(\"send\").click();\n                return false;\n            }\n        });\n    });\n</script>\n</head>\n<body>\n<h3>Chat!</h3>\n<div>\n  <input id=\"wsUri\" type=\"text\" />\n  <button id=\"connect\">Connect</button>&nbsp;|&nbsp;Status:\n  <span id=\"status\">disconnected</span>\n</div>\n<div id=\"log\"\n     style=\"width:20em;height:15em;overflow:auto;border:1px solid black\">\n</div>\n<form id=\"chatform\" onsubmit=\"return false;\">\n  <input id=\"text\" type=\"text\" />\n  <input id=\"send\" type=\"button\" value=\"Send\" />\n</form>\n</body>\n</html>\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\n    \"pkgconfig\",\n    # setuptools >= 67.0 required for Python 3.12+ support\n    # Next step should be >= 77.0 for PEP 639 support\n    # Don't bump too early to give distributors time to update\n    # their setuptools version.\n    \"setuptools >= 67.0\",\n]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname        = \"aiohttp\"\n# TODO: Update to just 'license = \"...\"' once setuptools is bumped to >=77\nlicense     = {text = \"Apache-2.0 AND MIT\"}\ndescription = \"Async http client/server framework (asyncio)\"\nreadme      = \"README.rst\"\nclassifiers = [\n  \"Development Status :: 5 - Production/Stable\",\n  \"Framework :: AsyncIO\",\n  \"Intended Audience :: Developers\",\n  \"Operating System :: POSIX\",\n  \"Operating System :: MacOS :: MacOS X\",\n  \"Operating System :: Microsoft :: Windows\",\n  \"Programming Language :: Python\",\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  \"Topic :: Internet :: WWW/HTTP\",\n]\nrequires-python = \">= 3.10\"\ndependencies = [\n  \"aiohappyeyeballs >= 2.5.0\",\n  \"aiosignal >= 1.4.0\",\n  \"async-timeout >= 4.0, < 6.0 ; python_version < '3.11'\",\n  \"frozenlist >= 1.1.1\",\n  \"multidict >=4.5, < 7.0\",\n  \"propcache >= 0.2.0\",\n  \"typing_extensions >= 4.4 ; python_version < '3.13'\",\n  \"yarl >= 1.17.0, < 2.0\",\n]\ndynamic = [\n  \"version\",\n]\n\n[project.optional-dependencies]\nspeedups = [\n  \"aiodns >= 3.3.0\",\n  \"Brotli >= 1.2; platform_python_implementation == 'CPython'\",\n  \"brotlicffi >= 1.2; platform_python_implementation != 'CPython'\",\n  \"backports.zstd; platform_python_implementation == 'CPython' and python_version < '3.14'\",\n]\n\n[[project.maintainers]]\nname = \"aiohttp team\"\nemail = \"team@aiohttp.org\"\n\n[project.urls]\n\"Homepage\"           = \"https://github.com/aio-libs/aiohttp\"\n\"Chat: Matrix\"       = \"https://matrix.to/#/#aio-libs:matrix.org\"\n\"Chat: Matrix Space\" = \"https://matrix.to/#/#aio-libs-space:matrix.org\"\n\"CI: GitHub Actions\" = \"https://github.com/aio-libs/aiohttp/actions?query=workflow%3ACI\"\n\"Coverage: codecov\"  = \"https://codecov.io/github/aio-libs/aiohttp\"\n\"Docs: Changelog\"    = \"https://docs.aiohttp.org/en/stable/changes.html\"\n\"Docs: RTD\"          = \"https://docs.aiohttp.org\"\n\"GitHub: issues\"     = \"https://github.com/aio-libs/aiohttp/issues\"\n\"GitHub: repo\"       = \"https://github.com/aio-libs/aiohttp\"\n\n[tool.setuptools]\nlicense-files = [\n  # TODO: Use 'project.license-files' instead once setuptools is bumped to >=77\n  \"LICENSE.txt\",\n  \"vendor/llhttp/LICENSE\",\n]\n\n[tool.setuptools.dynamic]\nversion = {attr = \"aiohttp.__version__\"}\n\n[tool.setuptools.packages.find]\ninclude = [\n  \"aiohttp\",\n  \"aiohttp.*\",\n]\n\n[tool.setuptools.exclude-package-data]\n\"*\" = [\"*.c\", \"*.h\"]\n\n[tool.towncrier]\n  package = \"aiohttp\"\n  filename = \"CHANGES.rst\"\n  directory = \"CHANGES/\"\n  title_format = \"{version} ({project_date})\"\n  template = \"CHANGES/.TEMPLATE.rst\"\n  issue_format = \"{issue}\"\n\n  # NOTE: The types are declared because:\n  # NOTE: - there is no mechanism to override just the value of\n  # NOTE:   `tool.towncrier.type.misc.showcontent`;\n  # NOTE: - and, we want to declare extra non-default types for\n  # NOTE:   clarity and flexibility.\n\n  [[tool.towncrier.section]]\n    path = \"\"\n\n  [[tool.towncrier.type]]\n    # Something we deemed an improper undesired behavior that got corrected\n    # in the release to match pre-agreed expectations.\n    directory = \"bugfix\"\n    name = \"Bug fixes\"\n    showcontent = true\n\n  [[tool.towncrier.type]]\n    # New behaviors, public APIs. That sort of stuff.\n    directory = \"feature\"\n    name = \"Features\"\n    showcontent = true\n\n  [[tool.towncrier.type]]\n    # Declarations of future API removals and breaking changes in behavior.\n    directory = \"deprecation\"\n    name = \"Deprecations (removal in next major release)\"\n    showcontent = true\n\n  [[tool.towncrier.type]]\n    # When something public gets removed in a breaking way. Could be\n    # deprecated in an earlier release.\n    directory = \"breaking\"\n    name = \"Removals and backward incompatible breaking changes\"\n    showcontent = true\n\n  [[tool.towncrier.type]]\n    # Notable updates to the documentation structure or build process.\n    directory = \"doc\"\n    name = \"Improved documentation\"\n    showcontent = true\n\n  [[tool.towncrier.type]]\n    # Notes for downstreams about unobvious side effects and tooling. Changes\n    # in the test invocation considerations and runtime assumptions.\n    directory = \"packaging\"\n    name = \"Packaging updates and notes for downstreams\"\n    showcontent = true\n\n  [[tool.towncrier.type]]\n    # Stuff that affects the contributor experience. e.g. Running tests,\n    # building the docs, setting up the development environment.\n    directory = \"contrib\"\n    name = \"Contributor-facing changes\"\n    showcontent = true\n\n  [[tool.towncrier.type]]\n    # Changes that are hard to assign to any of the above categories.\n    directory = \"misc\"\n    name = \"Miscellaneous internal changes\"\n    showcontent = true\n\n\n[tool.cibuildwheel]\ntest-command = \"\"\n# don't build PyPy wheels, install from source instead\nskip = \"pp*\"\n\n[tool.codespell]\nskip = '.git,*.pdf,*.svg,Makefile,CONTRIBUTORS.txt,venvs,_build'\nignore-words-list = 'te,assertIn'\n\n[tool.slotscheck]\n# TODO(3.13): Remove aiohttp.helpers once https://github.com/python/cpython/pull/106771\n# is available in all supported cpython versions\nexclude-modules = \"(^aiohttp\\\\.helpers)\"\n"
  },
  {
    "path": "requirements/base-ft.in",
    "content": "-r runtime-deps.in\n\ngunicorn\n"
  },
  {
    "path": "requirements/base-ft.txt",
    "content": "#\n# This file is autogenerated by pip-compile with Python 3.10\n# by the following command:\n#\n#    pip-compile --allow-unsafe --output-file=requirements/base-ft.txt --strip-extras requirements/base-ft.in\n#\naiodns==4.0.0\n    # via -r requirements/runtime-deps.in\naiohappyeyeballs==2.6.1\n    # via -r requirements/runtime-deps.in\naiosignal==1.4.0\n    # via -r requirements/runtime-deps.in\nasync-timeout==5.0.1 ; python_version < \"3.11\"\n    # via -r requirements/runtime-deps.in\nbackports-zstd==1.3.0 ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"\n    # via -r requirements/runtime-deps.in\nbrotli==1.2.0 ; platform_python_implementation == \"CPython\"\n    # via -r requirements/runtime-deps.in\ncffi==2.0.0\n    # via pycares\nfrozenlist==1.8.0\n    # via\n    #   -r requirements/runtime-deps.in\n    #   aiosignal\ngunicorn==25.1.0\n    # via -r requirements/base-ft.in\nidna==3.11\n    # via yarl\nmultidict==6.7.1\n    # via\n    #   -r requirements/runtime-deps.in\n    #   yarl\npackaging==26.0\n    # via gunicorn\npropcache==0.4.1\n    # via\n    #   -r requirements/runtime-deps.in\n    #   yarl\npycares==5.0.1\n    # via aiodns\npycparser==3.0\n    # via cffi\ntyping-extensions==4.15.0 ; python_version < \"3.13\"\n    # via\n    #   -r requirements/runtime-deps.in\n    #   aiosignal\n    #   multidict\nyarl==1.22.0\n    # via -r requirements/runtime-deps.in\n"
  },
  {
    "path": "requirements/base.in",
    "content": "-r runtime-deps.in\n\ngunicorn\nuvloop; platform_system != \"Windows\" and implementation_name == \"cpython\"  # MagicStack/uvloop#14\nwinloop; platform_system == \"Windows\" and implementation_name == \"cpython\"\n"
  },
  {
    "path": "requirements/base.txt",
    "content": "#\n# This file is autogenerated by pip-compile with Python 3.10\n# by the following command:\n#\n#    pip-compile --allow-unsafe --output-file=requirements/base.txt --strip-extras requirements/base.in\n#\naiodns==4.0.0\n    # via -r requirements/runtime-deps.in\naiohappyeyeballs==2.6.1\n    # via -r requirements/runtime-deps.in\naiosignal==1.4.0\n    # via -r requirements/runtime-deps.in\nasync-timeout==5.0.1 ; python_version < \"3.11\"\n    # via -r requirements/runtime-deps.in\nbackports-zstd==1.3.0 ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"\n    # via -r requirements/runtime-deps.in\nbrotli==1.2.0 ; platform_python_implementation == \"CPython\"\n    # via -r requirements/runtime-deps.in\ncffi==2.0.0\n    # via pycares\nfrozenlist==1.8.0\n    # via\n    #   -r requirements/runtime-deps.in\n    #   aiosignal\ngunicorn==25.1.0\n    # via -r requirements/base.in\nidna==3.11\n    # via yarl\nmultidict==6.7.1\n    # via\n    #   -r requirements/runtime-deps.in\n    #   yarl\npackaging==26.0\n    # via gunicorn\npropcache==0.4.1\n    # via\n    #   -r requirements/runtime-deps.in\n    #   yarl\npycares==5.0.1\n    # via aiodns\npycparser==3.0\n    # via cffi\ntyping-extensions==4.15.0 ; python_version < \"3.13\"\n    # via\n    #   -r requirements/runtime-deps.in\n    #   aiosignal\n    #   multidict\nuvloop==0.21.0 ; platform_system != \"Windows\" and implementation_name == \"cpython\"\n    # via -r requirements/base.in\nyarl==1.22.0\n    # via -r requirements/runtime-deps.in\n"
  },
  {
    "path": "requirements/constraints.in",
    "content": "-r cython.in\n-r dev.in\n-r doc-spelling.in\n-r lint.in\n"
  },
  {
    "path": "requirements/constraints.txt",
    "content": "#\n# This file is autogenerated by pip-compile with Python 3.10\n# by the following command:\n#\n#    pip-compile --allow-unsafe --output-file=requirements/constraints.txt --strip-extras requirements/constraints.in\n#\naiodns==4.0.0\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/runtime-deps.in\naiohappyeyeballs==2.6.1\n    # via -r requirements/runtime-deps.in\naiohttp-theme==0.1.7\n    # via -r requirements/doc.in\naiosignal==1.4.0\n    # via -r requirements/runtime-deps.in\nalabaster==1.0.0\n    # via sphinx\nannotated-types==0.7.0\n    # via pydantic\nasync-timeout==5.0.1 ; python_version < \"3.11\"\n    # via\n    #   -r requirements/runtime-deps.in\n    #   valkey\nbabel==2.18.0\n    # via sphinx\nbackports-zstd==1.3.0 ; implementation_name == \"cpython\"\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/runtime-deps.in\nblockbuster==1.5.26\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\nbrotli==1.2.0 ; platform_python_implementation == \"CPython\"\n    # via -r requirements/runtime-deps.in\nbuild==1.4.0\n    # via pip-tools\ncertifi==2026.2.25\n    # via requests\ncffi==2.0.0\n    # via\n    #   cryptography\n    #   pycares\n    #   pytest-codspeed\ncfgv==3.5.0\n    # via pre-commit\ncharset-normalizer==3.4.6\n    # via requests\nclick==8.3.1\n    # via\n    #   pip-tools\n    #   slotscheck\n    #   towncrier\n    #   wait-for-it\ncoverage==7.13.5\n    # via\n    #   -r requirements/test-common.in\n    #   pytest-cov\ncryptography==46.0.5\n    # via trustme\ncython==3.2.4\n    # via -r requirements/cython.in\ndistlib==0.4.0\n    # via virtualenv\ndocutils==0.21.2\n    # via sphinx\nexceptiongroup==1.3.1\n    # via pytest\nexecnet==2.1.2\n    # via pytest-xdist\nfilelock==3.25.2\n    # via\n    #   python-discovery\n    #   virtualenv\nforbiddenfruit==0.1.4\n    # via blockbuster\nfreezegun==1.5.5\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\nfrozenlist==1.8.0\n    # via\n    #   -r requirements/runtime-deps.in\n    #   aiosignal\ngunicorn==25.1.0\n    # via -r requirements/base.in\nidentify==2.6.17\n    # via pre-commit\nidna==3.11\n    # via\n    #   requests\n    #   trustme\n    #   yarl\nimagesize==2.0.0\n    # via sphinx\niniconfig==2.3.0\n    # via pytest\nisal==1.7.2 ; python_version < \"3.14\"\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\njinja2==3.1.6\n    # via\n    #   sphinx\n    #   towncrier\nlibrt==0.8.0\n    # via mypy\nmarkdown-it-py==4.0.0\n    # via rich\nmarkupsafe==3.0.3\n    # via jinja2\nmdurl==0.1.2\n    # via markdown-it-py\nmultidict==6.7.1\n    # via\n    #   -r requirements/multidict.in\n    #   -r requirements/runtime-deps.in\n    #   yarl\nmypy==1.19.1 ; implementation_name == \"cpython\"\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\nmypy-extensions==1.1.0\n    # via mypy\nnodeenv==1.10.0\n    # via pre-commit\npackaging==26.0\n    # via\n    #   build\n    #   gunicorn\n    #   pytest\n    #   sphinx\n    #   wheel\npathspec==1.0.4\n    # via mypy\npip-tools==7.5.3\n    # via -r requirements/dev.in\npkgconfig==1.6.0\n    # via -r requirements/test-common.in\nplatformdirs==4.9.4\n    # via\n    #   python-discovery\n    #   virtualenv\npluggy==1.6.0\n    # via\n    #   pytest\n    #   pytest-cov\npre-commit==4.5.1\n    # via -r requirements/lint.in\npropcache==0.4.1\n    # via\n    #   -r requirements/runtime-deps.in\n    #   yarl\nproxy-py==2.4.10\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\npycares==5.0.1\n    # via aiodns\npycparser==3.0\n    # via cffi\npydantic==2.12.5\n    # via python-on-whales\npydantic-core==2.41.5\n    # via pydantic\npyenchant==3.3.0\n    # via sphinxcontrib-spelling\npygments==2.19.2\n    # via\n    #   pytest\n    #   rich\n    #   sphinx\npyproject-hooks==1.2.0\n    # via\n    #   build\n    #   pip-tools\npytest==9.0.2\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\n    #   pytest-codspeed\n    #   pytest-cov\n    #   pytest-mock\n    #   pytest-xdist\npytest-codspeed==4.3.0\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\npytest-cov==7.0.0\n    # via -r requirements/test-common.in\npytest-mock==3.15.1\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\npytest-xdist==3.8.0\n    # via -r requirements/test-common.in\npython-dateutil==2.9.0.post0\n    # via freezegun\npython-discovery==1.2.0\n    # via virtualenv\npython-on-whales==0.81.0\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\npyyaml==6.0.3\n    # via pre-commit\nrequests==2.32.5\n    # via\n    #   sphinx\n    #   sphinxcontrib-spelling\nrich==14.3.3\n    # via pytest-codspeed\nsetuptools-git==1.2\n    # via -r requirements/test-common.in\nsix==1.17.0\n    # via python-dateutil\nslotscheck==0.19.1\n    # via -r requirements/lint.in\nsnowballstemmer==3.0.1\n    # via sphinx\nsphinx==8.1.3\n    # via\n    #   -r requirements/doc.in\n    #   sphinxcontrib-spelling\n    #   sphinxcontrib-towncrier\nsphinxcontrib-applehelp==2.0.0\n    # via sphinx\nsphinxcontrib-devhelp==2.0.0\n    # via sphinx\nsphinxcontrib-htmlhelp==2.1.0\n    # via sphinx\nsphinxcontrib-jsmath==1.0.1\n    # via sphinx\nsphinxcontrib-qthelp==2.0.0\n    # via sphinx\nsphinxcontrib-serializinghtml==2.0.0\n    # via sphinx\nsphinxcontrib-spelling==8.0.2 ; platform_system != \"Windows\"\n    # via -r requirements/doc-spelling.in\nsphinxcontrib-towncrier==0.5.0a0\n    # via -r requirements/doc.in\ntomli==2.4.0\n    # via\n    #   build\n    #   coverage\n    #   mypy\n    #   pip-tools\n    #   pytest\n    #   slotscheck\n    #   sphinx\n    #   towncrier\ntowncrier==25.8.0\n    # via\n    #   -r requirements/doc.in\n    #   sphinxcontrib-towncrier\ntrustme==1.2.1 ; platform_machine != \"i686\"\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\ntyping-extensions==4.15.0 ; python_version < \"3.13\"\n    # via\n    #   -r requirements/runtime-deps.in\n    #   aiosignal\n    #   cryptography\n    #   exceptiongroup\n    #   multidict\n    #   mypy\n    #   pydantic\n    #   pydantic-core\n    #   python-on-whales\n    #   typing-inspection\n    #   virtualenv\ntyping-inspection==0.4.2\n    # via pydantic\nurllib3==2.6.3\n    # via requests\nuvloop==0.21.0 ; platform_system != \"Windows\"\n    # via\n    #   -r requirements/base.in\n    #   -r requirements/lint.in\nvalkey==6.1.1\n    # via -r requirements/lint.in\nvirtualenv==21.2.0\n    # via pre-commit\nwait-for-it==2.3.0\n    # via -r requirements/test-common.in\nwheel==0.46.3\n    # via pip-tools\nyarl==1.22.0\n    # via -r requirements/runtime-deps.in\nzlib-ng==1.0.0\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\n\n# The following packages are considered to be unsafe in a requirements file:\npip==26.0.1\n    # via pip-tools\nsetuptools==82.0.1\n    # via pip-tools\n"
  },
  {
    "path": "requirements/cython.in",
    "content": "-r multidict.in\n\nCython\n"
  },
  {
    "path": "requirements/cython.txt",
    "content": "#\n# This file is autogenerated by pip-compile with python 3.10\n# by the following command:\n#\n#    pip-compile --allow-unsafe --output-file=requirements/cython.txt --resolver=backtracking --strip-extras requirements/cython.in\n#\ncython==3.2.4\n    # via -r requirements/cython.in\nmultidict==6.7.1\n    # via -r requirements/multidict.in\ntyping-extensions==4.15.0\n    # via multidict\n"
  },
  {
    "path": "requirements/dev.in",
    "content": "-r lint.in\n-r test.in\n-r doc.in\n\npip-tools\n"
  },
  {
    "path": "requirements/dev.txt",
    "content": "#\n# This file is autogenerated by pip-compile with Python 3.10\n# by the following command:\n#\n#    pip-compile --allow-unsafe --output-file=requirements/dev.txt --strip-extras requirements/dev.in\n#\naiodns==4.0.0\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/runtime-deps.in\naiohappyeyeballs==2.6.1\n    # via -r requirements/runtime-deps.in\naiohttp-theme==0.1.7\n    # via -r requirements/doc.in\naiosignal==1.4.0\n    # via -r requirements/runtime-deps.in\nalabaster==1.0.0\n    # via sphinx\nannotated-types==0.7.0\n    # via pydantic\nasync-timeout==5.0.1 ; python_version < \"3.11\"\n    # via\n    #   -r requirements/runtime-deps.in\n    #   valkey\nbabel==2.18.0\n    # via sphinx\nbackports-zstd==1.3.0 ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/runtime-deps.in\nblockbuster==1.5.26\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\nbrotli==1.2.0 ; platform_python_implementation == \"CPython\"\n    # via -r requirements/runtime-deps.in\nbuild==1.4.0\n    # via pip-tools\ncertifi==2026.2.25\n    # via requests\ncffi==2.0.0\n    # via\n    #   cryptography\n    #   pycares\n    #   pytest-codspeed\ncfgv==3.5.0\n    # via pre-commit\ncharset-normalizer==3.4.6\n    # via requests\nclick==8.3.1\n    # via\n    #   pip-tools\n    #   slotscheck\n    #   towncrier\n    #   wait-for-it\ncoverage==7.13.5\n    # via\n    #   -r requirements/test-common.in\n    #   pytest-cov\ncryptography==46.0.5\n    # via trustme\ndistlib==0.4.0\n    # via virtualenv\ndocutils==0.21.2\n    # via sphinx\nexceptiongroup==1.3.1\n    # via pytest\nexecnet==2.1.2\n    # via pytest-xdist\nfilelock==3.25.2\n    # via\n    #   python-discovery\n    #   virtualenv\nforbiddenfruit==0.1.4\n    # via blockbuster\nfreezegun==1.5.5\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\nfrozenlist==1.8.0\n    # via\n    #   -r requirements/runtime-deps.in\n    #   aiosignal\ngunicorn==25.1.0\n    # via -r requirements/base.in\nidentify==2.6.17\n    # via pre-commit\nidna==3.11\n    # via\n    #   requests\n    #   trustme\n    #   yarl\nimagesize==2.0.0\n    # via sphinx\niniconfig==2.3.0\n    # via pytest\nisal==1.7.2 ; python_version < \"3.14\"\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\njinja2==3.1.6\n    # via\n    #   sphinx\n    #   towncrier\nlibrt==0.8.0\n    # via mypy\nmarkdown-it-py==4.0.0\n    # via rich\nmarkupsafe==3.0.3\n    # via jinja2\nmdurl==0.1.2\n    # via markdown-it-py\nmultidict==6.7.1\n    # via\n    #   -r requirements/runtime-deps.in\n    #   yarl\nmypy==1.19.1 ; implementation_name == \"cpython\"\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\nmypy-extensions==1.1.0\n    # via mypy\nnodeenv==1.10.0\n    # via pre-commit\npackaging==26.0\n    # via\n    #   build\n    #   gunicorn\n    #   pytest\n    #   sphinx\n    #   wheel\npathspec==1.0.4\n    # via mypy\npip-tools==7.5.3\n    # via -r requirements/dev.in\npkgconfig==1.6.0\n    # via -r requirements/test-common.in\nplatformdirs==4.9.4\n    # via\n    #   python-discovery\n    #   virtualenv\npluggy==1.6.0\n    # via\n    #   pytest\n    #   pytest-cov\npre-commit==4.5.1\n    # via -r requirements/lint.in\npropcache==0.4.1\n    # via\n    #   -r requirements/runtime-deps.in\n    #   yarl\nproxy-py==2.4.10\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\npycares==5.0.1\n    # via aiodns\npycparser==3.0\n    # via cffi\npydantic==2.12.5\n    # via python-on-whales\npydantic-core==2.41.5\n    # via pydantic\npygments==2.19.2\n    # via\n    #   pytest\n    #   rich\n    #   sphinx\npyproject-hooks==1.2.0\n    # via\n    #   build\n    #   pip-tools\npytest==9.0.2\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\n    #   pytest-codspeed\n    #   pytest-cov\n    #   pytest-mock\n    #   pytest-xdist\npytest-codspeed==4.3.0\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\npytest-cov==7.0.0\n    # via -r requirements/test-common.in\npytest-mock==3.15.1\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\npytest-xdist==3.8.0\n    # via -r requirements/test-common.in\npython-dateutil==2.9.0.post0\n    # via freezegun\npython-discovery==1.2.0\n    # via virtualenv\npython-on-whales==0.81.0\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\npyyaml==6.0.3\n    # via pre-commit\nrequests==2.32.5\n    # via sphinx\nrich==14.3.3\n    # via pytest-codspeed\nsetuptools-git==1.2\n    # via -r requirements/test-common.in\nsix==1.17.0\n    # via python-dateutil\nslotscheck==0.19.1\n    # via -r requirements/lint.in\nsnowballstemmer==3.0.1\n    # via sphinx\nsphinx==8.1.3\n    # via\n    #   -r requirements/doc.in\n    #   sphinxcontrib-towncrier\nsphinxcontrib-applehelp==2.0.0\n    # via sphinx\nsphinxcontrib-devhelp==2.0.0\n    # via sphinx\nsphinxcontrib-htmlhelp==2.1.0\n    # via sphinx\nsphinxcontrib-jsmath==1.0.1\n    # via sphinx\nsphinxcontrib-qthelp==2.0.0\n    # via sphinx\nsphinxcontrib-serializinghtml==2.0.0\n    # via sphinx\nsphinxcontrib-towncrier==0.5.0a0\n    # via -r requirements/doc.in\ntomli==2.4.0\n    # via\n    #   build\n    #   coverage\n    #   mypy\n    #   pip-tools\n    #   pytest\n    #   slotscheck\n    #   sphinx\n    #   towncrier\ntowncrier==25.8.0\n    # via\n    #   -r requirements/doc.in\n    #   sphinxcontrib-towncrier\ntrustme==1.2.1 ; platform_machine != \"i686\"\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\ntyping-extensions==4.15.0 ; python_version < \"3.13\"\n    # via\n    #   -r requirements/runtime-deps.in\n    #   aiosignal\n    #   cryptography\n    #   exceptiongroup\n    #   multidict\n    #   mypy\n    #   pydantic\n    #   pydantic-core\n    #   python-on-whales\n    #   typing-inspection\n    #   virtualenv\ntyping-inspection==0.4.2\n    # via pydantic\nurllib3==2.6.3\n    # via requests\nuvloop==0.21.0 ; platform_system != \"Windows\" and implementation_name == \"cpython\"\n    # via\n    #   -r requirements/base.in\n    #   -r requirements/lint.in\nvalkey==6.1.1\n    # via -r requirements/lint.in\nvirtualenv==21.2.0\n    # via pre-commit\nwait-for-it==2.3.0\n    # via -r requirements/test-common.in\nwheel==0.46.3\n    # via pip-tools\nyarl==1.22.0\n    # via -r requirements/runtime-deps.in\nzlib-ng==1.0.0\n    # via\n    #   -r requirements/lint.in\n    #   -r requirements/test-common.in\n\n# The following packages are considered to be unsafe in a requirements file:\npip==26.0.1\n    # via pip-tools\nsetuptools==82.0.1\n    # via pip-tools\n"
  },
  {
    "path": "requirements/doc-spelling.in",
    "content": "-r doc.in\n\nsphinxcontrib-spelling; platform_system!=\"Windows\"  # We only use it in GitHub Actions CI/CD\n"
  },
  {
    "path": "requirements/doc-spelling.txt",
    "content": "#\n# This file is autogenerated by pip-compile with Python 3.10\n# by the following command:\n#\n#    pip-compile --allow-unsafe --output-file=requirements/doc-spelling.txt --strip-extras requirements/doc-spelling.in\n#\naiohttp-theme==0.1.7\n    # via -r requirements/doc.in\nalabaster==1.0.0\n    # via sphinx\nbabel==2.18.0\n    # via sphinx\ncertifi==2026.2.25\n    # via requests\ncharset-normalizer==3.4.6\n    # via requests\nclick==8.3.1\n    # via towncrier\ndocutils==0.21.2\n    # via sphinx\nidna==3.11\n    # via requests\nimagesize==2.0.0\n    # via sphinx\njinja2==3.1.6\n    # via\n    #   sphinx\n    #   towncrier\nmarkupsafe==3.0.3\n    # via jinja2\npackaging==26.0\n    # via sphinx\npyenchant==3.3.0\n    # via sphinxcontrib-spelling\npygments==2.19.2\n    # via sphinx\nrequests==2.32.5\n    # via\n    #   sphinx\n    #   sphinxcontrib-spelling\nsnowballstemmer==3.0.1\n    # via sphinx\nsphinx==8.1.3\n    # via\n    #   -r requirements/doc.in\n    #   sphinxcontrib-spelling\n    #   sphinxcontrib-towncrier\nsphinxcontrib-applehelp==2.0.0\n    # via sphinx\nsphinxcontrib-devhelp==2.0.0\n    # via sphinx\nsphinxcontrib-htmlhelp==2.1.0\n    # via sphinx\nsphinxcontrib-jsmath==1.0.1\n    # via sphinx\nsphinxcontrib-qthelp==2.0.0\n    # via sphinx\nsphinxcontrib-serializinghtml==2.0.0\n    # via sphinx\nsphinxcontrib-spelling==8.0.2 ; platform_system != \"Windows\"\n    # via -r requirements/doc-spelling.in\nsphinxcontrib-towncrier==0.5.0a0\n    # via -r requirements/doc.in\ntomli==2.4.0\n    # via\n    #   sphinx\n    #   towncrier\ntowncrier==25.8.0\n    # via\n    #   -r requirements/doc.in\n    #   sphinxcontrib-towncrier\nurllib3==2.6.3\n    # via requests\n"
  },
  {
    "path": "requirements/doc.in",
    "content": "aiohttp-theme\nsphinx\nsphinxcontrib-towncrier\ntowncrier\n"
  },
  {
    "path": "requirements/doc.txt",
    "content": "#\n# This file is autogenerated by pip-compile with python 3.10\n# To update, run:\n#\n#    pip-compile --allow-unsafe --output-file=requirements/doc.txt --resolver=backtracking --strip-extras requirements/doc.in\n#\naiohttp-theme==0.1.7\n    # via -r requirements/doc.in\nalabaster==1.0.0\n    # via sphinx\nbabel==2.18.0\n    # via sphinx\ncertifi==2026.2.25\n    # via requests\ncharset-normalizer==3.4.6\n    # via requests\nclick==8.3.1\n    # via towncrier\ndocutils==0.21.2\n    # via sphinx\nidna==3.11\n    # via requests\nimagesize==2.0.0\n    # via sphinx\njinja2==3.1.6\n    # via\n    #   sphinx\n    #   towncrier\nmarkupsafe==3.0.3\n    # via jinja2\npackaging==26.0\n    # via sphinx\npygments==2.19.2\n    # via sphinx\nrequests==2.32.5\n    # via sphinx\nsnowballstemmer==3.0.1\n    # via sphinx\nsphinx==8.1.3\n    # via\n    #   -r requirements/doc.in\n    #   sphinxcontrib-towncrier\nsphinxcontrib-applehelp==2.0.0\n    # via sphinx\nsphinxcontrib-devhelp==2.0.0\n    # via sphinx\nsphinxcontrib-htmlhelp==2.1.0\n    # via sphinx\nsphinxcontrib-jsmath==1.0.1\n    # via sphinx\nsphinxcontrib-qthelp==2.0.0\n    # via sphinx\nsphinxcontrib-serializinghtml==2.0.0\n    # via sphinx\nsphinxcontrib-towncrier==0.5.0a0\n    # via -r requirements/doc.in\ntomli==2.4.0\n    # via\n    #   sphinx\n    #   towncrier\ntowncrier==25.8.0\n    # via\n    #   -r requirements/doc.in\n    #   sphinxcontrib-towncrier\nurllib3==2.6.3\n    # via requests\n"
  },
  {
    "path": "requirements/lint.in",
    "content": "aiodns\nbackports.zstd; implementation_name == \"cpython\"\nblockbuster\nfreezegun\nisal\nmypy; implementation_name == \"cpython\"\npre-commit\nproxy.py\npytest\npytest-mock\npytest_codspeed\npython-on-whales\nslotscheck\ntrustme\nuvloop; platform_system != \"Windows\"\nvalkey\nzlib_ng\n"
  },
  {
    "path": "requirements/lint.txt",
    "content": "#\n# This file is autogenerated by pip-compile with Python 3.12\n# by the following command:\n#\n#    pip-compile --allow-unsafe --output-file=requirements/lint.txt --strip-extras requirements/lint.in\n#\naiodns==4.0.0\n    # via -r requirements/lint.in\nannotated-types==0.7.0\n    # via pydantic\nasync-timeout==5.0.1\n    # via valkey\nbackports-zstd==1.3.0 ; implementation_name == \"cpython\"\n    # via -r requirements/lint.in\nblockbuster==1.5.26\n    # via -r requirements/lint.in\ncffi==2.0.0\n    # via\n    #   cryptography\n    #   pycares\n    #   pytest-codspeed\ncfgv==3.5.0\n    # via pre-commit\nclick==8.3.1\n    # via slotscheck\ncryptography==46.0.5\n    # via trustme\ndistlib==0.4.0\n    # via virtualenv\nexceptiongroup==1.3.1\n    # via pytest\nfilelock==3.25.2\n    # via\n    #   python-discovery\n    #   virtualenv\nforbiddenfruit==0.1.4\n    # via blockbuster\nfreezegun==1.5.5\n    # via -r requirements/lint.in\nidentify==2.6.17\n    # via pre-commit\nidna==3.11\n    # via trustme\niniconfig==2.3.0\n    # via pytest\nisal==1.7.2\n    # via -r requirements/lint.in\nlibrt==0.8.0\n    # via mypy\nmarkdown-it-py==4.0.0\n    # via rich\nmdurl==0.1.2\n    # via markdown-it-py\nmypy==1.19.1 ; implementation_name == \"cpython\"\n    # via -r requirements/lint.in\nmypy-extensions==1.1.0\n    # via mypy\nnodeenv==1.10.0\n    # via pre-commit\npackaging==26.0\n    # via pytest\npathspec==1.0.4\n    # via mypy\nplatformdirs==4.9.4\n    # via\n    #   python-discovery\n    #   virtualenv\npluggy==1.6.0\n    # via pytest\npre-commit==4.5.1\n    # via -r requirements/lint.in\nproxy-py==2.4.10\n    # via -r requirements/lint.in\npycares==5.0.1\n    # via aiodns\npycparser==3.0\n    # via cffi\npydantic==2.12.5\n    # via python-on-whales\npydantic-core==2.41.5\n    # via pydantic\npygments==2.19.2\n    # via\n    #   pytest\n    #   rich\npytest==9.0.2\n    # via\n    #   -r requirements/lint.in\n    #   pytest-codspeed\n    #   pytest-mock\npytest-codspeed==4.3.0\n    # via -r requirements/lint.in\npytest-mock==3.15.1\n    # via -r requirements/lint.in\npython-dateutil==2.9.0.post0\n    # via freezegun\npython-discovery==1.2.0\n    # via virtualenv\npython-on-whales==0.81.0\n    # via -r requirements/lint.in\npyyaml==6.0.3\n    # via pre-commit\nrich==14.3.3\n    # via pytest-codspeed\nsix==1.17.0\n    # via python-dateutil\nslotscheck==0.19.1\n    # via -r requirements/lint.in\ntomli==2.4.0\n    # via\n    #   mypy\n    #   pytest\n    #   slotscheck\ntrustme==1.2.1\n    # via -r requirements/lint.in\ntyping-extensions==4.15.0\n    # via\n    #   cryptography\n    #   exceptiongroup\n    #   mypy\n    #   pydantic\n    #   pydantic-core\n    #   python-on-whales\n    #   typing-inspection\n    #   virtualenv\ntyping-inspection==0.4.2\n    # via pydantic\nuvloop==0.21.0 ; platform_system != \"Windows\"\n    # via -r requirements/lint.in\nvalkey==6.1.1\n    # via -r requirements/lint.in\nvirtualenv==21.2.0\n    # via pre-commit\nzlib-ng==1.0.0\n    # via -r requirements/lint.in\n"
  },
  {
    "path": "requirements/multidict.in",
    "content": "multidict\n"
  },
  {
    "path": "requirements/multidict.txt",
    "content": "#\n# This file is autogenerated by pip-compile with python 3.10\n# by the following command:\n#\n#    pip-compile --allow-unsafe --output-file=requirements/multidict.txt --resolver=backtracking --strip-extras requirements/multidict.in\n#\nmultidict==6.7.1\n    # via -r requirements/multidict.in\ntyping-extensions==4.15.0\n    # via multidict\n"
  },
  {
    "path": "requirements/runtime-deps.in",
    "content": "# Extracted from `pyproject.toml` via `make sync-direct-runtime-deps`\n\naiodns >= 3.3.0\naiohappyeyeballs >= 2.5.0\naiosignal >= 1.4.0\nasync-timeout >= 4.0, < 6.0 ; python_version < '3.11'\nbackports.zstd; platform_python_implementation == 'CPython' and python_version < '3.14'\nBrotli >= 1.2; platform_python_implementation == 'CPython'\nbrotlicffi >= 1.2; platform_python_implementation != 'CPython'\nfrozenlist >= 1.1.1\nmultidict >=4.5, < 7.0\npropcache >= 0.2.0\ntyping_extensions >= 4.4 ; python_version < '3.13'\nyarl >= 1.17.0, < 2.0\n"
  },
  {
    "path": "requirements/runtime-deps.txt",
    "content": "#\n# This file is autogenerated by pip-compile with Python 3.10\n# by the following command:\n#\n#    pip-compile --allow-unsafe --output-file=requirements/runtime-deps.txt --strip-extras requirements/runtime-deps.in\n#\naiodns==4.0.0\n    # via -r requirements/runtime-deps.in\naiohappyeyeballs==2.6.1\n    # via -r requirements/runtime-deps.in\naiosignal==1.4.0\n    # via -r requirements/runtime-deps.in\nasync-timeout==5.0.1 ; python_version < \"3.11\"\n    # via -r requirements/runtime-deps.in\nbackports-zstd==1.3.0 ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"\n    # via -r requirements/runtime-deps.in\nbrotli==1.2.0 ; platform_python_implementation == \"CPython\"\n    # via -r requirements/runtime-deps.in\ncffi==2.0.0\n    # via pycares\nfrozenlist==1.8.0\n    # via\n    #   -r requirements/runtime-deps.in\n    #   aiosignal\nidna==3.11\n    # via yarl\nmultidict==6.7.1\n    # via\n    #   -r requirements/runtime-deps.in\n    #   yarl\npropcache==0.4.1\n    # via\n    #   -r requirements/runtime-deps.in\n    #   yarl\npycares==5.0.1\n    # via aiodns\npycparser==3.0\n    # via cffi\ntyping-extensions==4.15.0 ; python_version < \"3.13\"\n    # via\n    #   -r requirements/runtime-deps.in\n    #   aiosignal\n    #   multidict\nyarl==1.22.0\n    # via -r requirements/runtime-deps.in\n"
  },
  {
    "path": "requirements/sync-direct-runtime-deps.py",
    "content": "#!/usr/bin/env python\n\"\"\"Sync direct runtime dependencies from pyproject.toml to runtime-deps.in.\"\"\"\n\nimport sys\nfrom pathlib import Path\n\nif sys.version_info >= (3, 11):\n    import tomllib\nelse:\n    raise RuntimeError(\"Use Python 3.11+ to run 'make sync-direct-runtime-deps'\")\n\ndata = tomllib.loads(Path(\"pyproject.toml\").read_text())\nreqs = (\n    data[\"project\"][\"dependencies\"]\n    + data[\"project\"][\"optional-dependencies\"][\"speedups\"]\n)\nreqs = sorted(reqs, key=str.casefold)\n\nwith open(Path(\"requirements\", \"runtime-deps.in\"), \"w\") as outfile:\n    header = \"# Extracted from `pyproject.toml` via `make sync-direct-runtime-deps`\\n\\n\"\n    outfile.write(header)\n    outfile.write(\"\\n\".join(reqs) + \"\\n\")\n"
  },
  {
    "path": "requirements/test-common.in",
    "content": "blockbuster\ncoverage\nfreezegun\nisal; python_version < \"3.14\" # no wheel for 3.14\nmypy; implementation_name == \"cpython\"\npkgconfig\nproxy.py >= 2.4.4rc5\npytest\npytest-cov\npytest-mock\npytest-xdist\npytest_codspeed\npython-on-whales\nsetuptools-git\ntrustme; platform_machine != \"i686\"  # no 32-bit wheels\nwait-for-it\nzlib_ng\n"
  },
  {
    "path": "requirements/test-common.txt",
    "content": "#\n# This file is autogenerated by pip-compile with Python 3.10\n# by the following command:\n#\n#    pip-compile --allow-unsafe --output-file=requirements/test-common.txt --strip-extras requirements/test-common.in\n#\nannotated-types==0.7.0\n    # via pydantic\nblockbuster==1.5.26\n    # via -r requirements/test-common.in\ncffi==2.0.0\n    # via\n    #   cryptography\n    #   pytest-codspeed\nclick==8.3.1\n    # via wait-for-it\ncoverage==7.13.5\n    # via\n    #   -r requirements/test-common.in\n    #   pytest-cov\ncryptography==46.0.5\n    # via trustme\nexceptiongroup==1.3.1\n    # via pytest\nexecnet==2.1.2\n    # via pytest-xdist\nforbiddenfruit==0.1.4\n    # via blockbuster\nfreezegun==1.5.5\n    # via -r requirements/test-common.in\nidna==3.11\n    # via trustme\niniconfig==2.3.0\n    # via pytest\nisal==1.8.0 ; python_version < \"3.14\"\n    # via -r requirements/test-common.in\nlibrt==0.8.0\n    # via mypy\nmarkdown-it-py==4.0.0\n    # via rich\nmdurl==0.1.2\n    # via markdown-it-py\nmypy==1.19.1 ; implementation_name == \"cpython\"\n    # via -r requirements/test-common.in\nmypy-extensions==1.1.0\n    # via mypy\npackaging==26.0\n    # via pytest\npathspec==1.0.4\n    # via mypy\npkgconfig==1.6.0\n    # via -r requirements/test-common.in\npluggy==1.6.0\n    # via\n    #   pytest\n    #   pytest-cov\nproxy-py==2.4.10\n    # via -r requirements/test-common.in\npycparser==3.0\n    # via cffi\npydantic==2.12.5\n    # via python-on-whales\npydantic-core==2.41.5\n    # via pydantic\npygments==2.19.2\n    # via\n    #   pytest\n    #   rich\npytest==9.0.2\n    # via\n    #   -r requirements/test-common.in\n    #   pytest-codspeed\n    #   pytest-cov\n    #   pytest-mock\n    #   pytest-xdist\npytest-codspeed==4.3.0\n    # via -r requirements/test-common.in\npytest-cov==7.0.0\n    # via -r requirements/test-common.in\npytest-mock==3.15.1\n    # via -r requirements/test-common.in\npytest-xdist==3.8.0\n    # via -r requirements/test-common.in\npython-dateutil==2.9.0.post0\n    # via freezegun\npython-on-whales==0.81.0\n    # via -r requirements/test-common.in\nrich==14.3.3\n    # via pytest-codspeed\nsetuptools-git==1.2\n    # via -r requirements/test-common.in\nsix==1.17.0\n    # via python-dateutil\ntomli==2.4.0\n    # via\n    #   coverage\n    #   mypy\n    #   pytest\ntrustme==1.2.1 ; platform_machine != \"i686\"\n    # via -r requirements/test-common.in\ntyping-extensions==4.15.0\n    # via\n    #   cryptography\n    #   exceptiongroup\n    #   mypy\n    #   pydantic\n    #   pydantic-core\n    #   python-on-whales\n    #   typing-inspection\ntyping-inspection==0.4.2\n    # via pydantic\nwait-for-it==2.3.0\n    # via -r requirements/test-common.in\nzlib-ng==1.0.0\n    # via -r requirements/test-common.in\n"
  },
  {
    "path": "requirements/test-ft.in",
    "content": "-r base-ft.in\n-r test-common.in\n"
  },
  {
    "path": "requirements/test-ft.txt",
    "content": "#\n# This file is autogenerated by pip-compile with Python 3.10\n# by the following command:\n#\n#    pip-compile --allow-unsafe --output-file=requirements/test-ft.txt --strip-extras requirements/test-ft.in\n#\naiodns==4.0.0\n    # via -r requirements/runtime-deps.in\naiohappyeyeballs==2.6.1\n    # via -r requirements/runtime-deps.in\naiosignal==1.4.0\n    # via -r requirements/runtime-deps.in\nannotated-types==0.7.0\n    # via pydantic\nasync-timeout==5.0.1 ; python_version < \"3.11\"\n    # via -r requirements/runtime-deps.in\nbackports-zstd==1.3.0 ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"\n    # via -r requirements/runtime-deps.in\nblockbuster==1.5.26\n    # via -r requirements/test-common.in\nbrotli==1.2.0 ; platform_python_implementation == \"CPython\"\n    # via -r requirements/runtime-deps.in\ncffi==2.0.0\n    # via\n    #   cryptography\n    #   pycares\n    #   pytest-codspeed\nclick==8.3.1\n    # via wait-for-it\ncoverage==7.13.5\n    # via\n    #   -r requirements/test-common.in\n    #   pytest-cov\ncryptography==46.0.5\n    # via trustme\nexceptiongroup==1.3.1\n    # via pytest\nexecnet==2.1.2\n    # via pytest-xdist\nforbiddenfruit==0.1.4\n    # via blockbuster\nfreezegun==1.5.5\n    # via -r requirements/test-common.in\nfrozenlist==1.8.0\n    # via\n    #   -r requirements/runtime-deps.in\n    #   aiosignal\ngunicorn==25.1.0\n    # via -r requirements/base-ft.in\nidna==3.11\n    # via\n    #   trustme\n    #   yarl\niniconfig==2.3.0\n    # via pytest\nisal==1.8.0 ; python_version < \"3.14\"\n    # via -r requirements/test-common.in\nlibrt==0.8.0\n    # via mypy\nmarkdown-it-py==4.0.0\n    # via rich\nmdurl==0.1.2\n    # via markdown-it-py\nmultidict==6.7.1\n    # via\n    #   -r requirements/runtime-deps.in\n    #   yarl\nmypy==1.19.1 ; implementation_name == \"cpython\"\n    # via -r requirements/test-common.in\nmypy-extensions==1.1.0\n    # via mypy\npackaging==26.0\n    # via\n    #   gunicorn\n    #   pytest\npathspec==1.0.4\n    # via mypy\npkgconfig==1.6.0\n    # via -r requirements/test-common.in\npluggy==1.6.0\n    # via\n    #   pytest\n    #   pytest-cov\npropcache==0.4.1\n    # via\n    #   -r requirements/runtime-deps.in\n    #   yarl\nproxy-py==2.4.10\n    # via -r requirements/test-common.in\npycares==5.0.1\n    # via aiodns\npycparser==3.0\n    # via cffi\npydantic==2.12.5\n    # via python-on-whales\npydantic-core==2.41.5\n    # via pydantic\npygments==2.19.2\n    # via\n    #   pytest\n    #   rich\npytest==9.0.2\n    # via\n    #   -r requirements/test-common.in\n    #   pytest-codspeed\n    #   pytest-cov\n    #   pytest-mock\n    #   pytest-xdist\npytest-codspeed==4.3.0\n    # via -r requirements/test-common.in\npytest-cov==7.0.0\n    # via -r requirements/test-common.in\npytest-mock==3.15.1\n    # via -r requirements/test-common.in\npytest-xdist==3.8.0\n    # via -r requirements/test-common.in\npython-dateutil==2.9.0.post0\n    # via freezegun\npython-on-whales==0.81.0\n    # via -r requirements/test-common.in\nrich==14.3.3\n    # via pytest-codspeed\nsetuptools-git==1.2\n    # via -r requirements/test-common.in\nsix==1.17.0\n    # via python-dateutil\ntomli==2.4.0\n    # via\n    #   coverage\n    #   mypy\n    #   pytest\ntrustme==1.2.1 ; platform_machine != \"i686\"\n    # via -r requirements/test-common.in\ntyping-extensions==4.15.0 ; python_version < \"3.13\"\n    # via\n    #   -r requirements/runtime-deps.in\n    #   aiosignal\n    #   cryptography\n    #   exceptiongroup\n    #   multidict\n    #   mypy\n    #   pydantic\n    #   pydantic-core\n    #   python-on-whales\n    #   typing-inspection\ntyping-inspection==0.4.2\n    # via pydantic\nwait-for-it==2.3.0\n    # via -r requirements/test-common.in\nyarl==1.22.0\n    # via -r requirements/runtime-deps.in\nzlib-ng==1.0.0\n    # via -r requirements/test-common.in\n"
  },
  {
    "path": "requirements/test.in",
    "content": "-r base.in\n-r test-common.in\n"
  },
  {
    "path": "requirements/test.txt",
    "content": "#\n# This file is autogenerated by pip-compile with Python 3.10\n# by the following command:\n#\n#    pip-compile --allow-unsafe --output-file=requirements/test.txt --strip-extras requirements/test.in\n#\naiodns==4.0.0\n    # via -r requirements/runtime-deps.in\naiohappyeyeballs==2.6.1\n    # via -r requirements/runtime-deps.in\naiosignal==1.4.0\n    # via -r requirements/runtime-deps.in\nannotated-types==0.7.0\n    # via pydantic\nasync-timeout==5.0.1 ; python_version < \"3.11\"\n    # via -r requirements/runtime-deps.in\nbackports-zstd==1.3.0 ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"\n    # via -r requirements/runtime-deps.in\nblockbuster==1.5.26\n    # via -r requirements/test-common.in\nbrotli==1.2.0 ; platform_python_implementation == \"CPython\"\n    # via -r requirements/runtime-deps.in\ncffi==2.0.0\n    # via\n    #   cryptography\n    #   pycares\n    #   pytest-codspeed\nclick==8.3.1\n    # via wait-for-it\ncoverage==7.13.5\n    # via\n    #   -r requirements/test-common.in\n    #   pytest-cov\ncryptography==46.0.5\n    # via trustme\nexceptiongroup==1.3.1\n    # via pytest\nexecnet==2.1.2\n    # via pytest-xdist\nforbiddenfruit==0.1.4\n    # via blockbuster\nfreezegun==1.5.5\n    # via -r requirements/test-common.in\nfrozenlist==1.8.0\n    # via\n    #   -r requirements/runtime-deps.in\n    #   aiosignal\ngunicorn==25.1.0\n    # via -r requirements/base.in\nidna==3.11\n    # via\n    #   trustme\n    #   yarl\niniconfig==2.3.0\n    # via pytest\nisal==1.7.2 ; python_version < \"3.14\"\n    # via -r requirements/test-common.in\nlibrt==0.8.0\n    # via mypy\nmarkdown-it-py==4.0.0\n    # via rich\nmdurl==0.1.2\n    # via markdown-it-py\nmultidict==6.7.1\n    # via\n    #   -r requirements/runtime-deps.in\n    #   yarl\nmypy==1.19.1 ; implementation_name == \"cpython\"\n    # via -r requirements/test-common.in\nmypy-extensions==1.1.0\n    # via mypy\npackaging==26.0\n    # via\n    #   gunicorn\n    #   pytest\npathspec==1.0.4\n    # via mypy\npkgconfig==1.6.0\n    # via -r requirements/test-common.in\npluggy==1.6.0\n    # via\n    #   pytest\n    #   pytest-cov\npropcache==0.4.1\n    # via\n    #   -r requirements/runtime-deps.in\n    #   yarl\nproxy-py==2.4.10\n    # via -r requirements/test-common.in\npycares==5.0.1\n    # via aiodns\npycparser==3.0\n    # via cffi\npydantic==2.12.5\n    # via python-on-whales\npydantic-core==2.41.5\n    # via pydantic\npygments==2.19.2\n    # via\n    #   pytest\n    #   rich\npytest==9.0.2\n    # via\n    #   -r requirements/test-common.in\n    #   pytest-codspeed\n    #   pytest-cov\n    #   pytest-mock\n    #   pytest-xdist\npytest-codspeed==4.3.0\n    # via -r requirements/test-common.in\npytest-cov==7.0.0\n    # via -r requirements/test-common.in\npytest-mock==3.15.1\n    # via -r requirements/test-common.in\npytest-xdist==3.8.0\n    # via -r requirements/test-common.in\npython-dateutil==2.9.0.post0\n    # via freezegun\npython-on-whales==0.81.0\n    # via -r requirements/test-common.in\nrich==14.3.3\n    # via pytest-codspeed\nsetuptools-git==1.2\n    # via -r requirements/test-common.in\nsix==1.17.0\n    # via python-dateutil\ntomli==2.4.0\n    # via\n    #   coverage\n    #   mypy\n    #   pytest\ntrustme==1.2.1 ; platform_machine != \"i686\"\n    # via -r requirements/test-common.in\ntyping-extensions==4.15.0 ; python_version < \"3.13\"\n    # via\n    #   -r requirements/runtime-deps.in\n    #   aiosignal\n    #   cryptography\n    #   exceptiongroup\n    #   multidict\n    #   mypy\n    #   pydantic\n    #   pydantic-core\n    #   python-on-whales\n    #   typing-inspection\ntyping-inspection==0.4.2\n    # via pydantic\nuvloop==0.21.0 ; platform_system != \"Windows\" and implementation_name == \"cpython\"\n    # via -r requirements/base.in\nwait-for-it==2.3.0\n    # via -r requirements/test-common.in\nyarl==1.22.0\n    # via -r requirements/runtime-deps.in\nzlib-ng==1.0.0\n    # via -r requirements/test-common.in\n"
  },
  {
    "path": "setup.cfg",
    "content": "[pep8]\nmax-line-length=79\n\n[easy_install]\nzip_ok = false\n\n[flake8]\nextend-select =\n  B950,\n  # NIC001 -- \"Implicitly concatenated str literals on one line\"\n  NIC001,\n  # NIC101 -- \"Implicitly concatenated bytes literals on one line\"\n  NIC101,\n# TODO: don't disable D*, fix up issues instead\nignore = N801,N802,N803,NIC002,NIC102,E203,E226,E305,W504,E252,E301,E302,E501,E704,W503,W504,D1,D4\nmax-line-length = 88\nper-file-ignores =\n    # I900: Shouldn't appear in requirements for examples.\n    examples/*:I900\n    docs/code/*:F841\n\n# flake8-requirements\nknown-modules = proxy.py:[proxy]\nrequirements-file = requirements/test.in\nrequirements-max-depth = 4\n\n[isort]\nline_length=88\ninclude_trailing_comma=True\nmulti_line_output=3\nforce_grid_wrap=0\ncombine_as_imports=True\n\nknown_third_party=jinja2,pytest,multidict,yarl,gunicorn,freezegun\nknown_first_party=aiohttp,aiohttp_jinja2,aiopg\n\n[report]\nexclude_lines =\n    @abc.abstractmethod\n    @abstractmethod\n\n[tool:pytest]\naddopts =\n    # `pytest-xdist`:\n    --numprocesses=auto\n\n    # show 10 slowest invocations:\n    --durations=10\n\n    # a bit of verbosity doesn't hurt:\n    -v\n\n    # report all the things == -rxXs:\n    -ra\n\n    # show values of the local vars in errors:\n    --showlocals\n\n    # `pytest-cov`:\n    -p pytest_cov\n    --cov=aiohttp\n    --cov=tests/\n\n    -m \"not dev_mode and not autobahn and not internal\"\nfilterwarnings =\n    error\n    ignore:module 'ssl' has no attribute 'OP_NO_COMPRESSION'. The Python interpreter is compiled against OpenSSL < 1.0.0. Ref. https.//docs.python.org/3/library/ssl.html#ssl.OP_NO_COMPRESSION:UserWarning\n    ignore:Unclosed client session <aiohttp.client.ClientSession object at 0x:ResourceWarning\n    ignore:The loop argument is deprecated:DeprecationWarning:asyncio\n    ignore:Creating a LegacyVersion has been deprecated and will be removed in the next major release:DeprecationWarning::\n    # The following deprecation warning is triggered by importing\n    # `gunicorn.util`. Hopefully, it'll get fixed in the future. See\n    # https://github.com/benoitc/gunicorn/issues/2840 for detail.\n    ignore:module 'sre_constants' is deprecated:DeprecationWarning:pkg_resources._vendor.pyparsing\n    # Deprecation warning emitted by setuptools v67.5.0+ triggered by importing\n    # `gunicorn.util`.\n    ignore:pkg_resources is deprecated as an API:DeprecationWarning\n    # The deprecation warning below is happening under Python 3.11 and\n    # is fixed by https://github.com/certifi/python-certifi/pull/199. It\n    # can be dropped with the next release of `certify`, specifically\n    # `certify > 2022.06.15`.\n    ignore:path is deprecated. Use files.. instead. Refer to https.//importlib-resources.readthedocs.io/en/latest/using.html#migrating-from-legacy for migration advice.:DeprecationWarning:certifi.core\n    # Dateutil deprecation warning already fixed upstream.\n    # Can be dropped with the next release, `dateutil > 2.8.2`\n    # https://github.com/dateutil/dateutil/pull/1285\n    ignore:datetime.*utcfromtimestamp\\(\\) is deprecated and scheduled for removal:DeprecationWarning:dateutil.tz.tz\n    # Tracked upstream and waiting for PR review\n    # https://github.com/spulec/freezegun/issues/508\n    # https://github.com/spulec/freezegun/pull/511\n    ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:freezegun.api\njunit_suite_name = aiohttp_test_suite\nnorecursedirs = dist docs build .tox .eggs\nminversion = 3.8.2\ntestpaths = tests/\nxfail_strict = true\nmarkers =\n    autobahn: Autobahn testsuite. Should be run as a separate job.\n    dev_mode: mark test to run in dev mode.\n    internal: tests which may cause issues for packagers, but should be run in aiohttp's CI.\n    skip_blockbuster: mark test to skip the blockbuster fixture.\n"
  },
  {
    "path": "setup.py",
    "content": "import os\nimport pathlib\nimport sys\n\nfrom setuptools import Extension, setup\n\nif sys.version_info < (3, 10):\n    raise RuntimeError(\"aiohttp 4.x requires Python 3.10+\")\n\n\nUSE_SYSTEM_DEPS = bool(\n    os.environ.get(\"AIOHTTP_USE_SYSTEM_DEPS\", os.environ.get(\"USE_SYSTEM_DEPS\"))\n)\nNO_EXTENSIONS: bool = bool(os.environ.get(\"AIOHTTP_NO_EXTENSIONS\"))\nHERE = pathlib.Path(__file__).parent\nIS_GIT_REPO = (HERE / \".git\").exists()\n\n\nif sys.implementation.name != \"cpython\":\n    NO_EXTENSIONS = True\n\n\nif (\n    not USE_SYSTEM_DEPS\n    and IS_GIT_REPO\n    and not (HERE / \"vendor/llhttp/README.md\").exists()\n):\n    print(\"Install submodules when building from git clone\", file=sys.stderr)\n    print(\"Hint:\", file=sys.stderr)\n    print(\"  git submodule update --init\", file=sys.stderr)\n    sys.exit(2)\n\n\n# NOTE: makefile cythonizes all Cython modules\n\nif USE_SYSTEM_DEPS:\n    import shlex\n\n    import pkgconfig\n\n    llhttp_sources = []\n    llhttp_kwargs = {\n        \"extra_compile_args\": shlex.split(pkgconfig.cflags(\"libllhttp\")),\n        \"extra_link_args\": shlex.split(pkgconfig.libs(\"libllhttp\")),\n    }\nelse:\n    llhttp_sources = [\n        \"vendor/llhttp/build/c/llhttp.c\",\n        \"vendor/llhttp/src/native/api.c\",\n        \"vendor/llhttp/src/native/http.c\",\n    ]\n    llhttp_kwargs = {\n        \"define_macros\": [(\"LLHTTP_STRICT_MODE\", 0)],\n        \"include_dirs\": [\"vendor/llhttp/build\"],\n    }\n\nextensions = [\n    Extension(\"aiohttp._websocket.mask\", [\"aiohttp/_websocket/mask.c\"]),\n    Extension(\n        \"aiohttp._http_parser\",\n        [\n            \"aiohttp/_http_parser.c\",\n            \"aiohttp/_find_header.c\",\n            *llhttp_sources,\n        ],\n        **llhttp_kwargs,\n    ),\n    Extension(\"aiohttp._http_writer\", [\"aiohttp/_http_writer.c\"]),\n    Extension(\"aiohttp._websocket.reader_c\", [\"aiohttp/_websocket/reader_c.c\"]),\n]\n\n\nbuild_type = \"Pure\" if NO_EXTENSIONS else \"Accelerated\"\nsetup_kwargs = {} if NO_EXTENSIONS else {\"ext_modules\": extensions}\n\nprint(\"*********************\", file=sys.stderr)\nprint(\"* {build_type} build *\".format_map(locals()), file=sys.stderr)\nprint(\"*********************\", file=sys.stderr)\nsetup(**setup_kwargs)\n"
  },
  {
    "path": "tests/autobahn/Dockerfile.aiohttp",
    "content": "FROM python:3.14\n\nCOPY ./ /src\n\nWORKDIR /src\n\nRUN pip install .\n"
  },
  {
    "path": "tests/autobahn/Dockerfile.autobahn",
    "content": "FROM crossbario/autobahn-testsuite:25.10.1\n\nRUN apt-get update && apt-get install python3 python3-pip -y\nRUN pip3 install wait-for-it\n\nCMD [\"wstest\", \"--mode\", \"fuzzingserver\", \"--spec\", \"/config/fuzzingserver.json\"]\n"
  },
  {
    "path": "tests/autobahn/client/client.py",
    "content": "#!/usr/bin/env python3\n\nimport asyncio\n\nfrom aiohttp import ClientSession, WSMsgType\n\n\nasync def client(url: str, name: str) -> None:\n    async with ClientSession(base_url=url) as session:\n        async with session.ws_connect(\"/getCaseCount\") as ws:\n            msg = await ws.receive()\n            assert msg.type is WSMsgType.TEXT\n            num_tests = int(msg.data)\n\n        for i in range(1, num_tests + 1):\n            async with session.ws_connect(\n                \"/runCase\", params={\"case\": i, \"agent\": name}\n            ) as ws:\n                async for msg in ws:\n                    if msg.type is WSMsgType.TEXT:\n                        await ws.send_str(msg.data)\n                    elif msg.type is WSMsgType.BINARY:\n                        await ws.send_bytes(msg.data)\n                    else:\n                        break\n\n        async with session.ws_connect(\"/updateReports\", params={\"agent\": name}) as ws:\n            pass\n\n\nif __name__ == \"__main__\":  # pragma: no branch\n    asyncio.run(client(\"http://localhost:9001\", \"aiohttp\"))\n"
  },
  {
    "path": "tests/autobahn/client/fuzzingserver.json",
    "content": "{\n   \"url\": \"ws://localhost:9001\",\n\n   \"options\": {\"failByDrop\": true},\n   \"outdir\": \"./reports/clients\",\n   \"webport\": 8080,\n\n   \"cases\": [\"*\"],\n   \"exclude-cases\": [],\n   \"exclude-agent-cases\": {}\n}\n"
  },
  {
    "path": "tests/autobahn/server/fuzzingclient.json",
    "content": "{\r\n    \"options\": {\"failByDrop\": true},\r\n    \"outdir\": \"./reports/servers\",\r\n\r\n    \"servers\": [\r\n        {\"agent\": \"AutobahnServer\", \"url\": \"ws://localhost:9001\"}\r\n    ],\r\n\r\n    \"cases\": [\"*\"],\r\n    \"exclude-cases\": [],\r\n    \"exclude-agent-cases\": {}\r\n}\r\n"
  },
  {
    "path": "tests/autobahn/server/server.py",
    "content": "#!/usr/bin/env python3\n\nimport logging\n\nfrom aiohttp import WSCloseCode, web\n\nwebsockets = web.AppKey(\"websockets\", list[web.WebSocketResponse])\n\n\nasync def wshandler(request: web.Request) -> web.WebSocketResponse:\n    ws = web.WebSocketResponse(autoclose=False)\n    await ws.prepare(request)\n\n    request.app[websockets].append(ws)\n\n    async for msg in ws:\n        if msg.type is web.WSMsgType.TEXT:\n            await ws.send_str(msg.data)\n        elif msg.type is web.WSMsgType.BINARY:\n            await ws.send_bytes(msg.data)\n        else:\n            break\n\n    return ws\n\n\nasync def on_shutdown(app: web.Application) -> None:\n    for ws in app[websockets]:\n        await ws.close(code=WSCloseCode.GOING_AWAY, message=b\"Server shutdown\")\n\n\nif __name__ == \"__main__\":  # pragma: no branch\n    logging.basicConfig(\n        level=logging.DEBUG, format=\"%(asctime)s %(levelname)s %(message)s\"\n    )\n\n    app = web.Application()\n    app[websockets] = []\n    app.router.add_route(\"GET\", \"/\", wshandler)\n    app.on_shutdown.append(on_shutdown)\n    web.run_app(app, port=9001)\n"
  },
  {
    "path": "tests/autobahn/test_autobahn.py",
    "content": "import json\nimport pprint\nimport subprocess\nfrom collections.abc import Iterator\nfrom pathlib import Path\nfrom typing import TYPE_CHECKING\n\nimport pytest\nfrom pytest import TempPathFactory\n\nif TYPE_CHECKING:\n    from python_on_whales import DockerException, docker\nelse:\n    python_on_whales = pytest.importorskip(\"python_on_whales\")\n    DockerException = python_on_whales.DockerException\n    docker = python_on_whales.docker\n\n# (Test number, test status, test report)\nResult = tuple[str, str, dict[str, object] | None]\n\n\n@pytest.fixture(scope=\"session\")\ndef report_dir(tmp_path_factory: TempPathFactory) -> Path:\n    return tmp_path_factory.mktemp(\"reports\")\n\n\n@pytest.fixture(scope=\"session\", autouse=True)\ndef build_autobahn_testsuite() -> Iterator[None]:\n    docker.build(\n        file=\"tests/autobahn/Dockerfile.autobahn\",\n        tags=[\"autobahn-testsuite\"],\n        context_path=\".\",\n    )\n\n    try:\n        yield\n    finally:\n        docker.image.remove(x=\"autobahn-testsuite\")\n\n\ndef get_report(path: Path, result: dict[str, str]) -> dict[str, object] | None:\n    if result[\"behaviorClose\"] == \"OK\":\n        return None\n    return json.loads((path / result[\"reportfile\"]).read_text())  # type: ignore[no-any-return]\n\n\ndef get_test_results(path: Path, name: str) -> tuple[Result, ...]:\n    results = json.loads((path / \"index.json\").read_text())[name]\n    return tuple(\n        (k, r[\"behaviorClose\"], get_report(path, r)) for k, r in results.items()\n    )\n\n\ndef process_xfail(\n    results: tuple[Result, ...], xfail: dict[str, str]\n) -> list[dict[str, object]]:\n    failed = []\n    for number, status, details in results:\n        if number in xfail:\n            assert status not in {\"OK\", \"INFORMATIONAL\"}  # Strict xfail\n            assert details is not None\n            if details[\"result\"] == xfail[number]:\n                continue\n        if status not in {\"OK\", \"INFORMATIONAL\"}:  # pragma: no cover\n            assert details is not None\n            pprint.pprint(details)\n            failed.append(details)\n    return failed\n\n\n@pytest.mark.autobahn\ndef test_client(report_dir: Path, request: pytest.FixtureRequest) -> None:\n    client = subprocess.Popen(\n        (\n            \"wait-for-it\",\n            \"-s\",\n            \"localhost:9001\",\n            \"--\",\n            \"coverage\",\n            \"run\",\n            \"-a\",\n            \"tests/autobahn/client/client.py\",\n        )\n    )\n    try:\n        autobahn_container = docker.run(\n            detach=True,\n            image=\"autobahn-testsuite\",\n            name=\"autobahn\",\n            publish=[(9001, 9001)],\n            remove=True,\n            volumes=[\n                (request.path.parent / \"client\", \"/config\"),\n                (report_dir, \"/reports\"),\n            ],\n        )\n        client.wait()\n    finally:\n        client.terminate()\n        client.wait()\n        autobahn_container.stop()\n\n    results = get_test_results(report_dir / \"clients\", \"aiohttp\")\n    xfail = {\n        \"3.4\": \"Actual events match at least one expected.\",\n        \"7.9.5\": \"The close code should have been 1002 or empty\",\n        \"9.1.4\": \"Did not receive message within 100 seconds.\",\n        \"9.1.5\": \"Did not receive message within 100 seconds.\",\n        \"9.1.6\": \"Did not receive message within 100 seconds.\",\n        \"9.2.4\": \"Did not receive message within 10 seconds.\",\n        \"9.2.5\": \"Did not receive message within 100 seconds.\",\n        \"9.2.6\": \"Did not receive message within 100 seconds.\",\n        \"9.3.1\": \"Did not receive message within 100 seconds.\",\n        \"9.3.2\": \"Did not receive message within 100 seconds.\",\n        \"9.3.3\": \"Did not receive message within 100 seconds.\",\n        \"9.3.4\": \"Did not receive message within 100 seconds.\",\n        \"9.3.5\": \"Did not receive message within 100 seconds.\",\n        \"9.3.6\": \"Did not receive message within 100 seconds.\",\n        \"9.3.7\": \"Did not receive message within 100 seconds.\",\n        \"9.3.8\": \"Did not receive message within 100 seconds.\",\n        \"9.3.9\": \"Did not receive message within 100 seconds.\",\n        \"9.4.1\": \"Did not receive message within 100 seconds.\",\n        \"9.4.2\": \"Did not receive message within 100 seconds.\",\n        \"9.4.3\": \"Did not receive message within 100 seconds.\",\n        \"9.4.4\": \"Did not receive message within 100 seconds.\",\n        \"9.4.5\": \"Did not receive message within 100 seconds.\",\n        \"9.4.6\": \"Did not receive message within 100 seconds.\",\n        \"9.4.7\": \"Did not receive message within 100 seconds.\",\n        \"9.4.8\": \"Did not receive message within 100 seconds.\",\n        \"9.4.9\": \"Did not receive message within 100 seconds.\",\n    }\n    assert not process_xfail(results, xfail)\n\n\n@pytest.mark.autobahn\ndef test_server(report_dir: Path, request: pytest.FixtureRequest) -> None:\n    server = subprocess.Popen(\n        (\"coverage\", \"run\", \"-a\", \"tests/autobahn/server/server.py\")\n    )\n    try:\n        docker.run(\n            image=\"autobahn-testsuite\",\n            name=\"autobahn\",\n            remove=True,\n            volumes=[\n                (request.path.parent / \"server\", \"/config\"),\n                (report_dir, \"/reports\"),\n            ],\n            networks=(\"host\",),\n            command=(\n                \"wait-for-it\",\n                \"-s\",\n                \"localhost:9001\",\n                \"--\",\n                \"wstest\",\n                \"--mode\",\n                \"fuzzingclient\",\n                \"--spec\",\n                \"/config/fuzzingclient.json\",\n            ),\n        )\n    finally:\n        server.terminate()\n        server.wait()\n\n    results = get_test_results(report_dir / \"servers\", \"AutobahnServer\")\n    xfail = {\n        \"7.9.5\": \"The close code should have been 1002 or empty\",\n        \"9.1.4\": \"Did not receive message within 100 seconds.\",\n        \"9.1.5\": \"Did not receive message within 100 seconds.\",\n        \"9.1.6\": \"Did not receive message within 100 seconds.\",\n        \"9.2.4\": \"Did not receive message within 10 seconds.\",\n        \"9.2.5\": \"Did not receive message within 100 seconds.\",\n        \"9.2.6\": \"Did not receive message within 100 seconds.\",\n        \"9.3.1\": \"Did not receive message within 100 seconds.\",\n        \"9.3.2\": \"Did not receive message within 100 seconds.\",\n        \"9.3.3\": \"Did not receive message within 100 seconds.\",\n        \"9.3.4\": \"Did not receive message within 100 seconds.\",\n        \"9.3.5\": \"Did not receive message within 100 seconds.\",\n        \"9.3.6\": \"Did not receive message within 100 seconds.\",\n        \"9.3.7\": \"Did not receive message within 100 seconds.\",\n        \"9.3.8\": \"Did not receive message within 100 seconds.\",\n        \"9.3.9\": \"Did not receive message within 100 seconds.\",\n        \"9.4.1\": \"Did not receive message within 100 seconds.\",\n        \"9.4.2\": \"Did not receive message within 100 seconds.\",\n        \"9.4.3\": \"Did not receive message within 100 seconds.\",\n        \"9.4.4\": \"Did not receive message within 100 seconds.\",\n        \"9.4.5\": \"Did not receive message within 100 seconds.\",\n        \"9.4.6\": \"Did not receive message within 100 seconds.\",\n        \"9.4.7\": \"Did not receive message within 100 seconds.\",\n        \"9.4.8\": \"Did not receive message within 100 seconds.\",\n        \"9.4.9\": \"Did not receive message within 100 seconds.\",\n    }\n    assert not process_xfail(results, xfail)\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "from __future__ import annotations  # TODO(PY311): Remove\n\nimport asyncio\nimport base64\nimport os\nimport platform\nimport socket\nimport ssl\nimport sys\nimport time\nfrom collections.abc import AsyncIterator, Callable, Iterator\nfrom concurrent.futures import Future, ThreadPoolExecutor\nfrom hashlib import md5, sha1, sha256\nfrom http.cookies import BaseCookie\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\nfrom typing import Any\nfrom unittest import mock\nfrom uuid import uuid4\n\nimport pytest\nimport trustme\nfrom multidict import CIMultiDict\nfrom yarl import URL\n\ntry:\n    from blockbuster import blockbuster_ctx\n\n    HAS_BLOCKBUSTER = True\nexcept ImportError:  # For downstreams only  # pragma: no cover\n    HAS_BLOCKBUSTER = False\n\nfrom aiohttp import payload\nfrom aiohttp.client import ClientSession\nfrom aiohttp.client_proto import ResponseHandler\nfrom aiohttp.client_reqrep import ClientRequest, ClientRequestArgs, ClientResponse\nfrom aiohttp.compression_utils import ZLibBackend, ZLibBackendProtocol, set_zlib_backend\nfrom aiohttp.helpers import TimerNoop\nfrom aiohttp.http import WS_KEY, HttpVersion11\nfrom aiohttp.test_utils import get_unused_port_socket, loop_context\n\n\ndef pytest_configure(config: pytest.Config) -> None:\n    # On Windows with Python 3.10/3.11, proxy.py's threaded mode can leave\n    # sockets not fully released by the time pytest's unraisableexception\n    # plugin collects warnings during teardown. Suppress these warnings\n    # since they are not actionable and only affect older Python versions.\n    if os.name == \"nt\" and sys.version_info < (3, 12):\n        config.addinivalue_line(\n            \"filterwarnings\",\n            \"ignore:Exception ignored in.*socket.*:pytest.PytestUnraisableExceptionWarning\",\n        )\n\n\ntry:\n    if sys.platform == \"win32\":\n        import winloop as uvloop\n    else:\n        import uvloop\nexcept ImportError:\n    uvloop = None  # type: ignore[assignment]\n\nif sys.version_info >= (3, 11):\n    from typing import Unpack\nelse:\n    from typing import Any as Unpack\n\n\npytest_plugins = (\"aiohttp.pytest_plugin\", \"pytester\")\n\nIS_HPUX = sys.platform.startswith(\"hp-ux\")\nIS_LINUX = sys.platform.startswith(\"linux\")\n\n\n@pytest.fixture(autouse=HAS_BLOCKBUSTER)\ndef blockbuster(request: pytest.FixtureRequest) -> Iterator[None]:\n    # Allow selectively disabling blockbuster for specific tests\n    # using the @pytest.mark.skip_blockbuster marker.\n    if \"skip_blockbuster\" in request.node.keywords:\n        yield\n        return\n\n    # No blockbuster for benchmark tests.\n    node = request.node.parent\n    while node:\n        if node.name.startswith(\"test_benchmarks\"):\n            yield\n            return\n        node = node.parent\n    with blockbuster_ctx(\n        \"aiohttp\", excluded_modules=[\"aiohttp.pytest_plugin\", \"aiohttp.test_utils\"]\n    ) as bb:\n        for func in [\n            \"os.getcwd\",\n            \"os.readlink\",\n            \"os.stat\",\n            \"os.path.abspath\",\n            \"os.path.samestat\",\n        ]:\n            bb.functions[func].can_block_in(\n                \"aiohttp/web_urldispatcher.py\", \"add_static\"\n            )\n        # Note: coverage.py uses locking internally which can cause false positives\n        # in blockbuster when it instruments code. This is particularly problematic\n        # on Windows where it can lead to flaky test failures.\n        # Additionally, we're not particularly worried about threading.Lock.acquire happening\n        # by accident in this codebase as we primarily use asyncio.Lock for\n        # synchronization in async code.\n        # Allow lock.acquire calls to prevent these false positives\n        bb.functions[\"threading.Lock.acquire\"].deactivate()\n        yield\n\n\n@pytest.fixture\ndef tls_certificate_authority() -> trustme.CA:\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        \"xn--prklad-4va.localhost\",\n        \"127.0.0.1\",\n        \"::1\",\n    )\n\n\n@pytest.fixture\ndef ssl_ctx(tls_certificate: trustme.LeafCert) -> ssl.SSLContext:\n    ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n    tls_certificate.configure_cert(ssl_ctx)\n    return ssl_ctx\n\n\n@pytest.fixture\ndef client_ssl_ctx(tls_certificate_authority: trustme.CA) -> ssl.SSLContext:\n    ssl_ctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)\n    tls_certificate_authority.configure_trust(ssl_ctx)\n    return ssl_ctx\n\n\n@pytest.fixture\ndef tls_ca_certificate_pem_path(tls_certificate_authority: trustme.CA) -> Iterator[str]:\n    with tls_certificate_authority.cert_pem.tempfile() as ca_cert_pem:\n        yield ca_cert_pem\n\n\n@pytest.fixture\ndef tls_certificate_pem_path(tls_certificate: trustme.LeafCert) -> Iterator[str]:\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_pem_bytes(tls_certificate: trustme.LeafCert) -> bytes:\n    return tls_certificate.cert_chain_pems[0].bytes()\n\n\n@pytest.fixture\ndef tls_certificate_fingerprint_sha256(tls_certificate_pem_bytes: bytes) -> bytes:\n    tls_cert_der = ssl.PEM_cert_to_DER_cert(tls_certificate_pem_bytes.decode())\n    return sha256(tls_cert_der).digest()\n\n\n@pytest.fixture\ndef pipe_name() -> str:\n    name = rf\"\\\\.\\pipe\\{uuid4().hex}\"\n    return name\n\n\n@pytest.fixture\ndef create_mocked_conn(\n    loop: asyncio.AbstractEventLoop,\n) -> Iterator[Callable[[], ResponseHandler]]:\n    def _proto_factory() -> Any:\n        proto = mock.create_autospec(ResponseHandler, instance=True)\n        proto.closed = loop.create_future()\n        proto.closed.set_result(None)\n        return proto\n\n    yield _proto_factory\n\n\n@pytest.fixture\ndef unix_sockname(\n    tmp_path: Path, tmp_path_factory: pytest.TempPathFactory\n) -> Iterator[str]:\n    # Generate an fs path to the UNIX domain socket for testing.\n\n    # N.B. Different OS kernels have different fs path length limitations\n    # for it. For Linux, it's 108, for HP-UX it's 92 (or higher) depending\n    # on its version. For most of the BSDs (Open, Free, macOS) it's\n    # mostly 104 but sometimes it can be down to 100.\n\n    # Ref: https://github.com/aio-libs/aiohttp/issues/3572\n    if not hasattr(socket, \"AF_UNIX\"):\n        pytest.skip(\"requires UNIX sockets\")\n\n    max_sock_len = 92 if IS_HPUX else 108 if IS_LINUX else 100\n    # Amount of bytes allocated for the UNIX socket path by OS kernel.\n    # Ref: https://unix.stackexchange.com/a/367012/27133\n\n    sock_file_name = \"unix.sock\"\n\n    root_tmp_dir = Path(\"/tmp\").resolve()\n    os_tmp_dir = Path(os.getenv(\"TMPDIR\", \"/tmp\")).resolve()\n    original_base_tmp_path = Path(\n        str(tmp_path_factory.getbasetemp()),\n    ).resolve()\n\n    original_base_tmp_path_hash = md5(\n        str(original_base_tmp_path).encode(),\n    ).hexdigest()\n\n    def make_tmp_dir(base_tmp_dir: Path) -> TemporaryDirectory[str]:\n        return TemporaryDirectory(\n            dir=str(base_tmp_dir),\n            prefix=\"pt-\",\n            suffix=f\"-{original_base_tmp_path_hash!s}\",\n        )\n\n    def assert_sock_fits(sock_path: str) -> None:\n        sock_path_len = len(sock_path.encode())\n        # exit-check to verify that it's correct and simplify debugging\n        # in the future\n        assert sock_path_len <= max_sock_len, (\n            \"Suggested UNIX socket ({sock_path}) is {sock_path_len} bytes \"\n            \"long but the current kernel only has {max_sock_len} bytes \"\n            \"allocated to hold it so it must be shorter. \"\n            \"See https://github.com/aio-libs/aiohttp/issues/3572 \"\n            \"for more info.\"\n        ).format_map(locals())\n\n    paths = original_base_tmp_path, os_tmp_dir, root_tmp_dir\n    unique_paths = [p for n, p in enumerate(paths) if p not in paths[:n]]\n    paths_num = len(unique_paths)\n\n    for num, tmp_dir_path in enumerate(paths, 1):  # pragma: no branch\n        with make_tmp_dir(tmp_dir_path) as tmps:\n            tmpd = Path(tmps).resolve()\n            sock_path = str(tmpd / sock_file_name)\n            sock_path_len = len(sock_path.encode())\n\n            if num >= paths_num:\n                # exit-check to verify that it's correct and simplify\n                # debugging in the future\n                assert_sock_fits(sock_path)\n\n            if sock_path_len <= max_sock_len:\n                yield sock_path\n                return\n\n\n@pytest.fixture\nasync def event_loop(loop: asyncio.AbstractEventLoop) -> asyncio.AbstractEventLoop:\n    return asyncio.get_running_loop()\n\n\n@pytest.fixture\ndef selector_loop() -> Iterator[asyncio.AbstractEventLoop]:\n    factory = asyncio.SelectorEventLoop\n    with loop_context(factory) as _loop:\n        asyncio.set_event_loop(_loop)\n        yield _loop\n\n\n@pytest.fixture\ndef uvloop_loop() -> Iterator[asyncio.AbstractEventLoop]:\n    if uvloop is None:\n        pytest.skip(\"uvloop is not installed\")\n    factory = uvloop.new_event_loop\n    with loop_context(factory) as _loop:\n        asyncio.set_event_loop(_loop)\n        yield _loop\n\n\n@pytest.fixture\ndef netrc_contents(\n    tmp_path: Path,\n    monkeypatch: pytest.MonkeyPatch,\n    request: pytest.FixtureRequest,\n) -> Path:\n    \"\"\"\n    Prepare :file:`.netrc` with given contents.\n\n    Monkey-patches :envvar:`NETRC` to point to created file.\n    \"\"\"\n    netrc_contents = getattr(request, \"param\", None)\n\n    netrc_file_path = tmp_path / \".netrc\"\n    if netrc_contents is not None:\n        netrc_file_path.write_text(netrc_contents)\n\n    monkeypatch.setenv(\"NETRC\", str(netrc_file_path))\n\n    return netrc_file_path\n\n\n@pytest.fixture\ndef netrc_default_contents(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Path:\n    \"\"\"Create a temporary netrc file with default test credentials and set NETRC env var.\"\"\"\n    netrc_file = tmp_path / \".netrc\"\n    netrc_file.write_text(\"default login netrc_user password netrc_pass\\n\")\n\n    monkeypatch.setenv(\"NETRC\", str(netrc_file))\n\n    return netrc_file\n\n\n@pytest.fixture\ndef no_netrc(monkeypatch: pytest.MonkeyPatch) -> None:\n    \"\"\"Ensure NETRC environment variable is not set.\"\"\"\n    monkeypatch.delenv(\"NETRC\", raising=False)\n\n\n@pytest.fixture\ndef netrc_other_host(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Path:\n    \"\"\"Create a temporary netrc file with credentials for a different host and set NETRC env var.\"\"\"\n    netrc_file = tmp_path / \".netrc\"\n    netrc_file.write_text(\"machine other.example.com login user password pass\\n\")\n\n    monkeypatch.setenv(\"NETRC\", str(netrc_file))\n\n    return netrc_file\n\n\n@pytest.fixture\ndef netrc_home_directory(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Path:\n    \"\"\"Create a netrc file in a mocked home directory without setting NETRC env var.\"\"\"\n    home_dir = tmp_path / \"home\"\n    home_dir.mkdir()\n    netrc_filename = \"_netrc\" if platform.system() == \"Windows\" else \".netrc\"\n    netrc_file = home_dir / netrc_filename\n    netrc_file.write_text(\"default login netrc_user password netrc_pass\\n\")\n\n    home_env_var = \"USERPROFILE\" if platform.system() == \"Windows\" else \"HOME\"\n    monkeypatch.setenv(home_env_var, str(home_dir))\n    # Ensure NETRC env var is not set\n    monkeypatch.delenv(\"NETRC\", raising=False)\n\n    return netrc_file\n\n\n@pytest.fixture\ndef start_connection() -> Iterator[mock.Mock]:\n    with mock.patch(\n        \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n        autospec=True,\n        spec_set=True,\n        return_value=mock.create_autospec(socket.socket, spec_set=True, instance=True),\n    ) as start_connection_mock:\n        yield start_connection_mock\n\n\n@pytest.fixture\ndef key_data() -> bytes:\n    return os.urandom(16)\n\n\n@pytest.fixture\ndef key(key_data: bytes) -> bytes:\n    return base64.b64encode(key_data)\n\n\n@pytest.fixture\ndef ws_key(key: bytes) -> str:\n    return base64.b64encode(sha1(key + WS_KEY).digest()).decode()\n\n\n@pytest.fixture\ndef enable_cleanup_closed() -> Iterator[None]:\n    \"\"\"Fixture to override the NEEDS_CLEANUP_CLOSED flag.\n\n    On Python 3.12.7+ and 3.13.1+ enable_cleanup_closed is not needed,\n    however we still want to test that it works.\n    \"\"\"\n    with mock.patch(\"aiohttp.connector.NEEDS_CLEANUP_CLOSED\", True):\n        yield\n\n\n@pytest.fixture\ndef unused_port_socket() -> Iterator[socket.socket]:\n    \"\"\"Return a socket that is unused on the current host.\n\n    Unlike aiohttp_used_port, the socket is yielded so there is no\n    race condition between checking if the port is in use and\n    binding to it later in the test.\n    \"\"\"\n    s = get_unused_port_socket(\"127.0.0.1\")\n    try:\n        yield s\n    finally:\n        s.close()\n\n\n@pytest.fixture(params=[\"zlib\", \"zlib_ng.zlib_ng\", \"isal.isal_zlib\"])\ndef parametrize_zlib_backend(\n    request: pytest.FixtureRequest,\n) -> Iterator[None]:\n    original_backend: ZLibBackendProtocol = ZLibBackend._zlib_backend\n    backend = pytest.importorskip(request.param)\n    set_zlib_backend(backend)\n    yield\n\n    set_zlib_backend(original_backend)\n\n\n@pytest.fixture()\nasync def cleanup_payload_pending_file_closes(\n    loop: asyncio.AbstractEventLoop,\n) -> AsyncIterator[None]:\n    \"\"\"Ensure all pending file close operations complete during test teardown.\"\"\"\n    yield\n    if payload._CLOSE_FUTURES:\n        # Only wait for futures from the current loop\n        loop_futures = [f for f in payload._CLOSE_FUTURES if f.get_loop() is loop]\n        if loop_futures:\n            await asyncio.gather(*loop_futures, return_exceptions=True)\n\n\n@pytest.fixture\nasync def make_client_request(\n    loop: asyncio.AbstractEventLoop,\n) -> AsyncIterator[Callable[[str, URL, Unpack[ClientRequestArgs]], ClientRequest]]:\n    \"\"\"Fixture to help creating test ClientRequest objects with defaults.\"\"\"\n    requests: list[ClientRequest] = []\n    sessions: list[ClientSession] = []\n\n    def maker(\n        method: str, url: URL, **kwargs: Unpack[ClientRequestArgs]\n    ) -> ClientRequest:\n        session = ClientSession()\n        sessions.append(session)\n        default_args: ClientRequestArgs = {\n            \"loop\": loop,\n            \"params\": {},\n            \"headers\": CIMultiDict[str](),\n            \"skip_auto_headers\": None,\n            \"data\": None,\n            \"cookies\": BaseCookie[str](),\n            \"auth\": None,\n            \"version\": HttpVersion11,\n            \"compress\": False,\n            \"chunked\": None,\n            \"expect100\": False,\n            \"response_class\": ClientResponse,\n            \"proxy\": None,\n            \"proxy_auth\": None,\n            \"timer\": TimerNoop(),\n            \"session\": session,\n            \"ssl\": True,\n            \"proxy_headers\": None,\n            \"traces\": [],\n            \"trust_env\": False,\n            \"server_hostname\": None,\n        }\n        request = ClientRequest(method, url, **(default_args | kwargs))\n        requests.append(request)\n        return request\n\n    yield maker\n\n    await asyncio.gather(\n        *(request._close() for request in requests),\n        *(session.close() for session in sessions),\n    )\n\n\n@pytest.fixture\ndef slow_executor() -> Iterator[ThreadPoolExecutor]:\n    \"\"\"Executor that adds delay to simulate slow operations.\n\n    Useful for testing cancellation and race conditions in compression tests.\n    \"\"\"\n\n    class SlowExecutor(ThreadPoolExecutor):\n        \"\"\"Executor that adds delay to operations.\"\"\"\n\n        def submit(\n            self, fn: Callable[..., Any], /, *args: Any, **kwargs: Any\n        ) -> Future[Any]:\n            def slow_fn(*args: Any, **kwargs: Any) -> Any:\n                time.sleep(0.05)  # Add delay to simulate slow operation\n                return fn(*args, **kwargs)\n\n            return super().submit(slow_fn, *args, **kwargs)\n\n    executor = SlowExecutor(max_workers=10)\n    yield executor\n    executor.shutdown(wait=True)\n"
  },
  {
    "path": "tests/data.unknown_mime_type",
    "content": "file content\n"
  },
  {
    "path": "tests/data.zero_bytes",
    "content": ""
  },
  {
    "path": "tests/github-urls.json",
    "content": "[\n  \"/\",\n  \"/advisories\",\n  \"/advisories/{ghsa_id}\",\n  \"/app\",\n  \"/app-manifests/{code}/conversions\",\n  \"/app/hook/config\",\n  \"/app/hook/deliveries\",\n  \"/app/hook/deliveries/{delivery_id}\",\n  \"/app/hook/deliveries/{delivery_id}/attempts\",\n  \"/app/installation-requests\",\n  \"/app/installations\",\n  \"/app/installations/{installation_id}\",\n  \"/app/installations/{installation_id}/access_tokens\",\n  \"/app/installations/{installation_id}/suspended\",\n  \"/applications/{client_id}/grant\",\n  \"/applications/{client_id}/token\",\n  \"/applications/{client_id}/token/scoped\",\n  \"/apps/{app_slug}\",\n  \"/assignments/{assignment_id}\",\n  \"/assignments/{assignment_id}/accepted_assignments\",\n  \"/assignments/{assignment_id}/grades\",\n  \"/classrooms\",\n  \"/classrooms/{classroom_id}\",\n  \"/classrooms/{classroom_id}/assignments\",\n  \"/codes_of_conduct\",\n  \"/codes_of_conduct/{key}\",\n  \"/emojis\",\n  \"/enterprises/{enterprise}/copilot/billing/seats\",\n  \"/enterprises/{enterprise}/copilot/metrics\",\n  \"/enterprises/{enterprise}/copilot/usage\",\n  \"/enterprises/{enterprise}/dependabot/alerts\",\n  \"/enterprises/{enterprise}/secret-scanning/alerts\",\n  \"/enterprises/{enterprise}/team/{team_slug}/copilot/metrics\",\n  \"/enterprises/{enterprise}/team/{team_slug}/copilot/usage\",\n  \"/events\",\n  \"/feeds\",\n  \"/gists\",\n  \"/gists/public\",\n  \"/gists/starred\",\n  \"/gists/{gist_id}\",\n  \"/gists/{gist_id}/comments\",\n  \"/gists/{gist_id}/comments/{comment_id}\",\n  \"/gists/{gist_id}/commits\",\n  \"/gists/{gist_id}/forks\",\n  \"/gists/{gist_id}/star\",\n  \"/gists/{gist_id}/{sha}\",\n  \"/gitignore/templates\",\n  \"/gitignore/templates/{name}\",\n  \"/installation/repositories\",\n  \"/installation/token\",\n  \"/issues\",\n  \"/licenses\",\n  \"/licenses/{license}\",\n  \"/markdown\",\n  \"/markdown/raw\",\n  \"/marketplace_listing/accounts/{account_id}\",\n  \"/marketplace_listing/plans\",\n  \"/marketplace_listing/plans/{plan_id}/accounts\",\n  \"/marketplace_listing/stubbed/accounts/{account_id}\",\n  \"/marketplace_listing/stubbed/plans\",\n  \"/marketplace_listing/stubbed/plans/{plan_id}/accounts\",\n  \"/meta\",\n  \"/networks/{owner}/{repo}/events\",\n  \"/notifications\",\n  \"/notifications/threads/{thread_id}\",\n  \"/notifications/threads/{thread_id}/subscription\",\n  \"/octocat\",\n  \"/organizations\",\n  \"/orgs/{org}\",\n  \"/orgs/{org}/actions/cache/usage\",\n  \"/orgs/{org}/actions/cache/usage-by-repository\",\n  \"/orgs/{org}/actions/oidc/customization/sub\",\n  \"/orgs/{org}/actions/permissions\",\n  \"/orgs/{org}/actions/permissions/repositories\",\n  \"/orgs/{org}/actions/permissions/repositories/{repository_id}\",\n  \"/orgs/{org}/actions/permissions/selected-actions\",\n  \"/orgs/{org}/actions/permissions/workflow\",\n  \"/orgs/{org}/actions/runner-groups\",\n  \"/orgs/{org}/actions/runner-groups/{runner_group_id}\",\n  \"/orgs/{org}/actions/runner-groups/{runner_group_id}/repositories\",\n  \"/orgs/{org}/actions/runner-groups/{runner_group_id}/repositories/{repository_id}\",\n  \"/orgs/{org}/actions/runner-groups/{runner_group_id}/runners\",\n  \"/orgs/{org}/actions/runner-groups/{runner_group_id}/runners/{runner_id}\",\n  \"/orgs/{org}/actions/runners\",\n  \"/orgs/{org}/actions/runners/downloads\",\n  \"/orgs/{org}/actions/runners/generate-jitconfig\",\n  \"/orgs/{org}/actions/runners/registration-token\",\n  \"/orgs/{org}/actions/runners/remove-token\",\n  \"/orgs/{org}/actions/runners/{runner_id}\",\n  \"/orgs/{org}/actions/runners/{runner_id}/labels\",\n  \"/orgs/{org}/actions/runners/{runner_id}/labels/{name}\",\n  \"/orgs/{org}/actions/secrets\",\n  \"/orgs/{org}/actions/secrets/public-key\",\n  \"/orgs/{org}/actions/secrets/{secret_name}\",\n  \"/orgs/{org}/actions/secrets/{secret_name}/repositories\",\n  \"/orgs/{org}/actions/secrets/{secret_name}/repositories/{repository_id}\",\n  \"/orgs/{org}/actions/variables\",\n  \"/orgs/{org}/actions/variables/{name}\",\n  \"/orgs/{org}/actions/variables/{name}/repositories\",\n  \"/orgs/{org}/actions/variables/{name}/repositories/{repository_id}\",\n  \"/orgs/{org}/attestations/{subject_digest}\",\n  \"/orgs/{org}/blocks\",\n  \"/orgs/{org}/blocks/{username}\",\n  \"/orgs/{org}/code-scanning/alerts\",\n  \"/orgs/{org}/code-security/configurations\",\n  \"/orgs/{org}/code-security/configurations/defaults\",\n  \"/orgs/{org}/code-security/configurations/detach\",\n  \"/orgs/{org}/code-security/configurations/{configuration_id}\",\n  \"/orgs/{org}/code-security/configurations/{configuration_id}/attach\",\n  \"/orgs/{org}/code-security/configurations/{configuration_id}/defaults\",\n  \"/orgs/{org}/code-security/configurations/{configuration_id}/repositories\",\n  \"/orgs/{org}/codespaces\",\n  \"/orgs/{org}/codespaces/access\",\n  \"/orgs/{org}/codespaces/access/selected_users\",\n  \"/orgs/{org}/codespaces/secrets\",\n  \"/orgs/{org}/codespaces/secrets/public-key\",\n  \"/orgs/{org}/codespaces/secrets/{secret_name}\",\n  \"/orgs/{org}/codespaces/secrets/{secret_name}/repositories\",\n  \"/orgs/{org}/codespaces/secrets/{secret_name}/repositories/{repository_id}\",\n  \"/orgs/{org}/copilot/billing\",\n  \"/orgs/{org}/copilot/billing/seats\",\n  \"/orgs/{org}/copilot/billing/selected_teams\",\n  \"/orgs/{org}/copilot/billing/selected_users\",\n  \"/orgs/{org}/copilot/metrics\",\n  \"/orgs/{org}/copilot/usage\",\n  \"/orgs/{org}/dependabot/alerts\",\n  \"/orgs/{org}/dependabot/secrets\",\n  \"/orgs/{org}/dependabot/secrets/public-key\",\n  \"/orgs/{org}/dependabot/secrets/{secret_name}\",\n  \"/orgs/{org}/dependabot/secrets/{secret_name}/repositories\",\n  \"/orgs/{org}/dependabot/secrets/{secret_name}/repositories/{repository_id}\",\n  \"/orgs/{org}/docker/conflicts\",\n  \"/orgs/{org}/events\",\n  \"/orgs/{org}/failed_invitations\",\n  \"/orgs/{org}/hooks\",\n  \"/orgs/{org}/hooks/{hook_id}\",\n  \"/orgs/{org}/hooks/{hook_id}/config\",\n  \"/orgs/{org}/hooks/{hook_id}/deliveries\",\n  \"/orgs/{org}/hooks/{hook_id}/deliveries/{delivery_id}\",\n  \"/orgs/{org}/hooks/{hook_id}/deliveries/{delivery_id}/attempts\",\n  \"/orgs/{org}/hooks/{hook_id}/pings\",\n  \"/orgs/{org}/insights/api/route-stats/{actor_type}/{actor_id}\",\n  \"/orgs/{org}/insights/api/subject-stats\",\n  \"/orgs/{org}/insights/api/summary-stats\",\n  \"/orgs/{org}/insights/api/summary-stats/users/{user_id}\",\n  \"/orgs/{org}/insights/api/summary-stats/{actor_type}/{actor_id}\",\n  \"/orgs/{org}/insights/api/time-stats\",\n  \"/orgs/{org}/insights/api/time-stats/users/{user_id}\",\n  \"/orgs/{org}/insights/api/time-stats/{actor_type}/{actor_id}\",\n  \"/orgs/{org}/insights/api/user-stats/{user_id}\",\n  \"/orgs/{org}/installation\",\n  \"/orgs/{org}/installations\",\n  \"/orgs/{org}/interaction-limits\",\n  \"/orgs/{org}/invitations\",\n  \"/orgs/{org}/invitations/{invitation_id}\",\n  \"/orgs/{org}/invitations/{invitation_id}/teams\",\n  \"/orgs/{org}/issues\",\n  \"/orgs/{org}/members\",\n  \"/orgs/{org}/members/{username}\",\n  \"/orgs/{org}/members/{username}/codespaces\",\n  \"/orgs/{org}/members/{username}/codespaces/{codespace_name}\",\n  \"/orgs/{org}/members/{username}/codespaces/{codespace_name}/stop\",\n  \"/orgs/{org}/members/{username}/copilot\",\n  \"/orgs/{org}/memberships/{username}\",\n  \"/orgs/{org}/migrations\",\n  \"/orgs/{org}/migrations/{migration_id}\",\n  \"/orgs/{org}/migrations/{migration_id}/archive\",\n  \"/orgs/{org}/migrations/{migration_id}/repos/{repo_name}/lock\",\n  \"/orgs/{org}/migrations/{migration_id}/repositories\",\n  \"/orgs/{org}/organization-roles\",\n  \"/orgs/{org}/organization-roles/teams/{team_slug}\",\n  \"/orgs/{org}/organization-roles/teams/{team_slug}/{role_id}\",\n  \"/orgs/{org}/organization-roles/users/{username}\",\n  \"/orgs/{org}/organization-roles/users/{username}/{role_id}\",\n  \"/orgs/{org}/organization-roles/{role_id}\",\n  \"/orgs/{org}/organization-roles/{role_id}/teams\",\n  \"/orgs/{org}/organization-roles/{role_id}/users\",\n  \"/orgs/{org}/outside_collaborators\",\n  \"/orgs/{org}/outside_collaborators/{username}\",\n  \"/orgs/{org}/packages\",\n  \"/orgs/{org}/packages/{package_type}/{package_name}\",\n  \"/orgs/{org}/packages/{package_type}/{package_name}/restore\",\n  \"/orgs/{org}/packages/{package_type}/{package_name}/versions\",\n  \"/orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}\",\n  \"/orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}/restore\",\n  \"/orgs/{org}/personal-access-token-requests\",\n  \"/orgs/{org}/personal-access-token-requests/{pat_request_id}\",\n  \"/orgs/{org}/personal-access-token-requests/{pat_request_id}/repositories\",\n  \"/orgs/{org}/personal-access-tokens\",\n  \"/orgs/{org}/personal-access-tokens/{pat_id}\",\n  \"/orgs/{org}/personal-access-tokens/{pat_id}/repositories\",\n  \"/orgs/{org}/projects\",\n  \"/orgs/{org}/properties/schema\",\n  \"/orgs/{org}/properties/schema/{custom_property_name}\",\n  \"/orgs/{org}/properties/values\",\n  \"/orgs/{org}/public_members\",\n  \"/orgs/{org}/public_members/{username}\",\n  \"/orgs/{org}/repos\",\n  \"/orgs/{org}/rulesets\",\n  \"/orgs/{org}/rulesets/rule-suites\",\n  \"/orgs/{org}/rulesets/rule-suites/{rule_suite_id}\",\n  \"/orgs/{org}/rulesets/{ruleset_id}\",\n  \"/orgs/{org}/secret-scanning/alerts\",\n  \"/orgs/{org}/security-advisories\",\n  \"/orgs/{org}/security-managers\",\n  \"/orgs/{org}/security-managers/teams/{team_slug}\",\n  \"/orgs/{org}/settings/billing/actions\",\n  \"/orgs/{org}/settings/billing/packages\",\n  \"/orgs/{org}/settings/billing/shared-storage\",\n  \"/orgs/{org}/team/{team_slug}/copilot/metrics\",\n  \"/orgs/{org}/team/{team_slug}/copilot/usage\",\n  \"/orgs/{org}/teams\",\n  \"/orgs/{org}/teams/{team_slug}\",\n  \"/orgs/{org}/teams/{team_slug}/discussions\",\n  \"/orgs/{org}/teams/{team_slug}/discussions/{discussion_number}\",\n  \"/orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments\",\n  \"/orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}\",\n  \"/orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions\",\n  \"/orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions/{reaction_id}\",\n  \"/orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions\",\n  \"/orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions/{reaction_id}\",\n  \"/orgs/{org}/teams/{team_slug}/invitations\",\n  \"/orgs/{org}/teams/{team_slug}/members\",\n  \"/orgs/{org}/teams/{team_slug}/memberships/{username}\",\n  \"/orgs/{org}/teams/{team_slug}/projects\",\n  \"/orgs/{org}/teams/{team_slug}/projects/{project_id}\",\n  \"/orgs/{org}/teams/{team_slug}/repos\",\n  \"/orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}\",\n  \"/orgs/{org}/teams/{team_slug}/teams\",\n  \"/orgs/{org}/{security_product}/{enablement}\",\n  \"/projects/columns/cards/{card_id}\",\n  \"/projects/columns/cards/{card_id}/moves\",\n  \"/projects/columns/{column_id}\",\n  \"/projects/columns/{column_id}/cards\",\n  \"/projects/columns/{column_id}/moves\",\n  \"/projects/{project_id}\",\n  \"/projects/{project_id}/collaborators\",\n  \"/projects/{project_id}/collaborators/{username}\",\n  \"/projects/{project_id}/collaborators/{username}/permission\",\n  \"/projects/{project_id}/columns\",\n  \"/rate_limit\",\n  \"/repos/{owner}/{repo}\",\n  \"/repos/{owner}/{repo}/actions/artifacts\",\n  \"/repos/{owner}/{repo}/actions/artifacts/{artifact_id}\",\n  \"/repos/{owner}/{repo}/actions/artifacts/{artifact_id}/{archive_format}\",\n  \"/repos/{owner}/{repo}/actions/cache/usage\",\n  \"/repos/{owner}/{repo}/actions/caches\",\n  \"/repos/{owner}/{repo}/actions/caches/{cache_id}\",\n  \"/repos/{owner}/{repo}/actions/jobs/{job_id}\",\n  \"/repos/{owner}/{repo}/actions/jobs/{job_id}/logs\",\n  \"/repos/{owner}/{repo}/actions/jobs/{job_id}/rerun\",\n  \"/repos/{owner}/{repo}/actions/oidc/customization/sub\",\n  \"/repos/{owner}/{repo}/actions/organization-secrets\",\n  \"/repos/{owner}/{repo}/actions/organization-variables\",\n  \"/repos/{owner}/{repo}/actions/permissions\",\n  \"/repos/{owner}/{repo}/actions/permissions/access\",\n  \"/repos/{owner}/{repo}/actions/permissions/selected-actions\",\n  \"/repos/{owner}/{repo}/actions/permissions/workflow\",\n  \"/repos/{owner}/{repo}/actions/runners\",\n  \"/repos/{owner}/{repo}/actions/runners/downloads\",\n  \"/repos/{owner}/{repo}/actions/runners/generate-jitconfig\",\n  \"/repos/{owner}/{repo}/actions/runners/registration-token\",\n  \"/repos/{owner}/{repo}/actions/runners/remove-token\",\n  \"/repos/{owner}/{repo}/actions/runners/{runner_id}\",\n  \"/repos/{owner}/{repo}/actions/runners/{runner_id}/labels\",\n  \"/repos/{owner}/{repo}/actions/runners/{runner_id}/labels/{name}\",\n  \"/repos/{owner}/{repo}/actions/runs\",\n  \"/repos/{owner}/{repo}/actions/runs/{run_id}\",\n  \"/repos/{owner}/{repo}/actions/runs/{run_id}/approvals\",\n  \"/repos/{owner}/{repo}/actions/runs/{run_id}/approve\",\n  \"/repos/{owner}/{repo}/actions/runs/{run_id}/artifacts\",\n  \"/repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}\",\n  \"/repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/jobs\",\n  \"/repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/logs\",\n  \"/repos/{owner}/{repo}/actions/runs/{run_id}/cancel\",\n  \"/repos/{owner}/{repo}/actions/runs/{run_id}/deployment_protection_rule\",\n  \"/repos/{owner}/{repo}/actions/runs/{run_id}/force-cancel\",\n  \"/repos/{owner}/{repo}/actions/runs/{run_id}/jobs\",\n  \"/repos/{owner}/{repo}/actions/runs/{run_id}/logs\",\n  \"/repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments\",\n  \"/repos/{owner}/{repo}/actions/runs/{run_id}/rerun\",\n  \"/repos/{owner}/{repo}/actions/runs/{run_id}/rerun-failed-jobs\",\n  \"/repos/{owner}/{repo}/actions/runs/{run_id}/timing\",\n  \"/repos/{owner}/{repo}/actions/secrets\",\n  \"/repos/{owner}/{repo}/actions/secrets/public-key\",\n  \"/repos/{owner}/{repo}/actions/secrets/{secret_name}\",\n  \"/repos/{owner}/{repo}/actions/variables\",\n  \"/repos/{owner}/{repo}/actions/variables/{name}\",\n  \"/repos/{owner}/{repo}/actions/workflows\",\n  \"/repos/{owner}/{repo}/actions/workflows/{workflow_id}\",\n  \"/repos/{owner}/{repo}/actions/workflows/{workflow_id}/disable\",\n  \"/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches\",\n  \"/repos/{owner}/{repo}/actions/workflows/{workflow_id}/enable\",\n  \"/repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs\",\n  \"/repos/{owner}/{repo}/actions/workflows/{workflow_id}/timing\",\n  \"/repos/{owner}/{repo}/activity\",\n  \"/repos/{owner}/{repo}/assignees\",\n  \"/repos/{owner}/{repo}/assignees/{assignee}\",\n  \"/repos/{owner}/{repo}/attestations\",\n  \"/repos/{owner}/{repo}/attestations/{subject_digest}\",\n  \"/repos/{owner}/{repo}/autolinks\",\n  \"/repos/{owner}/{repo}/autolinks/{autolink_id}\",\n  \"/repos/{owner}/{repo}/automated-security-fixes\",\n  \"/repos/{owner}/{repo}/branches\",\n  \"/repos/{owner}/{repo}/branches/{branch}\",\n  \"/repos/{owner}/{repo}/branches/{branch}/protection\",\n  \"/repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins\",\n  \"/repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews\",\n  \"/repos/{owner}/{repo}/branches/{branch}/protection/required_signatures\",\n  \"/repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks\",\n  \"/repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts\",\n  \"/repos/{owner}/{repo}/branches/{branch}/protection/restrictions\",\n  \"/repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps\",\n  \"/repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams\",\n  \"/repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users\",\n  \"/repos/{owner}/{repo}/branches/{branch}/rename\",\n  \"/repos/{owner}/{repo}/check-runs\",\n  \"/repos/{owner}/{repo}/check-runs/{check_run_id}\",\n  \"/repos/{owner}/{repo}/check-runs/{check_run_id}/annotations\",\n  \"/repos/{owner}/{repo}/check-runs/{check_run_id}/rerequest\",\n  \"/repos/{owner}/{repo}/check-suites\",\n  \"/repos/{owner}/{repo}/check-suites/preferences\",\n  \"/repos/{owner}/{repo}/check-suites/{check_suite_id}\",\n  \"/repos/{owner}/{repo}/check-suites/{check_suite_id}/check-runs\",\n  \"/repos/{owner}/{repo}/check-suites/{check_suite_id}/rerequest\",\n  \"/repos/{owner}/{repo}/code-scanning/alerts\",\n  \"/repos/{owner}/{repo}/code-scanning/alerts/{alert_number}\",\n  \"/repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances\",\n  \"/repos/{owner}/{repo}/code-scanning/analyses\",\n  \"/repos/{owner}/{repo}/code-scanning/analyses/{analysis_id}\",\n  \"/repos/{owner}/{repo}/code-scanning/codeql/databases\",\n  \"/repos/{owner}/{repo}/code-scanning/codeql/databases/{language}\",\n  \"/repos/{owner}/{repo}/code-scanning/codeql/variant-analyses\",\n  \"/repos/{owner}/{repo}/code-scanning/codeql/variant-analyses/{codeql_variant_analysis_id}\",\n  \"/repos/{owner}/{repo}/code-scanning/codeql/variant-analyses/{codeql_variant_analysis_id}/repos/{repo_owner}/{repo_name}\",\n  \"/repos/{owner}/{repo}/code-scanning/default-setup\",\n  \"/repos/{owner}/{repo}/code-scanning/sarifs\",\n  \"/repos/{owner}/{repo}/code-scanning/sarifs/{sarif_id}\",\n  \"/repos/{owner}/{repo}/code-security-configuration\",\n  \"/repos/{owner}/{repo}/codeowners/errors\",\n  \"/repos/{owner}/{repo}/codespaces\",\n  \"/repos/{owner}/{repo}/codespaces/devcontainers\",\n  \"/repos/{owner}/{repo}/codespaces/machines\",\n  \"/repos/{owner}/{repo}/codespaces/new\",\n  \"/repos/{owner}/{repo}/codespaces/permissions_check\",\n  \"/repos/{owner}/{repo}/codespaces/secrets\",\n  \"/repos/{owner}/{repo}/codespaces/secrets/public-key\",\n  \"/repos/{owner}/{repo}/codespaces/secrets/{secret_name}\",\n  \"/repos/{owner}/{repo}/collaborators\",\n  \"/repos/{owner}/{repo}/collaborators/{username}\",\n  \"/repos/{owner}/{repo}/collaborators/{username}/permission\",\n  \"/repos/{owner}/{repo}/comments\",\n  \"/repos/{owner}/{repo}/comments/{comment_id}\",\n  \"/repos/{owner}/{repo}/comments/{comment_id}/reactions\",\n  \"/repos/{owner}/{repo}/comments/{comment_id}/reactions/{reaction_id}\",\n  \"/repos/{owner}/{repo}/commits\",\n  \"/repos/{owner}/{repo}/commits/{commit_sha}/branches-where-head\",\n  \"/repos/{owner}/{repo}/commits/{commit_sha}/comments\",\n  \"/repos/{owner}/{repo}/commits/{commit_sha}/pulls\",\n  \"/repos/{owner}/{repo}/commits/{ref}\",\n  \"/repos/{owner}/{repo}/commits/{ref}/check-runs\",\n  \"/repos/{owner}/{repo}/commits/{ref}/check-suites\",\n  \"/repos/{owner}/{repo}/commits/{ref}/status\",\n  \"/repos/{owner}/{repo}/commits/{ref}/statuses\",\n  \"/repos/{owner}/{repo}/community/profile\",\n  \"/repos/{owner}/{repo}/compare/{basehead}\",\n  \"/repos/{owner}/{repo}/contents/{path}\",\n  \"/repos/{owner}/{repo}/contributors\",\n  \"/repos/{owner}/{repo}/dependabot/alerts\",\n  \"/repos/{owner}/{repo}/dependabot/alerts/{alert_number}\",\n  \"/repos/{owner}/{repo}/dependabot/secrets\",\n  \"/repos/{owner}/{repo}/dependabot/secrets/public-key\",\n  \"/repos/{owner}/{repo}/dependabot/secrets/{secret_name}\",\n  \"/repos/{owner}/{repo}/dependency-graph/compare/{basehead}\",\n  \"/repos/{owner}/{repo}/dependency-graph/sbom\",\n  \"/repos/{owner}/{repo}/dependency-graph/snapshots\",\n  \"/repos/{owner}/{repo}/deployments\",\n  \"/repos/{owner}/{repo}/deployments/{deployment_id}\",\n  \"/repos/{owner}/{repo}/deployments/{deployment_id}/statuses\",\n  \"/repos/{owner}/{repo}/deployments/{deployment_id}/statuses/{status_id}\",\n  \"/repos/{owner}/{repo}/dispatches\",\n  \"/repos/{owner}/{repo}/environments\",\n  \"/repos/{owner}/{repo}/environments/{environment_name}\",\n  \"/repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies\",\n  \"/repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}\",\n  \"/repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules\",\n  \"/repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/apps\",\n  \"/repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/{protection_rule_id}\",\n  \"/repos/{owner}/{repo}/environments/{environment_name}/secrets\",\n  \"/repos/{owner}/{repo}/environments/{environment_name}/secrets/public-key\",\n  \"/repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}\",\n  \"/repos/{owner}/{repo}/environments/{environment_name}/variables\",\n  \"/repos/{owner}/{repo}/environments/{environment_name}/variables/{name}\",\n  \"/repos/{owner}/{repo}/events\",\n  \"/repos/{owner}/{repo}/forks\",\n  \"/repos/{owner}/{repo}/git/blobs\",\n  \"/repos/{owner}/{repo}/git/blobs/{file_sha}\",\n  \"/repos/{owner}/{repo}/git/commits\",\n  \"/repos/{owner}/{repo}/git/commits/{commit_sha}\",\n  \"/repos/{owner}/{repo}/git/matching-refs/{ref}\",\n  \"/repos/{owner}/{repo}/git/ref/{ref}\",\n  \"/repos/{owner}/{repo}/git/refs\",\n  \"/repos/{owner}/{repo}/git/refs/{ref}\",\n  \"/repos/{owner}/{repo}/git/tags\",\n  \"/repos/{owner}/{repo}/git/tags/{tag_sha}\",\n  \"/repos/{owner}/{repo}/git/trees\",\n  \"/repos/{owner}/{repo}/git/trees/{tree_sha}\",\n  \"/repos/{owner}/{repo}/hooks\",\n  \"/repos/{owner}/{repo}/hooks/{hook_id}\",\n  \"/repos/{owner}/{repo}/hooks/{hook_id}/config\",\n  \"/repos/{owner}/{repo}/hooks/{hook_id}/deliveries\",\n  \"/repos/{owner}/{repo}/hooks/{hook_id}/deliveries/{delivery_id}\",\n  \"/repos/{owner}/{repo}/hooks/{hook_id}/deliveries/{delivery_id}/attempts\",\n  \"/repos/{owner}/{repo}/hooks/{hook_id}/pings\",\n  \"/repos/{owner}/{repo}/hooks/{hook_id}/tests\",\n  \"/repos/{owner}/{repo}/import\",\n  \"/repos/{owner}/{repo}/import/authors\",\n  \"/repos/{owner}/{repo}/import/authors/{author_id}\",\n  \"/repos/{owner}/{repo}/import/large_files\",\n  \"/repos/{owner}/{repo}/import/lfs\",\n  \"/repos/{owner}/{repo}/installation\",\n  \"/repos/{owner}/{repo}/interaction-limits\",\n  \"/repos/{owner}/{repo}/invitations\",\n  \"/repos/{owner}/{repo}/invitations/{invitation_id}\",\n  \"/repos/{owner}/{repo}/issues\",\n  \"/repos/{owner}/{repo}/issues/comments\",\n  \"/repos/{owner}/{repo}/issues/comments/{comment_id}\",\n  \"/repos/{owner}/{repo}/issues/comments/{comment_id}/reactions\",\n  \"/repos/{owner}/{repo}/issues/comments/{comment_id}/reactions/{reaction_id}\",\n  \"/repos/{owner}/{repo}/issues/events\",\n  \"/repos/{owner}/{repo}/issues/events/{event_id}\",\n  \"/repos/{owner}/{repo}/issues/{issue_number}\",\n  \"/repos/{owner}/{repo}/issues/{issue_number}/assignees\",\n  \"/repos/{owner}/{repo}/issues/{issue_number}/assignees/{assignee}\",\n  \"/repos/{owner}/{repo}/issues/{issue_number}/comments\",\n  \"/repos/{owner}/{repo}/issues/{issue_number}/events\",\n  \"/repos/{owner}/{repo}/issues/{issue_number}/labels\",\n  \"/repos/{owner}/{repo}/issues/{issue_number}/labels/{name}\",\n  \"/repos/{owner}/{repo}/issues/{issue_number}/lock\",\n  \"/repos/{owner}/{repo}/issues/{issue_number}/reactions\",\n  \"/repos/{owner}/{repo}/issues/{issue_number}/reactions/{reaction_id}\",\n  \"/repos/{owner}/{repo}/issues/{issue_number}/timeline\",\n  \"/repos/{owner}/{repo}/keys\",\n  \"/repos/{owner}/{repo}/keys/{key_id}\",\n  \"/repos/{owner}/{repo}/labels\",\n  \"/repos/{owner}/{repo}/labels/{name}\",\n  \"/repos/{owner}/{repo}/languages\",\n  \"/repos/{owner}/{repo}/license\",\n  \"/repos/{owner}/{repo}/merge-upstream\",\n  \"/repos/{owner}/{repo}/merges\",\n  \"/repos/{owner}/{repo}/milestones\",\n  \"/repos/{owner}/{repo}/milestones/{milestone_number}\",\n  \"/repos/{owner}/{repo}/milestones/{milestone_number}/labels\",\n  \"/repos/{owner}/{repo}/notifications\",\n  \"/repos/{owner}/{repo}/pages\",\n  \"/repos/{owner}/{repo}/pages/builds\",\n  \"/repos/{owner}/{repo}/pages/builds/latest\",\n  \"/repos/{owner}/{repo}/pages/builds/{build_id}\",\n  \"/repos/{owner}/{repo}/pages/deployments\",\n  \"/repos/{owner}/{repo}/pages/deployments/{pages_deployment_id}\",\n  \"/repos/{owner}/{repo}/pages/deployments/{pages_deployment_id}/cancel\",\n  \"/repos/{owner}/{repo}/pages/health\",\n  \"/repos/{owner}/{repo}/private-vulnerability-reporting\",\n  \"/repos/{owner}/{repo}/projects\",\n  \"/repos/{owner}/{repo}/properties/values\",\n  \"/repos/{owner}/{repo}/pulls\",\n  \"/repos/{owner}/{repo}/pulls/comments\",\n  \"/repos/{owner}/{repo}/pulls/comments/{comment_id}\",\n  \"/repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions\",\n  \"/repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions/{reaction_id}\",\n  \"/repos/{owner}/{repo}/pulls/{pull_number}\",\n  \"/repos/{owner}/{repo}/pulls/{pull_number}/codespaces\",\n  \"/repos/{owner}/{repo}/pulls/{pull_number}/comments\",\n  \"/repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies\",\n  \"/repos/{owner}/{repo}/pulls/{pull_number}/commits\",\n  \"/repos/{owner}/{repo}/pulls/{pull_number}/files\",\n  \"/repos/{owner}/{repo}/pulls/{pull_number}/merge\",\n  \"/repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers\",\n  \"/repos/{owner}/{repo}/pulls/{pull_number}/reviews\",\n  \"/repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}\",\n  \"/repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/comments\",\n  \"/repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/dismissals\",\n  \"/repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/events\",\n  \"/repos/{owner}/{repo}/pulls/{pull_number}/update-branch\",\n  \"/repos/{owner}/{repo}/readme\",\n  \"/repos/{owner}/{repo}/readme/{dir}\",\n  \"/repos/{owner}/{repo}/releases\",\n  \"/repos/{owner}/{repo}/releases/assets/{asset_id}\",\n  \"/repos/{owner}/{repo}/releases/generate-notes\",\n  \"/repos/{owner}/{repo}/releases/latest\",\n  \"/repos/{owner}/{repo}/releases/tags/{tag}\",\n  \"/repos/{owner}/{repo}/releases/{release_id}\",\n  \"/repos/{owner}/{repo}/releases/{release_id}/assets\",\n  \"/repos/{owner}/{repo}/releases/{release_id}/reactions\",\n  \"/repos/{owner}/{repo}/releases/{release_id}/reactions/{reaction_id}\",\n  \"/repos/{owner}/{repo}/rules/branches/{branch}\",\n  \"/repos/{owner}/{repo}/rulesets\",\n  \"/repos/{owner}/{repo}/rulesets/rule-suites\",\n  \"/repos/{owner}/{repo}/rulesets/rule-suites/{rule_suite_id}\",\n  \"/repos/{owner}/{repo}/rulesets/{ruleset_id}\",\n  \"/repos/{owner}/{repo}/secret-scanning/alerts\",\n  \"/repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}\",\n  \"/repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}/locations\",\n  \"/repos/{owner}/{repo}/secret-scanning/push-protection-bypasses\",\n  \"/repos/{owner}/{repo}/security-advisories\",\n  \"/repos/{owner}/{repo}/security-advisories/reports\",\n  \"/repos/{owner}/{repo}/security-advisories/{ghsa_id}\",\n  \"/repos/{owner}/{repo}/security-advisories/{ghsa_id}/cve\",\n  \"/repos/{owner}/{repo}/security-advisories/{ghsa_id}/forks\",\n  \"/repos/{owner}/{repo}/stargazers\",\n  \"/repos/{owner}/{repo}/stats/code_frequency\",\n  \"/repos/{owner}/{repo}/stats/commit_activity\",\n  \"/repos/{owner}/{repo}/stats/contributors\",\n  \"/repos/{owner}/{repo}/stats/participation\",\n  \"/repos/{owner}/{repo}/stats/punch_card\",\n  \"/repos/{owner}/{repo}/statuses/{sha}\",\n  \"/repos/{owner}/{repo}/subscribers\",\n  \"/repos/{owner}/{repo}/subscription\",\n  \"/repos/{owner}/{repo}/tags\",\n  \"/repos/{owner}/{repo}/tags/protection\",\n  \"/repos/{owner}/{repo}/tags/protection/{tag_protection_id}\",\n  \"/repos/{owner}/{repo}/tarball/{ref}\",\n  \"/repos/{owner}/{repo}/teams\",\n  \"/repos/{owner}/{repo}/topics\",\n  \"/repos/{owner}/{repo}/traffic/clones\",\n  \"/repos/{owner}/{repo}/traffic/popular/paths\",\n  \"/repos/{owner}/{repo}/traffic/popular/referrers\",\n  \"/repos/{owner}/{repo}/traffic/views\",\n  \"/repos/{owner}/{repo}/transfer\",\n  \"/repos/{owner}/{repo}/vulnerability-alerts\",\n  \"/repos/{owner}/{repo}/zipball/{ref}\",\n  \"/repos/{template_owner}/{template_repo}/generate\",\n  \"/repositories\",\n  \"/search/code\",\n  \"/search/commits\",\n  \"/search/issues\",\n  \"/search/labels\",\n  \"/search/repositories\",\n  \"/search/topics\",\n  \"/search/users\",\n  \"/teams/{team_id}\",\n  \"/teams/{team_id}/discussions\",\n  \"/teams/{team_id}/discussions/{discussion_number}\",\n  \"/teams/{team_id}/discussions/{discussion_number}/comments\",\n  \"/teams/{team_id}/discussions/{discussion_number}/comments/{comment_number}\",\n  \"/teams/{team_id}/discussions/{discussion_number}/comments/{comment_number}/reactions\",\n  \"/teams/{team_id}/discussions/{discussion_number}/reactions\",\n  \"/teams/{team_id}/invitations\",\n  \"/teams/{team_id}/members\",\n  \"/teams/{team_id}/members/{username}\",\n  \"/teams/{team_id}/memberships/{username}\",\n  \"/teams/{team_id}/projects\",\n  \"/teams/{team_id}/projects/{project_id}\",\n  \"/teams/{team_id}/repos\",\n  \"/teams/{team_id}/repos/{owner}/{repo}\",\n  \"/teams/{team_id}/teams\",\n  \"/user\",\n  \"/user/blocks\",\n  \"/user/blocks/{username}\",\n  \"/user/codespaces\",\n  \"/user/codespaces/secrets\",\n  \"/user/codespaces/secrets/public-key\",\n  \"/user/codespaces/secrets/{secret_name}\",\n  \"/user/codespaces/secrets/{secret_name}/repositories\",\n  \"/user/codespaces/secrets/{secret_name}/repositories/{repository_id}\",\n  \"/user/codespaces/{codespace_name}\",\n  \"/user/codespaces/{codespace_name}/exports\",\n  \"/user/codespaces/{codespace_name}/exports/{export_id}\",\n  \"/user/codespaces/{codespace_name}/machines\",\n  \"/user/codespaces/{codespace_name}/publish\",\n  \"/user/codespaces/{codespace_name}/start\",\n  \"/user/codespaces/{codespace_name}/stop\",\n  \"/user/docker/conflicts\",\n  \"/user/email/visibility\",\n  \"/user/emails\",\n  \"/user/followers\",\n  \"/user/following\",\n  \"/user/following/{username}\",\n  \"/user/gpg_keys\",\n  \"/user/gpg_keys/{gpg_key_id}\",\n  \"/user/installations\",\n  \"/user/installations/{installation_id}/repositories\",\n  \"/user/installations/{installation_id}/repositories/{repository_id}\",\n  \"/user/interaction-limits\",\n  \"/user/issues\",\n  \"/user/keys\",\n  \"/user/keys/{key_id}\",\n  \"/user/marketplace_purchases\",\n  \"/user/marketplace_purchases/stubbed\",\n  \"/user/memberships/orgs\",\n  \"/user/memberships/orgs/{org}\",\n  \"/user/migrations\",\n  \"/user/migrations/{migration_id}\",\n  \"/user/migrations/{migration_id}/archive\",\n  \"/user/migrations/{migration_id}/repos/{repo_name}/lock\",\n  \"/user/migrations/{migration_id}/repositories\",\n  \"/user/orgs\",\n  \"/user/packages\",\n  \"/user/packages/{package_type}/{package_name}\",\n  \"/user/packages/{package_type}/{package_name}/restore\",\n  \"/user/packages/{package_type}/{package_name}/versions\",\n  \"/user/packages/{package_type}/{package_name}/versions/{package_version_id}\",\n  \"/user/packages/{package_type}/{package_name}/versions/{package_version_id}/restore\",\n  \"/user/projects\",\n  \"/user/public_emails\",\n  \"/user/repos\",\n  \"/user/repository_invitations\",\n  \"/user/repository_invitations/{invitation_id}\",\n  \"/user/social_accounts\",\n  \"/user/ssh_signing_keys\",\n  \"/user/ssh_signing_keys/{ssh_signing_key_id}\",\n  \"/user/starred\",\n  \"/user/starred/{owner}/{repo}\",\n  \"/user/subscriptions\",\n  \"/user/teams\",\n  \"/user/{account_id}\",\n  \"/users\",\n  \"/users/{username}\",\n  \"/users/{username}/attestations/{subject_digest}\",\n  \"/users/{username}/docker/conflicts\",\n  \"/users/{username}/events\",\n  \"/users/{username}/events/orgs/{org}\",\n  \"/users/{username}/events/public\",\n  \"/users/{username}/followers\",\n  \"/users/{username}/following\",\n  \"/users/{username}/following/{target_user}\",\n  \"/users/{username}/gists\",\n  \"/users/{username}/gpg_keys\",\n  \"/users/{username}/hovercard\",\n  \"/users/{username}/installation\",\n  \"/users/{username}/keys\",\n  \"/users/{username}/orgs\",\n  \"/users/{username}/packages\",\n  \"/users/{username}/packages/{package_type}/{package_name}\",\n  \"/users/{username}/packages/{package_type}/{package_name}/restore\",\n  \"/users/{username}/packages/{package_type}/{package_name}/versions\",\n  \"/users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}\",\n  \"/users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}/restore\",\n  \"/users/{username}/projects\",\n  \"/users/{username}/received_events\",\n  \"/users/{username}/received_events/public\",\n  \"/users/{username}/repos\",\n  \"/users/{username}/settings/billing/actions\",\n  \"/users/{username}/settings/billing/packages\",\n  \"/users/{username}/settings/billing/shared-storage\",\n  \"/users/{username}/social_accounts\",\n  \"/users/{username}/ssh_signing_keys\",\n  \"/users/{username}/starred\",\n  \"/users/{username}/subscriptions\",\n  \"/versions\",\n  \"/zen\"\n]\n"
  },
  {
    "path": "tests/isolated/check_for_client_response_leak.py",
    "content": "import asyncio\nimport contextlib\nimport gc\nimport socket\nimport sys\n\nfrom aiohttp import ClientError, ClientSession, web\nfrom aiohttp.test_utils import REUSE_ADDRESS\n\ngc.set_debug(gc.DEBUG_LEAK)\n\n\nasync def main() -> None:\n    app = web.Application()\n\n    async def stream_handler(request: web.Request) -> web.Response:\n        assert request.transport is not None\n        request.transport.close()  # Forcefully closing connection\n        return web.Response()\n\n    app.router.add_get(\"/stream\", stream_handler)\n    with socket.create_server((\"127.0.0.1\", 0), reuse_port=REUSE_ADDRESS) as sock:\n        port = sock.getsockname()[1]\n\n        runner = web.AppRunner(app)\n        await runner.setup()\n        site = web.SockSite(runner, sock)\n        await site.start()\n\n        session = ClientSession()\n\n        async def fetch_stream(url: str) -> None:\n            \"\"\"Fetch a stream and read a few bytes from it.\"\"\"\n            with contextlib.suppress(ClientError):\n                await session.get(url)\n\n        client_task = asyncio.create_task(\n            fetch_stream(f\"http://localhost:{port}/stream\")\n        )\n        await client_task\n        gc.collect()\n        client_response_present = any(\n            type(obj).__name__ == \"ClientResponse\" for obj in gc.garbage\n        )\n        await session.close()\n        await runner.cleanup()\n        sys.exit(1 if client_response_present else 0)\n\n\nasyncio.run(main())\n"
  },
  {
    "path": "tests/isolated/check_for_request_leak.py",
    "content": "import asyncio\nimport gc\nimport socket\nimport sys\nfrom typing import NoReturn\n\nfrom aiohttp import ClientSession, web\nfrom aiohttp.test_utils import REUSE_ADDRESS\n\ngc.set_debug(gc.DEBUG_LEAK)\n\n\nasync def main() -> None:\n    app = web.Application()\n\n    async def handler(request: web.Request) -> NoReturn:\n        await request.json()\n        assert False\n\n    app.router.add_route(\"GET\", \"/json\", handler)\n    with socket.create_server((\"127.0.0.1\", 0), reuse_port=REUSE_ADDRESS) as sock:\n        port = sock.getsockname()[1]\n\n        runner = web.AppRunner(app)\n        await runner.setup()\n        site = web.SockSite(runner, sock)\n        await site.start()\n\n        async with ClientSession() as session:\n            async with session.get(f\"http://127.0.0.1:{port}/json\") as resp:\n                await resp.read()\n\n        # Give time for the cancelled task to be collected\n        await asyncio.sleep(0.5)\n        gc.collect()\n        request_present = any(type(obj).__name__ == \"Request\" for obj in gc.garbage)\n        await session.close()\n        await runner.cleanup()\n    sys.exit(1 if request_present else 0)\n\n\nasyncio.run(main())\n"
  },
  {
    "path": "tests/sample.txt",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "tests/test_base_protocol.py",
    "content": "import asyncio\nfrom contextlib import suppress\nfrom unittest import mock\n\nimport pytest\n\nfrom aiohttp.base_protocol import BaseProtocol\n\n\nasync def test_loop() -> None:\n    loop = asyncio.get_event_loop()\n    asyncio.set_event_loop(None)\n    pr = BaseProtocol(loop)\n    assert pr._loop is loop\n\n\nasync def test_pause_writing() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop)\n    assert not pr._paused\n    assert pr.writing_paused is False\n    pr.pause_writing()\n    assert pr._paused\n    assert pr.writing_paused is True  # type: ignore[unreachable]\n\n\nasync def test_pause_reading_no_transport() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop)\n    assert not pr._reading_paused\n    pr.pause_reading()\n    assert not pr._reading_paused\n\n\nasync def test_pause_reading_stub_transport() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop)\n    tr = asyncio.Transport()\n    pr.transport = tr\n    assert not pr._reading_paused\n    pr.pause_reading()\n    assert pr._reading_paused\n\n\nasync def test_resume_reading_no_transport() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop)\n    pr._reading_paused = True\n    pr.resume_reading()\n    assert pr._reading_paused\n\n\nasync def test_resume_reading_stub_transport() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop)\n    tr = asyncio.Transport()\n    pr.transport = tr\n    pr._reading_paused = True\n    pr.resume_reading()\n    assert not pr._reading_paused\n\n\nasync def test_resume_writing_no_waiters() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop=loop)\n    pr.pause_writing()\n    assert pr._paused\n    pr.resume_writing()\n    assert not pr._paused\n\n\nasync def test_resume_writing_waiter_done() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop=loop)\n    waiter = mock.Mock(done=mock.Mock(return_value=True))\n    pr._drain_waiter = waiter\n    pr._paused = True\n    pr.resume_writing()\n    assert not pr._paused\n    assert waiter.mock_calls == [mock.call.done()]\n\n\nasync def test_connection_made() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop=loop)\n    tr = mock.Mock()\n    assert pr.transport is None\n    pr.connection_made(tr)\n    assert pr.transport is not None\n\n\nasync def test_connection_lost_not_paused() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop=loop)\n    tr = mock.Mock()\n    pr.connection_made(tr)\n    assert pr.connected\n    pr.connection_lost(None)\n    assert pr.transport is None\n    assert not pr.connected\n\n\nasync def test_connection_lost_paused_without_waiter() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop=loop)\n    tr = mock.Mock()\n    pr.connection_made(tr)\n    assert pr.connected\n    pr.pause_writing()\n    pr.connection_lost(None)\n    assert pr.transport is None\n    assert not pr.connected\n\n\nasync def test_connection_lost_waiter_done() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop=loop)\n    pr._paused = True\n    waiter = mock.Mock(done=mock.Mock(return_value=True))\n    pr._drain_waiter = waiter\n    pr.connection_lost(None)\n    assert pr._drain_waiter is None\n    assert waiter.mock_calls == [mock.call.done()]  # type: ignore[unreachable]\n\n\nasync def test_drain_lost() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop=loop)\n    tr = mock.Mock()\n    pr.connection_made(tr)\n    pr.connection_lost(None)\n    with pytest.raises(ConnectionResetError):\n        await pr._drain_helper()\n\n\nasync def test_drain_not_paused() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop=loop)\n    tr = mock.Mock()\n    pr.connection_made(tr)\n    assert pr._drain_waiter is None\n    await pr._drain_helper()\n    assert pr._drain_waiter is None\n\n\nasync def test_resume_drain_waited() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop=loop)\n    tr = mock.Mock()\n    pr.connection_made(tr)\n    pr.pause_writing()\n\n    t = loop.create_task(pr._drain_helper())\n    await asyncio.sleep(0)\n\n    assert pr._drain_waiter is not None\n    pr.resume_writing()\n    await t\n    assert pr._drain_waiter is None\n\n\nasync def test_lost_drain_waited_ok() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop=loop)\n    tr = mock.Mock()\n    pr.connection_made(tr)\n    pr.pause_writing()\n\n    t = loop.create_task(pr._drain_helper())\n    await asyncio.sleep(0)\n\n    assert pr._drain_waiter is not None\n    pr.connection_lost(None)\n    await t\n    assert pr._drain_waiter is None\n\n\nasync def test_lost_drain_waited_exception() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop=loop)\n    tr = mock.Mock()\n    pr.connection_made(tr)\n    pr.pause_writing()\n\n    t = loop.create_task(pr._drain_helper())\n    await asyncio.sleep(0)\n\n    assert pr._drain_waiter is not None\n    exc = RuntimeError()\n    pr.connection_lost(exc)\n    with pytest.raises(ConnectionError, match=r\"^Connection lost$\") as cm:\n        await t\n    assert cm.value.__cause__ is exc\n    assert pr._drain_waiter is None\n\n\nasync def test_lost_drain_cancelled() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop=loop)\n    tr = mock.Mock()\n    pr.connection_made(tr)\n    pr.pause_writing()\n\n    fut = loop.create_future()\n\n    async def wait() -> None:\n        fut.set_result(None)\n        await pr._drain_helper()\n\n    t = loop.create_task(wait())\n    await fut\n    t.cancel()\n\n    assert pr._drain_waiter is not None\n    pr.connection_lost(None)\n    with suppress(asyncio.CancelledError):\n        await t\n    assert pr._drain_waiter is None\n\n\nasync def test_resume_drain_cancelled() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop=loop)\n    tr = mock.Mock()\n    pr.connection_made(tr)\n    pr.pause_writing()\n\n    fut = loop.create_future()\n\n    async def wait() -> None:\n        fut.set_result(None)\n        await pr._drain_helper()\n\n    t = loop.create_task(wait())\n    await fut\n    t.cancel()\n\n    assert pr._drain_waiter is not None\n    pr.resume_writing()\n    with suppress(asyncio.CancelledError):\n        await t\n    assert pr._drain_waiter is None\n\n\nasync def test_parallel_drain_race_condition() -> None:\n    loop = asyncio.get_event_loop()\n    pr = BaseProtocol(loop=loop)\n    tr = mock.Mock()\n    pr.connection_made(tr)\n    pr.pause_writing()\n\n    ts = [loop.create_task(pr._drain_helper()) for _ in range(5)]\n    assert not (await asyncio.wait(ts, timeout=0.5))[\n        0\n    ], \"All draining tasks must be pending\"\n\n    assert pr._drain_waiter is not None\n    pr.resume_writing()\n    await asyncio.gather(*ts)\n    assert pr._drain_waiter is None\n"
  },
  {
    "path": "tests/test_benchmarks_client.py",
    "content": "\"\"\"codspeed benchmarks for HTTP client.\"\"\"\n\nimport asyncio\n\nimport pytest\nfrom pytest_codspeed import BenchmarkFixture\nfrom yarl import URL\n\nfrom aiohttp import hdrs, request, web\nfrom aiohttp.pytest_plugin import AiohttpClient, AiohttpServer\n\n\ndef test_one_hundred_simple_get_requests(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 100 simple GET requests.\"\"\"\n    message_count = 100\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(message_count):\n            await client.get(\"/\")\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\ndef test_one_hundred_simple_get_requests_alternating_clients(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 100 simple GET requests with alternating clients.\"\"\"\n    message_count = 100\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client1 = await aiohttp_client(app)\n        client2 = await aiohttp_client(app)\n        for i in range(message_count):\n            if i % 2 == 0:\n                await client1.get(\"/\")\n            else:\n                await client2.get(\"/\")\n        await client1.close()\n        await client2.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\ndef test_one_hundred_simple_get_requests_no_session(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_server: AiohttpServer,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 100 simple GET requests without a session.\"\"\"\n    message_count = 100\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = loop.run_until_complete(aiohttp_server(app))\n    url = URL(f\"http://{server.host}:{server.port}/\")\n\n    async def run_client_benchmark() -> None:\n        for _ in range(message_count):\n            async with request(\"GET\", url):\n                pass\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\ndef test_one_hundred_simple_get_requests_multiple_methods_route(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 100 simple GET requests on a route with multiple methods.\"\"\"\n    message_count = 100\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    # GET intentionally registered last to ensure time complexity\n    # of the route lookup is benchmarked\n    for method in (\"DELETE\", \"HEAD\", \"OPTIONS\", \"PATCH\", \"POST\", \"PUT\", \"GET\"):\n        app.router.add_route(method, \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(message_count):\n            await client.get(\"/\")\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\ndef test_one_hundred_get_requests_with_1024_chunked_payload(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 100 GET requests with a small payload of 1024 bytes.\"\"\"\n    message_count = 100\n    payload = b\"a\" * 1024\n\n    async def handler(request: web.Request) -> web.Response:\n        resp = web.Response(body=payload)\n        resp.enable_chunked_encoding()\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(message_count):\n            resp = await client.get(\"/\")\n            await resp.read()\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\ndef test_one_hundred_get_requests_with_30000_chunked_payload(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 100 GET requests with a payload of 30000 bytes.\"\"\"\n    message_count = 100\n    payload = b\"a\" * 30000\n\n    async def handler(request: web.Request) -> web.Response:\n        resp = web.Response(body=payload)\n        resp.enable_chunked_encoding()\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(message_count):\n            resp = await client.get(\"/\")\n            await resp.read()\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\ndef test_one_hundred_get_requests_with_512kib_chunked_payload(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 100 GET requests with a payload of 512KiB using read.\"\"\"\n    message_count = 100\n    payload = b\"a\" * (2**19)\n\n    async def handler(request: web.Request) -> web.Response:\n        resp = web.Response(body=payload)\n        resp.enable_chunked_encoding()\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(message_count):\n            resp = await client.get(\"/\")\n            await resp.read()\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\ndef test_one_hundred_get_requests_iter_chunks_on_512kib_chunked_payload(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 100 GET requests with a payload of 512KiB using iter_chunks.\"\"\"\n    message_count = 100\n    payload = b\"a\" * (2**19)\n\n    async def handler(request: web.Request) -> web.Response:\n        resp = web.Response(body=payload)\n        resp.enable_chunked_encoding()\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(message_count):\n            resp = await client.get(\"/\")\n            async for _ in resp.content.iter_chunks():\n                pass\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\ndef test_get_request_with_251308_compressed_chunked_payload(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark compressed GET requests with a payload of 251308.\"\"\"\n    # This payload compresses to 251308 bytes\n    payload = b\"\".join(\n        [\n            bytes((*range(0, i), *range(i, 0, -1)))\n            for _ in range(255)\n            for i in range(255)\n        ]\n    )\n\n    async def handler(request: web.Request) -> web.Response:\n        resp = web.Response(body=payload, zlib_executor_size=16384)\n        resp.enable_compression()\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        resp = await client.get(\"/\")\n        await resp.read()\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\ndef test_one_hundred_get_requests_with_1024_content_length_payload(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 100 GET requests with a small payload of 1024 bytes.\"\"\"\n    message_count = 100\n    payload = b\"a\" * 1024\n    headers = {hdrs.CONTENT_LENGTH: str(len(payload))}\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=payload, headers=headers)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(message_count):\n            resp = await client.get(\"/\")\n            await resp.read()\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\ndef test_one_hundred_get_requests_with_30000_content_length_payload(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 100 GET requests with a payload of 30000 bytes.\"\"\"\n    message_count = 100\n    payload = b\"a\" * 30000\n    headers = {hdrs.CONTENT_LENGTH: str(len(payload))}\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=payload, headers=headers)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(message_count):\n            resp = await client.get(\"/\")\n            await resp.read()\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\ndef test_one_hundred_get_requests_with_512kib_content_length_payload(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 100 GET requests with a payload of 512KiB.\"\"\"\n    message_count = 100\n    payload = b\"a\" * (2**19)\n    headers = {hdrs.CONTENT_LENGTH: str(len(payload))}\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=payload, headers=headers)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(message_count):\n            resp = await client.get(\"/\")\n            await resp.read()\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\ndef test_one_hundred_simple_post_requests(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 100 simple POST requests.\"\"\"\n    message_count = 100\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"POST\", \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(message_count):\n            await client.post(\"/\", data=b\"any\")\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\ndef test_one_hundred_json_post_requests(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 100 JSON POST requests that check the content-type.\"\"\"\n    message_count = 100\n\n    async def handler(request: web.Request) -> web.Response:\n        _ = request.content_type\n        _ = request.charset\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"POST\", \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(message_count):\n            await client.post(\"/\", json={\"key\": \"value\"})\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\ndef test_ten_streamed_responses_iter_any(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 10 streamed responses using iter_any.\"\"\"\n    message_count = 10\n    data = b\"x\" * 65536  # 64 KiB chunk size\n\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse()\n        await resp.prepare(request)\n        for _ in range(10):\n            await resp.write(data)\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(message_count):\n            resp = await client.get(\"/\")\n            async for _ in resp.content.iter_any():\n                pass\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\ndef test_ten_streamed_responses_iter_chunked_4096(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 10 streamed responses using iter_chunked 4096.\"\"\"\n    message_count = 10\n    data = b\"x\" * 65536  # 64 KiB chunk size, 4096 iter_chunked\n\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse()\n        await resp.prepare(request)\n        for _ in range(10):\n            await resp.write(data)\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(message_count):\n            resp = await client.get(\"/\")\n            async for _ in resp.content.iter_chunked(4096):\n                pass\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\ndef test_ten_streamed_responses_iter_chunked_65536(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 10 streamed responses using iter_chunked 65536.\"\"\"\n    message_count = 10\n    data = b\"x\" * 65536  # 64 KiB chunk size, 64 KiB iter_chunked\n\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse()\n        await resp.prepare(request)\n        for _ in range(10):\n            await resp.write(data)\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(message_count):\n            resp = await client.get(\"/\")\n            async for _ in resp.content.iter_chunked(65536):\n                pass\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n\n\ndef test_ten_streamed_responses_iter_chunks(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark 10 streamed responses using iter_chunks.\"\"\"\n    message_count = 10\n    data = b\"x\" * 65536  # 64 KiB chunk size\n\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse()\n        await resp.prepare(request)\n        for _ in range(10):\n            await resp.write(data)\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(message_count):\n            resp = await client.get(\"/\")\n            async for _ in resp.content.iter_chunks():\n                pass\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n"
  },
  {
    "path": "tests/test_benchmarks_client_request.py",
    "content": "\"\"\"codspeed benchmarks for client requests.\"\"\"\n\nimport asyncio\nimport sys\nfrom collections.abc import Callable\nfrom http.cookies import BaseCookie\nfrom typing import Any\n\nfrom multidict import CIMultiDict\nfrom pytest_codspeed import BenchmarkFixture\nfrom yarl import URL\n\nfrom aiohttp.client_reqrep import ClientRequest, ClientRequestArgs, ClientResponse\nfrom aiohttp.cookiejar import CookieJar\nfrom aiohttp.helpers import TimerNoop\nfrom aiohttp.http_writer import HttpVersion11\nfrom aiohttp.tracing import Trace\n\nif sys.version_info >= (3, 11):\n    from typing import Unpack\n\n    _RequestMaker = Callable[[str, URL, Unpack[ClientRequestArgs]], ClientRequest]\nelse:\n    _RequestMaker = Any\n\n\nasync def test_client_request_update_cookies(\n    benchmark: BenchmarkFixture,\n    make_client_request: _RequestMaker,\n) -> None:\n    url = URL(\"http://python.org\")\n    req = make_client_request(\"get\", url)\n    cookie_jar = CookieJar()\n    cookie_jar.update_cookies({\"string\": \"Another string\"})\n    cookies = cookie_jar.filter_cookies(url)\n    assert cookies[\"string\"].value == \"Another string\"\n\n    @benchmark\n    def _run() -> None:\n        req._update_cookies(cookies=cookies)\n\n\ndef test_create_client_request_with_cookies(\n    loop: asyncio.AbstractEventLoop,\n    benchmark: BenchmarkFixture,\n) -> None:\n    url = URL(\"http://python.org\")\n    cookie_jar = CookieJar()\n    cookie_jar.update_cookies({\"cookie\": \"value\"})\n    cookies = cookie_jar.filter_cookies(url)\n    assert cookies[\"cookie\"].value == \"value\"\n    timer = TimerNoop()\n    traces: list[Trace] = []\n    headers = CIMultiDict[str]()\n\n    @benchmark\n    def _run() -> None:\n        ClientRequest(\n            method=\"get\",\n            url=url,\n            loop=loop,\n            params=None,\n            skip_auto_headers=None,\n            response_class=ClientResponse,\n            proxy=None,\n            proxy_auth=None,\n            proxy_headers=None,\n            timer=timer,\n            session=None,  # type: ignore[arg-type]\n            ssl=True,\n            traces=traces,\n            trust_env=False,\n            server_hostname=None,\n            headers=headers,\n            data=None,\n            cookies=cookies,\n            auth=None,\n            version=HttpVersion11,\n            compress=False,\n            chunked=None,\n            expect100=False,\n        )\n\n\ndef test_create_client_request_with_headers(\n    loop: asyncio.AbstractEventLoop,\n    benchmark: BenchmarkFixture,\n) -> None:\n    url = URL(\"http://python.org\")\n    timer = TimerNoop()\n    traces: list[Trace] = []\n    headers = CIMultiDict({\"header\": \"value\", \"another\": \"header\"})\n    cookies = BaseCookie[str]()\n\n    @benchmark\n    def _run() -> None:\n        ClientRequest(\n            method=\"get\",\n            url=url,\n            loop=loop,\n            params=None,\n            skip_auto_headers=None,\n            response_class=ClientResponse,\n            proxy=None,\n            proxy_auth=None,\n            proxy_headers=None,\n            timer=timer,\n            session=None,  # type: ignore[arg-type]\n            ssl=True,\n            traces=traces,\n            trust_env=False,\n            server_hostname=None,\n            headers=headers,\n            data=None,\n            cookies=cookies,\n            auth=None,\n            version=HttpVersion11,\n            compress=False,\n            chunked=None,\n            expect100=False,\n        )\n\n\ndef test_send_client_request_one_hundred(\n    loop: asyncio.AbstractEventLoop,\n    benchmark: BenchmarkFixture,\n    make_client_request: _RequestMaker,\n) -> None:\n    url = URL(\"http://python.org\")\n\n    async def make_req() -> ClientRequest:\n        \"\"\"Need async context.\"\"\"\n        return make_client_request(\"get\", url)\n\n    req = loop.run_until_complete(make_req())\n\n    class MockTransport(asyncio.Transport):\n        \"\"\"Mock transport for testing that do no real I/O.\"\"\"\n\n        def is_closing(self) -> bool:\n            \"\"\"Swallow is_closing.\"\"\"\n            return False\n\n        def write(self, data: bytes | bytearray | memoryview) -> None:\n            \"\"\"Swallow writes.\"\"\"\n\n    class MockProtocol(asyncio.BaseProtocol):\n\n        def __init__(self) -> None:\n            self.transport = MockTransport()\n\n        @property\n        def writing_paused(self) -> bool:\n            return False\n\n        async def _drain_helper(self) -> None:\n            \"\"\"Swallow drain.\"\"\"\n\n        def start_timeout(self) -> None:\n            \"\"\"Swallow start_timeout.\"\"\"\n\n    class MockConnector:\n\n        def __init__(self) -> None:\n            self.force_close = False\n\n    class MockConnection:\n        def __init__(self) -> None:\n            self.transport = None\n            self.protocol = MockProtocol()\n            self._connector = MockConnector()\n\n    conn = MockConnection()\n\n    async def send_requests() -> None:\n        for _ in range(100):\n            await req._send(conn)  # type: ignore[arg-type]\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(send_requests())\n"
  },
  {
    "path": "tests/test_benchmarks_client_ws.py",
    "content": "\"\"\"codspeed benchmarks for websocket client.\"\"\"\n\nimport asyncio\n\nimport pytest\nfrom pytest_codspeed import BenchmarkFixture\n\nfrom aiohttp import web\nfrom aiohttp._websocket.helpers import MSG_SIZE\nfrom aiohttp.pytest_plugin import AiohttpClient\n\n\ndef test_one_thousand_round_trip_websocket_text_messages(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark round trip of 1000 WebSocket text messages.\"\"\"\n    message_count = 1000\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        for _ in range(message_count):\n            await ws.send_str(\"answer\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_websocket_benchmark() -> None:\n        client = await aiohttp_client(app)\n        resp = await client.ws_connect(\"/\")\n        for _ in range(message_count):\n            await resp.receive()\n        await resp.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_websocket_benchmark())\n\n\n@pytest.mark.parametrize(\"msg_size\", [6, MSG_SIZE * 4], ids=[\"small\", \"large\"])\ndef test_one_thousand_round_trip_websocket_binary_messages(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n    msg_size: int,\n) -> None:\n    \"\"\"Benchmark round trip of 1000 WebSocket binary messages.\"\"\"\n    message_count = 1000\n    raw_message = b\"x\" * msg_size\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        for _ in range(message_count):\n            await ws.send_bytes(raw_message)\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_websocket_benchmark() -> None:\n        client = await aiohttp_client(app)\n        resp = await client.ws_connect(\"/\")\n        for _ in range(message_count):\n            await resp.receive()\n        await resp.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_websocket_benchmark())\n\n\ndef test_one_thousand_large_round_trip_websocket_text_messages(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark round trip of 100 large WebSocket text messages.\"\"\"\n    message_count = 100\n    raw_message = \"x\" * MSG_SIZE * 4\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        for _ in range(message_count):\n            await ws.send_str(raw_message)\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_websocket_benchmark() -> None:\n        client = await aiohttp_client(app)\n        resp = await client.ws_connect(\"/\")\n        for _ in range(message_count):\n            await resp.receive()\n        await resp.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_websocket_benchmark())\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\ndef test_client_send_large_websocket_compressed_messages(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark send of compressed WebSocket binary messages.\"\"\"\n    message_count = 10\n    raw_message = b\"x\" * 2**19  # 512 KiB\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        for _ in range(message_count):\n            await ws.receive()\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_websocket_benchmark() -> None:\n        client = await aiohttp_client(app)\n        resp = await client.ws_connect(\"/\", compress=15)\n        for _ in range(message_count):\n            await resp.send_bytes(raw_message)\n        await resp.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_websocket_benchmark())\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\ndef test_client_receive_large_websocket_compressed_messages(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark receive of compressed WebSocket binary messages.\"\"\"\n    message_count = 10\n    raw_message = b\"x\" * 2**19  # 512 KiB\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        for _ in range(message_count):\n            await ws.send_bytes(raw_message)\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_websocket_benchmark() -> None:\n        client = await aiohttp_client(app)\n        resp = await client.ws_connect(\"/\", compress=15)\n        for _ in range(message_count):\n            await resp.receive()\n        await resp.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_websocket_benchmark())\n"
  },
  {
    "path": "tests/test_benchmarks_cookiejar.py",
    "content": "\"\"\"codspeed benchmarks for cookies.\"\"\"\n\nfrom http.cookies import BaseCookie\n\nfrom pytest_codspeed import BenchmarkFixture\nfrom yarl import URL\n\nfrom aiohttp.cookiejar import CookieJar\n\n\nasync def test_load_cookies_into_temp_cookiejar(benchmark: BenchmarkFixture) -> None:\n    \"\"\"Benchmark for creating a temp CookieJar and filtering by URL.\n\n    This benchmark matches what the client request does when cookies\n    are passed to the request.\n    \"\"\"\n    all_cookies: BaseCookie[str] = BaseCookie()\n    url = URL(\"http://example.com\")\n    cookies = {\"cookie1\": \"value1\", \"cookie2\": \"value2\"}\n\n    @benchmark\n    def _run() -> None:\n        tmp_cookie_jar = CookieJar()\n        tmp_cookie_jar.update_cookies(cookies)\n        req_cookies = tmp_cookie_jar.filter_cookies(url)\n        all_cookies.load(req_cookies)\n"
  },
  {
    "path": "tests/test_benchmarks_http_websocket.py",
    "content": "\"\"\"codspeed benchmarks for http websocket.\"\"\"\n\nimport asyncio\n\nimport pytest\nfrom pytest_codspeed import BenchmarkFixture\n\nfrom aiohttp._websocket.helpers import MSG_SIZE, PACK_LEN3\nfrom aiohttp._websocket.reader import WebSocketDataQueue\nfrom aiohttp.base_protocol import BaseProtocol\nfrom aiohttp.http_websocket import WebSocketReader, WebSocketWriter, WSMsgType\n\n\ndef test_read_large_binary_websocket_messages(\n    loop: asyncio.AbstractEventLoop, benchmark: BenchmarkFixture\n) -> None:\n    \"\"\"Read one hundred large binary websocket messages.\"\"\"\n    queue = WebSocketDataQueue(BaseProtocol(loop), 2**18, loop=loop)\n    reader = WebSocketReader(queue, max_msg_size=2**18)\n\n    # PACK3 has a minimum message length of 2**16 bytes.\n    message = b\"x\" * ((2**16) + 1)\n    msg_length = len(message)\n    first_byte = 0x80 | 0 | WSMsgType.BINARY.value\n    header = PACK_LEN3(first_byte, 127, msg_length)\n    raw_message = header + message\n    feed_data = reader.feed_data\n\n    @benchmark\n    def _run() -> None:\n        for _ in range(100):\n            feed_data(raw_message)\n\n\ndef test_read_one_hundred_websocket_text_messages(\n    loop: asyncio.AbstractEventLoop, benchmark: BenchmarkFixture\n) -> None:\n    \"\"\"Benchmark reading 100 WebSocket text messages.\"\"\"\n    queue = WebSocketDataQueue(BaseProtocol(loop), 2**16, loop=loop)\n    reader = WebSocketReader(queue, max_msg_size=2**16)\n    raw_message = (\n        b'\\x81~\\x01!{\"id\":1,\"src\":\"shellyplugus-c049ef8c30e4\",\"dst\":\"aios-1453812500'\n        b'8\",\"result\":{\"name\":null,\"id\":\"shellyplugus-c049ef8c30e4\",\"mac\":\"C049EF8C30E'\n        b'4\",\"slot\":1,\"model\":\"SNPL-00116US\",\"gen\":2,\"fw_id\":\"20231219-133953/1.1.0-g3'\n        b'4b5d4f\",\"ver\":\"1.1.0\",\"app\":\"PlugUS\",\"auth_en\":false,\"auth_domain\":null}}'\n    )\n    feed_data = reader.feed_data\n\n    @benchmark\n    def _run() -> None:\n        for _ in range(100):\n            feed_data(raw_message)\n\n\nclass MockTransport(asyncio.Transport):\n    \"\"\"Mock transport for testing that do no real I/O.\"\"\"\n\n    def is_closing(self) -> bool:\n        \"\"\"Swallow is_closing.\"\"\"\n        return False\n\n    def write(self, data: bytes | bytearray | memoryview) -> None:\n        \"\"\"Swallow writes.\"\"\"\n\n\nclass MockProtocol(BaseProtocol):\n\n    async def _drain_helper(self) -> None:\n        \"\"\"Swallow drain.\"\"\"\n\n\ndef test_send_one_hundred_websocket_text_messages(\n    loop: asyncio.AbstractEventLoop, benchmark: BenchmarkFixture\n) -> None:\n    \"\"\"Benchmark sending 100 WebSocket text messages.\"\"\"\n    writer = WebSocketWriter(MockProtocol(loop=loop), MockTransport())\n    raw_message = b\"Hello, World!\" * 100\n\n    async def _send_one_hundred_websocket_text_messages() -> None:\n        for _ in range(100):\n            await writer.send_frame(raw_message, WSMsgType.TEXT)\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(_send_one_hundred_websocket_text_messages())\n\n\ndef test_send_one_hundred_large_websocket_text_messages(\n    loop: asyncio.AbstractEventLoop, benchmark: BenchmarkFixture\n) -> None:\n    \"\"\"Benchmark sending 100 WebSocket text messages.\"\"\"\n    writer = WebSocketWriter(MockProtocol(loop=loop), MockTransport())\n    raw_message = b\"x\" * MSG_SIZE * 4\n\n    async def _send_one_hundred_websocket_text_messages() -> None:\n        for _ in range(100):\n            await writer.send_frame(raw_message, WSMsgType.TEXT)\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(_send_one_hundred_websocket_text_messages())\n\n\ndef test_send_one_hundred_websocket_text_messages_with_mask(\n    loop: asyncio.AbstractEventLoop, benchmark: BenchmarkFixture\n) -> None:\n    \"\"\"Benchmark sending 100 masked WebSocket text messages.\"\"\"\n    writer = WebSocketWriter(MockProtocol(loop=loop), MockTransport(), use_mask=True)\n    raw_message = b\"Hello, World!\" * 100\n\n    async def _send_one_hundred_websocket_text_messages() -> None:\n        for _ in range(100):\n            await writer.send_frame(raw_message, WSMsgType.TEXT)\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(_send_one_hundred_websocket_text_messages())\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\ndef test_send_one_hundred_websocket_compressed_messages(\n    loop: asyncio.AbstractEventLoop, benchmark: BenchmarkFixture\n) -> None:\n    \"\"\"Benchmark sending 100 WebSocket compressed messages.\"\"\"\n    writer = WebSocketWriter(MockProtocol(loop=loop), MockTransport(), compress=15)\n    raw_message = b\"Hello, World!\" * 100\n\n    async def _send_one_hundred_websocket_compressed_messages() -> None:\n        for _ in range(100):\n            await writer.send_frame(raw_message, WSMsgType.BINARY)\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(_send_one_hundred_websocket_compressed_messages())\n"
  },
  {
    "path": "tests/test_benchmarks_http_writer.py",
    "content": "\"\"\"codspeed benchmarks for http writer.\"\"\"\n\nfrom multidict import CIMultiDict\nfrom pytest_codspeed import BenchmarkFixture\n\nfrom aiohttp import hdrs\nfrom aiohttp.http_writer import _serialize_headers\n\n\ndef test_serialize_headers(benchmark: BenchmarkFixture) -> None:\n    \"\"\"Benchmark 100 calls to _serialize_headers.\"\"\"\n    status_line = \"HTTP/1.1 200 OK\"\n    headers = CIMultiDict(\n        {\n            hdrs.CONTENT_TYPE: \"text/plain\",\n            hdrs.CONTENT_LENGTH: \"100\",\n            hdrs.CONNECTION: \"keep-alive\",\n            hdrs.DATE: \"Mon, 23 May 2005 22:38:34 GMT\",\n            hdrs.SERVER: \"Test/1.0\",\n            hdrs.CONTENT_ENCODING: \"gzip\",\n            hdrs.VARY: \"Accept-Encoding\",\n            hdrs.CACHE_CONTROL: \"no-cache\",\n            hdrs.PRAGMA: \"no-cache\",\n            hdrs.EXPIRES: \"0\",\n            hdrs.LAST_MODIFIED: \"Mon, 23 May 2005 22:38:34 GMT\",\n            hdrs.ETAG: \"1234567890\",\n        }\n    )\n\n    @benchmark\n    def _run() -> None:\n        for _ in range(100):\n            _serialize_headers(status_line, headers)\n"
  },
  {
    "path": "tests/test_benchmarks_web_fileresponse.py",
    "content": "\"\"\"codspeed benchmarks for the web file responses.\"\"\"\n\nimport asyncio\nimport pathlib\n\nfrom multidict import CIMultiDict\nfrom pytest_codspeed import BenchmarkFixture\n\nfrom aiohttp import ClientResponse, web\nfrom aiohttp.pytest_plugin import AiohttpClient\n\n\ndef test_simple_web_file_response(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark creating 100 simple web.FileResponse.\"\"\"\n    response_count = 100\n    filepath = pathlib.Path(__file__).parent / \"sample.txt\"\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        return web.FileResponse(path=filepath)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_file_response_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(response_count):\n            await client.get(\"/\")\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_file_response_benchmark())\n\n\ndef test_simple_web_file_sendfile_fallback_response(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark creating 100 simple web.FileResponse without sendfile.\"\"\"\n    response_count = 100\n    filepath = pathlib.Path(__file__).parent / \"sample.txt\"\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        transport = request.transport\n        assert transport is not None\n        transport._sendfile_compatible = False  # type: ignore[attr-defined]\n        return web.FileResponse(path=filepath)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def run_file_response_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(response_count):\n            await client.get(\"/\")\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_file_response_benchmark())\n\n\ndef test_simple_web_file_response_not_modified(\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark web.FileResponse that return a 304.\"\"\"\n    response_count = 100\n    filepath = pathlib.Path(__file__).parent / \"sample.txt\"\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        return web.FileResponse(path=filepath)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    async def make_last_modified_header() -> CIMultiDict[str]:\n        client = await aiohttp_client(app)\n        resp = await client.get(\"/\")\n        last_modified = resp.headers[\"Last-Modified\"]\n        headers = CIMultiDict({\"If-Modified-Since\": last_modified})\n        return headers\n\n    async def run_file_response_benchmark(\n        headers: CIMultiDict[str],\n    ) -> ClientResponse:\n        client = await aiohttp_client(app)\n        for _ in range(response_count):\n            resp = await client.get(\"/\", headers=headers)\n\n        await client.close()\n        return resp  # type: ignore[possibly-undefined]\n\n    headers = loop.run_until_complete(make_last_modified_header())\n\n    @benchmark\n    def _run() -> None:\n        resp = loop.run_until_complete(run_file_response_benchmark(headers))\n        assert resp.status == 304\n"
  },
  {
    "path": "tests/test_benchmarks_web_middleware.py",
    "content": "\"\"\"codspeed benchmarks for web middlewares.\"\"\"\n\nimport asyncio\n\nfrom pytest_codspeed import BenchmarkFixture\n\nfrom aiohttp import web\nfrom aiohttp.pytest_plugin import AiohttpClient\nfrom aiohttp.typedefs import Handler\n\n\ndef test_ten_web_middlewares(\n    benchmark: BenchmarkFixture,\n    loop: asyncio.AbstractEventLoop,\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Benchmark 100 requests with 10 middlewares.\"\"\"\n    message_count = 100\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    class MiddlewareClass:\n        async def call(\n            self, request: web.Request, handler: Handler\n        ) -> web.StreamResponse:\n            return await handler(request)\n\n    for _ in range(10):\n        app.middlewares.append(MiddlewareClass().call)\n\n    async def run_client_benchmark() -> None:\n        client = await aiohttp_client(app)\n        for _ in range(message_count):\n            await client.get(\"/\")\n        await client.close()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_client_benchmark())\n"
  },
  {
    "path": "tests/test_benchmarks_web_response.py",
    "content": "\"\"\"codspeed benchmarks for the web responses.\"\"\"\n\nfrom pytest_codspeed import BenchmarkFixture\n\nfrom aiohttp import web\n\n\ndef test_simple_web_response(benchmark: BenchmarkFixture) -> None:\n    \"\"\"Benchmark creating 100 simple web.Response.\"\"\"\n    response_count = 100\n\n    @benchmark\n    def _run() -> None:\n        for _ in range(response_count):\n            web.Response()\n\n\ndef test_web_response_with_headers(benchmark: BenchmarkFixture) -> None:\n    \"\"\"Benchmark creating 100 web.Response with headers.\"\"\"\n    response_count = 100\n    headers = {\n        \"Content-Type\": \"text/plain\",\n        \"Server\": \"aiohttp\",\n        \"Date\": \"Sun, 01 Aug 2021 12:00:00 GMT\",\n    }\n\n    @benchmark\n    def _run() -> None:\n        for _ in range(response_count):\n            web.Response(headers=headers)\n\n\ndef test_web_response_with_bytes_body(\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Benchmark creating 100 web.Response with bytes.\"\"\"\n    response_count = 100\n\n    @benchmark\n    def _run() -> None:\n        for _ in range(response_count):\n            web.Response(body=b\"Hello, World!\")\n\n\ndef test_web_response_with_text_body(benchmark: BenchmarkFixture) -> None:\n    \"\"\"Benchmark creating 100 web.Response with text.\"\"\"\n    response_count = 100\n\n    @benchmark\n    def _run() -> None:\n        for _ in range(response_count):\n            web.Response(text=\"Hello, World!\")\n\n\ndef test_simple_web_stream_response(benchmark: BenchmarkFixture) -> None:\n    \"\"\"Benchmark creating 100 simple web.StreamResponse.\"\"\"\n    response_count = 100\n\n    @benchmark\n    def _run() -> None:\n        for _ in range(response_count):\n            web.StreamResponse()\n"
  },
  {
    "path": "tests/test_benchmarks_web_urldispatcher.py",
    "content": "\"\"\"codspeed benchmarks for the URL dispatcher.\"\"\"\n\nimport asyncio\nimport json\nimport pathlib\nimport random\nimport string\nfrom pathlib import Path\nfrom typing import NoReturn, cast\nfrom unittest import mock\n\nimport pytest\nfrom multidict import CIMultiDict, CIMultiDictProxy\nfrom pytest_codspeed import BenchmarkFixture\nfrom yarl import URL\n\nimport aiohttp\nfrom aiohttp import web\nfrom aiohttp.http import HttpVersion, RawRequestMessage\n\n\n@pytest.fixture\ndef github_urls() -> list[str]:\n    \"\"\"GitHub api urls.\"\"\"\n    # The fixture provides OpenAPI generated info for github.\n    # To update the local data file please run the following command:\n    # $ curl https://raw.githubusercontent.com/github/rest-api-description/refs/heads/main/descriptions/api.github.com/api.github.com.json | jq \".paths | keys\" > github-urls.json\n\n    here = Path(__file__).parent\n    with (here / \"github-urls.json\").open() as f:\n        urls = json.load(f)\n\n    return cast(list[str], urls)\n\n\ndef _mock_request(method: str, path: str) -> web.Request:\n    message = RawRequestMessage(\n        method,\n        path,\n        HttpVersion(1, 1),\n        CIMultiDictProxy(CIMultiDict()),\n        (),\n        False,\n        None,\n        False,\n        False,\n        URL(path),\n    )\n\n    return web.Request(\n        message, mock.Mock(), mock.Mock(), mock.Mock(), mock.Mock(), mock.Mock()\n    )\n\n\ndef test_resolve_root_route(\n    loop: asyncio.AbstractEventLoop,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Resolve top level PlainResources route 100 times.\"\"\"\n    resolve_count = 100\n\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    app.freeze()\n    router = app.router\n    request = _mock_request(method=\"GET\", path=\"/\")\n\n    async def run_url_dispatcher_benchmark() -> web.UrlMappingMatchInfo | None:\n        ret = None\n        for _ in range(resolve_count):\n            ret = await router.resolve(request)\n\n        return ret\n\n    ret = loop.run_until_complete(run_url_dispatcher_benchmark())\n    assert ret is not None\n    assert ret.get_info()[\"path\"] == \"/\", ret.get_info()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_url_dispatcher_benchmark())\n\n\ndef test_resolve_root_route_with_many_fixed_routes(\n    loop: asyncio.AbstractEventLoop,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Resolve top level PlainResources route 100 times.\"\"\"\n    resolve_count = 100\n\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    for count in range(250):\n        app.router.add_route(\"GET\", f\"/api/server/dispatch/{count}/update\", handler)\n        app.router.add_route(\"GET\", f\"/api/server/dispatch/{count}\", handler)\n    app.router.add_route(\"GET\", \"/api/server/dispatch\", handler)\n    app.router.add_route(\"GET\", \"/api/server\", handler)\n    app.router.add_route(\"GET\", \"/api\", handler)\n    app.freeze()\n    router = app.router\n    request = _mock_request(method=\"GET\", path=\"/\")\n\n    async def run_url_dispatcher_benchmark() -> web.UrlMappingMatchInfo | None:\n        ret = None\n        for _ in range(resolve_count):\n            ret = await router.resolve(request)\n\n        return ret\n\n    ret = loop.run_until_complete(run_url_dispatcher_benchmark())\n    assert ret is not None\n    assert ret.get_info()[\"path\"] == \"/\", ret.get_info()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_url_dispatcher_benchmark())\n\n\ndef test_resolve_static_root_route(\n    loop: asyncio.AbstractEventLoop,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Resolve top level StaticResource route 100 times.\"\"\"\n    resolve_count = 100\n\n    app = web.Application()\n    here = pathlib.Path(aiohttp.__file__).parent\n    app.router.add_static(\"/\", here)\n    app.freeze()\n    router = app.router\n    request = _mock_request(method=\"GET\", path=\"/\")\n\n    async def run_url_dispatcher_benchmark() -> web.UrlMappingMatchInfo | None:\n        ret = None\n        for _ in range(resolve_count):\n            ret = await router.resolve(request)\n\n        return ret\n\n    ret = loop.run_until_complete(run_url_dispatcher_benchmark())\n    assert ret is not None\n    assert ret.get_info()[\"directory\"] == here, ret.get_info()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_url_dispatcher_benchmark())\n\n\ndef test_resolve_single_fixed_url_with_many_routes(\n    loop: asyncio.AbstractEventLoop,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Resolve PlainResources route 100 times.\"\"\"\n    resolve_count = 100\n\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    for count in range(250):\n        app.router.add_route(\"GET\", f\"/api/server/dispatch/{count}/update\", handler)\n    app.freeze()\n    router = app.router\n    request = _mock_request(method=\"GET\", path=\"/api/server/dispatch/1/update\")\n\n    async def run_url_dispatcher_benchmark() -> web.UrlMappingMatchInfo | None:\n        ret = None\n        for _ in range(resolve_count):\n            ret = await router.resolve(request)\n\n        return ret\n\n    ret = loop.run_until_complete(run_url_dispatcher_benchmark())\n    assert ret is not None\n    assert ret.get_info()[\"path\"] == \"/api/server/dispatch/1/update\", ret.get_info()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_url_dispatcher_benchmark())\n\n\ndef test_resolve_multiple_fixed_url_with_many_routes(\n    loop: asyncio.AbstractEventLoop,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Resolve 250 different PlainResources routes.\"\"\"\n\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    for count in range(250):\n        app.router.add_route(\"GET\", f\"/api/server/dispatch/{count}/update\", handler)\n    app.freeze()\n    router = app.router\n\n    requests = [\n        _mock_request(method=\"GET\", path=f\"/api/server/dispatch/{count}/update\")\n        for count in range(250)\n    ]\n\n    async def run_url_dispatcher_benchmark() -> web.UrlMappingMatchInfo | None:\n        ret = None\n        for request in requests:\n            ret = await router.resolve(request)\n        return ret\n\n    ret = loop.run_until_complete(run_url_dispatcher_benchmark())\n    assert ret is not None\n    assert ret.get_info()[\"path\"] == \"/api/server/dispatch/249/update\", ret.get_info()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_url_dispatcher_benchmark())\n\n\ndef test_resolve_multiple_level_fixed_url_with_many_routes(\n    loop: asyncio.AbstractEventLoop,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Resolve 1024 different PlainResources routes.\"\"\"\n\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    urls = [\n        f\"/api/{a}/{b}/{c}/{d}/{e}/update\"\n        for a in (\"a\", \"b\", \"c\", \"d\")\n        for b in (\"e\", \"f\", \"g\", \"h\")\n        for c in (\"i\", \"j\", \"k\", \"l\")\n        for d in (\"m\", \"n\", \"o\", \"p\")\n        for e in (\"n\", \"o\", \"p\", \"q\")\n    ]\n    for url in urls:\n        app.router.add_route(\"GET\", url, handler)\n    app.freeze()\n    router = app.router\n\n    requests = [(_mock_request(method=\"GET\", path=url), url) for url in urls]\n\n    async def run_url_dispatcher_benchmark() -> web.UrlMappingMatchInfo | None:\n        ret = None\n        for request, path in requests:\n            ret = await router.resolve(request)\n\n        return ret\n\n    ret = loop.run_until_complete(run_url_dispatcher_benchmark())\n    assert ret is not None\n    assert ret.get_info()[\"path\"] == url, ret.get_info()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_url_dispatcher_benchmark())\n\n\ndef test_resolve_dynamic_resource_url_with_many_static_routes(\n    loop: asyncio.AbstractEventLoop,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Resolve different a DynamicResource when there are 250 PlainResources registered.\"\"\"\n\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    for count in range(250):\n        app.router.add_route(\"GET\", f\"/api/server/other/{count}/update\", handler)\n    app.router.add_route(\"GET\", \"/api/server/dispatch/{customer}/update\", handler)\n    app.freeze()\n    router = app.router\n\n    requests = [\n        _mock_request(method=\"GET\", path=f\"/api/server/dispatch/{customer}/update\")\n        for customer in range(250)\n    ]\n\n    async def run_url_dispatcher_benchmark() -> web.UrlMappingMatchInfo | None:\n        ret = None\n        for request in requests:\n            ret = await router.resolve(request)\n\n        return ret\n\n    ret = loop.run_until_complete(run_url_dispatcher_benchmark())\n    assert ret is not None\n    assert (\n        ret.get_info()[\"formatter\"] == \"/api/server/dispatch/{customer}/update\"\n    ), ret.get_info()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_url_dispatcher_benchmark())\n\n\ndef test_resolve_dynamic_resource_url_with_many_dynamic_routes(\n    loop: asyncio.AbstractEventLoop,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Resolve different a DynamicResource when there are 250 DynamicResources registered.\"\"\"\n\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    for count in range(250):\n        app.router.add_route(\n            \"GET\", f\"/api/server/other/{{customer}}/update{count}\", handler\n        )\n    app.router.add_route(\"GET\", \"/api/server/dispatch/{customer}/update\", handler)\n    app.freeze()\n    router = app.router\n\n    requests = [\n        _mock_request(method=\"GET\", path=f\"/api/server/dispatch/{customer}/update\")\n        for customer in range(250)\n    ]\n\n    async def run_url_dispatcher_benchmark() -> web.UrlMappingMatchInfo | None:\n        ret = None\n        for request in requests:\n            ret = await router.resolve(request)\n\n        return ret\n\n    ret = loop.run_until_complete(run_url_dispatcher_benchmark())\n    assert ret is not None\n    assert (\n        ret.get_info()[\"formatter\"] == \"/api/server/dispatch/{customer}/update\"\n    ), ret.get_info()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_url_dispatcher_benchmark())\n\n\ndef test_resolve_dynamic_resource_url_with_many_dynamic_routes_with_common_prefix(\n    loop: asyncio.AbstractEventLoop,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Resolve different a DynamicResource when there are 250 DynamicResources registered with the same common prefix.\"\"\"\n\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    for count in range(250):\n        app.router.add_route(\"GET\", f\"/api/{{customer}}/show_{count}\", handler)\n    app.router.add_route(\"GET\", \"/api/{customer}/update\", handler)\n    app.freeze()\n    router = app.router\n\n    requests = [\n        _mock_request(method=\"GET\", path=f\"/api/{customer}/update\")\n        for customer in range(250)\n    ]\n\n    async def run_url_dispatcher_benchmark() -> web.UrlMappingMatchInfo | None:\n        ret = None\n        for request in requests:\n            ret = await router.resolve(request)\n\n        return ret\n\n    ret = loop.run_until_complete(run_url_dispatcher_benchmark())\n    assert ret is not None\n    assert ret.get_info()[\"formatter\"] == \"/api/{customer}/update\", ret.get_info()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_url_dispatcher_benchmark())\n\n\ndef test_resolve_gitapi(\n    loop: asyncio.AbstractEventLoop,\n    benchmark: BenchmarkFixture,\n    github_urls: list[str],\n) -> None:\n    \"\"\"Resolve DynamicResource for simulated github API.\"\"\"\n\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    for url in github_urls:\n        app.router.add_get(url, handler)\n    app.freeze()\n    router = app.router\n\n    # PR reviews API was selected absolutely voluntary.\n    # It is not any special but sits somewhere in the middle of the urls list.\n    # If anybody has better idea please suggest.\n\n    alnums = string.ascii_letters + string.digits\n\n    requests = []\n    for i in range(250):\n        owner = \"\".join(random.sample(alnums, 10))\n        repo = \"\".join(random.sample(alnums, 10))\n        pull_number = random.randint(0, 250)\n        requests.append(\n            _mock_request(\n                method=\"GET\", path=f\"/repos/{owner}/{repo}/pulls/{pull_number}/reviews\"\n            )\n        )\n\n    async def run_url_dispatcher_benchmark() -> web.UrlMappingMatchInfo | None:\n        ret = None\n        for request in requests:\n            ret = await router.resolve(request)\n        return ret\n\n    ret = loop.run_until_complete(run_url_dispatcher_benchmark())\n    assert ret is not None\n    assert (\n        ret.get_info()[\"formatter\"]\n        == \"/repos/{owner}/{repo}/pulls/{pull_number}/reviews\"\n    ), ret.get_info()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_url_dispatcher_benchmark())\n\n\ndef test_resolve_gitapi_subapps(\n    loop: asyncio.AbstractEventLoop,\n    benchmark: BenchmarkFixture,\n    github_urls: list[str],\n) -> None:\n    \"\"\"Resolve DynamicResource for simulated github API, grouped in subapps.\"\"\"\n\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    subapps = {\n        \"gists\": web.Application(),\n        \"orgs\": web.Application(),\n        \"projects\": web.Application(),\n        \"repos\": web.Application(),\n        \"teams\": web.Application(),\n        \"user\": web.Application(),\n        \"users\": web.Application(),\n    }\n\n    app = web.Application()\n    for url in github_urls:\n        parts = url.split(\"/\")\n        subapp = subapps.get(parts[1])\n        if subapp is not None:\n            sub_url = \"/\".join([\"\"] + parts[2:])\n            if not sub_url:\n                sub_url = \"/\"\n            subapp.router.add_get(sub_url, handler)\n        else:\n            app.router.add_get(url, handler)\n    for key, subapp in subapps.items():\n        app.add_subapp(\"/\" + key, subapp)\n    app.freeze()\n    router = app.router\n\n    # PR reviews API was selected absolutely voluntary.\n    # It is not any special but sits somewhere in the middle of the urls list.\n    # If anybody has better idea please suggest.\n\n    alnums = string.ascii_letters + string.digits\n\n    requests = []\n    for i in range(250):\n        owner = \"\".join(random.sample(alnums, 10))\n        repo = \"\".join(random.sample(alnums, 10))\n        pull_number = random.randint(0, 250)\n        requests.append(\n            _mock_request(\n                method=\"GET\", path=f\"/repos/{owner}/{repo}/pulls/{pull_number}/reviews\"\n            )\n        )\n\n    async def run_url_dispatcher_benchmark() -> web.UrlMappingMatchInfo | None:\n        ret = None\n        for request in requests:\n            ret = await router.resolve(request)\n        return ret\n\n    ret = loop.run_until_complete(run_url_dispatcher_benchmark())\n    assert ret is not None\n    assert (\n        ret.get_info()[\"formatter\"]\n        == \"/repos/{owner}/{repo}/pulls/{pull_number}/reviews\"\n    ), ret.get_info()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_url_dispatcher_benchmark())\n\n\ndef test_resolve_gitapi_root(\n    loop: asyncio.AbstractEventLoop,\n    benchmark: BenchmarkFixture,\n    github_urls: list[str],\n) -> None:\n    \"\"\"Resolve the plain root for simulated github API.\"\"\"\n\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    for url in github_urls:\n        app.router.add_get(url, handler)\n    app.freeze()\n    router = app.router\n\n    request = _mock_request(method=\"GET\", path=\"/\")\n\n    async def run_url_dispatcher_benchmark() -> web.UrlMappingMatchInfo | None:\n        ret = None\n        for i in range(250):\n            ret = await router.resolve(request)\n        return ret\n\n    ret = loop.run_until_complete(run_url_dispatcher_benchmark())\n    assert ret is not None\n    assert ret.get_info()[\"path\"] == \"/\", ret.get_info()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_url_dispatcher_benchmark())\n\n\ndef test_resolve_prefix_resources_many_prefix_many_plain(\n    loop: asyncio.AbstractEventLoop,\n    benchmark: BenchmarkFixture,\n) -> None:\n    \"\"\"Resolve prefix resource (sub_app) whene 250 PlainResources registered and there are 250 subapps that shares the same sub_app path prefix.\"\"\"\n\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    for count in range(250):\n        app.router.add_get(f\"/api/server/other/{count}/update\", handler)\n    for count in range(250):\n        subapp = web.Application()\n        # sub_apps exists for handling deep enough nested route trees\n        subapp.router.add_get(\"/deep/enough/sub/path\", handler)\n        app.add_subapp(f\"/api/path/to/plugin/{count}\", subapp)\n    app.freeze()\n    router = app.router\n\n    requests = [\n        _mock_request(method=\"GET\", path=\"/api/path/to/plugin/249/deep/enough/sub/path\")\n        for customer in range(250)\n    ]\n\n    async def run_url_dispatcher_benchmark() -> web.UrlMappingMatchInfo | None:\n        ret = None\n        for request in requests:\n            ret = await router.resolve(request)\n        return ret\n\n    ret = loop.run_until_complete(run_url_dispatcher_benchmark())\n    assert ret is not None\n    assert (\n        ret.get_info()[\"path\"] == \"/api/path/to/plugin/249/deep/enough/sub/path\"\n    ), ret.get_info()\n\n    @benchmark\n    def _run() -> None:\n        loop.run_until_complete(run_url_dispatcher_benchmark())\n"
  },
  {
    "path": "tests/test_circular_imports.py",
    "content": "\"\"\"Tests for circular imports in all local packages and modules.\n\nThis ensures all internal packages can be imported right away without\nany need to import some other module before doing so.\n\nThis module is based on an idea that pytest uses for self-testing:\n* https://github.com/sanitizers/octomachinery/blob/be18b54/tests/circular_imports_test.py\n* https://github.com/pytest-dev/pytest/blob/d18c75b/testing/test_meta.py\n* https://twitter.com/codewithanthony/status/1229445110510735361\n\"\"\"\n\nimport os\nimport pkgutil\nimport socket\nimport subprocess\nimport sys\nfrom collections.abc import Generator\nfrom itertools import chain\nfrom pathlib import Path\nfrom types import ModuleType\nfrom typing import TYPE_CHECKING, Union\n\nimport pytest\n\nif TYPE_CHECKING:\n    from _pytest.mark.structures import ParameterSet\n\nimport aiohttp\n\n\ndef _mark_aiohttp_worker_for_skipping(\n    importables: list[str],\n) -> list[Union[str, \"ParameterSet\"]]:\n    return [\n        (\n            pytest.param(\n                importable,\n                marks=pytest.mark.skipif(\n                    not hasattr(socket, \"AF_UNIX\"), reason=\"It's a UNIX-only module\"\n                ),\n            )\n            if importable == \"aiohttp.worker\"\n            else importable\n        )\n        for importable in importables\n    ]\n\n\ndef _find_all_importables(pkg: ModuleType) -> list[str]:\n    \"\"\"Find all importables in the project.\n\n    Return them in order.\n    \"\"\"\n    return sorted(\n        set(\n            chain.from_iterable(\n                _discover_path_importables(Path(p), pkg.__name__) for p in pkg.__path__\n            ),\n        ),\n    )\n\n\ndef _discover_path_importables(\n    pkg_pth: Path,\n    pkg_name: str,\n) -> Generator[str, None, None]:\n    \"\"\"Yield all importables under a given path and package.\"\"\"\n    for dir_path, _d, file_names in os.walk(pkg_pth):\n        pkg_dir_path = Path(dir_path)\n\n        if pkg_dir_path.parts[-1] == \"__pycache__\":\n            continue\n\n        if all(Path(_).suffix != \".py\" for _ in file_names):\n            continue\n\n        rel_pt = pkg_dir_path.relative_to(pkg_pth)\n        pkg_pref = \".\".join((pkg_name,) + rel_pt.parts)\n        yield from (\n            pkg_path\n            for _, pkg_path, _ in pkgutil.walk_packages(\n                (str(pkg_dir_path),),\n                prefix=f\"{pkg_pref}.\",\n            )\n        )\n\n\n@pytest.mark.parametrize(\n    \"import_path\",\n    _mark_aiohttp_worker_for_skipping(_find_all_importables(aiohttp)),\n)\ndef test_no_warnings(import_path: str) -> None:\n    \"\"\"Verify that exploding importables doesn't explode.\n\n    This is seeking for any import errors including ones caused\n    by circular imports.\n    \"\"\"\n    imp_cmd = (\n        # fmt: off\n        sys.executable,\n        \"-W\", \"error\",\n        # The following deprecation warning is triggered by importing\n        # `gunicorn.util`. Hopefully, it'll get fixed in the future. See\n        # https://github.com/benoitc/gunicorn/issues/2840 for detail.\n        \"-W\", \"ignore:module 'sre_constants' is \"\n        \"deprecated:DeprecationWarning:pkg_resources._vendor.pyparsing\",\n        # Also caused by `gunicorn.util` importing `pkg_resources`:\n        \"-W\", \"ignore:Creating a LegacyVersion has been deprecated and \"\n        \"will be removed in the next major release:\"\n        \"DeprecationWarning:\",\n        # Deprecation warning emitted by setuptools v67.5.0+ triggered by importing\n        # `gunicorn.util`.\n        \"-W\", \"ignore:pkg_resources is deprecated as an API:\"\n        \"DeprecationWarning\",\n        \"-c\", f\"import {import_path!s}\",\n        # fmt: on\n    )\n\n    subprocess.check_call(imp_cmd)\n"
  },
  {
    "path": "tests/test_classbasedview.py",
    "content": "from unittest import mock\n\nimport pytest\n\nfrom aiohttp import web\nfrom aiohttp.web_urldispatcher import View\n\n\ndef test_ctor() -> None:\n    request = mock.Mock()\n    view = View(request)\n    assert view.request is request\n\n\nasync def test_render_ok() -> None:\n    resp = web.Response(text=\"OK\")\n\n    class MyView(View):\n        async def get(self) -> web.StreamResponse:\n            return resp\n\n    request = mock.Mock()\n    request.method = \"GET\"\n    resp2 = await MyView(request)\n    assert resp is resp2\n\n\nasync def test_render_unknown_method() -> None:\n    class MyView(View):\n        async def get(self) -> web.StreamResponse:\n            assert False\n\n        options = get\n\n    request = mock.Mock()\n    request.method = \"UNKNOWN\"\n    with pytest.raises(web.HTTPMethodNotAllowed) as ctx:\n        await MyView(request)\n    assert ctx.value.headers[\"allow\"] == \"GET,OPTIONS\"\n    assert ctx.value.status == 405\n\n\nasync def test_render_unsupported_method() -> None:\n    class MyView(View):\n        async def get(self) -> web.StreamResponse:\n            assert False\n\n        options = delete = get\n\n    request = mock.Mock()\n    request.method = \"POST\"\n    with pytest.raises(web.HTTPMethodNotAllowed) as ctx:\n        await MyView(request)\n    assert ctx.value.headers[\"allow\"] == \"DELETE,GET,OPTIONS\"\n    assert ctx.value.status == 405\n"
  },
  {
    "path": "tests/test_client_connection.py",
    "content": "import asyncio\nimport gc\nfrom typing import Any\nfrom unittest import mock\n\nimport pytest\n\nfrom aiohttp.client_proto import ResponseHandler\nfrom aiohttp.client_reqrep import ConnectionKey\nfrom aiohttp.connector import BaseConnector, Connection\n\n\n@pytest.fixture\ndef key() -> object:\n    return object()\n\n\n@pytest.fixture\ndef loop() -> Any:\n    return mock.create_autospec(asyncio.AbstractEventLoop, spec_set=True, instance=True)\n\n\n@pytest.fixture\ndef connector() -> mock.Mock:\n    return mock.Mock()\n\n\n@pytest.fixture\ndef protocol() -> mock.Mock:\n    return mock.Mock(should_close=False)\n\n\ndef test_ctor(\n    connector: BaseConnector,\n    key: ConnectionKey,\n    protocol: ResponseHandler,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    conn = Connection(connector, key, protocol, loop)\n    assert conn.protocol is protocol\n    conn.close()\n\n\ndef test_callbacks_on_close(\n    connector: BaseConnector,\n    key: ConnectionKey,\n    protocol: ResponseHandler,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    conn = Connection(connector, key, protocol, loop)\n    notified = False\n\n    def cb() -> None:\n        nonlocal notified\n        notified = True\n\n    conn.add_callback(cb)\n    conn.close()\n    assert notified\n\n\ndef test_callbacks_on_release(\n    connector: BaseConnector,\n    key: ConnectionKey,\n    protocol: ResponseHandler,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    conn = Connection(connector, key, protocol, loop)\n    notified = False\n\n    def cb() -> None:\n        nonlocal notified\n        notified = True\n\n    conn.add_callback(cb)\n    conn.release()\n    assert notified\n\n\ndef test_callbacks_exception(\n    connector: BaseConnector,\n    key: ConnectionKey,\n    protocol: ResponseHandler,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    conn = Connection(connector, key, protocol, loop)\n    notified = False\n\n    def cb1() -> None:\n        raise Exception\n\n    def cb2() -> None:\n        nonlocal notified\n        notified = True\n\n    conn.add_callback(cb1)\n    conn.add_callback(cb2)\n    conn.close()\n    assert notified\n\n\ndef test_del(\n    connector: BaseConnector,\n    key: ConnectionKey,\n    protocol: ResponseHandler,\n    loop: mock.Mock,\n) -> None:\n    loop.is_closed.return_value = False\n    conn = Connection(connector, key, protocol, loop)\n    exc_handler = mock.Mock()\n    loop.set_exception_handler(exc_handler)\n\n    with pytest.warns(ResourceWarning):\n        del conn\n        gc.collect()\n\n    connector._release.assert_called_with(key, protocol, should_close=True)  # type: ignore[attr-defined]\n    msg = {\n        \"client_connection\": mock.ANY,  # conn was deleted\n        \"message\": \"Unclosed connection\",\n    }\n    msg[\"source_traceback\"] = mock.ANY\n    loop.call_exception_handler.assert_called_with(msg)\n\n\ndef test_close(\n    connector: BaseConnector,\n    key: ConnectionKey,\n    protocol: ResponseHandler,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    conn = Connection(connector, key, protocol, loop)\n    assert not conn.closed\n    conn.close()\n    assert conn._protocol is None\n    connector._release.assert_called_with(key, protocol, should_close=True)  # type: ignore[attr-defined]\n    assert conn.closed\n\n\ndef test_release(\n    connector: BaseConnector,\n    key: ConnectionKey,\n    protocol: ResponseHandler,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    conn = Connection(connector, key, protocol, loop)\n    assert not conn.closed\n    conn.release()\n    assert protocol.transport is not None\n    assert not protocol.transport.close.called  # type: ignore[attr-defined]\n    assert conn._protocol is None\n    connector._release.assert_called_with(key, protocol)  # type: ignore[attr-defined]\n    assert conn.closed\n\n\ndef test_release_proto_should_close(\n    connector: BaseConnector,\n    key: ConnectionKey,\n    protocol: ResponseHandler,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    protocol.should_close = True  # type: ignore[misc]\n    conn = Connection(connector, key, protocol, loop)\n    assert not conn.closed\n    conn.release()\n    assert protocol.transport is not None\n    assert not protocol.transport.close.called  # type: ignore[attr-defined]\n    assert conn._protocol is None\n    connector._release.assert_called_with(key, protocol)  # type: ignore[attr-defined]\n    assert conn.closed\n\n\ndef test_release_released(\n    connector: BaseConnector,\n    key: ConnectionKey,\n    protocol: ResponseHandler,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    conn = Connection(connector, key, protocol, loop)\n    conn.release()\n    connector._release.reset_mock()  # type: ignore[attr-defined]\n    conn.release()\n    assert protocol.transport is not None\n    assert not protocol.transport.close.called  # type: ignore[attr-defined]\n    assert conn._protocol is None\n    assert not connector._release.called  # type: ignore[attr-defined]\n"
  },
  {
    "path": "tests/test_client_exceptions.py",
    "content": "import errno\nimport pickle\n\nimport pytest\nfrom multidict import CIMultiDict, CIMultiDictProxy\nfrom yarl import URL\n\nfrom aiohttp import client, client_reqrep\n\n\nclass TestClientResponseError:\n    request_info = client.RequestInfo(\n        url=URL(\"http://example.com\"),\n        method=\"GET\",\n        headers=CIMultiDictProxy(CIMultiDict()),\n        real_url=URL(\"http://example.com\"),\n    )\n\n    def test_default_status(self) -> None:\n        err = client.ClientResponseError(history=(), request_info=self.request_info)\n        assert err.status == 0\n\n    def test_status(self) -> None:\n        err = client.ClientResponseError(\n            status=400, history=(), request_info=self.request_info\n        )\n        assert err.status == 400\n\n    @pytest.mark.xfail(reason=\"CIMultiDictProxy is not pickleable\")\n    def test_pickle(self) -> None:\n        err = client.ClientResponseError(request_info=self.request_info, history=())\n        for proto in range(pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(err, proto)\n            err2 = pickle.loads(pickled)\n            assert err2.request_info == self.request_info\n            assert err2.history == ()\n            assert err2.status == 0\n            assert err2.message == \"\"\n            assert err2.headers is None\n\n        err = client.ClientResponseError(\n            request_info=self.request_info,\n            history=(),\n            status=400,\n            message=\"Something wrong\",\n            headers=CIMultiDict(foo=\"bar\"),\n        )\n        err.foo = \"bar\"  # type: ignore[attr-defined]\n        for proto in range(pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(err, proto)\n            err2 = pickle.loads(pickled)\n            assert err2.request_info == self.request_info\n            assert err2.history == ()\n            assert err2.status == 400\n            assert err2.message == \"Something wrong\"\n            # Use headers.get() to verify static type is correct.\n            assert err2.headers.get(\"foo\") == \"bar\"\n            assert err2.foo == \"bar\"\n\n    def test_repr(self) -> None:\n        err = client.ClientResponseError(request_info=self.request_info, history=())\n        assert repr(err) == (f\"ClientResponseError({self.request_info!r}, ())\")\n\n        err = client.ClientResponseError(\n            request_info=self.request_info,\n            history=(),\n            status=400,\n            message=\"Something wrong\",\n            headers=CIMultiDict(),\n        )\n        assert repr(err) == (\n            \"ClientResponseError(%r, (), status=400, \"\n            \"message='Something wrong', headers=<CIMultiDict()>)\" % (self.request_info,)\n        )\n\n    def test_str(self) -> None:\n        err = client.ClientResponseError(\n            request_info=self.request_info,\n            history=(),\n            status=400,\n            message=\"Something wrong\",\n            headers=CIMultiDict(),\n        )\n        assert str(err) == (\"400, message='Something wrong', url='http://example.com'\")\n\n\nclass TestClientConnectorError:\n    connection_key = client_reqrep.ConnectionKey(\n        host=\"example.com\",\n        port=8080,\n        is_ssl=False,\n        ssl=True,\n        proxy=None,\n        proxy_auth=None,\n        proxy_headers_hash=None,\n    )\n\n    def test_ctor(self) -> None:\n        err = client.ClientConnectorError(\n            connection_key=self.connection_key,\n            os_error=OSError(errno.ENOENT, \"No such file\"),\n        )\n        assert err.errno == errno.ENOENT\n        assert err.strerror == \"No such file\"\n        assert err.os_error.errno == errno.ENOENT\n        assert err.os_error.strerror == \"No such file\"\n        assert err.host == \"example.com\"\n        assert err.port == 8080\n        assert err.ssl is True\n\n    def test_pickle(self) -> None:\n        err = client.ClientConnectorError(\n            connection_key=self.connection_key,\n            os_error=OSError(errno.ENOENT, \"No such file\"),\n        )\n        err.foo = \"bar\"  # type: ignore[attr-defined]\n        for proto in range(pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(err, proto)\n            err2 = pickle.loads(pickled)\n            assert err2.errno == errno.ENOENT\n            assert err2.strerror == \"No such file\"\n            assert err2.os_error.errno == errno.ENOENT\n            assert err2.os_error.strerror == \"No such file\"\n            assert err2.host == \"example.com\"\n            assert err2.port == 8080\n            assert err2.ssl is True\n            assert err2.foo == \"bar\"\n\n    def test_repr(self) -> None:\n        os_error = OSError(errno.ENOENT, \"No such file\")\n        err = client.ClientConnectorError(\n            connection_key=self.connection_key, os_error=os_error\n        )\n        assert repr(err) == (\n            f\"ClientConnectorError({self.connection_key!r}, {os_error!r})\"\n        )\n\n    def test_str(self) -> None:\n        err = client.ClientConnectorError(\n            connection_key=self.connection_key,\n            os_error=OSError(errno.ENOENT, \"No such file\"),\n        )\n        assert str(err) == (\n            \"Cannot connect to host example.com:8080 ssl:default [No such file]\"\n        )\n\n\nclass TestClientConnectorCertificateError:\n    connection_key = client_reqrep.ConnectionKey(\n        host=\"example.com\",\n        port=8080,\n        is_ssl=False,\n        ssl=True,\n        proxy=None,\n        proxy_auth=None,\n        proxy_headers_hash=None,\n    )\n\n    def test_ctor(self) -> None:\n        certificate_error = Exception(\"Bad certificate\")\n        err = client.ClientConnectorCertificateError(\n            connection_key=self.connection_key, certificate_error=certificate_error\n        )\n        assert err.certificate_error == certificate_error\n        assert err.host == \"example.com\"\n        assert err.port == 8080\n        assert err.ssl is False\n\n    def test_pickle(self) -> None:\n        certificate_error = Exception(\"Bad certificate\")\n        err = client.ClientConnectorCertificateError(\n            connection_key=self.connection_key, certificate_error=certificate_error\n        )\n        err.foo = \"bar\"\n        for proto in range(pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(err, proto)\n            err2 = pickle.loads(pickled)\n            assert err2.certificate_error.args == (\"Bad certificate\",)\n            assert err2.host == \"example.com\"\n            assert err2.port == 8080\n            assert err2.ssl is False\n            assert err2.foo == \"bar\"\n\n    def test_repr(self) -> None:\n        certificate_error = Exception(\"Bad certificate\")\n        err = client.ClientConnectorCertificateError(\n            connection_key=self.connection_key, certificate_error=certificate_error\n        )\n        assert repr(err) == (\n            \"ClientConnectorCertificateError(%r, %r)\"\n            % (self.connection_key, certificate_error)\n        )\n\n    def test_str(self) -> None:\n        certificate_error = Exception(\"Bad certificate\")\n        err = client.ClientConnectorCertificateError(\n            connection_key=self.connection_key, certificate_error=certificate_error\n        )\n        assert str(err) == (\n            \"Cannot connect to host example.com:8080 ssl:False\"\n            \" [Exception: ('Bad certificate',)]\"\n        )\n\n    def test_oserror(self) -> None:\n        certificate_error = OSError(1, \"Bad certificate\")\n        err = client.ClientConnectorCertificateError(\n            connection_key=self.connection_key, certificate_error=certificate_error\n        )\n        assert err.os_error == certificate_error\n        assert err.errno == 1\n        assert err.strerror == \"Bad certificate\"\n\n\nclass TestServerDisconnectedError:\n    def test_ctor(self) -> None:\n        err = client.ServerDisconnectedError()\n        assert err.message == \"Server disconnected\"\n\n        err = client.ServerDisconnectedError(message=\"No connection\")\n        assert err.message == \"No connection\"\n\n    def test_pickle(self) -> None:\n        err = client.ServerDisconnectedError(message=\"No connection\")\n        err.foo = \"bar\"  # type: ignore[attr-defined]\n        for proto in range(pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(err, proto)\n            err2 = pickle.loads(pickled)\n            assert err2.message == \"No connection\"\n            assert err2.foo == \"bar\"\n\n    def test_repr(self) -> None:\n        err = client.ServerDisconnectedError()\n        assert repr(err) == (\"ServerDisconnectedError('Server disconnected')\")\n\n        err = client.ServerDisconnectedError(message=\"No connection\")\n        assert repr(err) == \"ServerDisconnectedError('No connection')\"\n\n    def test_str(self) -> None:\n        err = client.ServerDisconnectedError()\n        assert str(err) == \"Server disconnected\"\n\n        err = client.ServerDisconnectedError(message=\"No connection\")\n        assert str(err) == \"No connection\"\n\n\nclass TestServerFingerprintMismatch:\n    def test_ctor(self) -> None:\n        err = client.ServerFingerprintMismatch(\n            expected=b\"exp\", got=b\"got\", host=\"example.com\", port=8080\n        )\n        assert err.expected == b\"exp\"\n        assert err.got == b\"got\"\n        assert err.host == \"example.com\"\n        assert err.port == 8080\n\n    def test_pickle(self) -> None:\n        err = client.ServerFingerprintMismatch(\n            expected=b\"exp\", got=b\"got\", host=\"example.com\", port=8080\n        )\n        err.foo = \"bar\"  # type: ignore[attr-defined]\n        for proto in range(pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(err, proto)\n            err2 = pickle.loads(pickled)\n            assert err2.expected == b\"exp\"\n            assert err2.got == b\"got\"\n            assert err2.host == \"example.com\"\n            assert err2.port == 8080\n            assert err2.foo == \"bar\"\n\n    def test_repr(self) -> None:\n        err = client.ServerFingerprintMismatch(b\"exp\", b\"got\", \"example.com\", 8080)\n        assert repr(err) == (\n            \"<ServerFingerprintMismatch expected=b'exp' \"\n            \"got=b'got' host='example.com' port=8080>\"\n        )\n\n\nclass TestInvalidURL:\n    def test_ctor(self) -> None:\n        err = client.InvalidURL(url=\":wrong:url:\", description=\":description:\")\n        assert err.url == \":wrong:url:\"\n        assert err.description == \":description:\"\n\n    def test_pickle(self) -> None:\n        err = client.InvalidURL(url=\":wrong:url:\")\n        err.foo = \"bar\"  # type: ignore[attr-defined]\n        for proto in range(pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(err, proto)\n            err2 = pickle.loads(pickled)\n            assert err2.url == \":wrong:url:\"\n            assert err2.foo == \"bar\"\n\n    def test_repr_no_description(self) -> None:\n        err = client.InvalidURL(url=\":wrong:url:\")\n        assert err.args == (\":wrong:url:\",)\n        assert repr(err) == \"<InvalidURL :wrong:url:>\"\n\n    def test_repr_yarl_URL(self) -> None:\n        err = client.InvalidURL(url=URL(\":wrong:url:\"))\n        assert repr(err) == \"<InvalidURL :wrong:url:>\"\n\n    def test_repr_with_description(self) -> None:\n        err = client.InvalidURL(url=\":wrong:url:\", description=\":description:\")\n        assert repr(err) == \"<InvalidURL :wrong:url: - :description:>\"\n\n    def test_str_no_description(self) -> None:\n        err = client.InvalidURL(url=\":wrong:url:\")\n        assert str(err) == \":wrong:url:\"\n\n    def test_none_description(self) -> None:\n        err = client.InvalidURL(\":wrong:url:\")\n        assert err.description is None\n\n    def test_str_with_description(self) -> None:\n        err = client.InvalidURL(url=\":wrong:url:\", description=\":description:\")\n        assert str(err) == \":wrong:url: - :description:\"\n"
  },
  {
    "path": "tests/test_client_fingerprint.py",
    "content": "import hashlib\nfrom unittest import mock\n\nimport pytest\n\nimport aiohttp\n\nssl = pytest.importorskip(\"ssl\")\n\n\ndef test_fingerprint_sha256() -> None:\n    sha256 = hashlib.sha256(b\"12345678\" * 64).digest()\n    fp = aiohttp.Fingerprint(sha256)\n    assert fp.fingerprint == sha256\n\n\ndef test_fingerprint_sha1() -> None:\n    sha1 = hashlib.sha1(b\"12345678\" * 64).digest()\n    with pytest.raises(ValueError):\n        aiohttp.Fingerprint(sha1)\n\n\ndef test_fingerprint_md5() -> None:\n    md5 = hashlib.md5(b\"12345678\" * 64).digest()\n    with pytest.raises(ValueError):\n        aiohttp.Fingerprint(md5)\n\n\ndef test_fingerprint_check_no_ssl() -> None:\n    sha256 = hashlib.sha256(b\"12345678\" * 64).digest()\n    fp = aiohttp.Fingerprint(sha256)\n    transport = mock.Mock()\n    transport.get_extra_info.return_value = None\n    fp.check(transport)\n"
  },
  {
    "path": "tests/test_client_functional.py",
    "content": "# HTTP client functional tests against aiohttp.web server\n\nimport asyncio\nimport datetime\nimport http.cookies\nimport io\nimport json\nimport logging\nimport pathlib\nimport socket\nimport ssl\nimport sys\nimport tarfile\nimport time\nimport zipfile\nimport zlib\nfrom collections.abc import AsyncIterator, Awaitable, Callable\nfrom contextlib import suppress\nfrom typing import Any, NoReturn\nfrom unittest import mock\n\ntry:\n    try:\n        import brotlicffi as brotli\n    except ImportError:\n        import brotli\nexcept ImportError:  # pragma: no cover\n    brotli = None\n\ntry:\n    from backports.zstd import ZstdCompressor\nexcept ImportError:\n    ZstdCompressor = None  # type: ignore[assignment,misc]  # pragma: no cover\n\nimport pytest\nimport trustme\nfrom multidict import MultiDict\nfrom pytest_mock import MockerFixture\nfrom yarl import URL, Query\n\nimport aiohttp\nfrom aiohttp import Fingerprint, ServerFingerprintMismatch, hdrs, payload, web\nfrom aiohttp.abc import AbstractResolver, ResolveResult\nfrom aiohttp.client_exceptions import (\n    ClientResponseError,\n    InvalidURL,\n    InvalidUrlClientError,\n    InvalidUrlRedirectClientError,\n    NonHttpUrlClientError,\n    NonHttpUrlRedirectClientError,\n    SocketTimeoutError,\n    TooManyRedirects,\n)\nfrom aiohttp.client_reqrep import ClientRequest\nfrom aiohttp.compression_utils import DEFAULT_MAX_DECOMPRESS_SIZE\nfrom aiohttp.http_exceptions import DecompressSizeError\nfrom aiohttp.payload import (\n    AsyncIterablePayload,\n    BufferedReaderPayload,\n    BytesIOPayload,\n    BytesPayload,\n    StringIOPayload,\n    StringPayload,\n)\nfrom aiohttp.pytest_plugin import AiohttpClient, AiohttpServer\nfrom aiohttp.test_utils import TestClient, TestServer, unused_port\nfrom aiohttp.typedefs import Handler\n\n\n@pytest.fixture(autouse=True)\ndef cleanup(\n    cleanup_payload_pending_file_closes: None,\n) -> None:\n    \"\"\"Ensure all pending file close operations complete during test teardown.\"\"\"\n\n\n@pytest.fixture\ndef here() -> pathlib.Path:\n    return pathlib.Path(__file__).parent\n\n\n@pytest.fixture\ndef fname(here: pathlib.Path) -> pathlib.Path:\n    return here / \"conftest.py\"\n\n\n@pytest.fixture\ndef headers_echo_client(\n    aiohttp_client: AiohttpClient,\n) -> Callable[..., Awaitable[TestClient[web.Request, web.Application]]]:\n    \"\"\"Create a client with an app that echoes request headers as JSON.\"\"\"\n\n    async def factory(**kwargs: Any) -> TestClient[web.Request, web.Application]:\n        async def handler(request: web.Request) -> web.Response:\n            return web.json_response({\"headers\": dict(request.headers)})\n\n        app = web.Application()\n        app.router.add_get(\"/\", handler)\n        return await aiohttp_client(app, **kwargs)\n\n    return factory\n\n\nasync def test_keepalive_two_requests_success(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.read()\n        assert b\"\" == body\n        return web.Response(body=b\"OK\")\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    connector = aiohttp.TCPConnector(limit=1)\n    client = await aiohttp_client(app, connector=connector)\n\n    async with client.get(\"/\") as resp1:\n        await resp1.read()\n    async with client.get(\"/\") as resp2:\n        await resp2.read()\n\n    assert client._session.connector is not None\n    assert 1 == len(client._session.connector._conns)\n\n\nasync def test_keepalive_after_head_requests_success(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.read()\n        assert b\"\" == body\n        return web.Response(body=b\"OK\")\n\n    cnt_conn_reuse = 0\n\n    async def on_reuseconn(session: object, ctx: object, params: object) -> None:\n        nonlocal cnt_conn_reuse\n        cnt_conn_reuse += 1\n\n    trace_config = aiohttp.TraceConfig()\n    trace_config._on_connection_reuseconn.append(on_reuseconn)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    app.router.add_route(\"HEAD\", \"/\", handler)\n\n    connector = aiohttp.TCPConnector(limit=1)\n    client = await aiohttp_client(\n        app, connector=connector, trace_configs=[trace_config]\n    )\n\n    async with client.head(\"/\") as resp1:\n        await resp1.read()\n    async with client.get(\"/\") as resp2:\n        await resp2.read()\n\n    assert 1 == cnt_conn_reuse\n\n\n@pytest.mark.parametrize(\"status\", (101, 204, 304))\nasync def test_keepalive_after_empty_body_status(\n    aiohttp_client: AiohttpClient, status: int\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.read()\n        assert b\"\" == body\n        return web.Response(status=status)\n\n    cnt_conn_reuse = 0\n\n    async def on_reuseconn(session: object, ctx: object, params: object) -> None:\n        nonlocal cnt_conn_reuse\n        cnt_conn_reuse += 1\n\n    trace_config = aiohttp.TraceConfig()\n    trace_config._on_connection_reuseconn.append(on_reuseconn)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    connector = aiohttp.TCPConnector(limit=1)\n    client = await aiohttp_client(\n        app, connector=connector, trace_configs=[trace_config]\n    )\n\n    async with client.get(\"/\") as resp1:\n        await resp1.read()\n    async with client.get(\"/\") as resp2:\n        await resp2.read()\n\n    assert cnt_conn_reuse == 1\n\n\n@pytest.mark.parametrize(\"status\", (101, 204, 304))\nasync def test_keepalive_after_empty_body_status_stream_response(\n    aiohttp_client: AiohttpClient, status: int\n) -> None:\n    async def handler(request: web.Request) -> web.StreamResponse:\n        stream_response = web.StreamResponse(status=status)\n        await stream_response.prepare(request)\n        return stream_response\n\n    cnt_conn_reuse = 0\n\n    async def on_reuseconn(session: object, ctx: object, params: object) -> None:\n        nonlocal cnt_conn_reuse\n        cnt_conn_reuse += 1\n\n    trace_config = aiohttp.TraceConfig()\n    trace_config._on_connection_reuseconn.append(on_reuseconn)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    connector = aiohttp.TCPConnector(limit=1)\n    client = await aiohttp_client(\n        app, connector=connector, trace_configs=[trace_config]\n    )\n\n    async with client.get(\"/\") as resp1:\n        await resp1.read()\n    async with client.get(\"/\") as resp2:\n        await resp2.read()\n\n    assert cnt_conn_reuse == 1\n\n\nasync def test_keepalive_response_released(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.read()\n        assert b\"\" == body\n        return web.Response(body=b\"OK\")\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    connector = aiohttp.TCPConnector(limit=1)\n    client = await aiohttp_client(app, connector=connector)\n\n    resp1 = await client.get(\"/\")\n    resp1.release()\n    resp2 = await client.get(\"/\")\n    resp2.release()\n\n    assert client._session.connector is not None\n    assert 1 == len(client._session.connector._conns)\n\n\nasync def test_upgrade_connection_not_released_after_read(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.read()\n        assert b\"\" == body\n        return web.Response(\n            status=101, headers={\"Connection\": \"Upgrade\", \"Upgrade\": \"tcp\"}\n        )\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        await resp.read()\n        assert resp.connection is not None\n        assert not resp.closed\n\n\nasync def test_keepalive_server_force_close_connection(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.read()\n        assert b\"\" == body\n        response = web.Response(body=b\"OK\")\n        response.force_close()\n        return response\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    connector = aiohttp.TCPConnector(limit=1)\n    client = await aiohttp_client(app, connector=connector)\n\n    resp1 = await client.get(\"/\")\n    resp1.close()\n    resp2 = await client.get(\"/\")\n    resp2.close()\n\n    assert client._session.connector is not None\n    assert 0 == len(client._session.connector._conns)\n\n\nasync def test_keepalive_timeout_async_sleep(unused_port_socket: socket.socket) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.read()\n        assert b\"\" == body\n        return web.Response(body=b\"OK\")\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    runner = web.AppRunner(app, tcp_keepalive=True, keepalive_timeout=0.001)\n    await runner.setup()\n\n    site = web.SockSite(runner, unused_port_socket)\n    await site.start()\n\n    host, port = unused_port_socket.getsockname()[:2]\n\n    try:\n        async with aiohttp.ClientSession() as sess:\n            resp1 = await sess.get(f\"http://{host}:{port}/\")\n            await resp1.read()\n            # wait for server keepalive_timeout\n            await asyncio.sleep(0.01)\n            resp2 = await sess.get(f\"http://{host}:{port}/\")\n            await resp2.read()\n    finally:\n        await asyncio.gather(runner.shutdown(), site.stop())\n\n\n@pytest.mark.skipif(\n    sys.version_info[:2] == (3, 11),\n    reason=\"https://github.com/pytest-dev/pytest/issues/10763\",\n)\nasync def test_keepalive_timeout_sync_sleep(unused_port_socket: socket.socket) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.read()\n        assert b\"\" == body\n        return web.Response(body=b\"OK\")\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    runner = web.AppRunner(app, tcp_keepalive=True, keepalive_timeout=0.001)\n    await runner.setup()\n\n    site = web.SockSite(runner, unused_port_socket)\n    await site.start()\n\n    host, port = unused_port_socket.getsockname()[:2]\n\n    try:\n        async with aiohttp.ClientSession() as sess:\n            resp1 = await sess.get(f\"http://{host}:{port}/\")\n            await resp1.read()\n            # wait for server keepalive_timeout\n            # time.sleep is a more challenging scenario than asyncio.sleep\n            time.sleep(0.01)\n            resp2 = await sess.get(f\"http://{host}:{port}/\")\n            await resp2.read()\n    finally:\n        await asyncio.gather(runner.shutdown(), site.stop())\n\n\nasync def test_release_early(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        await request.read()\n        return web.Response(body=b\"OK\")\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/\")\n    assert resp.closed\n    await resp.wait_for_close()\n    assert client._session.connector is not None\n    assert 1 == len(client._session.connector._conns)\n\n\nasync def test_HTTP_304(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.read()\n        assert b\"\" == body\n        return web.Response(status=304)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 304\n        content = await resp.read()\n    assert content == b\"\"\n\n\nasync def test_stream_request_on_server_eof(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"OK\", status=200)\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n    app.add_routes([web.put(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async def data_gen() -> AsyncIterator[bytes]:\n        for _ in range(2):  # pragma: no branch\n            yield b\"just data\"\n            await asyncio.sleep(0.1)\n\n    assert client.session.connector is not None\n    async with client.put(\"/\", data=data_gen()) as resp:\n        assert 200 == resp.status\n        assert len(client.session.connector._acquired) == 1\n        conn = next(iter(client.session.connector._acquired))\n\n    async with client.get(\"/\") as resp:\n        assert 200 == resp.status\n\n    # First connection should have been closed, otherwise server won't know if it\n    # received the full message.\n    conns = next(iter(client.session.connector._conns.values()))\n    assert len(conns) == 1\n    assert conns[0][0] is not conn\n\n\nasync def test_stream_request_on_server_eof_nested(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"OK\", status=200)\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n    app.add_routes([web.put(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async def data_gen() -> AsyncIterator[bytes]:\n        for _ in range(2):  # pragma: no branch\n            yield b\"just data\"\n            await asyncio.sleep(0.1)\n\n    assert client.session.connector is not None\n    async with client.put(\"/\", data=data_gen()) as resp:\n        first_conn = next(iter(client.session.connector._acquired))\n        assert 200 == resp.status\n\n        async with client.get(\"/\") as resp2:\n            assert 200 == resp2.status\n\n    # Should be 2 separate connections\n    conns = next(iter(client.session.connector._conns.values()))\n    assert len(conns) == 1\n\n    assert first_conn is not None\n    assert not first_conn.is_connected()\n    assert first_conn is not conns[0][0]\n\n\nasync def test_HTTP_304_WITH_BODY(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.read()\n        assert b\"\" == body\n        return web.Response(body=b\"test\", status=304)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 304\n        content = await resp.read()\n    assert content == b\"\"\n\n\nasync def test_auto_header_user_agent(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert \"aiohttp\" in request.headers[\"user-agent\"]\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert 200 == resp.status\n\n\nasync def test_skip_auto_headers_user_agent(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert hdrs.USER_AGENT not in request.headers\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\", skip_auto_headers=[\"user-agent\"]) as resp:\n        assert 200 == resp.status\n\n\nasync def test_skip_default_auto_headers_user_agent(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert hdrs.USER_AGENT not in request.headers\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app, skip_auto_headers=[\"user-agent\"])\n\n    async with client.get(\"/\") as resp:\n        assert 200 == resp.status\n\n\nasync def test_skip_auto_headers_content_type(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert hdrs.CONTENT_TYPE not in request.headers\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\", skip_auto_headers=[\"content-type\"]) as resp:\n        assert 200 == resp.status\n\n\nasync def test_post_data_bytesio(aiohttp_client: AiohttpClient) -> None:\n    data = b\"some buffer\"\n\n    async def handler(request: web.Request) -> web.Response:\n        assert len(data) == request.content_length\n        val = await request.read()\n        assert data == val\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"POST\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    with io.BytesIO(data) as file_handle:\n        async with client.post(\"/\", data=file_handle) as resp:\n            assert 200 == resp.status\n\n\nasync def test_post_data_with_bytesio_file(aiohttp_client: AiohttpClient) -> None:\n    data = b\"some buffer\"\n\n    async def handler(request: web.Request) -> web.Response:\n        post_data = await request.post()\n        assert [\"file\"] == list(post_data.keys())\n        file_field = post_data[\"file\"]\n        assert isinstance(file_field, web.FileField)\n        assert data == await asyncio.to_thread(file_field.file.read)\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"POST\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    with io.BytesIO(data) as file_handle:\n        async with client.post(\"/\", data={\"file\": file_handle}) as resp:\n            assert 200 == resp.status\n\n\nasync def test_post_data_stringio(aiohttp_client: AiohttpClient) -> None:\n    data = \"some buffer\"\n\n    async def handler(request: web.Request) -> web.Response:\n        assert len(data) == request.content_length\n        assert request.headers[\"CONTENT-TYPE\"] == \"text/plain; charset=utf-8\"\n        val = await request.text()\n        assert data == val\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"POST\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.post(\"/\", data=io.StringIO(data)) as resp:\n        assert 200 == resp.status\n\n\nasync def test_post_data_textio_encoding(aiohttp_client: AiohttpClient) -> None:\n    data = \"текст\"\n\n    async def handler(request: web.Request) -> web.Response:\n        assert request.headers[\"CONTENT-TYPE\"] == \"text/plain; charset=koi8-r\"\n        val = await request.text()\n        assert data == val\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"POST\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    pl = aiohttp.TextIOPayload(io.StringIO(data), encoding=\"koi8-r\")\n    async with client.post(\"/\", data=pl) as resp:\n        assert 200 == resp.status\n\n\nasync def test_post_data_zipfile_filelike(aiohttp_client: AiohttpClient) -> None:\n    data = b\"This is a zip file payload text file.\"\n\n    async def handler(request: web.Request) -> web.Response:\n        val = await request.read()\n        assert data == val, \"Transmitted zipfile member failed to match original data.\"\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"POST\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    buf = io.BytesIO()\n    with zipfile.ZipFile(file=buf, mode=\"w\") as zf:\n        with zf.open(\"payload1.txt\", mode=\"w\") as zip_filelike_writing:\n            zip_filelike_writing.write(data)\n\n    buf.seek(0)\n    zf = zipfile.ZipFile(file=buf, mode=\"r\")\n    async with client.post(\"/\", data=zf.open(\"payload1.txt\")) as resp:\n        assert resp.status == 200\n\n\nasync def test_post_data_tarfile_filelike(aiohttp_client: AiohttpClient) -> None:\n    data = b\"This is a tar file payload text file.\"\n\n    async def handler(request: web.Request) -> web.Response:\n        val = await request.read()\n        assert data == val, \"Transmitted tarfile member failed to match original data.\"\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"POST\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    buf = io.BytesIO()\n    with tarfile.open(fileobj=buf, mode=\"w\") as tf:\n        ti = tarfile.TarInfo(name=\"payload1.txt\")\n        ti.size = len(data)\n        tf.addfile(tarinfo=ti, fileobj=io.BytesIO(data))\n\n    # Random-access tarfile.\n    buf.seek(0)\n    tf = tarfile.open(fileobj=buf, mode=\"r:\")\n    async with client.post(\"/\", data=tf.extractfile(\"payload1.txt\")) as resp:\n        assert resp.status == 200\n\n    # Streaming tarfile.\n    buf.seek(0)\n    tf = tarfile.open(fileobj=buf, mode=\"r|\")\n    for entry in tf:\n        async with client.post(\"/\", data=tf.extractfile(entry)) as resp:\n            assert resp.status == 200\n\n\nasync def test_post_bytes_data_content_length_from_body(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that Content-Length is set from body payload size when sending bytes.\"\"\"\n    data = b\"test payload data\"\n\n    async def handler(request: web.Request) -> web.Response:\n        # Verify Content-Length header was set correctly\n        assert request.content_length == len(data)\n        assert request.headers.get(\"Content-Length\") == str(len(data))\n\n        # Verify we can read the data\n        val = await request.read()\n        assert data == val\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"POST\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    # Send bytes data - this should trigger the code path where\n    # Content-Length is set from body.size in update_transfer_encoding\n    async with client.post(\"/\", data=data) as resp:\n        assert resp.status == 200\n\n\nasync def test_post_custom_payload_without_content_length(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that Content-Length is set from payload.size when not explicitly provided.\"\"\"\n    data = b\"custom payload data\"\n\n    async def handler(request: web.Request) -> web.Response:\n        # Verify Content-Length header was set from payload size\n        assert request.content_length == len(data)\n        assert request.headers.get(\"Content-Length\") == str(len(data))\n\n        # Verify we can read the data\n        val = await request.read()\n        assert data == val\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"POST\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    # Create a BytesPayload directly - this ensures we test the path\n    # where update_transfer_encoding sets Content-Length from body.size\n    bytes_payload = payload.BytesPayload(data)\n\n    # Don't set Content-Length header explicitly\n    async with client.post(\"/\", data=bytes_payload) as resp:\n        assert resp.status == 200\n\n\nasync def test_ssl_client(\n    aiohttp_server: AiohttpServer,\n    ssl_ctx: ssl.SSLContext,\n    aiohttp_client: AiohttpClient,\n    client_ssl_ctx: ssl.SSLContext,\n) -> None:\n    connector = aiohttp.TCPConnector(ssl=client_ssl_ctx)\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"Test message\")\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = await aiohttp_server(app, ssl=ssl_ctx)\n    client = await aiohttp_client(server, connector=connector)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n        txt = await resp.text()\n    assert txt == \"Test message\"\n\n\n@pytest.mark.skipif(\n    sys.version_info < (3, 11), reason=\"ssl_shutdown_timeout requires Python 3.11+\"\n)\nasync def test_ssl_client_shutdown_timeout(\n    aiohttp_server: AiohttpServer,\n    ssl_ctx: ssl.SSLContext,\n    aiohttp_client: AiohttpClient,\n    client_ssl_ctx: ssl.SSLContext,\n) -> None:\n    # Test that ssl_shutdown_timeout is properly used during connection closure\n\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        connector = aiohttp.TCPConnector(ssl=client_ssl_ctx, ssl_shutdown_timeout=0.1)\n\n    async def streaming_handler(request: web.Request) -> NoReturn:\n        # Create a streaming response that continuously sends data\n        response = web.StreamResponse()\n        await response.prepare(request)\n\n        # Keep sending data until connection is closed\n        while True:\n            await response.write(b\"data chunk\\n\")\n            await asyncio.sleep(0.01)  # Small delay between chunks\n\n        assert False, \"not reached\"\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/stream\", streaming_handler)\n    server = await aiohttp_server(app, ssl=ssl_ctx)\n    client = await aiohttp_client(server, connector=connector)\n\n    # Verify the connector has the correct timeout\n    assert connector._ssl_shutdown_timeout == 0.1\n\n    # Start a streaming request to establish SSL connection with active data transfer\n    resp = await client.get(\"/stream\")\n    assert resp.status == 200\n\n    # Create a background task that continuously reads data\n    async def read_loop() -> None:\n        while True:\n            # Read \"data chunk\\n\"\n            await resp.content.read(11)\n\n    read_task = asyncio.create_task(read_loop())\n    await asyncio.sleep(0)  # Yield control to ensure read_task starts\n\n    # Record the time before closing\n    start_time = time.monotonic()\n\n    # Now close the connector while the stream is still active\n    # This will test the ssl_shutdown_timeout during an active connection\n    await connector.close()\n\n    # Verify the connection was closed within a reasonable time\n    # Should be close to ssl_shutdown_timeout (0.1s) but allow some margin\n    elapsed = time.monotonic() - start_time\n    assert elapsed < 0.3, f\"Connection closure took too long: {elapsed}s\"\n\n    read_task.cancel()\n    with suppress(asyncio.CancelledError):\n        await read_task\n    assert read_task.done(), \"Read task should be cancelled after connection closure\"\n\n\nasync def test_ssl_client_alpn(\n    aiohttp_server: AiohttpServer,\n    aiohttp_client: AiohttpClient,\n    ssl_ctx: ssl.SSLContext,\n) -> None:\n\n    async def handler(request: web.Request) -> web.Response:\n        assert request.transport is not None\n        sslobj = request.transport.get_extra_info(\"ssl_object\")\n        return web.Response(text=sslobj.selected_alpn_protocol())\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    ssl_ctx.set_alpn_protocols((\"http/1.1\",))\n    server = await aiohttp_server(app, ssl=ssl_ctx)\n\n    connector = aiohttp.TCPConnector(ssl=False)\n    client = await aiohttp_client(server, connector=connector)\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n        txt = await resp.text()\n    assert txt == \"http/1.1\"\n\n\nasync def test_tcp_connector_fingerprint_ok(\n    aiohttp_server: AiohttpServer,\n    aiohttp_client: AiohttpClient,\n    ssl_ctx: ssl.SSLContext,\n    tls_certificate_fingerprint_sha256: bytes,\n) -> None:\n    tls_fingerprint = Fingerprint(tls_certificate_fingerprint_sha256)\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"Test message\")\n\n    connector = aiohttp.TCPConnector(ssl=tls_fingerprint)\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = await aiohttp_server(app, ssl=ssl_ctx)\n    client = await aiohttp_client(server, connector=connector)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n\n\nasync def test_tcp_connector_fingerprint_fail(\n    aiohttp_server: AiohttpServer,\n    aiohttp_client: AiohttpClient,\n    ssl_ctx: ssl.SSLContext,\n    tls_certificate_fingerprint_sha256: bytes,\n) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    bad_fingerprint = b\"\\x00\" * len(tls_certificate_fingerprint_sha256)\n\n    connector = aiohttp.TCPConnector(ssl=Fingerprint(bad_fingerprint))\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = await aiohttp_server(app, ssl=ssl_ctx)\n    client = await aiohttp_client(server, connector=connector)\n\n    with pytest.raises(ServerFingerprintMismatch) as cm:\n        await client.get(\"/\")\n    exc = cm.value\n    assert exc.expected == bad_fingerprint\n    assert exc.got == tls_certificate_fingerprint_sha256\n\n\nasync def test_format_task_get(aiohttp_server: AiohttpServer) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"OK\")\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = await aiohttp_server(app)\n    client = aiohttp.ClientSession()\n    task = asyncio.create_task(client.get(server.make_url(\"/\")))\n    assert f\"{task}\".startswith(\"<Task pending\")\n    resp = await task\n    resp.close()\n    await client.close()\n\n\nasync def test_str_params(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert \"q=t est\" in request.rel_url.query_string\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\", params=\"q=t+est\") as resp:\n        assert 200 == resp.status\n\n\nasync def test_params_and_query_string(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test combining params with an existing query_string.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        assert request.rel_url.query_string == \"q=abc&q=test&d=dog\"\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/?q=abc\", params=\"q=test&d=dog\") as resp:\n        assert resp.status == 200\n\n\n@pytest.mark.parametrize(\"params\", [None, \"\", {}, MultiDict()])\nasync def test_empty_params_and_query_string(\n    aiohttp_client: AiohttpClient, params: Query\n) -> None:\n    \"\"\"Test combining empty params with an existing query_string.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        assert request.rel_url.query_string == \"q=abc\"\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/?q=abc\", params=params) as resp:\n        assert resp.status == 200\n\n\nasync def test_drop_params_on_redirect(aiohttp_client: AiohttpClient) -> None:\n    async def handler_redirect(request: web.Request) -> web.Response:\n        return web.Response(status=301, headers={\"Location\": \"/ok?a=redirect\"})\n\n    async def handler_ok(request: web.Request) -> web.Response:\n        assert request.rel_url.query_string == \"a=redirect\"\n        return web.Response(status=200)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/ok\", handler_ok)\n    app.router.add_route(\"GET\", \"/redirect\", handler_redirect)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/redirect\", params={\"a\": \"initial\"}) as resp:\n        assert resp.status == 200\n\n\nasync def test_drop_fragment_on_redirect(aiohttp_client: AiohttpClient) -> None:\n    async def handler_redirect(request: web.Request) -> web.Response:\n        return web.Response(status=301, headers={\"Location\": \"/ok#fragment\"})\n\n    async def handler_ok(request: web.Request) -> web.Response:\n        return web.Response(status=200)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/ok\", handler_ok)\n    app.router.add_route(\"GET\", \"/redirect\", handler_redirect)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/redirect\") as resp:\n        assert resp.status == 200\n        assert resp.url.path == \"/ok\"\n\n\nasync def test_drop_fragment(aiohttp_client: AiohttpClient) -> None:\n    async def handler_ok(request: web.Request) -> web.Response:\n        return web.Response(status=200)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/ok\", handler_ok)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/ok#fragment\") as resp:\n        assert resp.status == 200\n        assert resp.url.path == \"/ok\"\n\n\nasync def test_history(aiohttp_client: AiohttpClient) -> None:\n    async def handler_redirect(request: web.Request) -> web.Response:\n        return web.Response(status=301, headers={\"Location\": \"/ok\"})\n\n    async def handler_ok(request: web.Request) -> web.Response:\n        return web.Response(status=200)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/ok\", handler_ok)\n    app.router.add_route(\"GET\", \"/redirect\", handler_redirect)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/ok\") as resp:\n        assert len(resp.history) == 0\n        assert resp.status == 200\n\n    async with client.get(\"/redirect\") as resp_redirect:\n        assert len(resp_redirect.history) == 1\n        assert resp_redirect.history[0].status == 301\n        assert resp_redirect.status == 200\n\n\nasync def test_keepalive_closed_by_server(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.read()\n        assert b\"\" == body\n        resp = web.Response(body=b\"OK\")\n        resp.force_close()\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    connector = aiohttp.TCPConnector(limit=1)\n    client = await aiohttp_client(app, connector=connector)\n\n    async with client.get(\"/\") as resp1:\n        val1 = await resp1.read()\n    assert val1 == b\"OK\"\n    async with client.get(\"/\") as resp2:\n        val2 = await resp2.read()\n    assert val2 == b\"OK\"\n\n    assert client._session.connector is not None\n    assert 0 == len(client._session.connector._conns)\n\n\nasync def test_wait_for(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"OK\")\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await asyncio.wait_for(client.get(\"/\"), 10)\n    assert resp.status == 200\n    txt = await resp.text()\n    assert txt == \"OK\"\n\n\nasync def test_raw_headers(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n\n        raw_headers = tuple((bytes(h), bytes(v)) for h, v in resp.raw_headers)\n        assert raw_headers == (\n            (b\"Content-Length\", b\"0\"),\n            (b\"Date\", mock.ANY),\n            (b\"Server\", mock.ANY),\n        )\n\n\nasync def test_host_header_first(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert list(request.headers)[0] == hdrs.HOST\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n\n\nasync def test_empty_header_values(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        resp = web.Response()\n        resp.headers[\"X-Empty\"] = \"\"\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n        raw_headers = tuple((bytes(h), bytes(v)) for h, v in resp.raw_headers)\n        assert raw_headers == (\n            (b\"X-Empty\", b\"\"),\n            (b\"Content-Length\", b\"0\"),\n            (b\"Date\", mock.ANY),\n            (b\"Server\", mock.ANY),\n        )\n\n\nasync def test_204_with_gzipped_content_encoding(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse(status=204)\n        resp.content_length = 0\n        resp.content_type = \"application/json\"\n        # resp.enable_compression(web.ContentCoding.gzip)\n        resp.headers[\"Content-Encoding\"] = \"gzip\"\n        await resp.prepare(request)\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"DELETE\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.delete(\"/\") as resp:\n        assert resp.status == 204\n        assert resp.closed\n\n\nasync def test_timeout_on_reading_headers(\n    aiohttp_client: AiohttpClient, mocker: MockerFixture\n) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        await asyncio.sleep(0.1)\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    with pytest.raises(asyncio.TimeoutError):\n        await client.get(\"/\", timeout=aiohttp.ClientTimeout(total=0.01))\n\n\nasync def test_timeout_on_conn_reading_headers(\n    aiohttp_client: AiohttpClient, mocker: MockerFixture\n) -> None:\n    # tests case where user did not set a connection timeout\n\n    async def handler(request: web.Request) -> NoReturn:\n        await asyncio.sleep(0.1)\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    conn = aiohttp.TCPConnector()\n    client = await aiohttp_client(app, connector=conn)\n\n    with pytest.raises(asyncio.TimeoutError):\n        await client.get(\"/\", timeout=aiohttp.ClientTimeout(total=0.01))\n\n\nasync def test_timeout_on_session_read_timeout(\n    aiohttp_client: AiohttpClient, mocker: MockerFixture\n) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        await asyncio.sleep(0.1)\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    conn = aiohttp.TCPConnector()\n    client = await aiohttp_client(\n        app, connector=conn, timeout=aiohttp.ClientTimeout(sock_read=0.01)\n    )\n\n    with pytest.raises(asyncio.TimeoutError):\n        await client.get(\"/\")\n\n\nasync def test_read_timeout_between_chunks(\n    aiohttp_client: AiohttpClient, mocker: MockerFixture\n) -> None:\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = aiohttp.web.StreamResponse()\n        await resp.prepare(request)\n        # write data 4 times, with pauses. Total time 2 seconds.\n        for _ in range(4):\n            await asyncio.sleep(0.5)\n            await resp.write(b\"data\\n\")\n        return resp\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    # A timeout of 0.2 seconds should apply per read.\n    timeout = aiohttp.ClientTimeout(sock_read=1)\n    client = await aiohttp_client(app, timeout=timeout)\n\n    res = b\"\"\n    async with client.get(\"/\") as resp:\n        res += await resp.read()\n\n    assert res == b\"data\\n\" * 4\n\n\nasync def test_read_timeout_on_reading_chunks(\n    aiohttp_client: AiohttpClient, mocker: MockerFixture\n) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        resp = aiohttp.web.StreamResponse()\n        await resp.prepare(request)\n        await resp.write(b\"data\\n\")\n        await asyncio.sleep(1)\n        assert False\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    # A timeout of 0.2 seconds should apply per read.\n    timeout = aiohttp.ClientTimeout(sock_read=0.2)\n    client = await aiohttp_client(app, timeout=timeout)\n\n    async with client.get(\"/\") as resp:\n        assert (await resp.content.read(5)) == b\"data\\n\"\n        with pytest.raises(asyncio.TimeoutError):\n            await resp.content.read()\n\n\nasync def test_read_timeout_on_write(aiohttp_client: AiohttpClient) -> None:\n    async def gen_payload() -> AsyncIterator[bytes]:\n        # Delay writing to ensure read timeout isn't triggered before writing completes.\n        await asyncio.sleep(0.5)\n        yield b\"foo\"\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=await request.read())\n\n    app = web.Application()\n    app.router.add_put(\"/\", handler)\n\n    timeout = aiohttp.ClientTimeout(total=None, sock_read=0.1)\n    client = await aiohttp_client(app)\n    async with client.put(\"/\", data=gen_payload(), timeout=timeout) as resp:\n        result = await resp.read()  # Should not trigger a read timeout.\n    assert result == b\"foo\"\n\n\nasync def test_timeout_on_reading_data(\n    aiohttp_client: AiohttpClient, mocker: MockerFixture\n) -> None:\n    loop = asyncio.get_event_loop()\n\n    fut = loop.create_future()\n\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse(headers={\"content-length\": \"100\"})\n        await resp.prepare(request)\n        fut.set_result(None)\n        await asyncio.sleep(0.2)\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\", timeout=aiohttp.ClientTimeout(1)) as resp:\n        await fut\n\n        with pytest.raises(asyncio.TimeoutError):\n            await resp.read()\n\n\nasync def test_timeout_none(\n    aiohttp_client: AiohttpClient, mocker: MockerFixture\n) -> None:\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse()\n        await resp.prepare(request)\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\", timeout=None) as resp:\n        assert resp.status == 200\n\n\nasync def test_connection_timeout_error(\n    aiohttp_client: AiohttpClient, mocker: MockerFixture\n) -> None:\n    \"\"\"Test that ConnectionTimeoutError is raised when connection times out.\"\"\"\n\n    async def handler(request: web.Request) -> NoReturn:\n        assert False, \"Handler should not be called\"\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    # Mock the connector's connect method to raise asyncio.TimeoutError\n    mock_connect = mocker.patch.object(\n        client.session._connector, \"connect\", side_effect=asyncio.TimeoutError()\n    )\n\n    with pytest.raises(aiohttp.ConnectionTimeoutError) as exc_info:\n        await client.get(\"/\", timeout=aiohttp.ClientTimeout(connect=0.01))\n\n    assert \"Connection timeout to host\" in str(exc_info.value)\n    mock_connect.assert_called_once()\n\n\nasync def test_readline_error_on_conn_close(aiohttp_client: AiohttpClient) -> None:\n    loop = asyncio.get_event_loop()\n\n    async def handler(request: web.Request) -> NoReturn:\n        resp = web.StreamResponse()\n        await resp.prepare(request)\n\n        # make sure connection is closed by client.\n        with pytest.raises(aiohttp.ServerDisconnectedError):\n            for _ in range(10):\n                await resp.write(b\"data\\n\")\n                await asyncio.sleep(0.5)\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = await aiohttp_client(app)\n\n    async with aiohttp.ClientSession() as session:\n        timer_started = False\n        url, headers = server.make_url(\"/\"), {\"Connection\": \"Keep-alive\"}\n        resp = await session.get(url, headers=headers)\n        with pytest.raises(aiohttp.ClientConnectionError):\n            while True:\n                data = await resp.content.readline()\n                data = data.strip()\n                assert data\n                assert data == b\"data\"\n                if not timer_started:\n                    loop.call_later(1.0, resp.release)\n                    timer_started = True\n\n\nasync def test_no_error_on_conn_close_if_eof(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp_ = web.StreamResponse()\n        await resp_.prepare(request)\n        await resp_.write(b\"data\\n\")\n        await asyncio.sleep(0.5)\n        return resp_\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = await aiohttp_client(app)\n\n    async with aiohttp.ClientSession() as session:\n        url, headers = server.make_url(\"/\"), {\"Connection\": \"Keep-alive\"}\n        resp = await session.get(url, headers=headers)\n        while True:\n            data = await resp.content.readline()\n            data = data.strip()\n            if not data:\n                break\n            assert data == b\"data\"\n\n        assert resp.content.exception() is None\n\n\nasync def test_error_not_overwrote_on_conn_close(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp_ = web.StreamResponse()\n        await resp_.prepare(request)\n        return resp_\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = await aiohttp_client(app)\n\n    async with aiohttp.ClientSession() as session:\n        url, headers = server.make_url(\"/\"), {\"Connection\": \"Keep-alive\"}\n        resp = await session.get(url, headers=headers)\n        resp.content.set_exception(ValueError())\n\n    assert isinstance(resp.content.exception(), ValueError)\n\n\nasync def test_HTTP_200_OK_METHOD(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=request.method)\n\n    app = web.Application()\n    for meth in (\"get\", \"post\", \"put\", \"delete\", \"head\", \"patch\", \"options\"):\n        app.router.add_route(meth.upper(), \"/\", handler)\n\n    client = await aiohttp_client(app)\n    for meth in (\"get\", \"post\", \"put\", \"delete\", \"head\", \"patch\", \"options\"):\n        async with client.request(meth, \"/\") as resp:\n            assert resp.status == 200\n            assert len(resp.history) == 0\n\n            content1 = await resp.read()\n            content2 = await resp.read()\n            assert content1 == content2\n            content = await resp.text()\n\n        if meth == \"head\":\n            assert b\"\" == content1\n        else:\n            assert meth.upper() == content\n\n\nasync def test_HTTP_200_OK_METHOD_connector(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=request.method)\n\n    conn = aiohttp.TCPConnector()\n    conn.clear_dns_cache()\n\n    app = web.Application()\n    for meth in (\"get\", \"post\", \"put\", \"delete\", \"head\"):\n        app.router.add_route(meth.upper(), \"/\", handler)\n    client = await aiohttp_client(app, connector=conn)\n\n    for meth in (\"get\", \"post\", \"put\", \"delete\", \"head\"):\n        async with client.request(meth, \"/\") as resp:\n            content1 = await resp.read()\n            content2 = await resp.read()\n            assert content1 == content2\n            content = await resp.text()\n\n            assert resp.status == 200\n\n        if meth == \"head\":\n            assert b\"\" == content1\n        else:\n            assert meth.upper() == content\n\n\nasync def test_HTTP_302_REDIRECT_GET(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=request.method)\n\n    async def redirect(request: web.Request) -> NoReturn:\n        raise web.HTTPFound(location=\"/\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    app.router.add_get(\"/redirect\", redirect)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/redirect\") as resp:\n        assert 200 == resp.status\n        assert 1 == len(resp.history)\n\n\nasync def test_HTTP_302_REDIRECT_HEAD(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=request.method)\n\n    async def redirect(request: web.Request) -> NoReturn:\n        raise web.HTTPFound(location=\"/\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    app.router.add_get(\"/redirect\", redirect)\n    app.router.add_head(\"/\", handler)\n    app.router.add_head(\"/redirect\", redirect)\n    client = await aiohttp_client(app)\n\n    async with client.request(\"head\", \"/redirect\") as resp:\n        assert 200 == resp.status\n        assert 1 == len(resp.history)\n        assert resp.method == \"HEAD\"\n\n\nasync def test_HTTP_302_REDIRECT_NON_HTTP(aiohttp_client: AiohttpClient) -> None:\n    async def redirect(request: web.Request) -> NoReturn:\n        raise web.HTTPFound(location=\"ftp://127.0.0.1/test/\")\n\n    app = web.Application()\n    app.router.add_get(\"/redirect\", redirect)\n    client = await aiohttp_client(app)\n\n    with pytest.raises(NonHttpUrlRedirectClientError):\n        await client.get(\"/redirect\")\n\n\nasync def test_HTTP_302_REDIRECT_POST(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=request.method)\n\n    async def redirect(request: web.Request) -> NoReturn:\n        raise web.HTTPFound(location=\"/\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    app.router.add_post(\"/redirect\", redirect)\n    client = await aiohttp_client(app)\n\n    async with client.post(\"/redirect\") as resp:\n        assert resp.status == 200\n        assert 1 == len(resp.history)\n        txt = await resp.text()\n    assert txt == \"GET\"\n\n\nasync def test_HTTP_302_REDIRECT_POST_with_content_length_hdr(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=request.method)\n\n    async def redirect(request: web.Request) -> NoReturn:\n        await request.read()\n        raise web.HTTPFound(location=\"/\")\n\n    data = json.dumps({\"some\": \"data\"})\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    app.router.add_post(\"/redirect\", redirect)\n    client = await aiohttp_client(app)\n\n    async with client.post(\n        \"/redirect\", data=data, headers={\"Content-Length\": str(len(data))}\n    ) as resp:\n        assert resp.status == 200\n        assert 1 == len(resp.history)\n        txt = await resp.text()\n    assert txt == \"GET\"\n\n\nasync def test_HTTP_307_REDIRECT_POST(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=request.method)\n\n    async def redirect(request: web.Request) -> NoReturn:\n        await request.read()\n        raise web.HTTPTemporaryRedirect(location=\"/\")\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    app.router.add_post(\"/redirect\", redirect)\n    client = await aiohttp_client(app)\n\n    async with client.post(\"/redirect\", data={\"some\": \"data\"}) as resp:\n        assert resp.status == 200\n        assert 1 == len(resp.history)\n        txt = await resp.text()\n    assert txt == \"POST\"\n\n\nasync def test_HTTP_308_PERMANENT_REDIRECT_POST(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=request.method)\n\n    async def redirect(request: web.Request) -> NoReturn:\n        await request.read()\n        raise web.HTTPPermanentRedirect(location=\"/\")\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    app.router.add_post(\"/redirect\", redirect)\n    client = await aiohttp_client(app)\n\n    async with client.post(\"/redirect\", data={\"some\": \"data\"}) as resp:\n        assert resp.status == 200\n        assert 1 == len(resp.history)\n        txt = await resp.text()\n    assert txt == \"POST\"\n\n\nasync def test_HTTP_302_max_redirects(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    async def redirect(request: web.Request) -> NoReturn:\n        count = int(request.match_info[\"count\"])\n        assert count\n        raise web.HTTPFound(location=f\"/redirect/{count - 1}\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    app.router.add_get(r\"/redirect/{count:\\d+}\", redirect)\n    client = await aiohttp_client(app)\n\n    with pytest.raises(TooManyRedirects) as ctx:\n        await client.get(\"/redirect/5\", max_redirects=2)\n    assert 2 == len(ctx.value.history)\n    assert ctx.value.request_info.url.path == \"/redirect/5\"\n    assert ctx.value.request_info.method == \"GET\"\n\n\nasync def test_HTTP_200_GET_WITH_PARAMS(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(\n            text=\"&\".join(k + \"=\" + v for k, v in request.query.items())\n        )\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\", params={\"q\": \"test\"}) as resp:\n        assert resp.status == 200\n        txt = await resp.text()\n    assert txt == \"q=test\"\n\n\nasync def test_HTTP_200_GET_WITH_MultiDict_PARAMS(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(\n            text=\"&\".join(k + \"=\" + v for k, v in request.query.items())\n        )\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    params = MultiDict([(\"q\", \"test\"), (\"q\", \"test2\")])\n    async with client.get(\"/\", params=params) as resp:\n        assert resp.status == 200\n        txt = await resp.text()\n    assert txt == \"q=test&q=test2\"\n\n\nasync def test_HTTP_200_GET_WITH_MIXED_PARAMS(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(\n            text=\"&\".join(k + \"=\" + v for k, v in request.query.items())\n        )\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/?test=true\", params={\"q\": \"test\"}) as resp:\n        assert resp.status == 200\n        txt = await resp.text()\n    assert txt == \"test=true&q=test\"\n\n\nasync def test_POST_DATA(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        return web.json_response(dict(data))\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.post(\"/\", data={\"some\": \"data\"}) as resp:\n        assert resp.status == 200\n        content = await resp.json()\n    assert content == {\"some\": \"data\"}\n\n\nasync def test_POST_DATA_with_explicit_formdata(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        return web.json_response(dict(data))\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    form = aiohttp.FormData()\n    form.add_field(\"name\", \"text\")\n\n    async with client.post(\"/\", data=form) as resp:\n        assert resp.status == 200\n        content = await resp.json()\n    assert content == {\"name\": \"text\"}\n\n\nasync def test_POST_DATA_with_charset(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        mp = await request.multipart()\n        part = await mp.next()\n        assert isinstance(part, aiohttp.BodyPartReader)\n        text = await part.text()\n        return web.Response(text=text)\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    form = aiohttp.FormData()\n    form.add_field(\"name\", \"текст\", content_type=\"text/plain; charset=koi8-r\")\n\n    async with client.post(\"/\", data=form) as resp:\n        assert resp.status == 200\n        content = await resp.text()\n    assert content == \"текст\"\n\n\nasync def test_POST_DATA_formdats_with_charset(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        mp = await request.post()\n        assert \"name\" in mp\n        assert isinstance(mp[\"name\"], str)\n        return web.Response(text=mp[\"name\"])\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    form = aiohttp.FormData(charset=\"koi8-r\")\n    form.add_field(\"name\", \"текст\")\n\n    async with client.post(\"/\", data=form) as resp:\n        assert resp.status == 200\n        content = await resp.text()\n    assert content == \"текст\"\n\n\nasync def test_POST_DATA_with_charset_post(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        assert isinstance(data[\"name\"], str)\n        return web.Response(text=data[\"name\"])\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    form = aiohttp.FormData()\n    form.add_field(\"name\", \"текст\", content_type=\"text/plain; charset=koi8-r\")\n\n    async with client.post(\"/\", data=form) as resp:\n        assert resp.status == 200\n        content = await resp.text()\n    assert content == \"текст\"\n\n\nasync def test_POST_MultiDict(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        assert data == MultiDict([(\"q\", \"test1\"), (\"q\", \"test2\")])\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.post(\n        \"/\", data=MultiDict([(\"q\", \"test1\"), (\"q\", \"test2\")])\n    ) as resp:\n        assert 200 == resp.status\n\n\nasync def test_GET_DEFLATE(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.json_response({\"ok\": True})\n\n    with mock.patch.object(\n        ClientRequest, \"_write_bytes\", autospec=True, spec_set=True\n    ) as m:\n        app = web.Application()\n        app.router.add_get(\"/\", handler)\n        client = await aiohttp_client(app)\n\n        async with client.get(\"/\", data=b\"\", compress=True) as resp:\n            assert resp.status == 200\n            content = await resp.json()\n            assert content == {\"ok\": True}\n\n    # With an empty body, _write_bytes() should not be called at all.\n    m.assert_not_called()\n\n\nasync def test_GET_DEFLATE_no_body(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.json_response({\"ok\": True})\n\n    with mock.patch.object(ClientRequest, \"_write_bytes\") as mock_write_bytes:\n        app = web.Application()\n        app.router.add_get(\"/\", handler)\n        client = await aiohttp_client(app)\n\n        async with client.get(\"/\", data=None, compress=True) as resp:\n            assert resp.status == 200\n            content = await resp.json()\n            assert content == {\"ok\": True}\n\n    # No chunks should have been sent for an empty body.\n    mock_write_bytes.assert_not_called()\n\n\nasync def test_POST_DATA_DEFLATE(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        return web.json_response(dict(data))\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    # True is not a valid type, but still tested for backwards compatibility.\n    async with client.post(\"/\", data={\"some\": \"data\"}, compress=True) as resp:\n        assert resp.status == 200\n        content = await resp.json()\n    assert content == {\"some\": \"data\"}\n\n\nasync def test_POST_FILES(aiohttp_client: AiohttpClient, fname: pathlib.Path) -> None:\n    content1 = fname.read_bytes()\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        assert isinstance(data[\"some\"], web.FileField)\n        assert data[\"some\"].filename == fname.name\n        content2 = await asyncio.to_thread(data[\"some\"].file.read)\n        assert content2 == content1\n        assert isinstance(data[\"test\"], web.FileField)\n        assert await asyncio.to_thread(data[\"test\"].file.read) == b\"data\"\n        assert isinstance(data[\"some\"], web.FileField)\n        data[\"some\"].file.close()\n        data[\"test\"].file.close()\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with fname.open(\"rb\") as f:\n        async with client.post(\n            \"/\", data={\"some\": f, \"test\": io.BytesIO(b\"data\")}, chunked=True\n        ) as resp:\n            assert 200 == resp.status\n\n\nasync def test_POST_FILES_DEFLATE(\n    aiohttp_client: AiohttpClient, fname: pathlib.Path\n) -> None:\n    content1 = fname.read_bytes()\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        assert isinstance(data[\"some\"], web.FileField)\n        assert data[\"some\"].filename == fname.name\n        content2 = await asyncio.to_thread(data[\"some\"].file.read)\n        data[\"some\"].file.close()\n        assert content2 == content1\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with fname.open(\"rb\") as f:\n        async with client.post(\n            \"/\", data={\"some\": f}, chunked=True, compress=\"deflate\"\n        ) as resp:\n            assert 200 == resp.status\n\n\nasync def test_POST_bytes(aiohttp_client: AiohttpClient) -> None:\n    body = b\"0\" * 12345\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        assert body == data\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.post(\"/\", data=body) as resp:\n        assert 200 == resp.status\n\n\nasync def test_POST_bytes_too_large(aiohttp_client: AiohttpClient) -> None:\n    body = b\"0\" * (2**20 + 1)\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.content.read()\n        assert body == data\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with pytest.warns(ResourceWarning):\n        async with client.post(\"/\", data=body) as resp:\n            assert resp.status == 200\n\n\nasync def test_POST_FILES_STR(\n    aiohttp_client: AiohttpClient, fname: pathlib.Path\n) -> None:\n    content1 = fname.read_bytes().decode()\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        content2 = data[\"some\"]\n        assert content2 == content1\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with fname.open(\"rb\") as f:\n        async with client.post(\"/\", data={\"some\": f.read().decode()}) as resp:\n            assert 200 == resp.status\n\n\nasync def test_POST_FILES_STR_SIMPLE(\n    aiohttp_client: AiohttpClient, fname: pathlib.Path\n) -> None:\n    content = fname.read_bytes()\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        assert data == content\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with fname.open(\"rb\") as f:\n        async with client.post(\"/\", data=f.read()) as resp:\n            assert 200 == resp.status\n\n\nasync def test_POST_FILES_LIST(\n    aiohttp_client: AiohttpClient, fname: pathlib.Path\n) -> None:\n    content = fname.read_bytes()\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        assert isinstance(data[\"some\"], web.FileField)\n        assert fname.name == data[\"some\"].filename\n        assert await asyncio.to_thread(data[\"some\"].file.read) == content\n        data[\"some\"].file.close()\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with fname.open(\"rb\") as f:\n        async with client.post(\"/\", data=[(\"some\", f)]) as resp:\n            assert 200 == resp.status\n\n\nasync def test_POST_FILES_CT(\n    aiohttp_client: AiohttpClient, fname: pathlib.Path\n) -> None:\n    content = fname.read_bytes()\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        assert isinstance(data[\"some\"], web.FileField)\n        assert fname.name == data[\"some\"].filename\n        assert \"text/plain\" == data[\"some\"].content_type\n        assert await asyncio.to_thread(data[\"some\"].file.read) == content\n        data[\"some\"].file.close()\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with fname.open(\"rb\") as f:\n        form = aiohttp.FormData()\n        form.add_field(\"some\", f, content_type=\"text/plain\")\n        async with client.post(\"/\", data=form) as resp:\n            assert 200 == resp.status\n\n\nasync def test_POST_FILES_SINGLE(\n    aiohttp_client: AiohttpClient, fname: pathlib.Path\n) -> None:\n    content = fname.read_bytes().decode()\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.text()\n        assert data == content\n        # if system cannot determine 'text/x-python' MIME type\n        # then use 'application/octet-stream' default\n        assert request.content_type in [\n            \"text/plain\",\n            \"application/octet-stream\",\n            \"text/x-python\",\n        ]\n        assert \"content-disposition\" not in request.headers\n\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with fname.open(\"rb\") as f:\n        async with client.post(\"/\", data=f) as resp:\n            assert 200 == resp.status\n\n\nasync def test_POST_FILES_SINGLE_content_disposition(\n    aiohttp_client: AiohttpClient, fname: pathlib.Path\n) -> None:\n    content = fname.read_bytes().decode()\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.text()\n        assert data == content\n        # if system cannot determine 'application/pgp-keys' MIME type\n        # then use 'application/octet-stream' default\n        assert request.content_type in [\n            \"text/plain\",\n            \"application/octet-stream\",\n            \"text/x-python\",\n        ]\n        assert request.headers[\"content-disposition\"] == (\n            'inline; filename=\"conftest.py\"'\n        )\n\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with fname.open(\"rb\") as f:\n        async with client.post(\n            \"/\", data=aiohttp.get_payload(f, disposition=\"inline\")\n        ) as resp:\n            assert 200 == resp.status\n\n\nasync def test_POST_FILES_SINGLE_BINARY(\n    aiohttp_client: AiohttpClient, fname: pathlib.Path\n) -> None:\n    content = fname.read_bytes()\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        assert data == content\n        # if system cannot determine 'application/pgp-keys' MIME type\n        # then use 'application/octet-stream' default\n        assert request.content_type in [\n            \"application/pgp-keys\",\n            \"text/plain\",\n            \"text/x-python\",\n            \"application/octet-stream\",\n        ]\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with fname.open(\"rb\") as f:\n        async with client.post(\"/\", data=f) as resp:\n            assert 200 == resp.status\n\n\nasync def test_POST_FILES_IO(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        assert isinstance(data[\"unknown\"], web.FileField)\n        assert b\"data\" == await asyncio.to_thread(data[\"unknown\"].file.read)\n        assert data[\"unknown\"].content_type == \"application/octet-stream\"\n        assert data[\"unknown\"].filename == \"unknown\"\n        data[\"unknown\"].file.close()\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with io.BytesIO(b\"data\") as file_handle:\n        async with client.post(\"/\", data=[file_handle]) as resp:\n            assert 200 == resp.status\n\n\nasync def test_POST_FILES_IO_WITH_PARAMS(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        assert data[\"test\"] == \"true\"\n        assert isinstance(data[\"unknown\"], web.FileField)\n        assert data[\"unknown\"].content_type == \"application/octet-stream\"\n        assert data[\"unknown\"].filename == \"unknown\"\n        assert await asyncio.to_thread(data[\"unknown\"].file.read) == b\"data\"\n        data[\"unknown\"].file.close()\n        assert data.getall(\"q\") == [\"t1\", \"t2\"]\n\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with io.BytesIO(b\"data\") as file_handle:\n        async with client.post(\n            \"/\",\n            data=((\"test\", \"true\"), MultiDict([(\"q\", \"t1\"), (\"q\", \"t2\")]), file_handle),\n        ) as resp:\n            assert 200 == resp.status\n\n\nasync def test_POST_FILES_WITH_DATA(\n    aiohttp_client: AiohttpClient, fname: pathlib.Path\n) -> None:\n    content = fname.read_bytes()\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        assert data[\"test\"] == \"true\"\n        assert isinstance(data[\"some\"], web.FileField)\n        assert data[\"some\"].content_type in [\n            \"text/x-python\",\n            \"text/plain\",\n            \"application/octet-stream\",\n        ]\n        assert data[\"some\"].filename == fname.name\n        assert await asyncio.to_thread(data[\"some\"].file.read) == content\n        data[\"some\"].file.close()\n\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with fname.open(\"rb\") as f:\n        async with client.post(\"/\", data={\"test\": \"true\", \"some\": f}) as resp:\n            assert 200 == resp.status\n\n\nasync def test_POST_STREAM_DATA(\n    aiohttp_client: AiohttpClient, fname: pathlib.Path\n) -> None:\n    expected = fname.read_bytes()\n\n    async def handler(request: web.Request) -> web.Response:\n        assert request.content_type == \"application/octet-stream\"\n        content = await request.read()\n        assert request.content_length == len(expected)\n        assert content == expected\n\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    data_size = len(expected)\n\n    async def gen(fname: pathlib.Path) -> AsyncIterator[bytes]:\n        with fname.open(\"rb\") as f:\n            data = await asyncio.to_thread(f.read, 100)\n            while data:\n                yield data\n                data = await asyncio.to_thread(f.read, 100)\n\n    async with client.post(\n        \"/\", data=gen(fname), headers={\"Content-Length\": str(data_size)}\n    ) as resp:\n        assert 200 == resp.status\n\n\nasync def test_json(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert request.content_type == \"application/json\"\n        data = await request.json()\n        return web.Response(body=aiohttp.JsonPayload(data))\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.post(\"/\", json={\"some\": \"data\"}) as resp:\n        assert resp.status == 200\n        content = await resp.json()\n    assert content == {\"some\": \"data\"}\n\n    with pytest.raises(ValueError):\n        await client.post(\"/\", data=\"some data\", json={\"some\": \"data\"})\n\n\nasync def test_json_custom(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert request.content_type == \"application/json\"\n        data = await request.json()\n        return web.Response(body=aiohttp.JsonPayload(data))\n\n    used = False\n\n    def dumps(obj: Any) -> str:\n        nonlocal used\n        used = True\n        return json.dumps(obj)\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app, json_serialize=dumps)\n\n    async with client.post(\"/\", json={\"some\": \"data\"}) as resp:\n        assert resp.status == 200\n        assert used\n        content = await resp.json()\n    assert content == {\"some\": \"data\"}\n\n    with pytest.raises(ValueError):\n        await client.post(\"/\", data=\"some data\", json={\"some\": \"data\"})\n\n\nasync def test_json_serialize_bytes(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test ClientSession.json_serialize_bytes with bytes-returning encoder.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        assert request.content_type == \"application/json\"\n        data = await request.json()\n        return web.Response(body=aiohttp.JsonPayload(data))\n\n    json_bytes_encoder = mock.Mock(side_effect=lambda x: json.dumps(x).encode(\"utf-8\"))\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app, json_serialize_bytes=json_bytes_encoder)\n\n    async with client.post(\"/\", json={\"some\": \"data\"}) as resp:\n        assert resp.status == 200\n        assert json_bytes_encoder.called\n        content = await resp.json()\n    assert content == {\"some\": \"data\"}\n\n\nasync def test_expect_continue(aiohttp_client: AiohttpClient) -> None:\n    expect_called = False\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        assert data == {\"some\": \"data\"}\n        return web.Response()\n\n    async def expect_handler(request: web.Request) -> None:\n        nonlocal expect_called\n        expect = request.headers[hdrs.EXPECT]\n        assert expect.lower() == \"100-continue\"\n        assert request.transport is not None\n        request.transport.write(b\"HTTP/1.1 100 Continue\\r\\n\\r\\n\")\n        expect_called = True\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler, expect_handler=expect_handler)\n    client = await aiohttp_client(app)\n\n    async with client.post(\"/\", data={\"some\": \"data\"}, expect100=True) as resp:\n        assert 200 == resp.status\n    assert expect_called\n\n\nasync def test_expect100_with_no_body(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test expect100 with GET request that has no body.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"OK\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    # GET request with expect100=True but no body\n    async with client.get(\"/\", expect100=True) as resp:\n        assert resp.status == 200\n        assert await resp.text() == \"OK\"\n\n\nasync def test_expect100_continue_with_none_payload(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test expect100 continue handling when payload is None from the start.\"\"\"\n    expect_received = False\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"OK\")\n\n    async def expect_handler(request: web.Request) -> None:\n        nonlocal expect_received\n        expect_received = True\n        # Send 100 Continue\n        assert request.transport is not None\n        request.transport.write(b\"HTTP/1.1 100 Continue\\r\\n\\r\\n\")\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler, expect_handler=expect_handler)\n    client = await aiohttp_client(app)\n\n    # POST request with expect100=True but no body (data=None)\n    async with client.post(\"/\", expect100=True, data=None) as resp:\n        assert resp.status == 200\n        assert await resp.read() == b\"OK\"\n\n    # Expect handler should still be called even with no body\n    assert expect_received\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_encoding_deflate(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        resp = web.Response(text=\"text\")\n        resp.enable_chunked_encoding()\n        resp.enable_compression(web.ContentCoding.deflate)\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n        txt = await resp.text()\n    assert txt == \"text\"\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_encoding_deflate_nochunk(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        resp = web.Response(text=\"text\")\n        resp.enable_compression(web.ContentCoding.deflate)\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n        txt = await resp.text()\n    assert txt == \"text\"\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_encoding_gzip(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        resp = web.Response(text=\"text\")\n        resp.enable_chunked_encoding()\n        resp.enable_compression(web.ContentCoding.gzip)\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n        txt = await resp.text()\n    assert txt == \"text\"\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_encoding_gzip_write_by_chunks(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse()\n        resp.enable_compression(web.ContentCoding.gzip)\n        await resp.prepare(request)\n        await resp.write(b\"0\")\n        await resp.write(b\"0\")\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n        txt = await resp.text()\n    assert txt == \"00\"\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_encoding_gzip_nochunk(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        resp = web.Response(text=\"text\")\n        resp.enable_compression(web.ContentCoding.gzip)\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n        txt = await resp.text()\n    assert txt == \"text\"\n\n\nasync def test_bad_payload_compression(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        resp = web.Response(text=\"text\")\n        resp.headers[\"Content-Encoding\"] = \"gzip\"\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n\n        with pytest.raises(aiohttp.ClientPayloadError):\n            await resp.read()\n\n\nasync def test_payload_decompress_size_limit(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that decompression size limit triggers DecompressSizeError.\n\n    When a compressed payload expands beyond the configured limit,\n    we raise DecompressSizeError.\n    \"\"\"\n    # Create a highly compressible payload that exceeds the decompression limit.\n    # 64MiB of repeated bytes compresses to ~32KB but expands beyond the\n    # 32MiB per-call limit.\n    original = b\"A\" * (64 * 2**20)\n    compressed = zlib.compress(original)\n    assert len(original) > DEFAULT_MAX_DECOMPRESS_SIZE\n\n    async def handler(request: web.Request) -> web.Response:\n        # Send compressed data with Content-Encoding header\n        resp = web.Response(body=compressed)\n        resp.headers[\"Content-Encoding\"] = \"deflate\"\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n\n        with pytest.raises(aiohttp.ClientPayloadError) as exc_info:\n            await resp.read()\n\n        assert isinstance(exc_info.value.__cause__, DecompressSizeError)\n        assert \"Decompressed data exceeds\" in str(exc_info.value.__cause__)\n\n\n@pytest.mark.skipif(brotli is None, reason=\"brotli is not installed\")\nasync def test_payload_decompress_size_limit_brotli(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that brotli decompression size limit triggers DecompressSizeError.\"\"\"\n    assert brotli is not None\n    # Create a highly compressible payload that exceeds the decompression limit.\n    original = b\"A\" * (64 * 2**20)\n    compressed = brotli.compress(original)\n    assert len(original) > DEFAULT_MAX_DECOMPRESS_SIZE\n\n    async def handler(request: web.Request) -> web.Response:\n        resp = web.Response(body=compressed)\n        resp.headers[\"Content-Encoding\"] = \"br\"\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n\n        with pytest.raises(aiohttp.ClientPayloadError) as exc_info:\n            await resp.read()\n\n        assert isinstance(exc_info.value.__cause__, DecompressSizeError)\n        assert \"Decompressed data exceeds\" in str(exc_info.value.__cause__)\n\n\n@pytest.mark.skipif(ZstdCompressor is None, reason=\"backports.zstd is not installed\")\nasync def test_payload_decompress_size_limit_zstd(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that zstd decompression size limit triggers DecompressSizeError.\"\"\"\n    assert ZstdCompressor is not None\n    # Create a highly compressible payload that exceeds the decompression limit.\n    original = b\"A\" * (64 * 2**20)\n    compressor = ZstdCompressor()\n    compressed = compressor.compress(original) + compressor.flush()\n    assert len(original) > DEFAULT_MAX_DECOMPRESS_SIZE\n\n    async def handler(request: web.Request) -> web.Response:\n        resp = web.Response(body=compressed)\n        resp.headers[\"Content-Encoding\"] = \"zstd\"\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n\n        with pytest.raises(aiohttp.ClientPayloadError) as exc_info:\n            await resp.read()\n\n        assert isinstance(exc_info.value.__cause__, DecompressSizeError)\n        assert \"Decompressed data exceeds\" in str(exc_info.value.__cause__)\n\n\nasync def test_bad_payload_chunked_encoding(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse()\n        resp.force_close()\n        resp._length_check = False\n        resp.headers[\"Transfer-Encoding\"] = \"chunked\"\n        writer = await resp.prepare(request)\n        assert writer is not None\n        await writer.write(b\"9\\r\\n\\r\\n\")\n        await writer.write_eof()\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n\n        with pytest.raises(aiohttp.ClientPayloadError):\n            await resp.read()\n\n\nasync def test_no_payload_304_with_chunked_encoding(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test a 304 response with no payload with chunked set should have it removed.\"\"\"\n\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse(status=304)\n        resp.enable_chunked_encoding()\n        resp._length_check = False\n        resp.headers[\"Transfer-Encoding\"] = \"chunked\"\n        writer = await resp.prepare(request)\n        assert writer is not None\n        await writer.write_eof()\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 304\n        assert hdrs.CONTENT_LENGTH not in resp.headers\n        assert hdrs.TRANSFER_ENCODING not in resp.headers\n        await resp.read()\n\n\nasync def test_head_request_with_chunked_encoding(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test a head response with chunked set should have it removed.\"\"\"\n\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse(status=200)\n        resp.enable_chunked_encoding()\n        resp._length_check = False\n        resp.headers[\"Transfer-Encoding\"] = \"chunked\"\n        writer = await resp.prepare(request)\n        assert writer is not None\n        await writer.write_eof()\n        return resp\n\n    app = web.Application()\n    app.router.add_head(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.head(\"/\") as resp:\n        assert resp.status == 200\n        assert hdrs.CONTENT_LENGTH not in resp.headers\n        assert hdrs.TRANSFER_ENCODING not in resp.headers\n        await resp.read()\n\n\nasync def test_no_payload_200_with_chunked_encoding(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test chunked is preserved on a 200 response with no payload.\"\"\"\n\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse(status=200)\n        resp.enable_chunked_encoding()\n        resp._length_check = False\n        resp.headers[\"Transfer-Encoding\"] = \"chunked\"\n        writer = await resp.prepare(request)\n        assert writer is not None\n        await writer.write_eof()\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n        assert hdrs.CONTENT_LENGTH not in resp.headers\n        assert hdrs.TRANSFER_ENCODING in resp.headers\n        await resp.read()\n\n\nasync def test_bad_payload_content_length(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        resp = web.Response(text=\"text\")\n        resp.headers[\"Content-Length\"] = \"10000\"\n        resp.force_close()\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert 200 == resp.status\n\n        with pytest.raises(aiohttp.ClientPayloadError):\n            await resp.read()\n\n\nasync def test_payload_content_length_by_chunks(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse(headers={\"content-length\": \"2\"})\n        await resp.prepare(request)\n        await resp.write(b\"answer\")\n        await resp.write(b\"two\")\n        assert request.transport is not None\n        request.transport.close()\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        data = await resp.read()\n    assert data == b\"an\"\n\n\nasync def test_chunked(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        resp = web.Response(text=\"text\")\n        resp.enable_chunked_encoding()\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n        assert resp.headers[\"Transfer-Encoding\"] == \"chunked\"\n        txt = await resp.text()\n    assert txt == \"text\"\n\n\nasync def test_shortcuts(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=request.method)\n\n    app = web.Application()\n    for meth in (\"get\", \"post\", \"put\", \"delete\", \"head\", \"patch\", \"options\"):\n        app.router.add_route(meth.upper(), \"/\", handler)\n    client = await aiohttp_client(app)\n\n    for meth in (\"get\", \"post\", \"put\", \"delete\", \"head\", \"patch\", \"options\"):\n        coro = getattr(client.session, meth)\n        resp = await coro(client.make_url(\"/\"))\n\n        assert resp.status == 200\n        assert len(resp.history) == 0\n\n        content1 = await resp.read()\n        content2 = await resp.read()\n        assert content1 == content2\n        content = await resp.text()\n\n        if meth == \"head\":\n            assert b\"\" == content1\n        else:\n            assert meth.upper() == content\n\n\nasync def test_cookies(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert request.cookies.keys() == {\"test1\", \"test3\"}\n        assert request.cookies[\"test1\"] == \"123\"\n        assert request.cookies[\"test3\"] == \"456\"\n        return web.Response()\n\n    c: http.cookies.Morsel[str] = http.cookies.Morsel()\n    c.set(\"test3\", \"456\", \"456\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app, cookies={\"test1\": \"123\", \"test2\": c})\n\n    async with client.get(\"/\") as resp:\n        assert 200 == resp.status\n\n\nasync def test_cookies_per_request(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert request.cookies.keys() == {\"test1\", \"test3\", \"test4\", \"test6\"}\n        assert request.cookies[\"test1\"] == \"123\"\n        assert request.cookies[\"test3\"] == \"456\"\n        assert request.cookies[\"test4\"] == \"789\"\n        assert request.cookies[\"test6\"] == \"abc\"\n        return web.Response()\n\n    c: http.cookies.Morsel[str] = http.cookies.Morsel()\n    c.set(\"test3\", \"456\", \"456\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app, cookies={\"test1\": \"123\", \"test2\": c})\n\n    rc: http.cookies.Morsel[str] = http.cookies.Morsel()\n    rc.set(\"test6\", \"abc\", \"abc\")\n\n    cookies: dict[str, str | http.cookies.Morsel[str]]\n    cookies = {\"test4\": \"789\", \"test5\": rc}\n    async with client.get(\"/\", cookies=cookies) as resp:\n        assert 200 == resp.status\n\n\nasync def test_cookies_redirect(aiohttp_client: AiohttpClient) -> None:\n    async def redirect1(request: web.Request) -> web.Response:\n        ret = web.Response(status=301, headers={\"Location\": \"/redirect2\"})\n        ret.set_cookie(\"c\", \"1\")\n        return ret\n\n    async def redirect2(request: web.Request) -> web.Response:\n        ret = web.Response(status=301, headers={\"Location\": \"/\"})\n        ret.set_cookie(\"c\", \"2\")\n        return ret\n\n    async def handler(request: web.Request) -> web.Response:\n        assert request.cookies.keys() == {\"c\"}\n        assert request.cookies[\"c\"] == \"2\"\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/redirect1\", redirect1)\n    app.router.add_get(\"/redirect2\", redirect2)\n    app.router.add_get(\"/\", handler)\n\n    client = await aiohttp_client(app)\n    async with client.get(\"/redirect1\") as resp:\n        assert 200 == resp.status\n\n\nasync def test_cookies_on_empty_session_jar(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert \"custom-cookie\" in request.cookies\n        assert request.cookies[\"custom-cookie\"] == \"abc\"\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app, cookies=None)\n\n    async with client.get(\"/\", cookies={\"custom-cookie\": \"abc\"}) as resp:\n        assert 200 == resp.status\n\n\nasync def test_cookies_is_quoted_with_special_characters(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert 'cookie1=\"val/one\"' == request.headers[\"Cookie\"]\n        assert \"cookie1\" in request.cookies\n        assert request.cookies[\"cookie1\"] == \"val/one\"\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\", cookies={\"cookie1\": \"val/one\"}) as resp:\n        assert resp.status == 200\n\n\nasync def test_morsel_with_attributes(aiohttp_client: AiohttpClient) -> None:\n    # A comment from original test:\n    #\n    # No cookie attribute should pass here\n    # they are only used as filters\n    # whether to send particular cookie or not.\n    # E.g. if cookie expires it just becomes thrown away.\n    # Server who sent the cookie with some attributes\n    # already knows them, no need to send this back again and again\n\n    async def handler(request: web.Request) -> web.Response:\n        assert request.cookies.keys() == {\"test3\"}\n        assert request.cookies[\"test3\"] == \"456\"\n        return web.Response()\n\n    c: http.cookies.Morsel[str] = http.cookies.Morsel()\n    c.set(\"test3\", \"456\", \"456\")\n    c[\"httponly\"] = True\n    c[\"secure\"] = True\n    c[\"max-age\"] = 1000\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app, cookies={\"test2\": c})\n\n    async with client.get(\"/\") as resp:\n        assert 200 == resp.status\n\n\nasync def test_set_cookies(\n    aiohttp_client: AiohttpClient, caplog: pytest.LogCaptureFixture\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        ret = web.Response()\n        ret.set_cookie(\"c1\", \"cookie1\")\n        ret.set_cookie(\"c2\", \"cookie2\")\n        ret.headers.add(\n            \"Set-Cookie\",\n            \"invalid,cookie=value; \"  # Comma character is not allowed\n            \"HttpOnly; Path=/\",\n        )\n        return ret\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with caplog.at_level(logging.WARNING):\n        async with client.get(\"/\") as resp:\n            assert 200 == resp.status\n            cookie_names = {c.key for c in client.session.cookie_jar}\n            _ = resp.cookies\n        assert cookie_names == {\"c1\", \"c2\"}\n\n    assert \"Can not load cookies: Illegal cookie name 'invalid,cookie'\" in caplog.text\n\n\nasync def test_set_cookies_with_curly_braces(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that cookies with curly braces in names are now accepted (#2683).\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        ret = web.Response()\n        ret.set_cookie(\"c1\", \"cookie1\")\n        ret.headers.add(\n            \"Set-Cookie\",\n            \"ISAWPLB{A7F52349-3531-4DA9-8776-F74BC6F4F1BB}=\"\n            \"{925EC0B8-CB17-4BEB-8A35-1033813B0523}; \"\n            \"HttpOnly; Path=/\",\n        )\n        return ret\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert 200 == resp.status\n        cookie_names = {c.key for c in client.session.cookie_jar}\n        assert cookie_names == {\"c1\", \"ISAWPLB{A7F52349-3531-4DA9-8776-F74BC6F4F1BB}\"}\n\n\nasync def test_set_cookies_expired(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        ret = web.Response()\n        ret.set_cookie(\"c1\", \"cookie1\")\n        ret.set_cookie(\"c2\", \"cookie2\")\n        ret.headers.add(\n            \"Set-Cookie\",\n            \"c3=cookie3; HttpOnly; Path=/ Expires=Tue, 1 Jan 1980 12:00:00 GMT; \",\n        )\n        return ret\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert 200 == resp.status\n        cookie_names = {c.key for c in client.session.cookie_jar}\n    assert cookie_names == {\"c1\", \"c2\"}\n\n\nasync def test_set_cookies_max_age(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        ret = web.Response()\n        ret.set_cookie(\"c1\", \"cookie1\")\n        ret.set_cookie(\"c2\", \"cookie2\")\n        ret.headers.add(\"Set-Cookie\", \"c3=cookie3; HttpOnly; Path=/ Max-Age=1; \")\n        return ret\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert 200 == resp.status\n        cookie_names = {c.key for c in client.session.cookie_jar}\n        assert cookie_names == {\"c1\", \"c2\", \"c3\"}\n        await asyncio.sleep(2)\n        cookie_names = {c.key for c in client.session.cookie_jar}\n        assert cookie_names == {\"c1\", \"c2\"}\n\n\nasync def test_set_cookies_max_age_overflow(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        ret = web.Response()\n        ret.headers.add(\n            \"Set-Cookie\",\n            \"overflow=overflow; HttpOnly; Path=/ Max-Age=\" + str(overflow) + \"; \",\n        )\n        return ret\n\n    overflow = int(\n        datetime.datetime.max.replace(tzinfo=datetime.timezone.utc).timestamp()\n    )\n    empty = None\n    try:\n        empty = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(\n            seconds=overflow\n        )\n    except OverflowError as ex:\n        assert isinstance(ex, OverflowError)\n    assert not isinstance(empty, datetime.datetime)\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert 200 == resp.status\n        for cookie in client.session.cookie_jar:\n            assert cookie.key == \"overflow\"\n            assert int(cookie[\"max-age\"]) == int(overflow)\n\n\nasync def test_request_conn_error() -> None:\n    async with aiohttp.ClientSession() as client:\n        with pytest.raises(aiohttp.ClientConnectionError):\n            await client.get(\"http://0.0.0.0:1\")\n\n\n@pytest.mark.xfail\nasync def test_broken_connection(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert request.transport is not None\n        request.transport.close()\n        return web.Response(text=\"answer\" * 1000)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with pytest.raises(aiohttp.ClientResponseError):\n        await client.get(\"/\")\n\n\nasync def test_broken_connection_2(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse(headers={\"content-length\": \"1000\"})\n        await resp.prepare(request)\n        await resp.write(b\"answer\")\n        assert request.transport is not None\n        request.transport.close()\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        with pytest.raises(aiohttp.ClientPayloadError):\n            await resp.read()\n\n\nasync def test_custom_headers(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert request.headers[\"x-api-key\"] == \"foo\"\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.post(\n        \"/\", headers={\"Content-Type\": \"application/json\", \"x-api-key\": \"foo\"}\n    ) as resp:\n        assert resp.status == 200\n\n\nasync def test_redirect_to_absolute_url(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=request.method)\n\n    async def redirect(request: web.Request) -> NoReturn:\n        raise web.HTTPFound(location=client.make_url(\"/\"))\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    app.router.add_get(\"/redirect\", redirect)\n\n    client = await aiohttp_client(app)\n    async with client.get(\"/redirect\") as resp:\n        assert 200 == resp.status\n\n\nasync def test_redirect_without_location_header(aiohttp_client: AiohttpClient) -> None:\n    body = b\"redirect\"\n\n    async def handler_redirect(request: web.Request) -> web.Response:\n        return web.Response(status=301, body=body)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/redirect\", handler_redirect)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/redirect\") as resp:\n        data = await resp.read()\n    assert data == body\n\n\nINVALID_URL_WITH_ERROR_MESSAGE_YARL_NEW = (\n    # yarl.URL.__new__ raises ValueError\n    (\"http://:/\", \"http://:/\"),\n    (\"http://example.org:non_int_port/\", \"http://example.org:non_int_port/\"),\n)\n\nINVALID_URL_WITH_ERROR_MESSAGE_YARL_ORIGIN = (\n    # # yarl.URL.origin raises ValueError\n    (\"http:/\", \"http:///\"),\n    (\"http:/example.com\", \"http:///example.com\"),\n    (\"http:///example.com\", \"http:///example.com\"),\n)\n\nNON_HTTP_URL_WITH_ERROR_MESSAGE = (\n    (\"call:+380123456789\", r\"call:\\+380123456789\"),\n    (\"skype:handle\", \"skype:handle\"),\n    (\"slack://instance/room\", \"slack://instance/room\"),\n    (\"steam:code\", \"steam:code\"),\n    (\"twitter://handle\", \"twitter://handle\"),\n    (\"bluesky://profile/d:i:d\", \"bluesky://profile/d:i:d\"),\n)\n\n\n@pytest.mark.parametrize(\n    (\"url\", \"error_message_url\", \"expected_exception_class\"),\n    (\n        *(\n            (url, message, InvalidUrlClientError)\n            for (url, message) in INVALID_URL_WITH_ERROR_MESSAGE_YARL_NEW\n        ),\n        *(\n            (url, message, InvalidUrlClientError)\n            for (url, message) in INVALID_URL_WITH_ERROR_MESSAGE_YARL_ORIGIN\n        ),\n        *(\n            (url, message, NonHttpUrlClientError)\n            for (url, message) in NON_HTTP_URL_WITH_ERROR_MESSAGE\n        ),\n    ),\n)\nasync def test_invalid_and_non_http_url(\n    url: str, error_message_url: str, expected_exception_class: type[Exception]\n) -> None:\n    async with aiohttp.ClientSession() as http_session:\n        with pytest.raises(\n            expected_exception_class, match=rf\"^{error_message_url}( - [A-Za-z ]+)?\"\n        ):\n            await http_session.get(url)\n\n\n@pytest.mark.parametrize(\n    (\"invalid_redirect_url\", \"error_message_url\", \"expected_exception_class\"),\n    (\n        *(\n            (url, message, InvalidUrlRedirectClientError)\n            for (url, message) in INVALID_URL_WITH_ERROR_MESSAGE_YARL_ORIGIN\n            + INVALID_URL_WITH_ERROR_MESSAGE_YARL_NEW\n        ),\n        *(\n            (url, message, NonHttpUrlRedirectClientError)\n            for (url, message) in NON_HTTP_URL_WITH_ERROR_MESSAGE\n        ),\n    ),\n)\nasync def test_invalid_redirect_url(\n    aiohttp_client: AiohttpClient,\n    invalid_redirect_url: str,\n    error_message_url: str,\n    expected_exception_class: type[Exception],\n) -> None:\n    headers = {hdrs.LOCATION: invalid_redirect_url}\n\n    async def generate_redirecting_response(request: web.Request) -> web.Response:\n        return web.Response(status=301, headers=headers)\n\n    app = web.Application()\n    app.router.add_get(\"/redirect\", generate_redirecting_response)\n    client = await aiohttp_client(app)\n\n    with pytest.raises(\n        expected_exception_class, match=rf\"^{error_message_url}( - [A-Za-z ]+)?\"\n    ):\n        await client.get(\"/redirect\")\n\n\n@pytest.mark.parametrize(\n    (\"invalid_redirect_url\", \"error_message_url\", \"expected_exception_class\"),\n    (\n        *(\n            (url, message, InvalidUrlRedirectClientError)\n            for (url, message) in INVALID_URL_WITH_ERROR_MESSAGE_YARL_ORIGIN\n            + INVALID_URL_WITH_ERROR_MESSAGE_YARL_NEW\n        ),\n        *(\n            (url, message, NonHttpUrlRedirectClientError)\n            for (url, message) in NON_HTTP_URL_WITH_ERROR_MESSAGE\n        ),\n    ),\n)\nasync def test_invalid_redirect_url_multiple_redirects(\n    aiohttp_client: AiohttpClient,\n    invalid_redirect_url: str,\n    error_message_url: str,\n    expected_exception_class: type[Exception],\n) -> None:\n    app = web.Application()\n\n    for path, location in [\n        (\"/redirect\", \"/redirect1\"),\n        (\"/redirect1\", \"/redirect2\"),\n        (\"/redirect2\", invalid_redirect_url),\n    ]:\n\n        async def generate_redirecting_response(request: web.Request) -> web.Response:\n            return web.Response(status=301, headers={hdrs.LOCATION: location})\n\n        app.router.add_get(path, generate_redirecting_response)\n\n    client = await aiohttp_client(app)\n\n    with pytest.raises(\n        expected_exception_class, match=rf\"^{error_message_url}( - [A-Za-z ]+)?\"\n    ):\n        await client.get(\"/redirect\")\n\n\n@pytest.mark.parametrize(\n    (\"status\", \"expected_ok\"),\n    (\n        (200, True),\n        (201, True),\n        (301, True),\n        (400, False),\n        (403, False),\n        (500, False),\n    ),\n)\nasync def test_ok_from_status(\n    aiohttp_client: AiohttpClient, status: int, expected_ok: bool\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(status=status, body=b\"\")\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/endpoint\", handler)\n    client = await aiohttp_client(app, raise_for_status=False)\n    async with client.get(\"/endpoint\") as resp:\n        assert resp.ok is expected_ok\n\n\nasync def test_raise_for_status(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        raise web.HTTPBadRequest()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app, raise_for_status=True)\n\n    with pytest.raises(aiohttp.ClientResponseError):\n        await client.get(\"/\")\n\n\nasync def test_raise_for_status_per_request(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        raise web.HTTPBadRequest()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    with pytest.raises(aiohttp.ClientResponseError):\n        await client.get(\"/\", raise_for_status=True)\n\n\nasync def test_raise_for_status_disable_per_request(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        raise web.HTTPBadRequest()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app, raise_for_status=True)\n\n    async with client.get(\"/\", raise_for_status=False) as resp:\n        assert 400 == resp.status\n\n\nasync def test_request_raise_for_status_default(aiohttp_server: AiohttpServer) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        raise web.HTTPBadRequest()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with aiohttp.request(\"GET\", server.make_url(\"/\")) as resp:\n        assert resp.status == 400\n\n\nasync def test_request_raise_for_status_disabled(aiohttp_server: AiohttpServer) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        raise web.HTTPBadRequest()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n    url = server.make_url(\"/\")\n\n    async with aiohttp.request(\"GET\", url, raise_for_status=False) as resp:\n        assert resp.status == 400\n\n\nasync def test_request_raise_for_status_enabled(aiohttp_server: AiohttpServer) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        raise web.HTTPBadRequest()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n    url = server.make_url(\"/\")\n\n    with pytest.raises(aiohttp.ClientResponseError):\n        async with aiohttp.request(\"GET\", url, raise_for_status=True):\n            assert False\n\n\nasync def test_session_raise_for_status_coro(aiohttp_client: AiohttpClient) -> None:\n    async def handle(request: web.Request) -> web.Response:\n        return web.Response(text=\"ok\")\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handle)\n\n    raise_for_status_called = 0\n\n    async def custom_r4s(response: aiohttp.ClientResponse) -> None:\n        nonlocal raise_for_status_called\n        raise_for_status_called += 1\n        assert response.status == 200\n        assert response.request_info.method == \"GET\"\n\n    client = await aiohttp_client(app, raise_for_status=custom_r4s)\n    await client.get(\"/\")\n    assert raise_for_status_called == 1\n    await client.get(\"/\", raise_for_status=True)\n    assert raise_for_status_called == 1  # custom_r4s not called again\n    await client.get(\"/\", raise_for_status=False)\n    assert raise_for_status_called == 1  # custom_r4s not called again\n\n\nasync def test_request_raise_for_status_coro(aiohttp_client: AiohttpClient) -> None:\n    async def handle(request: web.Request) -> web.Response:\n        return web.Response(text=\"ok\")\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handle)\n\n    raise_for_status_called = 0\n\n    async def custom_r4s(response: aiohttp.ClientResponse) -> None:\n        nonlocal raise_for_status_called\n        raise_for_status_called += 1\n        assert response.status == 200\n        assert response.request_info.method == \"GET\"\n\n    client = await aiohttp_client(app)\n    await client.get(\"/\", raise_for_status=custom_r4s)\n    assert raise_for_status_called == 1\n    await client.get(\"/\", raise_for_status=True)\n    assert raise_for_status_called == 1  # custom_r4s not called again\n    await client.get(\"/\", raise_for_status=False)\n    assert raise_for_status_called == 1  # custom_r4s not called again\n\n\nasync def test_invalid_idna() -> None:\n    async with aiohttp.ClientSession() as session:\n        with pytest.raises(aiohttp.InvalidURL):\n            await session.get(\"http://\\u0080owhefopw.com\")\n\n\nasync def test_creds_in_auth_and_url() -> None:\n    async with aiohttp.ClientSession() as session:\n        with pytest.raises(ValueError):\n            await session.get(\n                \"http://user:pass@example.com\", auth=aiohttp.BasicAuth(\"user2\", \"pass2\")\n            )\n\n\nasync def test_creds_in_auth_and_redirect_url(\n    create_server_for_url_and_handler: Callable[[URL, Handler], Awaitable[TestServer]],\n) -> None:\n    \"\"\"Verify that credentials in redirect URLs can and do override any previous credentials.\"\"\"\n    url_from = URL(\"http://example.com\")\n    url_to = URL(\"http://user@example.com\")\n    redirected = False\n\n    async def srv(request: web.Request) -> web.Response:\n        nonlocal redirected\n\n        assert request.host == url_from.host\n\n        if not redirected:\n            redirected = True\n            raise web.HTTPMovedPermanently(url_to)\n\n        return web.Response()\n\n    server = await create_server_for_url_and_handler(url_from, srv)\n\n    etc_hosts = {\n        (url_from.host, 80): server,\n    }\n\n    class FakeResolver(AbstractResolver):\n        async def resolve(\n            self,\n            host: str,\n            port: int = 0,\n            family: socket.AddressFamily = socket.AF_INET,\n        ) -> list[ResolveResult]:\n            server = etc_hosts[(host, port)]\n            assert server.port is not None\n\n            return [\n                {\n                    \"hostname\": host,\n                    \"host\": server.host,\n                    \"port\": server.port,\n                    \"family\": socket.AF_INET,\n                    \"proto\": 0,\n                    \"flags\": socket.AI_NUMERICHOST,\n                }\n            ]\n\n        async def close(self) -> None:\n            \"\"\"Dummy\"\"\"\n\n    connector = aiohttp.TCPConnector(resolver=FakeResolver(), ssl=False)\n\n    async with (\n        aiohttp.ClientSession(connector=connector) as client,\n        client.get(url_from, auth=aiohttp.BasicAuth(\"user\", \"pass\")) as resp,\n    ):\n        assert len(resp.history) == 1\n        assert str(resp.url) == \"http://example.com\"\n        assert resp.status == 200\n        assert (\n            resp.request_info.headers.get(\"authorization\") == \"Basic dXNlcjo=\"\n        ), \"Expected redirect credentials to take precedence over provided auth\"\n\n\n@pytest.fixture\ndef create_server_for_url_and_handler(\n    aiohttp_server: AiohttpServer, tls_certificate_authority: trustme.CA\n) -> Callable[[URL, Handler], Awaitable[TestServer]]:\n    def create(url: URL, srv: Handler) -> Awaitable[TestServer]:\n        app = web.Application()\n        app.router.add_route(\"GET\", url.path, srv)\n\n        if url.scheme == \"https\":\n            assert url.host\n            cert = tls_certificate_authority.issue_cert(\n                url.host, \"localhost\", \"127.0.0.1\"\n            )\n            ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n            cert.configure_cert(ssl_ctx)\n            return aiohttp_server(app, ssl=ssl_ctx)\n        return aiohttp_server(app)\n\n    return create\n\n\n@pytest.mark.parametrize(\n    [\"url_from_s\", \"url_to_s\", \"is_drop_header_expected\"],\n    [\n        [\n            \"http://host1.com/path1\",\n            \"http://host2.com/path2\",\n            True,\n        ],\n        [\"http://host1.com/path1\", \"https://host1.com/path1\", False],\n        [\"https://host1.com/path1\", \"http://host1.com/path2\", True],\n    ],\n    ids=(\n        \"entirely different hosts\",\n        \"http -> https\",\n        \"https -> http\",\n    ),\n)\nasync def test_drop_auth_on_redirect_to_other_host(\n    create_server_for_url_and_handler: Callable[[URL, Handler], Awaitable[TestServer]],\n    url_from_s: str,\n    url_to_s: str,\n    is_drop_header_expected: bool,\n) -> None:\n    url_from, url_to = URL(url_from_s), URL(url_to_s)\n\n    async def srv_from(request: web.Request) -> NoReturn:\n        assert request.host == url_from.host\n        assert request.headers[\"Authorization\"] == \"Basic dXNlcjpwYXNz\"\n        raise web.HTTPFound(url_to)\n\n    async def srv_to(request: web.Request) -> web.Response:\n        assert request.host == url_to.host\n        if is_drop_header_expected:\n            assert \"Authorization\" not in request.headers, \"Header wasn't dropped\"\n            assert \"Proxy-Authorization\" not in request.headers\n            assert \"Cookie\" not in request.headers\n        else:\n            assert \"Authorization\" in request.headers, \"Header was dropped\"\n            assert \"Proxy-Authorization\" in request.headers\n            assert \"Cookie\" in request.headers\n        return web.Response()\n\n    server_from = await create_server_for_url_and_handler(url_from, srv_from)\n    server_to = await create_server_for_url_and_handler(url_to, srv_to)\n\n    assert (\n        url_from.host != url_to.host or server_from.scheme != server_to.scheme\n    ), \"Invalid test case, host or scheme must differ\"\n\n    protocol_port_map = {\n        \"http\": 80,\n        \"https\": 443,\n    }\n    etc_hosts = {\n        (url_from.host, protocol_port_map[server_from.scheme]): server_from,\n        (url_to.host, protocol_port_map[server_to.scheme]): server_to,\n    }\n\n    class FakeResolver(AbstractResolver):\n        async def resolve(\n            self,\n            host: str,\n            port: int = 0,\n            family: socket.AddressFamily = socket.AF_INET,\n        ) -> list[ResolveResult]:\n            server = etc_hosts[(host, port)]\n            assert server.port is not None\n\n            return [\n                {\n                    \"hostname\": host,\n                    \"host\": server.host,\n                    \"port\": server.port,\n                    \"family\": socket.AF_INET,\n                    \"proto\": 0,\n                    \"flags\": socket.AI_NUMERICHOST,\n                }\n            ]\n\n        async def close(self) -> None:\n            \"\"\"Dummy\"\"\"\n\n    connector = aiohttp.TCPConnector(resolver=FakeResolver(), ssl=False)\n\n    async with aiohttp.ClientSession(connector=connector) as client:\n        async with client.get(\n            url_from,\n            auth=aiohttp.BasicAuth(\"user\", \"pass\"),\n            headers={\"Proxy-Authorization\": \"Basic dXNlcjpwYXNz\", \"Cookie\": \"a=b\"},\n        ) as resp:\n            assert resp.status == 200\n        async with client.get(\n            url_from,\n            headers={\n                \"Authorization\": \"Basic dXNlcjpwYXNz\",\n                \"Proxy-Authorization\": \"Basic dXNlcjpwYXNz\",\n                \"Cookie\": \"a=b\",\n            },\n        ) as resp:\n            assert resp.status == 200\n\n\nasync def test_auth_persist_on_redirect_to_other_host_with_global_auth(\n    create_server_for_url_and_handler: Callable[[URL, Handler], Awaitable[TestServer]],\n) -> None:\n    url_from = URL(\"http://host1.com/path1\")\n    url_to = URL(\"http://host2.com/path2\")\n\n    async def srv_from(request: web.Request) -> NoReturn:\n        assert request.host == url_from.host\n        assert request.headers[\"Authorization\"] == \"Basic dXNlcjpwYXNz\"\n        raise web.HTTPFound(url_to)\n\n    async def srv_to(request: web.Request) -> web.Response:\n        assert request.host == url_to.host\n        assert \"Authorization\" in request.headers, \"Header was dropped\"\n        return web.Response()\n\n    server_from = await create_server_for_url_and_handler(url_from, srv_from)\n    server_to = await create_server_for_url_and_handler(url_to, srv_to)\n\n    assert (\n        url_from.host != url_to.host or server_from.scheme != server_to.scheme\n    ), \"Invalid test case, host or scheme must differ\"\n\n    protocol_port_map = {\n        \"http\": 80,\n        \"https\": 443,\n    }\n    etc_hosts = {\n        (url_from.host, protocol_port_map[server_from.scheme]): server_from,\n        (url_to.host, protocol_port_map[server_to.scheme]): server_to,\n    }\n\n    class FakeResolver(AbstractResolver):\n        async def resolve(\n            self,\n            host: str,\n            port: int = 0,\n            family: socket.AddressFamily = socket.AF_INET,\n        ) -> list[ResolveResult]:\n            server = etc_hosts[(host, port)]\n            assert server.port is not None\n\n            return [\n                {\n                    \"hostname\": host,\n                    \"host\": server.host,\n                    \"port\": server.port,\n                    \"family\": socket.AF_INET,\n                    \"proto\": 0,\n                    \"flags\": socket.AI_NUMERICHOST,\n                }\n            ]\n\n        async def close(self) -> None:\n            \"\"\"Dummy\"\"\"\n\n    connector = aiohttp.TCPConnector(resolver=FakeResolver(), ssl=False)\n\n    async with aiohttp.ClientSession(\n        connector=connector, auth=aiohttp.BasicAuth(\"user\", \"pass\")\n    ) as client:\n        async with client.get(url_from) as resp:\n            assert resp.status == 200\n\n\nasync def test_drop_auth_on_redirect_to_other_host_with_global_auth_and_base_url(\n    create_server_for_url_and_handler: Callable[[URL, Handler], Awaitable[TestServer]],\n) -> None:\n    url_from = URL(\"http://host1.com/path1\")\n    url_to = URL(\"http://host2.com/path2\")\n\n    async def srv_from(request: web.Request) -> NoReturn:\n        assert request.host == url_from.host\n        assert request.headers[\"Authorization\"] == \"Basic dXNlcjpwYXNz\"\n        raise web.HTTPFound(url_to)\n\n    async def srv_to(request: web.Request) -> web.Response:\n        assert request.host == url_to.host\n        assert \"Authorization\" not in request.headers, \"Header was not dropped\"\n        return web.Response()\n\n    server_from = await create_server_for_url_and_handler(url_from, srv_from)\n    server_to = await create_server_for_url_and_handler(url_to, srv_to)\n\n    assert (\n        url_from.host != url_to.host or server_from.scheme != server_to.scheme\n    ), \"Invalid test case, host or scheme must differ\"\n\n    protocol_port_map = {\n        \"http\": 80,\n        \"https\": 443,\n    }\n    etc_hosts = {\n        (url_from.host, protocol_port_map[server_from.scheme]): server_from,\n        (url_to.host, protocol_port_map[server_to.scheme]): server_to,\n    }\n\n    class FakeResolver(AbstractResolver):\n        async def resolve(\n            self,\n            host: str,\n            port: int = 0,\n            family: socket.AddressFamily = socket.AF_INET,\n        ) -> list[ResolveResult]:\n            server = etc_hosts[(host, port)]\n            assert server.port is not None\n\n            return [\n                {\n                    \"hostname\": host,\n                    \"host\": server.host,\n                    \"port\": server.port,\n                    \"family\": socket.AF_INET,\n                    \"proto\": 0,\n                    \"flags\": socket.AI_NUMERICHOST,\n                }\n            ]\n\n        async def close(self) -> None:\n            \"\"\"Dummy\"\"\"\n\n    connector = aiohttp.TCPConnector(resolver=FakeResolver(), ssl=False)\n\n    async with aiohttp.ClientSession(\n        connector=connector,\n        base_url=\"http://host1.com\",\n        auth=aiohttp.BasicAuth(\"user\", \"pass\"),\n    ) as client:\n        async with client.get(\"/path1\") as resp:\n            assert resp.status == 200\n\n\nasync def test_async_with_session() -> None:\n    async with aiohttp.ClientSession() as session:\n        pass\n\n    assert session.closed\n\n\nasync def test_session_close_awaitable() -> None:\n    session = aiohttp.ClientSession()\n    await session.close()\n\n    assert session.closed\n\n\nasync def test_close_resp_on_error_async_with_session(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        resp = web.StreamResponse(headers={\"content-length\": \"100\"})\n        await resp.prepare(request)\n        await asyncio.sleep(0.1)\n        assert False\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with aiohttp.ClientSession() as session:\n        with pytest.raises(RuntimeError):\n            async with session.get(server.make_url(\"/\")) as resp:\n                resp.content.set_exception(RuntimeError())\n                await resp.read()\n\n        assert session._connector is not None\n        assert len(session._connector._conns) == 0\n\n\nasync def test_release_resp_on_normal_exit_from_cm(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with aiohttp.ClientSession() as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            await resp.read()\n\n        assert session._connector is not None\n        assert len(session._connector._conns) == 1\n\n\nasync def test_non_close_detached_session_on_error_cm(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        resp = web.StreamResponse(headers={\"content-length\": \"100\"})\n        await resp.prepare(request)\n        await asyncio.sleep(0.1)\n        assert False\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    session = aiohttp.ClientSession()\n    cm = session.get(server.make_url(\"/\"))\n    assert not session.closed\n    with pytest.raises(RuntimeError):\n        async with cm as resp:\n            resp.content.set_exception(RuntimeError())\n            await resp.read()\n    assert not session.closed\n\n\nasync def test_close_detached_session_on_non_existing_addr() -> None:\n    class FakeResolver(AbstractResolver):\n        async def resolve(\n            self,\n            host: str,\n            port: int = 0,\n            family: socket.AddressFamily = socket.AF_INET,\n        ) -> list[ResolveResult]:\n            return []\n\n        async def close(self) -> None:\n            \"\"\"Dummy\"\"\"\n\n    connector = aiohttp.TCPConnector(resolver=FakeResolver())\n\n    session = aiohttp.ClientSession(connector=connector)\n\n    async with session:\n        cm = session.get(\"http://non-existing.example.com\")\n        assert not session.closed\n        with pytest.raises(Exception):\n            await cm\n\n    assert session.closed\n\n\nasync def test_aiohttp_request_context_manager(aiohttp_server: AiohttpServer) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with aiohttp.request(\"GET\", server.make_url(\"/\")) as resp:\n        await resp.read()\n        assert resp.status == 200\n\n\nasync def test_aiohttp_request_ctx_manager_close_sess_on_error(\n    ssl_ctx: ssl.SSLContext, aiohttp_server: AiohttpServer\n) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app, ssl=ssl_ctx)\n\n    cm = aiohttp.request(\"GET\", server.make_url(\"/\"))\n\n    with pytest.raises(aiohttp.ClientConnectionError):\n        async with cm:\n            pass\n\n    assert cm._session.closed\n    # Allow event loop to process transport cleanup\n    # on Python < 3.11\n    await asyncio.sleep(0)\n\n\nasync def test_aiohttp_request_ctx_manager_not_found() -> None:\n    with pytest.raises(aiohttp.ClientConnectionError):\n        async with aiohttp.request(\"GET\", \"http://wrong-dns-name.com\"):\n            assert False\n\n\nasync def test_raising_client_connector_dns_error_on_dns_failure() -> None:\n    \"\"\"Verify that the exception raised when a DNS lookup fails is specific to DNS.\"\"\"\n    with mock.patch(\n        \"aiohttp.connector.TCPConnector._resolve_host\", autospec=True, spec_set=True\n    ) as mock_resolve_host:\n        mock_resolve_host.side_effect = OSError(None, \"DNS lookup failed\")\n        with pytest.raises(aiohttp.ClientConnectorDNSError, match=\"DNS lookup failed\"):\n            async with aiohttp.request(\"GET\", \"http://wrong-dns-name.com\"):\n                assert False, \"never executed\"\n\n\nasync def test_aiohttp_request_coroutine(aiohttp_server: AiohttpServer) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    not_an_awaitable = aiohttp.request(\"GET\", server.make_url(\"/\"))\n    with pytest.raises(\n        TypeError,\n        match=(\n            \"^'_SessionRequestContextManager' object can't be awaited$\"\n            if sys.version_info >= (3, 14)\n            else \"^object _SessionRequestContextManager \"\n            \"can't be used in 'await' expression$\"\n        ),\n    ):\n        await not_an_awaitable  # type: ignore[misc]\n\n    await not_an_awaitable._coro  # coroutine 'ClientSession._request' was never awaited\n    await server.close()\n\n\nasync def test_aiohttp_request_ssl(\n    aiohttp_server: AiohttpServer,\n    ssl_ctx: ssl.SSLContext,\n    client_ssl_ctx: ssl.SSLContext,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app, ssl=ssl_ctx)\n\n    async with aiohttp.request(\"GET\", server.make_url(\"/\"), ssl=client_ssl_ctx) as resp:\n        assert resp.status == 200\n\n\nasync def test_yield_from_in_session_request(aiohttp_client: AiohttpClient) -> None:\n    # a test for backward compatibility with yield from syntax\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n\n    client = await aiohttp_client(app)\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n\n\nasync def test_session_auth(\n    headers_echo_client: Callable[\n        ..., Awaitable[TestClient[web.Request, web.Application]]\n    ],\n) -> None:\n    client = await headers_echo_client(auth=aiohttp.BasicAuth(\"login\", \"pass\"))\n\n    async with client.get(\"/\") as r:\n        assert r.status == 200\n        content = await r.json()\n    assert content[\"headers\"][\"Authorization\"] == \"Basic bG9naW46cGFzcw==\"\n\n\nasync def test_session_auth_override(\n    headers_echo_client: Callable[\n        ..., Awaitable[TestClient[web.Request, web.Application]]\n    ],\n) -> None:\n    client = await headers_echo_client(auth=aiohttp.BasicAuth(\"login\", \"pass\"))\n\n    async with client.get(\"/\", auth=aiohttp.BasicAuth(\"other_login\", \"pass\")) as r:\n        assert r.status == 200\n        content = await r.json()\n    val = content[\"headers\"][\"Authorization\"]\n    assert val == \"Basic b3RoZXJfbG9naW46cGFzcw==\"\n\n\nasync def test_session_auth_header_conflict(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n\n    client = await aiohttp_client(app, auth=aiohttp.BasicAuth(\"login\", \"pass\"))\n    headers = {\"Authorization\": \"Basic b3RoZXJfbG9naW46cGFzcw==\"}\n    with pytest.raises(ValueError):\n        await client.get(\"/\", headers=headers)\n\n\n@pytest.mark.usefixtures(\"netrc_default_contents\")\nasync def test_netrc_auth_from_env(  # type: ignore[misc]\n    headers_echo_client: Callable[\n        ..., Awaitable[TestClient[web.Request, web.Application]]\n    ],\n) -> None:\n    \"\"\"Test that netrc authentication works when NETRC env var is set and trust_env=True.\"\"\"\n    client = await headers_echo_client(trust_env=True)\n    async with client.get(\"/\") as r:\n        assert r.status == 200\n        content = await r.json()\n    # Base64 encoded \"netrc_user:netrc_pass\" is \"bmV0cmNfdXNlcjpuZXRyY19wYXNz\"\n    assert content[\"headers\"][\"Authorization\"] == \"Basic bmV0cmNfdXNlcjpuZXRyY19wYXNz\"\n\n\n@pytest.mark.usefixtures(\"no_netrc\")\nasync def test_netrc_auth_skipped_without_netrc_file(  # type: ignore[misc]\n    headers_echo_client: Callable[\n        ..., Awaitable[TestClient[web.Request, web.Application]]\n    ],\n) -> None:\n    \"\"\"Test that netrc authentication is skipped when no netrc file exists.\"\"\"\n    client = await headers_echo_client(trust_env=True)\n    async with client.get(\"/\") as r:\n        assert r.status == 200\n        content = await r.json()\n    # No Authorization header should be present\n    assert \"Authorization\" not in content[\"headers\"]\n\n\n@pytest.mark.usefixtures(\"netrc_home_directory\")\nasync def test_netrc_auth_from_home_directory(  # type: ignore[misc]\n    headers_echo_client: Callable[\n        ..., Awaitable[TestClient[web.Request, web.Application]]\n    ],\n) -> None:\n    \"\"\"Test that netrc authentication works from default ~/.netrc without NETRC env var.\"\"\"\n    client = await headers_echo_client(trust_env=True)\n    async with client.get(\"/\") as r:\n        assert r.status == 200\n        content = await r.json()\n    assert content[\"headers\"][\"Authorization\"] == \"Basic bmV0cmNfdXNlcjpuZXRyY19wYXNz\"\n\n\n@pytest.mark.usefixtures(\"netrc_default_contents\")\nasync def test_netrc_auth_overridden_by_explicit_auth(  # type: ignore[misc]\n    headers_echo_client: Callable[\n        ..., Awaitable[TestClient[web.Request, web.Application]]\n    ],\n) -> None:\n    \"\"\"Test that explicit auth parameter overrides netrc authentication.\"\"\"\n    client = await headers_echo_client(trust_env=True)\n    # Make request with explicit auth (should override netrc)\n    async with client.get(\n        \"/\", auth=aiohttp.BasicAuth(\"explicit_user\", \"explicit_pass\")\n    ) as r:\n        assert r.status == 200\n        content = await r.json()\n    # Base64 encoded \"explicit_user:explicit_pass\" is \"ZXhwbGljaXRfdXNlcjpleHBsaWNpdF9wYXNz\"\n    assert (\n        content[\"headers\"][\"Authorization\"]\n        == \"Basic ZXhwbGljaXRfdXNlcjpleHBsaWNpdF9wYXNz\"\n    )\n\n\nasync def test_session_headers(\n    headers_echo_client: Callable[\n        ..., Awaitable[TestClient[web.Request, web.Application]]\n    ],\n) -> None:\n    client = await headers_echo_client(headers={\"X-Real-IP\": \"192.168.0.1\"})\n\n    async with client.get(\"/\") as r:\n        assert r.status == 200\n        content = await r.json()\n    assert content[\"headers\"][\"X-Real-IP\"] == \"192.168.0.1\"\n\n\nasync def test_session_headers_merge(\n    headers_echo_client: Callable[\n        ..., Awaitable[TestClient[web.Request, web.Application]]\n    ],\n) -> None:\n    client = await headers_echo_client(\n        headers=[(\"X-Real-IP\", \"192.168.0.1\"), (\"X-Sent-By\", \"requests\")]\n    )\n\n    async with client.get(\"/\", headers={\"X-Sent-By\": \"aiohttp\"}) as r:\n        assert r.status == 200\n        content = await r.json()\n    assert content[\"headers\"][\"X-Real-IP\"] == \"192.168.0.1\"\n    assert content[\"headers\"][\"X-Sent-By\"] == \"aiohttp\"\n\n\nasync def test_multidict_headers(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert await request.read() == data\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n\n    client = await aiohttp_client(app)\n\n    data = b\"sample data\"\n\n    async with client.post(\n        \"/\", data=data, headers=MultiDict({\"Content-Length\": str(len(data))})\n    ) as r:\n        assert r.status == 200\n\n\nasync def test_request_conn_closed(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert request.transport is not None\n        request.transport.close()\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n\n    client = await aiohttp_client(app)\n    with pytest.raises(aiohttp.ServerDisconnectedError) as excinfo:\n        async with client.get(\"/\") as resp:\n            await resp.read()\n\n    assert str(excinfo.value) != \"\"\n\n\nasync def test_dont_close_explicit_connector(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n\n    client = await aiohttp_client(app)\n    async with client.get(\"/\") as r:\n        await r.read()\n\n    assert client.session.connector is not None\n    assert 1 == len(client.session.connector._conns)\n\n\nasync def test_server_close_keepalive_connection() -> None:\n    loop = asyncio.get_event_loop()\n\n    class Proto(asyncio.Protocol):\n        def connection_made(self, transport: asyncio.BaseTransport) -> None:\n            assert isinstance(transport, asyncio.Transport)\n            self.transp: asyncio.Transport | None = transport\n            self.data = b\"\"\n\n        def data_received(self, data: bytes) -> None:\n            self.data += data\n            assert data.endswith(b\"\\r\\n\\r\\n\")\n            assert self.transp is not None\n            self.transp.write(\n                b\"HTTP/1.1 200 OK\\r\\n\"\n                b\"CONTENT-LENGTH: 2\\r\\n\"\n                b\"CONNECTION: close\\r\\n\"\n                b\"\\r\\n\"\n                b\"ok\"\n            )\n            self.transp.close()\n\n        def connection_lost(self, exc: BaseException | None) -> None:\n            self.transp = None\n\n    server = await loop.create_server(Proto, \"127.0.0.1\", unused_port())\n\n    addr = server.sockets[0].getsockname()\n\n    connector = aiohttp.TCPConnector(limit=1)\n    async with aiohttp.ClientSession(connector=connector) as session:\n        url = \"http://{}:{}/\".format(*addr)\n        for i in range(2):\n            r = await session.request(\"GET\", url)\n            await r.read()\n            assert 0 == len(connector._conns)\n    await connector.close()\n    server.close()\n    await server.wait_closed()\n\n\nasync def test_handle_keepalive_on_closed_connection() -> None:\n    loop = asyncio.get_event_loop()\n\n    class Proto(asyncio.Protocol):\n        def connection_made(self, transport: asyncio.BaseTransport) -> None:\n            assert isinstance(transport, asyncio.Transport)\n            self.transp: asyncio.Transport | None = transport\n            self.data = b\"\"\n\n        def data_received(self, data: bytes) -> None:\n            self.data += data\n            assert data.endswith(b\"\\r\\n\\r\\n\")\n            assert self.transp is not None\n            self.transp.write(b\"HTTP/1.1 200 OK\\r\\nCONTENT-LENGTH: 2\\r\\n\\r\\nok\")\n            self.transp.close()\n\n        def connection_lost(self, exc: BaseException | None) -> None:\n            self.transp = None\n\n    server = await loop.create_server(Proto, \"127.0.0.1\", unused_port())\n\n    addr = server.sockets[0].getsockname()\n\n    async with aiohttp.TCPConnector(limit=1) as connector:\n        async with aiohttp.ClientSession(connector=connector) as session:\n            url = \"http://{}:{}/\".format(*addr)\n\n            r = await session.request(\"GET\", url)\n            await r.read()\n            assert 1 == len(connector._conns)\n            closed_conn = next(iter(connector._conns.values()))\n\n            await session.request(\"GET\", url)\n            assert 1 == len(connector._conns)\n            new_conn = next(iter(connector._conns.values()))\n            assert closed_conn is not new_conn\n\n    server.close()\n    await server.wait_closed()\n\n\nasync def test_error_in_performing_request(\n    ssl_ctx: ssl.SSLContext,\n    aiohttp_client: AiohttpClient,\n    aiohttp_server: AiohttpServer,\n) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    def exception_handler(loop: object, context: object) -> None:\n        \"\"\"Skip log messages about destroyed but pending tasks\"\"\"\n\n    loop = asyncio.get_event_loop()\n    loop.set_exception_handler(exception_handler)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    server = await aiohttp_server(app, ssl=ssl_ctx)\n\n    conn = aiohttp.TCPConnector(limit=1)\n    client = await aiohttp_client(server, connector=conn)\n\n    with pytest.raises(aiohttp.ClientConnectionError):\n        await client.get(\"/\")\n\n    # second try should not hang\n    with pytest.raises(aiohttp.ClientConnectionError):\n        await client.get(\"/\")\n\n\nasync def test_await_after_cancelling(aiohttp_client: AiohttpClient) -> None:\n    loop = asyncio.get_event_loop()\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    client = await aiohttp_client(app)\n\n    fut1 = loop.create_future()\n    fut2 = loop.create_future()\n\n    async def fetch1() -> None:\n        async with client.get(\"/\") as resp:\n            assert resp.status == 200\n            fut1.set_result(None)\n            with pytest.raises(asyncio.CancelledError):\n                await fut2\n\n    async def fetch2() -> None:\n        await fut1\n        async with client.get(\"/\") as resp:\n            assert resp.status == 200\n\n    async def canceller() -> None:\n        await fut1\n        fut2.cancel()\n\n    await asyncio.gather(fetch1(), fetch2(), canceller())\n\n\nasync def test_async_payload_generator(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        assert data == b\"1234567890\" * 100\n        return web.Response()\n\n    app = web.Application()\n    app.add_routes([web.post(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async def gen() -> AsyncIterator[bytes]:\n        for i in range(100):\n            yield b\"1234567890\"\n\n    async with client.post(\"/\", data=gen()) as resp:\n        assert resp.status == 200\n\n\nasync def test_read_from_closed_response(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"data\")\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n\n    with pytest.raises(aiohttp.ClientConnectionError):\n        await resp.read()\n\n\nasync def test_read_from_closed_response2(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"data\")\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n        await resp.read()\n\n    with pytest.raises(aiohttp.ClientConnectionError):\n        await resp.read()\n\n\nasync def test_json_from_closed_response(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.json_response(42)\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n        await resp.read()\n\n    # Should not allow reading outside of resp context even when body is available.\n    with pytest.raises(aiohttp.ClientConnectionError):\n        await resp.json()\n\n\nasync def test_text_from_closed_response(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"data\")\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n        await resp.read()\n\n    # Should not allow reading outside of resp context even when body is available.\n    with pytest.raises(aiohttp.ClientConnectionError):\n        await resp.text()\n\n\nasync def test_read_after_catch_raise_for_status(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"data\", status=404)\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        with pytest.raises(ClientResponseError, match=\"404\"):\n            # Should not release response when in async with context.\n            resp.raise_for_status()\n\n        result = await resp.read()\n        assert result == b\"data\"\n\n\nasync def test_read_after_raise_outside_context(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"data\", status=404)\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    with pytest.raises(ClientResponseError, match=\"404\"):\n        # No async with, so should release and therefore read() will fail.\n        resp.raise_for_status()\n\n    with pytest.raises(aiohttp.ClientConnectionError, match=r\"^Connection closed$\"):\n        await resp.read()\n\n\nasync def test_read_from_closed_content(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"data\")\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n\n    with pytest.raises(aiohttp.ClientConnectionError):\n        await resp.content.readline()\n\n\nasync def test_read_timeout(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        await asyncio.sleep(5)\n        assert False\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    timeout = aiohttp.ClientTimeout(sock_read=0.1)\n    client = await aiohttp_client(app, timeout=timeout)\n\n    with pytest.raises(aiohttp.ServerTimeoutError):\n        await client.get(\"/\")\n\n\nasync def test_socket_timeout(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        await asyncio.sleep(5)\n        assert False\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    timeout = aiohttp.ClientTimeout(sock_read=0.1)\n    client = await aiohttp_client(app, timeout=timeout)\n\n    with pytest.raises(SocketTimeoutError):\n        await client.get(\"/\")\n\n\nasync def test_read_timeout_closes_connection(aiohttp_client: AiohttpClient) -> None:\n    request_count = 0\n\n    async def handler(request: web.Request) -> web.Response:\n        nonlocal request_count\n        request_count += 1\n        if request_count < 3:\n            await asyncio.sleep(0.5)\n        return web.Response(body=f\"request:{request_count}\")\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    timeout = aiohttp.ClientTimeout(total=0.1)\n    client = await aiohttp_client(app, timeout=timeout)\n    with pytest.raises(asyncio.TimeoutError):\n        await client.get(\"/\")\n\n    # Make sure its really closed\n    assert client.session.connector is not None\n    assert not client.session.connector._conns\n\n    with pytest.raises(asyncio.TimeoutError):\n        await client.get(\"/\")\n\n    # Make sure its really closed\n    assert not client.session.connector._conns\n    async with client.get(\"/\") as result:\n        assert await result.read() == b\"request:3\"\n\n    # Make sure its not closed\n    assert client.session.connector._conns\n\n\nasync def test_read_timeout_on_prepared_response(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        resp = aiohttp.web.StreamResponse()\n        await resp.prepare(request)\n        await asyncio.sleep(5)\n        assert False\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    timeout = aiohttp.ClientTimeout(sock_read=0.1)\n    client = await aiohttp_client(app, timeout=timeout)\n\n    with pytest.raises(aiohttp.ServerTimeoutError):\n        async with client.get(\"/\") as resp:\n            await resp.read()\n\n\nasync def test_timeout_with_full_buffer(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        \"\"\"Server response that never ends and always has more data available.\"\"\"\n        resp = web.StreamResponse()\n        await resp.prepare(request)\n        while True:\n            await resp.write(b\"1\" * 1000)\n            await asyncio.sleep(0.01)\n\n    async def request(client: TestClient[web.Request, web.Application]) -> None:\n        timeout = aiohttp.ClientTimeout(total=0.5)\n        async with client.get(\"/\", timeout=timeout) as resp:\n            with pytest.raises(asyncio.TimeoutError):\n                async for data in resp.content.iter_chunked(1):\n                    await asyncio.sleep(0.01)\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n    # wait_for() used just to ensure that a failing test doesn't hang.\n    await asyncio.wait_for(request(client), 1)\n\n\nasync def test_read_bufsize_session_default(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"1234567\")\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app, read_bufsize=2)\n\n    async with client.get(\"/\") as resp:\n        assert resp.content.get_read_buffer_limits() == (2, 4)\n\n\nasync def test_read_bufsize_explicit(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"1234567\")\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\", read_bufsize=4) as resp:\n        assert resp.content.get_read_buffer_limits() == (4, 8)\n\n\nasync def test_http_empty_data_text(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        ret = \"ok\" if data == b\"\" else \"fail\"\n        resp = web.Response(text=ret)\n        resp.headers[\"Content-Type\"] = request.headers[\"Content-Type\"]\n        return resp\n\n    app = web.Application()\n    app.add_routes([web.post(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async with client.post(\"/\", data=\"\") as resp:\n        assert resp.status == 200\n        assert await resp.text() == \"ok\"\n        assert resp.headers[\"Content-Type\"] == \"text/plain; charset=utf-8\"\n\n\nasync def test_max_field_size_session_default(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(headers={\"Custom\": \"x\" * 8182})\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.headers[\"Custom\"] == \"x\" * 8182\n\n\nasync def test_max_field_size_session_default_fail(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(headers={\"Custom\": \"x\" * 8191})\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n    with pytest.raises(aiohttp.ClientResponseError):\n        await client.get(\"/\")\n\n\nasync def test_max_field_size_session_explicit(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(headers={\"Custom\": \"x\" * 8192})\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app, max_field_size=8200)\n\n    async with client.get(\"/\") as resp:\n        assert resp.headers[\"Custom\"] == \"x\" * 8192\n\n\nasync def test_max_headers_session_default(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(headers={f\"Custom-{i}\": \"x\" for i in range(120)})\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.headers[\"Custom-119\"] == \"x\"\n\n\nasync def test_max_headers_session_default_fail(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(headers={f\"Custom-{i}\": \"x\" for i in range(129)})\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n    with pytest.raises(aiohttp.ClientResponseError):\n        await client.get(\"/\")\n\n\nasync def test_max_headers_session_explicit(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(headers={f\"Custom-{i}\": \"x\" for i in range(130)})\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app, max_headers=140)\n\n    async with client.get(\"/\") as resp:\n        assert resp.headers[\"Custom-129\"] == \"x\"\n\n\nasync def test_max_headers_request_explicit(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(headers={f\"Custom-{i}\": \"x\" for i in range(130)})\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\", max_headers=140) as resp:\n        assert resp.headers[\"Custom-129\"] == \"x\"\n\n\nasync def test_max_field_size_request_explicit(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(headers={\"Custom\": \"x\" * 8192})\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\", max_field_size=8200) as resp:\n        assert resp.headers[\"Custom\"] == \"x\" * 8192\n\n\nasync def test_max_line_size_session_default(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(status=200, reason=\"x\" * 8177)\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.reason == \"x\" * 8177\n\n\nasync def test_max_line_size_session_default_fail(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(status=200, reason=\"x\" * 8192)\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n    with pytest.raises(aiohttp.ClientResponseError):\n        await client.get(\"/\")\n\n\nasync def test_max_line_size_session_explicit(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(status=200, reason=\"x\" * 8197)\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app, max_line_size=8210)\n\n    async with client.get(\"/\") as resp:\n        assert resp.reason == \"x\" * 8197\n\n\nasync def test_max_line_size_request_explicit(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(status=200, reason=\"x\" * 8197)\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\", max_line_size=8210) as resp:\n        assert resp.reason == \"x\" * 8197\n\n\nasync def test_rejected_upload(\n    aiohttp_client: AiohttpClient, tmp_path: pathlib.Path\n) -> None:\n    async def ok_handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    async def not_ok_handler(request: web.Request) -> NoReturn:\n        raise web.HTTPBadRequest()\n\n    app = web.Application()\n    app.router.add_get(\"/ok\", ok_handler)\n    app.router.add_post(\"/not_ok\", not_ok_handler)\n    client = await aiohttp_client(app)\n\n    file_size_bytes = 1024 * 1024\n    file_path = tmp_path / \"uploaded.txt\"\n    file_path.write_text(\"0\" * file_size_bytes, encoding=\"utf8\")\n\n    with open(file_path, \"rb\") as file:\n        data = {\"file\": file}\n        async with client.post(\"/not_ok\", data=data) as resp_not_ok:\n            assert resp_not_ok.status == 400\n\n    async with client.get(\"/ok\", timeout=aiohttp.ClientTimeout(total=1)) as resp_ok:\n        assert resp_ok.status == 200\n\n\nasync def test_request_with_wrong_ssl_type(aiohttp_client: AiohttpClient) -> None:\n    app = web.Application()\n    session = await aiohttp_client(app)\n\n    with pytest.raises(TypeError, match=\"ssl should be SSLContext, Fingerprint, .*\"):\n        await session.get(\"/\", ssl=42)  # type: ignore[arg-type]\n\n\n@pytest.mark.parametrize(\n    (\"value\", \"exc_type\"),\n    [(42, TypeError), (\"InvalidUrl\", InvalidURL)],\n)\nasync def test_request_with_wrong_proxy(\n    aiohttp_client: AiohttpClient, value: int | str, exc_type: type[Exception]\n) -> None:\n    app = web.Application()\n    session = await aiohttp_client(app)\n\n    with pytest.raises(exc_type):\n        await session.get(\"/\", proxy=value)  # type: ignore[arg-type]\n\n\nasync def test_raise_for_status_is_none(aiohttp_client: AiohttpClient) -> None:\n    async def handler(_: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    session = await aiohttp_client(app, raise_for_status=None)\n\n    await session.get(\"/\")\n\n\nasync def test_header_too_large_error(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"By default when not specifying `max_field_size` requests should fail with a 400 status code.\"\"\"\n\n    async def handler(_: web.Request) -> web.Response:\n        return web.Response(headers={\"VeryLargeHeader\": \"x\" * 10000})\n\n    app = web.Application()\n    app.add_routes([web.get(\"/\", handler)])\n    client = await aiohttp_client(app)\n\n    with pytest.raises(\n        aiohttp.ClientResponseError, match=\"Got more than 8190 bytes*\"\n    ) as exc_info:\n        await client.get(\"/\")\n    assert exc_info.value.status == 400\n\n\nasync def test_exception_when_read_outside_of_session(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"1\" * 1000000)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n\n    server = await aiohttp_server(app)\n    async with aiohttp.ClientSession() as sess:\n        resp = await sess.get(server.make_url(\"/\"))\n\n    with pytest.raises(RuntimeError, match=\"Connection closed\"):\n        await resp.read()\n\n\nasync def test_content_length_limit_enforced(aiohttp_server: AiohttpServer) -> None:\n    \"\"\"Test that Content-Length header value limits the amount of data sent to the server.\"\"\"\n    received_data = bytearray()\n\n    async def handler(request: web.Request) -> web.Response:\n        # Read all data from the request and store it\n        data = await request.read()\n        received_data.extend(data)\n        return web.Response(text=\"OK\")\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n\n    server = await aiohttp_server(app)\n\n    # Create data larger than what we'll limit with Content-Length\n    data = b\"X\" * 1000\n    # Only send 500 bytes even though data is 1000 bytes\n    headers = {\"Content-Length\": \"500\"}\n\n    async with aiohttp.ClientSession() as session:\n        await session.post(server.make_url(\"/\"), data=data, headers=headers)\n\n    # Verify only 500 bytes (not the full 1000) were received by the server\n    assert len(received_data) == 500\n    assert received_data == b\"X\" * 500\n\n\nasync def test_content_length_limit_with_multiple_reads(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that Content-Length header value limits multi read data properly.\"\"\"\n    received_data = bytearray()\n\n    async def handler(request: web.Request) -> web.Response:\n        # Read all data from the request and store it\n        data = await request.read()\n        received_data.extend(data)\n        return web.Response(text=\"OK\")\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n\n    server = await aiohttp_server(app)\n\n    # Create an async generator of data\n    async def data_generator() -> AsyncIterator[bytes]:\n        yield b\"Chunk1\" * 100  # 600 bytes\n        yield b\"Chunk2\" * 100  # another 600 bytes\n\n    # Limit to 800 bytes even though we'd generate 1200 bytes\n    headers = {\"Content-Length\": \"800\"}\n\n    async with aiohttp.ClientSession() as session:\n        async with session.post(\n            server.make_url(\"/\"), data=data_generator(), headers=headers\n        ) as resp:\n            await resp.read()  # Ensure response is fully read and connection cleaned up\n\n    # Verify only 800 bytes (not the full 1200) were received by the server\n    assert len(received_data) == 800\n    # First chunk fully sent (600 bytes)\n    assert received_data.startswith(b\"Chunk1\" * 100)\n\n    # The rest should be from the second chunk (the exact split might vary by implementation)\n    assert b\"Chunk2\" in received_data  # Some part of the second chunk was sent\n    # 200 bytes from the second chunk\n    assert len(received_data) - len(b\"Chunk1\" * 100) == 200\n\n\nasync def test_post_connection_cleanup_with_bytesio(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that connections are properly cleaned up when using BytesIO data.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"\")\n\n    app = web.Application()\n    app.router.add_post(\"/hello\", handler)\n    client = await aiohttp_client(app)\n\n    # Test with direct bytes and BytesIO multiple times to ensure connection cleanup\n    for _ in range(10):\n        async with client.post(\n            \"/hello\",\n            data=b\"x\",\n            headers={\"Content-Length\": \"1\"},\n        ) as response:\n            response.raise_for_status()\n\n        assert client._session.connector is not None\n        assert len(client._session.connector._conns) == 1\n\n        x = io.BytesIO(b\"x\")\n        async with client.post(\n            \"/hello\",\n            data=x,\n            headers={\"Content-Length\": \"1\"},\n        ) as response:\n            response.raise_for_status()\n\n        assert len(client._session.connector._conns) == 1\n\n\nasync def test_post_connection_cleanup_with_file(\n    aiohttp_client: AiohttpClient, here: pathlib.Path\n) -> None:\n    \"\"\"Test that connections are properly cleaned up when using file data.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        await request.read()\n        return web.Response(body=b\"\")\n\n    app = web.Application()\n    app.router.add_post(\"/hello\", handler)\n    client = await aiohttp_client(app)\n\n    test_file = here / \"data.unknown_mime_type\"\n\n    # Test with direct bytes and file multiple times to ensure connection cleanup\n    for _ in range(10):\n        async with client.post(\n            \"/hello\",\n            data=b\"xx\",\n            headers={\"Content-Length\": \"2\"},\n        ) as response:\n            response.raise_for_status()\n\n        assert client._session.connector is not None\n        assert len(client._session.connector._conns) == 1\n        fh = await asyncio.get_running_loop().run_in_executor(\n            None, open, test_file, \"rb\"\n        )\n\n        async with client.post(\n            \"/hello\",\n            data=fh,\n            headers={\"Content-Length\": str(test_file.stat().st_size)},\n        ) as response:\n            response.raise_for_status()\n\n        assert len(client._session.connector._conns) == 1\n\n\nasync def test_post_content_exception_connection_kept(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that connections are kept after content.set_exception() with POST.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        await request.read()\n        return web.Response(\n            body=b\"x\" * 1000\n        )  # Larger response to ensure it's not pre-buffered\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    # POST request with body - connection should be closed after content exception\n    resp = await client.post(\"/\", data=b\"request body\")\n\n    with pytest.raises(RuntimeError):\n        async with resp:\n            assert resp.status == 200\n            resp.content.set_exception(RuntimeError(\"Simulated error\"))\n            await resp.read()\n\n    assert resp.closed\n\n    # Wait for any pending operations to complete\n    await resp.wait_for_close()\n\n    assert client._session.connector is not None\n    # Connection is kept because content.set_exception() is a client-side operation\n    # that doesn't affect the underlying connection state\n    assert len(client._session.connector._conns) == 1\n\n\nasync def test_network_error_connection_closed(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that connections are closed after network errors.\"\"\"\n\n    async def handler(request: web.Request) -> NoReturn:\n        # Read the request body\n        await request.read()\n\n        # Start sending response but close connection before completing\n        response = web.StreamResponse()\n        response.content_length = 1000  # Promise 1000 bytes\n        await response.prepare(request)\n\n        # Send partial data then force close the connection\n        await response.write(b\"x\" * 100)  # Only send 100 bytes\n        # Force close the transport to simulate network error\n        assert request.transport is not None\n        request.transport.close()\n        assert False, \"Will not return\"\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    # POST request that will fail due to network error\n    with pytest.raises(aiohttp.ClientPayloadError):\n        resp = await client.post(\"/\", data=b\"request body\")\n        async with resp:\n            await resp.read()  # This should fail\n\n    # Give event loop a chance to process connection cleanup\n    await asyncio.sleep(0)\n\n    assert client._session.connector is not None\n    # Connection should be closed due to network error\n    assert len(client._session.connector._conns) == 0\n\n\nasync def test_client_side_network_error_connection_closed(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that connections are closed after client-side network errors.\"\"\"\n    handler_done = asyncio.Event()\n\n    async def handler(request: web.Request) -> NoReturn:\n        # Read the request body\n        await request.read()\n\n        # Start sending a large response\n        response = web.StreamResponse()\n        response.content_length = 10000  # Promise 10KB\n        await response.prepare(request)\n\n        # Send some data\n        await response.write(b\"x\" * 1000)\n\n        # Keep the response open - we'll interrupt from client side\n        await asyncio.wait_for(handler_done.wait(), timeout=5.0)\n        assert False, \"Will not return\"\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    # POST request that will fail due to client-side network error\n    with pytest.raises(aiohttp.ClientPayloadError):\n        resp = await client.post(\"/\", data=b\"request body\")\n        async with resp:\n            # Simulate client-side network error by closing the transport\n            # This simulates connection reset, network failure, etc.\n            assert resp.connection is not None\n            assert resp.connection.protocol is not None\n            assert resp.connection.protocol.transport is not None\n            resp.connection.protocol.transport.close()\n\n            # This should fail with connection error\n            await resp.read()\n\n    # Signal handler to finish\n    handler_done.set()\n\n    # Give event loop a chance to process connection cleanup\n    await asyncio.sleep(0)\n\n    assert client._session.connector is not None\n    # Connection should be closed due to client-side network error\n    assert len(client._session.connector._conns) == 0\n\n\nasync def test_empty_response_non_chunked(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test non-chunked response with empty body.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        # Return empty response with Content-Length: 0\n        return web.Response(body=b\"\", headers={\"Content-Length\": \"0\"})\n\n    app = web.Application()\n    app.router.add_get(\"/empty\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/empty\")\n    assert resp.status == 200\n    assert resp.headers.get(\"Content-Length\") == \"0\"\n    data = await resp.read()\n    assert data == b\"\"\n    resp.close()\n\n\nasync def test_set_eof_on_empty_response(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that triggers set_eof() method.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        # Return response that completes immediately\n        return web.Response(status=204)  # No Content\n\n    app = web.Application()\n    app.router.add_get(\"/no-content\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/no-content\")\n    assert resp.status == 204\n    data = await resp.read()\n    assert data == b\"\"\n    resp.close()\n\n\nasync def test_bytes_payload_redirect(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that BytesPayload can be reused across redirects.\"\"\"\n    data_received = []\n\n    async def redirect_handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        data_received.append((\"redirect\", data))\n        # Use 307 to preserve POST method\n        raise web.HTTPTemporaryRedirect(\"/final_destination\")\n\n    async def final_handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        data_received.append((\"final\", data))\n        return web.Response(text=f\"Received: {data.decode()}\")\n\n    app = web.Application()\n    app.router.add_post(\"/redirect\", redirect_handler)\n    app.router.add_post(\"/final_destination\", final_handler)\n\n    client = await aiohttp_client(app)\n\n    payload_data = b\"test payload data\"\n    payload = BytesPayload(payload_data)\n\n    resp = await client.post(\"/redirect\", data=payload)\n    assert resp.status == 200\n    text = await resp.text()\n    assert text == \"Received: test payload data\"\n    # Both endpoints should have received the data\n    assert data_received == [(\"redirect\", payload_data), (\"final\", payload_data)]\n\n\nasync def test_string_payload_redirect(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that StringPayload can be reused across redirects.\"\"\"\n    data_received = []\n\n    async def redirect_handler(request: web.Request) -> web.Response:\n        data = await request.text()\n        data_received.append((\"redirect\", data))\n        # Use 307 to preserve POST method\n        raise web.HTTPTemporaryRedirect(\"/final_destination\")\n\n    async def final_handler(request: web.Request) -> web.Response:\n        data = await request.text()\n        data_received.append((\"final\", data))\n        return web.Response(text=f\"Received: {data}\")\n\n    app = web.Application()\n    app.router.add_post(\"/redirect\", redirect_handler)\n    app.router.add_post(\"/final_destination\", final_handler)\n\n    client = await aiohttp_client(app)\n\n    payload_data = \"test string payload\"\n    payload = StringPayload(payload_data)\n\n    resp = await client.post(\"/redirect\", data=payload)\n    assert resp.status == 200\n    text = await resp.text()\n    assert text == \"Received: test string payload\"\n    # Both endpoints should have received the data\n    assert data_received == [(\"redirect\", payload_data), (\"final\", payload_data)]\n\n\nasync def test_async_iterable_payload_redirect(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test redirecting consumed AsyncIterablePayload raises an error.\"\"\"\n    data_received = []\n\n    async def redirect_handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        data_received.append((\"redirect\", data))\n        # Use 307 to preserve POST method\n        raise web.HTTPTemporaryRedirect(\"/final_destination\")\n\n    async def final_handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        data_received.append((\"final\", data))\n        return web.Response(text=f\"Received: {data.decode()}\")\n\n    app = web.Application()\n    app.router.add_post(\"/redirect\", redirect_handler)\n    app.router.add_post(\"/final_destination\", final_handler)\n\n    client = await aiohttp_client(app)\n\n    chunks = [b\"chunk1\", b\"chunk2\", b\"chunk3\"]\n\n    async def async_gen() -> AsyncIterator[bytes]:\n        for chunk in chunks:\n            yield chunk\n\n    payload = AsyncIterablePayload(async_gen())\n\n    with pytest.raises(\n        aiohttp.ClientPayloadError,\n        match=\"Cannot follow redirect with a consumed request body\",\n    ):\n        await client.post(\"/redirect\", data=payload)\n\n    # Only the first endpoint should have received data.\n    expected_data = b\"\".join(chunks)\n    assert data_received == [(\"redirect\", expected_data)]\n\n\n@pytest.mark.parametrize(\"status\", (301, 302))\nasync def test_async_iterable_payload_redirect_non_post_301_302(\n    aiohttp_client: AiohttpClient, status: int\n) -> None:\n    \"\"\"Test consumed async iterable body raises on 301/302 for non-POST methods.\"\"\"\n    data_received = []\n\n    async def redirect_handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        data_received.append((\"redirect\", data))\n        return web.Response(status=status, headers={\"Location\": \"/final_destination\"})\n\n    app = web.Application()\n    app.router.add_put(\"/redirect\", redirect_handler)\n\n    client = await aiohttp_client(app)\n\n    chunks = [b\"chunk1\", b\"chunk2\", b\"chunk3\"]\n\n    async def async_gen() -> AsyncIterator[bytes]:\n        for chunk in chunks:\n            yield chunk\n\n    payload = AsyncIterablePayload(async_gen())\n\n    with pytest.raises(\n        aiohttp.ClientPayloadError,\n        match=\"Cannot follow redirect with a consumed request body\",\n    ):\n        await client.put(\"/redirect\", data=payload)\n\n    expected_data = b\"\".join(chunks)\n    assert data_received == [(\"redirect\", expected_data)]\n\n\nasync def test_buffered_reader_payload_redirect(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that BufferedReaderPayload can be reused across redirects.\"\"\"\n    data_received = []\n\n    async def redirect_handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        data_received.append((\"redirect\", data))\n        # Use 307 to preserve POST method\n        raise web.HTTPTemporaryRedirect(\"/final_destination\")\n\n    async def final_handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        data_received.append((\"final\", data))\n        return web.Response(text=f\"Received: {data.decode()}\")\n\n    app = web.Application()\n    app.router.add_post(\"/redirect\", redirect_handler)\n    app.router.add_post(\"/final_destination\", final_handler)\n\n    client = await aiohttp_client(app)\n\n    payload_data = b\"buffered reader payload\"\n    buffer = io.BufferedReader(io.BytesIO(payload_data))\n    payload = BufferedReaderPayload(buffer)\n\n    resp = await client.post(\"/redirect\", data=payload)\n    assert resp.status == 200\n    text = await resp.text()\n    assert text == \"Received: buffered reader payload\"\n    # Both endpoints should have received the data\n    assert data_received == [(\"redirect\", payload_data), (\"final\", payload_data)]\n\n\nasync def test_string_io_payload_redirect(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that StringIOPayload can be reused across redirects.\"\"\"\n    data_received = []\n\n    async def redirect_handler(request: web.Request) -> web.Response:\n        data = await request.text()\n        data_received.append((\"redirect\", data))\n        # Use 307 to preserve POST method\n        raise web.HTTPTemporaryRedirect(\"/final_destination\")\n\n    async def final_handler(request: web.Request) -> web.Response:\n        data = await request.text()\n        data_received.append((\"final\", data))\n        return web.Response(text=f\"Received: {data}\")\n\n    app = web.Application()\n    app.router.add_post(\"/redirect\", redirect_handler)\n    app.router.add_post(\"/final_destination\", final_handler)\n\n    client = await aiohttp_client(app)\n\n    payload_data = \"string io payload\"\n    string_io = io.StringIO(payload_data)\n    payload = StringIOPayload(string_io)\n\n    resp = await client.post(\"/redirect\", data=payload)\n    assert resp.status == 200\n    text = await resp.text()\n    assert text == \"Received: string io payload\"\n    # Both endpoints should have received the data\n    assert data_received == [(\"redirect\", payload_data), (\"final\", payload_data)]\n\n\nasync def test_bytes_io_payload_redirect(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that BytesIOPayload can be reused across redirects.\"\"\"\n    data_received = []\n\n    async def redirect_handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        data_received.append((\"redirect\", data))\n        # Use 307 to preserve POST method\n        raise web.HTTPTemporaryRedirect(\"/final_destination\")\n\n    async def final_handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        data_received.append((\"final\", data))\n        return web.Response(text=f\"Received: {data.decode()}\")\n\n    app = web.Application()\n    app.router.add_post(\"/redirect\", redirect_handler)\n    app.router.add_post(\"/final_destination\", final_handler)\n\n    client = await aiohttp_client(app)\n\n    payload_data = b\"bytes io payload\"\n    bytes_io = io.BytesIO(payload_data)\n    payload = BytesIOPayload(bytes_io)\n\n    resp = await client.post(\"/redirect\", data=payload)\n    assert resp.status == 200\n    text = await resp.text()\n    assert text == \"Received: bytes io payload\"\n    # Both endpoints should have received the data\n    assert data_received == [(\"redirect\", payload_data), (\"final\", payload_data)]\n\n\nasync def test_multiple_redirects_with_bytes_payload(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test BytesPayload with multiple redirects.\"\"\"\n    data_received = []\n\n    async def redirect1_handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        data_received.append((\"redirect1\", data))\n        # Use 307 to preserve POST method\n        raise web.HTTPTemporaryRedirect(\"/redirect2\")\n\n    async def redirect2_handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        data_received.append((\"redirect2\", data))\n        # Use 307 to preserve POST method\n        raise web.HTTPTemporaryRedirect(\"/final_destination\")\n\n    async def final_handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        data_received.append((\"final\", data))\n        return web.Response(text=f\"Received after 2 redirects: {data.decode()}\")\n\n    app = web.Application()\n    app.router.add_post(\"/redirect\", redirect1_handler)\n    app.router.add_post(\"/redirect2\", redirect2_handler)\n    app.router.add_post(\"/final_destination\", final_handler)\n\n    client = await aiohttp_client(app)\n\n    payload_data = b\"multi-redirect-test\"\n    payload = BytesPayload(payload_data)\n\n    resp = await client.post(\"/redirect\", data=payload)\n    assert resp.status == 200\n    text = await resp.text()\n    assert text == f\"Received after 2 redirects: {payload_data.decode()}\"\n    # All 3 endpoints should have received the same data\n    assert data_received == [\n        (\"redirect1\", payload_data),\n        (\"redirect2\", payload_data),\n        (\"final\", payload_data),\n    ]\n\n\nasync def test_redirect_with_empty_payload(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test redirects with empty payloads.\"\"\"\n    data_received = []\n\n    async def redirect_handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        data_received.append((\"redirect\", data))\n        # Use 307 to preserve POST method\n        raise web.HTTPTemporaryRedirect(\"/final_destination\")\n\n    async def final_handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        data_received.append((\"final\", data))\n        return web.Response(text=\"Done\")\n\n    app = web.Application()\n    app.router.add_post(\"/redirect\", redirect_handler)\n    app.router.add_post(\"/final_destination\", final_handler)\n\n    client = await aiohttp_client(app)\n\n    # Test with empty BytesPayload\n    payload = BytesPayload(b\"\")\n    resp = await client.post(\"/redirect\", data=payload)\n    assert resp.status == 200\n    assert data_received == [(\"redirect\", b\"\"), (\"final\", b\"\")]\n\n\nasync def test_redirect_preserves_content_type(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that content-type is preserved across redirects.\"\"\"\n    content_types = []\n\n    async def redirect_handler(request: web.Request) -> web.Response:\n        content_types.append((\"redirect\", request.content_type))\n        # Use 307 to preserve POST method\n        raise web.HTTPTemporaryRedirect(\"/final_destination\")\n\n    async def final_handler(request: web.Request) -> web.Response:\n        content_types.append((\"final\", request.content_type))\n        return web.Response(text=\"Done\")\n\n    app = web.Application()\n    app.router.add_post(\"/redirect\", redirect_handler)\n    app.router.add_post(\"/final_destination\", final_handler)\n\n    client = await aiohttp_client(app)\n\n    # StringPayload should set content-type with charset\n    payload = StringPayload(\"test data\")\n    resp = await client.post(\"/redirect\", data=payload)\n    assert resp.status == 200\n    # Both requests should have the same content type\n    assert len(content_types) == 2\n    assert content_types[0][1] == \"text/plain\"\n    assert content_types[1][1] == \"text/plain\"\n\n\nclass MockedBytesPayload(BytesPayload):\n    \"\"\"A BytesPayload that tracks whether close() was called.\"\"\"\n\n    def __init__(self, data: bytes) -> None:\n        super().__init__(data)\n        self.close_called = False\n\n    async def close(self) -> None:\n        self.close_called = True\n        await super().close()\n\n\nasync def test_too_many_redirects_closes_payload(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that TooManyRedirects exception closes the request payload.\"\"\"\n\n    async def redirect_handler(request: web.Request) -> web.Response:\n        # Read the payload to simulate server processing\n        await request.read()\n        count = int(request.match_info.get(\"count\", 0))\n        # Use 307 to preserve POST method\n        return web.Response(\n            status=307, headers={hdrs.LOCATION: f\"/redirect/{count + 1}\"}\n        )\n\n    app = web.Application()\n    app.router.add_post(r\"/redirect/{count:\\d+}\", redirect_handler)\n\n    client = await aiohttp_client(app)\n\n    # Create a mocked payload to verify close() is called\n    payload = MockedBytesPayload(b\"test payload\")\n\n    with pytest.raises(TooManyRedirects):\n        await client.post(\"/redirect/0\", data=payload, max_redirects=2)\n\n    assert (\n        payload.close_called\n    ), \"Payload.close() was not called when TooManyRedirects was raised\"\n\n\nasync def test_invalid_url_redirect_closes_payload(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that InvalidUrlRedirectClientError exception closes the request payload.\"\"\"\n\n    async def redirect_handler(request: web.Request) -> web.Response:\n        # Read the payload to simulate server processing\n        await request.read()\n        # Return an invalid URL that will cause ValueError in URL parsing\n        # Using a URL with invalid port that's out of range\n        return web.Response(\n            status=307, headers={hdrs.LOCATION: \"http://example.com:999999/path\"}\n        )\n\n    app = web.Application()\n    app.router.add_post(\"/redirect\", redirect_handler)\n\n    client = await aiohttp_client(app)\n\n    # Create a mocked payload to verify close() is called\n    payload = MockedBytesPayload(b\"test payload\")\n\n    with pytest.raises(\n        InvalidUrlRedirectClientError,\n        match=\"Server attempted redirecting to a location that does not look like a URL\",\n    ):\n        await client.post(\"/redirect\", data=payload)\n\n    assert (\n        payload.close_called\n    ), \"Payload.close() was not called when InvalidUrlRedirectClientError was raised\"\n\n\nasync def test_non_http_redirect_closes_payload(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that NonHttpUrlRedirectClientError exception closes the request payload.\"\"\"\n\n    async def redirect_handler(request: web.Request) -> web.Response:\n        # Read the payload to simulate server processing\n        await request.read()\n        # Return a non-HTTP scheme URL\n        return web.Response(\n            status=307, headers={hdrs.LOCATION: \"ftp://example.com/file\"}\n        )\n\n    app = web.Application()\n    app.router.add_post(\"/redirect\", redirect_handler)\n\n    client = await aiohttp_client(app)\n\n    # Create a mocked payload to verify close() is called\n    payload = MockedBytesPayload(b\"test payload\")\n\n    with pytest.raises(NonHttpUrlRedirectClientError):\n        await client.post(\"/redirect\", data=payload)\n\n    assert (\n        payload.close_called\n    ), \"Payload.close() was not called when NonHttpUrlRedirectClientError was raised\"\n\n\nasync def test_invalid_redirect_origin_closes_payload(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that InvalidUrlRedirectClientError exception (invalid origin) closes the request payload.\"\"\"\n\n    async def redirect_handler(request: web.Request) -> web.Response:\n        # Read the payload to simulate server processing\n        await request.read()\n        # Return a URL that will fail origin() check - using a relative URL without host\n        return web.Response(status=307, headers={hdrs.LOCATION: \"http:///path\"})\n\n    app = web.Application()\n    app.router.add_post(\"/redirect\", redirect_handler)\n\n    client = await aiohttp_client(app)\n\n    # Create a mocked payload to verify close() is called\n    payload = MockedBytesPayload(b\"test payload\")\n\n    with pytest.raises(\n        InvalidUrlRedirectClientError, match=\"Invalid redirect URL origin\"\n    ):\n        await client.post(\"/redirect\", data=payload)\n\n    assert (\n        payload.close_called\n    ), \"Payload.close() was not called when InvalidUrlRedirectClientError (invalid origin) was raised\"\n\n\nasync def test_amazon_like_cookie_scenario(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test real-world cookie scenario similar to Amazon.\"\"\"\n\n    class FakeResolver(AbstractResolver):\n        def __init__(self, port: int):\n            self._port = port\n\n        async def resolve(\n            self, host: str, port: int = 0, family: int = 0\n        ) -> list[ResolveResult]:\n            if host in (\"amazon.it\", \"www.amazon.it\"):\n                return [\n                    {\n                        \"hostname\": host,\n                        \"host\": \"127.0.0.1\",\n                        \"port\": self._port,\n                        \"family\": socket.AF_INET,\n                        \"proto\": 0,\n                        \"flags\": 0,\n                    }\n                ]\n            assert False, f\"Unexpected host: {host}\"\n\n        async def close(self) -> None:\n            \"\"\"Close the resolver if needed.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        response = web.Response(text=\"Login successful\")\n\n        # Simulate Amazon-like cookies from the issue\n        cookies = [\n            \"session-id=146-7423990-7621939; Domain=.amazon.it; \"\n            \"Expires=Mon, 31-May-3024 10:00:00 GMT; Path=/; \"\n            \"Secure; HttpOnly\",\n            \"session-id=147-8529641-8642103; Domain=.www.amazon.it; \"\n            \"Expires=Mon, 31-May-3024 10:00:00 GMT; Path=/; HttpOnly\",\n            \"session-id-time=2082758401l; Domain=.amazon.it; \"\n            \"Expires=Mon, 31-May-3024 10:00:00 GMT; Path=/; Secure\",\n            \"session-id-time=2082758402l; Domain=.www.amazon.it; \"\n            \"Expires=Mon, 31-May-3024 10:00:00 GMT; Path=/\",\n            \"ubid-acbit=257-7531983-5395266; Domain=.amazon.it; \"\n            \"Expires=Mon, 31-May-3024 10:00:00 GMT; Path=/; Secure\",\n            'x-acbit=\"KdvJzu8W@Fx6Jj3EuNFLuP0N7OtkuCfs\"; Version=1; '\n            \"Domain=.amazon.it; Path=/; Secure; HttpOnly\",\n            \"at-acbit=Atza|IwEBIM-gLr8; Domain=.amazon.it; \"\n            \"Expires=Mon, 31-May-3024 10:00:00 GMT; Path=/; \"\n            \"Secure; HttpOnly\",\n            'sess-at-acbit=\"4+6VzSJPHIFD/OqO264hFxIng8Y=\"; '\n            \"Domain=.amazon.it; Expires=Mon, 31-May-3024 10:00:00 GMT; \"\n            \"Path=/; Secure; HttpOnly\",\n            \"lc-acbit=it_IT; Domain=.amazon.it; \"\n            \"Expires=Mon, 31-May-3024 10:00:00 GMT; Path=/\",\n            \"i18n-prefs=EUR; Domain=.amazon.it; \"\n            \"Expires=Mon, 31-May-3024 10:00:00 GMT; Path=/\",\n            \"av-profile=null; Domain=.amazon.it; \"\n            \"Expires=Mon, 31-May-3024 10:00:00 GMT; Path=/; Secure\",\n            'user-pref-token=\"Am81ywsJ69xObBnuJ2FbilVH0mg=\"; '\n            \"Domain=.amazon.it; Path=/; Secure\",\n        ]\n\n        for cookie in cookies:\n            response.headers.add(\"Set-Cookie\", cookie)\n\n        return response\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n\n    # Get the test server\n    server = await aiohttp_client(app)\n    port = server.port\n\n    # Create a new client session with our fake resolver\n    resolver = FakeResolver(port)\n\n    async with (\n        aiohttp.TCPConnector(resolver=resolver, force_close=True) as connector,\n        aiohttp.ClientSession(connector=connector) as session,\n    ):\n        # Make request to www.amazon.it which will resolve to\n        # 127.0.0.1:port. This allows cookies for both .amazon.it\n        # and .www.amazon.it domains\n        resp = await session.get(f\"http://www.amazon.it:{port}/\")\n\n        # Check headers\n        cookie_headers = resp.headers.getall(\"Set-Cookie\")\n        assert (\n            len(cookie_headers) == 12\n        ), f\"Expected 12 headers, got {len(cookie_headers)}\"\n\n        # Check parsed cookies - SimpleCookie only keeps the last\n        # cookie with each name. So we expect 10 unique cookie names\n        # (not 12)\n        expected_cookie_names = {\n            \"session-id\",  # Will only have one\n            \"session-id-time\",  # Will only have one\n            \"ubid-acbit\",\n            \"x-acbit\",\n            \"at-acbit\",\n            \"sess-at-acbit\",\n            \"lc-acbit\",\n            \"i18n-prefs\",\n            \"av-profile\",\n            \"user-pref-token\",\n        }\n        assert set(resp.cookies.keys()) == expected_cookie_names\n        assert (\n            len(resp.cookies) == 10\n        ), f\"Expected 10 cookies in SimpleCookie, got {len(resp.cookies)}\"\n\n        # The important part: verify the session's cookie jar has\n        # all cookies. The cookie jar should have all 12 cookies,\n        # not just 10\n        jar_cookies = list(session.cookie_jar)\n        assert (\n            len(jar_cookies) == 12\n        ), f\"Expected 12 cookies in jar, got {len(jar_cookies)}\"\n\n        # Verify we have both session-id cookies with different domains\n        session_ids = [c for c in jar_cookies if c.key == \"session-id\"]\n        assert (\n            len(session_ids) == 2\n        ), f\"Expected 2 session-id cookies, got {len(session_ids)}\"\n\n        # Verify the domains are different\n        session_id_domains = {c[\"domain\"] for c in session_ids}\n        assert session_id_domains == {\n            \"amazon.it\",\n            \"www.amazon.it\",\n        }, f\"Got domains: {session_id_domains}\"\n\n        # Verify we have both session-id-time cookies with different\n        # domains\n        session_id_times = [c for c in jar_cookies if c.key == \"session-id-time\"]\n        assert (\n            len(session_id_times) == 2\n        ), f\"Expected 2 session-id-time cookies, got {len(session_id_times)}\"\n\n        # Now test that the raw headers were properly preserved\n        assert resp._raw_cookie_headers is not None\n        assert (\n            len(resp._raw_cookie_headers) == 12\n        ), \"All raw headers should be preserved\"\n\n\n@pytest.mark.parametrize(\"status\", (307, 308))\nasync def test_file_upload_307_308_redirect(\n    aiohttp_client: AiohttpClient, tmp_path: pathlib.Path, status: int\n) -> None:\n    \"\"\"Test that file uploads work correctly with 307/308 redirects.\n\n    This verifies that file payloads maintain correct Content-Length\n    on redirect by properly handling the file position.\n    \"\"\"\n    received_bodies: list[bytes] = []\n\n    async def handler(request: web.Request) -> web.Response:\n        # Store the body content\n        body = await request.read()\n        received_bodies.append(body)\n\n        if str(request.url.path).endswith(\"/\"):\n            # Redirect URLs ending with / to remove the trailing slash\n            return web.Response(\n                status=status,\n                headers={\n                    \"Location\": str(request.url.with_path(request.url.path.rstrip(\"/\")))\n                },\n            )\n\n        # Return success with the body size\n        return web.json_response(\n            {\n                \"received_size\": len(body),\n                \"content_length\": request.headers.get(\"Content-Length\"),\n            }\n        )\n\n    app = web.Application()\n    app.router.add_post(\"/upload/\", handler)\n    app.router.add_post(\"/upload\", handler)\n\n    client = await aiohttp_client(app)\n\n    # Create a test file\n    test_file = tmp_path / f\"test_upload_{status}.txt\"\n    content = b\"This is test file content for upload.\"\n    await asyncio.to_thread(test_file.write_bytes, content)\n    expected_size = len(content)\n\n    # Upload file to URL with trailing slash (will trigger redirect)\n    f = await asyncio.to_thread(open, test_file, \"rb\")\n    try:\n        async with client.post(\"/upload/\", data=f) as resp:\n            assert resp.status == 200\n            result = await resp.json()\n\n            # The server should receive the full file content\n            assert result[\"received_size\"] == expected_size\n            assert result[\"content_length\"] == str(expected_size)\n\n            # Both requests should have received the same content\n            assert len(received_bodies) == 2\n            assert received_bodies[0] == content  # First request\n            assert received_bodies[1] == content  # After redirect\n    finally:\n        await asyncio.to_thread(f.close)\n\n\n@pytest.mark.parametrize(\"status\", [301, 302])\n@pytest.mark.parametrize(\"method\", [\"PUT\", \"PATCH\", \"DELETE\"])\nasync def test_file_upload_301_302_redirect_non_post(\n    aiohttp_client: AiohttpClient, tmp_path: pathlib.Path, status: int, method: str\n) -> None:\n    \"\"\"Test that file uploads work correctly with 301/302 redirects for non-POST methods.\n\n    Per RFC 9110, 301/302 redirects should preserve the method and body for non-POST requests.\n    \"\"\"\n    received_bodies: list[bytes] = []\n\n    async def handler(request: web.Request) -> web.Response:\n        # Store the body content\n        body = await request.read()\n        received_bodies.append(body)\n\n        if str(request.url.path).endswith(\"/\"):\n            # Redirect URLs ending with / to remove the trailing slash\n            return web.Response(\n                status=status,\n                headers={\n                    \"Location\": str(request.url.with_path(request.url.path.rstrip(\"/\")))\n                },\n            )\n\n        # Return success with the body size\n        return web.json_response(\n            {\n                \"method\": request.method,\n                \"received_size\": len(body),\n                \"content_length\": request.headers.get(\"Content-Length\"),\n            }\n        )\n\n    app = web.Application()\n    app.router.add_route(method, \"/upload/\", handler)\n    app.router.add_route(method, \"/upload\", handler)\n\n    client = await aiohttp_client(app)\n\n    # Create a test file\n    test_file = tmp_path / f\"test_upload_{status}_{method.lower()}.txt\"\n    content = f\"Test {method} file content for {status} redirect.\".encode()\n    await asyncio.to_thread(test_file.write_bytes, content)\n    expected_size = len(content)\n\n    # Upload file to URL with trailing slash (will trigger redirect)\n    f = await asyncio.to_thread(open, test_file, \"rb\")\n    try:\n        async with client.request(method, \"/upload/\", data=f) as resp:\n            assert resp.status == 200\n            result = await resp.json()\n\n            # The server should receive the full file content after redirect\n            assert result[\"method\"] == method  # Method should be preserved\n            assert result[\"received_size\"] == expected_size\n            assert result[\"content_length\"] == str(expected_size)\n\n            # Both requests should have received the same content\n            assert len(received_bodies) == 2\n            assert received_bodies[0] == content  # First request\n            assert received_bodies[1] == content  # After redirect\n    finally:\n        await asyncio.to_thread(f.close)\n\n\nasync def test_file_upload_307_302_redirect_chain(\n    aiohttp_client: AiohttpClient, tmp_path: pathlib.Path\n) -> None:\n    \"\"\"Test that file uploads work correctly with 307->302->200 redirect chain.\n\n    This verifies that:\n    1. 307 preserves POST method and file body\n    2. 302 changes POST to GET and drops the body\n    3. No body leaks to the final GET request\n    \"\"\"\n    received_requests: list[dict[str, Any]] = []\n\n    async def handler(request: web.Request) -> web.Response:\n        # Store request details\n        body = await request.read()\n        received_requests.append(\n            {\n                \"path\": str(request.url.path),\n                \"method\": request.method,\n                \"body_size\": len(body),\n                \"content_length\": request.headers.get(\"Content-Length\"),\n            }\n        )\n\n        if request.url.path == \"/upload307\":\n            # First redirect: 307 should preserve method and body\n            return web.Response(status=307, headers={\"Location\": \"/upload302\"})\n        elif request.url.path == \"/upload302\":\n            # Second redirect: 302 should change POST to GET\n            return web.Response(status=302, headers={\"Location\": \"/final\"})\n        else:\n            # Final destination\n            return web.json_response(\n                {\n                    \"final_method\": request.method,\n                    \"final_body_size\": len(body),\n                    \"requests_received\": len(received_requests),\n                }\n            )\n\n    app = web.Application()\n    app.router.add_route(\"*\", \"/upload307\", handler)\n    app.router.add_route(\"*\", \"/upload302\", handler)\n    app.router.add_route(\"*\", \"/final\", handler)\n\n    client = await aiohttp_client(app)\n\n    # Create a test file\n    test_file = tmp_path / \"test_redirect_chain.txt\"\n    content = b\"Test file content that should not leak to GET request\"\n    await asyncio.to_thread(test_file.write_bytes, content)\n    expected_size = len(content)\n\n    # Upload file to URL that triggers 307->302->final redirect chain\n    f = await asyncio.to_thread(open, test_file, \"rb\")\n    try:\n        async with client.post(\"/upload307\", data=f) as resp:\n            assert resp.status == 200\n            result = await resp.json()\n\n            # Verify the redirect chain\n            assert len(resp.history) == 2\n            assert resp.history[0].status == 307\n            assert resp.history[1].status == 302\n\n            # Verify final request is GET with no body\n            assert result[\"final_method\"] == \"GET\"\n            assert result[\"final_body_size\"] == 0\n            assert result[\"requests_received\"] == 3\n\n            # Verify the request sequence\n            assert len(received_requests) == 3\n\n            # First request (307): POST with full body\n            assert received_requests[0][\"path\"] == \"/upload307\"\n            assert received_requests[0][\"method\"] == \"POST\"\n            assert received_requests[0][\"body_size\"] == expected_size\n            assert received_requests[0][\"content_length\"] == str(expected_size)\n\n            # Second request (302): POST with preserved body from 307\n            assert received_requests[1][\"path\"] == \"/upload302\"\n            assert received_requests[1][\"method\"] == \"POST\"\n            assert received_requests[1][\"body_size\"] == expected_size\n            assert received_requests[1][\"content_length\"] == str(expected_size)\n\n            # Third request (final): GET with no body (302 changed method and dropped body)\n            assert received_requests[2][\"path\"] == \"/final\"\n            assert received_requests[2][\"method\"] == \"GET\"\n            assert received_requests[2][\"body_size\"] == 0\n            assert received_requests[2][\"content_length\"] is None\n\n    finally:\n        await asyncio.to_thread(f.close)\n\n\nasync def test_stream_reader_total_raw_bytes(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test whether StreamReader.total_raw_bytes returns the number of bytes downloaded\"\"\"\n    source_data = b\"@dKal^pH>1h|YW1:c2J$\" * 4096\n\n    async def handler(request: web.Request) -> web.Response:\n        response = web.Response(body=source_data)\n        response.enable_compression()\n        return response\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n\n    client = await aiohttp_client(app)\n\n    # Check for decompressed data\n    async with client.get(\n        \"/\", headers={\"Accept-Encoding\": \"gzip\"}, auto_decompress=True\n    ) as resp:\n        assert resp.headers[\"Content-Encoding\"] == \"gzip\"\n        assert int(resp.headers[\"Content-Length\"]) < len(source_data)\n        data = await resp.content.read()\n        assert len(data) == len(source_data)\n        assert resp.content.total_raw_bytes == int(resp.headers[\"Content-Length\"])\n\n    # Check for compressed data\n    async with client.get(\n        \"/\", headers={\"Accept-Encoding\": \"gzip\"}, auto_decompress=False\n    ) as resp:\n        assert resp.headers[\"Content-Encoding\"] == \"gzip\"\n        data = await resp.content.read()\n        assert resp.content.total_raw_bytes == len(data)\n        assert resp.content.total_raw_bytes == int(resp.headers[\"Content-Length\"])\n\n    # Check for non-compressed data\n    async with client.get(\n        \"/\", headers={\"Accept-Encoding\": \"identity\"}, auto_decompress=True\n    ) as resp:\n        assert \"Content-Encoding\" not in resp.headers\n        data = await resp.content.read()\n        assert resp.content.total_raw_bytes == len(data)\n        assert resp.content.total_raw_bytes == int(resp.headers[\"Content-Length\"])\n"
  },
  {
    "path": "tests/test_client_middleware.py",
    "content": "\"\"\"Tests for client middleware.\"\"\"\n\nimport json\nimport socket\nfrom typing import NoReturn\n\nimport pytest\n\nfrom aiohttp import (\n    ClientError,\n    ClientHandlerType,\n    ClientRequest,\n    ClientResponse,\n    ClientSession,\n    ClientTimeout,\n    TCPConnector,\n    web,\n)\nfrom aiohttp.abc import ResolveResult\nfrom aiohttp.client_middlewares import build_client_middlewares\nfrom aiohttp.client_proto import ResponseHandler\nfrom aiohttp.pytest_plugin import AiohttpServer\nfrom aiohttp.resolver import ThreadedResolver\nfrom aiohttp.tracing import Trace\n\n\nclass BlockedByMiddleware(ClientError):\n    \"\"\"Custom exception for when middleware blocks a request.\"\"\"\n\n\nasync def test_client_middleware_called(aiohttp_server: AiohttpServer) -> None:\n    \"\"\"Test that client middleware is called.\"\"\"\n    middleware_called = False\n    request_count = 0\n\n    async def handler(request: web.Request) -> web.Response:\n        nonlocal request_count\n        request_count += 1\n        return web.Response(text=f\"OK {request_count}\")\n\n    async def test_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        nonlocal middleware_called\n        middleware_called = True\n        response = await handler(request)\n        return response\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(test_middleware,)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"OK 1\"\n\n    assert middleware_called is True\n    assert request_count == 1\n\n\nasync def test_client_middleware_retry(aiohttp_server: AiohttpServer) -> None:\n    \"\"\"Test that middleware can trigger retries.\"\"\"\n    request_count = 0\n\n    async def handler(request: web.Request) -> web.Response:\n        nonlocal request_count\n        request_count += 1\n        if request_count == 1:\n            return web.Response(status=503)\n        return web.Response(text=f\"OK {request_count}\")\n\n    async def retry_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        response = None\n        for _ in range(2):  # pragma: no branch\n            response = await handler(request)\n            if response.ok:\n                return response\n        assert False, \"not reachable in test\"\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(retry_middleware,)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"OK 2\"\n\n    assert request_count == 2\n\n\nasync def test_client_middleware_per_request(aiohttp_server: AiohttpServer) -> None:\n    \"\"\"Test that middleware can be specified per request.\"\"\"\n    session_middleware_called = False\n    request_middleware_called = False\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"OK\")\n\n    async def session_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        nonlocal session_middleware_called\n        session_middleware_called = True\n        response = await handler(request)\n        return response\n\n    async def request_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        nonlocal request_middleware_called\n        request_middleware_called = True\n        response = await handler(request)\n        return response\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    # Request with session middleware\n    async with ClientSession(middlewares=(session_middleware,)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n\n    assert session_middleware_called is True\n    assert request_middleware_called is False\n\n    # Reset flags\n    session_middleware_called = False\n\n    # Request with override middleware\n    async with ClientSession(middlewares=(session_middleware,)) as session:\n        async with session.get(\n            server.make_url(\"/\"), middlewares=(request_middleware,)\n        ) as resp:\n            assert resp.status == 200\n\n    assert session_middleware_called is False\n    assert request_middleware_called is True\n\n\nasync def test_multiple_client_middlewares(aiohttp_server: AiohttpServer) -> None:\n    \"\"\"Test that multiple middlewares are executed in order.\"\"\"\n    calls: list[str] = []\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"OK\")\n\n    async def middleware1(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        calls.append(\"before1\")\n        response = await handler(request)\n        calls.append(\"after1\")\n        return response\n\n    async def middleware2(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        calls.append(\"before2\")\n        response = await handler(request)\n        calls.append(\"after2\")\n        return response\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(middleware1, middleware2)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n\n    # Middlewares are applied in reverse order (like server middlewares)\n    # So middleware1 wraps middleware2\n    assert calls == [\"before1\", \"before2\", \"after2\", \"after1\"]\n\n\nasync def test_client_middleware_auth_example(aiohttp_server: AiohttpServer) -> None:\n    \"\"\"Test an authentication middleware example.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        auth_header = request.headers.get(\"Authorization\")\n        if auth_header == \"Bearer valid-token\":\n            return web.Response(text=\"Authenticated\")\n        return web.Response(status=401, text=\"Unauthorized\")\n\n    async def auth_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        # Add authentication header before request\n        request.headers[\"Authorization\"] = \"Bearer valid-token\"\n        response = await handler(request)\n        return response\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    # Without middleware - should fail\n    async with ClientSession() as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 401\n\n    # With middleware - should succeed\n    async with ClientSession(middlewares=(auth_middleware,)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"Authenticated\"\n\n\nasync def test_client_middleware_challenge_auth(aiohttp_server: AiohttpServer) -> None:\n    \"\"\"Test authentication middleware with challenge/response pattern like digest auth.\"\"\"\n    request_count = 0\n    challenge_token = \"challenge-123\"\n\n    async def handler(request: web.Request) -> web.Response:\n        nonlocal request_count\n        request_count += 1\n\n        auth_header = request.headers.get(\"Authorization\")\n\n        # First request - no auth header, return challenge\n        if request_count == 1 and not auth_header:\n            return web.Response(\n                status=401,\n                headers={\n                    \"WWW-Authenticate\": f'Custom realm=\"test\", nonce=\"{challenge_token}\"'\n                },\n            )\n\n        # Subsequent requests - check for correct auth with challenge\n        if auth_header == f'Custom response=\"{challenge_token}-secret\"':\n            return web.Response(text=\"Authenticated\")\n\n        assert False, \"Should not reach here - invalid auth scenario\"\n\n    async def challenge_auth_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        nonce: str | None = None\n        attempted: bool = False\n\n        while True:\n            # If we have challenge data from previous attempt, add auth header\n            if nonce and attempted:\n                request.headers[\"Authorization\"] = f'Custom response=\"{nonce}-secret\"'\n\n            response = await handler(request)\n\n            # If we get a 401 with challenge, store it and retry\n            if response.status == 401 and not attempted:\n                www_auth = response.headers.get(\"WWW-Authenticate\")\n                if www_auth and \"nonce=\" in www_auth:\n                    # Extract nonce from authentication header\n                    nonce_start = www_auth.find('nonce=\"') + 7\n                    nonce_end = www_auth.find('\"', nonce_start)\n                    nonce = www_auth[nonce_start:nonce_end]\n                    attempted = True\n                    continue\n                else:\n                    assert False, \"Should not reach here\"\n\n            return response\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(challenge_auth_middleware,)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"Authenticated\"\n\n    # Should have made 2 requests: initial and retry with auth\n    assert request_count == 2\n\n\nasync def test_client_middleware_multi_step_auth(aiohttp_server: AiohttpServer) -> None:\n    \"\"\"Test middleware with multi-step authentication flow.\"\"\"\n    auth_state: dict[str, int] = {}\n    middleware_state: dict[str, int | str | None] = {\n        \"step\": 0,\n        \"session\": None,\n        \"challenge\": None,\n    }\n\n    async def handler(request: web.Request) -> web.Response:\n        client_id = request.headers.get(\"X-Client-ID\", \"unknown\")\n        auth_header = request.headers.get(\"Authorization\")\n        step = auth_state.get(client_id, 0)\n\n        # Step 0: No auth, request client ID\n        if step == 0 and not auth_header:\n            auth_state[client_id] = 1\n            return web.Response(\n                status=401, headers={\"X-Auth-Step\": \"1\", \"X-Session\": \"session-123\"}\n            )\n\n        # Step 1: Has session, request credentials\n        if step == 1 and auth_header == \"Bearer session-123\":\n            auth_state[client_id] = 2\n            return web.Response(\n                status=401, headers={\"X-Auth-Step\": \"2\", \"X-Challenge\": \"challenge-456\"}\n            )\n\n        # Step 2: Has challenge response, authenticate\n        if step == 2 and auth_header == \"Bearer challenge-456-response\":\n            return web.Response(text=\"Authenticated\")\n\n        assert False, \"Should not reach here - invalid multi-step auth flow\"\n\n    async def multi_step_auth_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        request.headers[\"X-Client-ID\"] = \"test-client\"\n\n        for _ in range(3):\n            # Apply auth based on current state\n            if middleware_state[\"step\"] == 1 and middleware_state[\"session\"]:\n                request.headers[\"Authorization\"] = (\n                    f\"Bearer {middleware_state['session']}\"\n                )\n            elif middleware_state[\"step\"] == 2 and middleware_state[\"challenge\"]:\n                request.headers[\"Authorization\"] = (\n                    f\"Bearer {middleware_state['challenge']}-response\"\n                )\n\n            response = await handler(request)\n\n            # Handle multi-step auth flow\n            if response.status == 401:\n                auth_step = response.headers.get(\"X-Auth-Step\")\n\n                if auth_step == \"1\":\n                    # First step: store session token\n                    middleware_state[\"session\"] = response.headers.get(\"X-Session\")\n                    middleware_state[\"step\"] = 1\n                    continue\n\n                elif auth_step == \"2\":\n                    # Second step: store challenge\n                    middleware_state[\"challenge\"] = response.headers.get(\"X-Challenge\")\n                    middleware_state[\"step\"] = 2\n                    continue\n                else:\n                    assert False, \"Should not reach here\"\n\n            return response\n        # This should not be reached but keeps mypy happy\n        assert False, \"Should not reach here\"\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(multi_step_auth_middleware,)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"Authenticated\"\n\n\nasync def test_client_middleware_conditional_retry(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test middleware with conditional retry based on response content.\"\"\"\n    request_count = 0\n    token_state: dict[str, str | bool] = {\n        \"token\": \"old-token\",\n        \"refreshed\": False,\n    }\n\n    async def handler(request: web.Request) -> web.Response:\n        nonlocal request_count\n        request_count += 1\n\n        auth_token = request.headers.get(\"X-Auth-Token\")\n\n        if request_count == 1:\n            # First request returns expired token error\n            return web.json_response(\n                {\"error\": \"token_expired\", \"refresh_required\": True}, status=401\n            )\n\n        if auth_token == \"refreshed-token\":\n            return web.json_response({\"data\": \"success\"})\n\n        assert False, \"Should not reach here - invalid token refresh flow\"\n\n    async def token_refresh_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        for _ in range(2):\n            # Add token to request\n            request.headers[\"X-Auth-Token\"] = str(token_state[\"token\"])\n\n            response = await handler(request)\n\n            # Check if token needs refresh\n            if response.status == 401 and not token_state[\"refreshed\"]:\n                data = await response.json()\n                if data.get(\"error\") == \"token_expired\" and data.get(\n                    \"refresh_required\"\n                ):\n                    # Simulate token refresh\n                    token_state[\"token\"] = \"refreshed-token\"\n                    token_state[\"refreshed\"] = True\n                    continue\n                else:\n                    assert False, \"Should not reach here\"\n\n            return response\n        # This should not be reached but keeps mypy happy\n        assert False, \"Should not reach here\"\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(token_refresh_middleware,)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            data = await resp.json()\n            assert data == {\"data\": \"success\"}\n\n    assert request_count == 2  # Initial request + retry after refresh\n\n\nasync def test_build_client_middlewares_empty() -> None:\n    \"\"\"Test build_client_middlewares with empty middlewares.\"\"\"\n\n    async def handler(request: ClientRequest) -> NoReturn:\n        \"\"\"Dummy handler.\"\"\"\n        assert False\n\n    # Test empty case\n    result = build_client_middlewares(handler, ())\n    assert result is handler  # Should return handler unchanged\n\n\nasync def test_client_middleware_class_based_auth(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test middleware using class-based pattern with instance state.\"\"\"\n\n    class TokenAuthMiddleware:\n        \"\"\"Middleware that handles token-based authentication.\"\"\"\n\n        def __init__(self, token: str) -> None:\n            self.token = token\n            self.request_count = 0\n\n        async def __call__(\n            self, request: ClientRequest, handler: ClientHandlerType\n        ) -> ClientResponse:\n            self.request_count += 1\n            request.headers[\"Authorization\"] = f\"Bearer {self.token}\"\n            return await handler(request)\n\n    async def handler(request: web.Request) -> web.Response:\n        auth_header = request.headers.get(\"Authorization\")\n        if auth_header == \"Bearer test-token\":\n            return web.Response(text=\"Authenticated\")\n        assert False, \"Should not reach here - class auth should always have token\"\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    # Create middleware instance\n    auth_middleware = TokenAuthMiddleware(\"test-token\")\n\n    async with ClientSession(middlewares=(auth_middleware,)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"Authenticated\"\n\n    # Verify the middleware was called\n    assert auth_middleware.request_count == 1\n\n\nasync def test_client_middleware_stateful_retry(aiohttp_server: AiohttpServer) -> None:\n    \"\"\"Test retry middleware using class with state management.\"\"\"\n\n    class RetryMiddleware:\n        \"\"\"Middleware that retries failed requests with backoff.\"\"\"\n\n        def __init__(self, max_retries: int = 3) -> None:\n            self.max_retries = max_retries\n\n        async def __call__(\n            self, request: ClientRequest, handler: ClientHandlerType\n        ) -> ClientResponse:\n            retry_count = 0\n\n            while True:\n                response = await handler(request)\n\n                if response.status >= 500 and retry_count < self.max_retries:\n                    retry_count += 1\n                    continue\n\n                return response\n\n    request_count = 0\n\n    async def handler(request: web.Request) -> web.Response:\n        nonlocal request_count\n        request_count += 1\n\n        if request_count < 3:\n            return web.Response(status=503)\n        return web.Response(text=\"Success\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    retry_middleware = RetryMiddleware(max_retries=2)\n\n    async with ClientSession(middlewares=(retry_middleware,)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"Success\"\n\n    assert request_count == 3  # Initial + 2 retries\n\n\nasync def test_client_middleware_multiple_instances(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test using multiple instances of the same middleware class.\"\"\"\n\n    class HeaderMiddleware:\n        \"\"\"Middleware that adds a header with instance-specific value.\"\"\"\n\n        def __init__(self, header_name: str, header_value: str) -> None:\n            self.header_name = header_name\n            self.header_value = header_value\n            self.applied = False\n\n        async def __call__(\n            self, request: ClientRequest, handler: ClientHandlerType\n        ) -> ClientResponse:\n            self.applied = True\n            request.headers[self.header_name] = self.header_value\n            return await handler(request)\n\n    headers_received = {}\n\n    async def handler(request: web.Request) -> web.Response:\n        headers_received.update(dict(request.headers))\n        return web.Response(text=\"OK\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    # Create two instances with different headers\n    middleware1 = HeaderMiddleware(\"X-Custom-1\", \"value1\")\n    middleware2 = HeaderMiddleware(\"X-Custom-2\", \"value2\")\n\n    async with ClientSession(middlewares=(middleware1, middleware2)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n\n    # Both middlewares should have been applied\n    assert middleware1.applied is True\n    assert middleware2.applied is True\n    assert headers_received.get(\"X-Custom-1\") == \"value1\"\n    assert headers_received.get(\"X-Custom-2\") == \"value2\"\n\n\nasync def test_request_middleware_overrides_session_middleware_with_empty(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that passing empty middlewares tuple to a request disables session-level middlewares.\"\"\"\n    session_middleware_called = False\n\n    async def handler(request: web.Request) -> web.Response:\n        auth_header = request.headers.get(\"Authorization\")\n        if auth_header:\n            return web.Response(text=f\"Auth: {auth_header}\")\n        return web.Response(text=\"No auth\")\n\n    async def session_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        nonlocal session_middleware_called\n        session_middleware_called = True\n        request.headers[\"Authorization\"] = \"Bearer session-token\"\n        response = await handler(request)\n        return response\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    # Create session with middleware\n    async with ClientSession(middlewares=(session_middleware,)) as session:\n        # First request uses session middleware\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"Auth: Bearer session-token\"\n            assert session_middleware_called is True\n\n        # Reset flags\n        session_middleware_called = False\n\n        # Second request explicitly disables middlewares with empty tuple\n        async with session.get(server.make_url(\"/\"), middlewares=()) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"No auth\"\n            assert session_middleware_called is False\n\n\nasync def test_request_middleware_overrides_session_middleware_with_specific(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that passing specific middlewares to a request overrides session-level middlewares.\"\"\"\n    session_middleware_called = False\n    request_middleware_called = False\n\n    async def handler(request: web.Request) -> web.Response:\n        auth_header = request.headers[\"Authorization\"]\n        return web.Response(text=f\"Auth: {auth_header}\")\n\n    async def session_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        nonlocal session_middleware_called\n        session_middleware_called = True\n        request.headers[\"Authorization\"] = \"Bearer session-token\"\n        response = await handler(request)\n        return response\n\n    async def request_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        nonlocal request_middleware_called\n        request_middleware_called = True\n        request.headers[\"Authorization\"] = \"Bearer request-token\"\n        response = await handler(request)\n        return response\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    # Create session with middleware\n    async with ClientSession(middlewares=(session_middleware,)) as session:\n        # First request uses session middleware\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"Auth: Bearer session-token\"\n            assert session_middleware_called is True\n            assert request_middleware_called is False\n\n        # Reset flags\n        session_middleware_called = False\n        request_middleware_called = False\n\n        # Second request uses request-specific middleware\n        async with session.get(\n            server.make_url(\"/\"), middlewares=(request_middleware,)\n        ) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"Auth: Bearer request-token\"\n            assert session_middleware_called is False\n            assert request_middleware_called is True\n\n\n@pytest.mark.parametrize(\n    \"exception_class,match_text\",\n    [\n        (ValueError, \"Middleware error\"),\n        (ClientError, \"Client error from middleware\"),\n        (OSError, \"OS error from middleware\"),\n    ],\n)\nasync def test_client_middleware_exception_closes_connection(\n    aiohttp_server: AiohttpServer,\n    exception_class: type[Exception],\n    match_text: str,\n) -> None:\n    \"\"\"Test that connections are closed when middleware raises an exception.\"\"\"\n\n    async def handler(request: web.Request) -> NoReturn:\n        assert False, \"Handler should not be reached\"\n\n    async def failing_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> NoReturn:\n        # Raise exception before the handler is called\n        raise exception_class(match_text)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    # Create custom connector\n    connector = TCPConnector()\n\n    async with ClientSession(\n        connector=connector, middlewares=(failing_middleware,)\n    ) as session:\n        # Make request that should fail in middleware\n        with pytest.raises(exception_class, match=match_text):\n            await session.get(server.make_url(\"/\"))\n\n    # Check that the connector has no active connections\n    # If connections were properly closed, _conns should be empty\n    assert len(connector._conns) == 0\n\n    await connector.close()\n\n\nasync def test_client_middleware_blocks_connection_before_established(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that middleware can block connections before they are established.\"\"\"\n    blocked_hosts = {\"blocked.example.com\", \"evil.com\"}\n    connection_attempts: list[str] = []\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"Reached\")\n\n    async def blocking_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        # Record the connection attempt\n        connection_attempts.append(str(request.url))\n\n        # Block requests to certain hosts\n        if request.url.host in blocked_hosts:\n            raise BlockedByMiddleware(f\"Connection to {request.url.host} is blocked\")\n\n        # Allow the request to proceed\n        return await handler(request)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    connector = TCPConnector()\n    async with ClientSession(\n        connector=connector, middlewares=(blocking_middleware,)\n    ) as session:\n        # Test allowed request\n        allowed_url = server.make_url(\"/\")\n        async with session.get(allowed_url) as resp:\n            assert resp.status == 200\n            assert await resp.text() == \"Reached\"\n\n        # Test blocked request\n        with pytest.raises(BlockedByMiddleware) as exc_info:\n            # Use a fake URL that would fail DNS if connection was attempted\n            await session.get(\"https://blocked.example.com/\")\n\n        assert \"Connection to blocked.example.com is blocked\" in str(exc_info.value)\n\n        # Test another blocked host\n        with pytest.raises(BlockedByMiddleware) as exc_info:\n            await session.get(\"https://evil.com/path\")\n\n        assert \"Connection to evil.com is blocked\" in str(exc_info.value)\n\n    # Verify that connections were attempted in the correct order\n    assert len(connection_attempts) == 3\n    assert allowed_url.host\n\n    assert connection_attempts == [\n        str(server.make_url(\"/\")),\n        \"https://blocked.example.com/\",\n        \"https://evil.com/path\",\n    ]\n\n    # Check that no connections were leaked\n    assert len(connector._conns) == 0\n\n    await connector.close()\n\n\nasync def test_client_middleware_blocks_connection_without_dns_lookup(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that middleware prevents DNS lookups for blocked hosts.\"\"\"\n    blocked_hosts = {\"blocked.domain.tld\"}\n    dns_lookups_made: list[str] = []\n\n    # Create a simple server for the allowed request\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"OK\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    class TrackingResolver(ThreadedResolver):\n        async def resolve(\n            self,\n            hostname: str,\n            port: int = 0,\n            family: socket.AddressFamily = socket.AF_INET,\n        ) -> list[ResolveResult]:\n            dns_lookups_made.append(hostname)\n            return await super().resolve(hostname, port, family)\n\n    async def blocking_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        # Block requests to certain hosts before DNS lookup\n        if request.url.host in blocked_hosts:\n            raise BlockedByMiddleware(f\"Blocked by policy: {request.url.host}\")\n\n        return await handler(request)\n\n    resolver = TrackingResolver()\n    connector = TCPConnector(resolver=resolver)\n    async with ClientSession(\n        connector=connector, middlewares=(blocking_middleware,)\n    ) as session:\n        # Test blocked request to non-existent domain\n        with pytest.raises(BlockedByMiddleware) as exc_info:\n            await session.get(\"https://blocked.domain.tld/\")\n\n        assert \"Blocked by policy: blocked.domain.tld\" in str(exc_info.value)\n\n        # Verify that no DNS lookup was made for the blocked domain\n        assert \"blocked.domain.tld\" not in dns_lookups_made\n\n        # Test allowed request to existing server - this should trigger DNS lookup\n        async with session.get(f\"http://localhost:{server.port}\") as resp:\n            assert resp.status == 200\n\n        # Verify that DNS lookup was made for the allowed request\n        # The server might use a hostname that requires DNS resolution\n        assert len(dns_lookups_made) > 0\n\n        # Make sure blocked domain is still not in DNS lookups\n        assert \"blocked.domain.tld\" not in dns_lookups_made\n\n    # Clean up\n    await connector.close()\n\n\nasync def test_client_middleware_retry_reuses_connection(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that connections are reused when middleware performs retries.\"\"\"\n    request_count = 0\n\n    async def handler(request: web.Request) -> web.Response:\n        nonlocal request_count\n        request_count += 1\n        if request_count == 1:\n            return web.Response(status=400)  # First request returns 400 with no body\n        return web.Response(text=\"OK\")\n\n    class TrackingConnector(TCPConnector):\n        \"\"\"Connector that tracks connection attempts.\"\"\"\n\n        connection_attempts = 0\n\n        async def _create_connection(\n            self, req: ClientRequest, traces: list[\"Trace\"], timeout: \"ClientTimeout\"\n        ) -> ResponseHandler:\n            self.connection_attempts += 1\n            return await super()._create_connection(req, traces, timeout)\n\n    class RetryOnceMiddleware:\n        \"\"\"Middleware that retries exactly once.\"\"\"\n\n        def __init__(self) -> None:\n            self.attempt_count = 0\n\n        async def __call__(\n            self, request: ClientRequest, handler: ClientHandlerType\n        ) -> ClientResponse:\n            retry_count = 0\n            while True:\n                self.attempt_count += 1\n                response = await handler(request)\n                if response.status == 400 and retry_count == 0:\n                    retry_count += 1\n                    continue\n                return response\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    connector = TrackingConnector()\n    middleware = RetryOnceMiddleware()\n\n    async with ClientSession(connector=connector, middlewares=(middleware,)) as session:\n        # Make initial request\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"OK\"\n\n    # Should have made 2 request attempts (initial + 1 retry)\n    assert middleware.attempt_count == 2\n    # Should have created only 1 connection (reused on retry)\n    assert connector.connection_attempts == 1\n\n    await connector.close()\n\n\nasync def test_middleware_uses_session_avoids_recursion_with_path_check(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that middleware can avoid infinite recursion using a path check.\"\"\"\n    log_collector: list[dict[str, str]] = []\n\n    async def log_api_handler(request: web.Request) -> web.Response:\n        \"\"\"Handle log API requests.\"\"\"\n        data: dict[str, str] = await request.json()\n        log_collector.append(data)\n        return web.Response(text=\"OK\")\n\n    async def main_handler(request: web.Request) -> web.Response:\n        \"\"\"Handle main server requests.\"\"\"\n        return web.Response(text=f\"Hello from {request.path}\")\n\n    # Create log API server\n    log_app = web.Application()\n    log_app.router.add_post(\"/log\", log_api_handler)\n    log_server = await aiohttp_server(log_app)\n\n    # Create main server\n    main_app = web.Application()\n    main_app.router.add_get(\"/{path:.*}\", main_handler)\n    main_server = await aiohttp_server(main_app)\n\n    async def log_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        \"\"\"Log requests to external API, avoiding recursion with path check.\"\"\"\n        # Avoid infinite recursion by not logging requests to the /log endpoint\n        if request.url.path != \"/log\":\n            # Use the session from the request to make the logging call\n            async with request.session.post(\n                f\"http://localhost:{log_server.port}/log\",\n                json={\"method\": str(request.method), \"url\": str(request.url)},\n            ) as resp:\n                assert resp.status == 200\n\n        return await handler(request)\n\n    # Create session with the middleware\n    async with ClientSession(middlewares=(log_middleware,)) as session:\n        # Make request to main server - should be logged\n        async with session.get(main_server.make_url(\"/test\")) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"Hello from /test\"\n\n        # Make direct request to log API - should NOT be logged (avoid recursion)\n        async with session.post(\n            log_server.make_url(\"/log\"),\n            json={\"method\": \"DIRECT_POST\", \"url\": \"manual_test_entry\"},\n        ) as resp:\n            assert resp.status == 200\n\n    # Check logs\n    # The first request should be logged\n    # The second request (to /log) should also be logged but not the middleware's own log request\n    assert len(log_collector) == 2\n    assert log_collector[0][\"method\"] == \"GET\"\n    assert log_collector[0][\"url\"] == str(main_server.make_url(\"/test\"))\n    assert log_collector[1][\"method\"] == \"DIRECT_POST\"\n    assert log_collector[1][\"url\"] == \"manual_test_entry\"\n\n\nasync def test_middleware_uses_session_avoids_recursion_with_disabled_middleware(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that middleware can avoid infinite recursion by disabling middleware.\"\"\"\n    log_collector: list[dict[str, str]] = []\n    request_count = 0\n\n    async def log_api_handler(request: web.Request) -> web.Response:\n        \"\"\"Handle log API requests.\"\"\"\n        nonlocal request_count\n        request_count += 1\n        data: dict[str, str] = await request.json()\n        log_collector.append(data)\n        return web.Response(text=\"OK\")\n\n    async def main_handler(request: web.Request) -> web.Response:\n        \"\"\"Handle main server requests.\"\"\"\n        return web.Response(text=f\"Hello from {request.path}\")\n\n    # Create log API server\n    log_app = web.Application()\n    log_app.router.add_post(\"/log\", log_api_handler)\n    log_server = await aiohttp_server(log_app)\n\n    # Create main server\n    main_app = web.Application()\n    main_app.router.add_get(\"/{path:.*}\", main_handler)\n    main_server = await aiohttp_server(main_app)\n\n    async def log_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        \"\"\"Log all requests using session with disabled middleware.\"\"\"\n        # Use the session from the request to make the logging call\n        # Disable middleware to avoid infinite recursion\n        async with request.session.post(\n            f\"http://localhost:{log_server.port}/log\",\n            json={\"method\": str(request.method), \"url\": str(request.url)},\n            middlewares=(),  # This prevents infinite recursion\n        ) as resp:\n            assert resp.status == 200\n\n        return await handler(request)\n\n    # Create session with the middleware\n    async with ClientSession(middlewares=(log_middleware,)) as session:\n        # Make request to main server - should be logged\n        async with session.get(main_server.make_url(\"/test\")) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"Hello from /test\"\n\n        # Make another request - should also be logged\n        async with session.get(main_server.make_url(\"/another\")) as resp:\n            assert resp.status == 200\n\n    # Check logs - both requests should be logged\n    assert len(log_collector) == 2\n    assert log_collector[0][\"method\"] == \"GET\"\n    assert log_collector[0][\"url\"] == str(main_server.make_url(\"/test\"))\n    assert log_collector[1][\"method\"] == \"GET\"\n    assert log_collector[1][\"url\"] == str(main_server.make_url(\"/another\"))\n\n    # Ensure that log requests were made without the middleware\n    # (request_count equals number of logged requests, not infinite)\n    assert request_count == 2\n\n\nasync def test_middleware_can_check_request_body(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that middleware can check request body.\"\"\"\n    received_bodies: list[str] = []\n    received_headers: list[dict[str, str]] = []\n\n    async def handler(request: web.Request) -> web.Response:\n        \"\"\"Server handler that receives requests.\"\"\"\n        body = await request.text()\n        received_bodies.append(body)\n        received_headers.append(dict(request.headers))\n        return web.Response(text=\"OK\")\n\n    app = web.Application()\n    app.router.add_post(\"/api\", handler)\n    app.router.add_get(\"/api\", handler)  # Add GET handler too\n    server = await aiohttp_server(app)\n\n    class CustomAuth:\n        \"\"\"Middleware that follows the GitHub discussion pattern for authentication.\"\"\"\n\n        def __init__(self, secretkey: str) -> None:\n            self.secretkey = secretkey\n\n        def get_hash(self, request: ClientRequest) -> str:\n            data = request.body.decode(\"utf-8\") or \"{}\"\n\n            # Simulate authentication hash without using real crypto\n            return f\"SIGNATURE-{self.secretkey}-{len(data)}-{data[:10]}\"\n\n        async def __call__(\n            self, request: ClientRequest, handler: ClientHandlerType\n        ) -> ClientResponse:\n            request.headers[\"CUSTOM-AUTH\"] = self.get_hash(request)\n            return await handler(request)\n\n    middleware = CustomAuth(\"test-secret-key\")\n\n    async with ClientSession(middlewares=(middleware,)) as session:\n        # Test 1: Send JSON data with user/action\n        data1 = {\"user\": \"alice\", \"action\": \"login\"}\n        json_str1 = json.dumps(data1)\n        async with session.post(\n            server.make_url(\"/api\"),\n            data=json_str1,\n            headers={\"Content-Type\": \"application/json\"},\n        ) as resp:\n            assert resp.status == 200\n\n        # Test 2: Send JSON data with different fields\n        data2 = {\"user\": \"bob\", \"value\": 42}\n        json_str2 = json.dumps(data2)\n        async with session.post(\n            server.make_url(\"/api\"),\n            data=json_str2,\n            headers={\"Content-Type\": \"application/json\"},\n        ) as resp:\n            assert resp.status == 200\n\n        # Test 3: Send GET request with no body\n        async with session.get(server.make_url(\"/api\")) as resp:\n            assert resp.status == 200  # GET with empty body still should validate\n\n        # Test 4: Send plain text (non-JSON)\n        text_data = \"plain text body\"\n        async with session.post(\n            server.make_url(\"/api\"),\n            data=text_data,\n            headers={\"Content-Type\": \"text/plain\"},\n        ) as resp:\n            assert resp.status == 200\n\n    # Verify server received the correct headers with authentication\n    headers1 = received_headers[0]\n    assert (\n        headers1[\"CUSTOM-AUTH\"]\n        == f\"SIGNATURE-test-secret-key-{len(json_str1)}-{json_str1[:10]}\"\n    )\n\n    headers2 = received_headers[1]\n    assert (\n        headers2[\"CUSTOM-AUTH\"]\n        == f\"SIGNATURE-test-secret-key-{len(json_str2)}-{json_str2[:10]}\"\n    )\n\n    headers3 = received_headers[2]\n    # GET request with no body should have empty JSON body\n    assert headers3[\"CUSTOM-AUTH\"] == \"SIGNATURE-test-secret-key-2-{}\"\n\n    headers4 = received_headers[3]\n    assert (\n        headers4[\"CUSTOM-AUTH\"]\n        == f\"SIGNATURE-test-secret-key-{len(text_data)}-{text_data[:10]}\"\n    )\n\n    # Verify all responses were successful\n    assert received_bodies[0] == json_str1\n    assert received_bodies[1] == json_str2\n    assert received_bodies[2] == \"\"  # GET request has no body\n    assert received_bodies[3] == text_data\n\n\nasync def test_client_middleware_update_shorter_body(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that middleware can update request body using update_body method.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.text()\n        return web.Response(text=body)\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async def update_body_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        # Update the request body\n        await request.update_body(b\"short body\")\n        return await handler(request)\n\n    async with ClientSession(middlewares=(update_body_middleware,)) as session:\n        async with session.post(server.make_url(\"/\"), data=b\"original body\") as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"short body\"\n\n\nasync def test_client_middleware_update_longer_body(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that middleware can update request body using update_body method.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.text()\n        return web.Response(text=body)\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async def update_body_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        # Update the request body\n        await request.update_body(b\"much much longer body\")\n        return await handler(request)\n\n    async with ClientSession(middlewares=(update_body_middleware,)) as session:\n        async with session.post(server.make_url(\"/\"), data=b\"original body\") as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"much much longer body\"\n\n\nasync def test_client_middleware_update_string_body(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that middleware can update request body using update_body method.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.text()\n        return web.Response(text=body)\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async def update_body_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        # Update the request body\n        await request.update_body(\"this is a string\")\n        return await handler(request)\n\n    async with ClientSession(middlewares=(update_body_middleware,)) as session:\n        async with session.post(server.make_url(\"/\"), data=\"original string\") as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"this is a string\"\n\n\nasync def test_client_middleware_switch_types(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that middleware can update request body using update_body method.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.text()\n        return web.Response(text=body)\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async def update_body_middleware(\n        request: ClientRequest, handler: ClientHandlerType\n    ) -> ClientResponse:\n        # Update the request body\n        await request.update_body(\"now a string\")\n        return await handler(request)\n\n    async with ClientSession(middlewares=(update_body_middleware,)) as session:\n        async with session.post(server.make_url(\"/\"), data=b\"original bytes\") as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"now a string\"\n"
  },
  {
    "path": "tests/test_client_middleware_digest_auth.py",
    "content": "\"\"\"Test digest authentication middleware for aiohttp client.\"\"\"\n\nimport io\nimport re\nimport time\nfrom collections.abc import Generator\nfrom hashlib import md5, sha1\nfrom typing import Literal\nfrom unittest import mock\n\nimport pytest\nfrom yarl import URL\n\nfrom aiohttp import ClientSession, hdrs\nfrom aiohttp.client_exceptions import ClientError\nfrom aiohttp.client_middleware_digest_auth import (\n    _HEADER_PAIRS_PATTERN,\n    DigestAuthChallenge,\n    DigestAuthMiddleware,\n    DigestFunctions,\n    escape_quotes,\n    parse_header_pairs,\n    unescape_quotes,\n)\nfrom aiohttp.client_reqrep import ClientResponse\nfrom aiohttp.payload import BytesIOPayload\nfrom aiohttp.pytest_plugin import AiohttpServer\nfrom aiohttp.web import Application, Request, Response\n\n\n@pytest.fixture\ndef digest_auth_mw() -> DigestAuthMiddleware:\n    return DigestAuthMiddleware(\"user\", \"pass\")\n\n\n@pytest.fixture\ndef basic_challenge() -> DigestAuthChallenge:\n    \"\"\"Return a basic digest auth challenge with required fields only.\"\"\"\n    return DigestAuthChallenge(realm=\"test\", nonce=\"abc\")\n\n\n@pytest.fixture\ndef complete_challenge() -> DigestAuthChallenge:\n    \"\"\"Return a complete digest auth challenge with all fields.\"\"\"\n    return DigestAuthChallenge(\n        realm=\"test\", nonce=\"abc\", qop=\"auth\", algorithm=\"MD5\", opaque=\"xyz\"\n    )\n\n\n@pytest.fixture\ndef qop_challenge() -> DigestAuthChallenge:\n    \"\"\"Return a digest auth challenge with qop field.\"\"\"\n    return DigestAuthChallenge(realm=\"test\", nonce=\"abc\", qop=\"auth\")\n\n\n@pytest.fixture\ndef no_qop_challenge() -> DigestAuthChallenge:\n    \"\"\"Return a digest auth challenge without qop.\"\"\"\n    return DigestAuthChallenge(realm=\"test-realm\", nonce=\"testnonce\", algorithm=\"MD5\")\n\n\n@pytest.fixture\ndef auth_mw_with_challenge(\n    digest_auth_mw: DigestAuthMiddleware, complete_challenge: DigestAuthChallenge\n) -> DigestAuthMiddleware:\n    \"\"\"Return a digest auth middleware with pre-set challenge.\"\"\"\n    digest_auth_mw._challenge = complete_challenge\n    digest_auth_mw._last_nonce_bytes = complete_challenge[\"nonce\"].encode(\"utf-8\")\n    digest_auth_mw._nonce_count = 0\n    return digest_auth_mw\n\n\n@pytest.fixture\ndef mock_sha1_digest() -> Generator[mock.MagicMock, None, None]:\n    \"\"\"Mock SHA1 to return a predictable value for testing.\"\"\"\n    mock_digest = mock.MagicMock(spec=sha1())\n    mock_digest.hexdigest.return_value = \"deadbeefcafebabe\"\n    with mock.patch(\"hashlib.sha1\", return_value=mock_digest) as patched:\n        yield patched\n\n\n@pytest.fixture\ndef mock_md5_digest() -> Generator[mock.MagicMock, None, None]:\n    \"\"\"Mock MD5 to return a predictable value for testing.\"\"\"\n    mock_digest = mock.MagicMock(spec=md5())\n    mock_digest.hexdigest.return_value = \"abcdef0123456789\"\n    with mock.patch(\"hashlib.md5\", return_value=mock_digest) as patched:\n        yield patched\n\n\n@pytest.mark.parametrize(\n    (\"response_status\", \"headers\", \"expected_result\", \"expected_challenge\"),\n    [\n        # Valid digest with all fields\n        (\n            401,\n            {\n                \"www-authenticate\": 'Digest realm=\"test\", nonce=\"abc\", '\n                'qop=\"auth\", opaque=\"xyz\", algorithm=MD5'\n            },\n            True,\n            {\n                \"realm\": \"test\",\n                \"nonce\": \"abc\",\n                \"qop\": \"auth\",\n                \"algorithm\": \"MD5\",\n                \"opaque\": \"xyz\",\n            },\n        ),\n        # Valid digest without opaque\n        (\n            401,\n            {\"www-authenticate\": 'Digest realm=\"test\", nonce=\"abc\", qop=\"auth\"'},\n            True,\n            {\"realm\": \"test\", \"nonce\": \"abc\", \"qop\": \"auth\"},\n        ),\n        # Valid digest with empty realm (RFC 7616 Section 3.3 allows this)\n        (\n            401,\n            {\"www-authenticate\": 'Digest realm=\"\", nonce=\"abc\", qop=\"auth\"'},\n            True,\n            {\"realm\": \"\", \"nonce\": \"abc\", \"qop\": \"auth\"},\n        ),\n        # Non-401 status\n        (200, {}, False, {}),  # No challenge should be set\n    ],\n)\nasync def test_authenticate_scenarios(\n    digest_auth_mw: DigestAuthMiddleware,\n    response_status: int,\n    headers: dict[str, str],\n    expected_result: bool,\n    expected_challenge: dict[str, str],\n) -> None:\n    \"\"\"Test different authentication scenarios.\"\"\"\n    response = mock.MagicMock(spec=ClientResponse)\n    response.status = response_status\n    response.headers = headers\n\n    result = digest_auth_mw._authenticate(response)\n    assert result == expected_result\n\n    if expected_result:\n        challenge_dict = dict(digest_auth_mw._challenge)\n        for key, value in expected_challenge.items():\n            assert challenge_dict[key] == value\n\n\n@pytest.mark.parametrize(\n    (\"challenge\", \"expected_error\"),\n    [\n        (\n            DigestAuthChallenge(),\n            \"Malformed Digest auth challenge: Missing 'realm' parameter\",\n        ),\n        (\n            DigestAuthChallenge(nonce=\"abc\"),\n            \"Malformed Digest auth challenge: Missing 'realm' parameter\",\n        ),\n        (\n            DigestAuthChallenge(realm=\"test\"),\n            \"Malformed Digest auth challenge: Missing 'nonce' parameter\",\n        ),\n        (\n            DigestAuthChallenge(realm=\"test\", nonce=\"\"),\n            \"Security issue: Digest auth challenge contains empty 'nonce' value\",\n        ),\n    ],\n)\nasync def test_encode_validation_errors(\n    digest_auth_mw: DigestAuthMiddleware,\n    challenge: DigestAuthChallenge,\n    expected_error: str,\n) -> None:\n    \"\"\"Test validation errors when encoding digest auth headers.\"\"\"\n    digest_auth_mw._challenge = challenge\n    with pytest.raises(ClientError, match=expected_error):\n        await digest_auth_mw._encode(\"GET\", URL(\"http://example.com/resource\"), b\"\")\n\n\nasync def test_encode_digest_with_md5(\n    auth_mw_with_challenge: DigestAuthMiddleware,\n) -> None:\n    header = await auth_mw_with_challenge._encode(\n        \"GET\", URL(\"http://example.com/resource\"), b\"\"\n    )\n    assert header.startswith(\"Digest \")\n    assert 'username=\"user\"' in header\n    assert \"algorithm=MD5\" in header\n\n\n@pytest.mark.parametrize(\n    \"algorithm\", [\"MD5-SESS\", \"SHA-SESS\", \"SHA-256-SESS\", \"SHA-512-SESS\"]\n)\nasync def test_encode_digest_with_sess_algorithms(\n    digest_auth_mw: DigestAuthMiddleware,\n    qop_challenge: DigestAuthChallenge,\n    algorithm: str,\n) -> None:\n    \"\"\"Test that all session-based digest algorithms work correctly.\"\"\"\n    # Create a modified challenge with the test algorithm\n    challenge = qop_challenge.copy()\n    challenge[\"algorithm\"] = algorithm\n    digest_auth_mw._challenge = challenge\n\n    header = await digest_auth_mw._encode(\n        \"GET\", URL(\"http://example.com/resource\"), b\"\"\n    )\n    assert f\"algorithm={algorithm}\" in header\n\n\nasync def test_encode_unsupported_algorithm(\n    digest_auth_mw: DigestAuthMiddleware, basic_challenge: DigestAuthChallenge\n) -> None:\n    \"\"\"Test that unsupported algorithm raises ClientError.\"\"\"\n    # Create a modified challenge with an unsupported algorithm\n    challenge = basic_challenge.copy()\n    challenge[\"algorithm\"] = \"UNSUPPORTED\"\n    digest_auth_mw._challenge = challenge\n\n    with pytest.raises(ClientError, match=\"Unsupported hash algorithm\"):\n        await digest_auth_mw._encode(\"GET\", URL(\"http://example.com/resource\"), b\"\")\n\n\n@pytest.mark.parametrize(\"algorithm\", [\"MD5\", \"MD5-SESS\", \"SHA-256\"])\nasync def test_encode_algorithm_case_preservation_uppercase(\n    digest_auth_mw: DigestAuthMiddleware,\n    qop_challenge: DigestAuthChallenge,\n    algorithm: str,\n) -> None:\n    \"\"\"Test that uppercase algorithm case is preserved in the response header.\"\"\"\n    # Create a challenge with the specific algorithm case\n    challenge = qop_challenge.copy()\n    challenge[\"algorithm\"] = algorithm\n    digest_auth_mw._challenge = challenge\n\n    header = await digest_auth_mw._encode(\n        \"GET\", URL(\"http://example.com/resource\"), b\"\"\n    )\n\n    # The algorithm in the response should match the exact case from the challenge\n    assert f\"algorithm={algorithm}\" in header\n\n\n@pytest.mark.parametrize(\"algorithm\", [\"md5\", \"MD5-sess\", \"sha-256\"])\nasync def test_encode_algorithm_case_preservation_lowercase(\n    digest_auth_mw: DigestAuthMiddleware,\n    qop_challenge: DigestAuthChallenge,\n    algorithm: str,\n) -> None:\n    \"\"\"Test that lowercase/mixed-case algorithm is preserved in the response header.\"\"\"\n    # Create a challenge with the specific algorithm case\n    challenge = qop_challenge.copy()\n    challenge[\"algorithm\"] = algorithm\n    digest_auth_mw._challenge = challenge\n\n    header = await digest_auth_mw._encode(\n        \"GET\", URL(\"http://example.com/resource\"), b\"\"\n    )\n\n    # The algorithm in the response should match the exact case from the challenge\n    assert f\"algorithm={algorithm}\" in header\n    # Also verify it's not the uppercase version\n    assert f\"algorithm={algorithm.upper()}\" not in header\n\n\nasync def test_invalid_qop_rejected(\n    digest_auth_mw: DigestAuthMiddleware, basic_challenge: DigestAuthChallenge\n) -> None:\n    \"\"\"Test that invalid Quality of Protection values are rejected.\"\"\"\n    # Use bad QoP value to trigger error\n    challenge = basic_challenge.copy()\n    challenge[\"qop\"] = \"badvalue\"\n    challenge[\"algorithm\"] = \"MD5\"\n    digest_auth_mw._challenge = challenge\n\n    # This should raise an error about unsupported QoP\n    with pytest.raises(ClientError, match=\"Unsupported Quality of Protection\"):\n        await digest_auth_mw._encode(\"GET\", URL(\"http://example.com\"), b\"\")\n\n\ndef compute_expected_digest(\n    algorithm: str,\n    username: str,\n    password: str,\n    realm: str,\n    nonce: str,\n    uri: str,\n    method: str,\n    qop: str,\n    nc: str,\n    cnonce: str,\n    body: str = \"\",\n) -> str:\n    hash_fn = DigestFunctions[algorithm]\n\n    def H(x: str) -> str:\n        return hash_fn(x.encode()).hexdigest()\n\n    def KD(secret: str, data: str) -> str:\n        return H(f\"{secret}:{data}\")\n\n    A1 = f\"{username}:{realm}:{password}\"\n    HA1 = H(A1)\n\n    if algorithm.upper().endswith(\"-SESS\"):\n        HA1 = H(f\"{HA1}:{nonce}:{cnonce}\")\n\n    A2 = f\"{method}:{uri}\"\n    if \"auth-int\" in qop:\n        entity_hash = H(body)\n        A2 = f\"{A2}:{entity_hash}\"\n    HA2 = H(A2)\n\n    if qop:\n        return KD(HA1, f\"{nonce}:{nc}:{cnonce}:{qop}:{HA2}\")\n    else:\n        return KD(HA1, f\"{nonce}:{HA2}\")\n\n\n@pytest.mark.parametrize(\"qop\", [\"auth\", \"auth-int\", \"auth,auth-int\", \"\"])\n@pytest.mark.parametrize(\"algorithm\", sorted(DigestFunctions.keys()))\n@pytest.mark.parametrize(\n    (\"body\", \"body_str\"),\n    [\n        (b\"\", \"\"),  # Bytes case\n        (\n            BytesIOPayload(io.BytesIO(b\"this is a body\")),\n            \"this is a body\",\n        ),  # BytesIOPayload case\n    ],\n)\nasync def test_digest_response_exact_match(\n    qop: str,\n    algorithm: str,\n    body: Literal[b\"\"] | BytesIOPayload,\n    body_str: str,\n    mock_sha1_digest: mock.MagicMock,\n) -> None:\n    # Fixed input values\n    login = \"user\"\n    password = \"pass\"\n    realm = \"example.com\"\n    nonce = \"abc123nonce\"\n    cnonce = \"deadbeefcafebabe\"\n    nc = 1\n    ncvalue = f\"{nc+1:08x}\"\n    method = \"GET\"\n    uri = \"/secret\"\n    qop = \"auth-int\" if \"auth-int\" in qop else \"auth\"\n\n    # Create the auth object\n    auth = DigestAuthMiddleware(login, password)\n    auth._challenge = DigestAuthChallenge(\n        realm=realm, nonce=nonce, qop=qop, algorithm=algorithm\n    )\n    auth._last_nonce_bytes = nonce.encode(\"utf-8\")\n    auth._nonce_count = nc\n\n    header = await auth._encode(method, URL(f\"http://host{uri}\"), body)\n\n    # Get expected digest\n    expected = compute_expected_digest(\n        algorithm=algorithm,\n        username=login,\n        password=password,\n        realm=realm,\n        nonce=nonce,\n        uri=uri,\n        method=method,\n        qop=qop,\n        nc=ncvalue,\n        cnonce=cnonce,\n        body=body_str,\n    )\n\n    # Check that the response digest is exactly correct\n    assert f'response=\"{expected}\"' in header\n\n\n@pytest.mark.parametrize(\n    (\"header\", \"expected_result\"),\n    [\n        # Normal quoted values\n        (\n            'realm=\"example.com\", nonce=\"12345\", qop=\"auth\"',\n            {\"realm\": \"example.com\", \"nonce\": \"12345\", \"qop\": \"auth\"},\n        ),\n        # Unquoted values\n        (\n            \"realm=example.com, nonce=12345, qop=auth\",\n            {\"realm\": \"example.com\", \"nonce\": \"12345\", \"qop\": \"auth\"},\n        ),\n        # Mixed quoted/unquoted with commas in quoted values\n        (\n            'realm=\"ex,ample\", nonce=12345, qop=\"auth\", domain=\"/test\"',\n            {\n                \"realm\": \"ex,ample\",\n                \"nonce\": \"12345\",\n                \"qop\": \"auth\",\n                \"domain\": \"/test\",\n            },\n        ),\n        # Header with scheme\n        (\n            'Digest realm=\"example.com\", nonce=\"12345\", qop=\"auth\"',\n            {\"realm\": \"example.com\", \"nonce\": \"12345\", \"qop\": \"auth\"},\n        ),\n        # No spaces after commas\n        (\n            'realm=\"test\",nonce=\"123\",qop=\"auth\"',\n            {\"realm\": \"test\", \"nonce\": \"123\", \"qop\": \"auth\"},\n        ),\n        # Extra whitespace\n        (\n            'realm  =  \"test\"  ,  nonce  =  \"123\"',\n            {\"realm\": \"test\", \"nonce\": \"123\"},\n        ),\n        # Escaped quotes\n        (\n            'realm=\"test\\\\\"realm\", nonce=\"123\"',\n            {\"realm\": 'test\"realm', \"nonce\": \"123\"},\n        ),\n        # Single quotes (treated as regular chars)\n        (\n            \"realm='test', nonce=123\",\n            {\"realm\": \"'test'\", \"nonce\": \"123\"},\n        ),\n        # Empty header\n        (\"\", {}),\n    ],\n    ids=[\n        \"fully_quoted_header\",\n        \"unquoted_header\",\n        \"mixed_quoted_unquoted_with_commas\",\n        \"header_with_scheme\",\n        \"no_spaces_after_commas\",\n        \"extra_whitespace\",\n        \"escaped_quotes\",\n        \"single_quotes_as_regular_chars\",\n        \"empty_header\",\n    ],\n)\ndef test_parse_header_pairs(header: str, expected_result: dict[str, str]) -> None:\n    \"\"\"Test parsing HTTP header pairs with various formats.\"\"\"\n    result = parse_header_pairs(header)\n    assert result == expected_result\n\n\ndef test_digest_auth_middleware_callable(digest_auth_mw: DigestAuthMiddleware) -> None:\n    \"\"\"Test that DigestAuthMiddleware is callable.\"\"\"\n    assert callable(digest_auth_mw)\n\n\ndef test_middleware_invalid_login() -> None:\n    \"\"\"Test that invalid login values raise errors.\"\"\"\n    with pytest.raises(ValueError, match=\"None is not allowed as login value\"):\n        DigestAuthMiddleware(None, \"pass\")  # type: ignore[arg-type]\n\n    with pytest.raises(ValueError, match=\"None is not allowed as password value\"):\n        DigestAuthMiddleware(\"user\", None)  # type: ignore[arg-type]\n\n    with pytest.raises(ValueError, match=r\"A \\\":\\\" is not allowed in username\"):\n        DigestAuthMiddleware(\"user:name\", \"pass\")\n\n\nasync def test_escaping_quotes_in_auth_header() -> None:\n    \"\"\"Test that double quotes are properly escaped in auth header.\"\"\"\n    auth = DigestAuthMiddleware('user\"with\"quotes', \"pass\")\n    auth._challenge = DigestAuthChallenge(\n        realm='realm\"with\"quotes',\n        nonce='nonce\"with\"quotes',\n        qop=\"auth\",\n        algorithm=\"MD5\",\n        opaque='opaque\"with\"quotes',\n    )\n\n    header = await auth._encode(\"GET\", URL(\"http://example.com/path\"), b\"\")\n\n    # Check that quotes are escaped in the header\n    assert 'username=\"user\\\\\"with\\\\\"quotes\"' in header\n    assert 'realm=\"realm\\\\\"with\\\\\"quotes\"' in header\n    assert 'nonce=\"nonce\\\\\"with\\\\\"quotes\"' in header\n    assert 'opaque=\"opaque\\\\\"with\\\\\"quotes\"' in header\n\n\nasync def test_template_based_header_construction(\n    auth_mw_with_challenge: DigestAuthMiddleware,\n    mock_sha1_digest: mock.MagicMock,\n    mock_md5_digest: mock.MagicMock,\n) -> None:\n    \"\"\"Test that the template-based header construction works correctly.\"\"\"\n    header = await auth_mw_with_challenge._encode(\n        \"GET\", URL(\"http://example.com/test\"), b\"\"\n    )\n\n    # Split the header into scheme and parameters\n    scheme, params_str = header.split(\" \", 1)\n    assert scheme == \"Digest\"\n\n    # Parse the parameters into a dictionary\n    params = {\n        key: value[1:-1] if value.startswith('\"') and value.endswith('\"') else value\n        for key, value in (param.split(\"=\", 1) for param in params_str.split(\", \"))\n    }\n\n    # Check all required fields are present\n    assert \"username\" in params\n    assert \"realm\" in params\n    assert \"nonce\" in params\n    assert \"uri\" in params\n    assert \"response\" in params\n    assert \"algorithm\" in params\n    assert \"qop\" in params\n    assert \"nc\" in params\n    assert \"cnonce\" in params\n    assert \"opaque\" in params\n\n    # Check that fields are quoted correctly\n    quoted_fields = [\n        \"username\",\n        \"realm\",\n        \"nonce\",\n        \"uri\",\n        \"response\",\n        \"opaque\",\n        \"cnonce\",\n    ]\n    unquoted_fields = [\"algorithm\", \"qop\", \"nc\"]\n\n    # Re-check the original header for proper quoting\n    for field in quoted_fields:\n        assert f'{field}=\"{params[field]}\"' in header\n\n    for field in unquoted_fields:\n        assert f\"{field}={params[field]}\" in header\n\n    # Check specific values\n    assert params[\"username\"] == \"user\"\n    assert params[\"realm\"] == \"test\"\n    assert params[\"algorithm\"] == \"MD5\"\n    assert params[\"nc\"] == \"00000001\"  # nonce_count = 1 (incremented from 0)\n    assert params[\"uri\"] == \"/test\"  # path component of URL\n\n\n@pytest.mark.parametrize(\n    (\"test_string\", \"expected_escaped\", \"description\"),\n    [\n        ('value\"with\"quotes', 'value\\\\\"with\\\\\"quotes', \"Basic string with quotes\"),\n        (\"\", \"\", \"Empty string\"),\n        (\"no quotes\", \"no quotes\", \"String without quotes\"),\n        ('with\"one\"quote', 'with\\\\\"one\\\\\"quote', \"String with one quoted segment\"),\n        (\n            'many\"quotes\"in\"string',\n            'many\\\\\"quotes\\\\\"in\\\\\"string',\n            \"String with multiple quoted segments\",\n        ),\n        ('\"\"', '\\\\\"\\\\\"', \"Just double quotes\"),\n        ('\"', '\\\\\"', \"Single double quote\"),\n        ('already\\\\\"escaped', 'already\\\\\\\\\"escaped', \"Already escaped quotes\"),\n    ],\n)\ndef test_quote_escaping_functions(\n    test_string: str, expected_escaped: str, description: str\n) -> None:\n    \"\"\"Test that escape_quotes and unescape_quotes work correctly.\"\"\"\n    # Test escaping\n    escaped = escape_quotes(test_string)\n    assert escaped == expected_escaped\n\n    # Test unescaping (should return to original)\n    unescaped = unescape_quotes(escaped)\n    assert unescaped == test_string\n\n    # Test that they're inverse operations\n    assert unescape_quotes(escape_quotes(test_string)) == test_string\n\n\nasync def test_middleware_retry_on_401(\n    aiohttp_server: AiohttpServer, digest_auth_mw: DigestAuthMiddleware\n) -> None:\n    \"\"\"Test that the middleware retries on 401 errors.\"\"\"\n    request_count = 0\n\n    async def handler(request: Request) -> Response:\n        nonlocal request_count\n        request_count += 1\n\n        if request_count == 1:\n            # First request returns 401 with digest challenge\n            challenge = 'Digest realm=\"test\", nonce=\"abc123\", qop=\"auth\", algorithm=MD5'\n            return Response(\n                status=401,\n                headers={\"WWW-Authenticate\": challenge},\n                text=\"Unauthorized\",\n            )\n\n        # Second request should have Authorization header\n        auth_header = request.headers.get(hdrs.AUTHORIZATION)\n        if auth_header and auth_header.startswith(\"Digest \"):\n            # Return success response\n            return Response(text=\"OK\")\n\n        # This branch should not be reached in the tests\n        assert False, \"This branch should not be reached\"\n\n    app = Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(digest_auth_mw,)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text_content = await resp.text()\n            assert text_content == \"OK\"\n\n    assert request_count == 2  # Initial request + retry with auth\n\n\nasync def test_digest_auth_no_qop(\n    aiohttp_server: AiohttpServer,\n    digest_auth_mw: DigestAuthMiddleware,\n    no_qop_challenge: DigestAuthChallenge,\n    mock_sha1_digest: mock.MagicMock,\n) -> None:\n    \"\"\"Test digest auth with a server that doesn't provide a QoP parameter.\"\"\"\n    request_count = 0\n    realm = no_qop_challenge[\"realm\"]\n    nonce = no_qop_challenge[\"nonce\"]\n    algorithm = no_qop_challenge[\"algorithm\"]\n    username = \"user\"\n    password = \"pass\"\n    uri = \"/\"\n\n    async def handler(request: Request) -> Response:\n        nonlocal request_count\n        request_count += 1\n\n        if request_count == 1:\n            # First request returns 401 with digest challenge without qop\n            challenge = (\n                f'Digest realm=\"{realm}\", nonce=\"{nonce}\", algorithm={algorithm}'\n            )\n            return Response(\n                status=401,\n                headers={\"WWW-Authenticate\": challenge},\n                text=\"Unauthorized\",\n            )\n\n        # Second request should have Authorization header\n        auth_header = request.headers.get(hdrs.AUTHORIZATION)\n        assert auth_header and auth_header.startswith(\"Digest \")\n\n        # Successful auth should have no qop param\n        assert \"qop=\" not in auth_header\n        assert \"nc=\" not in auth_header\n        assert \"cnonce=\" not in auth_header\n\n        expected_digest = compute_expected_digest(\n            algorithm=algorithm,\n            username=username,\n            password=password,\n            realm=realm,\n            nonce=nonce,\n            uri=uri,\n            method=\"GET\",\n            qop=\"\",  # This is the key part - explicitly setting qop=\"\"\n            nc=\"\",  # Not needed for non-qop digest\n            cnonce=\"\",  # Not needed for non-qop digest\n        )\n        # We mock the cnonce, so we can check the expected digest\n        assert expected_digest in auth_header\n\n        return Response(text=\"OK\")\n\n    app = Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(digest_auth_mw,)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text_content = await resp.text()\n            assert text_content == \"OK\"\n\n    assert request_count == 2  # Initial request + retry with auth\n\n\nasync def test_digest_auth_without_opaque(\n    aiohttp_server: AiohttpServer, digest_auth_mw: DigestAuthMiddleware\n) -> None:\n    \"\"\"Test digest auth with a server that doesn't provide an opaque parameter.\"\"\"\n    request_count = 0\n\n    async def handler(request: Request) -> Response:\n        nonlocal request_count\n        request_count += 1\n\n        if request_count == 1:\n            # First request returns 401 with digest challenge without opaque\n            challenge = (\n                'Digest realm=\"test-realm\", nonce=\"testnonce\", '\n                'qop=\"auth\", algorithm=MD5'\n            )\n            return Response(\n                status=401,\n                headers={\"WWW-Authenticate\": challenge},\n                text=\"Unauthorized\",\n            )\n\n        # Second request should have Authorization header\n        auth_header = request.headers.get(hdrs.AUTHORIZATION)\n        assert auth_header and auth_header.startswith(\"Digest \")\n        # Successful auth should have no opaque param\n        assert \"opaque=\" not in auth_header\n\n        return Response(text=\"OK\")\n\n    app = Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(digest_auth_mw,)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text_content = await resp.text()\n            assert text_content == \"OK\"\n\n    assert request_count == 2  # Initial request + retry with auth\n\n\n@pytest.mark.parametrize(\n    \"www_authenticate\",\n    [\n        None,\n        \"DigestWithoutSpace\",\n        'Basic realm=\"test\"',\n        \"Digest \",\n        \"Digest =invalid, format\",\n    ],\n)\nasync def test_auth_header_no_retry(\n    aiohttp_server: AiohttpServer,\n    www_authenticate: str,\n    digest_auth_mw: DigestAuthMiddleware,\n) -> None:\n    \"\"\"Test that middleware doesn't retry with invalid WWW-Authenticate headers.\"\"\"\n    request_count = 0\n\n    async def handler(request: Request) -> Response:\n        nonlocal request_count\n        request_count += 1\n\n        # First (and only) request returns 401\n        headers = {}\n        if www_authenticate is not None:\n            headers[\"WWW-Authenticate\"] = www_authenticate\n\n        # Use a custom HTTPUnauthorized instead of the helper since\n        # we're specifically testing malformed headers\n        return Response(status=401, headers=headers, text=\"Unauthorized\")\n\n    app = Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(digest_auth_mw,)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 401\n\n    # No retry should happen\n    assert request_count == 1\n\n\nasync def test_direct_success_no_auth_needed(\n    aiohttp_server: AiohttpServer, digest_auth_mw: DigestAuthMiddleware\n) -> None:\n    \"\"\"Test middleware with a direct 200 response with no auth challenge.\"\"\"\n    request_count = 0\n\n    async def handler(request: Request) -> Response:\n        nonlocal request_count\n        request_count += 1\n\n        # Return success without auth challenge\n        return Response(text=\"OK\")\n\n    app = Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(digest_auth_mw,)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            text = await resp.text()\n            assert resp.status == 200\n            assert text == \"OK\"\n\n    # Verify only one request was made\n    assert request_count == 1\n\n\nasync def test_no_retry_on_second_401(\n    aiohttp_server: AiohttpServer, digest_auth_mw: DigestAuthMiddleware\n) -> None:\n    \"\"\"Test digest auth does not retry on second 401.\"\"\"\n    request_count = 0\n\n    async def handler(request: Request) -> Response:\n        nonlocal request_count\n        request_count += 1\n\n        # Always return 401 challenge\n        challenge = 'Digest realm=\"test\", nonce=\"abc123\", qop=\"auth\", algorithm=MD5'\n        return Response(\n            status=401,\n            headers={\"WWW-Authenticate\": challenge},\n            text=\"Unauthorized\",\n        )\n\n    app = Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    # Create a session that uses the digest auth middleware\n    async with ClientSession(middlewares=(digest_auth_mw,)) as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            await resp.text()\n            assert resp.status == 401\n\n    # Verify we made exactly 2 requests (initial + 1 retry)\n    assert request_count == 2\n\n\nasync def test_preemptive_auth_disabled(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that preemptive authentication can be disabled.\"\"\"\n    digest_auth_mw = DigestAuthMiddleware(\"user\", \"pass\", preemptive=False)\n    request_count = 0\n    auth_headers = []\n\n    async def handler(request: Request) -> Response:\n        nonlocal request_count\n        request_count += 1\n        auth_headers.append(request.headers.get(hdrs.AUTHORIZATION))\n\n        if not request.headers.get(hdrs.AUTHORIZATION):\n            # Return 401 with digest challenge\n            challenge = 'Digest realm=\"test\", nonce=\"abc123\", qop=\"auth\", algorithm=MD5'\n            return Response(\n                status=401,\n                headers={\"WWW-Authenticate\": challenge},\n                text=\"Unauthorized\",\n            )\n\n        return Response(text=\"OK\")\n\n    app = Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(digest_auth_mw,)) as session:\n        # First request will get 401 and store challenge\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"OK\"\n\n        # Second request should NOT send auth preemptively (preemptive=False)\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"OK\"\n\n    # With preemptive disabled, each request needs 401 challenge first\n    assert request_count == 4  # 2 requests * 2 (401 + retry)\n    assert auth_headers[0] is None  # First request has no auth\n    assert auth_headers[1] is not None  # Second request has auth after 401\n    assert auth_headers[2] is None  # Third request has no auth (preemptive disabled)\n    assert auth_headers[3] is not None  # Fourth request has auth after 401\n\n\nasync def test_preemptive_auth_with_stale_nonce(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test preemptive auth handles stale nonce responses correctly.\"\"\"\n    digest_auth_mw = DigestAuthMiddleware(\"user\", \"pass\", preemptive=True)\n    request_count = 0\n    current_nonce = 0\n\n    async def handler(request: Request) -> Response:\n        nonlocal request_count, current_nonce\n        request_count += 1\n\n        auth_header = request.headers.get(hdrs.AUTHORIZATION)\n\n        if not auth_header:\n            # First request without auth\n            current_nonce = 1\n            challenge = f'Digest realm=\"test\", nonce=\"nonce{current_nonce}\", qop=\"auth\", algorithm=MD5'\n            return Response(\n                status=401,\n                headers={\"WWW-Authenticate\": challenge},\n                text=\"Unauthorized\",\n            )\n\n        # For the second set of requests, always consider the first nonce stale\n        if request_count == 3 and current_nonce == 1:\n            # Stale nonce - request new auth with stale=true\n            current_nonce = 2\n            challenge = f'Digest realm=\"test\", nonce=\"nonce{current_nonce}\", qop=\"auth\", algorithm=MD5, stale=true'\n            return Response(\n                status=401,\n                headers={\"WWW-Authenticate\": challenge},\n                text=\"Unauthorized - Stale nonce\",\n            )\n\n        return Response(text=\"OK\")\n\n    app = Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(digest_auth_mw,)) as session:\n        # First request - will get 401, then retry with auth\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"OK\"\n\n        # Second request - will use preemptive auth with nonce1, get 401 stale, retry with nonce2\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            text = await resp.text()\n            assert text == \"OK\"\n\n    # Verify the expected flow:\n    # Request 1: no auth -> 401\n    # Request 2: retry with auth -> 200\n    # Request 3: preemptive auth with old nonce -> 401 stale\n    # Request 4: retry with new nonce -> 200\n    assert request_count == 4\n\n\nasync def test_preemptive_auth_updates_nonce_count(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that preemptive auth properly increments nonce count.\"\"\"\n    digest_auth_mw = DigestAuthMiddleware(\"user\", \"pass\", preemptive=True)\n    request_count = 0\n    nonce_counts = []\n\n    async def handler(request: Request) -> Response:\n        nonlocal request_count\n        request_count += 1\n\n        auth_header = request.headers.get(hdrs.AUTHORIZATION)\n\n        if not auth_header:\n            # First request without auth\n            challenge = 'Digest realm=\"test\", nonce=\"abc123\", qop=\"auth\", algorithm=MD5'\n            return Response(\n                status=401,\n                headers={\"WWW-Authenticate\": challenge},\n                text=\"Unauthorized\",\n            )\n\n        # Extract nc (nonce count) from auth header\n        nc_match = auth_header.split(\"nc=\")[1].split(\",\")[0].strip()\n        nonce_counts.append(nc_match)\n\n        return Response(text=\"OK\")\n\n    app = Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(digest_auth_mw,)) as session:\n        # Make multiple requests to see nonce count increment\n        for _ in range(3):\n            async with session.get(server.make_url(\"/\")) as resp:\n                assert resp.status == 200\n                await resp.text()\n\n    # First request has no auth, then gets 401 and retries with nc=00000001\n    # Second and third requests use preemptive auth with nc=00000002 and nc=00000003\n    assert len(nonce_counts) == 3\n    assert nonce_counts[0] == \"00000001\"\n    assert nonce_counts[1] == \"00000002\"\n    assert nonce_counts[2] == \"00000003\"\n\n\nasync def test_preemptive_auth_respects_protection_space(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that preemptive auth only applies to URLs within the protection space.\"\"\"\n    digest_auth_mw = DigestAuthMiddleware(\"user\", \"pass\", preemptive=True)\n    request_count = 0\n    auth_headers = []\n    requested_paths = []\n\n    async def handler(request: Request) -> Response:\n        nonlocal request_count\n        request_count += 1\n        auth_headers.append(request.headers.get(hdrs.AUTHORIZATION))\n        requested_paths.append(request.path)\n\n        if not request.headers.get(hdrs.AUTHORIZATION):\n            # Return 401 with digest challenge including domain parameter\n            challenge = 'Digest realm=\"test\", nonce=\"abc123\", qop=\"auth\", algorithm=MD5, domain=\"/api /admin\"'\n            return Response(\n                status=401,\n                headers={\"WWW-Authenticate\": challenge},\n                text=\"Unauthorized\",\n            )\n\n        return Response(text=\"OK\")\n\n    app = Application()\n    app.router.add_get(\"/api/endpoint\", handler)\n    app.router.add_get(\"/admin/panel\", handler)\n    app.router.add_get(\"/public/page\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(digest_auth_mw,)) as session:\n        # First request to /api/endpoint - should get 401 and retry with auth\n        async with session.get(server.make_url(\"/api/endpoint\")) as resp:\n            assert resp.status == 200\n\n        # Second request to /api/endpoint - should use preemptive auth (in protection space)\n        async with session.get(server.make_url(\"/api/endpoint\")) as resp:\n            assert resp.status == 200\n\n        # Third request to /admin/panel - should use preemptive auth (in protection space)\n        async with session.get(server.make_url(\"/admin/panel\")) as resp:\n            assert resp.status == 200\n\n        # Fourth request to /public/page - should NOT use preemptive auth (outside protection space)\n        async with session.get(server.make_url(\"/public/page\")) as resp:\n            assert resp.status == 200\n\n    # Verify auth headers\n    assert auth_headers[0] is None  # First request to /api/endpoint - no auth\n    assert auth_headers[1] is not None  # Retry with auth\n    assert (\n        auth_headers[2] is not None\n    )  # Second request to /api/endpoint - preemptive auth\n    assert auth_headers[3] is not None  # Request to /admin/panel - preemptive auth\n    assert auth_headers[4] is None  # First request to /public/page - no preemptive auth\n    assert auth_headers[5] is not None  # Retry with auth\n\n    # Verify paths\n    assert requested_paths == [\n        \"/api/endpoint\",  # Initial request\n        \"/api/endpoint\",  # Retry with auth\n        \"/api/endpoint\",  # Second request with preemptive auth\n        \"/admin/panel\",  # Request with preemptive auth\n        \"/public/page\",  # Initial request (no preemptive auth)\n        \"/public/page\",  # Retry with auth\n    ]\n\n\nasync def test_preemptive_auth_with_absolute_domain_uris(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test preemptive auth with absolute URIs in domain parameter.\"\"\"\n    digest_auth_mw = DigestAuthMiddleware(\"user\", \"pass\", preemptive=True)\n    request_count = 0\n    auth_headers = []\n\n    async def handler(request: Request) -> Response:\n        nonlocal request_count\n        request_count += 1\n        auth_headers.append(request.headers.get(hdrs.AUTHORIZATION))\n\n        if not request.headers.get(hdrs.AUTHORIZATION):\n            # Return 401 with digest challenge including absolute URI in domain\n            server_url = str(request.url.with_path(\"/protected\"))\n            challenge = f'Digest realm=\"test\", nonce=\"abc123\", qop=\"auth\", algorithm=MD5, domain=\"{server_url}\"'\n            return Response(\n                status=401,\n                headers={\"WWW-Authenticate\": challenge},\n                text=\"Unauthorized\",\n            )\n\n        return Response(text=\"OK\")\n\n    app = Application()\n    app.router.add_get(\"/protected/resource\", handler)\n    app.router.add_get(\"/unprotected/resource\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(digest_auth_mw,)) as session:\n        # First request to protected resource\n        async with session.get(server.make_url(\"/protected/resource\")) as resp:\n            assert resp.status == 200\n\n        # Second request to protected resource - should use preemptive auth\n        async with session.get(server.make_url(\"/protected/resource\")) as resp:\n            assert resp.status == 200\n\n        # Request to unprotected resource - should NOT use preemptive auth\n        async with session.get(server.make_url(\"/unprotected/resource\")) as resp:\n            assert resp.status == 200\n\n    # Verify auth pattern\n    assert auth_headers[0] is None  # First request - no auth\n    assert auth_headers[1] is not None  # Retry with auth\n    assert auth_headers[2] is not None  # Second request - preemptive auth\n    assert auth_headers[3] is None  # Unprotected resource - no preemptive auth\n    assert auth_headers[4] is not None  # Retry with auth\n\n\nasync def test_preemptive_auth_without_domain_uses_origin(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that preemptive auth without domain parameter applies to entire origin.\"\"\"\n    digest_auth_mw = DigestAuthMiddleware(\"user\", \"pass\", preemptive=True)\n    request_count = 0\n    auth_headers = []\n\n    async def handler(request: Request) -> Response:\n        nonlocal request_count\n        request_count += 1\n        auth_headers.append(request.headers.get(hdrs.AUTHORIZATION))\n\n        if not request.headers.get(hdrs.AUTHORIZATION):\n            # Return 401 with digest challenge without domain parameter\n            challenge = 'Digest realm=\"test\", nonce=\"abc123\", qop=\"auth\", algorithm=MD5'\n            return Response(\n                status=401,\n                headers={\"WWW-Authenticate\": challenge},\n                text=\"Unauthorized\",\n            )\n\n        return Response(text=\"OK\")\n\n    app = Application()\n    app.router.add_get(\"/path1\", handler)\n    app.router.add_get(\"/path2\", handler)\n    server = await aiohttp_server(app)\n\n    async with ClientSession(middlewares=(digest_auth_mw,)) as session:\n        # First request\n        async with session.get(server.make_url(\"/path1\")) as resp:\n            assert resp.status == 200\n\n        # Second request to different path - should still use preemptive auth\n        async with session.get(server.make_url(\"/path2\")) as resp:\n            assert resp.status == 200\n\n    # Verify auth pattern\n    assert auth_headers[0] is None  # First request - no auth\n    assert auth_headers[1] is not None  # Retry with auth\n    assert (\n        auth_headers[2] is not None\n    )  # Second request - preemptive auth (entire origin)\n\n\n@pytest.mark.parametrize(\n    (\"status\", \"headers\", \"expected\"),\n    [\n        (200, {}, False),\n        (401, {\"www-authenticate\": \"\"}, False),\n        (401, {\"www-authenticate\": \"DigestWithoutSpace\"}, False),\n        (401, {\"www-authenticate\": \"Basic realm=test\"}, False),\n        (401, {\"www-authenticate\": \"Digest \"}, False),\n        (401, {\"www-authenticate\": \"Digest =invalid, format\"}, False),\n    ],\n    ids=[\n        \"different_status_code\",\n        \"empty_www_authenticate_header\",\n        \"no_space_after_scheme\",\n        \"different_scheme\",\n        \"empty_parameters\",\n        \"malformed_parameters\",\n    ],\n)\ndef test_authenticate_with_malformed_headers(\n    digest_auth_mw: DigestAuthMiddleware,\n    status: int,\n    headers: dict[str, str],\n    expected: bool,\n) -> None:\n    \"\"\"Test _authenticate method with various edge cases.\"\"\"\n    response = mock.MagicMock(spec=ClientResponse)\n    response.status = status\n    response.headers = headers\n\n    result = digest_auth_mw._authenticate(response)\n    assert result == expected\n\n\n@pytest.mark.parametrize(\n    (\"protection_space_url\", \"request_url\", \"expected\"),\n    [\n        # Exact match\n        (\"http://example.com/app1\", \"http://example.com/app1\", True),\n        # Path with trailing slash should match\n        (\"http://example.com/app1\", \"http://example.com/app1/\", True),\n        # Subpaths should match\n        (\"http://example.com/app1\", \"http://example.com/app1/resource\", True),\n        (\"http://example.com/app1\", \"http://example.com/app1/sub/path\", True),\n        # Should NOT match different paths that start with same prefix\n        (\"http://example.com/app1\", \"http://example.com/app1xx\", False),\n        (\"http://example.com/app1\", \"http://example.com/app123\", False),\n        # Protection space with trailing slash\n        (\"http://example.com/app1/\", \"http://example.com/app1/\", True),\n        (\"http://example.com/app1/\", \"http://example.com/app1/resource\", True),\n        (\n            \"http://example.com/app1/\",\n            \"http://example.com/app1\",\n            False,\n        ),  # No trailing slash\n        # Root protection space\n        (\"http://example.com/\", \"http://example.com/\", True),\n        (\"http://example.com/\", \"http://example.com/anything\", True),\n        (\"http://example.com/\", \"http://example.com\", False),  # No trailing slash\n        # Different origins should not match\n        (\"http://example.com/app1\", \"https://example.com/app1\", False),\n        (\"http://example.com/app1\", \"http://other.com/app1\", False),\n        (\"http://example.com:8080/app1\", \"http://example.com/app1\", False),\n    ],\n    ids=[\n        \"exact_match\",\n        \"path_with_trailing_slash\",\n        \"subpath_match\",\n        \"deep_subpath_match\",\n        \"no_match_app1xx\",\n        \"no_match_app123\",\n        \"protection_with_slash_exact\",\n        \"protection_with_slash_subpath\",\n        \"protection_with_slash_no_match_without\",\n        \"root_protection_exact\",\n        \"root_protection_subpath\",\n        \"root_protection_no_match_without_slash\",\n        \"different_scheme\",\n        \"different_host\",\n        \"different_port\",\n    ],\n)\ndef test_in_protection_space(\n    digest_auth_mw: DigestAuthMiddleware,\n    protection_space_url: str,\n    request_url: str,\n    expected: bool,\n) -> None:\n    \"\"\"Test _in_protection_space method with various URL patterns.\"\"\"\n    digest_auth_mw._protection_space = [protection_space_url]\n    result = digest_auth_mw._in_protection_space(URL(request_url))\n    assert result == expected\n\n\ndef test_in_protection_space_multiple_spaces(\n    digest_auth_mw: DigestAuthMiddleware,\n) -> None:\n    \"\"\"Test _in_protection_space with multiple protection spaces.\"\"\"\n    digest_auth_mw._protection_space = [\n        \"http://example.com/api\",\n        \"http://example.com/admin/\",\n        \"http://example.com/secure/area\",\n    ]\n\n    # Test various URLs\n    assert digest_auth_mw._in_protection_space(URL(\"http://example.com/api\")) is True\n    assert digest_auth_mw._in_protection_space(URL(\"http://example.com/api/v1\")) is True\n    assert (\n        digest_auth_mw._in_protection_space(URL(\"http://example.com/admin/panel\"))\n        is True\n    )\n    assert (\n        digest_auth_mw._in_protection_space(\n            URL(\"http://example.com/secure/area/resource\")\n        )\n        is True\n    )\n\n    # These should not match\n    assert digest_auth_mw._in_protection_space(URL(\"http://example.com/apiv2\")) is False\n    assert (\n        digest_auth_mw._in_protection_space(URL(\"http://example.com/admin\")) is False\n    )  # No trailing slash\n    assert (\n        digest_auth_mw._in_protection_space(URL(\"http://example.com/secure\")) is False\n    )\n    assert digest_auth_mw._in_protection_space(URL(\"http://example.com/other\")) is False\n\n\nasync def test_case_sensitive_algorithm_server(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test authentication with a server that requires exact algorithm case matching.\n\n    This simulates servers like Prusa printers that expect the algorithm\n    to be returned with the exact same case as sent in the challenge.\n    \"\"\"\n    digest_auth_mw = DigestAuthMiddleware(\"testuser\", \"testpass\")\n    request_count = 0\n    auth_algorithms: list[str] = []\n\n    async def handler(request: Request) -> Response:\n        nonlocal request_count\n        request_count += 1\n\n        if not (auth_header := request.headers.get(hdrs.AUTHORIZATION)):\n            # Send challenge with lowercase-sess algorithm (like Prusa)\n            challenge = 'Digest realm=\"Administrator\", nonce=\"test123\", qop=\"auth\", algorithm=\"MD5-sess\", opaque=\"xyz123\"'\n            return Response(\n                status=401,\n                headers={\"WWW-Authenticate\": challenge},\n                text=\"Unauthorized\",\n            )\n\n        # Extract algorithm from auth response\n        algo_match = re.search(r\"algorithm=([^,\\s]+)\", auth_header)\n        assert algo_match is not None\n        auth_algorithms.append(algo_match.group(1))\n\n        # Case-sensitive server: only accept exact case match\n        assert \"algorithm=MD5-sess\" in auth_header\n        return Response(text=\"Success\")\n\n    app = Application()\n    app.router.add_get(\"/api/test\", handler)\n    server = await aiohttp_server(app)\n\n    async with (\n        ClientSession(middlewares=(digest_auth_mw,)) as session,\n        session.get(server.make_url(\"/api/test\")) as resp,\n    ):\n        assert resp.status == 200\n        text = await resp.text()\n        assert text == \"Success\"\n\n    # Verify the middleware preserved the exact algorithm case\n    assert request_count == 2  # Initial 401 + successful retry\n    assert len(auth_algorithms) == 1\n    assert auth_algorithms[0] == \"MD5-sess\"  # Not \"MD5-SESS\"\n\n\ndef test_regex_performance() -> None:\n    \"\"\"Test that the regex pattern doesn't suffer from ReDoS issues.\"\"\"\n    REGEX_TIME_THRESHOLD_SECONDS = 0.08\n    value = \"0\" * 54773 + \"\\\\0=a\"\n\n    start = time.perf_counter()\n    matches = _HEADER_PAIRS_PATTERN.findall(value)\n    elapsed = time.perf_counter() - start\n\n    # If this is taking more time, there's probably a performance/ReDoS issue.\n    assert elapsed < REGEX_TIME_THRESHOLD_SECONDS, (\n        f\"Regex took {elapsed * 1000:.1f}ms, \"\n        f\"expected <{REGEX_TIME_THRESHOLD_SECONDS * 1000:.0f}ms - potential ReDoS issue\"\n    )\n    # This example shouldn't produce a match either.\n    assert not matches\n"
  },
  {
    "path": "tests/test_client_proto.py",
    "content": "import asyncio\nfrom unittest import mock\n\nfrom multidict import CIMultiDict\nfrom pytest_mock import MockerFixture\nfrom yarl import URL\n\nfrom aiohttp import http\nfrom aiohttp.client_exceptions import ClientOSError, ServerDisconnectedError\nfrom aiohttp.client_proto import ResponseHandler\nfrom aiohttp.client_reqrep import ClientResponse\nfrom aiohttp.helpers import TimerNoop\nfrom aiohttp.http_parser import RawResponseMessage\n\n\nasync def test_force_close(loop: asyncio.AbstractEventLoop) -> None:\n    \"\"\"Ensure that the force_close method sets the should_close attribute to True.\n\n    This is used externally in aiodocker\n    https://github.com/aio-libs/aiodocker/issues/920\n    \"\"\"\n    proto = ResponseHandler(loop=loop)\n    proto.force_close()\n    assert proto.should_close\n\n\nasync def test_oserror(loop: asyncio.AbstractEventLoop) -> None:\n    proto = ResponseHandler(loop=loop)\n    transport = mock.Mock()\n    proto.connection_made(transport)\n    proto.connection_lost(OSError())\n\n    assert proto.should_close\n    assert isinstance(proto.exception(), ClientOSError)\n\n\nasync def test_pause_resume_on_error(loop: asyncio.AbstractEventLoop) -> None:\n    proto = ResponseHandler(loop=loop)\n    transport = mock.Mock()\n    proto.connection_made(transport)\n\n    proto.pause_reading()\n    assert proto._reading_paused\n\n    proto.resume_reading()\n    assert not proto._reading_paused\n\n\nasync def test_client_proto_bad_message(loop: asyncio.AbstractEventLoop) -> None:\n    proto = ResponseHandler(loop=loop)\n    transport = mock.Mock()\n    proto.connection_made(transport)\n    proto.set_response_params()\n\n    proto.data_received(b\"HTTP\\r\\n\\r\\n\")\n    assert proto.should_close\n    assert transport.close.called\n    assert isinstance(proto.exception(), http.HttpProcessingError)\n\n\nasync def test_uncompleted_message(loop: asyncio.AbstractEventLoop) -> None:\n    proto = ResponseHandler(loop=loop)\n    transport = mock.Mock()\n    proto.connection_made(transport)\n    proto.set_response_params(read_until_eof=True)\n\n    proto.data_received(\n        b\"HTTP/1.1 301 Moved Permanently\\r\\nLocation: http://python.org/\"\n    )\n    proto.connection_lost(None)\n\n    exc = proto.exception()\n    assert isinstance(exc, ServerDisconnectedError)\n    assert isinstance(exc.message, RawResponseMessage)\n    assert exc.message.code == 301\n    assert dict(exc.message.headers) == {\"Location\": \"http://python.org/\"}\n\n\nasync def test_data_received_after_close(loop: asyncio.AbstractEventLoop) -> None:\n    proto = ResponseHandler(loop=loop)\n    transport = mock.Mock()\n    proto.connection_made(transport)\n    proto.set_response_params(read_until_eof=True)\n    proto.close()\n    assert transport.close.called\n    transport.close.reset_mock()\n    proto.data_received(b\"HTTP\\r\\n\\r\\n\")\n    assert proto.should_close\n    assert not transport.close.called\n    assert isinstance(proto.exception(), http.HttpProcessingError)\n\n\nasync def test_multiple_responses_one_byte_at_a_time(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    proto = ResponseHandler(loop=loop)\n    proto.connection_made(mock.Mock())\n    conn = mock.Mock(protocol=proto)\n    proto.set_response_params(read_until_eof=True)\n\n    for _ in range(2):\n        messages = (\n            b\"HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\n\\r\\nab\"\n            b\"HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\n\\r\\ncd\"\n            b\"HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\n\\r\\nef\"\n        )\n        for i in range(len(messages)):\n            proto.data_received(messages[i : i + 1])\n\n        expected = [b\"ab\", b\"cd\", b\"ef\"]\n        url = URL(\"http://def-cl-resp.org\")\n        for payload in expected:\n            response = ClientResponse(\n                \"get\",\n                url,\n                writer=mock.Mock(),\n                continue100=None,\n                timer=TimerNoop(),\n                traces=[],\n                loop=loop,\n                session=mock.Mock(),\n                request_headers=CIMultiDict[str](),\n                original_url=url,\n            )\n            await response.start(conn)\n            await response.read() == payload\n\n\nasync def test_unexpected_exception_during_data_received(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    proto = ResponseHandler(loop=loop)\n\n    class PatchableHttpResponseParser(http.HttpResponseParser):\n        \"\"\"Subclass of HttpResponseParser to make it patchable.\"\"\"\n\n    with mock.patch(\n        \"aiohttp.client_proto.HttpResponseParser\", PatchableHttpResponseParser\n    ):\n        proto.connection_made(mock.Mock())\n        conn = mock.Mock(protocol=proto)\n        proto.set_response_params(read_until_eof=True)\n        proto.data_received(b\"HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\n\\r\\nab\")\n        url = URL(\"http://def-cl-resp.org\")\n        response = ClientResponse(\n            \"get\",\n            url,\n            writer=mock.Mock(),\n            continue100=None,\n            timer=TimerNoop(),\n            traces=[],\n            loop=loop,\n            session=mock.Mock(),\n            request_headers=CIMultiDict[str](),\n            original_url=url,\n        )\n        await response.start(conn)\n        await response.read() == b\"ab\"\n        with mock.patch.object(proto._parser, \"feed_data\", side_effect=ValueError):\n            proto.data_received(b\"HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\n\\r\\ncd\")\n\n    assert isinstance(proto.exception(), http.HttpProcessingError)\n\n\nasync def test_client_protocol_readuntil_eof(loop: asyncio.AbstractEventLoop) -> None:\n    proto = ResponseHandler(loop=loop)\n    transport = mock.Mock()\n    proto.connection_made(transport)\n    conn = mock.Mock()\n    conn.protocol = proto\n\n    proto.data_received(b\"HTTP/1.1 200 Ok\\r\\n\\r\\n\")\n\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=mock.Mock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    proto.set_response_params(read_until_eof=True)\n    await response.start(conn)\n\n    assert not response.content.is_eof()\n\n    proto.data_received(b\"0000\")\n    data = await response.content.readany()\n    assert data == b\"0000\"\n\n    proto.data_received(b\"1111\")\n    data = await response.content.readany()\n    assert data == b\"1111\"\n\n    proto.connection_lost(None)\n    assert response.content.is_eof()\n\n\nasync def test_empty_data(loop: asyncio.AbstractEventLoop) -> None:\n    proto = ResponseHandler(loop=loop)\n    proto.data_received(b\"\")\n\n    # do nothing\n\n\nasync def test_schedule_timeout(loop: asyncio.AbstractEventLoop) -> None:\n    proto = ResponseHandler(loop=loop)\n    proto.set_response_params(read_timeout=1)\n    assert proto._read_timeout_handle is None\n    proto.start_timeout()\n    assert proto._read_timeout_handle is not None\n\n\nasync def test_drop_timeout(loop: asyncio.AbstractEventLoop) -> None:\n    proto = ResponseHandler(loop=loop)\n    proto.set_response_params(read_timeout=1)\n    proto.start_timeout()\n    assert proto._read_timeout_handle is not None\n    proto._drop_timeout()\n    assert proto._read_timeout_handle is None\n\n\nasync def test_reschedule_timeout(loop: asyncio.AbstractEventLoop) -> None:\n    proto = ResponseHandler(loop=loop)\n    proto.set_response_params(read_timeout=1)\n    proto.start_timeout()\n    assert proto._read_timeout_handle is not None\n    h = proto._read_timeout_handle\n    proto._reschedule_timeout()\n    assert proto._read_timeout_handle is not None\n    assert proto._read_timeout_handle is not h\n\n\nasync def test_eof_received(loop: asyncio.AbstractEventLoop) -> None:\n    proto = ResponseHandler(loop=loop)\n    proto.set_response_params(read_timeout=1)\n    proto.start_timeout()\n    assert proto._read_timeout_handle is not None\n    proto.eof_received()\n    assert proto._read_timeout_handle is None\n\n\nasync def test_connection_lost_sets_transport_to_none(\n    loop: asyncio.AbstractEventLoop, mocker: MockerFixture\n) -> None:\n    \"\"\"Ensure that the transport is set to None when the connection is lost.\n\n    This ensures the writer knows that the connection is closed.\n    \"\"\"\n    proto = ResponseHandler(loop=loop)\n    proto.connection_made(mocker.Mock())\n    assert proto.transport is not None\n\n    proto.connection_lost(OSError())\n\n    assert proto.transport is None\n\n\nasync def test_connection_lost_exception_is_marked_retrieved(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that connection_lost properly handles exceptions without warnings.\"\"\"\n    proto = ResponseHandler(loop=loop)\n    proto.connection_made(mock.Mock())\n\n    # Access closed property before connection_lost to ensure future is created\n    closed_future = proto.closed\n    assert closed_future is not None\n\n    # Simulate an SSL shutdown timeout error\n    ssl_error = TimeoutError(\"SSL shutdown timed out\")\n    proto.connection_lost(ssl_error)\n\n    # Verify the exception was set on the closed future\n    assert closed_future.done()\n    exc = closed_future.exception()\n    assert exc is not None\n    assert \"Connection lost: SSL shutdown timed out\" in str(exc)\n    assert exc.__cause__ is ssl_error\n\n\nasync def test_closed_property_lazy_creation(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that closed future is created lazily.\"\"\"\n    proto = ResponseHandler(loop=loop)\n\n    # Initially, the closed future should not be created\n    assert proto._closed is None\n\n    # Accessing the property should create the future\n    closed_future = proto.closed\n    assert closed_future is not None\n    assert isinstance(closed_future, asyncio.Future)\n    assert not closed_future.done()\n\n    # Subsequent access should return the same future\n    assert proto.closed is closed_future\n\n\nasync def test_closed_property_after_connection_lost(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that closed property returns None after connection_lost if never accessed.\"\"\"\n    proto = ResponseHandler(loop=loop)\n    proto.connection_made(mock.Mock())\n\n    # Don't access proto.closed before connection_lost\n    proto.connection_lost(None)\n\n    # After connection_lost, closed should return None if it was never accessed\n    assert proto.closed is None\n\n\nasync def test_abort(loop: asyncio.AbstractEventLoop) -> None:\n    \"\"\"Test the abort() method.\"\"\"\n    proto = ResponseHandler(loop=loop)\n\n    # Create a mock transport\n    transport = mock.Mock()\n    proto.connection_made(transport)\n\n    # Set up some state\n    proto._payload = mock.Mock()\n\n    # Mock _drop_timeout method using patch.object\n    with mock.patch.object(proto, \"_drop_timeout\") as mock_drop_timeout:\n        # Call abort\n        proto.abort()\n\n        # Verify transport.abort() was called\n        transport.abort.assert_called_once()\n\n        # Verify cleanup\n        assert proto.transport is None\n        assert proto._payload is None\n        assert proto._exception is None  # type: ignore[unreachable]\n        mock_drop_timeout.assert_called_once()\n\n\nasync def test_abort_without_transport(loop: asyncio.AbstractEventLoop) -> None:\n    \"\"\"Test abort() when transport is None.\"\"\"\n    proto = ResponseHandler(loop=loop)\n\n    # Mock _drop_timeout method using patch.object\n    with mock.patch.object(proto, \"_drop_timeout\") as mock_drop_timeout:\n        # Call abort without transport\n        proto.abort()\n\n        # Should not raise and should still clean up\n        assert proto._exception is None\n        mock_drop_timeout.assert_not_called()\n"
  },
  {
    "path": "tests/test_client_request.py",
    "content": "import asyncio\nimport hashlib\nimport io\nimport pathlib\nimport sys\nfrom collections.abc import AsyncIterator, Callable, Iterable\nfrom http.cookies import BaseCookie, SimpleCookie\nfrom typing import Any\nfrom unittest import mock\n\nimport pytest\nfrom multidict import CIMultiDict, CIMultiDictProxy, istr\nfrom yarl import URL\n\nimport aiohttp\nfrom aiohttp import BaseConnector, hdrs, payload\nfrom aiohttp.abc import AbstractStreamWriter\nfrom aiohttp.base_protocol import BaseProtocol\nfrom aiohttp.client_exceptions import ClientConnectionError\nfrom aiohttp.client_reqrep import (\n    ClientRequest,\n    ClientRequestArgs,\n    ClientResponse,\n    Fingerprint,\n    _gen_default_accept_encoding,\n)\nfrom aiohttp.compression_utils import ZLibBackend\nfrom aiohttp.connector import Connection\nfrom aiohttp.hdrs import METH_DELETE\nfrom aiohttp.helpers import TimerNoop\nfrom aiohttp.http import HttpVersion10, HttpVersion11, StreamWriter\nfrom aiohttp.multipart import MultipartWriter\n\nif sys.version_info >= (3, 11):\n    from typing import Unpack\n\n    _RequestMaker = Callable[[str, URL, Unpack[ClientRequestArgs]], ClientRequest]\nelse:\n    _RequestMaker = Any\n\n\nclass WriterMock(mock.AsyncMock):\n    def add_done_callback(self, cb: Callable[[], None]) -> None:\n        \"\"\"Dummy method.\"\"\"\n\n    def remove_done_callback(self, cb: Callable[[], None]) -> None:\n        \"\"\"Dummy method.\"\"\"\n\n\nALL_METHODS = frozenset(\n    (*ClientRequest.GET_METHODS, *ClientRequest.POST_METHODS, METH_DELETE)\n)\n\n\n@pytest.fixture\ndef buf() -> bytearray:\n    return bytearray()\n\n\n@pytest.fixture\ndef protocol(\n    loop: asyncio.AbstractEventLoop, transport: asyncio.Transport\n) -> BaseProtocol:\n    protocol = mock.Mock()\n    protocol.transport = transport\n    protocol._drain_helper.return_value = loop.create_future()\n    protocol._drain_helper.return_value.set_result(None)\n    return protocol\n\n\n@pytest.fixture\ndef transport(buf: bytearray) -> mock.Mock:\n    transport = mock.create_autospec(asyncio.Transport, spec_set=True, instance=True)\n\n    def write(chunk: bytes) -> None:\n        buf.extend(chunk)\n\n    def writelines(chunks: Iterable[bytes]) -> None:\n        for chunk in chunks:\n            buf.extend(chunk)\n\n    transport.write.side_effect = write\n    transport.writelines.side_effect = writelines\n    transport.is_closing.return_value = False\n\n    return transport  # type: ignore[no-any-return]\n\n\n@pytest.fixture\ndef conn(transport: asyncio.Transport, protocol: BaseProtocol) -> Connection:\n    return mock.Mock(transport=transport, protocol=protocol)\n\n\nasync def test_method1(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org/\"))\n    assert req.method == \"GET\"\n\n\nasync def test_method2(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"head\", URL(\"http://python.org/\"))\n    assert req.method == \"HEAD\"\n\n\nasync def test_method3(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"HEAD\", URL(\"http://python.org/\"))\n    assert req.method == \"HEAD\"\n\n\nasync def test_method_invalid(make_client_request: _RequestMaker) -> None:\n    with pytest.raises(ValueError, match=\"Method cannot contain non-token characters\"):\n        make_client_request(\"METHOD WITH\\nWHITESPACES\", URL(\"http://python.org/\"))\n\n\nasync def test_version_1_0(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org/\"), version=HttpVersion10)\n    assert req.version == (1, 0)\n\n\nasync def test_version_default(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org/\"))\n    assert req.version == (1, 1)\n\n\nasync def test_request_info(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org/\"))\n    url = URL(\"http://python.org/\")\n    h = CIMultiDictProxy(req.headers)\n    # Create a response to test request_info\n    resp = req.response_class(\n        \"GET\",\n        url,\n        writer=None,\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=req.loop,\n        session=None,\n        request_headers=req.headers,\n        original_url=url,\n    )\n    assert resp.request_info == aiohttp.RequestInfo(url, \"GET\", h, url)\n\n\nasync def test_request_info_with_fragment(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org/#urlfragment\"))\n    h = CIMultiDictProxy(req.headers)\n    # Create a response to test request_info\n    resp = req.response_class(\n        \"GET\",\n        URL(\"http://python.org/\"),\n        writer=None,\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=req.loop,\n        session=None,\n        request_headers=req.headers,\n        original_url=URL(\"http://python.org/#urlfragment\"),\n    )\n    assert resp.request_info == aiohttp.RequestInfo(\n        URL(\"http://python.org/\"),\n        \"GET\",\n        h,\n        URL(\"http://python.org/#urlfragment\"),\n    )\n\n\nasync def test_host_port_default_http(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org/\"))\n    assert req.url.host == \"python.org\"\n    assert req.url.port == 80\n    assert not req.is_ssl()\n\n\nasync def test_host_port_default_https(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"https://python.org/\"))\n    assert req.url.host == \"python.org\"\n    assert req.url.port == 443\n    assert req.is_ssl()\n\n\nasync def test_host_port_nondefault_http(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org:960/\"))\n    assert req.url.host == \"python.org\"\n    assert req.url.port == 960\n    assert not req.is_ssl()\n\n\nasync def test_host_port_nondefault_https(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"https://python.org:960/\"))\n    assert req.url.host == \"python.org\"\n    assert req.url.port == 960\n    assert req.is_ssl()\n\n\nasync def test_host_port_default_ws(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"ws://python.org/\"))\n    assert req.url.host == \"python.org\"\n    assert req.url.port == 80\n    assert not req.is_ssl()\n\n\nasync def test_host_port_default_wss(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"wss://python.org/\"))\n    assert req.url.host == \"python.org\"\n    assert req.url.port == 443\n    assert req.is_ssl()\n\n\nasync def test_host_port_nondefault_ws(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"ws://python.org:960/\"))\n    assert req.url.host == \"python.org\"\n    assert req.url.port == 960\n    assert not req.is_ssl()\n\n\nasync def test_host_port_nondefault_wss(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"wss://python.org:960/\"))\n    assert req.url.host == \"python.org\"\n    assert req.url.port == 960\n    assert req.is_ssl()\n\n\nasync def test_host_port_none_port(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"unix://localhost/path\"))\n    assert req.headers[hdrs.HOST] == \"localhost\"\n\n\nasync def test_host_port_err(make_client_request: _RequestMaker) -> None:\n    with pytest.raises(ValueError):\n        make_client_request(\"get\", URL(\"http://python.org:123e/\"))\n\n\nasync def test_hostname_err(make_client_request: _RequestMaker) -> None:\n    with pytest.raises(ValueError):\n        make_client_request(\"get\", URL(\"http://:8080/\"))\n\n\nasync def test_host_header_host_first(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org/\"))\n    assert list(req.headers)[0] == hdrs.HOST\n\n\nasync def test_host_header_host_without_port(\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org/\"))\n    assert req.headers[hdrs.HOST] == \"python.org\"\n\n\nasync def test_host_header_host_with_default_port(\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org:80/\"))\n    assert req.headers[hdrs.HOST] == \"python.org\"\n\n\nasync def test_host_header_host_with_nondefault_port(\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org:99/\"))\n    assert req.headers[\"HOST\"] == \"python.org:99\"\n\n\nasync def test_host_header_host_idna_encode(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://xn--9caa.com\"))\n    assert req.headers[\"HOST\"] == \"xn--9caa.com\"\n\n\nasync def test_host_header_host_unicode(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://éé.com\"))\n    assert req.headers[\"HOST\"] == \"xn--9caa.com\"\n\n\nasync def test_host_header_explicit_host(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\n        \"get\", URL(\"http://python.org/\"), headers=CIMultiDict({\"host\": \"example.com\"})\n    )\n    assert req.headers[\"HOST\"] == \"example.com\"\n\n\nasync def test_host_header_explicit_host_with_port(\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"get\",\n        URL(\"http://python.org/\"),\n        headers=CIMultiDict({\"host\": \"example.com:99\"}),\n    )\n    assert req.headers[\"HOST\"] == \"example.com:99\"\n\n\nasync def test_host_header_ipv4(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://127.0.0.2\"))\n    assert req.headers[\"HOST\"] == \"127.0.0.2\"\n\n\nasync def test_host_header_ipv6(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://[::2]\"))\n    assert req.headers[\"HOST\"] == \"[::2]\"\n\n\nasync def test_host_header_ipv4_with_port(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://127.0.0.2:99\"))\n    assert req.headers[\"HOST\"] == \"127.0.0.2:99\"\n\n\nasync def test_host_header_ipv6_with_port(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://[::2]:99\"))\n    assert req.headers[\"HOST\"] == \"[::2]:99\"\n\n\n@pytest.mark.parametrize(\n    (\"url\", \"headers\", \"expected\"),\n    (\n        pytest.param(\n            \"http://localhost.\", CIMultiDict(), \"localhost\", id=\"dot only at the end\"\n        ),\n        pytest.param(\n            \"http://python.org.\", CIMultiDict(), \"python.org\", id=\"single dot\"\n        ),\n        pytest.param(\n            \"http://python.org.:99\",\n            CIMultiDict(),\n            \"python.org:99\",\n            id=\"single dot with port\",\n        ),\n        pytest.param(\n            \"http://python.org...:99\",\n            CIMultiDict(),\n            \"python.org:99\",\n            id=\"multiple dots with port\",\n        ),\n        pytest.param(\n            \"http://python.org.:99\",\n            CIMultiDict({\"host\": \"example.com.:99\"}),\n            \"example.com.:99\",\n            id=\"explicit host header\",\n        ),\n        pytest.param(\"https://python.org.\", CIMultiDict(), \"python.org\", id=\"https\"),\n        pytest.param(\"https://...\", CIMultiDict(), \"\", id=\"only dots\"),\n        pytest.param(\n            \"http://príklad.example.org.:99\",\n            CIMultiDict(),\n            \"xn--prklad-4va.example.org:99\",\n            id=\"single dot with port idna\",\n        ),\n    ),\n)\nasync def test_host_header_fqdn(  # type: ignore[misc]\n    make_client_request: _RequestMaker,\n    url: str,\n    headers: CIMultiDict[str],\n    expected: str,\n) -> None:\n    req = make_client_request(\"get\", URL(url), headers=headers)\n    assert req.headers[\"HOST\"] == expected\n\n\nasync def test_default_headers_useragent(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org/\"))\n\n    assert \"SERVER\" not in req.headers\n    assert \"USER-AGENT\" in req.headers\n\n\nasync def test_default_headers_useragent_custom(\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"get\",\n        URL(\"http://python.org/\"),\n        headers=CIMultiDict({\"user-agent\": \"my custom agent\"}),\n    )\n\n    assert \"USER-Agent\" in req.headers\n    assert \"my custom agent\" == req.headers[\"User-Agent\"]\n\n\nasync def test_skip_default_useragent_header(\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"get\", URL(\"http://python.org/\"), skip_auto_headers={istr(\"user-agent\")}\n    )\n\n    assert \"User-Agent\" not in req.headers\n\n\nasync def test_headers(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\n        \"post\",\n        URL(\"http://python.org/\"),\n        headers=CIMultiDict({hdrs.CONTENT_TYPE: \"text/plain\"}),\n    )\n\n    assert hdrs.CONTENT_TYPE in req.headers\n    assert req.headers[hdrs.CONTENT_TYPE] == \"text/plain\"\n    assert \"gzip\" in req.headers[hdrs.ACCEPT_ENCODING]\n\n\nasync def test_headers_list(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\n        \"post\",\n        URL(\"http://python.org/\"),\n        headers=CIMultiDict(((\"Content-Type\", \"text/plain\"),)),\n    )\n    assert \"CONTENT-TYPE\" in req.headers\n    assert req.headers[\"CONTENT-TYPE\"] == \"text/plain\"\n\n\nasync def test_headers_default(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\n        \"get\",\n        URL(\"http://python.org/\"),\n        headers=CIMultiDict({\"ACCEPT-ENCODING\": \"deflate\"}),\n    )\n    assert req.headers[\"ACCEPT-ENCODING\"] == \"deflate\"\n\n\nasync def test_invalid_url(make_client_request: _RequestMaker) -> None:\n    with pytest.raises(aiohttp.InvalidURL):\n        make_client_request(\"get\", URL(\"hiwpefhipowhefopw\"))\n\n\nasync def test_no_path(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org\"))\n    assert \"/\" == req.url.path\n\n\nasync def test_ipv6_default_http_port(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://[2001:db8::1]/\"))\n    assert req.url.host == \"2001:db8::1\"\n    assert req.url.port == 80\n    assert not req.is_ssl()\n\n\nasync def test_ipv6_default_https_port(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"https://[2001:db8::1]/\"))\n    assert req.url.host == \"2001:db8::1\"\n    assert req.url.port == 443\n    assert req.is_ssl()\n\n\nasync def test_ipv6_nondefault_http_port(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://[2001:db8::1]:960/\"))\n    assert req.url.host == \"2001:db8::1\"\n    assert req.url.port == 960\n    assert not req.is_ssl()\n\n\nasync def test_ipv6_nondefault_https_port(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"https://[2001:db8::1]:960/\"))\n    assert req.url.host == \"2001:db8::1\"\n    assert req.url.port == 960\n    assert req.is_ssl()\n\n\nasync def test_basic_auth(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\n        \"get\", URL(\"http://python.org\"), auth=aiohttp.BasicAuth(\"nkim\", \"1234\")\n    )\n    assert \"AUTHORIZATION\" in req.headers\n    assert \"Basic bmtpbToxMjM0\" == req.headers[\"AUTHORIZATION\"]\n\n\nasync def test_basic_auth_utf8(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\n        \"get\",\n        URL(\"http://python.org\"),\n        auth=aiohttp.BasicAuth(\"nkim\", \"секрет\", \"utf-8\"),\n    )\n    assert \"AUTHORIZATION\" in req.headers\n    assert \"Basic bmtpbTrRgdC10LrRgNC10YI=\" == req.headers[\"AUTHORIZATION\"]\n\n\nasync def test_basic_auth_tuple_forbidden(make_client_request: _RequestMaker) -> None:\n    with pytest.raises(TypeError):\n        make_client_request(\"get\", URL(\"http://python.org\"), auth=(\"nkim\", \"1234\"))  # type: ignore[arg-type]\n\n\nasync def test_basic_auth_from_url(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://nkim:1234@python.org\"))\n    assert \"AUTHORIZATION\" in req.headers\n    assert \"Basic bmtpbToxMjM0\" == req.headers[\"AUTHORIZATION\"]\n    assert \"python.org\" == req.url.host\n\n\nasync def test_basic_auth_no_user_from_url(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://:1234@python.org\"))\n    assert \"AUTHORIZATION\" in req.headers\n    assert \"Basic OjEyMzQ=\" == req.headers[\"AUTHORIZATION\"]\n    assert \"python.org\" == req.url.host\n\n\nasync def test_basic_auth_from_url_overridden(\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"get\", URL(\"http://garbage@python.org\"), auth=aiohttp.BasicAuth(\"nkim\", \"1234\")\n    )\n    assert \"AUTHORIZATION\" in req.headers\n    assert \"Basic bmtpbToxMjM0\" == req.headers[\"AUTHORIZATION\"]\n    assert \"python.org\" == req.url.host\n\n\nasync def test_path_is_not_double_encoded1(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://0.0.0.0/get/test case\"))\n    assert req.url.raw_path == \"/get/test%20case\"\n\n\nasync def test_path_is_not_double_encoded2(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://0.0.0.0/get/test%2fcase\"))\n    assert req.url.raw_path == \"/get/test%2Fcase\"\n\n\nasync def test_path_is_not_double_encoded3(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://0.0.0.0/get/test%20case\"))\n    assert req.url.raw_path == \"/get/test%20case\"\n\n\nasync def test_path_safe_chars_preserved(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://0.0.0.0/get/:=+/%2B/\"))\n    assert req.url.path == \"/get/:=+/+/\"\n\n\nasync def test_params_are_added_before_fragment1(\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"GET\", URL(\"http://example.com/path#fragment\"), params={\"a\": \"b\"}\n    )\n    assert str(req.url) == \"http://example.com/path?a=b\"\n\n\nasync def test_params_are_added_before_fragment2(\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"GET\", URL(\"http://example.com/path?key=value#fragment\"), params={\"a\": \"b\"}\n    )\n    assert str(req.url) == \"http://example.com/path?key=value&a=b\"\n\n\nasync def test_path_not_contain_fragment1(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"GET\", URL(\"http://example.com/path#fragment\"))\n    assert req.url.path == \"/path\"\n\n\nasync def test_path_not_contain_fragment2(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"GET\", URL(\"http://example.com/path?key=value#fragment\"))\n    assert str(req.url) == \"http://example.com/path?key=value\"\n\n\nasync def test_cookies(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\n        \"get\", URL(\"http://test.com/path\"), cookies=BaseCookie({\"cookie1\": \"val1\"})\n    )\n\n    assert \"COOKIE\" in req.headers\n    assert \"cookie1=val1\" == req.headers[\"COOKIE\"]\n\n\nasync def test_cookies_merge_with_headers(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\n        \"get\",\n        URL(\"http://test.com/path\"),\n        headers=CIMultiDict({\"cookie\": \"cookie1=val1\"}),\n        cookies=BaseCookie({\"cookie2\": \"val2\"}),\n    )\n\n    assert \"cookie1=val1; cookie2=val2\" == req.headers[\"COOKIE\"]\n\n\nasync def test_query_multivalued_param(make_client_request: _RequestMaker) -> None:\n    for meth in ALL_METHODS:\n        req = make_client_request(\n            meth, URL(\"http://python.org\"), params=((\"test\", \"foo\"), (\"test\", \"baz\"))\n        )\n\n        assert str(req.url) == \"http://python.org/?test=foo&test=baz\"\n\n\nasync def test_query_str_param(make_client_request: _RequestMaker) -> None:\n    for meth in ALL_METHODS:\n        req = make_client_request(meth, URL(\"http://python.org\"), params=\"test=foo\")\n        assert str(req.url) == \"http://python.org/?test=foo\"\n\n\nasync def test_query_bytes_param_raises(make_client_request: _RequestMaker) -> None:\n    for meth in ALL_METHODS:\n        with pytest.raises(TypeError):\n            make_client_request(meth, URL(\"http://python.org\"), params=b\"test=foo\")  # type: ignore[arg-type]\n\n\nasync def test_query_str_param_is_not_encoded(\n    make_client_request: _RequestMaker,\n) -> None:\n    for meth in ALL_METHODS:\n        req = make_client_request(meth, URL(\"http://python.org\"), params=\"test=f+oo\")\n        assert str(req.url) == \"http://python.org/?test=f+oo\"\n\n\nasync def test_params_update_path_and_url(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\n        \"get\", URL(\"http://python.org\"), params=((\"test\", \"foo\"), (\"test\", \"baz\"))\n    )\n    assert str(req.url) == \"http://python.org/?test=foo&test=baz\"\n\n\nasync def test_params_empty_path_and_url(make_client_request: _RequestMaker) -> None:\n    req_empty = make_client_request(\"get\", URL(\"http://python.org\"), params={})\n    assert str(req_empty.url) == \"http://python.org\"\n    req_none = make_client_request(\"get\", URL(\"http://python.org\"))\n    assert str(req_none.url) == \"http://python.org\"\n\n\nasync def test_gen_netloc_all(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\n        \"get\",\n        URL(\n            \"https://aiohttp:pwpwpw@12345678901234567890123456789012345678901234567890:8080\"\n        ),\n    )\n    assert (\n        req.headers[\"HOST\"]\n        == \"12345678901234567890123456789\" + \"012345678901234567890:8080\"\n    )\n\n\nasync def test_gen_netloc_no_port(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\n        \"get\",\n        URL(\n            \"https://aiohttp:pwpwpw@12345678901234567890123456789012345678901234567890/\"\n        ),\n    )\n    assert (\n        req.headers[\"HOST\"] == \"12345678901234567890123456789\" + \"012345678901234567890\"\n    )\n\n\nasync def test_cookie_coded_value_preserved(\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Verify the coded value of a cookie is preserved.\"\"\"\n    # https://github.com/aio-libs/aiohttp/pull/1453\n    req = make_client_request(\"get\", URL(\"http://python.org\"), loop=loop)\n    req._update_cookies(cookies=SimpleCookie('ip-cookie=\"second\"; Domain=127.0.0.1;'))\n    assert req.headers[\"COOKIE\"] == 'ip-cookie=\"second\"'\n\n\nasync def test_update_cookies_with_special_chars_in_existing_header(\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that update_cookies handles existing cookies with special characters.\"\"\"\n    # Create request with a cookie that has special characters (real-world example)\n    req = make_client_request(\n        \"get\",\n        URL(\"http://python.org\"),\n        headers=CIMultiDict(\n            {\"Cookie\": \"ISAWPLB{A7F52349-3531-4DA9-8776-F74BC6F4F1BB}=value1\"}\n        ),\n        loop=loop,\n    )\n\n    # Update with another cookie\n    req._update_cookies(cookies=BaseCookie({\"normal_cookie\": \"value2\"}))\n\n    # Both cookies should be preserved in the exact order\n    assert (\n        req.headers[\"COOKIE\"]\n        == \"ISAWPLB{A7F52349-3531-4DA9-8776-F74BC6F4F1BB}=value1; normal_cookie=value2\"\n    )\n\n\nasync def test_update_cookies_with_quoted_existing_header(\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that update_cookies handles existing cookies with quoted values.\"\"\"\n    # Create request with cookies that have quoted values\n    req = make_client_request(\n        \"get\",\n        URL(\"http://python.org\"),\n        headers=CIMultiDict({\"Cookie\": 'session=\"value;with;semicolon\"; token=abc123'}),\n        loop=loop,\n    )\n\n    # Update with another cookie\n    req._update_cookies(cookies=BaseCookie({\"new_cookie\": \"new_value\"}))\n\n    # All cookies should be preserved with their original coded values\n    # The quoted value should be preserved as-is\n    assert (\n        req.headers[\"COOKIE\"]\n        == 'new_cookie=new_value; session=\"value;with;semicolon\"; token=abc123'\n    )\n\n\nasync def test_connection_header(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org\"), loop=loop)\n    req.headers.clear()\n\n    req.version = HttpVersion11\n    req.headers.clear()\n    with mock.patch.object(conn._connector, \"force_close\", False):\n        await req._send(conn)\n    assert req.headers.get(\"CONNECTION\") is None\n\n    req.version = HttpVersion10\n    req.headers.clear()\n    with mock.patch.object(conn._connector, \"force_close\", False):\n        await req._send(conn)\n    assert req.headers.get(\"CONNECTION\") == \"keep-alive\"\n\n    req.version = HttpVersion11\n    req.headers.clear()\n    with mock.patch.object(conn._connector, \"force_close\", True):\n        await req._send(conn)\n    assert req.headers.get(\"CONNECTION\") == \"close\"\n\n    req.version = HttpVersion10\n    req.headers.clear()\n    with mock.patch.object(conn._connector, \"force_close\", True):\n        await req._send(conn)\n    assert not req.headers.get(\"CONNECTION\")\n\n\nasync def test_no_content_length(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org\"), loop=loop)\n    resp = await req._send(conn)\n    assert req.headers.get(\"CONTENT-LENGTH\") is None\n    await req._close()\n    resp.close()\n\n\nasync def test_no_content_length_head(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\"head\", URL(\"http://python.org\"), loop=loop)\n    resp = await req._send(conn)\n    assert req.headers.get(\"CONTENT-LENGTH\") is None\n    await req._close()\n    resp.close()\n\n\nasync def test_content_type_auto_header_get(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org\"), loop=loop)\n    resp = await req._send(conn)\n    assert \"CONTENT-TYPE\" not in req.headers\n    resp.close()\n    await req._close()\n\n\nasync def test_content_type_auto_header_form(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"post\", URL(\"http://python.org\"), data={\"hey\": \"you\"}, loop=loop\n    )\n    resp = await req._send(conn)\n    assert \"application/x-www-form-urlencoded\" == req.headers.get(\"CONTENT-TYPE\")\n    resp.close()\n\n\nasync def test_content_type_auto_header_bytes(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"post\", URL(\"http://python.org\"), data=b\"hey you\", loop=loop\n    )\n    resp = await req._send(conn)\n    assert \"application/octet-stream\" == req.headers.get(\"CONTENT-TYPE\")\n    resp.close()\n\n\nasync def test_content_type_skip_auto_header_bytes(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"post\",\n        URL(\"http://python.org\"),\n        data=b\"hey you\",\n        skip_auto_headers={\"Content-Type\"},\n        loop=loop,\n    )\n    assert req.skip_auto_headers == CIMultiDict({\"CONTENT-TYPE\": None})\n    resp = await req._send(conn)\n    assert \"CONTENT-TYPE\" not in req.headers\n    resp.close()\n\n\nasync def test_content_type_skip_auto_header_form(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"post\",\n        URL(\"http://python.org\"),\n        data={\"hey\": \"you\"},\n        loop=loop,\n        skip_auto_headers={\"Content-Type\"},\n    )\n    resp = await req._send(conn)\n    assert \"CONTENT-TYPE\" not in req.headers\n    resp.close()\n\n\nasync def test_content_type_auto_header_content_length_no_skip(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    with io.BytesIO(b\"hey\") as file_handle:\n        req = make_client_request(\n            \"post\",\n            URL(\"http://python.org\"),\n            data=file_handle,\n            skip_auto_headers={\"Content-Length\"},\n            loop=loop,\n        )\n        resp = await req._send(conn)\n        assert req.headers.get(\"CONTENT-LENGTH\") == \"3\"\n        resp.close()\n\n\nasync def test_urlencoded_formdata_charset(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"post\",\n        URL(\"http://python.org\"),\n        data=aiohttp.FormData({\"hey\": \"you\"}, charset=\"koi8-r\"),\n        loop=loop,\n    )\n    async with await req._send(conn):\n        await asyncio.sleep(0)\n    assert \"application/x-www-form-urlencoded; charset=koi8-r\" == req.headers.get(\n        \"CONTENT-TYPE\"\n    )\n\n\nasync def test_formdata_boundary_from_headers(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    boundary = \"some_boundary\"\n    file_path = pathlib.Path(__file__).parent / \"aiohttp.png\"\n    with file_path.open(\"rb\") as f:\n        req = make_client_request(\n            \"post\",\n            URL(\"http://python.org\"),\n            data={\"aiohttp.png\": f},\n            headers=CIMultiDict(\n                {\"Content-Type\": f\"multipart/form-data; boundary={boundary}\"}\n            ),\n            loop=loop,\n        )\n        async with await req._send(conn):\n            await asyncio.sleep(0)\n        assert isinstance(req.body, MultipartWriter)\n        assert req.body._boundary == boundary.encode()\n\n\nasync def test_post_data(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    for meth in ClientRequest.POST_METHODS:\n        req = make_client_request(\n            meth, URL(\"http://python.org/\"), data={\"life\": \"42\"}, loop=loop\n        )\n        resp = await req._send(conn)\n        assert \"/\" == req.url.path\n        assert isinstance(req.body, payload.Payload)\n        assert b\"life=42\" == req.body._value\n        assert \"application/x-www-form-urlencoded\" == req.headers[\"CONTENT-TYPE\"]\n        await req._close()\n        resp.close()\n\n\nasync def test_pass_falsy_data(\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    with mock.patch(\"aiohttp.client_reqrep.ClientRequest._update_body_from_data\") as m:\n        req = make_client_request(\"post\", URL(\"http://python.org/\"), data={}, loop=loop)\n        m.assert_called_once_with({})\n    await req._close()\n\n\nasync def test_pass_falsy_data_file(\n    loop: asyncio.AbstractEventLoop,\n    tmp_path: pathlib.Path,\n    make_client_request: _RequestMaker,\n) -> None:\n    testfile = (tmp_path / \"tmpfile\").open(\"w+b\")\n    testfile.write(b\"data\")\n    testfile.seek(0)\n    skip = frozenset([hdrs.CONTENT_TYPE])\n    req = make_client_request(\n        \"post\",\n        URL(\"http://python.org/\"),\n        data=testfile,\n        skip_auto_headers=skip,\n        loop=loop,\n    )\n    assert req.headers.get(\"CONTENT-LENGTH\", None) is not None\n    await req._close()\n    testfile.close()\n\n\n# Elasticsearch API requires to send request body with GET-requests\nasync def test_get_with_data(\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    for meth in ClientRequest.GET_METHODS:\n        req = make_client_request(\n            meth, URL(\"http://python.org/\"), data={\"life\": \"42\"}, loop=loop\n        )\n        assert \"/\" == req.url.path\n        assert isinstance(req.body, payload.Payload)\n        assert b\"life=42\" == req.body._value\n        await req._close()\n\n\nasync def test_bytes_data(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    for meth in ClientRequest.POST_METHODS:\n        req = make_client_request(\n            meth, URL(\"http://python.org/\"), data=b\"binary data\", loop=loop\n        )\n        resp = await req._send(conn)\n        assert \"/\" == req.url.path\n        assert isinstance(req.body, payload.BytesPayload)\n        assert b\"binary data\" == req.body._value\n        assert \"application/octet-stream\" == req.headers[\"CONTENT-TYPE\"]\n        await req._close()\n        resp.close()\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_content_encoding(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"post\", URL(\"http://python.org/\"), data=\"foo\", compress=\"deflate\", loop=loop\n    )\n    with mock.patch(\n        \"aiohttp.client_reqrep.StreamWriter\", autospec=True, spec_set=True\n    ) as m_writer:\n        resp = await req._send(conn)\n    assert req.headers[\"TRANSFER-ENCODING\"] == \"chunked\"\n    assert req.headers[\"CONTENT-ENCODING\"] == \"deflate\"\n    m_writer.return_value.enable_compression.assert_called_with(\"deflate\")\n    await req._close()\n    resp.close()\n\n\nasync def test_content_encoding_dont_set_headers_if_no_body(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"post\", URL(\"http://python.org/\"), compress=\"deflate\", loop=loop\n    )\n    resp = await req._send(conn)\n    assert \"TRANSFER-ENCODING\" not in req.headers\n    assert \"CONTENT-ENCODING\" not in req.headers\n    await req._close()\n    resp.close()\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_content_encoding_header(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"post\",\n        URL(\"http://python.org/\"),\n        data=\"foo\",\n        headers=CIMultiDict({\"Content-Encoding\": \"deflate\"}),\n        loop=loop,\n    )\n    with mock.patch(\n        \"aiohttp.client_reqrep.StreamWriter\", autospec=True, spec_set=True\n    ) as m_writer:\n        resp = await req._send(conn)\n\n    assert not m_writer.return_value.enable_compression.called\n    assert not m_writer.return_value.enable_chunking.called\n    await req._close()\n    resp.close()\n\n\nasync def test_compress_and_content_encoding(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    with pytest.raises(ValueError):\n        make_client_request(\n            \"post\",\n            URL(\"http://python.org/\"),\n            data=\"foo\",\n            headers=CIMultiDict({\"content-encoding\": \"deflate\"}),\n            compress=\"deflate\",\n            loop=loop,\n        )\n\n\nasync def test_chunked(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"post\",\n        URL(\"http://python.org/\"),\n        headers=CIMultiDict({\"TRANSFER-ENCODING\": \"gzip\"}),\n        loop=loop,\n    )\n    resp = await req._send(conn)\n    assert \"gzip\" == req.headers[\"TRANSFER-ENCODING\"]\n    await req._close()\n    resp.close()\n\n\nasync def test_chunked2(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"post\",\n        URL(\"http://python.org/\"),\n        headers=CIMultiDict({\"Transfer-encoding\": \"chunked\"}),\n        loop=loop,\n    )\n    resp = await req._send(conn)\n    assert \"chunked\" == req.headers[\"TRANSFER-ENCODING\"]\n    await req._close()\n    resp.close()\n\n\nasync def test_chunked_empty_body(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Ensure write_bytes is called even if the body is empty.\"\"\"\n    req = make_client_request(\n        \"post\",\n        URL(\"http://python.org/\"),\n        chunked=True,\n        loop=loop,\n        data=b\"\",\n    )\n    with mock.patch.object(req, \"_write_bytes\") as write_bytes:\n        resp = await req._send(conn)\n    assert \"chunked\" == req.headers[\"TRANSFER-ENCODING\"]\n    assert write_bytes.called\n    await req._close()\n    resp.close()\n\n\nasync def test_chunked_explicit(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"post\", URL(\"http://python.org/\"), chunked=True, loop=loop\n    )\n    with mock.patch(\n        \"aiohttp.client_reqrep.StreamWriter\", autospec=True, spec_set=True\n    ) as m_writer:\n        resp = await req._send(conn)\n\n    assert \"chunked\" == req.headers[\"TRANSFER-ENCODING\"]\n    m_writer.return_value.enable_chunking.assert_called_with()\n    await req._close()\n    resp.close()\n\n\nasync def test_chunked_length(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    with pytest.raises(ValueError):\n        make_client_request(\n            \"post\",\n            URL(\"http://python.org/\"),\n            headers=CIMultiDict({\"CONTENT-LENGTH\": \"1000\"}),\n            chunked=True,\n            loop=loop,\n        )\n\n\nasync def test_chunked_transfer_encoding(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    with pytest.raises(ValueError):\n        make_client_request(\n            \"post\",\n            URL(\"http://python.org/\"),\n            headers=CIMultiDict({\"TRANSFER-ENCODING\": \"chunked\"}),\n            chunked=True,\n            loop=loop,\n        )\n\n\nasync def test_file_upload_not_chunked(\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    file_path = pathlib.Path(__file__).parent / \"aiohttp.png\"\n    with file_path.open(\"rb\") as f:\n        req = make_client_request(\"post\", URL(\"http://python.org/\"), data=f, loop=loop)\n        assert not req.chunked\n        assert req.headers[\"CONTENT-LENGTH\"] == str(file_path.stat().st_size)\n        await req._close()\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_precompressed_data_stays_intact(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    data = ZLibBackend.compress(b\"foobar\")\n    req = make_client_request(\n        \"post\",\n        URL(\"http://python.org/\"),\n        data=data,\n        headers=CIMultiDict({\"CONTENT-ENCODING\": \"deflate\"}),\n        compress=False,\n        loop=loop,\n    )\n    assert not req.compress\n    assert not req.chunked\n    assert req.headers[\"CONTENT-ENCODING\"] == \"deflate\"\n    await req._close()\n\n\nasync def test_body_with_size_sets_content_length(\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that when body has a size and no Content-Length header is set, it gets added.\"\"\"\n    # Create a BytesPayload which has a size property\n    data = b\"test data\"\n\n    # Create request with data that will create a BytesPayload\n    req = make_client_request(\n        \"post\",\n        URL(\"http://python.org/\"),\n        data=data,\n        loop=loop,\n    )\n\n    # Verify Content-Length was set from body.size\n    assert req.headers[\"CONTENT-LENGTH\"] == str(len(data))\n    assert req.body is not None\n    assert req._body is not None  # When _body is set, body returns it\n    assert req._body.size == len(data)\n    await req._close()\n\n\nasync def test_body_payload_with_size_no_content_length(\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that when a body payload is set via update_body, Content-Length is added.\"\"\"\n    # Create a payload with a known size\n    data = b\"payload data\"\n    bytes_payload = payload.BytesPayload(data)\n\n    # Create request with no data initially\n    req = make_client_request(\n        \"post\",\n        URL(\"http://python.org/\"),\n        loop=loop,\n    )\n\n    # POST method with None body should have Content-Length: 0\n    assert req.headers[hdrs.CONTENT_LENGTH] == \"0\"\n\n    # Update body using the public method\n    await req.update_body(bytes_payload)\n\n    # Verify Content-Length was set from body.size\n    assert req.headers[hdrs.CONTENT_LENGTH] == str(len(data))\n    assert req.body is bytes_payload\n    assert req._body is bytes_payload  # Access _body which is the Payload\n    assert req._body.size == len(data)\n\n    # Set body back to None\n    await req.update_body(None)\n\n    # Verify Content-Length is back to 0 for POST with None body\n    assert req.headers[hdrs.CONTENT_LENGTH] == \"0\"\n\n    await req._close()\n\n\nasync def test_file_upload_not_chunked_seek(\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    file_path = pathlib.Path(__file__).parent / \"aiohttp.png\"\n    with file_path.open(\"rb\") as f:\n        f.seek(100)\n        req = make_client_request(\"post\", URL(\"http://python.org/\"), data=f, loop=loop)\n        assert req.headers[\"CONTENT-LENGTH\"] == str(file_path.stat().st_size - 100)\n        await req._close()\n\n\nasync def test_file_upload_force_chunked(\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    file_path = pathlib.Path(__file__).parent / \"aiohttp.png\"\n    with file_path.open(\"rb\") as f:\n        req = make_client_request(\n            \"post\", URL(\"http://python.org/\"), data=f, chunked=True, loop=loop\n        )\n        assert req.chunked\n        assert \"CONTENT-LENGTH\" not in req.headers\n        await req._close()\n\n\nasync def test_expect100(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"get\", URL(\"http://python.org/\"), expect100=True, loop=loop\n    )\n    resp = await req._send(conn)\n    assert \"100-continue\" == req.headers[\"EXPECT\"]\n    assert req._continue is not None\n    req._terminate()\n    resp.close()\n\n\nasync def test_expect_100_continue_header(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"get\",\n        URL(\"http://python.org/\"),\n        headers=CIMultiDict({\"expect\": \"100-continue\"}),\n        loop=loop,\n    )\n    resp = await req._send(conn)\n    assert \"100-continue\" == req.headers[\"EXPECT\"]\n    assert req._continue is not None\n    req._terminate()\n    resp.close()\n\n\nasync def test_data_stream(\n    loop: asyncio.AbstractEventLoop,\n    buf: bytearray,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"binary data\"\n        yield b\" result\"\n\n    req = make_client_request(\"POST\", URL(\"http://python.org/\"), data=gen(), loop=loop)\n    assert req.chunked\n    assert req.headers[\"TRANSFER-ENCODING\"] == \"chunked\"\n    original_write_bytes = req._write_bytes\n\n    async def _mock_write_bytes(\n        writer: AbstractStreamWriter, conn: mock.Mock, content_length: int | None\n    ) -> None:\n        # Ensure the task is scheduled\n        await asyncio.sleep(0)\n        await original_write_bytes(writer, conn, content_length)\n\n    with mock.patch.object(req, \"_write_bytes\", _mock_write_bytes):\n        resp = await req._send(conn)\n    assert asyncio.isfuture(req._writer)\n    await resp.wait_for_close()\n    assert req._writer is None\n    assert (  # type: ignore[unreachable]\n        buf.split(b\"\\r\\n\\r\\n\", 1)[1] == b\"b\\r\\nbinary data\\r\\n7\\r\\n result\\r\\n0\\r\\n\\r\\n\"\n    )\n    await req._close()\n\n\nasync def test_data_file(\n    loop: asyncio.AbstractEventLoop,\n    buf: bytearray,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    with io.BufferedReader(io.BytesIO(b\"*\" * 2)) as file_handle:\n        req = make_client_request(\n            \"POST\",\n            URL(\"http://python.org/\"),\n            data=file_handle,\n            loop=loop,\n        )\n        assert req.chunked\n        assert isinstance(req.body, payload.BufferedReaderPayload)\n        assert req.headers[\"TRANSFER-ENCODING\"] == \"chunked\"\n\n        original_write_bytes = req._write_bytes\n\n        async def _mock_write_bytes(\n            writer: AbstractStreamWriter, conn: mock.Mock, content_length: int | None\n        ) -> None:\n            # Ensure the task is scheduled so _writer isn't None\n            await asyncio.sleep(0)\n            await original_write_bytes(writer, conn, content_length)\n\n        with mock.patch.object(req, \"_write_bytes\", _mock_write_bytes):\n            resp = await req._send(conn)\n\n        assert asyncio.isfuture(req._writer)\n        await resp.wait_for_close()\n\n        assert req._writer is None\n        assert buf.split(b\"\\r\\n\\r\\n\", 1)[1] == b\"2\\r\\n\" + b\"*\" * 2 + b\"\\r\\n0\\r\\n\\r\\n\"  # type: ignore[unreachable]\n        await req._close()\n\n\nasync def test_data_stream_exc(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    fut = loop.create_future()\n\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"binary data\"\n        await fut\n\n    req = make_client_request(\"POST\", URL(\"http://python.org/\"), data=gen(), loop=loop)\n    assert req.chunked\n    assert req.headers[\"TRANSFER-ENCODING\"] == \"chunked\"\n\n    async def throw_exc() -> None:\n        await asyncio.sleep(0.01)\n        fut.set_exception(ValueError)\n\n    t = loop.create_task(throw_exc())\n\n    async with await req._send(conn):\n        assert req._writer is not None\n        await req._writer\n        await t\n        # assert conn.close.called\n        assert conn.protocol is not None\n        assert conn.protocol.set_exception.called\n    await req._close()\n\n\nasync def test_data_stream_exc_chain(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    fut = loop.create_future()\n\n    async def gen() -> AsyncIterator[None]:\n        await fut\n        assert False\n        yield  # type: ignore[unreachable]  # pragma: no cover\n\n    req = make_client_request(\"POST\", URL(\"http://python.org/\"), data=gen(), loop=loop)\n\n    inner_exc = ValueError()\n\n    async def throw_exc() -> None:\n        await asyncio.sleep(0.01)\n        fut.set_exception(inner_exc)\n\n    t = loop.create_task(throw_exc())\n\n    async with await req._send(conn):\n        assert req._writer is not None\n        await req._writer\n    await t\n    # assert conn.close.called\n    assert conn.protocol.set_exception.called\n    outer_exc = conn.protocol.set_exception.call_args[0][0]\n    assert isinstance(outer_exc, ClientConnectionError)\n    assert outer_exc.__cause__ is inner_exc\n    await req._close()\n\n\nasync def test_data_stream_continue(\n    loop: asyncio.AbstractEventLoop,\n    buf: bytearray,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"binary data\"\n        yield b\" result\"\n\n    req = make_client_request(\n        \"POST\", URL(\"http://python.org/\"), data=gen(), expect100=True, loop=loop\n    )\n    assert req.chunked\n\n    async def coro() -> None:\n        await asyncio.sleep(0.0001)\n        assert req._continue is not None\n        req._continue.set_result(1)\n\n    t = loop.create_task(coro())\n\n    resp = await req._send(conn)\n    assert req._writer is not None\n    await req._writer\n    await t\n    assert (\n        buf.split(b\"\\r\\n\\r\\n\", 1)[1] == b\"b\\r\\nbinary data\\r\\n7\\r\\n result\\r\\n0\\r\\n\\r\\n\"\n    )\n    await req._close()\n    resp.close()\n\n\nasync def test_data_continue(\n    loop: asyncio.AbstractEventLoop,\n    buf: bytearray,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"POST\", URL(\"http://python.org/\"), data=b\"data\", expect100=True, loop=loop\n    )\n\n    async def coro() -> None:\n        await asyncio.sleep(0.0001)\n        assert req._continue is not None\n        req._continue.set_result(1)\n\n    t = loop.create_task(coro())\n\n    resp = await req._send(conn)\n\n    assert req._writer is not None\n    await req._writer\n    await t\n    assert buf.split(b\"\\r\\n\\r\\n\", 1)[1] == b\"data\"\n    await req._close()\n    resp.close()\n\n\nasync def test_close(\n    loop: asyncio.AbstractEventLoop,\n    buf: bytearray,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    async def gen() -> AsyncIterator[bytes]:\n        await asyncio.sleep(0.00001)\n        yield b\"result\"\n\n    req = make_client_request(\"POST\", URL(\"http://python.org/\"), data=gen(), loop=loop)\n    resp = await req._send(conn)\n    await req._close()\n    assert buf.split(b\"\\r\\n\\r\\n\", 1)[1] == b\"6\\r\\nresult\\r\\n0\\r\\n\\r\\n\"\n    await req._close()\n    resp.close()\n\n\nasync def test_bad_version(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"GET\",\n        URL(\"http://python.org\"),\n        loop=loop,\n        headers=CIMultiDict({\"Connection\": \"Close\"}),\n        version=(\"1\", \"1\\r\\nInjected-Header: not allowed\"),  # type: ignore[arg-type]\n    )\n\n    with pytest.raises(AttributeError):\n        await req._send(conn)\n\n\nasync def test_custom_response_class(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    class CustomResponse(ClientResponse):\n        async def read(self) -> bytes:\n            return b\"customized!\"\n\n    req = make_client_request(\n        \"GET\", URL(\"http://python.org/\"), response_class=CustomResponse, loop=loop\n    )\n    resp = await req._send(conn)\n    assert await resp.read() == b\"customized!\"\n    await req._close()\n    resp.close()\n\n\nasync def test_oserror_on_write_bytes(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\"POST\", URL(\"http://python.org/\"), loop=loop)\n    await req.update_body(b\"test data\")\n\n    writer = WriterMock()\n    writer.write.side_effect = OSError\n\n    await req._write_bytes(writer, conn, None)\n\n    assert conn.protocol.set_exception.called\n    exc = conn.protocol.set_exception.call_args[0][0]\n    assert isinstance(exc, aiohttp.ClientOSError)\n\n\n@pytest.mark.skipif(sys.version_info < (3, 11), reason=\"Needs Task.cancelling()\")\nasync def test_cancel_close(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org\"), loop=loop)\n    req._writer = asyncio.Future()  # type: ignore[assignment]\n\n    t = asyncio.create_task(req._close())\n\n    # Start waiting on _writer\n    await asyncio.sleep(0)\n\n    t.cancel()\n    # Cancellation should not be suppressed.\n    with pytest.raises(asyncio.CancelledError):\n        await t\n\n\nasync def test_terminate(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org\"), loop=loop)\n\n    async def _mock_write_bytes(*args: object, **kwargs: object) -> None:\n        # Ensure the task is scheduled\n        await asyncio.sleep(0)\n\n    with mock.patch.object(req, \"_write_bytes\", _mock_write_bytes):\n        resp = await req._send(conn)\n\n    assert req._writer is not None\n    assert resp._writer is not None\n    await resp._writer\n    writer = WriterMock()\n    writer.done = mock.Mock(return_value=False)\n    writer.cancel = mock.Mock()\n    req._writer = writer\n    resp._writer = writer\n\n    assert req._writer is not None\n    assert resp._writer is not None\n    req._terminate()\n    writer.cancel.assert_called_with()\n    writer.done.assert_called_with()\n    resp.close()\n\n\ndef test_terminate_with_closed_loop(\n    loop: asyncio.AbstractEventLoop,\n    conn: mock.Mock,\n) -> None:\n    req = resp = writer = None\n\n    async def go() -> None:\n        nonlocal req, resp, writer\n        # Can't use make_client_request here, due to closing the loop mid-test.\n        req = ClientRequest(\n            \"get\",\n            URL(\"http://python.org\"),\n            loop=loop,\n            params={},\n            headers=CIMultiDict[str](),\n            skip_auto_headers=None,\n            data=None,\n            cookies=BaseCookie[str](),\n            auth=None,\n            version=HttpVersion11,\n            compress=False,\n            chunked=None,\n            expect100=False,\n            response_class=ClientResponse,\n            proxy=None,\n            proxy_auth=None,\n            timer=TimerNoop(),\n            session=None,  # type: ignore[arg-type]\n            ssl=True,\n            proxy_headers=None,\n            traces=[],\n            trust_env=False,\n            server_hostname=None,\n        )\n\n        async def _mock_write_bytes(*args: object, **kwargs: object) -> None:\n            # Ensure the task is scheduled\n            await asyncio.sleep(0)\n\n        with mock.patch.object(req, \"_write_bytes\", _mock_write_bytes):\n            resp = await req._send(conn)\n\n        assert req._writer is not None\n        writer = WriterMock()\n        writer.done = mock.Mock(return_value=False)\n        req._writer = writer\n        resp._writer = writer\n\n        await asyncio.sleep(0.05)\n\n    loop.run_until_complete(go())\n\n    loop.close()\n    assert req is not None\n    req._terminate()\n    assert req._writer is None\n    assert writer is not None\n    assert not writer.cancel.called\n    assert resp is not None\n    resp.close()\n\n\nasync def test_terminate_without_writer(make_client_request: _RequestMaker) -> None:\n    req = make_client_request(\"get\", URL(\"http://python.org\"))\n    assert req._writer is None\n\n    req._terminate()\n    assert req._writer is None\n\n\nasync def test_custom_req_rep(\n    loop: asyncio.AbstractEventLoop, create_mocked_conn: mock.Mock\n) -> None:\n    conn = None\n\n    class CustomResponse(ClientResponse):\n        async def start(self, connection: Connection) -> ClientResponse:\n            nonlocal conn\n            conn = connection\n            self.status = 123\n            self.reason = \"Test OK\"\n            self._headers = CIMultiDictProxy(CIMultiDict())\n            self.cookies = SimpleCookie()\n            return self\n\n    called = False\n\n    class CustomRequest(ClientRequest):\n        async def _send(self, conn: Connection) -> ClientResponse:\n            resp = self.response_class(\n                self.method,\n                self.url,\n                writer=self._writer,\n                continue100=self._continue,\n                timer=self._timer,\n                traces=self._traces,\n                loop=self.loop,\n                session=self._session,\n                request_headers=self.headers,\n                original_url=self.original_url,\n            )\n            self.response = resp\n            nonlocal called\n            called = True\n            return resp\n\n    async def create_connection(\n        req: ClientRequest, traces: object, timeout: object\n    ) -> Connection:\n        assert isinstance(req, CustomRequest)\n        return create_mocked_conn()  # type: ignore[no-any-return]\n\n    connector = BaseConnector()\n    with mock.patch.object(connector, \"_create_connection\", create_connection):\n        session = aiohttp.ClientSession(\n            request_class=CustomRequest,\n            response_class=CustomResponse,\n            connector=connector,\n        )\n\n        resp = await session.request(\"get\", URL(\"http://example.com/path/to\"))\n        assert isinstance(resp, CustomResponse)\n        assert called\n        resp.close()\n        await session.close()\n        assert conn is not None\n        conn.close()\n\n\ndef test_bad_fingerprint(loop: asyncio.AbstractEventLoop) -> None:\n    with pytest.raises(ValueError):\n        Fingerprint(b\"invalid\")\n\n\ndef test_insecure_fingerprint_md5(loop: asyncio.AbstractEventLoop) -> None:\n    with pytest.raises(ValueError):\n        Fingerprint(hashlib.md5(b\"foo\").digest())\n\n\ndef test_insecure_fingerprint_sha1(loop: asyncio.AbstractEventLoop) -> None:\n    with pytest.raises(ValueError):\n        Fingerprint(hashlib.sha1(b\"foo\").digest())\n\n\n@pytest.mark.parametrize(\n    \"has_brotli,has_zstd,expected\",\n    [\n        (False, False, \"gzip, deflate\"),\n        (True, False, \"gzip, deflate, br\"),\n        (False, True, \"gzip, deflate, zstd\"),\n        (True, True, \"gzip, deflate, br, zstd\"),\n    ],\n)\ndef test_gen_default_accept_encoding(\n    has_brotli: bool, has_zstd: bool, expected: str\n) -> None:\n    with mock.patch(\"aiohttp.client_reqrep.HAS_BROTLI\", has_brotli):\n        with mock.patch(\"aiohttp.client_reqrep.HAS_ZSTD\", has_zstd):\n            assert _gen_default_accept_encoding() == expected\n\n\n@pytest.mark.parametrize(\n    \"netrc_contents\",\n    (\"machine example.com login username password pass\\n\",),\n    indirect=(\"netrc_contents\",),\n)\n@pytest.mark.usefixtures(\"netrc_contents\")\nasync def test_basicauth_from_netrc_present_untrusted_env(  # type: ignore[misc]\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test no authorization header is sent via netrc if trust_env is False\"\"\"\n    req = make_client_request(\"get\", URL(\"http://example.com\"), trust_env=False)\n    assert hdrs.AUTHORIZATION not in req.headers\n\n\n@pytest.mark.parametrize(\n    \"netrc_contents\",\n    (\"\",),\n    indirect=(\"netrc_contents\",),\n)\n@pytest.mark.usefixtures(\"netrc_contents\")\nasync def test_basicauth_from_empty_netrc(  # type: ignore[misc]\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that no Authorization header is sent when netrc is empty\"\"\"\n    req = make_client_request(\"get\", URL(\"http://example.com\"), trust_env=True)\n    assert hdrs.AUTHORIZATION not in req.headers\n\n\nasync def test_connection_key_with_proxy(\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Verify the proxy headers are included in the ConnectionKey when a proxy is used.\"\"\"\n    proxy = URL(\"http://proxy.example.com\")\n    req = make_client_request(\n        \"GET\",\n        URL(\"http://example.com\"),\n        proxy=proxy,\n        proxy_headers=CIMultiDict({\"X-Proxy\": \"true\"}),\n        loop=asyncio.get_running_loop(),\n    )\n    assert req.connection_key.proxy_headers_hash is not None\n    await req._close()\n\n\nasync def test_connection_key_without_proxy(\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Verify the proxy headers are not included in the ConnectionKey when a proxy is used.\"\"\"\n    # If proxy is unspecified, proxy_headers should be ignored\n    req = make_client_request(\n        \"GET\",\n        URL(\"http://example.com\"),\n        proxy_headers=CIMultiDict({\"X-Proxy\": \"true\"}),\n        loop=asyncio.get_running_loop(),\n    )\n    assert req.connection_key.proxy_headers_hash is None\n    await req._close()\n\n\ndef test_request_info_back_compat() -> None:\n    \"\"\"Test RequestInfo can be created without real_url.\"\"\"\n    url = URL(\"http://example.com\")\n    other_url = URL(\"http://example.org\")\n    assert (\n        aiohttp.RequestInfo(\n            url=url, method=\"GET\", headers=CIMultiDictProxy(CIMultiDict())\n        ).real_url\n        is url\n    )\n    assert (\n        aiohttp.RequestInfo(url, \"GET\", CIMultiDictProxy(CIMultiDict())).real_url is url\n    )\n    assert (\n        aiohttp.RequestInfo(\n            url, \"GET\", CIMultiDictProxy(CIMultiDict()), real_url=url\n        ).real_url\n        is url\n    )\n    assert (\n        aiohttp.RequestInfo(\n            url, \"GET\", CIMultiDictProxy(CIMultiDict()), real_url=other_url\n        ).real_url\n        is other_url\n    )\n\n\ndef test_request_info_tuple_new() -> None:\n    \"\"\"Test RequestInfo must be created with real_url using tuple.__new__.\"\"\"\n    url = URL(\"http://example.com\")\n    with pytest.raises(IndexError):\n        tuple.__new__(\n            aiohttp.RequestInfo, (url, \"GET\", CIMultiDictProxy(CIMultiDict()))\n        ).real_url\n\n    assert (\n        tuple.__new__(\n            aiohttp.RequestInfo, (url, \"GET\", CIMultiDictProxy(CIMultiDict()), url)\n        ).real_url\n        is url\n    )\n\n\nasync def test_get_content_length(make_client_request: _RequestMaker) -> None:\n    \"\"\"Test _get_content_length method extracts Content-Length correctly.\"\"\"\n    req = make_client_request(\"get\", URL(\"http://python.org/\"))\n\n    # No Content-Length header\n    assert req._get_content_length() is None\n\n    # Valid Content-Length header\n    req.headers[\"Content-Length\"] = \"42\"\n    assert req._get_content_length() == 42\n\n    # Invalid Content-Length header\n    req.headers[\"Content-Length\"] = \"invalid\"\n    with pytest.raises(ValueError, match=\"Invalid Content-Length header: invalid\"):\n        req._get_content_length()\n\n\nasync def test_write_bytes_with_content_length_limit(\n    loop: asyncio.AbstractEventLoop,\n    buf: bytearray,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that write_bytes respects content_length limit for different body types.\"\"\"\n    # Test with bytes data\n    data = b\"Hello World\"\n    req = make_client_request(\"post\", URL(\"http://python.org/\"), loop=loop)\n\n    await req.update_body(data)\n\n    writer = StreamWriter(protocol=conn.protocol, loop=loop)\n    # Use content_length=5 to truncate data\n    await req._write_bytes(writer, conn, 5)\n\n    # Verify only the first 5 bytes were written\n    assert buf == b\"Hello\"\n    await req._close()\n\n\n@pytest.mark.parametrize(\n    \"data\",\n    [\n        [b\"Part1\", b\"Part2\", b\"Part3\"],\n        b\"Part1Part2Part3\",\n    ],\n)\nasync def test_write_bytes_with_iterable_content_length_limit(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop,\n    buf: bytearray,\n    conn: mock.Mock,\n    data: list[bytes] | bytes,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that write_bytes respects content_length limit for iterable data.\"\"\"\n    # Test with iterable data\n    req = make_client_request(\"post\", URL(\"http://python.org/\"), loop=loop)\n\n    # Convert list to async generator if needed\n    if isinstance(data, list):\n\n        async def gen() -> AsyncIterator[bytes]:\n            for chunk in data:\n                yield chunk\n\n        await req.update_body(gen())\n    else:\n        await req.update_body(data)\n\n    writer = StreamWriter(protocol=conn.protocol, loop=loop)\n    # Use content_length=7 to truncate at the middle of Part2\n    await req._write_bytes(writer, conn, 7)\n    assert len(buf) == 7\n    assert buf == b\"Part1Pa\"\n    await req._close()\n\n\nasync def test_write_bytes_empty_iterable_with_content_length(\n    loop: asyncio.AbstractEventLoop,\n    buf: bytearray,\n    conn: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that write_bytes handles empty iterable body with content_length.\"\"\"\n    req = make_client_request(\"post\", URL(\"http://python.org/\"), loop=loop)\n\n    # Create an empty async generator\n    async def gen() -> AsyncIterator[bytes]:\n        return\n        yield  # pragma: no cover  # This makes it a generator but never executes\n\n    await req.update_body(gen())\n\n    writer = StreamWriter(protocol=conn.protocol, loop=loop)\n    # Use content_length=10 with empty body\n    await req._write_bytes(writer, conn, 10)\n\n    # Verify nothing was written\n    assert len(buf) == 0\n    await req._close()\n\n\nasync def test_update_body_closes_previous_payload(\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that update_body properly closes the previous payload.\"\"\"\n    req = make_client_request(\"POST\", URL(\"http://python.org/\"))\n\n    # Create a mock payload that tracks if it was closed\n    mock_payload = mock.create_autospec(payload.Payload, spec_set=True, instance=True)\n\n    # Set initial payload\n    req._body = mock_payload\n\n    # Update body with new data\n    await req.update_body(b\"new body data\")\n\n    # Verify the previous payload was closed\n    mock_payload.close.assert_called_once()\n\n    # Verify new body is set (it's a BytesPayload now)\n    assert isinstance(req.body, payload.BytesPayload)\n\n    await req._close()\n\n\nasync def test_update_body_with_different_types(\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test update_body with various data types.\"\"\"\n    req = make_client_request(\"POST\", URL(\"http://python.org/\"))\n\n    # Test with bytes\n    await req.update_body(b\"bytes data\")\n    assert isinstance(req.body, payload.BytesPayload)\n\n    # Test with string\n    await req.update_body(\"string data\")\n    assert isinstance(req.body, payload.BytesPayload)\n\n    # Test with None (clears body)\n    await req.update_body(None)\n    assert req.body._value == b\"\"\n\n    await req._close()\n\n\nasync def test_update_body_with_chunked_encoding(\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that update_body properly handles chunked transfer encoding.\"\"\"\n    # Create request with chunked=True\n    req = make_client_request(\"POST\", URL(\"http://python.org/\"), chunked=True)\n\n    # Verify Transfer-Encoding header is set\n    assert req.headers[\"Transfer-Encoding\"] == \"chunked\"\n    assert \"Content-Length\" not in req.headers\n\n    # Update body - should maintain chunked encoding\n    await req.update_body(b\"chunked data\")\n    assert req.headers[\"Transfer-Encoding\"] == \"chunked\"\n    assert \"Content-Length\" not in req.headers\n    assert isinstance(req.body, payload.BytesPayload)\n\n    # Update with different body - chunked should remain\n    await req.update_body(b\"different chunked data\")\n    assert req.headers[\"Transfer-Encoding\"] == \"chunked\"\n    assert \"Content-Length\" not in req.headers\n\n    # Clear body - chunked header should remain\n    await req.update_body(None)\n    assert req.headers[\"Transfer-Encoding\"] == \"chunked\"\n    assert \"Content-Length\" not in req.headers\n\n    await req._close()\n\n\nasync def test_update_body_get_method_with_none_body(\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that update_body with GET method and None body doesn't call update_transfer_encoding.\"\"\"\n    # Create GET request\n    req = make_client_request(\"GET\", URL(\"http://python.org/\"))\n\n    # GET requests shouldn't have Transfer-Encoding or Content-Length initially\n    assert \"Transfer-Encoding\" not in req.headers\n    assert \"Content-Length\" not in req.headers\n\n    # Update body to None - should not trigger update_transfer_encoding\n    # This covers the branch where body is None AND method is in GET_METHODS\n    await req.update_body(None)\n\n    # Headers should remain unchanged\n    assert \"Transfer-Encoding\" not in req.headers\n    assert \"Content-Length\" not in req.headers\n\n    await req._close()\n\n\nasync def test_update_body_updates_content_length(\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that update_body properly updates Content-Length header when body size changes.\"\"\"\n    req = make_client_request(\"POST\", URL(\"http://python.org/\"))\n\n    # Set initial body with known size\n    await req.update_body(b\"initial data\")\n    initial_content_length = req.headers.get(\"Content-Length\")\n    assert initial_content_length == \"12\"  # len(b\"initial data\") = 12\n\n    # Update body with different size\n    await req.update_body(b\"much longer data than before\")\n    new_content_length = req.headers.get(\"Content-Length\")\n    assert new_content_length == \"28\"  # len(b\"much longer data than before\") = 28\n\n    # Update body with shorter data\n    await req.update_body(b\"short\")\n    assert req.headers.get(\"Content-Length\") == \"5\"  # len(b\"short\") = 5\n\n    # Clear body\n    await req.update_body(None)\n    # For None body with POST method, Content-Length should be set to 0\n    assert req.headers[hdrs.CONTENT_LENGTH] == \"0\"\n\n    await req._close()\n\n\nasync def test_expect100_with_body_becomes_empty(\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that write_bytes handles body becoming empty after expect100 handling.\"\"\"\n    # Create a mock writer and connection\n    mock_writer = mock.create_autospec(StreamWriter, instance=True, spec_set=True)\n    mock_conn = mock.Mock()\n\n    # Create a request\n    req = make_client_request(\n        \"POST\", URL(\"http://test.example.com/\"), loop=asyncio.get_event_loop()\n    )\n    req._body = mock.Mock()  # Start with a body\n\n    # Now set body to empty payload to simulate a race condition\n    # where req._body is set to None after expect100 handling\n    req._body = payload.PAYLOAD_REGISTRY.get(b\"\", disposition=None)\n\n    await req._write_bytes(mock_writer, mock_conn, None)\n\n\n@pytest.mark.parametrize(\n    (\"method\", \"data\", \"expected_content_length\"),\n    [\n        # GET methods should not have Content-Length with None body\n        (\"GET\", None, None),\n        (\"HEAD\", None, None),\n        (\"OPTIONS\", None, None),\n        (\"TRACE\", None, None),\n        # POST methods should have Content-Length: 0 with None body\n        (\"POST\", None, \"0\"),\n        (\"PUT\", None, \"0\"),\n        (\"PATCH\", None, \"0\"),\n        (\"DELETE\", None, \"0\"),\n        # Empty bytes should always set Content-Length: 0\n        (\"GET\", b\"\", \"0\"),\n        (\"HEAD\", b\"\", \"0\"),\n        (\"POST\", b\"\", \"0\"),\n        (\"PUT\", b\"\", \"0\"),\n        # Non-empty bytes should set appropriate Content-Length\n        (\"GET\", b\"test\", \"4\"),\n        (\"POST\", b\"test\", \"4\"),\n        (\"PUT\", b\"hello world\", \"11\"),\n        (\"PATCH\", b\"data\", \"4\"),\n        (\"DELETE\", b\"x\", \"1\"),\n    ],\n)\nasync def test_content_length_for_methods(  # type: ignore[misc]\n    method: str,\n    data: bytes | None,\n    expected_content_length: str | None,\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that Content-Length header is set correctly for all HTTP methods.\"\"\"\n    req = make_client_request(method, URL(\"http://python.org/\"), data=data, loop=loop)\n\n    actual_content_length = req.headers.get(hdrs.CONTENT_LENGTH)\n    assert actual_content_length == expected_content_length\n\n\n@pytest.mark.parametrize(\"method\", [\"GET\", \"HEAD\", \"OPTIONS\", \"TRACE\"])\ndef test_get_methods_classification(method: str) -> None:\n    \"\"\"Test that GET-like methods are correctly classified.\"\"\"\n    assert method in ClientRequest.GET_METHODS\n\n\n@pytest.mark.parametrize(\"method\", [\"POST\", \"PUT\", \"PATCH\", \"DELETE\"])\ndef test_non_get_methods_classification(method: str) -> None:\n    \"\"\"Test that POST-like methods are not in GET_METHODS.\"\"\"\n    assert method not in ClientRequest.GET_METHODS\n\n\nasync def test_content_length_with_string_data(\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test Content-Length when data is a string.\"\"\"\n    data = \"Hello, World!\"\n    req = make_client_request(\"POST\", URL(\"http://python.org/\"), data=data, loop=loop)\n    # String should be encoded to bytes, default encoding is utf-8\n    assert req.headers[hdrs.CONTENT_LENGTH] == str(len(data.encode(\"utf-8\")))\n    await req._close()\n\n\nasync def test_content_length_with_async_iterable(\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that async iterables use chunked encoding, not Content-Length.\"\"\"\n\n    async def data_gen() -> AsyncIterator[bytes]:\n        yield b\"chunk1\"  # pragma: no cover\n\n    req = make_client_request(\n        \"POST\", URL(\"http://python.org/\"), data=data_gen(), loop=loop\n    )\n    assert hdrs.CONTENT_LENGTH not in req.headers\n    assert req.chunked\n    assert req.headers[hdrs.TRANSFER_ENCODING] == \"chunked\"\n    await req._close()\n\n\nasync def test_content_length_not_overridden(\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that explicitly set Content-Length is not overridden.\"\"\"\n    req = make_client_request(\n        \"POST\",\n        URL(\"http://python.org/\"),\n        data=b\"test\",\n        headers=CIMultiDict({hdrs.CONTENT_LENGTH: \"100\"}),\n        loop=loop,\n    )\n    # Should keep the explicitly set value\n    assert req.headers[hdrs.CONTENT_LENGTH] == \"100\"\n    await req._close()\n\n\nasync def test_content_length_with_formdata(\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test Content-Length with FormData.\"\"\"\n    form = aiohttp.FormData()\n    form.add_field(\"field\", \"value\")\n\n    req = make_client_request(\"POST\", URL(\"http://python.org/\"), data=form, loop=loop)\n    # FormData with known size should set Content-Length\n    assert hdrs.CONTENT_LENGTH in req.headers\n    await req._close()\n\n\nasync def test_no_content_length_with_chunked(\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that chunked encoding prevents Content-Length header.\"\"\"\n    req = make_client_request(\n        \"POST\",\n        URL(\"http://python.org/\"),\n        data=b\"test\",\n        chunked=True,\n        loop=loop,\n    )\n    assert hdrs.CONTENT_LENGTH not in req.headers\n    assert req.headers[hdrs.TRANSFER_ENCODING] == \"chunked\"\n    await req._close()\n\n\n@pytest.mark.parametrize(\"method\", [\"POST\", \"PUT\", \"PATCH\", \"DELETE\"])\nasync def test_update_body_none_sets_content_length_zero(  # type: ignore[misc]\n    method: str,\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that updating body to None sets Content-Length: 0 for POST-like methods.\"\"\"\n    # Create request with initial body\n    req = make_client_request(\n        method, URL(\"http://python.org/\"), data=b\"initial\", loop=loop\n    )\n    assert req.headers[hdrs.CONTENT_LENGTH] == \"7\"\n\n    # Update body to None\n    await req.update_body(None)\n    assert req.headers[hdrs.CONTENT_LENGTH] == \"0\"\n    await req._close()\n\n\n@pytest.mark.parametrize(\"method\", [\"GET\", \"HEAD\", \"OPTIONS\", \"TRACE\"])\nasync def test_update_body_none_no_content_length_for_get_methods(  # type: ignore[misc]\n    method: str,\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that updating body to None doesn't set Content-Length for GET-like methods.\"\"\"\n    # Create request with initial body\n    req = make_client_request(\n        method, URL(\"http://python.org/\"), data=b\"initial\", loop=loop\n    )\n    assert req.headers[hdrs.CONTENT_LENGTH] == \"7\"\n\n    # Update body to None\n    await req.update_body(None)\n    assert hdrs.CONTENT_LENGTH not in req.headers\n    await req._close()\n\n\nasync def test_multiple_requests_share_empty_body_safely(\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that multiple ClientRequest objects safely share the empty body payload.\"\"\"\n    requests: list[ClientRequest] = []\n    for i in range(5):\n        req = make_client_request(\"GET\", URL(f\"http://example.com/path{i}\"))\n        requests.append(req)\n\n    empty_body = ClientRequest._EMPTY_BODY\n    for i, req in enumerate(requests):\n        assert req.body is empty_body, f\"Request {i} has different empty body\"\n        assert req.body.size == 0\n        assert req.body.consumed is False\n\n    assert empty_body.consumed is False\n    assert empty_body.size == 0\n\n\nasync def test_empty_body_isolation_after_update(\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that updating one request's body doesn't affect other requests.\"\"\"\n    req1 = make_client_request(\"POST\", URL(\"http://example.com/1\"))\n    req2 = make_client_request(\"POST\", URL(\"http://example.com/2\"))\n\n    assert req1.body is ClientRequest._EMPTY_BODY\n    assert req2.body is ClientRequest._EMPTY_BODY\n\n    await req1.update_body(b\"new data\")\n\n    assert req1.body is not ClientRequest._EMPTY_BODY\n    assert req1.body.size == 8\n\n    assert req2.body is ClientRequest._EMPTY_BODY\n    assert req2.body.size == 0\n    assert req2.body.consumed is False\n\n    assert ClientRequest._EMPTY_BODY.consumed is False\n    assert ClientRequest._EMPTY_BODY.size == 0\n"
  },
  {
    "path": "tests/test_client_response.py",
    "content": "# Tests for aiohttp/client.py\n\nimport asyncio\nimport gc\nimport sys\nfrom http.cookies import SimpleCookie\nfrom json import JSONDecodeError\nfrom unittest import mock\n\nimport pytest\nfrom multidict import CIMultiDict, CIMultiDictProxy\nfrom pytest_mock import MockerFixture\nfrom yarl import URL\n\nimport aiohttp\nfrom aiohttp import ClientSession, hdrs, http\nfrom aiohttp.client_reqrep import ClientResponse\nfrom aiohttp.connector import Connection\nfrom aiohttp.helpers import TimerNoop\nfrom aiohttp.multipart import BadContentDispositionHeader\nfrom aiohttp.tracing import Trace\n\n\nclass WriterMock(mock.AsyncMock):\n    def done(self) -> bool:\n        return True\n\n\n@pytest.fixture\ndef session() -> mock.Mock:\n    return mock.Mock()\n\n\nasync def test_http_processing_error(session: ClientSession) -> None:\n    loop = mock.Mock()\n    url = URL(\"http://del-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    loop.get_debug = mock.Mock()\n    loop.get_debug.return_value = True\n\n    connection = mock.Mock()\n    connection.protocol = aiohttp.DataQueue(loop)\n    connection.protocol.set_exception(http.HttpProcessingError())\n\n    with pytest.raises(aiohttp.ClientResponseError) as info:\n        await response.start(connection)\n\n    assert info.value.request_info.url is url\n    response.close()\n\n\ndef test_del(session: ClientSession) -> None:\n    loop = mock.Mock()\n    url = URL(\"http://del-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    loop.get_debug = mock.Mock()\n    loop.get_debug.return_value = True\n\n    connection = mock.Mock()\n    response._closed = False\n    response._connection = connection\n    loop.set_exception_handler(lambda loop, ctx: None)\n\n    with pytest.warns(ResourceWarning):\n        del response\n        gc.collect()\n\n    connection.release.assert_called_with()\n\n\ndef test_close(loop: asyncio.AbstractEventLoop, session: ClientSession) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    response._closed = False\n    response._connection = mock.Mock()\n    response.close()\n    assert response.connection is None\n    response.close()\n    response.close()\n\n\ndef test_wait_for_100_1(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://python.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        continue100=loop.create_future(),\n        writer=WriterMock(),\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    assert response._continue is not None\n    response.close()\n\n\ndef test_wait_for_100_2(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://python.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        continue100=None,\n        writer=WriterMock(),\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    assert response._continue is None\n    response.close()\n\n\ndef test_repr(loop: asyncio.AbstractEventLoop, session: ClientSession) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    response.status = 200\n    response.reason = \"Ok\"\n    assert \"<ClientResponse(http://def-cl-resp.org) [200 Ok]>\" in repr(response)\n\n\ndef test_repr_non_ascii_url() -> None:\n    url = URL(\"http://fake-host.org/\\u03bb\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    assert \"<ClientResponse(http://fake-host.org/%CE%BB) [None None]>\" in repr(response)\n\n\ndef test_repr_non_ascii_reason() -> None:\n    url = URL(\"http://fake-host.org/path\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    response.reason = \"\\u03bb\"\n    assert \"<ClientResponse(http://fake-host.org/path) [None \\\\u03bb]>\" in repr(\n        response\n    )\n\n\nasync def test_read_and_release_connection(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    def side_effect(*args: object, **kwargs: object) -> \"asyncio.Future[bytes]\":\n        fut = loop.create_future()\n        fut.set_result(b\"payload\")\n        return fut\n\n    content = response.content = mock.Mock()\n    content.read.side_effect = side_effect\n\n    res = await response.read()\n    assert res == b\"payload\"\n    assert response._connection is None\n\n\nasync def test_read_and_release_connection_with_error(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    content = response.content = mock.Mock()\n    content.read.return_value = loop.create_future()\n    content.read.return_value.set_exception(ValueError)\n\n    with pytest.raises(ValueError):\n        await response.read()\n    assert response._closed\n\n\nasync def test_release(loop: asyncio.AbstractEventLoop, session: ClientSession) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    fut = loop.create_future()\n    fut.set_result(b\"\")\n    content = response.content = mock.Mock()\n    content.readany.return_value = fut\n\n    response.release()\n    assert response._connection is None\n\n\n@pytest.mark.skipif(\n    sys.implementation.name != \"cpython\",\n    reason=\"Other implementations has different GC strategies\",\n)\nasync def test_release_on_del(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    connection = mock.Mock()\n    connection.protocol.upgraded = False\n\n    def run(conn: Connection) -> None:\n        url = URL(\"http://def-cl-resp.org\")\n        response = ClientResponse(\n            \"get\",\n            url,\n            writer=WriterMock(),\n            continue100=None,\n            timer=TimerNoop(),\n            traces=[],\n            loop=loop,\n            session=session,\n            request_headers=CIMultiDict[str](),\n            original_url=url,\n        )\n        response._closed = False\n        response._connection = conn\n\n    run(connection)\n\n    assert connection.release.called\n\n\nasync def test_response_eof(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=None,\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    response._closed = False\n    conn = response._connection = mock.Mock()\n    conn.protocol.upgraded = False\n\n    response._response_eof()\n    assert conn.release.called\n    assert response._connection is None\n\n\nasync def test_response_eof_upgraded(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    conn = response._connection = mock.Mock()\n    conn.protocol.upgraded = True\n\n    response._response_eof()\n    assert not conn.release.called\n    assert response._connection is conn\n\n\nasync def test_response_eof_after_connection_detach(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=None,\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    response._closed = False\n    conn = response._connection = mock.Mock()\n    conn.protocol = None\n\n    response._response_eof()\n    assert conn.release.called\n    assert response._connection is None\n\n\nasync def test_text(loop: asyncio.AbstractEventLoop, session: ClientSession) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    def side_effect(*args: object, **kwargs: object) -> \"asyncio.Future[bytes]\":\n        fut = loop.create_future()\n        fut.set_result('{\"тест\": \"пройден\"}'.encode(\"cp1251\"))\n        return fut\n\n    h = {\"Content-Type\": \"application/json;charset=cp1251\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    content = response.content = mock.Mock()\n    content.read.side_effect = side_effect\n\n    res = await response.text()\n    assert res == '{\"тест\": \"пройден\"}'\n    assert response._connection is None\n\n\nasync def test_text_bad_encoding(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    def side_effect(*args: object, **kwargs: object) -> \"asyncio.Future[bytes]\":\n        fut = loop.create_future()\n        fut.set_result('{\"тестkey\": \"пройденvalue\"}'.encode(\"cp1251\"))\n        return fut\n\n    # lie about the encoding\n    h = {\"Content-Type\": \"application/json;charset=utf-8\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    content = response.content = mock.Mock()\n    content.read.side_effect = side_effect\n    with pytest.raises(UnicodeDecodeError):\n        await response.text()\n    # only the valid utf-8 characters will be returned\n    res = await response.text(errors=\"ignore\")\n    assert res == '{\"key\": \"value\"}'\n    assert response._connection is None\n\n\nasync def test_text_badly_encoded_encoding_header(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    session._resolve_charset = lambda *_: \"utf-8\"\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    def side_effect(*args: object, **kwargs: object) -> \"asyncio.Future[bytes]\":\n        fut = loop.create_future()\n        fut.set_result(b\"foo\")\n        return fut\n\n    h = {\"Content-Type\": \"text/html; charset=\\udc81gutf-8\\udc81\\udc8d\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    content = response.content = mock.Mock()\n    content.read.side_effect = side_effect\n\n    await response.read()\n    encoding = response.get_encoding()\n\n    assert encoding == \"utf-8\"\n\n\nasync def test_text_custom_encoding(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    def side_effect(*args: object, **kwargs: object) -> \"asyncio.Future[bytes]\":\n        fut = loop.create_future()\n        fut.set_result('{\"тест\": \"пройден\"}'.encode(\"cp1251\"))\n        return fut\n\n    h = {\"Content-Type\": \"application/json\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    content = response.content = mock.Mock()\n    content.read.side_effect = side_effect\n    with mock.patch.object(response, \"get_encoding\") as m:\n        res = await response.text(encoding=\"cp1251\")\n        assert res == '{\"тест\": \"пройден\"}'\n        assert response._connection is None\n        assert not m.called\n\n\n@pytest.mark.parametrize(\"content_type\", (\"text/plain\", \"text/plain;charset=invalid\"))\nasync def test_text_charset_resolver(\n    content_type: str, loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    session._resolve_charset = lambda r, b: \"cp1251\"\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    def side_effect(*args: object, **kwargs: object) -> \"asyncio.Future[bytes]\":\n        fut = loop.create_future()\n        fut.set_result('{\"тест\": \"пройден\"}'.encode(\"cp1251\"))\n        return fut\n\n    h = {\"Content-Type\": content_type}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    content = response.content = mock.Mock()\n    content.read.side_effect = side_effect\n\n    await response.read()\n    res = await response.text()\n    assert res == '{\"тест\": \"пройден\"}'\n    assert response._connection is None\n    assert response.get_encoding() == \"cp1251\"\n\n\nasync def test_get_encoding_body_none(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    h = {\"Content-Type\": \"text/html\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    content = response.content = mock.Mock()\n    content.read.side_effect = AssertionError\n\n    with pytest.raises(\n        RuntimeError,\n        match=\"^Cannot compute fallback encoding of a not yet read body$\",\n    ):\n        response.get_encoding()\n    assert response.closed\n\n\nasync def test_text_after_read(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    def side_effect(*args: object, **kwargs: object) -> \"asyncio.Future[bytes]\":\n        fut = loop.create_future()\n        fut.set_result('{\"тест\": \"пройден\"}'.encode(\"cp1251\"))\n        return fut\n\n    h = {\"Content-Type\": \"application/json;charset=cp1251\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    content = response.content = mock.Mock()\n    content.read.side_effect = side_effect\n\n    res = await response.text()\n    assert res == '{\"тест\": \"пройден\"}'\n    assert response._connection is None\n\n\nasync def test_json(loop: asyncio.AbstractEventLoop, session: ClientSession) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    def side_effect(*args: object, **kwargs: object) -> \"asyncio.Future[bytes]\":\n        fut = loop.create_future()\n        fut.set_result('{\"тест\": \"пройден\"}'.encode(\"cp1251\"))\n        return fut\n\n    h = {\"Content-Type\": \"application/json;charset=cp1251\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    content = response.content = mock.Mock()\n    content.read.side_effect = side_effect\n\n    res = await response.json()\n    assert res == {\"тест\": \"пройден\"}\n    assert response._connection is None\n\n\nasync def test_json_extended_content_type(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    def side_effect(*args: object, **kwargs: object) -> \"asyncio.Future[bytes]\":\n        fut = loop.create_future()\n        fut.set_result('{\"тест\": \"пройден\"}'.encode(\"cp1251\"))\n        return fut\n\n    h = {\"Content-Type\": \"application/this.is-1_content+subtype+json;charset=cp1251\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    content = response.content = mock.Mock()\n    content.read.side_effect = side_effect\n\n    res = await response.json()\n    assert res == {\"тест\": \"пройден\"}\n    assert response._connection is None\n\n\nasync def test_json_custom_content_type(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    def side_effect(*args: object, **kwargs: object) -> \"asyncio.Future[bytes]\":\n        fut = loop.create_future()\n        fut.set_result('{\"тест\": \"пройден\"}'.encode(\"cp1251\"))\n        return fut\n\n    h = {\"Content-Type\": \"custom/type;charset=cp1251\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    content = response.content = mock.Mock()\n    content.read.side_effect = side_effect\n\n    res = await response.json(content_type=\"custom/type\")\n    assert res == {\"тест\": \"пройден\"}\n    assert response._connection is None\n\n\nasync def test_json_custom_loader(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    h = {\"Content-Type\": \"application/json;charset=cp1251\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    response._body = b\"data\"\n\n    def custom(content: str) -> str:\n        return content + \"-custom\"\n\n    res = await response.json(loads=custom)\n    assert res == \"data-custom\"\n\n\nasync def test_json_invalid_content_type(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    h = {\"Content-Type\": \"data/octet-stream\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    response._body = b\"\"\n    response.status = 500\n\n    with pytest.raises(aiohttp.ContentTypeError) as info:\n        await response.json()\n\n    assert info.value.request_info == response.request_info\n    assert info.value.status == 500\n\n\nasync def test_json_no_content(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    h = {\"Content-Type\": \"application/json\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    response._body = b\"\"\n\n    with pytest.raises(JSONDecodeError):\n        await response.json(content_type=None)\n\n\nasync def test_json_override_encoding(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    def side_effect(*args: object, **kwargs: object) -> \"asyncio.Future[bytes]\":\n        fut = loop.create_future()\n        fut.set_result('{\"тест\": \"пройден\"}'.encode(\"cp1251\"))\n        return fut\n\n    h = {\"Content-Type\": \"application/json;charset=utf8\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    content = response.content = mock.Mock()\n    content.read.side_effect = side_effect\n    with mock.patch.object(response, \"get_encoding\") as m:\n        res = await response.json(encoding=\"cp1251\")\n        assert res == {\"тест\": \"пройден\"}\n        assert response._connection is None\n        assert not m.called\n\n\ndef test_get_encoding_unknown(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    h = {\"Content-Type\": \"application/json\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    assert response.get_encoding() == \"utf-8\"\n\n\ndef test_raise_for_status_2xx() -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    response.status = 200\n    response.reason = \"OK\"\n    response.raise_for_status()  # should not raise\n\n\ndef test_raise_for_status_4xx() -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    response.status = 409\n    response.reason = \"CONFLICT\"\n    with pytest.raises(aiohttp.ClientResponseError) as cm:\n        response.raise_for_status()\n    assert str(cm.value.status) == \"409\"\n    assert str(cm.value.message) == \"CONFLICT\"\n    assert response.closed\n\n\ndef test_raise_for_status_4xx_without_reason() -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    response.status = 404\n    response.reason = \"\"\n    with pytest.raises(aiohttp.ClientResponseError) as cm:\n        response.raise_for_status()\n    assert str(cm.value.status) == \"404\"\n    assert str(cm.value.message) == \"\"\n    assert response.closed\n\n\ndef test_resp_host() -> None:\n    url = URL(\"http://del-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    assert \"del-cl-resp.org\" == response.host\n\n\ndef test_content_type() -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    h = {\"Content-Type\": \"application/json;charset=cp1251\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n\n    assert \"application/json\" == response.content_type\n\n\ndef test_content_type_no_header() -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    response._headers = CIMultiDictProxy(CIMultiDict({}))\n\n    assert \"application/octet-stream\" == response.content_type\n\n\ndef test_charset() -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    h = {\"Content-Type\": \"application/json;charset=cp1251\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n\n    assert \"cp1251\" == response.charset\n\n\ndef test_charset_no_header() -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    response._headers = CIMultiDictProxy(CIMultiDict({}))\n\n    assert response.charset is None\n\n\ndef test_charset_no_charset() -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    h = {\"Content-Type\": \"application/json\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n\n    assert response.charset is None\n\n\ndef test_content_disposition_full() -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    h = {\"Content-Disposition\": 'attachment; filename=\"archive.tar.gz\"; foo=bar'}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n\n    assert response.content_disposition is not None\n    assert \"attachment\" == response.content_disposition.type\n    assert \"bar\" == response.content_disposition.parameters[\"foo\"]\n    assert \"archive.tar.gz\" == response.content_disposition.filename\n    with pytest.raises(TypeError):\n        response.content_disposition.parameters[\"foo\"] = \"baz\"  # type: ignore[index]\n\n\ndef test_content_disposition_no_parameters() -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    h = {\"Content-Disposition\": \"attachment\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n\n    assert response.content_disposition is not None\n    assert \"attachment\" == response.content_disposition.type\n    assert response.content_disposition.filename is None\n    assert {} == response.content_disposition.parameters\n\n\n@pytest.mark.parametrize(\n    \"content_disposition\",\n    (\n        'attachment; filename=\"archive.tar.gz\";',\n        'attachment;; filename=\"archive.tar.gz\"',\n    ),\n)\ndef test_content_disposition_empty_parts(content_disposition: str) -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    h = {\"Content-Disposition\": content_disposition}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n\n    with pytest.warns(BadContentDispositionHeader):\n        assert response.content_disposition is not None\n        assert \"attachment\" == response.content_disposition.type\n        assert \"archive.tar.gz\" == response.content_disposition.filename\n\n\ndef test_content_disposition_no_header() -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    response._headers = CIMultiDictProxy(CIMultiDict({}))\n\n    assert response.content_disposition is None\n\n\ndef test_default_encoding_is_utf8() -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=None,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    response._headers = CIMultiDictProxy(CIMultiDict({}))\n    response._body = b\"\"\n\n    assert response.get_encoding() == \"utf-8\"\n\n\ndef test_response_request_info() -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    h = {\"Content-Type\": \"application/json;charset=cp1251\"}\n    headers = CIMultiDict(h)\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=headers,\n        original_url=url,\n    )\n    assert url == response.request_info.url\n    assert \"get\" == response.request_info.method\n    assert headers == response.request_info.headers\n\n\ndef test_request_info_in_exception() -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    h = {\"Content-Type\": \"application/json;charset=cp1251\"}\n    headers = CIMultiDict(h)\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=headers,\n        original_url=url,\n    )\n    response.status = 409\n    response.reason = \"CONFLICT\"\n    with pytest.raises(aiohttp.ClientResponseError) as cm:\n        response.raise_for_status()\n    assert cm.value.request_info == response.request_info\n\n\ndef test_no_redirect_history_in_exception() -> None:\n    url = URL(\"http://def-cl-resp.org\")\n    h = {\"Content-Type\": \"application/json;charset=cp1251\"}\n    headers = CIMultiDict(h)\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=headers,\n        original_url=url,\n    )\n    response.status = 409\n    response.reason = \"CONFLICT\"\n    with pytest.raises(aiohttp.ClientResponseError) as cm:\n        response.raise_for_status()\n    assert () == cm.value.history\n\n\ndef test_redirect_history_in_exception() -> None:\n    hist_url = URL(\"http://def-cl-resp.org\")\n    u = \"http://def-cl-resp.org/index.htm\"\n    url = URL(u)\n    hist_headers = {\"Content-Type\": \"application/json;charset=cp1251\", \"Location\": u}\n    h = {\"Content-Type\": \"application/json;charset=cp1251\"}\n    headers = CIMultiDict(h)\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=headers,\n        original_url=url,\n    )\n    response.status = 409\n    response.reason = \"CONFLICT\"\n\n    hist_response = ClientResponse(\n        \"get\",\n        hist_url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=headers,\n        original_url=hist_url,\n    )\n\n    hist_response._headers = CIMultiDictProxy(CIMultiDict(hist_headers))\n    hist_response.status = 301\n    hist_response.reason = \"REDIRECT\"\n\n    response._history = (hist_response,)\n    with pytest.raises(aiohttp.ClientResponseError) as cm:\n        response.raise_for_status()\n    assert (hist_response,) == cm.value.history\n\n\nasync def test_response_read_triggers_callback(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    trace = mock.create_autospec(Trace, instance=True, spec_set=True)\n    response_method = \"get\"\n    response_url = URL(\"http://def-cl-resp.org\")\n    response_body = b\"This is response\"\n\n    response = ClientResponse(\n        response_method,\n        response_url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        loop=loop,\n        session=session,\n        traces=[trace],\n        request_headers=CIMultiDict[str](),\n        original_url=response_url,\n    )\n\n    def side_effect(*args: object, **kwargs: object) -> \"asyncio.Future[bytes]\":\n        fut = loop.create_future()\n        fut.set_result(response_body)\n        return fut\n\n    h = {\"Content-Type\": \"application/json;charset=cp1251\"}\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    content = response.content = mock.Mock()\n    content.read.side_effect = side_effect\n\n    res = await response.read()\n    assert res == response_body\n    assert response._connection is None\n\n    assert trace.send_response_chunk_received.called\n    assert trace.send_response_chunk_received.call_args == mock.call(\n        response_method, response_url, response_body\n    )\n\n\ndef test_response_cookies(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://python.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    cookies = response.cookies\n    # Ensure the same cookies object is returned each time\n    assert response.cookies is cookies\n\n\ndef test_response_real_url(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org/#urlfragment\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    assert response.url == url.with_fragment(None)\n    assert response.real_url == url\n\n\ndef test_response_links_comma_separated(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org/\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    h = (\n        (\n            \"Link\",\n            (\n                \"<http://example.com/page/1.html>; rel=next, \"\n                \"<http://example.com/>; rel=home\"\n            ),\n        ),\n    )\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    assert response.links == {\n        \"next\": {\"url\": URL(\"http://example.com/page/1.html\"), \"rel\": \"next\"},\n        \"home\": {\"url\": URL(\"http://example.com/\"), \"rel\": \"home\"},\n    }\n\n\ndef test_response_links_multiple_headers(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org/\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    h = (\n        (\"Link\", \"<http://example.com/page/1.html>; rel=next\"),\n        (\"Link\", \"<http://example.com/>; rel=home\"),\n    )\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    assert response.links == {\n        \"next\": {\"url\": URL(\"http://example.com/page/1.html\"), \"rel\": \"next\"},\n        \"home\": {\"url\": URL(\"http://example.com/\"), \"rel\": \"home\"},\n    }\n\n\ndef test_response_links_no_rel(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org/\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    h = ((\"Link\", \"<http://example.com/>\"),)\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    assert response.links == {\n        \"http://example.com/\": {\"url\": URL(\"http://example.com/\")}\n    }\n\n\ndef test_response_links_quoted(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org/\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    h = ((\"Link\", '<http://example.com/>; rel=\"home-page\"'),)\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    assert response.links == {\n        \"home-page\": {\"url\": URL(\"http://example.com/\"), \"rel\": \"home-page\"}\n    }\n\n\ndef test_response_links_relative(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org/\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    h = ((\"Link\", \"</relative/path>; rel=rel\"),)\n    response._headers = CIMultiDictProxy(CIMultiDict(h))\n    assert response.links == {\n        \"rel\": {\"url\": URL(\"http://def-cl-resp.org/relative/path\"), \"rel\": \"rel\"}\n    }\n\n\ndef test_response_links_empty(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    url = URL(\"http://def-cl-resp.org/\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    response._headers = CIMultiDictProxy(CIMultiDict())\n    assert response.links == {}\n\n\ndef test_response_not_closed_after_get_ok(mocker: MockerFixture) -> None:\n    url = URL(\"http://del-cl-resp.org\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=mock.Mock(),\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    response.status = 400\n    response.reason = \"Bad Request\"\n    response._closed = False\n    spy = mocker.spy(response, \"raise_for_status\")\n    assert not response.ok\n    assert not response.closed\n    assert spy.call_count == 0\n\n\ndef test_response_duplicate_cookie_names(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    \"\"\"\n    Test that response.cookies handles duplicate cookie names correctly.\n\n    Note: This behavior (losing cookies with same name but different domains/paths)\n    is arguably undesirable, but we promise to return a SimpleCookie object, and\n    SimpleCookie uses cookie name as the key. This is documented behavior.\n\n    To access all cookies including duplicates, users should use:\n    - response.headers.getall('Set-Cookie') for raw headers\n    - The session's cookie jar correctly stores all cookies\n    \"\"\"\n    url = URL(\"http://example.com\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    # Set headers with duplicate cookie names but different domains\n    headers = CIMultiDict(\n        [\n            (\n                \"Set-Cookie\",\n                \"session-id=123-4567890; Domain=.example.com; Path=/; Secure\",\n            ),\n            (\"Set-Cookie\", \"session-id=098-7654321; Domain=.www.example.com; Path=/\"),\n            (\"Set-Cookie\", \"user-pref=dark; Domain=.example.com; Path=/\"),\n            (\"Set-Cookie\", \"user-pref=light; Domain=api.example.com; Path=/\"),\n        ]\n    )\n    response._headers = CIMultiDictProxy(headers)\n    # Set raw cookie headers as done in ClientResponse.start()\n    response._raw_cookie_headers = tuple(headers.getall(\"Set-Cookie\", []))\n\n    # SimpleCookie only keeps the last cookie with each name\n    # This is expected behavior since SimpleCookie uses name as the key\n    assert len(response.cookies) == 2  # Only 'session-id' and 'user-pref'\n    assert response.cookies[\"session-id\"].value == \"098-7654321\"  # Last one wins\n    assert response.cookies[\"user-pref\"].value == \"light\"  # Last one wins\n\n\ndef test_response_raw_cookie_headers_preserved(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    \"\"\"Test that raw Set-Cookie headers are preserved in _raw_cookie_headers.\"\"\"\n    url = URL(\"http://example.com\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    # Set headers with multiple cookies\n    cookie_headers = [\n        \"session-id=123; Domain=.example.com; Path=/; Secure\",\n        \"session-id=456; Domain=.www.example.com; Path=/\",\n        \"tracking=xyz; Domain=.example.com; Path=/; HttpOnly\",\n    ]\n\n    headers: CIMultiDict[str] = CIMultiDict()\n    for cookie_hdr in cookie_headers:\n        headers.add(\"Set-Cookie\", cookie_hdr)\n\n    response._headers = CIMultiDictProxy(headers)\n\n    # Set raw cookie headers as done in ClientResponse.start()\n    response._raw_cookie_headers = tuple(response.headers.getall(hdrs.SET_COOKIE, []))\n\n    # Verify raw headers are preserved\n    assert response._raw_cookie_headers == tuple(cookie_headers)\n    assert len(response._raw_cookie_headers) == 3\n\n    # But SimpleCookie only has unique names\n    assert len(response.cookies) == 2  # 'session-id' and 'tracking'\n\n\ndef test_response_cookies_setter_updates_raw_headers(\n    loop: asyncio.AbstractEventLoop, session: ClientSession\n) -> None:\n    \"\"\"Test that setting cookies property updates _raw_cookie_headers.\"\"\"\n    url = URL(\"http://example.com\")\n    response = ClientResponse(\n        \"get\",\n        url,\n        writer=WriterMock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=loop,\n        session=session,\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n\n    # Create a SimpleCookie with some cookies\n    cookies = SimpleCookie()\n    cookies[\"session-id\"] = \"123456\"\n    cookies[\"session-id\"][\"domain\"] = \".example.com\"\n    cookies[\"session-id\"][\"path\"] = \"/\"\n    cookies[\"session-id\"][\"secure\"] = True\n\n    cookies[\"tracking\"] = \"xyz789\"\n    cookies[\"tracking\"][\"domain\"] = \".example.com\"\n    cookies[\"tracking\"][\"httponly\"] = True\n\n    # Set the cookies property\n    response.cookies = cookies\n\n    # Verify _raw_cookie_headers was updated\n    assert response._raw_cookie_headers is not None\n    assert len(response._raw_cookie_headers) == 2\n    assert isinstance(response._raw_cookie_headers, tuple)\n\n    # Check the raw headers contain the expected cookie strings\n    raw_headers = list(response._raw_cookie_headers)\n    assert any(\"session-id=123456\" in h for h in raw_headers)\n    assert any(\"tracking=xyz789\" in h for h in raw_headers)\n    assert any(\"Secure\" in h for h in raw_headers)\n    assert any(\"HttpOnly\" in h for h in raw_headers)\n\n    # Verify cookies property returns the same object\n    assert response.cookies is cookies\n\n    # Test setting empty cookies\n    empty_cookies = SimpleCookie()\n    response.cookies = empty_cookies\n    # Should not set _raw_cookie_headers for empty cookies\n    assert response._raw_cookie_headers is None\n"
  },
  {
    "path": "tests/test_client_session.py",
    "content": "import asyncio\nimport contextlib\nimport gc\nimport io\nimport json\nimport sys\nimport warnings\nfrom collections import deque\nfrom collections.abc import Awaitable, Callable, Iterator\nfrom http.cookies import BaseCookie, SimpleCookie\nfrom types import SimpleNamespace\nfrom typing import Any, NoReturn, TypedDict, cast\nfrom unittest import mock\nfrom uuid import uuid4\n\nimport pytest\nfrom multidict import CIMultiDict, MultiDict\nfrom pytest_mock import MockerFixture\nfrom yarl import URL\n\nimport aiohttp\nfrom aiohttp import abc, client, hdrs, tracing, web\nfrom aiohttp.client import ClientSession\nfrom aiohttp.client_proto import ResponseHandler\nfrom aiohttp.client_reqrep import ClientRequest, ConnectionKey\nfrom aiohttp.connector import BaseConnector, Connection, TCPConnector, UnixConnector\nfrom aiohttp.cookiejar import CookieJar\nfrom aiohttp.http import RawResponseMessage\nfrom aiohttp.payload import Payload\nfrom aiohttp.pytest_plugin import AiohttpClient, AiohttpServer\nfrom aiohttp.test_utils import TestServer\nfrom aiohttp.tracing import (\n    Trace,\n    TraceRequestChunkSentParams,\n    TraceRequestEndParams,\n    TraceRequestExceptionParams,\n    TraceRequestHeadersSentParams,\n    TraceRequestRedirectParams,\n    TraceRequestStartParams,\n    TraceResponseChunkReceivedParams,\n)\n\n\nclass _Params(TypedDict):\n    headers: dict[str, str]\n    max_redirects: int\n    compress: str\n    chunked: bool\n    expect100: bool\n    read_until_eof: bool\n\n\n@pytest.fixture\ndef connector(\n    loop: asyncio.AbstractEventLoop, create_mocked_conn: Callable[[], ResponseHandler]\n) -> Iterator[BaseConnector]:\n    async def make_conn() -> BaseConnector:\n        return BaseConnector()\n\n    key = ConnectionKey(\"localhost\", 80, False, True, None, None, None)\n    conn = loop.run_until_complete(make_conn())\n    proto = create_mocked_conn()\n    conn._conns[key] = deque([(proto, 123)])\n    yield conn\n    loop.run_until_complete(conn.close())\n\n\n@pytest.fixture\ndef create_session(\n    loop: asyncio.AbstractEventLoop,\n) -> Iterator[Callable[..., Awaitable[ClientSession]]]:\n    session = None\n\n    async def maker(*args: Any, **kwargs: Any) -> ClientSession:\n        nonlocal session\n        session = ClientSession(*args, **kwargs)\n        return session\n\n    yield maker\n    if session is not None:\n        loop.run_until_complete(session.close())\n\n\n@pytest.fixture\ndef session(\n    create_session: Callable[..., Awaitable[ClientSession]],\n    loop: asyncio.AbstractEventLoop,\n) -> ClientSession:\n    return loop.run_until_complete(create_session())\n\n\n@pytest.fixture\ndef params() -> _Params:\n    return dict(\n        headers={\"Authorization\": \"Basic ...\"},\n        max_redirects=2,\n        compress=\"deflate\",\n        chunked=True,\n        expect100=True,\n        read_until_eof=False,\n    )\n\n\n@pytest.fixture\nasync def auth_server(aiohttp_server: AiohttpServer) -> TestServer:\n    \"\"\"Create a server with an auth handler that returns auth header or 'no_auth'.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        auth_header = request.headers.get(hdrs.AUTHORIZATION)\n        if auth_header:\n            return web.Response(text=f\"auth:{auth_header}\")\n        return web.Response(text=\"no_auth\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    return await aiohttp_server(app)\n\n\nasync def test_close_coro(\n    create_session: Callable[..., Awaitable[ClientSession]],\n) -> None:\n    session = await create_session()\n    await session.close()\n\n\nasync def test_init_headers_simple_dict(\n    create_session: Callable[..., Awaitable[ClientSession]],\n) -> None:\n    session = await create_session(headers={\"h1\": \"header1\", \"h2\": \"header2\"})\n    assert sorted(session.headers.items()) == ([(\"h1\", \"header1\"), (\"h2\", \"header2\")])\n\n\nasync def test_init_headers_list_of_tuples(\n    create_session: Callable[..., Awaitable[ClientSession]],\n) -> None:\n    session = await create_session(\n        headers=[(\"h1\", \"header1\"), (\"h2\", \"header2\"), (\"h3\", \"header3\")]\n    )\n    assert session.headers == CIMultiDict(\n        [(\"h1\", \"header1\"), (\"h2\", \"header2\"), (\"h3\", \"header3\")]\n    )\n\n\nasync def test_init_headers_MultiDict(\n    create_session: Callable[..., Awaitable[ClientSession]],\n) -> None:\n    session = await create_session(\n        headers=MultiDict([(\"h1\", \"header1\"), (\"h2\", \"header2\"), (\"h3\", \"header3\")])\n    )\n    assert session.headers == CIMultiDict(\n        [(\"H1\", \"header1\"), (\"H2\", \"header2\"), (\"H3\", \"header3\")]\n    )\n\n\nasync def test_init_headers_list_of_tuples_with_duplicates(\n    create_session: Callable[..., Awaitable[ClientSession]],\n) -> None:\n    session = await create_session(\n        headers=[(\"h1\", \"header11\"), (\"h2\", \"header21\"), (\"h1\", \"header12\")]\n    )\n    assert session.headers == CIMultiDict(\n        [(\"H1\", \"header11\"), (\"H2\", \"header21\"), (\"H1\", \"header12\")]\n    )\n\n\nasync def test_init_cookies_with_simple_dict(\n    create_session: Callable[..., Awaitable[ClientSession]],\n) -> None:\n    session = await create_session(cookies={\"c1\": \"cookie1\", \"c2\": \"cookie2\"})\n    cookies = session.cookie_jar.filter_cookies(URL())\n    assert set(cookies) == {\"c1\", \"c2\"}\n    assert cookies[\"c1\"].value == \"cookie1\"\n    assert cookies[\"c2\"].value == \"cookie2\"\n\n\nasync def test_init_cookies_with_list_of_tuples(\n    create_session: Callable[..., Awaitable[ClientSession]],\n) -> None:\n    session = await create_session(cookies=[(\"c1\", \"cookie1\"), (\"c2\", \"cookie2\")])\n\n    cookies = session.cookie_jar.filter_cookies(URL())\n    assert set(cookies) == {\"c1\", \"c2\"}\n    assert cookies[\"c1\"].value == \"cookie1\"\n    assert cookies[\"c2\"].value == \"cookie2\"\n\n\nasync def test_merge_headers(\n    create_session: Callable[..., Awaitable[ClientSession]],\n) -> None:\n    # Check incoming simple dict\n    session = await create_session(headers={\"h1\": \"header1\", \"h2\": \"header2\"})\n    headers = session._prepare_headers({\"h1\": \"h1\"})\n\n    assert isinstance(headers, CIMultiDict)\n    assert headers == {\"h1\": \"h1\", \"h2\": \"header2\"}\n\n\nasync def test_merge_headers_with_multi_dict(\n    create_session: Callable[..., Awaitable[ClientSession]],\n) -> None:\n    session = await create_session(headers={\"h1\": \"header1\", \"h2\": \"header2\"})\n    headers = session._prepare_headers(MultiDict([(\"h1\", \"h1\")]))\n    assert isinstance(headers, CIMultiDict)\n    assert headers == {\"h1\": \"h1\", \"h2\": \"header2\"}\n\n\nasync def test_merge_headers_with_list_of_tuples(\n    create_session: Callable[..., Awaitable[ClientSession]],\n) -> None:\n    session = await create_session(headers={\"h1\": \"header1\", \"h2\": \"header2\"})\n    headers = session._prepare_headers([(\"h1\", \"h1\")])\n    assert isinstance(headers, CIMultiDict)\n    assert headers == {\"h1\": \"h1\", \"h2\": \"header2\"}\n\n\nasync def test_merge_headers_with_list_of_tuples_duplicated_names(\n    create_session: Callable[..., Awaitable[ClientSession]],\n) -> None:\n    session = await create_session(headers={\"h1\": \"header1\", \"h2\": \"header2\"})\n\n    headers = session._prepare_headers([(\"h1\", \"v1\"), (\"h1\", \"v2\")])\n\n    assert isinstance(headers, CIMultiDict)\n    assert list(sorted(headers.items())) == [\n        (\"h1\", \"v1\"),\n        (\"h1\", \"v2\"),\n        (\"h2\", \"header2\"),\n    ]\n\n\n@pytest.mark.parametrize(\"obj\", (object(), None))\nasync def test_invalid_data(session: ClientSession, obj: object) -> None:\n    with pytest.raises(TypeError, match=\"expected str\"):\n        await session.post(\"http://example.test/\", data={\"some\": obj})\n\n\nasync def test_http_GET(session: ClientSession, params: _Params) -> None:\n    with mock.patch(\n        \"aiohttp.client.ClientSession._request\", autospec=True, spec_set=True\n    ) as patched:\n        await session.get(\"http://test.example.com\", params={\"x\": 1}, **params)\n    assert patched.called, \"`ClientSession._request` not called\"\n    assert list(patched.call_args) == [\n        (session, \"GET\", \"http://test.example.com\"),\n        dict(params={\"x\": 1}, allow_redirects=True, **params),\n    ]\n\n\nasync def test_http_OPTIONS(session: ClientSession, params: _Params) -> None:\n    with mock.patch(\n        \"aiohttp.client.ClientSession._request\", autospec=True, spec_set=True\n    ) as patched:\n        await session.options(\"http://opt.example.com\", params={\"x\": 2}, **params)\n    assert patched.called, \"`ClientSession._request` not called\"\n    assert list(patched.call_args) == [\n        (session, \"OPTIONS\", \"http://opt.example.com\"),\n        dict(params={\"x\": 2}, allow_redirects=True, **params),\n    ]\n\n\nasync def test_http_HEAD(session: ClientSession, params: _Params) -> None:\n    with mock.patch(\n        \"aiohttp.client.ClientSession._request\", autospec=True, spec_set=True\n    ) as patched:\n        await session.head(\"http://head.example.com\", params={\"x\": 2}, **params)\n    assert patched.called, \"`ClientSession._request` not called\"\n    assert list(patched.call_args) == [\n        (session, \"HEAD\", \"http://head.example.com\"),\n        dict(params={\"x\": 2}, allow_redirects=False, **params),\n    ]\n\n\nasync def test_http_POST(session: ClientSession, params: _Params) -> None:\n    with mock.patch(\n        \"aiohttp.client.ClientSession._request\", autospec=True, spec_set=True\n    ) as patched:\n        await session.post(\n            \"http://post.example.com\", params={\"x\": 2}, data=\"Some_data\", **params\n        )\n    assert patched.called, \"`ClientSession._request` not called\"\n    assert list(patched.call_args) == [\n        (session, \"POST\", \"http://post.example.com\"),\n        dict(params={\"x\": 2}, data=\"Some_data\", **params),\n    ]\n\n\nasync def test_http_PUT(session: ClientSession, params: _Params) -> None:\n    with mock.patch(\n        \"aiohttp.client.ClientSession._request\", autospec=True, spec_set=True\n    ) as patched:\n        await session.put(\n            \"http://put.example.com\", params={\"x\": 2}, data=\"Some_data\", **params\n        )\n    assert patched.called, \"`ClientSession._request` not called\"\n    assert list(patched.call_args) == [\n        (session, \"PUT\", \"http://put.example.com\"),\n        dict(params={\"x\": 2}, data=\"Some_data\", **params),\n    ]\n\n\nasync def test_http_PATCH(session: ClientSession, params: _Params) -> None:\n    with mock.patch(\n        \"aiohttp.client.ClientSession._request\", autospec=True, spec_set=True\n    ) as patched:\n        await session.patch(\n            \"http://patch.example.com\", params={\"x\": 2}, data=\"Some_data\", **params\n        )\n    assert patched.called, \"`ClientSession._request` not called\"\n    assert list(patched.call_args) == [\n        (session, \"PATCH\", \"http://patch.example.com\"),\n        dict(params={\"x\": 2}, data=\"Some_data\", **params),\n    ]\n\n\nasync def test_http_DELETE(session: ClientSession, params: _Params) -> None:\n    with mock.patch(\n        \"aiohttp.client.ClientSession._request\", autospec=True, spec_set=True\n    ) as patched:\n        await session.delete(\"http://delete.example.com\", params={\"x\": 2}, **params)\n    assert patched.called, \"`ClientSession._request` not called\"\n    assert list(patched.call_args) == [\n        (session, \"DELETE\", \"http://delete.example.com\"),\n        dict(params={\"x\": 2}, **params),\n    ]\n\n\nasync def test_close(\n    create_session: Callable[..., Awaitable[ClientSession]], connector: BaseConnector\n) -> None:\n    session = await create_session(connector=connector)\n\n    await session.close()\n    assert session.connector is None\n    assert connector.closed\n\n\nasync def test_closed(session: ClientSession) -> None:\n    assert not session.closed\n    await session.close()\n    assert session.closed\n\n\nasync def test_connector(\n    create_session: Callable[..., Awaitable[ClientSession]],\n    loop: asyncio.AbstractEventLoop,\n    mocker: MockerFixture,\n) -> None:\n    connector = TCPConnector()\n    m = mocker.spy(connector, \"close\")\n    session = await create_session(connector=connector)\n    assert session.connector is connector\n\n    await session.close()\n    assert m.called\n    await connector.close()\n\n\nasync def test_create_connector(\n    create_session: Callable[..., Awaitable[ClientSession]],\n    loop: asyncio.AbstractEventLoop,\n    mocker: MockerFixture,\n) -> None:\n    session = await create_session()\n    m = mocker.spy(session.connector, \"close\")\n\n    await session.close()\n    assert m.called\n\n\n@pytest.mark.skipif(\n    sys.version_info < (3, 11),\n    reason=\"Use test_ssl_shutdown_timeout_passed_to_connector_pre_311 for Python < 3.11\",\n)\nasync def test_ssl_shutdown_timeout_passed_to_connector() -> None:\n    # Test default value (no warning expected)\n    async with ClientSession() as session:\n        assert isinstance(session.connector, TCPConnector)\n        assert session.connector._ssl_shutdown_timeout == 0\n\n    # Test custom value - expect deprecation warning\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        async with ClientSession(ssl_shutdown_timeout=1.0) as session:\n            assert isinstance(session.connector, TCPConnector)\n            assert session.connector._ssl_shutdown_timeout == 1.0\n\n    # Test None value - expect deprecation warning\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        async with ClientSession(ssl_shutdown_timeout=None) as session:\n            assert isinstance(session.connector, TCPConnector)\n            assert session.connector._ssl_shutdown_timeout is None\n\n    # Test that it doesn't affect when custom connector is provided\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        custom_conn = TCPConnector(ssl_shutdown_timeout=2.0)\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        async with ClientSession(\n            connector=custom_conn, ssl_shutdown_timeout=1.0\n        ) as session:\n            assert session.connector is not None\n            assert isinstance(session.connector, TCPConnector)\n            assert (\n                session.connector._ssl_shutdown_timeout == 2.0\n            )  # Should use connector's value\n\n\n@pytest.mark.skipif(\n    sys.version_info >= (3, 11),\n    reason=\"This test is for Python < 3.11 runtime warning behavior\",\n)\nasync def test_ssl_shutdown_timeout_passed_to_connector_pre_311() -> None:\n    \"\"\"Test that both deprecation and runtime warnings are issued on Python < 3.11.\"\"\"\n    # Test custom value - expect both deprecation and runtime warnings\n    with warnings.catch_warnings(record=True) as w:\n        warnings.simplefilter(\"always\")\n        async with ClientSession(ssl_shutdown_timeout=1.0) as session:\n            assert isinstance(session.connector, TCPConnector)\n            assert session.connector._ssl_shutdown_timeout == 1.0\n        # Should have deprecation warnings (from ClientSession and TCPConnector) and runtime warning\n        # ClientSession emits 1 DeprecationWarning, TCPConnector emits 1 DeprecationWarning + 1 RuntimeWarning = 3 total\n        assert len(w) == 3\n        deprecation_count = sum(\n            1 for warn in w if issubclass(warn.category, DeprecationWarning)\n        )\n        runtime_count = sum(\n            1 for warn in w if issubclass(warn.category, RuntimeWarning)\n        )\n        assert deprecation_count == 2  # One from ClientSession, one from TCPConnector\n        assert runtime_count == 1  # One from TCPConnector\n\n    # Test with custom connector\n    with warnings.catch_warnings(record=True) as w:\n        warnings.simplefilter(\"always\")\n        custom_conn = TCPConnector(ssl_shutdown_timeout=2.0)\n        # Should have both deprecation and runtime warnings\n        assert len(w) == 2\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        async with ClientSession(\n            connector=custom_conn, ssl_shutdown_timeout=1.0\n        ) as session:\n            assert session.connector is not None\n            assert isinstance(session.connector, TCPConnector)\n            assert (\n                session.connector._ssl_shutdown_timeout == 2.0\n            )  # Should use connector's value\n\n\ndef test_connector_loop(loop: asyncio.AbstractEventLoop) -> None:\n    with contextlib.ExitStack() as stack:\n        another_loop = asyncio.new_event_loop()\n        stack.enter_context(contextlib.closing(another_loop))\n\n        async def make_connector() -> TCPConnector:\n            return TCPConnector()\n\n        connector = another_loop.run_until_complete(make_connector())\n\n        with pytest.raises(RuntimeError) as ctx:\n\n            async def make_sess() -> ClientSession:\n                return ClientSession(connector=connector)\n\n            loop.run_until_complete(make_sess())\n        expected = \"Session and connector have to use same event loop\"\n        assert str(ctx.value).startswith(expected)\n        another_loop.run_until_complete(connector.close())\n\n\ndef test_detach(loop: asyncio.AbstractEventLoop, session: ClientSession) -> None:\n    conn = session.connector\n    assert conn is not None\n    try:\n        assert not conn.closed\n        session.detach()\n        assert session.connector is None\n        assert session.closed\n        assert not conn.closed\n    finally:\n        loop.run_until_complete(conn.close())\n\n\nasync def test_request_closed_session(session: ClientSession) -> None:\n    await session.close()\n    with pytest.raises(RuntimeError):\n        await session.request(\"get\", \"/\")\n\n\nasync def test_close_flag_for_closed_connector(session: ClientSession) -> None:\n    conn = session.connector\n    assert conn is not None\n    assert not session.closed\n    await conn.close()\n    assert session.closed\n\n\nasync def test_double_close(\n    connector: BaseConnector, create_session: Callable[..., Awaitable[ClientSession]]\n) -> None:\n    session = await create_session(connector=connector)\n\n    await session.close()\n    assert session.connector is None\n    await session.close()\n    assert session.closed\n    assert connector.closed\n\n\nasync def test_del(connector: BaseConnector, loop: asyncio.AbstractEventLoop) -> None:\n    loop.set_debug(False)\n    # N.B. don't use session fixture, it stores extra reference internally\n    session = ClientSession(connector=connector)\n    logs = []\n    loop.set_exception_handler(lambda loop, ctx: logs.append(ctx))\n\n    with pytest.warns(ResourceWarning):\n        del session\n        gc.collect()\n\n    assert len(logs) == 1\n    expected = {\"client_session\": mock.ANY, \"message\": \"Unclosed client session\"}\n    assert logs[0] == expected\n\n\nasync def test_del_debug(\n    connector: BaseConnector, loop: asyncio.AbstractEventLoop\n) -> None:\n    loop.set_debug(True)\n    # N.B. don't use session fixture, it stores extra reference internally\n    session = ClientSession(connector=connector)\n    logs = []\n    loop.set_exception_handler(lambda loop, ctx: logs.append(ctx))\n\n    with pytest.warns(ResourceWarning):\n        del session\n        gc.collect()\n\n    assert len(logs) == 1\n    expected = {\n        \"client_session\": mock.ANY,\n        \"message\": \"Unclosed client session\",\n        \"source_traceback\": mock.ANY,\n    }\n    assert logs[0] == expected\n\n\nasync def test_borrow_connector_loop(\n    connector: BaseConnector,\n    create_session: Callable[..., Awaitable[ClientSession]],\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    async with ClientSession(connector=connector) as session:\n        assert session._loop is loop\n\n\nasync def test_reraise_os_error(\n    create_session: Callable[..., Awaitable[ClientSession]],\n    create_mocked_conn: Callable[[], ResponseHandler],\n) -> None:\n    err = OSError(1, \"permission error\")\n    req = mock.create_autospec(aiohttp.ClientRequest, spec_set=True)\n    req_factory = mock.Mock(return_value=req)\n    req._send.side_effect = err\n    req._body = mock.create_autospec(Payload, spec_set=True, instance=True)\n    session = await create_session(request_class=req_factory)\n\n    async def create_connection(\n        req: object, traces: object, timeout: object\n    ) -> ResponseHandler:\n        # return self.transport, self.protocol\n        return create_mocked_conn()\n\n    with mock.patch.object(session._connector, \"_create_connection\", create_connection):\n        with mock.patch.object(\n            session._connector, \"_release\", autospec=True, spec_set=True\n        ):\n            with pytest.raises(aiohttp.ClientOSError) as ctx:\n                await session.request(\"get\", \"http://example.com\")\n            e = ctx.value\n            assert e.errno == err.errno\n            assert e.strerror == err.strerror\n\n\nasync def test_close_conn_on_error(\n    create_session: Callable[..., Awaitable[ClientSession]],\n    create_mocked_conn: Callable[[], ResponseHandler],\n) -> None:\n    class UnexpectedException(BaseException):\n        pass\n\n    err = UnexpectedException(\"permission error\")\n    req = mock.create_autospec(aiohttp.ClientRequest, spec_set=True)\n    req_factory = mock.Mock(return_value=req)\n    req._send.side_effect = err\n    req._body = mock.create_autospec(Payload, spec_set=True, instance=True)\n    session = await create_session(request_class=req_factory)\n\n    connections = []\n    assert session._connector is not None\n    original_connect = session._connector.connect\n\n    async def connect(\n        req: ClientRequest, traces: list[Trace], timeout: aiohttp.ClientTimeout\n    ) -> Connection:\n        conn = await original_connect(req, traces, timeout)\n        connections.append(conn)\n        return conn\n\n    async def create_connection(\n        req: object, traces: object, timeout: object\n    ) -> ResponseHandler:\n        # return self.transport, self.protocol\n        conn = create_mocked_conn()\n        return conn\n\n    with mock.patch.object(session._connector, \"connect\", connect):\n        with mock.patch.object(\n            session._connector, \"_create_connection\", create_connection\n        ):\n            with mock.patch.object(\n                session._connector, \"_release\", autospec=True, spec_set=True\n            ):\n                with pytest.raises(UnexpectedException):\n                    async with session.request(\"get\", \"http://example.com\"):\n                        pass\n\n                # normally called during garbage collection.  triggers an exception\n                # if the connection wasn't already closed\n                for c in connections:\n                    c.__del__()\n\n\n@pytest.mark.parametrize(\"protocol\", [\"http\", \"https\", \"ws\", \"wss\"])\nasync def test_ws_connect_allowed_protocols(  # type: ignore[misc]\n    create_session: Callable[..., Awaitable[ClientSession]],\n    create_mocked_conn: Callable[[], ResponseHandler],\n    protocol: str,\n    ws_key: str,\n    key_data: bytes,\n) -> None:\n    resp = mock.create_autospec(aiohttp.ClientResponse, spec_set=True, instance=True)\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n    }\n    resp.url = URL(f\"{protocol}://example\")\n    resp.cookies = SimpleCookie()\n\n    req = mock.create_autospec(aiohttp.ClientRequest, spec_set=True)\n    req._body = None  # No body for WebSocket upgrade requests\n    req_factory = mock.Mock(return_value=req)\n    req._send = mock.AsyncMock(return_value=resp)\n    # BaseConnector allows all high level protocols by default\n    connector = BaseConnector()\n\n    session = await create_session(connector=connector, request_class=req_factory)\n\n    connections = []\n    assert session._connector is not None\n    original_connect = session._connector.connect\n\n    async def connect(\n        req: ClientRequest, traces: list[Trace], timeout: aiohttp.ClientTimeout\n    ) -> Connection:\n        conn = await original_connect(req, traces, timeout)\n        connections.append(conn)\n        return conn\n\n    async def create_connection(\n        req: object, traces: object, timeout: object\n    ) -> ResponseHandler:\n        return create_mocked_conn()\n\n    connector = session._connector\n    with (\n        mock.patch.object(connector, \"connect\", connect),\n        mock.patch.object(connector, \"_create_connection\", create_connection),\n        mock.patch.object(connector, \"_release\"),\n        mock.patch(\"aiohttp.client.os\") as m_os,\n    ):\n        m_os.urandom.return_value = key_data\n        await session.ws_connect(f\"{protocol}://example\")\n\n    # normally called during garbage collection.  triggers an exception\n    # if the connection wasn't already closed\n    for c in connections:\n        c.close()\n        c.__del__()\n\n    await session.close()\n\n\n@pytest.mark.parametrize(\"protocol\", [\"http\", \"https\", \"ws\", \"wss\", \"unix\"])\nasync def test_ws_connect_unix_socket_allowed_protocols(  # type: ignore[misc]\n    create_session: Callable[..., Awaitable[ClientSession]],\n    create_mocked_conn: Callable[[], ResponseHandler],\n    protocol: str,\n    ws_key: str,\n    key_data: bytes,\n) -> None:\n    resp = mock.create_autospec(aiohttp.ClientResponse, spec_set=True, instance=True)\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n    }\n    resp.url = URL(f\"{protocol}://example\")\n    resp.cookies = SimpleCookie()\n\n    req = mock.create_autospec(aiohttp.ClientRequest, spec_set=True)\n    req._body = None  # No body for WebSocket upgrade requests\n    req_factory = mock.Mock(return_value=req)\n    req._send = mock.AsyncMock(return_value=resp)\n    # UnixConnector allows all high level protocols by default and unix sockets\n    session = await create_session(\n        connector=UnixConnector(path=\"\"), request_class=req_factory\n    )\n\n    connections = []\n    assert session._connector is not None\n    original_connect = session._connector.connect\n\n    async def connect(\n        req: ClientRequest, traces: list[Trace], timeout: aiohttp.ClientTimeout\n    ) -> Connection:\n        conn = await original_connect(req, traces, timeout)\n        connections.append(conn)\n        return conn\n\n    async def create_connection(\n        req: object, traces: object, timeout: object\n    ) -> ResponseHandler:\n        return create_mocked_conn()\n\n    connector = session._connector\n    with (\n        mock.patch.object(connector, \"connect\", connect),\n        mock.patch.object(connector, \"_create_connection\", create_connection),\n        mock.patch.object(connector, \"_release\"),\n        mock.patch(\"aiohttp.client.os\") as m_os,\n    ):\n        m_os.urandom.return_value = key_data\n        await session.ws_connect(f\"{protocol}://example\")\n\n    # normally called during garbage collection.  triggers an exception\n    # if the connection wasn't already closed\n    for c in connections:\n        c.close()\n        c.__del__()\n\n    await session.close()\n\n\nasync def test_cookie_jar_usage(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    req_url = None\n\n    class MockCookieJar(abc.AbstractCookieJar):\n        def __init__(self) -> None:\n            self._update_cookies_mock = mock.Mock()\n            self._filter_cookies_mock = mock.Mock(return_value=BaseCookie())\n            self._clear_mock = mock.Mock()\n            self._clear_domain_mock = mock.Mock()\n            self._items: list[Any] = []\n\n        @property\n        def quote_cookie(self) -> bool:\n            return True\n\n        def clear(self, predicate: abc.ClearCookiePredicate | None = None) -> None:\n            self._clear_mock(predicate)\n\n        def clear_domain(self, domain: str) -> None:\n            self._clear_domain_mock(domain)\n\n        def update_cookies(self, cookies: Any, response_url: URL = URL()) -> None:\n            self._update_cookies_mock(cookies, response_url)\n\n        def filter_cookies(self, request_url: URL) -> BaseCookie[str]:\n            return cast(BaseCookie[str], self._filter_cookies_mock(request_url))\n\n        def __len__(self) -> int:\n            return len(self._items)\n\n        def __iter__(self) -> Iterator[Any]:\n            return iter(self._items)\n\n    jar = MockCookieJar()\n\n    assert jar.quote_cookie is True\n    assert len(jar) == 0\n    assert list(jar) == []\n    jar.clear()\n    jar.clear_domain(\"example.com\")\n\n    async def handler(request: web.Request) -> web.Response:\n        nonlocal req_url\n        req_url = \"http://%s/\" % request.host\n\n        resp = web.Response()\n        resp.set_cookie(\"response\", \"resp_value\")\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    session = await aiohttp_client(\n        app, cookies={\"request\": \"req_value\"}, cookie_jar=jar\n    )\n\n    # Updating the cookie jar with initial user defined cookies\n    jar._update_cookies_mock.assert_called_with({\"request\": \"req_value\"}, URL())\n\n    jar._update_cookies_mock.reset_mock()\n    resp = await session.get(\"/\")\n    resp.release()\n    assert req_url is not None\n\n    # Filtering the cookie jar before sending the request,\n    # getting the request URL as only parameter\n    jar._filter_cookies_mock.assert_called_with(URL(req_url))\n\n    # Updating the cookie jar with the response cookies\n    assert jar._update_cookies_mock.called\n    resp_cookies = jar._update_cookies_mock.call_args[0][0]\n    # Now update_cookies is called with a list of tuples\n    assert isinstance(resp_cookies, list)\n    assert len(resp_cookies) == 1\n    assert resp_cookies[0][0] == \"response\"\n    assert resp_cookies[0][1].value == \"resp_value\"\n\n\nasync def test_cookies_with_not_quoted_cookie_jar(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    async def handler(_: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = await aiohttp_server(app)\n    jar = CookieJar(quote_cookie=False)\n    cookies = {\"name\": \"val=foobar\"}\n    async with aiohttp.ClientSession(cookie_jar=jar) as sess:\n        resp = await sess.request(\"GET\", server.make_url(\"/\"), cookies=cookies)\n    assert resp.request_info.headers.get(\"Cookie\", \"\") == \"name=val=foobar\"\n\n\nasync def test_session_default_version(loop: asyncio.AbstractEventLoop) -> None:\n    session = aiohttp.ClientSession()\n    assert session.version == aiohttp.HttpVersion11\n    await session.close()\n\n\nasync def test_proxy_str(session: ClientSession, params: _Params) -> None:\n    with mock.patch(\n        \"aiohttp.client.ClientSession._request\", autospec=True, spec_set=True\n    ) as patched:\n        await session.get(\"http://test.example.com\", proxy=\"http://proxy.com\", **params)\n    assert patched.called, \"`ClientSession._request` not called\"\n    assert list(patched.call_args) == [\n        (session, \"GET\", \"http://test.example.com\"),\n        dict(allow_redirects=True, proxy=\"http://proxy.com\", **params),\n    ]\n\n\nasync def test_default_proxy(loop: asyncio.AbstractEventLoop) -> None:\n    proxy_url = URL(\"http://proxy.example.com\")\n    proxy_auth = mock.Mock()\n    proxy_url2 = URL(\"http://proxy.example2.com\")\n    proxy_auth2 = mock.Mock()\n\n    class OnCall(Exception):\n        pass\n\n    request_class_mock = mock.Mock(side_effect=OnCall())\n    session = ClientSession(\n        proxy=proxy_url, proxy_auth=proxy_auth, request_class=request_class_mock\n    )\n\n    assert session._default_proxy == proxy_url, \"`ClientSession._default_proxy` not set\"\n    assert (\n        session._default_proxy_auth == proxy_auth\n    ), \"`ClientSession._default_proxy_auth` not set\"\n\n    with pytest.raises(OnCall):\n        await session.get(\n            \"http://example.com\",\n        )\n\n    assert request_class_mock.called, \"request class not called\"\n    assert (\n        request_class_mock.call_args[1].get(\"proxy\") == proxy_url\n    ), \"`ClientSession._request` uses default proxy not one used in ClientSession.get\"\n    assert (\n        request_class_mock.call_args[1].get(\"proxy_auth\") == proxy_auth\n    ), \"`ClientSession._request` uses default proxy_auth not one used in ClientSession.get\"\n\n    request_class_mock.reset_mock()\n    with pytest.raises(OnCall):\n        await session.get(\n            \"http://example.com\", proxy=proxy_url2, proxy_auth=proxy_auth2\n        )\n\n    assert request_class_mock.called, \"request class not called\"\n    assert (\n        request_class_mock.call_args[1].get(\"proxy\") == proxy_url2\n    ), \"`ClientSession._request` uses default proxy not one used in ClientSession.get\"\n    assert (\n        request_class_mock.call_args[1].get(\"proxy_auth\") == proxy_auth2\n    ), \"`ClientSession._request` uses default proxy_auth not one used in ClientSession.get\"\n\n    await session.close()\n\n\nasync def test_request_tracing(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.json_response({\"ok\": True})\n\n    # Define callback signatures\n    async def on_request_start_callback(\n        session: ClientSession,\n        trace_config_ctx: SimpleNamespace,\n        params: TraceRequestStartParams,\n    ) -> None:\n        \"\"\"Mock signature\"\"\"\n\n    async def on_request_end_callback(\n        session: ClientSession,\n        trace_config_ctx: SimpleNamespace,\n        params: TraceRequestEndParams,\n    ) -> None:\n        \"\"\"Mock signature\"\"\"\n\n    async def on_request_redirect_callback(\n        session: ClientSession,\n        trace_config_ctx: SimpleNamespace,\n        params: TraceRequestRedirectParams,\n    ) -> None:\n        \"\"\"Mock signature\"\"\"\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n\n    trace_config_ctx = mock.Mock()\n    body = \"This is request body\"\n    gathered_req_headers: CIMultiDict[str] = CIMultiDict()\n\n    # Create mocks with signatures(above)\n    on_request_start = mock.create_autospec(on_request_start_callback, spec_set=True)\n    on_request_end = mock.create_autospec(on_request_end_callback, spec_set=True)\n    on_request_redirect = mock.create_autospec(\n        on_request_redirect_callback, spec_set=True\n    )\n\n    with io.BytesIO() as gathered_req_body, io.BytesIO() as gathered_res_body:\n\n        async def on_request_chunk_sent(\n            session: object,\n            context: object,\n            params: tracing.TraceRequestChunkSentParams,\n        ) -> None:\n            gathered_req_body.write(params.chunk)\n\n        async def on_response_chunk_received(\n            session: object,\n            context: object,\n            params: tracing.TraceResponseChunkReceivedParams,\n        ) -> None:\n            gathered_res_body.write(params.chunk)\n\n        async def on_request_headers_sent(\n            session: object,\n            context: object,\n            params: tracing.TraceRequestHeadersSentParams,\n        ) -> None:\n            gathered_req_headers.extend(params.headers)\n\n        trace_config = aiohttp.TraceConfig(\n            trace_config_ctx_factory=mock.Mock(return_value=trace_config_ctx)\n        )\n        trace_config.on_request_start.append(on_request_start)\n        trace_config.on_request_end.append(on_request_end)\n        trace_config.on_request_chunk_sent.append(on_request_chunk_sent)\n        trace_config.on_response_chunk_received.append(on_response_chunk_received)\n        trace_config.on_request_redirect.append(on_request_redirect)\n        trace_config.on_request_headers_sent.append(on_request_headers_sent)\n\n        headers = CIMultiDict({\"Custom-Header\": \"Custom value\"})\n        session = await aiohttp_client(\n            app, trace_configs=[trace_config], headers=headers\n        )\n\n        async with session.post(\"/\", data=body, trace_request_ctx={}) as resp:\n            await resp.json()\n\n            on_request_start.assert_called_once_with(\n                session.session,\n                trace_config_ctx,\n                aiohttp.TraceRequestStartParams(\n                    hdrs.METH_POST, session.make_url(\"/\"), headers\n                ),\n            )\n\n            on_request_end.assert_called_once_with(\n                session.session,\n                trace_config_ctx,\n                aiohttp.TraceRequestEndParams(\n                    hdrs.METH_POST, session.make_url(\"/\"), headers, resp\n                ),\n            )\n            assert not on_request_redirect.called\n            assert gathered_req_body.getvalue() == body.encode(\"utf8\")\n            assert gathered_res_body.getvalue() == json.dumps({\"ok\": True}).encode(\n                \"utf8\"\n            )\n            assert gathered_req_headers[\"Custom-Header\"] == \"Custom value\"\n\n\nasync def test_request_tracing_url_params(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def root_handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    async def redirect_handler(request: web.Request) -> NoReturn:\n        raise web.HTTPFound(\"/\")\n\n    # Define callback signatures\n    async def on_request_start_callback(\n        session: ClientSession,\n        trace_config_ctx: SimpleNamespace,\n        params: TraceRequestStartParams,\n    ) -> None:\n        \"\"\"Mock signature\"\"\"\n\n    async def on_request_end_callback(\n        session: ClientSession,\n        trace_config_ctx: SimpleNamespace,\n        params: TraceRequestEndParams,\n    ) -> None:\n        \"\"\"Mock signature\"\"\"\n\n    async def on_request_redirect_callback(\n        session: ClientSession,\n        trace_config_ctx: SimpleNamespace,\n        params: TraceRequestRedirectParams,\n    ) -> None:\n        \"\"\"Mock signature\"\"\"\n\n    async def on_request_exception_callback(\n        session: ClientSession,\n        trace_config_ctx: SimpleNamespace,\n        params: TraceRequestExceptionParams,\n    ) -> None:\n        \"\"\"Mock signature\"\"\"\n\n    async def on_request_chunk_sent_callback(\n        session: ClientSession,\n        trace_config_ctx: SimpleNamespace,\n        params: TraceRequestChunkSentParams,\n    ) -> None:\n        \"\"\"Mock signature\"\"\"\n\n    async def on_response_chunk_received_callback(\n        session: ClientSession,\n        trace_config_ctx: SimpleNamespace,\n        params: TraceResponseChunkReceivedParams,\n    ) -> None:\n        \"\"\"Mock signature\"\"\"\n\n    async def on_request_headers_sent_callback(\n        session: ClientSession,\n        trace_config_ctx: SimpleNamespace,\n        params: TraceRequestHeadersSentParams,\n    ) -> None:\n        \"\"\"Mock signature\"\"\"\n\n    app = web.Application()\n    app.router.add_get(\"/\", root_handler)\n    app.router.add_get(\"/redirect\", redirect_handler)\n\n    on_request_start = mock.create_autospec(on_request_start_callback, spec_set=True)\n    on_request_redirect = mock.create_autospec(\n        on_request_redirect_callback, spec_set=True\n    )\n    on_request_end = mock.create_autospec(on_request_end_callback, spec_set=True)\n    on_request_exception = mock.create_autospec(\n        on_request_exception_callback, spec_set=True\n    )\n    on_request_chunk_sent = mock.create_autospec(\n        on_request_chunk_sent_callback, spec_set=True\n    )\n    on_response_chunk_received = mock.create_autospec(\n        on_response_chunk_received_callback, spec_set=True\n    )\n    on_request_headers_sent = mock.create_autospec(\n        on_request_headers_sent_callback, spec_set=True\n    )\n    mocks = [\n        on_request_start,\n        on_request_redirect,\n        on_request_end,\n        on_request_exception,\n        on_request_chunk_sent,\n        on_response_chunk_received,\n        on_request_headers_sent,\n    ]\n\n    trace_config = aiohttp.TraceConfig(\n        trace_config_ctx_factory=mock.Mock(return_value=mock.Mock())\n    )\n    trace_config.on_request_start.append(on_request_start)\n    trace_config.on_request_redirect.append(on_request_redirect)\n    trace_config.on_request_end.append(on_request_end)\n    trace_config.on_request_exception.append(on_request_exception)\n    trace_config.on_request_chunk_sent.append(on_request_chunk_sent)\n    trace_config.on_response_chunk_received.append(on_response_chunk_received)\n    trace_config.on_request_headers_sent.append(on_request_headers_sent)\n\n    session = await aiohttp_client(app, trace_configs=[trace_config])\n\n    def reset_mocks() -> None:\n        for m in mocks:\n            m.reset_mock()\n\n    def to_trace_urls(mock_func: mock.Mock) -> list[URL]:\n        return [call_args[0][-1].url for call_args in mock_func.call_args_list]\n\n    def to_url(path: str) -> URL:\n        return session.make_url(path)\n\n    # Standard\n    req: Callable[[], Awaitable[aiohttp.ClientResponse]]\n    for req in (\n        lambda: session.get(\"/?x=0\"),\n        lambda: session.get(\"/\", params=dict(x=0)),\n    ):\n        reset_mocks()\n        async with req() as resp:\n            await resp.text()\n            assert to_trace_urls(on_request_start) == [to_url(\"/?x=0\")]\n            assert to_trace_urls(on_request_redirect) == []\n            assert to_trace_urls(on_request_end) == [to_url(\"/?x=0\")]\n            assert to_trace_urls(on_request_exception) == []\n            assert to_trace_urls(on_request_chunk_sent) == []\n            assert to_trace_urls(on_response_chunk_received) == [to_url(\"/?x=0\")]\n            assert to_trace_urls(on_request_headers_sent) == [to_url(\"/?x=0\")]\n\n    # Redirect\n    for req in (\n        lambda: session.get(\"/redirect?x=0\"),\n        lambda: session.get(\"/redirect\", params=dict(x=0)),\n    ):\n        reset_mocks()\n        async with req() as resp:\n            await resp.text()\n            assert to_trace_urls(on_request_start) == [to_url(\"/redirect?x=0\")]\n            assert to_trace_urls(on_request_redirect) == [to_url(\"/redirect?x=0\")]\n            assert to_trace_urls(on_request_end) == [to_url(\"/\")]\n            assert to_trace_urls(on_request_exception) == []\n            assert to_trace_urls(on_request_chunk_sent) == []\n            assert to_trace_urls(on_response_chunk_received) == [to_url(\"/\")]\n            assert to_trace_urls(on_request_headers_sent) == [\n                to_url(\"/redirect?x=0\"),\n                to_url(\"/\"),\n            ]\n\n    # Exception\n    with mock.patch(\"aiohttp.client.TCPConnector.connect\") as connect_patched:\n        connect_patched.side_effect = Exception()\n\n        for req in (\n            lambda: session.get(\"/?x=0\"),\n            lambda: session.get(\"/\", params=dict(x=0)),\n        ):\n            reset_mocks()\n            with contextlib.suppress(Exception):\n                await req()\n            assert to_trace_urls(on_request_start) == [to_url(\"/?x=0\")]\n            assert to_trace_urls(on_request_redirect) == []\n            assert to_trace_urls(on_request_end) == []\n            assert to_trace_urls(on_request_exception) == [to_url(\"?x=0\")]\n            assert to_trace_urls(on_request_chunk_sent) == []\n            assert to_trace_urls(on_response_chunk_received) == []\n            assert to_trace_urls(on_request_headers_sent) == []\n\n\nasync def test_request_tracing_exception() -> None:\n    on_request_end = mock.AsyncMock()\n    on_request_exception = mock.AsyncMock()\n\n    trace_config = aiohttp.TraceConfig()\n    trace_config.on_request_end.append(on_request_end)\n    trace_config.on_request_exception.append(on_request_exception)\n\n    with mock.patch(\"aiohttp.client.TCPConnector.connect\") as connect_patched:\n        error = Exception()\n        connect_patched.side_effect = error\n\n        session = aiohttp.ClientSession(trace_configs=[trace_config])\n\n        try:\n            await session.get(\"http://example.com\")\n        except Exception:\n            pass\n\n        on_request_exception.assert_called_once_with(\n            session,\n            mock.ANY,\n            aiohttp.TraceRequestExceptionParams(\n                hdrs.METH_GET, URL(\"http://example.com\"), CIMultiDict(), error\n            ),\n        )\n        assert not on_request_end.called\n\n    await session.close()\n\n\nasync def test_request_tracing_interpose_headers(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n\n    headers: CIMultiDict[str] = CIMultiDict()\n\n    class MyClientRequest(ClientRequest):\n        def __init__(self, *args: Any, **kwargs: Any):\n            nonlocal headers\n            super().__init__(*args, **kwargs)\n            headers = self.headers\n\n    async def new_headers(\n        session: object, trace_config_ctx: object, data: tracing.TraceRequestStartParams\n    ) -> None:\n        data.headers[\"foo\"] = \"bar\"\n\n    trace_config = aiohttp.TraceConfig()\n    trace_config.on_request_start.append(new_headers)\n\n    session = await aiohttp_client(\n        app, request_class=MyClientRequest, trace_configs=[trace_config]\n    )\n\n    await session.get(\"/\")\n    assert headers[\"foo\"] == \"bar\"\n\n\ndef test_client_session_inheritance() -> None:\n    with pytest.raises(TypeError):\n\n        class A(ClientSession):  # type: ignore[misc]\n            pass\n\n\nasync def test_client_session_custom_attr() -> None:\n    session = ClientSession()\n    with pytest.raises(AttributeError):\n        session.custom = None  # type: ignore[attr-defined]\n    await session.close()\n\n\nasync def test_client_session_timeout_default_args(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    session1 = ClientSession()\n    assert session1.timeout == client.DEFAULT_TIMEOUT\n    await session1.close()\n\n\nasync def test_client_session_timeout_zero(\n    create_mocked_conn: Callable[[], ResponseHandler],\n) -> None:\n    async def create_connection(\n        req: object, traces: object, timeout: object\n    ) -> ResponseHandler:\n        await asyncio.sleep(0.01)\n        conn = create_mocked_conn()\n        conn.connected = True  # type: ignore[misc]\n        assert conn.transport is not None\n        conn.transport.is_closing.return_value = False  # type: ignore[attr-defined]\n        msg = mock.create_autospec(RawResponseMessage, spec_set=True, code=200)\n        conn.read.return_value = (msg, mock.Mock())  # type: ignore[attr-defined]\n        return conn\n\n    timeout = client.ClientTimeout(total=10, connect=0, sock_connect=0, sock_read=0)\n    async with ClientSession(timeout=timeout) as session:\n        with mock.patch.object(\n            session._connector, \"_create_connection\", create_connection\n        ):\n            try:\n                resp = await session.get(\"http://example.com\")\n            except asyncio.TimeoutError:  # pragma: no cover\n                pytest.fail(\"0 should disable timeout.\")\n            resp.close()\n\n\nasync def test_client_session_timeout_bad_argument() -> None:\n    with pytest.raises(ValueError):\n        ClientSession(timeout=\"test_bad_argumnet\")  # type: ignore[arg-type]\n    with pytest.raises(ValueError):\n        ClientSession(timeout=100)  # type: ignore[arg-type]\n\n\nasync def test_requote_redirect_url_default() -> None:\n    session = ClientSession()\n    assert session.requote_redirect_url\n    await session.close()\n\n\nasync def test_requote_redirect_url_default_disable() -> None:\n    session = ClientSession(requote_redirect_url=False)\n    assert not session.requote_redirect_url\n    await session.close()\n\n\n@pytest.mark.parametrize(\n    (\"base_url\", \"url\", \"expected_url\"),\n    [\n        pytest.param(\n            None,\n            \"http://example.com/test\",\n            URL(\"http://example.com/test\"),\n            id=\"base_url=None url='http://example.com/test'\",\n        ),\n        pytest.param(\n            None,\n            URL(\"http://example.com/test\"),\n            URL(\"http://example.com/test\"),\n            id=\"base_url=None url=URL('http://example.com/test')\",\n        ),\n        pytest.param(\n            \"http://example.com\",\n            \"/test\",\n            URL(\"http://example.com/test\"),\n            id=\"base_url='http://example.com' url='/test'\",\n        ),\n        pytest.param(\n            URL(\"http://example.com\"),\n            \"/test\",\n            URL(\"http://example.com/test\"),\n            id=\"base_url=URL('http://example.com') url='/test'\",\n        ),\n        pytest.param(\n            URL(\"http://example.com/test1/\"),\n            \"test2\",\n            URL(\"http://example.com/test1/test2\"),\n            id=\"base_url=URL('http://example.com/test1/') url='test2'\",\n        ),\n        pytest.param(\n            URL(\"http://example.com/test1/\"),\n            \"/test2\",\n            URL(\"http://example.com/test2\"),\n            id=\"base_url=URL('http://example.com/test1/') url='/test2'\",\n        ),\n        pytest.param(\n            URL(\"http://example.com/test1/\"),\n            \"test2?q=foo#bar\",\n            URL(\"http://example.com/test1/test2?q=foo#bar\"),\n            id=\"base_url=URL('http://example.com/test1/') url='test2?q=foo#bar'\",\n        ),\n        pytest.param(\n            URL(\"http://example.com/test1/\"),\n            \"http://foo.com/bar\",\n            URL(\"http://foo.com/bar\"),\n            id=\"base_url=URL('http://example.com/test1/') url='http://foo.com/bar'\",\n        ),\n        pytest.param(\n            URL(\"http://example.com\"),\n            \"http://foo.com/bar\",\n            URL(\"http://foo.com/bar\"),\n            id=\"base_url=URL('http://example.com') url='http://foo.com/bar'\",\n        ),\n        pytest.param(\n            URL(\"http://example.com/test1/\"),\n            \"http://foo.com\",\n            URL(\"http://foo.com\"),\n            id=\"base_url=URL('http://example.com/test1/') url='http://foo.com'\",\n        ),\n    ],\n)\nasync def test_build_url_returns_expected_url(  # type: ignore[misc]\n    create_session: Callable[..., Awaitable[ClientSession]],\n    base_url: URL | str | None,\n    url: URL | str,\n    expected_url: URL,\n) -> None:\n    session = await create_session(base_url)\n    assert session._build_url(url) == expected_url\n\n\nasync def test_base_url_without_trailing_slash() -> None:\n    with pytest.raises(ValueError, match=\"base_url must have a trailing '/'\"):\n        ClientSession(base_url=\"http://example.com/test\")\n\n\nasync def test_instantiation_with_invalid_timeout_value(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    loop.set_debug(False)\n    logs = []\n    loop.set_exception_handler(lambda loop, ctx: logs.append(ctx))\n    with pytest.raises(ValueError, match=\"timeout parameter cannot be .*\"):\n        ClientSession(timeout=1)  # type: ignore[arg-type]\n    # should not have \"Unclosed client session\" warning\n    assert not logs\n\n\n@pytest.mark.parametrize(\n    (\"outer_name\", \"inner_name\"),\n    [\n        (\"skip_auto_headers\", \"_skip_auto_headers\"),\n        (\"auth\", \"_default_auth\"),\n        (\"json_serialize\", \"_json_serialize\"),\n        (\"connector_owner\", \"_connector_owner\"),\n        (\"raise_for_status\", \"_raise_for_status\"),\n        (\"trust_env\", \"_trust_env\"),\n        (\"trace_configs\", \"_trace_configs\"),\n    ],\n)\nasync def test_properties(\n    session: ClientSession, outer_name: str, inner_name: str\n) -> None:\n    value = uuid4()\n    setattr(session, inner_name, value)\n    assert value == getattr(session, outer_name)\n\n\n@pytest.mark.usefixtures(\"netrc_default_contents\")\nasync def test_netrc_auth_with_trust_env(auth_server: TestServer) -> None:\n    \"\"\"Test that netrc authentication works with ClientSession when NETRC env var is set.\"\"\"\n    async with (\n        ClientSession(trust_env=True) as session,\n        session.get(auth_server.make_url(\"/\")) as resp,\n    ):\n        text = await resp.text()\n        # Base64 encoded \"netrc_user:netrc_pass\" is \"bmV0cmNfdXNlcjpuZXRyY19wYXNz\"\n        assert text == \"auth:Basic bmV0cmNfdXNlcjpuZXRyY19wYXNz\"\n\n\n@pytest.mark.usefixtures(\"netrc_default_contents\")\nasync def test_netrc_auth_skipped_without_trust_env(auth_server: TestServer) -> None:\n    \"\"\"Test that netrc authentication is skipped when trust_env=False.\"\"\"\n    async with (\n        ClientSession(trust_env=False) as session,\n        session.get(auth_server.make_url(\"/\")) as resp,\n    ):\n        text = await resp.text()\n        assert text == \"no_auth\"\n\n\n@pytest.mark.usefixtures(\"no_netrc\")\nasync def test_netrc_auth_skipped_without_netrc_file(auth_server: TestServer) -> None:\n    \"\"\"Test that netrc authentication is skipped when no netrc file exists.\"\"\"\n    async with (\n        ClientSession(trust_env=True) as session,\n        session.get(auth_server.make_url(\"/\")) as resp,\n    ):\n        text = await resp.text()\n        assert text == \"no_auth\"\n\n\n@pytest.mark.usefixtures(\"netrc_home_directory\")\nasync def test_netrc_auth_from_home_directory(auth_server: TestServer) -> None:\n    \"\"\"Test that netrc authentication works from default ~/.netrc location without NETRC env var.\"\"\"\n    async with (\n        ClientSession(trust_env=True) as session,\n        session.get(auth_server.make_url(\"/\")) as resp,\n    ):\n        text = await resp.text()\n        assert text == \"auth:Basic bmV0cmNfdXNlcjpuZXRyY19wYXNz\"\n\n\n@pytest.mark.usefixtures(\"netrc_default_contents\")\nasync def test_netrc_auth_overridden_by_explicit_auth(auth_server: TestServer) -> None:\n    \"\"\"Test that explicit auth parameter overrides netrc authentication.\"\"\"\n    async with (\n        ClientSession(trust_env=True) as session,\n        session.get(\n            auth_server.make_url(\"/\"),\n            auth=aiohttp.BasicAuth(\"explicit_user\", \"explicit_pass\"),\n        ) as resp,\n    ):\n        text = await resp.text()\n        # Base64 encoded \"explicit_user:explicit_pass\" is \"ZXhwbGljaXRfdXNlcjpleHBsaWNpdF9wYXNz\"\n        assert text == \"auth:Basic ZXhwbGljaXRfdXNlcjpleHBsaWNpdF9wYXNz\"\n\n\n@pytest.mark.usefixtures(\"netrc_other_host\")\nasync def test_netrc_auth_host_not_in_netrc(auth_server: TestServer) -> None:\n    \"\"\"Test that netrc lookup returns None when host is not in netrc file.\"\"\"\n    async with (\n        ClientSession(trust_env=True) as session,\n        session.get(auth_server.make_url(\"/\")) as resp,\n    ):\n        text = await resp.text()\n        # Should not have auth since the host is not in netrc\n        assert text == \"no_auth\"\n"
  },
  {
    "path": "tests/test_client_ws.py",
    "content": "import asyncio\nimport base64\nimport hashlib\nimport os\nfrom collections.abc import Mapping\nfrom unittest import mock\n\nimport pytest\n\nimport aiohttp\nfrom aiohttp import (\n    ClientConnectionResetError,\n    ClientWSTimeout,\n    ServerDisconnectedError,\n    client,\n    hdrs,\n)\nfrom aiohttp._websocket.writer import WebSocketWriter as RealWebSocketWriter\nfrom aiohttp.http import WS_KEY\nfrom aiohttp.http_websocket import WSMessageClose\nfrom aiohttp.streams import EofStream\n\n\nasync def test_ws_connect(\n    ws_key: str, loop: asyncio.AbstractEventLoop, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n        hdrs.SEC_WEBSOCKET_PROTOCOL: \"chat\",\n    }\n    resp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            res = await aiohttp.ClientSession().ws_connect(\n                \"http://test.org\", protocols=(\"t1\", \"t2\", \"chat\")\n            )\n\n    assert isinstance(res, client.ClientWebSocketResponse)\n    assert res.protocol == \"chat\"\n    assert hdrs.ORIGIN not in m_req.call_args[1][\"headers\"]\n\n\nasync def test_ws_connect_read_timeout_is_reset_to_inf(\n    ws_key: str, loop: asyncio.AbstractEventLoop, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n        hdrs.SEC_WEBSOCKET_PROTOCOL: \"chat\",\n    }\n    resp.connection.protocol.read_timeout = 0.5\n    with (\n        mock.patch(\"aiohttp.client.os\") as m_os,\n        mock.patch(\"aiohttp.client.ClientSession.request\") as m_req,\n    ):\n        m_os.urandom.return_value = key_data\n        m_req.return_value = loop.create_future()\n        m_req.return_value.set_result(resp)\n\n        res = await aiohttp.ClientSession().ws_connect(\n            \"http://test.org\", protocols=(\"t1\", \"t2\", \"chat\")\n        )\n\n    assert isinstance(res, client.ClientWebSocketResponse)\n    assert res.protocol == \"chat\"\n    assert hdrs.ORIGIN not in m_req.call_args[1][\"headers\"]\n    assert resp.connection.protocol.read_timeout is None\n\n\nasync def test_ws_connect_read_timeout_stays_inf(\n    ws_key: str, loop: asyncio.AbstractEventLoop, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n        hdrs.SEC_WEBSOCKET_PROTOCOL: \"chat\",\n    }\n    resp.connection.protocol.read_timeout = None\n    with (\n        mock.patch(\"aiohttp.client.os\") as m_os,\n        mock.patch(\"aiohttp.client.ClientSession.request\") as m_req,\n    ):\n        m_os.urandom.return_value = key_data\n        m_req.return_value = loop.create_future()\n        m_req.return_value.set_result(resp)\n\n        res = await aiohttp.ClientSession().ws_connect(\n            \"http://test.org\",\n            protocols=(\"t1\", \"t2\", \"chat\"),\n            timeout=ClientWSTimeout(0.5),\n        )\n\n    assert isinstance(res, client.ClientWebSocketResponse)\n    assert res.protocol == \"chat\"\n    assert hdrs.ORIGIN not in m_req.call_args[1][\"headers\"]\n    assert resp.connection.protocol.read_timeout is None\n\n\nasync def test_ws_connect_read_timeout_reset_to_max(\n    ws_key: str, loop: asyncio.AbstractEventLoop, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n        hdrs.SEC_WEBSOCKET_PROTOCOL: \"chat\",\n    }\n    resp.connection.protocol.read_timeout = 0.5\n    with (\n        mock.patch(\"aiohttp.client.os\") as m_os,\n        mock.patch(\"aiohttp.client.ClientSession.request\") as m_req,\n    ):\n        m_os.urandom.return_value = key_data\n        m_req.return_value = loop.create_future()\n        m_req.return_value.set_result(resp)\n\n        res = await aiohttp.ClientSession().ws_connect(\n            \"http://test.org\",\n            protocols=(\"t1\", \"t2\", \"chat\"),\n            timeout=ClientWSTimeout(1.0),\n        )\n\n    assert isinstance(res, client.ClientWebSocketResponse)\n    assert res.protocol == \"chat\"\n    assert hdrs.ORIGIN not in m_req.call_args[1][\"headers\"]\n    assert resp.connection.protocol.read_timeout == 1.0\n\n\nasync def test_ws_connect_with_origin(\n    key_data: bytes, loop: asyncio.AbstractEventLoop\n) -> None:\n    resp = mock.Mock()\n    resp.status = 403\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            origin = \"https://example.org/page.html\"\n            with pytest.raises(client.WSServerHandshakeError):\n                await aiohttp.ClientSession().ws_connect(\n                    \"http://test.org\", origin=origin\n                )\n\n    assert hdrs.ORIGIN in m_req.call_args[1][\"headers\"]\n    assert m_req.call_args[1][\"headers\"][hdrs.ORIGIN] == origin\n\n\nasync def test_ws_connect_with_params(\n    ws_key: str, loop: asyncio.AbstractEventLoop, key_data: bytes\n) -> None:\n    params = {\"key1\": \"value1\", \"key2\": \"value2\"}\n\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n        hdrs.SEC_WEBSOCKET_PROTOCOL: \"chat\",\n    }\n    resp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            await aiohttp.ClientSession().ws_connect(\n                \"http://test.org\", protocols=(\"t1\", \"t2\", \"chat\"), params=params\n            )\n\n    assert m_req.call_args[1][\"params\"] == params\n\n\nasync def test_ws_connect_custom_response(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    class CustomResponse(client.ClientWebSocketResponse):\n        def read(self, decode: bool = False) -> str:\n            return \"customized!\"\n\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n    }\n    resp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            res = await aiohttp.ClientSession(\n                ws_response_class=CustomResponse\n            ).ws_connect(\"http://test.org\")\n\n    # TODO(PY313): Use TypeVar(default=) to make ClientSession Generic over response classes.\n    #              Then .ws_connect() can return CustomResponse here.\n    assert res.read() == \"customized!\"  # type: ignore[attr-defined]\n\n\nasync def test_ws_connect_err_status(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 500\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n    }\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            with pytest.raises(client.WSServerHandshakeError) as ctx:\n                await aiohttp.ClientSession().ws_connect(\n                    \"http://test.org\", protocols=(\"t1\", \"t2\", \"chat\")\n                )\n\n    assert ctx.value.message == \"Invalid response status\"\n\n\nasync def test_ws_connect_err_upgrade(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"test\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n    }\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            with pytest.raises(client.WSServerHandshakeError) as ctx:\n                await aiohttp.ClientSession().ws_connect(\n                    \"http://test.org\", protocols=(\"t1\", \"t2\", \"chat\")\n                )\n\n    assert ctx.value.message == \"Invalid upgrade header\"\n\n\nasync def test_ws_connect_err_conn(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"close\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n    }\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            with pytest.raises(client.WSServerHandshakeError) as ctx:\n                await aiohttp.ClientSession().ws_connect(\n                    \"http://test.org\", protocols=(\"t1\", \"t2\", \"chat\")\n                )\n\n    assert ctx.value.message == \"Invalid connection header\"\n\n\nasync def test_ws_connect_err_challenge(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: \"asdfasdfasdfasdfasdfasdf\",\n    }\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            with pytest.raises(client.WSServerHandshakeError) as ctx:\n                await aiohttp.ClientSession().ws_connect(\n                    \"http://test.org\", protocols=(\"t1\", \"t2\", \"chat\")\n                )\n\n    assert ctx.value.message == \"Invalid challenge response\"\n\n\nasync def test_ws_connect_common_headers(\n    ws_key: str, loop: asyncio.AbstractEventLoop, key_data: bytes\n) -> None:\n    # Emulate a headers dict being reused for a second ws_connect.\n\n    # In this scenario, we need to ensure that the newly generated secret key\n    # is sent to the server, not the stale key.\n    headers: dict[str, str] = {}\n\n    async def test_connection() -> None:\n        async def mock_get(\n            *args: object, headers: Mapping[str, str], **kwargs: object\n        ) -> mock.Mock:\n            resp = mock.Mock()\n            resp.status = 101\n            key = headers[hdrs.SEC_WEBSOCKET_KEY]\n            accept = base64.b64encode(\n                hashlib.sha1(base64.b64encode(base64.b64decode(key)) + WS_KEY).digest()\n            ).decode()\n            resp.headers = {\n                hdrs.UPGRADE: \"websocket\",\n                hdrs.CONNECTION: \"upgrade\",\n                hdrs.SEC_WEBSOCKET_ACCEPT: accept,\n                hdrs.SEC_WEBSOCKET_PROTOCOL: \"chat\",\n            }\n            resp.connection.protocol.read_timeout = None\n            return resp\n\n        with mock.patch(\"aiohttp.client.os\") as m_os:\n            with mock.patch(\n                \"aiohttp.client.ClientSession.request\", side_effect=mock_get\n            ) as m_req:\n                m_os.urandom.return_value = key_data\n\n                res = await aiohttp.ClientSession().ws_connect(\n                    \"http://test.org\", protocols=(\"t1\", \"t2\", \"chat\"), headers=headers\n                )\n\n        assert isinstance(res, client.ClientWebSocketResponse)\n        assert res.protocol == \"chat\"\n        assert hdrs.ORIGIN not in m_req.call_args[1][\"headers\"]\n\n    await test_connection()\n    # Generate a new ws key\n    key_data = os.urandom(16)\n    await test_connection()\n\n\nasync def test_close(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    mresp = mock.Mock()\n    mresp.status = 101\n    mresp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n    }\n    mresp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.WebSocketWriter\") as WebSocketWriter:\n        with mock.patch(\"aiohttp.client.os\") as m_os:\n            with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n                m_os.urandom.return_value = key_data\n                m_req.return_value = loop.create_future()\n                m_req.return_value.set_result(mresp)\n                writer = mock.create_autospec(\n                    RealWebSocketWriter, instance=True, spec_set=True\n                )\n                WebSocketWriter.return_value = writer\n\n                session = aiohttp.ClientSession()\n                resp = await session.ws_connect(\"http://test.org\")\n                assert not resp.closed\n\n                resp._reader.feed_data(WSMessageClose(data=0, size=0, extra=\"\"))\n\n                res = await resp.close()\n                writer.close.assert_called_with(1000, b\"\")\n                assert resp.closed\n                assert res  # type: ignore[unreachable]\n                assert resp.exception() is None\n\n                # idempotent\n                res = await resp.close()\n                assert not res\n                assert writer.close.call_count == 1\n\n                await session.close()\n\n\nasync def test_close_eofstream(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    mresp = mock.Mock()\n    mresp.status = 101\n    mresp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n    }\n    mresp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.WebSocketWriter\") as WebSocketWriter:\n        with mock.patch(\"aiohttp.client.os\") as m_os:\n            with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n                m_os.urandom.return_value = key_data\n                m_req.return_value = loop.create_future()\n                m_req.return_value.set_result(mresp)\n                writer = WebSocketWriter.return_value = mock.Mock()\n\n                session = aiohttp.ClientSession()\n                resp = await session.ws_connect(\"http://test.org\")\n                assert not resp.closed\n\n                exc = EofStream()\n                resp._reader.set_exception(exc)\n\n                await resp.receive()\n                writer.close.assert_called_with(1000, b\"\")\n                assert resp.closed\n\n                await session.close()  # type: ignore[unreachable]\n\n\nasync def test_close_connection_lost(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    \"\"\"Test the websocket client handles the connection being closed out from under it.\"\"\"\n    mresp = mock.Mock(spec_set=client.ClientResponse)\n    mresp.status = 101\n    mresp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n    }\n    mresp.connection.protocol.read_timeout = None\n    with (\n        mock.patch(\"aiohttp.client.WebSocketWriter\"),\n        mock.patch(\"aiohttp.client.os\") as m_os,\n        mock.patch(\"aiohttp.client.ClientSession.request\") as m_req,\n    ):\n        m_os.urandom.return_value = key_data\n        m_req.return_value = loop.create_future()\n        m_req.return_value.set_result(mresp)\n\n        session = aiohttp.ClientSession()\n        resp = await session.ws_connect(\"http://test.org\")\n        assert not resp.closed\n\n        exc = ServerDisconnectedError()\n        resp._reader.set_exception(exc)\n\n        msg = await resp.receive()\n        assert msg.type is aiohttp.WSMsgType.CLOSED\n        assert resp.closed\n\n        await session.close()  # type: ignore[unreachable]\n\n\nasync def test_close_exc(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    mresp = mock.Mock()\n    mresp.status = 101\n    mresp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n    }\n    mresp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.WebSocketWriter\") as WebSocketWriter:\n        with mock.patch(\"aiohttp.client.os\") as m_os:\n            with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n                m_os.urandom.return_value = key_data\n                m_req.return_value = loop.create_future()\n                m_req.return_value.set_result(mresp)\n                writer = mock.create_autospec(\n                    RealWebSocketWriter, instance=True, spec_set=True\n                )\n                WebSocketWriter.return_value = writer\n\n                session = aiohttp.ClientSession()\n                resp = await session.ws_connect(\"http://test.org\")\n                assert not resp.closed\n\n                exc = ValueError()\n                resp._reader.set_exception(exc)\n\n                await resp.close()\n                assert resp.closed\n                assert resp.exception() is exc  # type: ignore[unreachable]\n\n                await session.close()\n\n\nasync def test_close_exc2(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    mresp = mock.Mock()\n    mresp.status = 101\n    mresp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n    }\n    mresp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.WebSocketWriter\") as WebSocketWriter:\n        with mock.patch(\"aiohttp.client.os\") as m_os:\n            with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n                m_os.urandom.return_value = key_data\n                m_req.return_value = loop.create_future()\n                m_req.return_value.set_result(mresp)\n                writer = WebSocketWriter.return_value = mock.Mock()\n\n                resp = await aiohttp.ClientSession().ws_connect(\"http://test.org\")\n                assert not resp.closed\n\n                exc = ValueError()\n                writer.close.side_effect = exc\n\n                await resp.close()\n                assert resp.closed\n                assert resp.exception() is exc  # type: ignore[unreachable]\n\n                resp._closed = False\n                writer.close.side_effect = asyncio.CancelledError()\n                with pytest.raises(asyncio.CancelledError):\n                    await resp.close()\n\n\n@pytest.mark.parametrize(\"exc\", (ClientConnectionResetError, ConnectionResetError))\nasync def test_send_data_after_close(\n    exc: type[Exception],\n    ws_key: str,\n    key_data: bytes,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    mresp = mock.Mock()\n    mresp.status = 101\n    mresp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n    }\n    mresp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(mresp)\n\n            resp = await aiohttp.ClientSession().ws_connect(\"http://test.org\")\n            resp._writer._closing = True\n\n            for meth, args in (\n                (resp.ping, ()),\n                (resp.pong, ()),\n                (resp.send_str, (\"s\",)),\n                (resp.send_bytes, (b\"b\",)),\n                (resp.send_json, ({},)),\n                (resp.send_frame, (b\"\", aiohttp.WSMsgType.BINARY)),\n            ):\n                with pytest.raises(exc):  # Verify exc can be caught with both classes\n                    await meth(*args)\n\n\nasync def test_send_data_type_errors(\n    ws_key: str, key_data: bytes, loop: asyncio.AbstractEventLoop\n) -> None:\n    mresp = mock.Mock()\n    mresp.status = 101\n    mresp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n    }\n    mresp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.WebSocketWriter\") as WebSocketWriter:\n        with mock.patch(\"aiohttp.client.os\") as m_os:\n            with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n                m_os.urandom.return_value = key_data\n                m_req.return_value = loop.create_future()\n                m_req.return_value.set_result(mresp)\n                WebSocketWriter.return_value = mock.Mock()\n\n                resp = await aiohttp.ClientSession().ws_connect(\"http://test.org\")\n\n                with pytest.raises(TypeError):\n                    await resp.send_str(b\"s\")  # type: ignore[arg-type]\n                with pytest.raises(TypeError):\n                    await resp.send_bytes(\"b\")  # type: ignore[arg-type]\n                with pytest.raises(TypeError):\n                    await resp.send_json(set())\n\n\nasync def test_reader_read_exception(\n    ws_key: str, key_data: bytes, loop: asyncio.AbstractEventLoop\n) -> None:\n    hresp = mock.Mock()\n    hresp.status = 101\n    hresp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n    }\n    hresp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.WebSocketWriter\") as WebSocketWriter:\n        with mock.patch(\"aiohttp.client.os\") as m_os:\n            with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n                m_os.urandom.return_value = key_data\n                m_req.return_value = loop.create_future()\n                m_req.return_value.set_result(hresp)\n\n                writer = mock.create_autospec(\n                    RealWebSocketWriter, instance=True, spec_set=True\n                )\n                WebSocketWriter.return_value = writer\n\n                session = aiohttp.ClientSession()\n                resp = await session.ws_connect(\"http://test.org\")\n\n                exc = ValueError()\n                resp._reader.set_exception(exc)\n\n                msg = await resp.receive()\n                assert msg.type == aiohttp.WSMsgType.ERROR\n                assert resp.exception() is exc\n\n                await session.close()\n\n\nasync def test_receive_runtime_err(loop: asyncio.AbstractEventLoop) -> None:\n    resp = client.ClientWebSocketResponse(\n        mock.Mock(),\n        mock.Mock(),\n        mock.Mock(),\n        mock.Mock(),\n        ClientWSTimeout(ws_receive=10.0),\n        True,\n        True,\n        loop,\n    )\n    resp._waiting = True\n\n    with pytest.raises(RuntimeError):\n        await resp.receive()\n\n\nasync def test_heartbeat_reset_coalesces_on_data(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    response = mock.Mock()\n    response.connection = None\n    resp = client.ClientWebSocketResponse(\n        mock.Mock(),\n        mock.Mock(),\n        None,\n        response,\n        ClientWSTimeout(ws_receive=10.0),\n        True,\n        True,\n        loop,\n        heartbeat=0.05,\n    )\n    with mock.patch.object(resp, \"_reset_heartbeat\", autospec=True) as reset:\n        resp._on_data_received()\n        resp._on_data_received()\n\n        await asyncio.sleep(0)\n\n        assert reset.call_count == 1\n\n\nasync def test_receive_does_not_reset_heartbeat(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    response = mock.Mock()\n    response.connection = None\n    msg = mock.Mock(type=aiohttp.WSMsgType.TEXT)\n    reader = mock.Mock()\n    reader.read = mock.AsyncMock(return_value=msg)\n    resp = client.ClientWebSocketResponse(\n        reader,\n        mock.Mock(),\n        None,\n        response,\n        ClientWSTimeout(ws_receive=10.0),\n        True,\n        True,\n        loop,\n        heartbeat=0.05,\n    )\n    with mock.patch.object(resp, \"_reset_heartbeat\", autospec=True) as reset:\n        received = await resp.receive()\n\n    assert received is msg\n    reset.assert_not_called()\n\n\nasync def test_cancel_heartbeat_cancels_pending_heartbeat_reset_handle(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    response = mock.Mock()\n    response.connection = None\n    resp = client.ClientWebSocketResponse(\n        mock.Mock(),\n        mock.Mock(),\n        None,\n        response,\n        ClientWSTimeout(ws_receive=10.0),\n        True,\n        True,\n        loop,\n        heartbeat=0.05,\n    )\n\n    resp._on_data_received()\n    handle = resp._heartbeat_reset_handle\n    assert handle is not None\n\n    resp._cancel_heartbeat()\n\n    assert resp._heartbeat_reset_handle is None\n    assert resp._need_heartbeat_reset is False\n    assert handle.cancelled()\n\n\nasync def test_flush_heartbeat_reset_returns_early_when_not_needed(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    response = mock.Mock()\n    response.connection = None\n    resp = client.ClientWebSocketResponse(\n        mock.Mock(),\n        mock.Mock(),\n        None,\n        response,\n        ClientWSTimeout(ws_receive=10.0),\n        True,\n        True,\n        loop,\n        heartbeat=0.05,\n    )\n    resp._need_heartbeat_reset = False\n\n    with mock.patch.object(resp, \"_reset_heartbeat\", autospec=True) as reset:\n        resp._flush_heartbeat_reset()\n        reset.assert_not_called()\n\n\nasync def test_send_heartbeat_returns_early_when_reset_is_pending(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    response = mock.Mock()\n    response.connection = None\n    writer = mock.Mock()\n    resp = client.ClientWebSocketResponse(\n        mock.Mock(),\n        writer,\n        None,\n        response,\n        ClientWSTimeout(ws_receive=10.0),\n        True,\n        True,\n        loop,\n        heartbeat=0.05,\n    )\n    resp._need_heartbeat_reset = True\n\n    resp._send_heartbeat()\n\n    writer.send_frame.assert_not_called()\n\n\nasync def test_ws_connect_close_resp_on_err(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 500\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n    }\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            with pytest.raises(client.WSServerHandshakeError):\n                await aiohttp.ClientSession().ws_connect(\n                    \"http://test.org\", protocols=(\"t1\", \"t2\", \"chat\")\n                )\n            resp.close.assert_called_with()\n\n\nasync def test_ws_connect_non_overlapped_protocols(\n    ws_key: str, loop: asyncio.AbstractEventLoop, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n        hdrs.SEC_WEBSOCKET_PROTOCOL: \"other,another\",\n    }\n    resp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            res = await aiohttp.ClientSession().ws_connect(\n                \"http://test.org\", protocols=(\"t1\", \"t2\", \"chat\")\n            )\n\n    assert res.protocol is None\n\n\nasync def test_ws_connect_non_overlapped_protocols_2(\n    ws_key: str, loop: asyncio.AbstractEventLoop, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n        hdrs.SEC_WEBSOCKET_PROTOCOL: \"other,another\",\n    }\n    resp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            connector = aiohttp.TCPConnector(force_close=True)\n            res = await aiohttp.ClientSession(connector=connector).ws_connect(\n                \"http://test.org\", protocols=(\"t1\", \"t2\", \"chat\")\n            )\n\n    assert res.protocol is None\n    del res\n\n\nasync def test_ws_connect_deflate(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n        hdrs.SEC_WEBSOCKET_EXTENSIONS: \"permessage-deflate\",\n    }\n    resp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            res = await aiohttp.ClientSession().ws_connect(\n                \"http://test.org\", compress=15\n            )\n\n    assert res.compress == 15\n    assert res.client_notakeover is False\n\n\nasync def test_ws_connect_deflate_per_message(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    mresp = mock.Mock()\n    mresp.status = 101\n    mresp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n        hdrs.SEC_WEBSOCKET_EXTENSIONS: \"permessage-deflate\",\n    }\n    mresp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.WebSocketWriter\") as WebSocketWriter:\n        with mock.patch(\"aiohttp.client.os\") as m_os:\n            with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n                m_os.urandom.return_value = key_data\n                m_req.return_value = loop.create_future()\n                m_req.return_value.set_result(mresp)\n                writer = mock.create_autospec(\n                    RealWebSocketWriter, instance=True, spec_set=True\n                )\n\n                WebSocketWriter.return_value = writer\n\n                session = aiohttp.ClientSession()\n                resp = await session.ws_connect(\"http://test.org\")\n\n                await resp.send_str(\"string\", compress=-1)\n                writer.send_frame.assert_called_with(\n                    b\"string\", aiohttp.WSMsgType.TEXT, compress=-1\n                )\n\n                await resp.send_bytes(b\"bytes\", compress=15)\n                writer.send_frame.assert_called_with(\n                    b\"bytes\", aiohttp.WSMsgType.BINARY, compress=15\n                )\n\n                await resp.send_json([{}], compress=-9)\n                writer.send_frame.assert_called_with(\n                    b\"[{}]\", aiohttp.WSMsgType.TEXT, compress=-9\n                )\n\n                await resp.send_frame(b\"[{}]\", aiohttp.WSMsgType.TEXT, compress=-9)\n                writer.send_frame.assert_called_with(\n                    b\"[{}]\", aiohttp.WSMsgType.TEXT, -9\n                )\n\n                await session.close()\n\n\nasync def test_ws_connect_deflate_server_not_support(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n    }\n    resp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            res = await aiohttp.ClientSession().ws_connect(\n                \"http://test.org\", compress=15\n            )\n\n    assert res.compress == 0\n    assert res.client_notakeover is False\n\n\nasync def test_ws_connect_deflate_notakeover(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n        hdrs.SEC_WEBSOCKET_EXTENSIONS: \"permessage-deflate; \"\n        \"client_no_context_takeover\",\n    }\n    resp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            res = await aiohttp.ClientSession().ws_connect(\n                \"http://test.org\", compress=15\n            )\n\n    assert res.compress == 15\n    assert res.client_notakeover is True\n\n\nasync def test_ws_connect_deflate_client_wbits(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n        hdrs.SEC_WEBSOCKET_EXTENSIONS: \"permessage-deflate; \"\n        \"client_max_window_bits=10\",\n    }\n    resp.connection.protocol.read_timeout = None\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            res = await aiohttp.ClientSession().ws_connect(\n                \"http://test.org\", compress=15\n            )\n\n    assert res.compress == 10\n    assert res.client_notakeover is False\n\n\nasync def test_ws_connect_deflate_client_wbits_bad(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n        hdrs.SEC_WEBSOCKET_EXTENSIONS: \"permessage-deflate; \"\n        \"client_max_window_bits=6\",\n    }\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            with pytest.raises(client.WSServerHandshakeError):\n                await aiohttp.ClientSession().ws_connect(\"http://test.org\", compress=15)\n\n\nasync def test_ws_connect_deflate_server_ext_bad(\n    loop: asyncio.AbstractEventLoop, ws_key: str, key_data: bytes\n) -> None:\n    resp = mock.Mock()\n    resp.status = 101\n    resp.headers = {\n        hdrs.UPGRADE: \"websocket\",\n        hdrs.CONNECTION: \"upgrade\",\n        hdrs.SEC_WEBSOCKET_ACCEPT: ws_key,\n        hdrs.SEC_WEBSOCKET_EXTENSIONS: \"permessage-deflate; bad\",\n    }\n    with mock.patch(\"aiohttp.client.os\") as m_os:\n        with mock.patch(\"aiohttp.client.ClientSession.request\") as m_req:\n            m_os.urandom.return_value = key_data\n            m_req.return_value = loop.create_future()\n            m_req.return_value.set_result(resp)\n\n            with pytest.raises(client.WSServerHandshakeError):\n                await aiohttp.ClientSession().ws_connect(\"http://test.org\", compress=15)\n"
  },
  {
    "path": "tests/test_client_ws_functional.py",
    "content": "import asyncio\nimport json\nimport struct\nimport sys\nfrom contextlib import suppress\nfrom typing import Literal, NoReturn\nfrom unittest import mock\n\nimport pytest\n\nimport aiohttp\nfrom aiohttp import (\n    ClientConnectionResetError,\n    ServerTimeoutError,\n    WSMessageTypeError,\n    WSMsgType,\n    hdrs,\n    web,\n)\nfrom aiohttp._websocket.models import WSMessageBinary\nfrom aiohttp._websocket.reader import WebSocketDataQueue\nfrom aiohttp.client_ws import ClientWSTimeout\nfrom aiohttp.http import WSCloseCode\nfrom aiohttp.pytest_plugin import AiohttpClient, AiohttpServer\n\nif sys.version_info >= (3, 11):\n    import asyncio as async_timeout\nelse:\n    import async_timeout\n\n\nclass PatchableWebSocketDataQueue(WebSocketDataQueue):\n    \"\"\"A WebSocketDataQueue that can be patched.\"\"\"\n\n\nasync def test_send_recv_text(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive_str()\n        await ws.send_str(msg + \"/answer\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n    await resp.send_str(\"ask\")\n\n    assert resp.get_extra_info(\"socket\") is not None\n\n    data = await resp.receive_str()\n    assert data == \"ask/answer\"\n    await resp.close()\n\n    assert resp.get_extra_info(\"socket\") is None\n\n\nasync def test_send_recv_bytes_bad_type(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive_str()\n        await ws.send_str(msg + \"/answer\")\n        await ws.close()\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n    await resp.send_str(\"ask\")\n\n    with pytest.raises(WSMessageTypeError):\n        await resp.receive_bytes()\n    await resp.close()\n\n\nasync def test_recv_bytes_after_close(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        await ws.close()\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n\n    with pytest.raises(\n        WSMessageTypeError,\n        match=f\"Received message {WSMsgType.CLOSE}:.+ is not WSMsgType.BINARY\",\n    ):\n        await resp.receive_bytes()\n    await resp.close()\n\n\nasync def test_send_recv_bytes(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive_bytes()\n        await ws.send_bytes(msg + b\"/answer\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n\n    await resp.send_bytes(b\"ask\")\n\n    data = await resp.receive_bytes()\n    assert data == b\"ask/answer\"\n\n    await resp.close()\n\n\nasync def test_send_recv_text_bad_type(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive_bytes()\n        await ws.send_bytes(msg + b\"/answer\")\n        await ws.close()\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n\n    await resp.send_bytes(b\"ask\")\n\n    with pytest.raises(WSMessageTypeError):\n        await resp.receive_str()\n    await resp.close()\n\n\nasync def test_recv_text_after_close(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        await ws.close()\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n\n    with pytest.raises(\n        WSMessageTypeError,\n        match=f\"Received message {WSMsgType.CLOSE}:.+ is not WSMsgType.TEXT\",\n    ):\n        await resp.receive_str()\n    await resp.close()\n\n\nasync def test_send_recv_json(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        data = await ws.receive_json()\n        await ws.send_json({\"response\": data[\"request\"]})\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n    payload = {\"request\": \"test\"}\n    await resp.send_json(payload)\n\n    data = await resp.receive_json()\n    assert data[\"response\"] == payload[\"request\"]\n    await resp.close()\n\n\nasync def test_send_recv_json_bytes(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        await ws.send_bytes(json.dumps({\"response\": \"x\"}).encode())\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n    data = await resp.receive()\n    assert isinstance(data, WSMessageBinary)\n    assert data.json() == {\"response\": \"x\"}\n    await resp.close()\n\n\nasync def test_send_json_bytes_client(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test ClientWebSocketResponse.send_json_bytes sends binary frame.\"\"\"\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive()\n        assert msg.type is WSMsgType.BINARY\n        data = json.loads(msg.data)\n        await ws.send_json_bytes(\n            {\"response\": data[\"request\"]},\n            dumps=lambda x: json.dumps(x).encode(\"utf-8\"),\n        )\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n    test_payload = {\"request\": \"test\"}\n    await resp.send_json_bytes(\n        test_payload, dumps=lambda x: json.dumps(x).encode(\"utf-8\")\n    )\n\n    msg = await resp.receive()\n    assert msg.type is WSMsgType.BINARY\n    data = json.loads(msg.data)\n    assert data[\"response\"] == test_payload[\"request\"]\n    await resp.close()\n\n\nasync def test_send_json_bytes_custom_encoder(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test send_json_bytes with custom bytes-returning encoder.\"\"\"\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive()\n        assert msg.type is WSMsgType.BINARY\n        # Custom encoder uses compact separators\n        assert msg.data == b'{\"test\":\"value\"}'\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n    await resp.send_json_bytes(\n        {\"test\": \"value\"},\n        dumps=lambda x: json.dumps(x, separators=(\",\", \":\")).encode(\"utf-8\"),\n    )\n    await resp.close()\n\n\nasync def test_send_recv_frame(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive()\n        assert msg.type is WSMsgType.BINARY\n        await ws.send_frame(msg.data, msg.type)\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n    await resp.send_frame(b\"test\", WSMsgType.BINARY)\n\n    data = await resp.receive()\n    assert data.data == b\"test\"\n    assert data.type is WSMsgType.BINARY\n    await resp.close()\n\n\nasync def test_ping_pong(aiohttp_client: AiohttpClient) -> None:\n    loop = asyncio.get_event_loop()\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive_bytes()\n        await ws.ping()\n        await ws.send_bytes(msg + b\"/answer\")\n        try:\n            await ws.close()\n        finally:\n            closed.set_result(1)\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n\n    await resp.ping()\n    await resp.send_bytes(b\"ask\")\n\n    msg = await resp.receive()\n    assert msg.type == aiohttp.WSMsgType.BINARY\n    assert msg.data == b\"ask/answer\"\n\n    msg = await resp.receive()\n    assert msg.type == aiohttp.WSMsgType.CLOSE\n\n    await resp.close()\n    await closed\n\n\nasync def test_ping_pong_manual(aiohttp_client: AiohttpClient) -> None:\n    loop = asyncio.get_event_loop()\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive_bytes()\n        await ws.ping()\n        await ws.send_bytes(msg + b\"/answer\")\n        try:\n            await ws.close()\n        finally:\n            closed.set_result(1)\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\", autoping=False)\n\n    await resp.ping()\n    await resp.send_bytes(b\"ask\")\n\n    msg = await resp.receive()\n    assert msg.type == aiohttp.WSMsgType.PONG\n\n    msg = await resp.receive()\n    assert msg.type == aiohttp.WSMsgType.PING\n    await resp.pong()\n\n    msg = await resp.receive()\n    assert msg.data == b\"ask/answer\"\n\n    msg = await resp.receive()\n    assert msg.type == aiohttp.WSMsgType.CLOSE\n\n    await closed\n\n\nasync def test_close(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        await ws.receive_bytes()\n        await ws.send_str(\"test\")\n\n        await ws.receive()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n\n    await resp.send_bytes(b\"ask\")\n\n    closed = await resp.close()\n    assert closed\n    assert resp.closed\n    assert resp.close_code == 1000\n\n    msg = await resp.receive()\n    assert msg.type == aiohttp.WSMsgType.CLOSED\n\n\nasync def test_concurrent_task_close(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        await ws.receive()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    client = await aiohttp_client(app)\n    async with client.ws_connect(\"/\") as resp:\n        # wait for the message in a separate task\n        task = asyncio.create_task(resp.receive())\n\n        # Make sure we start to wait on receiving message before closing the connection\n        await asyncio.sleep(0.1)\n\n        closed = await resp.close()\n\n        await task\n\n        assert closed\n        assert resp.closed\n        assert resp.close_code == 1000\n\n\nasync def test_concurrent_close(aiohttp_client: AiohttpClient) -> None:\n    client_ws: aiohttp.ClientWebSocketResponse | None = None\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        await ws.receive_bytes()\n        await ws.send_str(\"test\")\n\n        assert client_ws is not None\n        await client_ws.close()\n\n        msg = await ws.receive()\n        assert msg.type is aiohttp.WSMsgType.CLOSE\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    ws = client_ws = await client.ws_connect(\"/\")\n\n    await ws.send_bytes(b\"ask\")\n\n    msg = await ws.receive()\n    assert msg.type is aiohttp.WSMsgType.CLOSING\n\n    await asyncio.sleep(0.01)\n    msg = await ws.receive()\n    assert msg.type is aiohttp.WSMsgType.CLOSED\n\n\nasync def test_concurrent_close_multiple_tasks(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        await ws.receive_bytes()\n        await ws.send_str(\"test\")\n\n        msg = await ws.receive()\n        assert msg.type is aiohttp.WSMsgType.CLOSE\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    ws = await client.ws_connect(\"/\")\n\n    await ws.send_bytes(b\"ask\")\n\n    task1 = asyncio.create_task(ws.close())\n    task2 = asyncio.create_task(ws.close())\n\n    msg = await ws.receive()\n    assert msg.type is aiohttp.WSMsgType.CLOSED\n\n    await task1\n    await task2\n\n    msg = await ws.receive()\n    assert msg.type is aiohttp.WSMsgType.CLOSED\n\n\nasync def test_close_from_server(aiohttp_client: AiohttpClient) -> None:\n    loop = asyncio.get_event_loop()\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        try:\n            await ws.receive_bytes()\n            await ws.close()\n        finally:\n            closed.set_result(1)\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n\n    await resp.send_bytes(b\"ask\")\n\n    msg = await resp.receive()\n    assert msg.type == aiohttp.WSMsgType.CLOSE\n    assert resp.closed\n\n    msg = await resp.receive()\n    assert msg.type == aiohttp.WSMsgType.CLOSED\n\n    await closed\n\n\nasync def test_close_manual(aiohttp_client: AiohttpClient) -> None:\n    loop = asyncio.get_event_loop()\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        await ws.receive_bytes()\n        await ws.send_str(\"test\")\n\n        try:\n            await ws.close()\n        finally:\n            closed.set_result(1)\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\", autoclose=False)\n    await resp.send_bytes(b\"ask\")\n\n    msg = await resp.receive()\n    assert msg.data == \"test\"\n\n    msg = await resp.receive()\n    assert msg.type == aiohttp.WSMsgType.CLOSE\n    assert msg.data == 1000\n    assert msg.extra == \"\"\n    assert not resp.closed\n\n    await resp.close()\n    await closed\n    assert resp.closed\n\n\nasync def test_close_timeout_sock_close_read(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        await ws.receive_bytes()\n        await ws.send_str(\"test\")\n        await asyncio.sleep(1)\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    timeout = ClientWSTimeout(ws_close=0.2)\n    resp = await client.ws_connect(\"/\", timeout=timeout, autoclose=False)\n\n    await resp.send_bytes(b\"ask\")\n\n    msg = await resp.receive()\n    assert msg.data == \"test\"\n    assert msg.type == aiohttp.WSMsgType.TEXT\n\n    await resp.close()\n    assert resp.closed\n    assert isinstance(resp.exception(), asyncio.TimeoutError)\n\n\nasync def test_close_timeout_deprecated(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        await ws.receive_bytes()\n        await ws.send_str(\"test\")\n        await asyncio.sleep(1)\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    with pytest.warns(\n        DeprecationWarning,\n        match=\"parameter 'timeout' of type 'float' \"\n        \"is deprecated, please use \"\n        r\"'timeout=ClientWSTimeout\\(ws_close=...\\)'\",\n    ):\n        resp = await client.ws_connect(\"/\", timeout=0.2, autoclose=False)\n\n    await resp.send_bytes(b\"ask\")\n\n    msg = await resp.receive()\n    assert msg.data == \"test\"\n    assert msg.type == aiohttp.WSMsgType.TEXT\n\n    await resp.close()\n    assert resp.closed\n    assert isinstance(resp.exception(), asyncio.TimeoutError)\n\n\nasync def test_close_cancel(aiohttp_client: AiohttpClient) -> None:\n    loop = asyncio.get_event_loop()\n\n    async def handler(request: web.Request) -> NoReturn:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        await ws.receive_bytes()\n        await ws.send_str(\"test\")\n        await asyncio.sleep(10)\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\", autoclose=False)\n\n    await resp.send_bytes(b\"ask\")\n\n    text = await resp.receive()\n    assert text.data == \"test\"\n\n    t = loop.create_task(resp.close())\n    await asyncio.sleep(0.1)\n    t.cancel()\n    await asyncio.sleep(0.1)\n    assert resp.closed\n    assert resp.exception() is None\n\n\nasync def test_override_default_headers(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        assert request.headers[hdrs.SEC_WEBSOCKET_VERSION] == \"8\"\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        await ws.send_str(\"answer\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    headers = {hdrs.SEC_WEBSOCKET_VERSION: \"8\"}\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\", headers=headers)\n    msg = await resp.receive()\n    assert msg.data == \"answer\"\n    await resp.close()\n\n\nasync def test_additional_headers(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        assert request.headers[\"x-hdr\"] == \"xtra\"\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        await ws.send_str(\"answer\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\", headers={\"x-hdr\": \"xtra\"})\n    msg = await resp.receive()\n    assert msg.data == \"answer\"\n    await resp.close()\n\n\nasync def test_recv_protocol_error(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        await ws.receive_str()\n        assert ws._writer is not None\n        ws._writer.transport.write(b\"01234\" * 100)\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n    await resp.send_str(\"ask\")\n\n    msg = await resp.receive()\n    assert msg.type == aiohttp.WSMsgType.ERROR\n    assert type(msg.data) is aiohttp.WebSocketError\n    assert msg.data.code == aiohttp.WSCloseCode.PROTOCOL_ERROR\n    assert str(msg.data) == \"Received frame with non-zero reserved bits\"\n    assert msg.extra is None\n    await resp.close()\n\n\nasync def test_recv_timeout(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        await ws.receive_str()\n\n        await asyncio.sleep(0.1)\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n    await resp.send_str(\"ask\")\n\n    with pytest.raises(asyncio.TimeoutError):\n        async with async_timeout.timeout(0.01):\n            await resp.receive()\n\n    await resp.close()\n\n\nasync def test_receive_timeout_sock_read(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        await ws.receive()\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    client = await aiohttp_client(app)\n    receive_timeout = ClientWSTimeout(ws_receive=0.1)\n    resp = await client.ws_connect(\"/\", timeout=receive_timeout)\n\n    with pytest.raises(asyncio.TimeoutError):\n        await resp.receive(timeout=0.05)\n\n    await resp.close()\n\n\nasync def test_receive_timeout_deprecation(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        await ws.receive()\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    client = await aiohttp_client(app)\n    with pytest.warns(\n        DeprecationWarning,\n        match=\"float parameter 'receive_timeout' \"\n        \"is deprecated, please use parameter \"\n        r\"'timeout=ClientWSTimeout\\(ws_receive=...\\)'\",\n    ):\n        resp = await client.ws_connect(\"/\", receive_timeout=0.1)\n\n    with pytest.raises(asyncio.TimeoutError):\n        await resp.receive(timeout=0.05)\n\n    await resp.close()\n\n\nasync def test_custom_receive_timeout(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        await ws.receive()\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n\n    with pytest.raises(asyncio.TimeoutError):\n        await resp.receive(0.05)\n\n    await resp.close()\n\n\nasync def test_heartbeat(aiohttp_client: AiohttpClient) -> None:\n    ping_received = False\n\n    async def handler(request: web.Request) -> NoReturn:\n        nonlocal ping_received\n        ws = web.WebSocketResponse(autoping=False)\n        await ws.prepare(request)\n        msg = await ws.receive()\n        assert msg.type == aiohttp.WSMsgType.PING\n        ping_received = True\n        await ws.close()\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\", heartbeat=0.01)\n    await asyncio.sleep(0.1)\n    await resp.receive()\n    await resp.close()\n\n    assert ping_received\n\n\nasync def test_heartbeat_connection_closed(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that the connection is closed while ping is in progress.\"\"\"\n\n    async def handler(request: web.Request) -> NoReturn:\n        ws = web.WebSocketResponse(autoping=False)\n        await ws.prepare(request)\n        await ws.receive()\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\", heartbeat=0.1)\n    ping_count = 0\n    # We patch write here to simulate a connection reset error\n    # since if we closed the connection normally, the client would\n    # would cancel the heartbeat task and we wouldn't get a ping\n    assert resp._conn is not None\n    with (\n        mock.patch.object(\n            resp._conn.transport, \"write\", side_effect=ClientConnectionResetError\n        ),\n        mock.patch.object(\n            resp._writer, \"send_frame\", wraps=resp._writer.send_frame\n        ) as send_frame,\n    ):\n        await resp.receive()\n        ping_count = send_frame.call_args_list.count(mock.call(b\"\", WSMsgType.PING))\n    # Connection should be closed roughly after 1.5x heartbeat.\n    await asyncio.sleep(0.2)\n    assert ping_count == 1\n    assert resp.close_code is WSCloseCode.ABNORMAL_CLOSURE\n\n\nasync def test_heartbeat_no_pong(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that the connection is closed if no pong is received without sending messages.\"\"\"\n    ping_received = False\n\n    async def handler(request: web.Request) -> NoReturn:\n        nonlocal ping_received\n        ws = web.WebSocketResponse(autoping=False)\n        await ws.prepare(request)\n        msg = await ws.receive()\n        assert msg.type == aiohttp.WSMsgType.PING\n        ping_received = True\n        await ws.receive()\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\", heartbeat=0.1)\n\n    # Connection should be closed roughly after 1.5x heartbeat.\n    await asyncio.sleep(0.2)\n    assert ping_received\n    assert resp.close_code is WSCloseCode.ABNORMAL_CLOSURE\n\n\nasync def test_heartbeat_does_not_timeout_while_receiving_large_frame(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Slowly receiving a single large frame should not trip heartbeat.\n\n    Regression test for the behavior described in\n    https://github.com/aio-libs/aiohttp/discussions/12023: on slow connections,\n    the websocket heartbeat used to be reset only after a full message was read,\n    which could cause a ping/pong timeout while bytes were still being received.\n    \"\"\"\n    payload = b\"x\" * 2048\n    heartbeat = 0.1\n    chunk_size = 64\n    delay = 0.01\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        # Disable auto-PONG so a heartbeat PING during frame streaming would\n        # surface as a timeout/closure on the client side.\n        ws = web.WebSocketResponse(autoping=False)\n        await ws.prepare(request)\n\n        assert ws._writer is not None\n        transport = ws._writer.transport\n\n        # Server-to-client frames are not masked.\n        length = len(payload)  # payload is fixed length of 2048 bytes\n        header = bytes((0x82, 126)) + struct.pack(\"!H\", length)\n\n        frame = header + payload\n        for i in range(0, len(frame), chunk_size):\n            transport.write(frame[i : i + chunk_size])\n            await asyncio.sleep(delay)\n\n        # Ensure the server side is cleaned up.\n        with suppress(asyncio.TimeoutError):\n            await ws.receive(timeout=1.0)\n        with suppress(Exception):\n            await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.ws_connect(\"/\", heartbeat=heartbeat) as resp:\n        # If heartbeat were not reset on incoming bytes, the client would send\n        # a PING while this frame is still being streamed.\n        with mock.patch.object(\n            resp._writer, \"send_frame\", wraps=resp._writer.send_frame\n        ) as sf:\n            msg = await resp.receive()\n            assert (\n                sf.call_args_list.count(mock.call(b\"\", WSMsgType.PING)) == 0\n            ), \"Heartbeat PING sent while data was still being received\"\n        assert msg.type is WSMsgType.BINARY\n        assert msg.data == payload\n        assert not resp.closed\n\n\nasync def test_heartbeat_no_pong_after_receive_many_messages(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that the connection is closed if no pong is received after receiving many messages.\"\"\"\n    ping_received = False\n\n    async def handler(request: web.Request) -> NoReturn:\n        nonlocal ping_received\n        ws = web.WebSocketResponse(autoping=False)\n        await ws.prepare(request)\n        for _ in range(5):\n            await ws.send_str(\"test\")\n        await asyncio.sleep(0.05)\n        for _ in range(5):\n            await ws.send_str(\"test\")\n        msg = await ws.receive()\n        ping_received = msg.type is aiohttp.WSMsgType.PING\n        await ws.receive()\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\", heartbeat=0.1)\n\n    for _ in range(10):\n        test_msg = await resp.receive()\n        assert test_msg.data == \"test\"\n    # Connection should be closed roughly after 1.5x heartbeat.\n\n    await asyncio.sleep(0.2)\n    assert ping_received\n    assert resp.close_code is WSCloseCode.ABNORMAL_CLOSURE\n\n\nasync def test_heartbeat_no_pong_after_send_many_messages(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that the connection is closed if no pong is received after sending many messages.\"\"\"\n    ping_received = False\n\n    async def handler(request: web.Request) -> NoReturn:\n        nonlocal ping_received\n        ws = web.WebSocketResponse(autoping=False)\n        await ws.prepare(request)\n        for _ in range(10):\n            msg = await ws.receive()\n            assert msg.data == \"test\"\n            assert msg.type is aiohttp.WSMsgType.TEXT\n        msg = await ws.receive()\n        ping_received = msg.type is aiohttp.WSMsgType.PING\n        await ws.receive()\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\", heartbeat=0.1)\n\n    for _ in range(5):\n        await resp.send_str(\"test\")\n    await asyncio.sleep(0.05)\n    for _ in range(5):\n        await resp.send_str(\"test\")\n    # Connection should be closed roughly after 1.5x heartbeat.\n    await asyncio.sleep(0.2)\n    assert ping_received\n    assert resp.close_code is WSCloseCode.ABNORMAL_CLOSURE\n\n\nasync def test_heartbeat_no_pong_concurrent_receive(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    ping_received = False\n\n    async def handler(request: web.Request) -> NoReturn:\n        nonlocal ping_received\n        ws = web.WebSocketResponse(autoping=False)\n        with mock.patch(\n            \"aiohttp.web_ws.WebSocketDataQueue\", PatchableWebSocketDataQueue\n        ):\n            await ws.prepare(request)\n        msg = await ws.receive()\n        ping_received = msg.type is aiohttp.WSMsgType.PING\n        with mock.patch.object(\n            ws._reader, \"feed_eof\", autospec=True, spec_set=True, return_value=None\n        ):\n            await asyncio.sleep(10.0)\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    with mock.patch(\"aiohttp.client.WebSocketDataQueue\", PatchableWebSocketDataQueue):\n        client = await aiohttp_client(app)\n        resp = await client.ws_connect(\"/\", heartbeat=0.1)\n    with mock.patch.object(\n        resp._reader, \"feed_eof\", autospec=True, spec_set=True, return_value=None\n    ):\n        # Connection should be closed roughly after 1.5x heartbeat.\n        msg = await resp.receive(5.0)\n        assert ping_received\n        assert resp.close_code is WSCloseCode.ABNORMAL_CLOSURE\n        assert msg.type is WSMsgType.ERROR\n        assert isinstance(msg.data, ServerTimeoutError)\n        assert str(msg.data) == \"No PONG received after 0.05 seconds\"\n\n\nasync def test_close_websocket_while_ping_inflight(\n    aiohttp_client: AiohttpClient, loop: asyncio.AbstractEventLoop\n) -> None:\n    \"\"\"Test closing the websocket while a ping is in-flight.\"\"\"\n    ping_received = False\n\n    async def handler(request: web.Request) -> NoReturn:\n        nonlocal ping_received\n        ws = web.WebSocketResponse(autoping=False)\n        await ws.prepare(request)\n        msg = await ws.receive()\n        assert msg.type is aiohttp.WSMsgType.BINARY\n        msg = await ws.receive()\n        ping_received = msg.type is aiohttp.WSMsgType.PING\n        await ws.receive()\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\", heartbeat=0.1)\n    await resp.send_bytes(b\"ask\")\n\n    cancelled = False\n    ping_started = loop.create_future()\n\n    async def delayed_send_frame(\n        message: bytes, opcode: int, compress: int | None = None\n    ) -> None:\n        assert opcode == WSMsgType.PING\n        nonlocal cancelled\n        ping_started.set_result(None)\n        try:\n            await asyncio.sleep(1)\n        except asyncio.CancelledError:\n            cancelled = True\n            raise\n\n    with mock.patch.object(resp._writer, \"send_frame\", delayed_send_frame):\n        async with async_timeout.timeout(1):\n            await ping_started\n\n    await resp.close()\n    await asyncio.sleep(0)\n    assert ping_started.result() is None\n    assert cancelled is True\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_send_recv_compress(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive_str()\n        await ws.send_str(msg + \"/answer\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\", compress=15)\n    await resp.send_str(\"ask\")\n\n    assert resp.compress == 15\n\n    data = await resp.receive_str()\n    assert data == \"ask/answer\"\n\n    await resp.close()\n    assert resp.get_extra_info(\"socket\") is None\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_send_recv_compress_wbits(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive_str()\n        await ws.send_str(msg + \"/answer\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\", compress=9)\n    await resp.send_str(\"ask\")\n\n    # Client indicates supports wbits 15\n    # Server supports wbit 15 for decode\n    assert resp.compress == 15\n\n    data = await resp.receive_str()\n    assert data == \"ask/answer\"\n\n    await resp.close()\n    assert resp.get_extra_info(\"socket\") is None\n\n\nasync def test_send_recv_compress_wbit_error(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    with pytest.raises(ValueError):\n        await client.ws_connect(\"/\", compress=1)\n\n\nasync def test_ws_client_async_for(aiohttp_client: AiohttpClient) -> None:\n    items = [\"q1\", \"q2\", \"q3\"]\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        for i in items:\n            await ws.send_str(i)\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n    it = iter(items)\n    async for msg in resp:\n        assert msg.data == next(it)\n\n    with pytest.raises(StopIteration):\n        next(it)\n\n    assert resp.closed\n\n\nasync def test_ws_async_with(aiohttp_server: AiohttpServer) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        msg = await ws.receive()\n        assert msg.type is WSMsgType.TEXT\n        await ws.send_str(msg.data + \"/answer\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    server = await aiohttp_server(app)\n\n    async with aiohttp.ClientSession() as client:\n        async with client.ws_connect(server.make_url(\"/\")) as ws:\n            await ws.send_str(\"request\")\n            msg = await ws.receive()\n            assert msg.data == \"request/answer\"\n\n        assert ws.closed\n\n\nasync def test_ws_async_with_send(aiohttp_server: AiohttpServer) -> None:\n    # send_xxx methods have to return awaitable objects\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        msg = await ws.receive()\n        assert msg.type is WSMsgType.TEXT\n        await ws.send_str(msg.data + \"/answer\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    server = await aiohttp_server(app)\n\n    async with aiohttp.ClientSession() as client:\n        async with client.ws_connect(server.make_url(\"/\")) as ws:\n            await ws.send_str(\"request\")\n            msg = await ws.receive()\n            assert msg.data == \"request/answer\"\n\n        assert ws.closed\n\n\nasync def test_ws_async_with_shortcut(aiohttp_server: AiohttpServer) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        msg = await ws.receive()\n        assert msg.type is WSMsgType.TEXT\n        await ws.send_str(msg.data + \"/answer\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with aiohttp.ClientSession() as client:\n        async with client.ws_connect(server.make_url(\"/\")) as ws:\n            await ws.send_str(\"request\")\n            msg = await ws.receive()\n            assert msg.data == \"request/answer\"\n\n        assert ws.closed\n\n\nasync def test_closed_async_for(aiohttp_client: AiohttpClient) -> None:\n    loop = asyncio.get_event_loop()\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        try:\n            await ws.send_bytes(b\"started\")\n            await ws.receive_bytes()\n        finally:\n            closed.set_result(1)\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n\n    messages = []\n    async for msg in resp:\n        messages.append(msg)\n        assert b\"started\" == msg.data\n        await resp.send_bytes(b\"ask\")\n        await resp.close()\n\n    assert 1 == len(messages)\n    assert messages[0].type == aiohttp.WSMsgType.BINARY\n    assert messages[0].data == b\"started\"\n    assert resp.closed\n\n    await closed\n\n\nasync def test_peer_connection_lost(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive_str()\n        assert msg == \"ask\"\n        await ws.send_str(\"answer\")\n        assert request.transport is not None\n        request.transport.close()\n        await asyncio.sleep(10)\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n    await resp.send_str(\"ask\")\n    assert \"answer\" == await resp.receive_str()\n\n    msg = await resp.receive()\n    assert msg.type == aiohttp.WSMsgType.CLOSED\n    await resp.close()\n\n\nasync def test_peer_connection_lost_iter(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive_str()\n        assert msg == \"ask\"\n        await ws.send_str(\"answer\")\n        assert request.transport is not None\n        request.transport.close()\n        await asyncio.sleep(100)\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n    await resp.send_str(\"ask\")\n    async for msg in resp:\n        assert \"answer\" == msg.data\n\n    await resp.close()\n\n\nasync def test_ws_connect_with_wrong_ssl_type(aiohttp_client: AiohttpClient) -> None:\n    app = web.Application()\n    session = await aiohttp_client(app)\n\n    with pytest.raises(TypeError, match=\"ssl should be SSLContext, .*\"):\n        await session.ws_connect(\"/\", ssl=42)\n\n\nasync def test_websocket_connection_not_closed_properly(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that closing the connection via __del__ does not raise an exception.\"\"\"\n\n    async def handler(request: web.Request) -> NoReturn:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        await ws.close()\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n    assert resp._conn is not None\n    # Simulate the connection not being closed properly\n    # https://github.com/aio-libs/aiohttp/issues/9880\n    resp._conn.release()\n\n    # Clean up so the test does not leak\n    await resp.close()\n\n\nasync def test_websocket_connection_cancellation(\n    aiohttp_client: AiohttpClient, loop: asyncio.AbstractEventLoop\n) -> None:\n    \"\"\"Test canceling the WebSocket connection task does not raise an exception in __del__.\"\"\"\n\n    async def handler(request: web.Request) -> NoReturn:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        await ws.close()\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    sync_future: asyncio.Future[list[aiohttp.ClientWebSocketResponse[bool]]] = (\n        loop.create_future()\n    )\n    client = await aiohttp_client(app)\n\n    async def websocket_task() -> None:\n        resp = await client.ws_connect(\"/\")\n        assert resp is not None  # ensure we hold a reference to the response\n        # The test harness will cleanup the unclosed websocket\n        # for us, so we need to copy the websockets to ensure\n        # we can control the cleanup\n        sync_future.set_result(client._websockets.copy())\n        client._websockets.clear()\n        await asyncio.sleep(0)\n\n    task = loop.create_task(websocket_task())\n    websockets = await sync_future\n    task.cancel()\n    with pytest.raises(asyncio.CancelledError):\n        await task\n\n    websocket = websockets.pop()\n    # Call the `__del__` methods manually since when it gets gc'd it not reproducible\n    del websocket._response\n\n    # Cleanup properly\n    websocket._response = mock.Mock()\n    await websocket.close()\n\n\nasync def test_receive_text_as_bytes_client_side(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test client receiving TEXT messages as raw bytes with decode_text=False.\"\"\"\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive_str()\n        await ws.send_str(msg + \"/answer\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    # Connect with decode_text=False\n    resp = await client.ws_connect(\"/\", decode_text=False)\n    await resp.send_str(\"ask\")\n\n    # Receive TEXT message as bytes\n    msg = await resp.receive()\n    assert msg.type is WSMsgType.TEXT\n    assert isinstance(msg.data, bytes)\n    assert msg.data == b\"ask/answer\"\n\n    await resp.close()\n\n\nasync def test_receive_text_as_bytes_server_side(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test server receiving TEXT messages as raw bytes with decode_text=False.\"\"\"\n\n    async def handler(request: web.Request) -> web.WebSocketResponse[Literal[False]]:\n        ws: web.WebSocketResponse[Literal[False]] = web.WebSocketResponse(\n            decode_text=False\n        )\n        await ws.prepare(request)\n\n        # Receive TEXT message as bytes\n        msg = await ws.receive()\n        assert msg.type is WSMsgType.TEXT\n        assert isinstance(msg.data, bytes)\n        assert msg.data == b\"test message\"\n\n        # Send response\n        await ws.send_bytes(msg.data + b\"/reply\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.ws_connect(\"/\")\n    await resp.send_str(\"test message\")\n\n    msg = await resp.receive()\n    assert msg.type is WSMsgType.BINARY\n    assert msg.data == b\"test message/reply\"\n\n    await resp.close()\n\n\nasync def test_receive_text_as_bytes_json_parsing(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test using orjson or similar parsers with raw bytes from TEXT messages.\"\"\"\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive_str()\n        data = json.loads(msg)\n        await ws.send_str(json.dumps({\"response\": data[\"value\"] * 2}))\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    # Connect with decode_text=False to get raw bytes\n    resp = await client.ws_connect(\"/\", decode_text=False)\n    await resp.send_str(json.dumps({\"value\": 42}))\n\n    # Receive TEXT message as bytes\n    msg = await resp.receive()\n    assert msg.type is WSMsgType.TEXT\n    assert isinstance(msg.data, bytes)\n\n    # Parse JSON using msg.json() method (covers WSMessageTextBytes.json())\n    data = msg.json()\n    assert data == {\"response\": 84}\n\n    await resp.close()\n\n\nasync def test_decode_text_default_true(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that decode_text defaults to True for backward compatibility.\"\"\"\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive_str()\n        await ws.send_str(msg + \"/reply\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    # Default behavior (decode_text=True)\n    resp = await client.ws_connect(\"/\")\n    await resp.send_str(\"test\")\n\n    # Should receive TEXT message as string\n    msg = await resp.receive()\n    assert msg.type is WSMsgType.TEXT\n    assert isinstance(msg.data, str)\n    assert msg.data == \"test/reply\"\n\n    await resp.close()\n\n\nasync def test_receive_str_returns_bytes_with_decode_text_false(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that receive_str() returns bytes when decode_text=False.\"\"\"\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        await ws.send_str(\"hello world\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.ws_connect(\"/\", decode_text=False) as ws:\n        # receive_str() should return bytes when decode_text=False\n        data = await ws.receive_str()\n        assert isinstance(data, bytes)\n        assert data == b\"hello world\"\n\n\nasync def test_receive_str_returns_str_with_decode_text_true(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that receive_str() returns str when decode_text=True (default).\"\"\"\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        await ws.send_str(\"hello world\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.ws_connect(\"/\") as ws:\n        # receive_str() should return str when decode_text=True (default)\n        data = await ws.receive_str()\n        assert isinstance(data, str)\n        assert data == \"hello world\"\n\n\nasync def test_receive_json_with_orjson_style_loads(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test receive_json() with orjson-style loads that accepts bytes.\"\"\"\n\n    def orjson_style_loads(data: bytes) -> dict[str, int]:\n        \"\"\"Mock orjson.loads that accepts bytes.\"\"\"\n        assert isinstance(data, bytes)\n        result: dict[str, int] = json.loads(data)\n        return result\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        await ws.send_str('{\"value\": 42}')\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.ws_connect(\"/\", decode_text=False) as ws:\n        # receive_json() with orjson-style loads should work with bytes\n        data = await ws.receive_json(loads=orjson_style_loads)\n        assert data == {\"value\": 42}\n"
  },
  {
    "path": "tests/test_compression_utils.py",
    "content": "\"\"\"Tests for compression utils.\"\"\"\n\nimport pytest\n\nfrom aiohttp.compression_utils import ZLibBackend, ZLibCompressor, ZLibDecompressor\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_compression_round_trip_in_executor() -> None:\n    \"\"\"Ensure that compression and decompression work correctly in the executor.\"\"\"\n    compressor = ZLibCompressor(\n        strategy=ZLibBackend.Z_DEFAULT_STRATEGY, max_sync_chunk_size=1\n    )\n    assert type(compressor._compressor) is type(ZLibBackend.compressobj())\n    decompressor = ZLibDecompressor(max_sync_chunk_size=1)\n    assert type(decompressor._decompressor) is type(ZLibBackend.decompressobj())\n    data = b\"Hi\" * 100\n    compressed_data = await compressor.compress(data) + compressor.flush()\n    decompressed_data = await decompressor.decompress(compressed_data)\n    assert data == decompressed_data\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_compression_round_trip_in_event_loop() -> None:\n    \"\"\"Ensure that compression and decompression work correctly in the event loop.\"\"\"\n    compressor = ZLibCompressor(\n        strategy=ZLibBackend.Z_DEFAULT_STRATEGY, max_sync_chunk_size=10000\n    )\n    assert type(compressor._compressor) is type(ZLibBackend.compressobj())\n    decompressor = ZLibDecompressor(max_sync_chunk_size=10000)\n    assert type(decompressor._decompressor) is type(ZLibBackend.decompressobj())\n    data = b\"Hi\" * 100\n    compressed_data = await compressor.compress(data) + compressor.flush()\n    decompressed_data = await decompressor.decompress(compressed_data)\n    assert data == decompressed_data\n"
  },
  {
    "path": "tests/test_connector.py",
    "content": "# Tests of http client with custom Connector\nimport asyncio\nimport contextlib\nimport gc\nimport hashlib\nimport platform\nimport socket\nimport ssl\nimport sys\nimport uuid\nimport warnings\nfrom collections import defaultdict, deque\nfrom collections.abc import Awaitable, Callable, Iterator, Sequence\nfrom concurrent import futures\nfrom contextlib import closing, suppress\nfrom typing import Any, Literal, NoReturn\nfrom unittest import mock\n\nimport pytest\nfrom multidict import CIMultiDict\nfrom pytest_mock import MockerFixture\nfrom yarl import URL\n\nimport aiohttp\nfrom aiohttp import (\n    ClientRequest,\n    ClientSession,\n    ClientTimeout,\n    connector as connector_module,\n    hdrs,\n    web,\n)\nfrom aiohttp.abc import AbstractResolver, ResolveResult\nfrom aiohttp.client_proto import ResponseHandler\nfrom aiohttp.client_reqrep import ClientRequestArgs, ConnectionKey\nfrom aiohttp.connector import (\n    _SSL_CONTEXT_UNVERIFIED,\n    _SSL_CONTEXT_VERIFIED,\n    AddrInfoType,\n    Connection,\n    TCPConnector,\n    _ConnectTunnelConnection,\n    _DNSCacheTable,\n)\nfrom aiohttp.pytest_plugin import AiohttpClient, AiohttpServer\nfrom aiohttp.resolver import AsyncResolver\nfrom aiohttp.test_utils import unused_port\nfrom aiohttp.tracing import Trace\n\nif sys.version_info >= (3, 11):\n    from typing import Unpack\n\n    _RequestMaker = Callable[[str, URL, Unpack[ClientRequestArgs]], ClientRequest]\nelse:\n    _RequestMaker = Any\n\n\n@pytest.fixture\ndef key() -> ConnectionKey:\n    # Connection key\n    return ConnectionKey(\"localhost\", 80, False, True, None, None, None)\n\n\n@pytest.fixture\ndef key2() -> ConnectionKey:\n    # Connection key\n    return ConnectionKey(\"localhost\", 80, False, True, None, None, None)\n\n\n@pytest.fixture\ndef other_host_key2() -> ConnectionKey:\n    # Connection key\n    return ConnectionKey(\"otherhost\", 80, False, True, None, None, None)\n\n\n@pytest.fixture\ndef ssl_key() -> ConnectionKey:\n    # Connection key\n    return ConnectionKey(\"localhost\", 80, True, True, None, None, None)\n\n\n@pytest.fixture\ndef unix_server(\n    loop: asyncio.AbstractEventLoop, unix_sockname: str\n) -> Iterator[Callable[[web.Application], Awaitable[None]]]:\n    runners = []\n\n    async def go(app: web.Application) -> None:\n        runner = web.AppRunner(app)\n        runners.append(runner)\n        await runner.setup()\n        site = web.UnixSite(runner, unix_sockname)\n        await site.start()\n\n    yield go\n\n    for runner in runners:\n        loop.run_until_complete(runner.cleanup())\n\n\n@pytest.fixture\ndef named_pipe_server(\n    proactor_loop: asyncio.AbstractEventLoop, pipe_name: str\n) -> Iterator[Callable[[web.Application], Awaitable[None]]]:\n    runners = []\n\n    async def go(app: web.Application) -> None:\n        runner = web.AppRunner(app)\n        runners.append(runner)\n        await runner.setup()\n        site = web.NamedPipeSite(runner, pipe_name)\n        await site.start()\n\n    yield go\n\n    for runner in runners:\n        proactor_loop.run_until_complete(runner.cleanup())\n\n\ndef create_mocked_conn(\n    conn_closing_result: asyncio.AbstractEventLoop | None = None,\n    should_close: bool = True,\n    **kwargs: object,\n) -> mock.Mock:\n    assert \"loop\" not in kwargs\n    try:\n        loop = asyncio.get_running_loop()\n    except RuntimeError:\n        loop = asyncio.get_event_loop()\n\n    f = loop.create_future()\n    proto: mock.Mock = mock.create_autospec(\n        ResponseHandler, instance=True, should_close=should_close, closed=f\n    )\n    f.set_result(conn_closing_result)\n    return proto\n\n\nasync def test_connection_del(loop: asyncio.AbstractEventLoop) -> None:\n    connector = mock.Mock()\n    key = mock.Mock()\n    protocol = mock.Mock()\n    loop.set_debug(False)\n    conn = Connection(connector, key, protocol, loop=loop)\n    exc_handler = mock.Mock()\n    loop.set_exception_handler(exc_handler)\n\n    with pytest.warns(ResourceWarning):\n        del conn\n        gc.collect()\n\n    await asyncio.sleep(0)\n    connector._release.assert_called_with(key, protocol, should_close=True)\n    msg = {\n        \"message\": mock.ANY,\n        \"client_connection\": mock.ANY,\n    }\n    exc_handler.assert_called_with(loop, msg)\n\n\ndef test_connection_del_loop_debug(loop: asyncio.AbstractEventLoop) -> None:\n    connector = mock.Mock()\n    key = mock.Mock()\n    protocol = mock.Mock()\n    loop.set_debug(True)\n    conn = Connection(connector, key, protocol, loop=loop)\n    exc_handler = mock.Mock()\n    loop.set_exception_handler(exc_handler)\n\n    with pytest.warns(ResourceWarning):\n        del conn\n        gc.collect()\n\n    msg = {\n        \"message\": mock.ANY,\n        \"client_connection\": mock.ANY,\n        \"source_traceback\": mock.ANY,\n    }\n    exc_handler.assert_called_with(loop, msg)\n\n\ndef test_connection_del_loop_closed(loop: asyncio.AbstractEventLoop) -> None:\n    connector = mock.Mock()\n    key = mock.Mock()\n    protocol = mock.Mock()\n    loop.set_debug(True)\n    conn = Connection(connector, key, protocol, loop=loop)\n    exc_handler = mock.Mock()\n    loop.set_exception_handler(exc_handler)\n    loop.close()\n\n    with pytest.warns(ResourceWarning):\n        del conn\n        gc.collect()\n\n    assert not connector._release.called\n    assert not exc_handler.called\n\n\nasync def test_del(loop: asyncio.AbstractEventLoop, key: ConnectionKey) -> None:\n    conn = aiohttp.BaseConnector()\n    proto = create_mocked_conn(loop, should_close=False)\n    conn._release(key, proto)\n    conns_impl = conn._conns\n\n    exc_handler = mock.Mock()\n    loop.set_exception_handler(exc_handler)\n\n    with pytest.warns(ResourceWarning):\n        del conn\n        gc.collect()\n\n    assert not conns_impl\n    proto.close.assert_called_with()\n    msg = {\n        \"connector\": mock.ANY,  # conn was deleted\n        \"connections\": mock.ANY,\n        \"message\": \"Unclosed connector\",\n    }\n    exc_handler.assert_called_with(loop, msg)\n\n\n@pytest.mark.xfail\nasync def test_del_with_scheduled_cleanup(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey\n) -> None:\n    loop.set_debug(True)\n    conn = aiohttp.BaseConnector(keepalive_timeout=0.01)\n    transp = create_mocked_conn(loop)\n    conn._conns[key] = deque([(transp, 123)])\n\n    conns_impl = conn._conns\n    exc_handler = mock.Mock()\n    loop.set_exception_handler(exc_handler)\n\n    with pytest.warns(ResourceWarning):\n        # obviously doesn't deletion because loop has a strong\n        # reference to connector's instance method, isn't it?\n        del conn\n        await asyncio.sleep(0.01)\n        gc.collect()\n\n    assert not conns_impl\n    transp.close.assert_called_with()\n    msg = {\"connector\": mock.ANY, \"message\": \"Unclosed connector\"}  # conn was deleted\n    if loop.get_debug():\n        msg[\"source_traceback\"] = mock.ANY\n    exc_handler.assert_called_with(loop, msg)\n\n\n@pytest.mark.skipif(\n    sys.implementation.name != \"cpython\", reason=\"CPython GC is required for the test\"\n)\ndef test_del_with_closed_loop(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey\n) -> None:\n    async def make_conn() -> aiohttp.BaseConnector:\n        return aiohttp.BaseConnector()\n\n    conn = loop.run_until_complete(make_conn())\n    transp = create_mocked_conn(loop)\n    conn._conns[key] = deque([(transp, 123)])\n\n    conns_impl = conn._conns\n    exc_handler = mock.Mock()\n    loop.set_exception_handler(exc_handler)\n    loop.close()\n\n    with pytest.warns(ResourceWarning):\n        del conn\n        gc.collect()\n\n    assert not conns_impl\n    assert not transp.close.called\n    assert exc_handler.called\n\n\nasync def test_del_empty_connector(loop: asyncio.AbstractEventLoop) -> None:\n    conn = aiohttp.BaseConnector()\n\n    exc_handler = mock.Mock()\n    loop.set_exception_handler(exc_handler)\n\n    del conn\n\n    assert not exc_handler.called\n\n\nasync def test_create_conn() -> None:\n    conn = aiohttp.BaseConnector()\n    with pytest.raises(NotImplementedError):\n        await conn._create_connection(object(), [], object())  # type: ignore[arg-type]\n    await conn.close()\n\n\nasync def test_async_context_manager(loop: asyncio.AbstractEventLoop) -> None:\n    conn = aiohttp.BaseConnector()\n\n    async with conn as c:\n        assert conn is c\n\n    assert conn.closed\n\n\nasync def test_close(key: ConnectionKey) -> None:\n    proto = create_mocked_conn()\n\n    conn = aiohttp.BaseConnector()\n    assert not conn.closed\n    conn._conns[key] = deque([(proto, 0)])\n    await conn.close()\n\n    assert not conn._conns\n    assert proto.close.called\n    assert conn.closed\n\n\nasync def test_close_with_proto_closed_none(key: ConnectionKey) -> None:\n    \"\"\"Test close when protocol.closed is None.\"\"\"\n    # Create protocols where closed property returns None\n    proto1 = mock.create_autospec(ResponseHandler, instance=True)\n    proto1.closed = None\n    proto1.close = mock.Mock()\n\n    proto2 = mock.create_autospec(ResponseHandler, instance=True)\n    proto2.closed = None\n    proto2.close = mock.Mock()\n\n    conn = aiohttp.BaseConnector()\n    conn._conns[key] = deque([(proto1, 0)])\n    conn._acquired.add(proto2)\n\n    # Close the connector - this should handle the case where proto.closed is None\n    await conn.close()\n\n    # Verify close was called on both protocols\n    assert proto1.close.called\n    assert proto2.close.called\n    assert conn.closed\n\n\nasync def test_get(loop: asyncio.AbstractEventLoop, key: ConnectionKey) -> None:\n    conn = aiohttp.BaseConnector()\n    try:\n        assert await conn._get(key, []) is None\n\n        proto = create_mocked_conn(loop)\n        conn._conns[key] = deque([(proto, loop.time())])\n        connection = await conn._get(key, [])\n        assert connection is not None\n        assert connection.protocol == proto\n        connection.close()\n    finally:\n        await conn.close()\n\n\nasync def test_get_unconnected_proto(loop: asyncio.AbstractEventLoop) -> None:\n    conn = aiohttp.BaseConnector()\n    key = ConnectionKey(\"localhost\", 80, False, False, None, None, None)\n    try:\n        assert await conn._get(key, []) is None\n\n        proto = create_mocked_conn(loop)\n        conn._conns[key] = deque([(proto, loop.time())])\n        connection = await conn._get(key, [])\n        assert connection is not None\n        assert connection.protocol == proto\n        connection.close()\n\n        assert await conn._get(key, []) is None\n        conn._conns[key] = deque([(proto, loop.time())])\n        proto.is_connected = lambda *args: False\n        assert await conn._get(key, []) is None\n    finally:\n        await conn.close()\n\n\nasync def test_get_unconnected_proto_ssl(loop: asyncio.AbstractEventLoop) -> None:\n    conn = aiohttp.BaseConnector()\n    key = ConnectionKey(\"localhost\", 80, True, False, None, None, None)\n    try:\n        assert await conn._get(key, []) is None\n\n        proto = create_mocked_conn(loop)\n        conn._conns[key] = deque([(proto, loop.time())])\n        connection = await conn._get(key, [])\n        assert connection is not None\n        assert connection.protocol == proto\n        connection.close()\n\n        assert await conn._get(key, []) is None\n        conn._conns[key] = deque([(proto, loop.time())])\n        proto.is_connected = lambda *args: False\n        assert await conn._get(key, []) is None\n    finally:\n        await conn.close()\n\n\nasync def test_get_expired(loop: asyncio.AbstractEventLoop) -> None:\n    conn = aiohttp.BaseConnector()\n    key = ConnectionKey(\"localhost\", 80, False, False, None, None, None)\n    try:\n        assert await conn._get(key, []) is None\n\n        proto = create_mocked_conn(loop)\n        conn._conns[key] = deque([(proto, loop.time() - 1000)])\n        assert await conn._get(key, []) is None\n        assert not conn._conns\n    finally:\n        await conn.close()\n\n\n@pytest.mark.usefixtures(\"enable_cleanup_closed\")\nasync def test_get_expired_ssl(loop: asyncio.AbstractEventLoop) -> None:\n    conn = aiohttp.BaseConnector(enable_cleanup_closed=True)\n    key = ConnectionKey(\"localhost\", 80, True, False, None, None, None)\n    try:\n        assert await conn._get(key, []) is None\n\n        proto = create_mocked_conn(loop)\n        transport = proto.transport\n        conn._conns[key] = deque([(proto, loop.time() - 1000)])\n        assert await conn._get(key, []) is None\n        assert not conn._conns\n        assert conn._cleanup_closed_transports == [transport]\n    finally:\n        await conn.close()\n\n\nasync def test_release_acquired(key: ConnectionKey) -> None:\n    proto = create_mocked_conn()\n    conn = aiohttp.BaseConnector(limit=5, limit_per_host=10)\n    with mock.patch.object(conn, \"_release_waiter\", autospec=True, spec_set=True) as m:\n        conn._acquired.add(proto)\n        conn._acquired_per_host[key].add(proto)\n        conn._release_acquired(key, proto)\n        assert 0 == len(conn._acquired)\n        assert 0 == len(conn._acquired_per_host)\n        assert m.called\n\n        conn._release_acquired(key, proto)\n        assert 0 == len(conn._acquired)\n        assert 0 == len(conn._acquired_per_host)\n\n        await conn.close()\n\n\nasync def test_release_acquired_closed(key: ConnectionKey) -> None:\n    proto = create_mocked_conn()\n    conn = aiohttp.BaseConnector(limit=5)\n    with mock.patch.object(conn, \"_release_waiter\", autospec=True, spec_set=True) as m:\n        conn._acquired.add(proto)\n        conn._acquired_per_host[key].add(proto)\n        conn._closed = True\n        conn._release_acquired(key, proto)\n        assert 1 == len(conn._acquired)\n        assert 1 == len(conn._acquired_per_host[key])\n        assert not m.called\n        await conn.close()\n\n\nasync def test_release(loop: asyncio.AbstractEventLoop, key: ConnectionKey) -> None:\n    conn = aiohttp.BaseConnector()\n    with mock.patch.object(conn, \"_release_waiter\", autospec=True, spec_set=True) as m:\n        proto = create_mocked_conn(loop, should_close=False)\n\n        conn._acquired.add(proto)\n        conn._acquired_per_host[key].add(proto)\n\n        conn._release(key, proto)\n        assert m.called\n        assert conn._cleanup_handle is not None\n        assert conn._conns[key][0][0] == proto\n        assert conn._conns[key][0][1] == pytest.approx(loop.time(), abs=0.1)\n        assert not conn._cleanup_closed_transports\n        await conn.close()\n\n\n@pytest.mark.usefixtures(\"enable_cleanup_closed\")\nasync def test_release_ssl_transport(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop, ssl_key: ConnectionKey\n) -> None:\n    conn = aiohttp.BaseConnector(enable_cleanup_closed=True)\n    with mock.patch.object(conn, \"_release_waiter\", autospec=True, spec_set=True):\n        proto = create_mocked_conn(loop)\n        transport = proto.transport\n        conn._acquired.add(proto)\n        conn._acquired_per_host[ssl_key].add(proto)\n\n        conn._release(ssl_key, proto, should_close=True)\n        assert conn._cleanup_closed_transports == [transport]\n        await conn.close()\n\n\nasync def test_release_already_closed(key: ConnectionKey) -> None:\n    conn = aiohttp.BaseConnector()\n\n    proto = create_mocked_conn()\n    conn._acquired.add(proto)\n    await conn.close()\n\n    with mock.patch.object(\n        conn, \"_release_acquired\", autospec=True, spec_set=True\n    ) as m1:\n        with mock.patch.object(\n            conn, \"_release_waiter\", autospec=True, spec_set=True\n        ) as m2:\n            conn._release(key, proto)\n            assert not m1.called\n            assert not m2.called\n\n\nasync def test_release_waiter_no_limit(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey, key2: ConnectionKey\n) -> None:\n    # limit is 0\n    conn = aiohttp.BaseConnector(limit=0)\n    w = mock.Mock()\n    w.done.return_value = False\n    conn._waiters[key][w] = None\n    conn._release_waiter()\n    assert len(conn._waiters[key]) == 0\n    assert w.done.called\n    await conn.close()\n\n\nasync def test_release_waiter_first_available(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey, key2: ConnectionKey\n) -> None:\n    conn = aiohttp.BaseConnector()\n    w1, w2 = mock.Mock(), mock.Mock()\n    w1.done.return_value = False\n    w2.done.return_value = False\n    conn._waiters[key][w2] = None\n    conn._waiters[key2][w1] = None\n    conn._release_waiter()\n    assert (\n        w1.set_result.called\n        and not w2.set_result.called\n        or not w1.set_result.called\n        and w2.set_result.called\n    )\n    await conn.close()\n\n\nasync def test_release_waiter_release_first(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey, key2: ConnectionKey\n) -> None:\n    conn = aiohttp.BaseConnector(limit=1)\n    w1, w2 = mock.Mock(), mock.Mock()\n    w1.done.return_value = False\n    w2.done.return_value = False\n    conn._waiters[key][w1] = None\n    conn._waiters[key][w2] = None\n    conn._release_waiter()\n    assert w1.set_result.called\n    assert not w2.set_result.called\n    await conn.close()\n\n\nasync def test_release_waiter_skip_done_waiter(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey, key2: ConnectionKey\n) -> None:\n    conn = aiohttp.BaseConnector(limit=1)\n    w1, w2 = mock.Mock(), mock.Mock()\n    w1.done.return_value = True\n    w2.done.return_value = False\n    conn._waiters[key][w1] = None\n    conn._waiters[key][w2] = None\n    conn._release_waiter()\n    assert not w1.set_result.called\n    assert w2.set_result.called\n    await conn.close()\n\n\nasync def test_release_waiter_per_host(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey, key2: ConnectionKey\n) -> None:\n    # no limit\n    conn = aiohttp.BaseConnector(limit=0, limit_per_host=2)\n    w1, w2 = mock.Mock(), mock.Mock()\n    w1.done.return_value = False\n    w2.done.return_value = False\n    conn._waiters[key][w1] = None\n    conn._waiters[key2][w2] = None\n    conn._release_waiter()\n    assert (w1.set_result.called and not w2.set_result.called) or (\n        not w1.set_result.called and w2.set_result.called\n    )\n    await conn.close()\n\n\nasync def test_release_waiter_no_available(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey, key2: ConnectionKey\n) -> None:\n    # limit is 0\n    conn = aiohttp.BaseConnector(limit=0)\n    w = mock.Mock()\n    w.done.return_value = False\n    conn._waiters[key][w] = None\n    with mock.patch.object(\n        conn, \"_available_connections\", autospec=True, spec_set=True, return_value=0\n    ):\n        conn._release_waiter()\n        assert len(conn._waiters) == 1\n        assert not w.done.called\n        await conn.close()\n\n\nasync def test_release_close(key: ConnectionKey) -> None:\n    conn = aiohttp.BaseConnector()\n    proto = create_mocked_conn(should_close=True)\n\n    conn._acquired.add(proto)\n    conn._release(key, proto)\n    assert not conn._conns\n    assert proto.close.called\n\n    await conn.close()\n\n\nasync def test__release_acquired_per_host1(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey\n) -> None:\n    conn = aiohttp.BaseConnector(limit_per_host=10)\n    conn._release_acquired(key, create_mocked_conn(loop))\n    assert len(conn._acquired_per_host) == 0\n\n    await conn.close()\n\n\nasync def test__release_acquired_per_host2(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey\n) -> None:\n    conn = aiohttp.BaseConnector(limit_per_host=10)\n    handler = create_mocked_conn(loop)\n    conn._acquired_per_host[key].add(handler)\n    conn._release_acquired(key, handler)\n    assert len(conn._acquired_per_host) == 0\n\n    await conn.close()\n\n\nasync def test__release_acquired_per_host3(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey\n) -> None:\n    conn = aiohttp.BaseConnector(limit_per_host=10)\n    handler = create_mocked_conn(loop)\n    handler2 = create_mocked_conn(loop)\n    conn._acquired_per_host[key].add(handler)\n    conn._acquired_per_host[key].add(handler2)\n    conn._release_acquired(key, handler)\n    assert len(conn._acquired_per_host) == 1\n    assert conn._acquired_per_host[key] == {handler2}\n\n    await conn.close()\n\n\nasync def test_tcp_connector_certificate_error(\n    loop: asyncio.AbstractEventLoop,\n    start_connection: mock.AsyncMock,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\"GET\", URL(\"https://127.0.0.1:443\"), loop=loop)\n\n    conn = aiohttp.TCPConnector()\n    with mock.patch.object(\n        conn._loop,\n        \"create_connection\",\n        autospec=True,\n        spec_set=True,\n        side_effect=ssl.CertificateError,\n    ):\n        with pytest.raises(aiohttp.ClientConnectorCertificateError) as ctx:\n            await conn.connect(req, [], ClientTimeout())\n\n        assert isinstance(ctx.value, ssl.CertificateError)\n        assert isinstance(ctx.value.certificate_error, ssl.CertificateError)\n        assert isinstance(ctx.value, aiohttp.ClientSSLError)\n\n    await conn.close()\n\n\nasync def test_tcp_connector_server_hostname_default(\n    loop: asyncio.AbstractEventLoop,\n    start_connection: mock.AsyncMock,\n    make_client_request: _RequestMaker,\n) -> None:\n    conn = aiohttp.TCPConnector()\n\n    with mock.patch.object(\n        conn._loop, \"create_connection\", autospec=True, spec_set=True\n    ) as create_connection:\n        create_connection.return_value = mock.Mock(), mock.Mock()\n\n        req = make_client_request(\"GET\", URL(\"https://127.0.0.1:443\"), loop=loop)\n\n        with closing(await conn.connect(req, [], ClientTimeout())):\n            assert create_connection.call_args.kwargs[\"server_hostname\"] == \"127.0.0.1\"\n\n    await conn.close()\n\n\nasync def test_tcp_connector_server_hostname_override(\n    loop: asyncio.AbstractEventLoop,\n    start_connection: mock.AsyncMock,\n    make_client_request: _RequestMaker,\n) -> None:\n    conn = aiohttp.TCPConnector()\n\n    with mock.patch.object(\n        conn._loop, \"create_connection\", autospec=True, spec_set=True\n    ) as create_connection:\n        create_connection.return_value = mock.Mock(), mock.Mock()\n\n        req = make_client_request(\n            \"GET\", URL(\"https://127.0.0.1:443\"), loop=loop, server_hostname=\"localhost\"\n        )\n\n        with closing(await conn.connect(req, [], ClientTimeout())):\n            assert create_connection.call_args.kwargs[\"server_hostname\"] == \"localhost\"\n\n    await conn.close()\n\n\nasync def test_tcp_connector_multiple_hosts_errors(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    conn = aiohttp.TCPConnector()\n\n    ip1 = \"192.168.1.1\"\n    ip2 = \"192.168.1.2\"\n    ip3 = \"192.168.1.3\"\n    ip4 = \"192.168.1.4\"\n    ip5 = \"192.168.1.5\"\n    ips = [ip1, ip2, ip3, ip4, ip5]\n    addrs_tried = []\n    ips_tried = []\n\n    fingerprint = hashlib.sha256(b\"foo\").digest()\n\n    req = make_client_request(\n        \"GET\",\n        URL(\"https://mocked.host\"),\n        ssl=aiohttp.Fingerprint(fingerprint),\n        loop=loop,\n    )\n\n    async def _resolve_host(\n        host: str, port: int, traces: object = None\n    ) -> list[ResolveResult]:\n        return [\n            {\n                \"hostname\": host,\n                \"host\": ip,\n                \"port\": port,\n                \"family\": socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": socket.AI_NUMERICHOST,\n            }\n            for ip in ips\n        ]\n\n    os_error = certificate_error = ssl_error = fingerprint_error = False\n    connected = False\n\n    async def start_connection(\n        addr_infos: Sequence[AddrInfoType], **kwargs: object\n    ) -> socket.socket:\n        first_addr_info = addr_infos[0]\n        first_addr_info_addr = first_addr_info[-1]\n        addrs_tried.append(first_addr_info_addr)\n\n        mock_socket = mock.create_autospec(socket.socket, spec_set=True, instance=True)\n        mock_socket.getpeername.return_value = first_addr_info_addr\n        return mock_socket  # type: ignore[no-any-return]\n\n    async def create_connection(\n        *args: object, sock: socket.socket | None = None, **kwargs: object\n    ) -> tuple[ResponseHandler, ResponseHandler]:\n        nonlocal os_error, certificate_error, ssl_error, fingerprint_error\n        nonlocal connected\n\n        assert isinstance(sock, socket.socket)\n        addr_info = sock.getpeername()\n        ip = addr_info[0]\n\n        ips_tried.append(ip)\n\n        if ip == ip1:\n            os_error = True\n            raise OSError\n\n        if ip == ip2:\n            certificate_error = True\n            raise ssl.CertificateError\n\n        if ip == ip3:\n            ssl_error = True\n            raise ssl.SSLError\n\n        if ip == ip4:\n            # Close the socket since we are not actually connecting\n            # and we don't want to leak it.\n            sock.close()\n\n            fingerprint_error = True\n            tr = create_mocked_conn(loop)\n            pr = create_mocked_conn(loop)\n\n            def get_extra_info(param: str) -> object:\n                if param == \"sslcontext\":\n                    return True\n\n                if param == \"ssl_object\":\n                    s = mock.Mock()\n                    s.getpeercert.return_value = b\"not foo\"\n                    return s\n\n                if param == \"peername\":\n                    return (\"192.168.1.5\", 12345)\n\n                if param == \"socket\":\n                    return sock\n\n                assert False, param\n\n            tr.get_extra_info = get_extra_info\n            return tr, pr\n\n        if ip == ip5:\n            # Close the socket since we are not actually connecting\n            # and we don't want to leak it.\n            sock.close()\n\n            connected = True\n            tr = create_mocked_conn(loop)\n            pr = create_mocked_conn(loop)\n\n            def get_extra_info(param: str) -> object:\n                if param == \"sslcontext\":\n                    return True\n\n                if param == \"ssl_object\":\n                    s = mock.Mock()\n                    s.getpeercert.return_value = b\"foo\"\n                    return s\n\n                assert False\n\n            tr.get_extra_info = get_extra_info\n            return tr, pr\n\n        assert False\n\n    with (\n        mock.patch.object(\n            conn,\n            \"_resolve_host\",\n            autospec=True,\n            spec_set=True,\n            side_effect=_resolve_host,\n        ),\n        mock.patch.object(\n            conn._loop,\n            \"create_connection\",\n            autospec=True,\n            spec_set=True,\n            side_effect=create_connection,\n        ),\n        mock.patch(\n            \"aiohttp.connector.aiohappyeyeballs.start_connection\", start_connection\n        ),\n    ):\n        established_connection = await conn.connect(req, [], ClientTimeout())\n\n    assert ips_tried == ips\n    assert addrs_tried == [(ip, 443) for ip in ips]\n\n    assert os_error\n    assert certificate_error\n    assert ssl_error\n    assert fingerprint_error\n    assert connected\n\n    established_connection.close()\n\n    await conn.close()\n\n\n@pytest.mark.parametrize(\n    (\"happy_eyeballs_delay\"),\n    [0.1, 0.25, None],\n)\nasync def test_tcp_connector_happy_eyeballs(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop,\n    happy_eyeballs_delay: float | None,\n    make_client_request: _RequestMaker,\n) -> None:\n    conn = aiohttp.TCPConnector(happy_eyeballs_delay=happy_eyeballs_delay)\n\n    ip1 = \"dead::beef::\"\n    ip2 = \"192.168.1.1\"\n    ips = [ip1, ip2]\n    addrs_tried = []\n\n    req = make_client_request(\n        \"GET\",\n        URL(\"https://mocked.host\"),\n        loop=loop,\n    )\n\n    async def _resolve_host(\n        host: str, port: int, traces: object = None\n    ) -> list[ResolveResult]:\n        return [\n            {\n                \"hostname\": host,\n                \"host\": ip,\n                \"port\": port,\n                \"family\": socket.AF_INET6 if \":\" in ip else socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": socket.AI_NUMERICHOST,\n            }\n            for ip in ips\n        ]\n\n    os_error = False\n    connected = False\n\n    async def sock_connect(*args: tuple[str, int], **kwargs: object) -> None:\n        addr = args[1]\n        nonlocal os_error\n\n        addrs_tried.append(addr)\n\n        if addr[0] == ip1:\n            os_error = True\n            raise OSError\n\n    async def create_connection(\n        *args: object, sock: socket.socket | None = None, **kwargs: object\n    ) -> tuple[ResponseHandler, ResponseHandler]:\n        assert isinstance(sock, socket.socket)\n        # Close the socket since we are not actually connecting\n        # and we don't want to leak it.\n        sock.close()\n\n        nonlocal connected\n        connected = True\n        tr = create_mocked_conn(loop)\n        pr = create_mocked_conn(loop)\n        return tr, pr\n\n    with mock.patch.object(\n        conn, \"_resolve_host\", autospec=True, spec_set=True, side_effect=_resolve_host\n    ):\n        with mock.patch.object(\n            conn._loop,\n            \"sock_connect\",\n            autospec=True,\n            spec_set=True,\n            side_effect=sock_connect,\n        ):\n            with mock.patch.object(\n                conn._loop,\n                \"create_connection\",\n                autospec=True,\n                spec_set=True,\n                side_effect=create_connection,\n            ):\n                established_connection = await conn.connect(req, [], ClientTimeout())\n\n                assert addrs_tried == [(ip1, 443, 0, 0), (ip2, 443)]\n\n                assert os_error\n                assert connected\n\n                established_connection.close()\n\n    await conn.close()\n\n\nasync def test_tcp_connector_interleave(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    conn = aiohttp.TCPConnector(interleave=2)\n\n    ip1 = \"192.168.1.1\"\n    ip2 = \"192.168.1.2\"\n    ip3 = \"dead::beef::\"\n    ip4 = \"aaaa::beef::\"\n    ip5 = \"192.168.1.5\"\n    ips = [ip1, ip2, ip3, ip4, ip5]\n    success_ips = []\n    interleave_val = None\n\n    req = make_client_request(\n        \"GET\",\n        URL(\"https://mocked.host\"),\n        loop=loop,\n    )\n\n    async def _resolve_host(\n        host: str, port: int, traces: object = None\n    ) -> list[ResolveResult]:\n        return [\n            {\n                \"hostname\": host,\n                \"host\": ip,\n                \"port\": port,\n                \"family\": socket.AF_INET6 if \":\" in ip else socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": socket.AI_NUMERICHOST,\n            }\n            for ip in ips\n        ]\n\n    async def start_connection(\n        addr_infos: Sequence[AddrInfoType],\n        *,\n        interleave: int | None = None,\n        **kwargs: object,\n    ) -> socket.socket:\n        nonlocal interleave_val\n        interleave_val = interleave\n        # Mock the 4th host connecting successfully\n        fourth_addr_info = addr_infos[3]\n        fourth_addr_info_addr = fourth_addr_info[-1]\n        mock_socket = mock.create_autospec(socket.socket, spec_set=True, instance=True)\n        mock_socket.getpeername.return_value = fourth_addr_info_addr\n        return mock_socket  # type: ignore[no-any-return]\n\n    async def create_connection(\n        *args: object, sock: socket.socket | None = None, **kwargs: object\n    ) -> tuple[ResponseHandler, ResponseHandler]:\n        assert isinstance(sock, socket.socket)\n        addr_info = sock.getpeername()\n        ip = addr_info[0]\n\n        success_ips.append(ip)\n\n        # Close the socket since we are not actually connecting\n        # and we don't want to leak it.\n        sock.close()\n        tr = create_mocked_conn(loop)\n        pr = create_mocked_conn(loop)\n        return tr, pr\n\n    with (\n        mock.patch.object(\n            conn,\n            \"_resolve_host\",\n            autospec=True,\n            spec_set=True,\n            side_effect=_resolve_host,\n        ),\n        mock.patch.object(\n            conn._loop,\n            \"create_connection\",\n            autospec=True,\n            spec_set=True,\n            side_effect=create_connection,\n        ),\n        mock.patch(\n            \"aiohttp.connector.aiohappyeyeballs.start_connection\", start_connection\n        ),\n    ):\n        established_connection = await conn.connect(req, [], ClientTimeout())\n\n    assert success_ips == [ip4]\n    assert interleave_val == 2\n    established_connection.close()\n\n    await conn.close()\n\n\nasync def test_tcp_connector_family_is_respected(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    conn = aiohttp.TCPConnector(family=socket.AF_INET)\n\n    ip1 = \"dead::beef::\"\n    ip2 = \"192.168.1.1\"\n    ips = [ip1, ip2]\n    addrs_tried = []\n\n    req = make_client_request(\n        \"GET\",\n        URL(\"https://mocked.host\"),\n        loop=loop,\n    )\n\n    async def _resolve_host(\n        host: str, port: int, traces: object = None\n    ) -> list[ResolveResult]:\n        return [\n            {\n                \"hostname\": host,\n                \"host\": ip,\n                \"port\": port,\n                \"family\": socket.AF_INET6 if \":\" in ip else socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": socket.AI_NUMERICHOST,\n            }\n            for ip in ips\n        ]\n\n    connected = False\n\n    async def sock_connect(*args: tuple[str, int], **kwargs: object) -> None:\n        addr = args[1]\n        addrs_tried.append(addr)\n\n    async def create_connection(\n        *args: object, sock: socket.socket | None = None, **kwargs: object\n    ) -> tuple[ResponseHandler, ResponseHandler]:\n        assert isinstance(sock, socket.socket)\n        # Close the socket since we are not actually connecting\n        # and we don't want to leak it.\n        sock.close()\n\n        nonlocal connected\n        connected = True\n        tr = create_mocked_conn(loop)\n        pr = create_mocked_conn(loop)\n        return tr, pr\n\n    with mock.patch.object(\n        conn, \"_resolve_host\", autospec=True, spec_set=True, side_effect=_resolve_host\n    ):\n        with mock.patch.object(\n            conn._loop,\n            \"sock_connect\",\n            autospec=True,\n            spec_set=True,\n            side_effect=sock_connect,\n        ):\n            with mock.patch.object(\n                conn._loop,\n                \"create_connection\",\n                autospec=True,\n                spec_set=True,\n                side_effect=create_connection,\n            ):\n                established_connection = await conn.connect(req, [], ClientTimeout())\n\n                # We should only try the IPv4 address since we specified\n                # the family to be AF_INET\n                assert addrs_tried == [(ip2, 443)]\n\n                assert connected\n\n                established_connection.close()\n\n\n@pytest.mark.parametrize(\n    (\"request_url\"),\n    [\n        (\"http://mocked.host\"),\n        (\"https://mocked.host\"),\n    ],\n)\nasync def test_tcp_connector_multiple_hosts_one_timeout(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop,\n    request_url: str,\n    make_client_request: _RequestMaker,\n) -> None:\n    conn = aiohttp.TCPConnector()\n\n    ip1 = \"192.168.1.1\"\n    ip2 = \"192.168.1.2\"\n    ips = [ip1, ip2]\n    ips_tried = []\n    ips_success = []\n    timeout_error = False\n    connected = False\n\n    req = make_client_request(\n        \"GET\",\n        URL(request_url),\n        loop=loop,\n    )\n\n    async def _resolve_host(\n        host: str, port: int, traces: object = None\n    ) -> list[ResolveResult]:\n        return [\n            {\n                \"hostname\": host,\n                \"host\": ip,\n                \"port\": port,\n                \"family\": socket.AF_INET6 if \":\" in ip else socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": socket.AI_NUMERICHOST,\n            }\n            for ip in ips\n        ]\n\n    async def start_connection(\n        addr_infos: Sequence[AddrInfoType],\n        *,\n        interleave: int | None = None,\n        **kwargs: object,\n    ) -> socket.socket:\n        nonlocal timeout_error\n\n        addr_info = addr_infos[0]\n        addr_info_addr = addr_info[-1]\n\n        ip = addr_info_addr[0]\n        ips_tried.append(ip)\n\n        if ip == ip1:\n            timeout_error = True\n            raise asyncio.TimeoutError\n\n        if ip == ip2:\n            mock_socket = mock.create_autospec(\n                socket.socket, spec_set=True, instance=True\n            )\n            mock_socket.getpeername.return_value = addr_info_addr\n            return mock_socket  # type: ignore[no-any-return]\n\n        assert False\n\n    async def create_connection(\n        *args: object, sock: socket.socket | None = None, **kwargs: object\n    ) -> tuple[ResponseHandler, ResponseHandler]:\n        nonlocal connected\n\n        assert isinstance(sock, socket.socket)\n        addr_info = sock.getpeername()\n        ip = addr_info[0]\n        ips_success.append(ip)\n        connected = True\n\n        # Close the socket since we are not actually connecting\n        # and we don't want to leak it.\n        sock.close()\n        tr = create_mocked_conn(loop)\n        pr = create_mocked_conn(loop)\n        return tr, pr\n\n    with (\n        mock.patch.object(\n            conn,\n            \"_resolve_host\",\n            autospec=True,\n            spec_set=True,\n            side_effect=_resolve_host,\n        ),\n        mock.patch.object(\n            conn._loop,\n            \"create_connection\",\n            autospec=True,\n            spec_set=True,\n            side_effect=create_connection,\n        ),\n        mock.patch(\n            \"aiohttp.connector.aiohappyeyeballs.start_connection\", start_connection\n        ),\n    ):\n        established_connection = await conn.connect(req, [], ClientTimeout())\n\n    assert ips_tried == ips\n    assert ips_success == [ip2]\n\n    assert timeout_error\n    assert connected\n\n    established_connection.close()\n\n    await conn.close()\n\n\nasync def test_tcp_connector_resolve_host(loop: asyncio.AbstractEventLoop) -> None:\n    conn = aiohttp.TCPConnector(use_dns_cache=True)\n\n    res = await conn._resolve_host(\"localhost\", 8080)\n    assert res\n    for rec in res:\n        if rec[\"family\"] == socket.AF_INET:\n            assert rec[\"host\"] == \"127.0.0.1\"\n            assert rec[\"hostname\"] == \"localhost\"\n            assert rec[\"port\"] == 8080\n        else:\n            assert rec[\"family\"] == socket.AF_INET6\n            assert rec[\"hostname\"] == \"localhost\"\n            assert rec[\"port\"] == 8080\n            if platform.system() == \"Darwin\":\n                assert rec[\"host\"] in (\"::1\", \"fe80::1\", \"fe80::1%lo0\")\n            else:\n                assert rec[\"host\"] == \"::1\"\n\n    await conn.close()\n\n\n@pytest.fixture\ndef dns_response(loop: asyncio.AbstractEventLoop) -> Callable[[], Awaitable[list[str]]]:\n    async def coro() -> list[str]:\n        # simulates a network operation\n        await asyncio.sleep(0)\n        return [\"127.0.0.1\"]\n\n    return coro\n\n\nasync def test_tcp_connector_dns_cache_not_expired(\n    loop: asyncio.AbstractEventLoop, dns_response: Callable[[], Awaitable[list[str]]]\n) -> None:\n    with mock.patch(\"aiohttp.connector.DefaultResolver\") as m_resolver:\n\n        mock_default_resolver = mock.create_autospec(\n            AsyncResolver, instance=True, spec_set=True\n        )\n        mock_default_resolver.resolve.return_value = await dns_response()\n        m_resolver.return_value = mock_default_resolver\n\n        conn = aiohttp.TCPConnector(use_dns_cache=True, ttl_dns_cache=10)\n        await conn._resolve_host(\"localhost\", 8080)\n        await conn._resolve_host(\"localhost\", 8080)\n        m_resolver().resolve.assert_called_once_with(\"localhost\", 8080, family=0)\n\n        await conn.close()\n\n\nasync def test_tcp_connector_dns_cache_forever(\n    loop: asyncio.AbstractEventLoop, dns_response: Callable[[], Awaitable[list[str]]]\n) -> None:\n    with mock.patch(\"aiohttp.connector.DefaultResolver\") as m_resolver:\n        mock_default_resolver = mock.create_autospec(\n            AsyncResolver, instance=True, spec_set=True\n        )\n        mock_default_resolver.resolve.return_value = await dns_response()\n        m_resolver.return_value = mock_default_resolver\n\n        conn = aiohttp.TCPConnector(use_dns_cache=True, ttl_dns_cache=10)\n        await conn._resolve_host(\"localhost\", 8080)\n        await conn._resolve_host(\"localhost\", 8080)\n        mock_default_resolver.resolve.assert_called_once_with(\n            \"localhost\", 8080, family=0\n        )\n\n        await conn.close()\n\n\nasync def test_tcp_connector_use_dns_cache_disabled(\n    loop: asyncio.AbstractEventLoop, dns_response: Callable[[], Awaitable[list[str]]]\n) -> None:\n    with mock.patch(\"aiohttp.connector.DefaultResolver\") as m_resolver:\n\n        mock_default_resolver = mock.create_autospec(\n            AsyncResolver, instance=True, spec_set=True\n        )\n        mock_default_resolver.resolve.side_effect = [\n            await dns_response(),\n            await dns_response(),\n        ]\n        m_resolver.return_value = mock_default_resolver\n\n        conn = aiohttp.TCPConnector(use_dns_cache=False)\n\n        await conn._resolve_host(\"localhost\", 8080)\n        await conn._resolve_host(\"localhost\", 8080)\n        mock_default_resolver.resolve.assert_has_calls(\n            [\n                mock.call(\"localhost\", 8080, family=0),\n                mock.call(\"localhost\", 8080, family=0),\n            ]\n        )\n\n        await conn.close()\n\n\nasync def test_tcp_connector_dns_throttle_requests(\n    loop: asyncio.AbstractEventLoop, dns_response: Callable[[], Awaitable[list[str]]]\n) -> None:\n    with mock.patch(\"aiohttp.connector.DefaultResolver\") as m_resolver:\n        mock_default_resolver = mock.create_autospec(\n            AbstractResolver, instance=True, spec_set=True\n        )\n\n        async def mock_resolve(*_args: object, **_kwargs: object) -> list[str]:\n            return await dns_response()\n\n        mock_default_resolver.resolve.side_effect = mock_resolve\n        m_resolver.return_value = mock_default_resolver\n\n        conn = aiohttp.TCPConnector(use_dns_cache=True, ttl_dns_cache=10)\n\n        t = loop.create_task(conn._resolve_host(\"localhost\", 8080))\n        t2 = loop.create_task(conn._resolve_host(\"localhost\", 8080))\n        await asyncio.sleep(0)\n        await asyncio.sleep(0)\n        mock_default_resolver.resolve.assert_called_once_with(\n            \"localhost\", 8080, family=0\n        )\n        t.cancel()\n        t2.cancel()\n        with pytest.raises(asyncio.CancelledError):\n            await asyncio.gather(t, t2)\n\n        await conn.close()\n\n\nasync def test_tcp_connector_dns_throttle_requests_exception_spread(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    with mock.patch(\"aiohttp.connector.DefaultResolver\") as m_resolver:\n        e = Exception()\n\n        mock_resolver_instance = mock.create_autospec(\n            AbstractResolver, instance=True, spec_set=True\n        )\n        mock_resolver_instance.resolve.side_effect = e\n        m_resolver.return_value = mock_resolver_instance\n\n        conn = aiohttp.TCPConnector(use_dns_cache=True, ttl_dns_cache=10)\n\n        r1 = loop.create_task(conn._resolve_host(\"localhost\", 8080))\n        r2 = loop.create_task(conn._resolve_host(\"localhost\", 8080))\n        await asyncio.sleep(0)\n        await asyncio.sleep(0)\n        await asyncio.sleep(0)\n        await asyncio.sleep(0)\n        assert r1.exception() == e\n        assert r2.exception() == e\n\n        await conn.close()\n\n\nasync def test_tcp_connector_dns_throttle_requests_cancelled_when_close(\n    loop: asyncio.AbstractEventLoop, dns_response: Callable[[], Awaitable[list[str]]]\n) -> None:\n    with mock.patch(\"aiohttp.connector.DefaultResolver\") as m_resolver:\n\n        async def mock_resolve(*_args: object, **_kwargs: object) -> list[str]:\n            return await dns_response()\n\n        mock_default_resolver = mock.create_autospec(\n            AbstractResolver, instance=True, spec_set=True\n        )\n        mock_default_resolver.resolve.side_effect = mock_resolve\n        m_resolver.return_value = mock_default_resolver\n        conn = aiohttp.TCPConnector(use_dns_cache=True, ttl_dns_cache=10)\n        t = loop.create_task(conn._resolve_host(\"localhost\", 8080))\n        f = loop.create_task(conn._resolve_host(\"localhost\", 8080))\n\n        await asyncio.sleep(0)\n        await asyncio.sleep(0)\n        await conn.close()\n\n        t.cancel()\n        with pytest.raises(asyncio.CancelledError):\n            await asyncio.gather(t, f)\n\n        await conn.close()\n\n\n@pytest.fixture\ndef dns_response_error(\n    loop: asyncio.AbstractEventLoop,\n) -> Callable[[], Awaitable[NoReturn]]:\n    async def coro() -> NoReturn:\n        # simulates a network operation\n        await asyncio.sleep(0)\n        raise socket.gaierror(-3, \"Temporary failure in name resolution\")\n\n    return coro\n\n\nasync def test_tcp_connector_cancel_dns_error_captured(\n    loop: asyncio.AbstractEventLoop,\n    dns_response_error: Callable[[], Awaitable[NoReturn]],\n    make_client_request: _RequestMaker,\n) -> None:\n    exception_handler_called = False\n\n    def exception_handler(loop: asyncio.AbstractEventLoop, context: object) -> None:\n        nonlocal exception_handler_called\n        exception_handler_called = True\n\n    loop.set_exception_handler(mock.Mock(side_effect=exception_handler))\n\n    with mock.patch(\"aiohttp.connector.DefaultResolver\") as m_resolver:\n        req = make_client_request(\"GET\", URL(\"http://temporary-failure:80\"), loop=loop)\n        conn = aiohttp.TCPConnector(\n            use_dns_cache=False,\n        )\n        m_resolver().resolve.return_value = dns_response_error()\n        m_resolver().close = mock.AsyncMock()\n        f = loop.create_task(conn._create_direct_connection(req, [], ClientTimeout()))\n\n        await asyncio.sleep(0)\n        f.cancel()\n        with pytest.raises(asyncio.CancelledError):\n            await f\n\n        gc.collect()\n        assert exception_handler_called is False\n\n        await conn.close()\n\n\nasync def test_tcp_connector_dns_tracing(\n    loop: asyncio.AbstractEventLoop, dns_response: Callable[[], Awaitable[list[str]]]\n) -> None:\n    session = mock.Mock()\n    trace_config_ctx = mock.Mock()\n    on_dns_resolvehost_start = mock.AsyncMock()\n    on_dns_resolvehost_end = mock.AsyncMock()\n    on_dns_cache_hit = mock.AsyncMock()\n    on_dns_cache_miss = mock.AsyncMock()\n\n    trace_config = aiohttp.TraceConfig(\n        trace_config_ctx_factory=mock.Mock(return_value=trace_config_ctx)\n    )\n    trace_config.on_dns_resolvehost_start.append(on_dns_resolvehost_start)\n    trace_config.on_dns_resolvehost_end.append(on_dns_resolvehost_end)\n    trace_config.on_dns_cache_hit.append(on_dns_cache_hit)\n    trace_config.on_dns_cache_miss.append(on_dns_cache_miss)\n    trace_config.freeze()\n    traces = [Trace(session, trace_config, trace_config.trace_config_ctx())]\n\n    with mock.patch(\"aiohttp.connector.DefaultResolver\") as m_resolver:\n        conn = aiohttp.TCPConnector(use_dns_cache=True, ttl_dns_cache=10)\n\n        m_resolver().resolve.return_value = dns_response()\n        m_resolver().close = mock.AsyncMock()\n\n        await conn._resolve_host(\"localhost\", 8080, traces=traces)\n        on_dns_resolvehost_start.assert_called_once_with(\n            session,\n            trace_config_ctx,\n            aiohttp.TraceDnsResolveHostStartParams(\"localhost\"),\n        )\n        on_dns_resolvehost_end.assert_called_once_with(\n            session, trace_config_ctx, aiohttp.TraceDnsResolveHostEndParams(\"localhost\")\n        )\n        on_dns_cache_miss.assert_called_once_with(\n            session, trace_config_ctx, aiohttp.TraceDnsCacheMissParams(\"localhost\")\n        )\n        assert not on_dns_cache_hit.called\n\n        await conn._resolve_host(\"localhost\", 8080, traces=traces)\n        on_dns_cache_hit.assert_called_once_with(\n            session, trace_config_ctx, aiohttp.TraceDnsCacheHitParams(\"localhost\")\n        )\n\n        await conn.close()\n\n\nasync def test_tcp_connector_dns_tracing_cache_disabled(\n    loop: asyncio.AbstractEventLoop, dns_response: Callable[[], Awaitable[list[str]]]\n) -> None:\n    session = mock.Mock()\n    trace_config_ctx = mock.Mock()\n    on_dns_resolvehost_start = mock.AsyncMock()\n    on_dns_resolvehost_end = mock.AsyncMock()\n\n    trace_config = aiohttp.TraceConfig(\n        trace_config_ctx_factory=mock.Mock(return_value=trace_config_ctx)\n    )\n    trace_config.on_dns_resolvehost_start.append(on_dns_resolvehost_start)\n    trace_config.on_dns_resolvehost_end.append(on_dns_resolvehost_end)\n    trace_config.freeze()\n    traces = [Trace(session, trace_config, trace_config.trace_config_ctx())]\n\n    with mock.patch(\"aiohttp.connector.DefaultResolver\") as m_resolver:\n        conn = aiohttp.TCPConnector(use_dns_cache=False)\n\n        m_resolver().resolve.side_effect = [dns_response(), dns_response()]\n        m_resolver().close = mock.AsyncMock()\n\n        await conn._resolve_host(\"localhost\", 8080, traces=traces)\n\n        await conn._resolve_host(\"localhost\", 8080, traces=traces)\n\n        on_dns_resolvehost_start.assert_has_calls(\n            [\n                mock.call(\n                    session,\n                    trace_config_ctx,\n                    aiohttp.TraceDnsResolveHostStartParams(\"localhost\"),\n                ),\n                mock.call(\n                    session,\n                    trace_config_ctx,\n                    aiohttp.TraceDnsResolveHostStartParams(\"localhost\"),\n                ),\n            ]\n        )\n        on_dns_resolvehost_end.assert_has_calls(\n            [\n                mock.call(\n                    session,\n                    trace_config_ctx,\n                    aiohttp.TraceDnsResolveHostEndParams(\"localhost\"),\n                ),\n                mock.call(\n                    session,\n                    trace_config_ctx,\n                    aiohttp.TraceDnsResolveHostEndParams(\"localhost\"),\n                ),\n            ]\n        )\n\n        await conn.close()\n\n\nasync def test_tcp_connector_dns_tracing_throttle_requests(\n    loop: asyncio.AbstractEventLoop, dns_response: Callable[[], Awaitable[list[str]]]\n) -> None:\n    session = mock.Mock()\n    trace_config_ctx = mock.Mock()\n    on_dns_cache_hit = mock.AsyncMock()\n    on_dns_cache_miss = mock.AsyncMock()\n\n    trace_config = aiohttp.TraceConfig(\n        trace_config_ctx_factory=mock.Mock(return_value=trace_config_ctx)\n    )\n    trace_config.on_dns_cache_hit.append(on_dns_cache_hit)\n    trace_config.on_dns_cache_miss.append(on_dns_cache_miss)\n    trace_config.freeze()\n    traces = [Trace(session, trace_config, trace_config.trace_config_ctx())]\n\n    with mock.patch(\"aiohttp.connector.DefaultResolver\") as m_resolver:\n        conn = aiohttp.TCPConnector(use_dns_cache=True, ttl_dns_cache=10)\n        m_resolver().resolve.return_value = dns_response()\n        m_resolver().close = mock.AsyncMock()\n        t = loop.create_task(conn._resolve_host(\"localhost\", 8080, traces=traces))\n        t1 = loop.create_task(conn._resolve_host(\"localhost\", 8080, traces=traces))\n        await asyncio.sleep(0)\n        await asyncio.sleep(0)\n        on_dns_cache_hit.assert_called_once_with(\n            session, trace_config_ctx, aiohttp.TraceDnsCacheHitParams(\"localhost\")\n        )\n        on_dns_cache_miss.assert_called_once_with(\n            session, trace_config_ctx, aiohttp.TraceDnsCacheMissParams(\"localhost\")\n        )\n        t.cancel()\n        t1.cancel()\n        with pytest.raises(asyncio.CancelledError):\n            await asyncio.gather(t, t1)\n\n        await conn.close()\n\n\nasync def test_tcp_connector_close_resolver() -> None:\n    m_resolver = mock.create_autospec(AbstractResolver, instance=True, spec_set=True)\n    with mock.patch(\"aiohttp.connector.DefaultResolver\", return_value=m_resolver):\n        conn = aiohttp.TCPConnector(use_dns_cache=True, ttl_dns_cache=10)\n        await conn.close()\n        m_resolver.close.assert_awaited_once()\n\n\nasync def test_dns_error(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    connector = aiohttp.TCPConnector()\n    with mock.patch.object(\n        connector,\n        \"_resolve_host\",\n        autospec=True,\n        spec_set=True,\n        side_effect=OSError(\"dont take it serious\"),\n    ):\n        req = make_client_request(\"GET\", URL(\"http://www.python.org\"), loop=loop)\n\n        with pytest.raises(aiohttp.ClientConnectorError):\n            await connector.connect(req, [], ClientTimeout())\n\n    await connector.close()\n\n\nasync def test_get_pop_empty_conns(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey\n) -> None:\n    # see issue #473\n    conn = aiohttp.BaseConnector()\n    assert await conn._get(key, []) is None\n    assert not conn._conns\n\n    await conn.close()\n\n\nasync def test_release_close_do_not_add_to_pool(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey\n) -> None:\n    # see issue #473\n    conn = aiohttp.BaseConnector()\n\n    proto = create_mocked_conn(loop, should_close=True)\n\n    conn._acquired.add(proto)\n    conn._release(key, proto)\n    assert not conn._conns\n\n    await conn.close()\n\n\nasync def test_release_close_do_not_delete_existing_connections(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey\n) -> None:\n    proto1 = create_mocked_conn(loop)\n\n    conn = aiohttp.BaseConnector()\n    conn._conns[key] = deque([(proto1, 1)])\n\n    proto = create_mocked_conn(loop, should_close=True)\n    conn._acquired.add(proto)\n    conn._release(key, proto)\n    assert conn._conns[key] == deque([(proto1, 1)])\n    assert proto.close.called\n    await conn.close()\n\n\nasync def test_release_not_started(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey\n) -> None:\n    conn = aiohttp.BaseConnector()\n    proto = create_mocked_conn(should_close=False)\n    conn._acquired.add(proto)\n    conn._release(key, proto)\n    # assert conn._conns == {key: [(proto, 10)]}\n    rec = conn._conns[key]\n    assert rec[0][0] == proto\n    assert rec[0][1] == pytest.approx(loop.time(), abs=0.05)\n    assert not proto.close.called\n    await conn.close()\n\n\nasync def test_release_not_opened(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey\n) -> None:\n    conn = aiohttp.BaseConnector()\n\n    proto = create_mocked_conn(loop)\n    conn._acquired.add(proto)\n    conn._release(key, proto)\n    assert proto.close.called\n\n    await conn.close()\n\n\nasync def test_connect(\n    loop: asyncio.AbstractEventLoop,\n    key: ConnectionKey,\n    make_client_request: _RequestMaker,\n) -> None:\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\"GET\", URL(\"http://localhost:80\"), loop=loop)\n\n    conn = aiohttp.BaseConnector()\n    conn._conns[key] = deque([(proto, loop.time())])\n    with mock.patch.object(conn, \"_create_connection\", create_mocked_conn(loop)) as m:\n        m.return_value = loop.create_future()\n        m.return_value.set_result(proto)\n\n        connection = await conn.connect(req, [], ClientTimeout())\n        assert not m.called\n        assert connection._protocol is proto\n        assert connection.transport is proto.transport\n        assert isinstance(connection, Connection)\n        connection.close()\n\n    await conn.close()\n\n\nasync def test_connect_tracing(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    session = mock.Mock()\n    trace_config_ctx = mock.Mock()\n    on_connection_create_start = mock.AsyncMock()\n    on_connection_create_end = mock.AsyncMock()\n\n    trace_config = aiohttp.TraceConfig(\n        trace_config_ctx_factory=mock.Mock(return_value=trace_config_ctx)\n    )\n    trace_config.on_connection_create_start.append(on_connection_create_start)\n    trace_config.on_connection_create_end.append(on_connection_create_end)\n    trace_config.freeze()\n    traces = [Trace(session, trace_config, trace_config.trace_config_ctx())]\n\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\"GET\", URL(\"http://host:80\"), loop=loop)\n\n    conn = aiohttp.BaseConnector()\n    with mock.patch.object(\n        conn, \"_create_connection\", autospec=True, spec_set=True, return_value=proto\n    ):\n        conn2 = await conn.connect(req, traces, ClientTimeout())\n        conn2.release()\n\n        on_connection_create_start.assert_called_with(\n            session, trace_config_ctx, aiohttp.TraceConnectionCreateStartParams()\n        )\n        on_connection_create_end.assert_called_with(\n            session, trace_config_ctx, aiohttp.TraceConnectionCreateEndParams()\n        )\n\n\n@pytest.mark.parametrize(\n    \"signal\",\n    [\n        \"on_connection_create_start\",\n        \"on_connection_create_end\",\n    ],\n)\nasync def test_exception_during_connetion_create_tracing(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop, signal: str, make_client_request: _RequestMaker\n) -> None:\n    session = mock.Mock()\n    trace_config_ctx = mock.Mock()\n    on_signal = mock.AsyncMock(side_effect=asyncio.CancelledError)\n    trace_config = aiohttp.TraceConfig(\n        trace_config_ctx_factory=mock.Mock(return_value=trace_config_ctx)\n    )\n    getattr(trace_config, signal).append(on_signal)\n    trace_config.freeze()\n    traces = [Trace(session, trace_config, trace_config.trace_config_ctx())]\n\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\"GET\", URL(\"http://host:80\"), loop=loop)\n    key = req.connection_key\n    conn = aiohttp.BaseConnector()\n    assert not conn._acquired\n    assert key not in conn._acquired_per_host\n\n    with (\n        pytest.raises(asyncio.CancelledError),\n        mock.patch.object(\n            conn, \"_create_connection\", autospec=True, spec_set=True, return_value=proto\n        ),\n    ):\n        await conn.connect(req, traces, ClientTimeout())\n\n    assert not conn._acquired\n    assert key not in conn._acquired_per_host\n\n\nasync def test_exception_during_connection_queued_tracing(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    session = mock.Mock()\n    trace_config_ctx = mock.Mock()\n    on_signal = mock.AsyncMock(side_effect=asyncio.CancelledError)\n    trace_config = aiohttp.TraceConfig(\n        trace_config_ctx_factory=mock.Mock(return_value=trace_config_ctx)\n    )\n    trace_config.on_connection_queued_start.append(on_signal)\n    trace_config.freeze()\n    traces = [Trace(session, trace_config, trace_config.trace_config_ctx())]\n\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\"GET\", URL(\"http://host:80\"), loop=loop)\n    key = req.connection_key\n    conn = aiohttp.BaseConnector(limit=1)\n    assert not conn._acquired\n    assert key not in conn._acquired_per_host\n\n    with (\n        pytest.raises(asyncio.CancelledError),\n        mock.patch.object(\n            conn, \"_create_connection\", autospec=True, spec_set=True, return_value=proto\n        ),\n    ):\n        resp1 = await conn.connect(req, traces, ClientTimeout())\n        assert resp1\n        # 2nd connect request will be queued\n        await conn.connect(req, traces, ClientTimeout())\n\n    resp1.close()\n    assert not conn._waiters\n    assert not conn._acquired\n    assert key not in conn._acquired_per_host\n\n    await conn.close()\n\n\nasync def test_exception_during_connection_reuse_tracing(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    session = mock.Mock()\n    trace_config_ctx = mock.Mock()\n    on_signal = mock.AsyncMock(side_effect=asyncio.CancelledError)\n    trace_config = aiohttp.TraceConfig(\n        trace_config_ctx_factory=mock.Mock(return_value=trace_config_ctx)\n    )\n    trace_config.on_connection_reuseconn.append(on_signal)\n    trace_config.freeze()\n    traces = [Trace(session, trace_config, trace_config.trace_config_ctx())]\n\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\"GET\", URL(\"http://host:80\"), loop=loop)\n    key = req.connection_key\n    conn = aiohttp.BaseConnector()\n    assert not conn._acquired\n    assert key not in conn._acquired_per_host\n\n    with (\n        pytest.raises(asyncio.CancelledError),\n        mock.patch.object(\n            conn, \"_create_connection\", autospec=True, spec_set=True, return_value=proto\n        ),\n    ):\n        resp = await conn.connect(req, traces, ClientTimeout())\n        with mock.patch.object(resp.protocol, \"should_close\", False):\n            resp.release()\n        assert not conn._acquired\n        assert key not in conn._acquired_per_host\n        assert key in conn._conns\n\n        await conn.connect(req, traces, ClientTimeout())\n\n    assert not conn._acquired\n    assert key not in conn._acquired_per_host\n\n\nasync def test_cancellation_during_waiting_for_free_connection(\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    session = mock.Mock()\n    trace_config_ctx = mock.Mock()\n    waiter_wait_stated_future = loop.create_future()\n\n    async def on_connection_queued_start(*args: object, **kwargs: object) -> None:\n        waiter_wait_stated_future.set_result(None)\n\n    trace_config = aiohttp.TraceConfig(\n        trace_config_ctx_factory=mock.Mock(return_value=trace_config_ctx)\n    )\n    trace_config.on_connection_queued_start.append(on_connection_queued_start)\n    trace_config.freeze()\n    traces = [Trace(session, trace_config, trace_config.trace_config_ctx())]\n\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\"GET\", URL(\"http://host:80\"), loop=loop)\n    key = req.connection_key\n    conn = aiohttp.BaseConnector(limit=1)\n    assert not conn._acquired\n    assert key not in conn._acquired_per_host\n\n    with mock.patch.object(\n        conn, \"_create_connection\", autospec=True, spec_set=True, return_value=proto\n    ):\n        resp1 = await conn.connect(req, traces, ClientTimeout())\n        assert resp1\n        # 2nd connect request will be queued\n        task = asyncio.create_task(conn.connect(req, traces, ClientTimeout()))\n        await waiter_wait_stated_future\n        list(conn._waiters[key])[0].cancel()\n        with pytest.raises(asyncio.CancelledError):\n            await task\n\n    resp1.close()\n    assert not conn._waiters\n    assert not conn._acquired\n    assert key not in conn._acquired_per_host\n\n\nasync def test_close_during_connect(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    fut = loop.create_future()\n    req = make_client_request(\"GET\", URL(\"http://host:80\"), loop=loop)\n\n    conn = aiohttp.BaseConnector()\n    with mock.patch.object(conn, \"_create_connection\", lambda *args: fut):\n        task = loop.create_task(conn.connect(req, [], ClientTimeout()))\n        await asyncio.sleep(0)\n        await conn.close()\n\n        fut.set_result(proto)\n        with pytest.raises(aiohttp.ClientConnectionError):\n            await task\n\n        assert proto.close.called\n\n\n@pytest.mark.usefixtures(\"enable_cleanup_closed\")\nasync def test_ctor_cleanup() -> None:\n    loop = mock.Mock()\n    loop.time.return_value = 1.5\n    conn = aiohttp.BaseConnector(keepalive_timeout=10, enable_cleanup_closed=True)\n    assert conn._cleanup_handle is None\n    assert conn._cleanup_closed_handle is not None\n\n    await conn.close()\n\n\nasync def test_cleanup(key: ConnectionKey) -> None:\n    # The test sets the clock to 300s. It starts with 2 connections in the\n    # pool. The first connection has use time of 10s. When cleanup reaches it,\n    # it computes the deadline = 300 - 15.0 = 285.0 (15s being the default\n    # keep-alive timeout value), then checks that it's overdue because\n    # 10 - 285.0 < 0, and releases it since it's in connected state. The second\n    # connection, though, is in disconnected state so it doesn't bother to\n    # check if it's past due and closes the underlying transport.\n\n    m1 = mock.Mock()\n    m2 = mock.Mock()\n    m1.is_connected.return_value = True\n    m2.is_connected.return_value = False\n    testset: defaultdict[ConnectionKey, deque[tuple[ResponseHandler, float]]] = (\n        defaultdict(deque)\n    )\n    testset[key] = deque([(m1, 10), (m2, 300)])\n\n    loop = mock.Mock()\n    loop.time.return_value = 300\n    async with aiohttp.BaseConnector() as conn:\n        conn._loop = loop\n        conn._conns = testset\n        existing_handle = conn._cleanup_handle = mock.Mock()\n\n        with mock.patch(\"aiohttp.connector.monotonic\", return_value=300):\n            conn._cleanup()\n        assert existing_handle.cancel.called\n        assert conn._conns == {}\n        assert conn._cleanup_handle is None\n\n\n@pytest.mark.usefixtures(\"enable_cleanup_closed\")\nasync def test_cleanup_close_ssl_transport(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop, ssl_key: ConnectionKey\n) -> None:\n    proto = create_mocked_conn(loop)\n    transport = proto.transport\n    testset: defaultdict[ConnectionKey, deque[tuple[ResponseHandler, float]]] = (\n        defaultdict(deque)\n    )\n    testset[ssl_key] = deque([(proto, 10)])\n\n    loop = mock.Mock()\n    new_time = asyncio.get_event_loop().time() + 300\n    loop.time.return_value = new_time\n    conn = aiohttp.BaseConnector(enable_cleanup_closed=True)\n    conn._loop = loop\n    conn._conns = testset\n    existing_handle = conn._cleanup_handle = mock.Mock()\n\n    with mock.patch(\"aiohttp.connector.monotonic\", return_value=new_time):\n        conn._cleanup()\n    assert existing_handle.cancel.called\n    assert conn._conns == {}\n    assert conn._cleanup_closed_transports == [transport]\n\n    await conn.close()\n    await asyncio.sleep(0)  # Give cleanup a chance to close transports\n\n\nasync def test_cleanup2(loop: asyncio.AbstractEventLoop, key: ConnectionKey) -> None:\n    m = create_mocked_conn()\n    m.is_connected.return_value = True\n    testset: defaultdict[ConnectionKey, deque[tuple[ResponseHandler, float]]] = (\n        defaultdict(deque)\n    )\n    testset[key] = deque([(m, 300)])\n\n    conn = aiohttp.BaseConnector(keepalive_timeout=10)\n    conn._loop = mock.Mock()\n    conn._loop.time.return_value = 300\n    with mock.patch(\"aiohttp.connector.monotonic\", return_value=300):\n        conn._conns = testset\n        conn._cleanup()\n    assert conn._conns == testset\n\n    assert conn._cleanup_handle is not None\n    conn._loop.call_at.assert_called_with(310, mock.ANY, mock.ANY)\n    await conn.close()\n\n\nasync def test_cleanup3(loop: asyncio.AbstractEventLoop, key: ConnectionKey) -> None:\n    m = create_mocked_conn(loop)\n    m.is_connected.return_value = True\n    testset: defaultdict[ConnectionKey, deque[tuple[ResponseHandler, float]]] = (\n        defaultdict(deque)\n    )\n    testset[key] = deque([(m, 290.1), (create_mocked_conn(loop), 305.1)])\n\n    conn = aiohttp.BaseConnector(keepalive_timeout=10)\n    conn._loop = mock.Mock()\n    conn._loop.time.return_value = 308.5\n    conn._conns = testset\n\n    with mock.patch(\"aiohttp.connector.monotonic\", return_value=308.5):\n        conn._cleanup()\n\n    assert conn._conns == {key: deque([testset[key][1]])}\n\n    assert conn._cleanup_handle is not None\n    conn._loop.call_at.assert_called_with(319, mock.ANY, mock.ANY)\n    await conn.close()\n\n\n@pytest.mark.usefixtures(\"enable_cleanup_closed\")\nasync def test_cleanup_closed(\n    loop: asyncio.AbstractEventLoop, mocker: MockerFixture\n) -> None:\n    m = mocker.spy(loop, \"call_at\")\n    conn = aiohttp.BaseConnector(enable_cleanup_closed=True)\n\n    tr = mock.Mock()\n    conn._cleanup_closed_handle = cleanup_closed_handle = mock.Mock()\n    conn._cleanup_closed_transports = [tr]\n    conn._cleanup_closed()\n    assert tr.abort.called\n    assert not conn._cleanup_closed_transports\n    assert m.called\n    assert cleanup_closed_handle.cancel.called\n\n    await conn.close()\n\n\nasync def test_cleanup_closed_is_noop_on_fixed_cpython() -> None:\n    \"\"\"Ensure that enable_cleanup_closed is a noop on fixed Python versions.\"\"\"\n    with (\n        mock.patch(\"aiohttp.connector.NEEDS_CLEANUP_CLOSED\", False),\n        pytest.warns(DeprecationWarning, match=\"cleanup_closed ignored\"),\n    ):\n        conn = aiohttp.BaseConnector(enable_cleanup_closed=True)\n        assert conn._cleanup_closed_disabled is True\n\n\nasync def test_cleanup_closed_disabled(\n    loop: asyncio.AbstractEventLoop, mocker: MockerFixture\n) -> None:\n    conn = aiohttp.BaseConnector(enable_cleanup_closed=False)\n\n    tr = mock.Mock()\n    conn._cleanup_closed_transports = [tr]\n    conn._cleanup_closed()\n    assert tr.abort.called\n    assert not conn._cleanup_closed_transports\n\n    await conn.close()\n\n\nasync def test_tcp_connector_ctor(loop: asyncio.AbstractEventLoop) -> None:\n    conn = aiohttp.TCPConnector()\n    assert conn._ssl is True\n\n    assert conn.use_dns_cache\n    assert conn.family == 0\n\n    await conn.close()\n\n\n@pytest.mark.skipif(\n    sys.version_info < (3, 11),\n    reason=\"Use test_tcp_connector_ssl_shutdown_timeout_pre_311 for Python < 3.11\",\n)\nasync def test_tcp_connector_ssl_shutdown_timeout(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    # Test default value (no warning expected)\n    conn = aiohttp.TCPConnector()\n    assert conn._ssl_shutdown_timeout == 0\n    await conn.close()\n\n    # Test custom value - expect deprecation warning\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=1.0)\n    assert conn._ssl_shutdown_timeout == 1.0\n    await conn.close()\n\n    # Test None value - expect deprecation warning\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=None)\n    assert conn._ssl_shutdown_timeout is None\n    await conn.close()\n\n\n@pytest.mark.skipif(\n    sys.version_info >= (3, 11),\n    reason=\"This test is for Python < 3.11 runtime warning behavior\",\n)\nasync def test_tcp_connector_ssl_shutdown_timeout_pre_311(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that both deprecation and runtime warnings are issued on Python < 3.11.\"\"\"\n    # Test custom value - expect both deprecation and runtime warnings\n    with warnings.catch_warnings(record=True) as w:\n        warnings.simplefilter(\"always\")\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=1.0)\n        # Should have both deprecation and runtime warnings\n        assert len(w) == 2\n        assert any(issubclass(warn.category, DeprecationWarning) for warn in w)\n        assert any(issubclass(warn.category, RuntimeWarning) for warn in w)\n    assert conn._ssl_shutdown_timeout == 1.0\n    await conn.close()\n\n\n@pytest.mark.skipif(\n    sys.version_info < (3, 11), reason=\"ssl_shutdown_timeout requires Python 3.11+\"\n)\nasync def test_tcp_connector_ssl_shutdown_timeout_passed_to_create_connection(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop,\n    start_connection: mock.AsyncMock,\n    make_client_request: _RequestMaker,\n) -> None:\n    # Test that ssl_shutdown_timeout is passed to create_connection for SSL connections\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=2.5)\n\n    with mock.patch.object(\n        conn._loop, \"create_connection\", autospec=True, spec_set=True\n    ) as create_connection:\n        create_connection.return_value = mock.Mock(), mock.Mock()\n\n        req = make_client_request(\"GET\", URL(\"https://example.com\"), loop=loop)\n\n        with closing(await conn.connect(req, [], ClientTimeout())):\n            assert create_connection.call_args.kwargs[\"ssl_shutdown_timeout\"] == 2.5\n\n    await conn.close()\n\n    # Test with None value\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=None)\n\n    with mock.patch.object(\n        conn._loop, \"create_connection\", autospec=True, spec_set=True\n    ) as create_connection:\n        create_connection.return_value = mock.Mock(), mock.Mock()\n\n        req = make_client_request(\"GET\", URL(\"https://example.com\"), loop=loop)\n\n        with closing(await conn.connect(req, [], ClientTimeout())):\n            # When ssl_shutdown_timeout is None, it should not be in kwargs\n            assert \"ssl_shutdown_timeout\" not in create_connection.call_args.kwargs\n\n    await conn.close()\n\n    # Test that ssl_shutdown_timeout is NOT passed for non-SSL connections\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=2.5)\n\n    with mock.patch.object(\n        conn._loop, \"create_connection\", autospec=True, spec_set=True\n    ) as create_connection:\n        create_connection.return_value = mock.Mock(), mock.Mock()\n\n        req = make_client_request(\"GET\", URL(\"http://example.com\"), loop=loop)\n\n        with closing(await conn.connect(req, [], ClientTimeout())):\n            # For non-SSL connections, ssl_shutdown_timeout should not be passed\n            assert \"ssl_shutdown_timeout\" not in create_connection.call_args.kwargs\n\n    await conn.close()\n\n\n@pytest.mark.skipif(sys.version_info >= (3, 11), reason=\"Test for Python < 3.11\")\nasync def test_tcp_connector_ssl_shutdown_timeout_not_passed_pre_311(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop,\n    start_connection: mock.AsyncMock,\n    make_client_request: _RequestMaker,\n) -> None:\n    # Test that ssl_shutdown_timeout is NOT passed to create_connection on Python < 3.11\n    with warnings.catch_warnings(record=True) as w:\n        warnings.simplefilter(\"always\")\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=2.5)\n        # Should have both deprecation and runtime warnings\n        assert len(w) == 2\n        assert any(issubclass(warn.category, DeprecationWarning) for warn in w)\n        assert any(issubclass(warn.category, RuntimeWarning) for warn in w)\n\n        with mock.patch.object(\n            conn._loop, \"create_connection\", autospec=True, spec_set=True\n        ) as create_connection:\n            create_connection.return_value = mock.Mock(), mock.Mock()\n\n            # Test with HTTPS\n            req = make_client_request(\"GET\", URL(\"https://example.com\"), loop=loop)\n            with closing(await conn.connect(req, [], ClientTimeout())):\n                assert \"ssl_shutdown_timeout\" not in create_connection.call_args.kwargs\n\n            # Test with HTTP\n            req = make_client_request(\"GET\", URL(\"http://example.com\"), loop=loop)\n            with closing(await conn.connect(req, [], ClientTimeout())):\n                assert \"ssl_shutdown_timeout\" not in create_connection.call_args.kwargs\n\n        await conn.close()\n\n\nasync def test_tcp_connector_close_abort_ssl_when_shutdown_timeout_zero(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that close() uses abort() for SSL connections when ssl_shutdown_timeout=0.\"\"\"\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=0)\n\n    # Create a mock SSL protocol\n    proto = mock.create_autospec(ResponseHandler, instance=True)\n    proto.closed = None\n\n    # Create mock SSL transport\n    transport = mock.Mock()\n    transport.get_extra_info.return_value = mock.Mock()  # Returns SSL context\n    transport.is_closing.return_value = False\n    proto.transport = transport\n\n    # Add the protocol to acquired connections\n    conn._acquired.add(proto)\n\n    # Close the connector\n    await conn.close()\n\n    # Verify abort was called instead of close for SSL connection\n    proto.abort.assert_called_once()\n    proto.close.assert_not_called()\n\n\nasync def test_tcp_connector_close_doesnt_abort_non_ssl_when_shutdown_timeout_zero(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that close() still uses close() for non-SSL connections even when ssl_shutdown_timeout=0.\"\"\"\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=0)\n\n    # Create a mock non-SSL protocol\n    proto = mock.create_autospec(ResponseHandler, instance=True)\n    proto.closed = None\n\n    # Create mock non-SSL transport\n    transport = mock.Mock()\n    transport.get_extra_info.return_value = None  # No SSL context\n    transport.is_closing.return_value = False\n    proto.transport = transport\n\n    # Add the protocol to acquired connections\n    conn._acquired.add(proto)\n\n    # Close the connector\n    await conn.close()\n\n    # Verify close was called for non-SSL connection\n    proto.close.assert_called_once()\n    proto.abort.assert_not_called()\n\n\nasync def test_tcp_connector_ssl_shutdown_timeout_warning_pre_311(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that a warning is issued for non-zero ssl_shutdown_timeout on Python < 3.11.\"\"\"\n    with (\n        mock.patch.object(sys, \"version_info\", (3, 10, 0)),\n        warnings.catch_warnings(record=True) as w,\n    ):\n        warnings.simplefilter(\"always\")\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=5.0)\n\n        # We should get two warnings: deprecation and runtime warning\n        assert len(w) == 2\n\n        # Find each warning type\n        deprecation_warning = next(\n            (warn for warn in w if issubclass(warn.category, DeprecationWarning)), None\n        )\n        runtime_warning = next(\n            (warn for warn in w if issubclass(warn.category, RuntimeWarning)), None\n        )\n\n        assert deprecation_warning is not None\n        assert \"ssl_shutdown_timeout parameter is deprecated\" in str(\n            deprecation_warning.message\n        )\n\n        assert runtime_warning is not None\n        assert \"ssl_shutdown_timeout=5.0 is ignored on Python < 3.11\" in str(\n            runtime_warning.message\n        )\n        assert \"only ssl_shutdown_timeout=0 is supported\" in str(\n            runtime_warning.message\n        )\n\n        # Verify the value is still stored\n        assert conn._ssl_shutdown_timeout == 5.0\n\n        await conn.close()\n\n\nasync def test_tcp_connector_ssl_shutdown_timeout_zero_no_warning_pre_311(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that no warning is issued for ssl_shutdown_timeout=0 on Python < 3.11.\"\"\"\n    with (\n        mock.patch.object(sys, \"version_info\", (3, 10, 0)),\n        warnings.catch_warnings(record=True) as w,\n    ):\n        warnings.simplefilter(\"always\")\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=0)\n\n        # We should get one warning: deprecation\n        assert len(w) == 1\n        assert issubclass(w[0].category, DeprecationWarning)\n        assert \"ssl_shutdown_timeout parameter is deprecated\" in str(w[0].message)\n        assert conn._ssl_shutdown_timeout == 0\n\n        await conn.close()\n\n\nasync def test_tcp_connector_ssl_shutdown_timeout_sentinel_no_warning_pre_311(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that no warning is issued when sentinel is used on Python < 3.11.\"\"\"\n    with (\n        mock.patch.object(sys, \"version_info\", (3, 10, 0)),\n        warnings.catch_warnings(record=True) as w,\n    ):\n        warnings.simplefilter(\"always\")\n        conn = aiohttp.TCPConnector()  # Uses sentinel by default\n\n        assert len(w) == 0\n        assert conn._ssl_shutdown_timeout == 0  # Default value\n\n        await conn.close()\n\n\nasync def test_tcp_connector_ssl_shutdown_timeout_zero_not_passed(\n    loop: asyncio.AbstractEventLoop,\n    start_connection: mock.AsyncMock,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that ssl_shutdown_timeout=0 is NOT passed to create_connection.\"\"\"\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=0)\n\n    with mock.patch.object(\n        conn._loop, \"create_connection\", autospec=True, spec_set=True\n    ) as create_connection:\n        create_connection.return_value = mock.Mock(), mock.Mock()\n\n        # Test with HTTPS\n        req = make_client_request(\"GET\", URL(\"https://example.com\"), loop=loop)\n        with closing(await conn.connect(req, [], ClientTimeout())):\n            # Verify ssl_shutdown_timeout was NOT passed\n            assert \"ssl_shutdown_timeout\" not in create_connection.call_args.kwargs\n\n        # Test with HTTP (should not have ssl_shutdown_timeout anyway)\n        req = make_client_request(\"GET\", URL(\"http://example.com\"), loop=loop)\n        with closing(await conn.connect(req, [], ClientTimeout())):\n            assert \"ssl_shutdown_timeout\" not in create_connection.call_args.kwargs\n\n    await conn.close()\n\n\n@pytest.mark.skipif(\n    sys.version_info < (3, 11), reason=\"ssl_shutdown_timeout requires Python 3.11+\"\n)\nasync def test_tcp_connector_ssl_shutdown_timeout_nonzero_passed(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop,\n    start_connection: mock.AsyncMock,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Test that non-zero ssl_shutdown_timeout IS passed to create_connection on Python 3.11+.\"\"\"\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=5.0)\n\n    with mock.patch.object(\n        conn._loop, \"create_connection\", autospec=True, spec_set=True\n    ) as create_connection:\n        create_connection.return_value = mock.Mock(), mock.Mock()\n\n        # Test with HTTPS\n        req = make_client_request(\"GET\", URL(\"https://example.com\"), loop=loop)\n        with closing(await conn.connect(req, [], ClientTimeout())):\n            # Verify ssl_shutdown_timeout WAS passed\n            assert create_connection.call_args.kwargs[\"ssl_shutdown_timeout\"] == 5.0\n\n        # Test with HTTP (should not have ssl_shutdown_timeout)\n        req = make_client_request(\"GET\", URL(\"http://example.com\"), loop=loop)\n        with closing(await conn.connect(req, [], ClientTimeout())):\n            assert \"ssl_shutdown_timeout\" not in create_connection.call_args.kwargs\n\n    await conn.close()\n\n\nasync def test_tcp_connector_close_abort_ssl_connections_in_conns(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that SSL connections in _conns are aborted when ssl_shutdown_timeout=0.\"\"\"\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=0)\n\n    # Create mock SSL protocol\n    proto = mock.create_autospec(ResponseHandler, instance=True)\n    proto.closed = None\n\n    # Create mock SSL transport\n    transport = mock.Mock()\n    transport.get_extra_info.return_value = mock.Mock()  # Returns SSL context\n    proto.transport = transport\n\n    # Add the protocol to _conns\n    key = ConnectionKey(\"host\", 443, True, True, None, None, None)\n    conn._conns[key] = deque([(proto, loop.time())])\n\n    # Close the connector\n    await conn.close()\n\n    # Verify abort was called for SSL connection\n    proto.abort.assert_called_once()\n    proto.close.assert_not_called()\n\n\nasync def test_tcp_connector_allowed_protocols(loop: asyncio.AbstractEventLoop) -> None:\n    conn = aiohttp.TCPConnector()\n    assert conn.allowed_protocol_schema_set == {\"\", \"tcp\", \"http\", \"https\", \"ws\", \"wss\"}\n\n\nasync def test_start_tls_exception_with_ssl_shutdown_timeout_zero(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test _start_tls_connection exception handling with ssl_shutdown_timeout=0.\"\"\"\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=0)\n\n    underlying_transport = mock.Mock()\n    req = mock.Mock()\n    req.server_hostname = None\n    req.host = \"example.com\"\n    req.is_ssl = mock.Mock(return_value=True)\n\n    # Patch _get_ssl_context to return a valid context and make start_tls fail\n    with (\n        mock.patch.object(\n            conn, \"_get_ssl_context\", return_value=ssl.create_default_context()\n        ),\n        mock.patch.object(conn._loop, \"start_tls\", side_effect=OSError(\"TLS failed\")),\n    ):\n        with pytest.raises(OSError):\n            await conn._start_tls_connection(underlying_transport, req, ClientTimeout())\n\n    # Should abort, not close\n    underlying_transport.abort.assert_called_once()\n    underlying_transport.close.assert_not_called()\n\n\n@pytest.mark.skipif(\n    sys.version_info < (3, 11),\n    reason=\"Use test_start_tls_exception_with_ssl_shutdown_timeout_nonzero_pre_311 for Python < 3.11\",\n)\nasync def test_start_tls_exception_with_ssl_shutdown_timeout_nonzero(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test _start_tls_connection exception handling with ssl_shutdown_timeout>0.\"\"\"\n    with pytest.warns(\n        DeprecationWarning, match=\"ssl_shutdown_timeout parameter is deprecated\"\n    ):\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=1.0)\n\n    underlying_transport = mock.Mock()\n    req = mock.Mock()\n    req.server_hostname = None\n    req.host = \"example.com\"\n    req.is_ssl = mock.Mock(return_value=True)\n\n    # Patch _get_ssl_context to return a valid context and make start_tls fail\n    with (\n        mock.patch.object(\n            conn, \"_get_ssl_context\", return_value=ssl.create_default_context()\n        ),\n        mock.patch.object(conn._loop, \"start_tls\", side_effect=OSError(\"TLS failed\")),\n    ):\n        with pytest.raises(OSError):\n            await conn._start_tls_connection(underlying_transport, req, ClientTimeout())\n\n    # Should close, not abort\n    underlying_transport.close.assert_called_once()\n    underlying_transport.abort.assert_not_called()\n\n\n@pytest.mark.skipif(\n    sys.version_info >= (3, 11),\n    reason=\"This test is for Python < 3.11 runtime warning behavior\",\n)\nasync def test_start_tls_exception_with_ssl_shutdown_timeout_nonzero_pre_311(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test _start_tls_connection exception handling with ssl_shutdown_timeout>0 on Python < 3.11.\"\"\"\n    with warnings.catch_warnings(record=True) as w:\n        warnings.simplefilter(\"always\")\n        conn = aiohttp.TCPConnector(ssl_shutdown_timeout=1.0)\n        # Should have both deprecation and runtime warnings\n        assert len(w) == 2\n        assert any(issubclass(warn.category, DeprecationWarning) for warn in w)\n        assert any(issubclass(warn.category, RuntimeWarning) for warn in w)\n\n    underlying_transport = mock.Mock()\n    req = mock.Mock()\n    req.server_hostname = None\n    req.host = \"example.com\"\n    req.is_ssl = mock.Mock(return_value=True)\n\n    # Patch _get_ssl_context to return a valid context and make start_tls fail\n    with (\n        mock.patch.object(\n            conn, \"_get_ssl_context\", return_value=ssl.create_default_context()\n        ),\n        mock.patch.object(conn._loop, \"start_tls\", side_effect=OSError(\"TLS failed\")),\n    ):\n        with pytest.raises(OSError):\n            await conn._start_tls_connection(underlying_transport, req, ClientTimeout())\n\n    # Should close, not abort\n    underlying_transport.close.assert_called_once()\n    underlying_transport.abort.assert_not_called()\n\n\ndef test_client_timeout_total_zero_raises() -> None:\n    \"\"\"Test that ClientTimeout(total=0) raises ValueError.\n\n    Related to https://github.com/aio-libs/aiohttp/issues/11859\n    Using total=0 to disable timeouts is no longer supported in v4,\n    use None instead.\n    \"\"\"\n    with pytest.raises(ValueError, match=\"total timeout must be a positive number\"):\n        ClientTimeout(total=0)\n\n\ndef test_client_timeout_total_none_is_valid() -> None:\n    \"\"\"Test that ClientTimeout(total=None) is still valid for disabling timeouts.\"\"\"\n    timeout = ClientTimeout(total=None)\n    assert timeout.total is None\n\n\nasync def test_invalid_ssl_param() -> None:\n    with pytest.raises(TypeError):\n        aiohttp.TCPConnector(ssl=object())  # type: ignore[arg-type]\n\n\nasync def test_tcp_connector_ctor_fingerprint_valid(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    valid = aiohttp.Fingerprint(hashlib.sha256(b\"foo\").digest())\n    conn = aiohttp.TCPConnector(ssl=valid)\n    assert conn._ssl is valid\n\n    await conn.close()\n\n\nasync def test_insecure_fingerprint_md5(loop: asyncio.AbstractEventLoop) -> None:\n    with pytest.raises(ValueError):\n        aiohttp.TCPConnector(ssl=aiohttp.Fingerprint(hashlib.md5(b\"foo\").digest()))\n\n\nasync def test_insecure_fingerprint_sha1(loop: asyncio.AbstractEventLoop) -> None:\n    with pytest.raises(ValueError):\n        aiohttp.TCPConnector(ssl=aiohttp.Fingerprint(hashlib.sha1(b\"foo\").digest()))\n\n\nasync def test_tcp_connector_clear_dns_cache(loop: asyncio.AbstractEventLoop) -> None:\n    conn = aiohttp.TCPConnector()\n    h1: ResolveResult = {\n        \"hostname\": \"a\",\n        \"host\": \"127.0.0.1\",\n        \"port\": 80,\n        \"family\": socket.AF_INET,\n        \"proto\": 0,\n        \"flags\": socket.AI_NUMERICHOST,\n    }\n    h2: ResolveResult = {\n        \"hostname\": \"a\",\n        \"host\": \"127.0.0.1\",\n        \"port\": 80,\n        \"family\": socket.AF_INET,\n        \"proto\": 0,\n        \"flags\": socket.AI_NUMERICHOST,\n    }\n    hosts = [h1, h2]\n    conn._cached_hosts.add((\"localhost\", 123), hosts)\n    conn._cached_hosts.add((\"localhost\", 124), hosts)\n    conn.clear_dns_cache(\"localhost\", 123)\n    with pytest.raises(KeyError):\n        conn._cached_hosts.next_addrs((\"localhost\", 123))\n\n    assert conn._cached_hosts.next_addrs((\"localhost\", 124)) == hosts\n\n    # Remove removed element is OK\n    conn.clear_dns_cache(\"localhost\", 123)\n    with pytest.raises(KeyError):\n        conn._cached_hosts.next_addrs((\"localhost\", 123))\n\n    conn.clear_dns_cache()\n    with pytest.raises(KeyError):\n        conn._cached_hosts.next_addrs((\"localhost\", 124))\n\n    await conn.close()\n\n\nasync def test_tcp_connector_clear_dns_cache_bad_args(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    conn = aiohttp.TCPConnector()\n    with pytest.raises(ValueError):\n        conn.clear_dns_cache(\"localhost\")\n\n    await conn.close()\n\n\nasync def test___get_ssl_context1() -> None:\n    conn = aiohttp.TCPConnector()\n    req = mock.Mock()\n    req.is_ssl.return_value = False\n    assert conn._get_ssl_context(req) is None\n\n    await conn.close()\n\n\nasync def test___get_ssl_context2() -> None:\n    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\n    conn = aiohttp.TCPConnector()\n    req = mock.Mock()\n    req.is_ssl.return_value = True\n    req.ssl = ctx\n    assert conn._get_ssl_context(req) is ctx\n\n    await conn.close()\n\n\nasync def test___get_ssl_context3() -> None:\n    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\n    conn = aiohttp.TCPConnector(ssl=ctx)\n    req = mock.Mock()\n    req.is_ssl.return_value = True\n    req.ssl = True\n    assert conn._get_ssl_context(req) is ctx\n\n    await conn.close()\n\n\nasync def test___get_ssl_context4() -> None:\n    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\n    conn = aiohttp.TCPConnector(ssl=ctx)\n    req = mock.Mock()\n    req.is_ssl.return_value = True\n    req.ssl = False\n    assert conn._get_ssl_context(req) is _SSL_CONTEXT_UNVERIFIED\n\n    await conn.close()\n\n\nasync def test___get_ssl_context5() -> None:\n    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\n    conn = aiohttp.TCPConnector(ssl=ctx)\n    req = mock.Mock()\n    req.is_ssl.return_value = True\n    req.ssl = aiohttp.Fingerprint(hashlib.sha256(b\"1\").digest())\n    assert conn._get_ssl_context(req) is _SSL_CONTEXT_UNVERIFIED\n\n    await conn.close()\n\n\nasync def test___get_ssl_context6() -> None:\n    conn = aiohttp.TCPConnector()\n    req = mock.Mock()\n    req.is_ssl.return_value = True\n    req.ssl = True\n    assert conn._get_ssl_context(req) is _SSL_CONTEXT_VERIFIED\n\n    await conn.close()\n\n\nasync def test_ssl_context_once() -> None:\n    \"\"\"Test the ssl context is created only once and shared between connectors.\"\"\"\n    conn1 = aiohttp.TCPConnector()\n    conn2 = aiohttp.TCPConnector()\n    conn3 = aiohttp.TCPConnector()\n\n    req = mock.Mock()\n    req.is_ssl.return_value = True\n    req.ssl = True\n    assert conn1._get_ssl_context(req) is _SSL_CONTEXT_VERIFIED\n    assert conn2._get_ssl_context(req) is _SSL_CONTEXT_VERIFIED\n    assert conn3._get_ssl_context(req) is _SSL_CONTEXT_VERIFIED\n\n\nasync def test_close_twice(loop: asyncio.AbstractEventLoop, key: ConnectionKey) -> None:\n    proto: ResponseHandler = create_mocked_conn(loop)\n\n    conn = aiohttp.BaseConnector()\n    conn._conns[key] = deque([(proto, 0)])\n    await conn.close()\n\n    assert not conn._conns\n    assert proto.close.called  # type: ignore[attr-defined]\n    assert conn.closed\n\n    conn._conns = \"Invalid\"  # type: ignore[assignment]  # fill with garbage\n    await conn.close()\n    assert conn.closed\n\n\nasync def test_close_cancels_cleanup_handle(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey\n) -> None:\n    conn = aiohttp.BaseConnector()\n    conn._release(key, create_mocked_conn(should_close=False))\n    assert conn._cleanup_handle is not None\n    await conn.close()\n    assert conn._cleanup_handle is None\n\n\nasync def test_close_cancels_resolve_host(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    cancelled = False\n\n    async def delay_resolve(*args: object, **kwargs: object) -> None:\n        \"\"\"Delay resolve() task in order to test cancellation.\"\"\"\n        nonlocal cancelled\n        try:\n            await asyncio.sleep(10)\n        except asyncio.CancelledError:\n            cancelled = True\n            raise\n\n    conn = aiohttp.TCPConnector()\n    req = make_client_request(\n        \"GET\", URL(\"http://localhost:80\"), loop=loop, response_class=mock.Mock()\n    )\n    with mock.patch.object(conn._resolver, \"resolve\", delay_resolve):\n        t = asyncio.create_task(conn.connect(req, [], ClientTimeout()))\n        # Let it create the internal task\n        await asyncio.sleep(0)\n        # Let that task start running\n        await asyncio.sleep(0)\n\n        # We now have a task being tracked and can ensure that .close() cancels it.\n        assert len(conn._resolve_host_tasks) == 1\n        await conn.close()\n        assert cancelled\n        assert len(conn._resolve_host_tasks) == 0\n\n        with suppress(asyncio.CancelledError):\n            await t\n\n\nasync def test_multiple_dns_resolution_requests_success(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    \"\"\"Verify that multiple DNS resolution requests are handled correctly.\"\"\"\n\n    async def delay_resolve(*args: object, **kwargs: object) -> list[ResolveResult]:\n        \"\"\"Delayed resolve() task.\"\"\"\n        for _ in range(3):\n            await asyncio.sleep(0)\n        return [\n            {\n                \"hostname\": \"localhost\",\n                \"host\": \"127.0.0.1\",\n                \"port\": 80,\n                \"family\": socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": socket.AI_NUMERICHOST,\n            },\n        ]\n\n    conn = aiohttp.TCPConnector(force_close=True)\n    req = make_client_request(\n        \"GET\", URL(\"http://localhost:80\"), loop=loop, response_class=mock.Mock()\n    )\n    with (\n        mock.patch.object(conn._resolver, \"resolve\", delay_resolve),\n        mock.patch(\n            \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n            side_effect=OSError(1, \"Forced connection to fail\"),\n        ),\n    ):\n        task1 = asyncio.create_task(conn.connect(req, [], ClientTimeout()))\n\n        # Let it create the internal task\n        await asyncio.sleep(0)\n        # Let that task start running\n        await asyncio.sleep(0)\n\n        # Ensure the task is running\n        assert len(conn._resolve_host_tasks) == 1\n\n        task2 = asyncio.create_task(conn.connect(req, [], ClientTimeout()))\n        task3 = asyncio.create_task(conn.connect(req, [], ClientTimeout()))\n\n        with pytest.raises(\n            aiohttp.ClientConnectorError, match=\"Forced connection to fail\"\n        ):\n            await task1\n\n        # Verify the the task is finished\n        assert len(conn._resolve_host_tasks) == 0\n\n        with pytest.raises(\n            aiohttp.ClientConnectorError, match=\"Forced connection to fail\"\n        ):\n            await task2\n        with pytest.raises(\n            aiohttp.ClientConnectorError, match=\"Forced connection to fail\"\n        ):\n            await task3\n\n\nasync def test_multiple_dns_resolution_requests_failure(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    \"\"\"Verify that DNS resolution failure for multiple requests is handled correctly.\"\"\"\n\n    async def delay_resolve(*args: object, **kwargs: object) -> list[ResolveResult]:\n        \"\"\"Delayed resolve() task.\"\"\"\n        for _ in range(3):\n            await asyncio.sleep(0)\n        raise OSError(None, \"DNS Resolution mock failure\")\n\n    conn = aiohttp.TCPConnector(force_close=True)\n    req = make_client_request(\n        \"GET\", URL(\"http://localhost:80\"), loop=loop, response_class=mock.Mock()\n    )\n    with (\n        mock.patch.object(conn._resolver, \"resolve\", delay_resolve),\n        mock.patch(\n            \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n            side_effect=OSError(1, \"Forced connection to fail\"),\n        ),\n    ):\n        task1 = asyncio.create_task(conn.connect(req, [], ClientTimeout()))\n\n        # Let it create the internal task\n        await asyncio.sleep(0)\n        # Let that task start running\n        await asyncio.sleep(0)\n\n        # Ensure the task is running\n        assert len(conn._resolve_host_tasks) == 1\n\n        task2 = asyncio.create_task(conn.connect(req, [], ClientTimeout()))\n        task3 = asyncio.create_task(conn.connect(req, [], ClientTimeout()))\n\n        with pytest.raises(\n            aiohttp.ClientConnectorError, match=\"DNS Resolution mock failure\"\n        ):\n            await task1\n\n        # Verify the the task is finished\n        assert len(conn._resolve_host_tasks) == 0\n\n        with pytest.raises(\n            aiohttp.ClientConnectorError, match=\"DNS Resolution mock failure\"\n        ):\n            await task2\n        with pytest.raises(\n            aiohttp.ClientConnectorError, match=\"DNS Resolution mock failure\"\n        ):\n            await task3\n\n\nasync def test_multiple_dns_resolution_requests_cancelled(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    \"\"\"Verify that DNS resolution cancellation does not affect other tasks.\"\"\"\n\n    async def delay_resolve(*args: object, **kwargs: object) -> list[ResolveResult]:\n        \"\"\"Delayed resolve() task.\"\"\"\n        for _ in range(3):\n            await asyncio.sleep(0)\n        raise OSError(None, \"DNS Resolution mock failure\")\n\n    conn = aiohttp.TCPConnector(force_close=True)\n    req = make_client_request(\n        \"GET\", URL(\"http://localhost:80\"), loop=loop, response_class=mock.Mock()\n    )\n    with (\n        mock.patch.object(conn._resolver, \"resolve\", delay_resolve),\n        mock.patch(\n            \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n            side_effect=OSError(1, \"Forced connection to fail\"),\n        ),\n    ):\n        task1 = asyncio.create_task(conn.connect(req, [], ClientTimeout()))\n\n        # Let it create the internal task\n        await asyncio.sleep(0)\n        # Let that task start running\n        await asyncio.sleep(0)\n\n        # Ensure the task is running\n        assert len(conn._resolve_host_tasks) == 1\n\n        task2 = asyncio.create_task(conn.connect(req, [], ClientTimeout()))\n        task3 = asyncio.create_task(conn.connect(req, [], ClientTimeout()))\n\n        task1.cancel()\n        with pytest.raises(asyncio.CancelledError):\n            await task1\n\n        with pytest.raises(\n            aiohttp.ClientConnectorError, match=\"DNS Resolution mock failure\"\n        ):\n            await task2\n        with pytest.raises(\n            aiohttp.ClientConnectorError, match=\"DNS Resolution mock failure\"\n        ):\n            await task3\n\n        # Verify the the task is finished\n        assert len(conn._resolve_host_tasks) == 0\n\n\nasync def test_multiple_dns_resolution_requests_first_cancelled(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    \"\"\"Verify that first DNS resolution cancellation does not make other resolutions fail.\"\"\"\n\n    async def delay_resolve(*args: object, **kwargs: object) -> list[ResolveResult]:\n        \"\"\"Delayed resolve() task.\"\"\"\n        for _ in range(3):\n            await asyncio.sleep(0)\n        return [\n            {\n                \"hostname\": \"localhost\",\n                \"host\": \"127.0.0.1\",\n                \"port\": 80,\n                \"family\": socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": socket.AI_NUMERICHOST,\n            },\n        ]\n\n    conn = aiohttp.TCPConnector(force_close=True)\n    req = make_client_request(\n        \"GET\", URL(\"http://localhost:80\"), loop=loop, response_class=mock.Mock()\n    )\n    with (\n        mock.patch.object(conn._resolver, \"resolve\", delay_resolve),\n        mock.patch(\n            \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n            side_effect=OSError(1, \"Forced connection to fail\"),\n        ),\n    ):\n        task1 = asyncio.create_task(conn.connect(req, [], ClientTimeout()))\n\n        # Let it create the internal task\n        await asyncio.sleep(0)\n        # Let that task start running\n        await asyncio.sleep(0)\n\n        # Ensure the task is running\n        assert len(conn._resolve_host_tasks) == 1\n\n        task2 = asyncio.create_task(conn.connect(req, [], ClientTimeout()))\n        task3 = asyncio.create_task(conn.connect(req, [], ClientTimeout()))\n\n        task1.cancel()\n        with pytest.raises(asyncio.CancelledError):\n            await task1\n\n        # The second and third tasks should still make the connection\n        # even if the first one is cancelled\n        with pytest.raises(\n            aiohttp.ClientConnectorError, match=\"Forced connection to fail\"\n        ):\n            await task2\n        with pytest.raises(\n            aiohttp.ClientConnectorError, match=\"Forced connection to fail\"\n        ):\n            await task3\n\n        # Verify the the task is finished\n        assert len(conn._resolve_host_tasks) == 0\n\n\nasync def test_multiple_dns_resolution_requests_first_fails_second_successful(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    \"\"\"Verify that first DNS resolution fails the first time and is successful the second time.\"\"\"\n    attempt = 0\n\n    async def delay_resolve(*args: object, **kwargs: object) -> list[ResolveResult]:\n        \"\"\"Delayed resolve() task.\"\"\"\n        nonlocal attempt\n        for _ in range(3):\n            await asyncio.sleep(0)\n        attempt += 1\n        if attempt == 1:\n            raise OSError(None, \"DNS Resolution mock failure\")\n        return [\n            {\n                \"hostname\": \"localhost\",\n                \"host\": \"127.0.0.1\",\n                \"port\": 80,\n                \"family\": socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": socket.AI_NUMERICHOST,\n            },\n        ]\n\n    conn = aiohttp.TCPConnector(force_close=True)\n    req = make_client_request(\n        \"GET\", URL(\"http://localhost:80\"), loop=loop, response_class=mock.Mock()\n    )\n    with (\n        mock.patch.object(conn._resolver, \"resolve\", delay_resolve),\n        mock.patch(\n            \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n            side_effect=OSError(1, \"Forced connection to fail\"),\n        ),\n    ):\n        task1 = asyncio.create_task(conn.connect(req, [], ClientTimeout()))\n\n        # Let it create the internal task\n        await asyncio.sleep(0)\n        # Let that task start running\n        await asyncio.sleep(0)\n\n        # Ensure the task is running\n        assert len(conn._resolve_host_tasks) == 1\n\n        task2 = asyncio.create_task(conn.connect(req, [], ClientTimeout()))\n\n        with pytest.raises(\n            aiohttp.ClientConnectorError, match=\"DNS Resolution mock failure\"\n        ):\n            await task1\n\n        assert len(conn._resolve_host_tasks) == 0\n        # The second task should also get the dns resolution failure\n        with pytest.raises(\n            aiohttp.ClientConnectorError, match=\"DNS Resolution mock failure\"\n        ):\n            await task2\n\n        # The third task is created after the resolution finished so\n        # it should try again and succeed\n        task3 = asyncio.create_task(conn.connect(req, [], ClientTimeout()))\n        # Let it create the internal task\n        await asyncio.sleep(0)\n        # Let that task start running\n        await asyncio.sleep(0)\n\n        # Ensure the task is running\n        assert len(conn._resolve_host_tasks) == 1\n\n        with pytest.raises(\n            aiohttp.ClientConnectorError, match=\"Forced connection to fail\"\n        ):\n            await task3\n\n        # Verify the the task is finished\n        assert len(conn._resolve_host_tasks) == 0\n\n\nasync def test_close_abort_closed_transports(loop: asyncio.AbstractEventLoop) -> None:\n    tr = mock.Mock()\n\n    conn = aiohttp.BaseConnector()\n    conn._cleanup_closed_transports.append(tr)\n    await conn.close()\n\n    assert not conn._cleanup_closed_transports\n    assert tr.abort.called\n    assert conn.closed\n\n\n@pytest.mark.usefixtures(\"enable_cleanup_closed\")\nasync def test_close_cancels_cleanup_closed_handle(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    conn = aiohttp.BaseConnector(enable_cleanup_closed=True)\n    assert conn._cleanup_closed_handle is not None\n    await conn.close()\n    assert conn._cleanup_closed_handle is None\n\n\nasync def test_ctor_with_default_loop(loop: asyncio.AbstractEventLoop) -> None:\n    conn = aiohttp.BaseConnector()\n    assert loop is conn._loop\n\n    await conn.close()\n\n\nasync def test_base_connector_allows_high_level_protocols(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    conn = aiohttp.BaseConnector()\n    assert conn.allowed_protocol_schema_set == {\n        \"\",\n        \"http\",\n        \"https\",\n        \"ws\",\n        \"wss\",\n    }\n\n\nasync def test_connect_with_limit(\n    loop: asyncio.AbstractEventLoop,\n    key: ConnectionKey,\n    make_client_request: _RequestMaker,\n) -> None:\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\n        \"GET\", URL(\"http://localhost:80\"), loop=loop, response_class=mock.Mock()\n    )\n\n    conn = aiohttp.BaseConnector(limit=1, limit_per_host=10)\n    conn._conns[key] = deque([(proto, loop.time())])\n    with mock.patch.object(\n        conn, \"_create_connection\", autospec=True, spec_set=True, return_value=proto\n    ):\n        connection1 = await conn.connect(req, [], ClientTimeout())\n        assert connection1._protocol == proto\n\n        assert 1 == len(conn._acquired)\n        assert proto in conn._acquired\n        assert key in conn._acquired_per_host\n        assert proto in conn._acquired_per_host[key]\n\n        acquired = False\n\n        async def f() -> None:\n            nonlocal acquired\n            connection2 = await conn.connect(req, [], ClientTimeout())\n            acquired = True\n            assert 1 == len(conn._acquired)\n            assert 1 == len(conn._acquired_per_host[key])\n            connection2.release()\n\n        task = loop.create_task(f())\n\n        await asyncio.sleep(0.01)\n        assert not acquired\n        connection1.release()\n        await asyncio.sleep(0)\n        assert acquired\n        await task  # type: ignore[unreachable]\n        await conn.close()\n\n\nasync def test_connect_queued_operation_tracing(\n    loop: asyncio.AbstractEventLoop,\n    key: ConnectionKey,\n    make_client_request: _RequestMaker,\n) -> None:\n    session = mock.Mock()\n    trace_config_ctx = mock.Mock()\n    on_connection_queued_start = mock.AsyncMock()\n    on_connection_queued_end = mock.AsyncMock()\n\n    trace_config = aiohttp.TraceConfig(\n        trace_config_ctx_factory=mock.Mock(return_value=trace_config_ctx)\n    )\n    trace_config.on_connection_queued_start.append(on_connection_queued_start)\n    trace_config.on_connection_queued_end.append(on_connection_queued_end)\n    trace_config.freeze()\n    traces = [Trace(session, trace_config, trace_config.trace_config_ctx())]\n\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\n        \"GET\", URL(\"http://localhost1:80\"), loop=loop, response_class=mock.Mock()\n    )\n\n    conn = aiohttp.BaseConnector(limit=1)\n    conn._conns[key] = deque([(proto, loop.time())])\n    with mock.patch.object(\n        conn, \"_create_connection\", autospec=True, spec_set=True, return_value=proto\n    ):\n        connection1 = await conn.connect(req, traces, ClientTimeout())\n\n        async def f() -> None:\n            connection2 = await conn.connect(req, traces, ClientTimeout())\n            on_connection_queued_start.assert_called_with(\n                session, trace_config_ctx, aiohttp.TraceConnectionQueuedStartParams()\n            )\n            on_connection_queued_end.assert_called_with(\n                session, trace_config_ctx, aiohttp.TraceConnectionQueuedEndParams()\n            )\n            connection2.release()\n\n        task = asyncio.ensure_future(f())\n        await asyncio.sleep(0.01)\n        connection1.release()\n        await task\n        await conn.close()\n\n\nasync def test_connect_reuseconn_tracing(\n    loop: asyncio.AbstractEventLoop,\n    key: ConnectionKey,\n    make_client_request: _RequestMaker,\n) -> None:\n    session = mock.Mock()\n    trace_config_ctx = mock.Mock()\n    on_connection_reuseconn = mock.AsyncMock()\n\n    trace_config = aiohttp.TraceConfig(\n        trace_config_ctx_factory=mock.Mock(return_value=trace_config_ctx)\n    )\n    trace_config.on_connection_reuseconn.append(on_connection_reuseconn)\n    trace_config.freeze()\n    traces = [Trace(session, trace_config, trace_config.trace_config_ctx())]\n\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\n        \"GET\", URL(\"http://localhost:80\"), loop=loop, response_class=mock.Mock()\n    )\n\n    conn = aiohttp.BaseConnector(limit=1)\n    conn._conns[key] = deque([(proto, loop.time())])\n    conn2 = await conn.connect(req, traces, ClientTimeout())\n    conn2.release()\n\n    on_connection_reuseconn.assert_called_with(\n        session, trace_config_ctx, aiohttp.TraceConnectionReuseconnParams()\n    )\n    await conn.close()\n\n\n@pytest.mark.parametrize(\n    \"test_case,wait_for_con,expect_proxy_auth_header\",\n    [\n        (\"use_proxy_with_embedded_auth\", False, True),\n        (\"use_proxy_with_auth_headers\", True, True),\n        (\"use_proxy_no_auth\", False, False),\n        (\"dont_use_proxy\", False, False),\n    ],\n)\nasync def test_connect_reuse_proxy_headers(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n    test_case: str,\n    wait_for_con: bool,\n    expect_proxy_auth_header: bool,\n) -> None:\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    if test_case != \"dont_use_proxy\":\n        proxy = (\n            URL(\"http://user:password@example.com\")\n            if test_case == \"use_proxy_with_embedded_auth\"\n            else URL(\"http://example.com\")\n        )\n        proxy_headers = (\n            CIMultiDict({hdrs.AUTHORIZATION: \"Basic dXNlcjpwYXNzd29yZA==\"})\n            if test_case == \"use_proxy_with_auth_headers\"\n            else None\n        )\n    else:\n        proxy = None\n        proxy_headers = None\n    key = ConnectionKey(\n        \"localhost\",\n        80,\n        False,\n        True,\n        proxy,\n        None,\n        hash(tuple(proxy_headers.items())) if proxy_headers else None,\n    )\n    req = make_client_request(\n        \"GET\",\n        URL(\"http://localhost:80\"),\n        loop=loop,\n        response_class=mock.Mock(),\n        proxy=proxy,\n        proxy_headers=proxy_headers,\n    )\n\n    conn = aiohttp.BaseConnector(limit=1)\n\n    async def _create_con(*args: Any, **kwargs: Any) -> None:\n        conn._conns[key] = deque([(proto, loop.time())])\n\n    with contextlib.ExitStack() as stack:\n        if wait_for_con:\n            # Simulate no available connections\n            stack.enter_context(\n                mock.patch.object(\n                    conn, \"_available_connections\", autospec=True, return_value=0\n                )\n            )\n            # Upon waiting for a connection, populate _conns with our proto,\n            # mocking a connection becoming immediately available\n            stack.enter_context(\n                mock.patch.object(\n                    conn,\n                    \"_wait_for_available_connection\",\n                    autospec=True,\n                    side_effect=_create_con,\n                )\n            )\n        else:\n            await _create_con()\n        # Call function to test\n        conn2 = await conn.connect(req, [], ClientTimeout())\n    conn2.release()\n    await conn.close()\n\n    if expect_proxy_auth_header:\n        assert req.headers[hdrs.PROXY_AUTHORIZATION] == \"Basic dXNlcjpwYXNzd29yZA==\"\n    else:\n        assert hdrs.PROXY_AUTHORIZATION not in req.headers\n\n\nasync def test_connect_with_limit_and_limit_per_host(\n    loop: asyncio.AbstractEventLoop,\n    key: ConnectionKey,\n    make_client_request: _RequestMaker,\n) -> None:\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\"GET\", URL(\"http://localhost:80\"), loop=loop)\n\n    conn = aiohttp.BaseConnector(limit=1000, limit_per_host=1)\n    conn._conns[key] = deque([(proto, loop.time())])\n    with mock.patch.object(\n        conn, \"_create_connection\", autospec=True, spec_set=True, return_value=proto\n    ):\n        acquired = False\n        connection1 = await conn.connect(req, [], ClientTimeout())\n\n        async def f() -> None:\n            nonlocal acquired\n            connection2 = await conn.connect(req, [], ClientTimeout())\n            acquired = True\n            assert 1 == len(conn._acquired)\n            assert 1 == len(conn._acquired_per_host[key])\n            connection2.release()\n\n        task = loop.create_task(f())\n\n        await asyncio.sleep(0.01)\n        assert not acquired\n        connection1.release()\n        await asyncio.sleep(0)\n        assert acquired\n        await task  # type: ignore[unreachable]\n        await conn.close()\n\n\nasync def test_connect_with_no_limit_and_limit_per_host(\n    loop: asyncio.AbstractEventLoop,\n    key: ConnectionKey,\n    make_client_request: _RequestMaker,\n) -> None:\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\"GET\", URL(\"http://localhost1:80\"), loop=loop)\n\n    conn = aiohttp.BaseConnector(limit=0, limit_per_host=1)\n    conn._conns[key] = deque([(proto, loop.time())])\n    with mock.patch.object(\n        conn, \"_create_connection\", autospec=True, spec_set=True, return_value=proto\n    ):\n        acquired = False\n        connection1 = await conn.connect(req, [], ClientTimeout())\n\n        async def f() -> None:\n            nonlocal acquired\n            connection2 = await conn.connect(req, [], ClientTimeout())\n            acquired = True\n            connection2.release()\n\n        task = loop.create_task(f())\n\n        await asyncio.sleep(0.01)\n        assert not acquired\n        connection1.release()\n        await asyncio.sleep(0)\n        assert acquired\n        await task  # type: ignore[unreachable]\n        await conn.close()\n\n\nasync def test_connect_with_no_limits(\n    loop: asyncio.AbstractEventLoop,\n    key: ConnectionKey,\n    make_client_request: _RequestMaker,\n) -> None:\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\"GET\", URL(\"http://localhost:80\"), loop=loop)\n\n    conn = aiohttp.BaseConnector(limit=0, limit_per_host=0)\n    conn._conns[key] = deque([(proto, loop.time())])\n    with mock.patch.object(\n        conn, \"_create_connection\", autospec=True, spec_set=True, return_value=proto\n    ):\n        acquired = False\n        connection1 = await conn.connect(req, [], ClientTimeout())\n\n        async def f() -> None:\n            nonlocal acquired\n            connection2 = await conn.connect(req, [], ClientTimeout())\n            acquired = True\n            assert 1 == len(conn._acquired)\n            assert not conn._acquired_per_host\n            connection2.release()\n\n        task = loop.create_task(f())\n\n        await asyncio.sleep(0.01)\n        assert acquired\n        connection1.release()\n        await task\n        await conn.close()\n\n\nasync def test_connect_with_limit_cancelled(\n    loop: asyncio.AbstractEventLoop,\n    key: ConnectionKey,\n    make_client_request: _RequestMaker,\n) -> None:\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\"GET\", URL(\"http://host:80\"), loop=loop)\n\n    conn = aiohttp.BaseConnector(limit=1)\n    conn._conns[key] = deque([(proto, loop.time())])\n    with mock.patch.object(\n        conn, \"_create_connection\", autospec=True, spec_set=True, return_value=proto\n    ):\n        connection = await conn.connect(req, [], ClientTimeout())\n        assert connection._protocol == proto\n        assert connection.transport == proto.transport\n\n        assert 1 == len(conn._acquired)\n\n        with pytest.raises(asyncio.TimeoutError):\n            # limit exhausted\n            await asyncio.wait_for(conn.connect(req, [], ClientTimeout()), 0.01)\n        connection.close()\n\n        await conn.close()\n\n\nasync def test_connect_with_capacity_release_waiters(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    async def check_with_exc(err: Exception) -> None:\n        conn = aiohttp.BaseConnector(limit=1)\n        with mock.patch.object(\n            conn, \"_create_connection\", autospec=True, spec_set=True, side_effect=err\n        ):\n            with pytest.raises(Exception):\n                req = mock.Mock()\n                await conn.connect(req, [], ClientTimeout())\n\n            assert not conn._waiters\n\n        await conn.close()\n\n    await check_with_exc(OSError(1, \"permission error\"))\n    await check_with_exc(RuntimeError())\n    await check_with_exc(asyncio.TimeoutError())\n\n\nasync def test_connect_with_limit_concurrent(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    proto = create_mocked_conn(loop)\n    proto.should_close = False\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\"GET\", URL(\"http://host:80\"), loop=loop)\n\n    max_connections = 2\n    num_connections = 0\n\n    conn = aiohttp.BaseConnector(limit=max_connections)\n\n    # Use a real coroutine for _create_connection; a mock would mask\n    # problems that only happen when the method yields.\n\n    async def create_connection(\n        req: object, traces: object, timeout: object\n    ) -> ResponseHandler:\n        nonlocal num_connections\n        num_connections += 1\n        await asyncio.sleep(0)\n\n        # Make a new transport mock each time because acquired\n        # transports are stored in a set. Reusing the same object\n        # messes with the count.\n        proto = create_mocked_conn(loop, should_close=False)\n        proto.is_connected.return_value = True\n\n        return proto\n\n    # Simulate something like a crawler. It opens a connection, does\n    # something with it, closes it, then creates tasks that make more\n    # connections and waits for them to finish. The crawler is started\n    # with multiple concurrent requests and stops when it hits a\n    # predefined maximum number of requests.\n\n    max_requests = 50\n    num_requests = 0\n    start_requests = max_connections + 1\n\n    async def f(start: bool = True) -> None:\n        nonlocal num_requests\n        if num_requests == max_requests:\n            return\n        num_requests += 1\n        if not start:\n            connection = await conn.connect(req, [], ClientTimeout())\n            await asyncio.sleep(0)\n            connection.release()\n            await asyncio.sleep(0)\n        tasks = [loop.create_task(f(start=False)) for i in range(start_requests)]\n        await asyncio.wait(tasks)\n\n    with mock.patch.object(conn, \"_create_connection\", create_connection):\n        await f()\n        await conn.close()\n\n        assert max_connections == num_connections\n\n\nasync def test_connect_waiters_cleanup(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\"GET\", URL(\"http://host:80\"), loop=loop)\n\n    conn = aiohttp.BaseConnector(limit=1)\n    with mock.patch.object(conn, \"_available_connections\", return_value=0):\n        t = loop.create_task(conn.connect(req, [], ClientTimeout()))\n\n        await asyncio.sleep(0)\n        assert conn._waiters.keys()\n\n        t.cancel()\n        await asyncio.sleep(0)\n        assert not conn._waiters.keys()\n\n    await conn.close()\n\n\nasync def test_connect_waiters_cleanup_key_error(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\"GET\", URL(\"http://host:80\"), loop=loop)\n\n    conn = aiohttp.BaseConnector(limit=1, limit_per_host=10)\n    with mock.patch.object(\n        conn, \"_available_connections\", autospec=True, spec_set=True, return_value=0\n    ):\n        t = loop.create_task(conn.connect(req, [], ClientTimeout()))\n\n        await asyncio.sleep(0)\n        assert conn._waiters.keys()\n\n        # we delete the entry explicitly before the\n        # canceled connection grabs the loop again, we\n        # must expect a none failure termination\n        conn._waiters.clear()\n        t.cancel()\n        await asyncio.sleep(0)\n        assert not conn._waiters.keys() == []\n\n    await conn.close()\n\n\nasync def test_close_with_acquired_connection(\n    loop: asyncio.AbstractEventLoop,\n    key: ConnectionKey,\n    make_client_request: _RequestMaker,\n) -> None:\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\"GET\", URL(\"http://host:80\"), loop=loop)\n\n    conn = aiohttp.BaseConnector(limit=1)\n    conn._conns[key] = deque([(proto, loop.time())])\n    with mock.patch.object(\n        conn, \"_create_connection\", autospec=True, spec_set=True, return_value=proto\n    ):\n        connection = await conn.connect(req, [], ClientTimeout())\n\n        assert 1 == len(conn._acquired)\n        await conn.close()\n        assert 0 == len(conn._acquired)\n        assert conn.closed\n        proto.close.assert_called_with()\n\n        assert not connection.closed\n        connection.close()\n        assert connection.closed\n\n\nasync def test_default_force_close(loop: asyncio.AbstractEventLoop) -> None:\n    connector = aiohttp.BaseConnector()\n    assert not connector.force_close\n\n    await connector.close()\n\n\nasync def test_limit_property(loop: asyncio.AbstractEventLoop) -> None:\n    conn = aiohttp.BaseConnector(limit=15)\n    assert 15 == conn.limit\n\n    await conn.close()\n\n\nasync def test_limit_per_host_property(loop: asyncio.AbstractEventLoop) -> None:\n    conn = aiohttp.BaseConnector(limit_per_host=15)\n    assert 15 == conn.limit_per_host\n\n    await conn.close()\n\n\nasync def test_limit_property_default(loop: asyncio.AbstractEventLoop) -> None:\n    conn = aiohttp.BaseConnector()\n    assert conn.limit == 100\n    await conn.close()\n\n\nasync def test_limit_per_host_property_default(loop: asyncio.AbstractEventLoop) -> None:\n    conn = aiohttp.BaseConnector()\n    assert conn.limit_per_host == 0\n    await conn.close()\n\n\nasync def test_force_close_and_explicit_keep_alive(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    aiohttp.BaseConnector(force_close=True)\n    aiohttp.BaseConnector(force_close=True, keepalive_timeout=None)\n    with pytest.raises(ValueError):\n        aiohttp.BaseConnector(keepalive_timeout=30, force_close=True)\n\n\nasync def test_error_on_connection(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey\n) -> None:\n    conn = aiohttp.BaseConnector(limit=1, limit_per_host=10)\n\n    req = mock.Mock()\n    req.connection_key = key\n    proto = create_mocked_conn(loop)\n    i = 0\n\n    fut = loop.create_future()\n    exc = OSError()\n\n    async def create_connection(\n        req: object, traces: object, timeout: object\n    ) -> ResponseHandler:\n        nonlocal i\n        i += 1\n        if i == 1:\n            await fut\n            raise exc\n        elif i == 2:\n            return proto\n        assert False\n\n    with mock.patch.object(conn, \"_create_connection\", create_connection):\n        t1 = loop.create_task(conn.connect(req, [], ClientTimeout()))\n        t2 = loop.create_task(conn.connect(req, [], ClientTimeout()))\n        await asyncio.sleep(0)\n        assert not t1.done()\n        assert not t2.done()\n        assert len(conn._acquired_per_host[key]) == 1\n\n        fut.set_result(None)\n        with pytest.raises(OSError):\n            await t1\n\n        ret = await t2\n        assert len(conn._acquired_per_host[key]) == 1\n\n        assert ret._key == key\n        assert ret.protocol == proto\n        assert proto in conn._acquired\n        ret.release()\n\n    await conn.close()\n\n\nasync def test_cancelled_waiter(loop: asyncio.AbstractEventLoop) -> None:\n    conn = aiohttp.BaseConnector(limit=1)\n    req = mock.Mock()\n    req.connection_key = \"key\"\n    proto = create_mocked_conn(loop)\n\n    async def create_connection(req: object, traces: object = None) -> ResponseHandler:\n        await asyncio.sleep(1)\n        return proto\n\n    with mock.patch.object(conn, \"_create_connection\", create_connection):\n        conn._acquired.add(proto)\n\n        conn2 = loop.create_task(conn.connect(req, [], ClientTimeout()))\n        await asyncio.sleep(0)\n        conn2.cancel()\n\n        with pytest.raises(asyncio.CancelledError):\n            await conn2\n\n    await conn.close()\n\n\nasync def test_error_on_connection_with_cancelled_waiter(\n    loop: asyncio.AbstractEventLoop, key: ConnectionKey\n) -> None:\n    conn = aiohttp.BaseConnector(limit=1, limit_per_host=10)\n\n    req = mock.Mock()\n    req.connection_key = key\n    proto = create_mocked_conn()\n    i = 0\n\n    fut1 = loop.create_future()\n    fut2 = loop.create_future()\n    exc = OSError()\n\n    async def create_connection(\n        req: object, traces: object, timeout: object\n    ) -> ResponseHandler:\n        nonlocal i\n        i += 1\n        if i == 1:\n            await fut1\n            raise exc\n        if i == 2:\n            await fut2\n        elif i == 3:\n            return proto\n        assert False\n\n    with mock.patch.object(conn, \"_create_connection\", create_connection):\n        t1 = loop.create_task(conn.connect(req, [], ClientTimeout()))\n        t2 = loop.create_task(conn.connect(req, [], ClientTimeout()))\n        t3 = loop.create_task(conn.connect(req, [], ClientTimeout()))\n        await asyncio.sleep(0)\n        assert not t1.done()\n        assert not t2.done()\n        assert len(conn._acquired_per_host[key]) == 1\n\n        fut1.set_result(None)\n        fut2.cancel()\n        with pytest.raises(OSError):\n            await t1\n\n        with pytest.raises(asyncio.CancelledError):\n            await t2\n\n        ret = await t3\n        assert len(conn._acquired_per_host[key]) == 1\n\n        assert ret._key == key\n        assert ret.protocol == proto\n        assert proto in conn._acquired\n        ret.release()\n\n    await conn.close()\n\n\nasync def test_tcp_connector(\n    aiohttp_client: AiohttpClient, loop: asyncio.AbstractEventLoop\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    r = await client.get(\"/\")\n    assert r.status == 200\n\n\n@pytest.mark.skipif(not hasattr(socket, \"AF_UNIX\"), reason=\"requires UNIX sockets\")\nasync def test_unix_connector_not_found(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    connector = aiohttp.UnixConnector(\"/\" + uuid.uuid4().hex)\n\n    req = make_client_request(\"GET\", URL(\"http://www.python.org\"), loop=loop)\n    with pytest.raises(aiohttp.ClientConnectorError):\n        await connector.connect(req, [], ClientTimeout())\n\n\n@pytest.mark.skipif(not hasattr(socket, \"AF_UNIX\"), reason=\"requires UNIX sockets\")\nasync def test_unix_connector_permission(  # type: ignore[misc]\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    m = mock.AsyncMock(side_effect=PermissionError())\n    with mock.patch.object(loop, \"create_unix_connection\", m):\n        connector = aiohttp.UnixConnector(\"/\" + uuid.uuid4().hex)\n\n        req = make_client_request(\"GET\", URL(\"http://www.python.org\"), loop=loop)\n        with pytest.raises(aiohttp.ClientConnectorError):\n            await connector.connect(req, [], ClientTimeout())\n\n\n@pytest.mark.skipif(\n    platform.system() != \"Windows\", reason=\"Proactor Event loop present only in Windows\"\n)\nasync def test_named_pipe_connector_wrong_loop(\n    selector_loop: asyncio.AbstractEventLoop, pipe_name: str\n) -> None:\n    with pytest.raises(RuntimeError):\n        aiohttp.NamedPipeConnector(pipe_name)\n\n\n@pytest.mark.skipif(\n    platform.system() != \"Windows\", reason=\"Proactor Event loop present only in Windows\"\n)\nasync def test_named_pipe_connector_not_found(  # type: ignore[misc]\n    proactor_loop: asyncio.AbstractEventLoop,\n    pipe_name: str,\n    make_client_request: _RequestMaker,\n) -> None:\n    asyncio.set_event_loop(proactor_loop)\n    connector = aiohttp.NamedPipeConnector(pipe_name)\n\n    req = make_client_request(\"GET\", URL(\"http://www.python.org\"), loop=proactor_loop)\n    with pytest.raises(aiohttp.ClientConnectorError):\n        await connector.connect(req, [], ClientTimeout())\n\n\n@pytest.mark.skipif(\n    platform.system() != \"Windows\", reason=\"Proactor Event loop present only in Windows\"\n)\nasync def test_named_pipe_connector_permission(  # type: ignore[misc]\n    proactor_loop: asyncio.AbstractEventLoop,\n    pipe_name: str,\n    make_client_request: _RequestMaker,\n) -> None:\n    m = mock.AsyncMock(side_effect=PermissionError())\n    with mock.patch.object(proactor_loop, \"create_pipe_connection\", m):\n        asyncio.set_event_loop(proactor_loop)\n        connector = aiohttp.NamedPipeConnector(pipe_name)\n\n        req = make_client_request(\n            \"GET\", URL(\"http://www.python.org\"), loop=proactor_loop\n        )\n        with pytest.raises(aiohttp.ClientConnectorError):\n            await connector.connect(req, [], ClientTimeout())\n\n\nasync def test_default_use_dns_cache() -> None:\n    conn = aiohttp.TCPConnector()\n    assert conn.use_dns_cache\n\n    await conn.close()\n\n\nasync def test_resolver_not_called_with_address_is_ip(\n    loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    resolver = mock.MagicMock()\n    connector = aiohttp.TCPConnector(resolver=resolver)\n\n    req = make_client_request(\n        \"GET\",\n        URL(f\"http://127.0.0.1:{unused_port()}\"),\n        loop=loop,\n        response_class=mock.Mock(),\n    )\n\n    with pytest.raises(OSError):\n        await connector.connect(req, [], ClientTimeout())\n\n    resolver.resolve.assert_not_called()\n\n    await connector.close()\n\n\nasync def test_tcp_connector_raise_connector_ssl_error(\n    aiohttp_server: AiohttpServer, ssl_ctx: ssl.SSLContext\n) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n\n    srv = await aiohttp_server(app, ssl=ssl_ctx)\n\n    port = unused_port()\n    conn = aiohttp.TCPConnector(local_addr=(\"127.0.0.1\", port))\n\n    session = aiohttp.ClientSession(connector=conn)\n    url = srv.make_url(\"/\")\n\n    err = aiohttp.ClientConnectorCertificateError\n    with pytest.raises(err) as ctx:\n        await session.get(url)\n\n    assert isinstance(ctx.value, aiohttp.ClientConnectorCertificateError)\n    assert isinstance(ctx.value.certificate_error, ssl.SSLError)\n\n    await session.close()\n\n    await conn.close()\n\n\n@pytest.mark.parametrize(\n    \"host\",\n    (\n        pytest.param(\"127.0.0.1\", id=\"ip address\"),\n        pytest.param(\"localhost\", id=\"domain name\"),\n        pytest.param(\"localhost.\", id=\"fully-qualified domain name\"),\n        pytest.param(\n            \"localhost...\", id=\"fully-qualified domain name with multiple trailing dots\"\n        ),\n        pytest.param(\"príklad.localhost.\", id=\"idna fully-qualified domain name\"),\n    ),\n)\nasync def test_tcp_connector_do_not_raise_connector_ssl_error(\n    aiohttp_server: AiohttpServer,\n    ssl_ctx: ssl.SSLContext,\n    client_ssl_ctx: ssl.SSLContext,\n    host: str,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n\n    srv = await aiohttp_server(app, ssl=ssl_ctx)\n    port = unused_port()\n    conn = aiohttp.TCPConnector(local_addr=(\"127.0.0.1\", port))\n\n    # resolving something.localhost with the real DNS resolver does not work on macOS, so we have a stub.\n    async def _resolve_host(\n        host: str, port: int, traces: object = None\n    ) -> list[ResolveResult]:\n        return [\n            {\n                \"hostname\": host,\n                \"host\": \"127.0.0.1\",\n                \"port\": port,\n                \"family\": socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": socket.AI_NUMERICHOST,\n            },\n            {\n                \"hostname\": host,\n                \"host\": \"::1\",\n                \"port\": port,\n                \"family\": socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": socket.AI_NUMERICHOST,\n            },\n        ]\n\n    with mock.patch.object(\n        conn, \"_resolve_host\", autospec=True, spec_set=True, side_effect=_resolve_host\n    ):\n        session = aiohttp.ClientSession(connector=conn)\n        url = srv.make_url(\"/\")\n\n        r = await session.get(url.with_host(host), ssl=client_ssl_ctx)\n\n        r.release()\n        first_conn = next(iter(conn._conns.values()))[0][0]\n\n        assert first_conn.transport is not None\n        _sslcontext = first_conn.transport._ssl_protocol._sslcontext  # type: ignore[attr-defined]\n\n        assert _sslcontext is client_ssl_ctx\n        r.close()\n\n        await session.close()\n        await conn.close()\n\n\nasync def test_tcp_connector_uses_provided_local_addr(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    srv = await aiohttp_server(app)\n\n    port = unused_port()\n    conn = aiohttp.TCPConnector(local_addr=(\"127.0.0.1\", port))\n\n    session = aiohttp.ClientSession(connector=conn)\n    url = srv.make_url(\"/\")\n\n    r = await session.get(url)\n    r.release()\n\n    first_conn = next(iter(conn._conns.values()))[0][0]\n    assert first_conn.transport is not None\n    assert first_conn.transport.get_extra_info(\"sockname\") == (\"127.0.0.1\", port)\n    r.close()\n    await session.close()\n    await conn.close()\n\n\nasync def test_unix_connector(\n    unix_server: Callable[[web.Application], Awaitable[None]], unix_sockname: str\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    await unix_server(app)\n\n    url = \"http://127.0.0.1/\"\n\n    connector = aiohttp.UnixConnector(unix_sockname)\n    assert unix_sockname == connector.path\n    assert connector.allowed_protocol_schema_set == {\n        \"\",\n        \"http\",\n        \"https\",\n        \"ws\",\n        \"wss\",\n        \"unix\",\n    }\n\n    session = ClientSession(connector=connector)\n    r = await session.get(url)\n    assert r.status == 200\n    r.close()\n    await session.close()\n\n\n@pytest.mark.skipif(\n    platform.system() != \"Windows\", reason=\"Proactor Event loop present only in Windows\"\n)\nasync def test_named_pipe_connector(\n    proactor_loop: asyncio.AbstractEventLoop,\n    named_pipe_server: Callable[[web.Application], Awaitable[None]],\n    pipe_name: str,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    await named_pipe_server(app)\n\n    url = \"http://this-does-not-matter.com\"\n\n    connector = aiohttp.NamedPipeConnector(pipe_name)\n    assert pipe_name == connector.path\n    assert connector.allowed_protocol_schema_set == {\n        \"\",\n        \"http\",\n        \"https\",\n        \"ws\",\n        \"wss\",\n        \"npipe\",\n    }\n\n    session = ClientSession(connector=connector)\n    r = await session.get(url)\n    assert r.status == 200\n    r.close()\n    await session.close()\n\n\nclass TestDNSCacheTable:\n    host1 = (\"localhost\", 80)\n    host2 = (\"foo\", 80)\n    result1: ResolveResult = {\n        \"hostname\": \"localhost\",\n        \"host\": \"127.0.0.1\",\n        \"port\": 80,\n        \"family\": socket.AF_INET,\n        \"proto\": 0,\n        \"flags\": socket.AI_NUMERICHOST,\n    }\n    result2: ResolveResult = {\n        \"hostname\": \"foo\",\n        \"host\": \"127.0.0.2\",\n        \"port\": 80,\n        \"family\": socket.AF_INET,\n        \"proto\": 0,\n        \"flags\": socket.AI_NUMERICHOST,\n    }\n\n    @pytest.fixture\n    def dns_cache_table(self) -> _DNSCacheTable:\n        return _DNSCacheTable()\n\n    def test_next_addrs_basic(self, dns_cache_table: _DNSCacheTable) -> None:\n        dns_cache_table.add(self.host1, [self.result1])\n        dns_cache_table.add(self.host2, [self.result2])\n\n        addrs = dns_cache_table.next_addrs(self.host1)\n        assert addrs == [self.result1]\n        addrs = dns_cache_table.next_addrs(self.host2)\n        assert addrs == [self.result2]\n        with pytest.raises(KeyError):\n            dns_cache_table.next_addrs((\"no-such-host\", 80))\n\n    def test_remove(self, dns_cache_table: _DNSCacheTable) -> None:\n        dns_cache_table.add(self.host1, [self.result1])\n        dns_cache_table.remove(self.host1)\n        with pytest.raises(KeyError):\n            dns_cache_table.next_addrs(self.host1)\n\n    def test_clear(self, dns_cache_table: _DNSCacheTable) -> None:\n        dns_cache_table.add(self.host1, [self.result1])\n        dns_cache_table.clear()\n        with pytest.raises(KeyError):\n            dns_cache_table.next_addrs(self.host1)\n\n    def test_not_expired_ttl_None(self, dns_cache_table: _DNSCacheTable) -> None:\n        dns_cache_table.add(self.host1, [self.result1])\n        assert not dns_cache_table.expired(self.host1)\n\n    def test_not_expired_ttl(self) -> None:\n        dns_cache_table = _DNSCacheTable(ttl=0.1)\n        dns_cache_table.add(self.host1, [self.result1])\n        assert not dns_cache_table.expired(self.host1)\n\n    def test_expired_ttl(self, monkeypatch: pytest.MonkeyPatch) -> None:\n        dns_cache_table = _DNSCacheTable(ttl=1)\n        monkeypatch.setattr(\"aiohttp.connector.monotonic\", lambda: 1)\n        dns_cache_table.add(self.host1, [self.result1])\n        monkeypatch.setattr(\"aiohttp.connector.monotonic\", lambda: 2)\n        assert not dns_cache_table.expired(self.host1)\n        monkeypatch.setattr(\"aiohttp.connector.monotonic\", lambda: 3)\n        assert dns_cache_table.expired(self.host1)\n\n    def test_never_expire(self, monkeypatch: pytest.MonkeyPatch) -> None:\n        dns_cache_table = _DNSCacheTable(ttl=None)\n        monkeypatch.setattr(\"aiohttp.connector.monotonic\", lambda: 1)\n        dns_cache_table.add(self.host1, [self.result1])\n        monkeypatch.setattr(\"aiohttp.connector.monotonic\", lambda: 10000000)\n        assert not dns_cache_table.expired(self.host1)\n\n    def test_always_expire(self, monkeypatch: pytest.MonkeyPatch) -> None:\n        dns_cache_table = _DNSCacheTable(ttl=0)\n        monkeypatch.setattr(\"aiohttp.connector.monotonic\", lambda: 1)\n        dns_cache_table.add(self.host1, [self.result1])\n        monkeypatch.setattr(\"aiohttp.connector.monotonic\", lambda: 1.00001)\n        assert dns_cache_table.expired(self.host1)\n\n    def test_next_addrs(self, dns_cache_table: _DNSCacheTable) -> None:\n        result3: ResolveResult = {\n            \"hostname\": \"foo\",\n            \"host\": \"127.0.0.3\",\n            \"port\": 80,\n            \"family\": socket.AF_INET,\n            \"proto\": 0,\n            \"flags\": socket.AI_NUMERICHOST,\n        }\n        dns_cache_table.add(self.host2, [self.result1, self.result2, result3])\n\n        # Each calls to next_addrs return the hosts using\n        # a round robin strategy.\n        addrs = dns_cache_table.next_addrs(self.host2)\n        assert addrs == [self.result1, self.result2, result3]\n\n        addrs = dns_cache_table.next_addrs(self.host2)\n        assert addrs == [self.result2, result3, self.result1]\n\n        addrs = dns_cache_table.next_addrs(self.host2)\n        assert addrs == [result3, self.result1, self.result2]\n\n        addrs = dns_cache_table.next_addrs(self.host2)\n        assert addrs == [self.result1, self.result2, result3]\n\n    def test_next_addrs_single(self, dns_cache_table: _DNSCacheTable) -> None:\n        dns_cache_table.add(self.host2, [self.result1])\n\n        addrs = dns_cache_table.next_addrs(self.host2)\n        assert addrs == [self.result1]\n\n        addrs = dns_cache_table.next_addrs(self.host2)\n        assert addrs == [self.result1]\n\n    def test_max_size_eviction(self) -> None:\n        table = _DNSCacheTable(max_size=2)\n\n        table.add(self.host1, [self.result1])\n        table.add(self.host2, [self.result2])\n\n        host3 = (\"example.com\", 80)\n        result3: ResolveResult = {\n            **self.result1,\n            \"hostname\": \"example.com\",\n            \"host\": \"1.2.3.4\",\n        }\n        table.add(host3, [result3])\n\n        assert len(table._addrs_rr) == 2\n        assert self.host1 not in table._addrs_rr\n        assert host3 in table._addrs_rr\n\n    def test_lru_eviction(self) -> None:\n        table = _DNSCacheTable(max_size=2)\n\n        table.add(self.host1, [self.result1])\n        table.add(self.host2, [self.result2])\n\n        table.next_addrs(self.host1)\n\n        host3 = (\"example.com\", 80)\n        result3: ResolveResult = {\n            **self.result1,\n            \"hostname\": \"example.com\",\n            \"host\": \"1.2.3.4\",\n        }\n        table.add(host3, [result3])\n\n        assert self.host1 in table._addrs_rr\n        assert self.host2 not in table._addrs_rr\n\n    def test_lru_eviction_add(self) -> None:\n        table = _DNSCacheTable(max_size=2)\n\n        table.add(self.host1, [self.result1])\n        table.add(self.host2, [self.result2])\n\n        # Re-add, thus making host1 the most recently used.\n        table.add(self.host1, [self.result1])\n\n        host3 = (\"example.com\", 80)\n        result3: ResolveResult = {\n            **self.result1,\n            \"hostname\": \"example.com\",\n            \"host\": \"1.2.3.4\",\n        }\n        table.add(host3, [result3])\n\n        assert self.host1 in table._addrs_rr\n        assert self.host2 not in table._addrs_rr\n\n\nasync def test_connector_cache_trace_race() -> None:\n    class DummyTracer(Trace):\n        def __init__(self) -> None:\n            \"\"\"Dummy\"\"\"\n\n        async def send_dns_cache_hit(self, *args: object, **kwargs: object) -> None:\n            connector._cached_hosts.remove((\"\", 0))\n\n    token: ResolveResult = {\n        \"hostname\": \"localhost\",\n        \"host\": \"127.0.0.1\",\n        \"port\": 80,\n        \"family\": socket.AF_INET,\n        \"proto\": 0,\n        \"flags\": socket.AI_NUMERICHOST,\n    }\n    connector = TCPConnector()\n    connector._cached_hosts.add((\"\", 0), [token])\n\n    traces = [DummyTracer()]\n    assert await connector._resolve_host(\"\", 0, traces) == [token]\n\n    await connector.close()\n\n\nasync def test_connector_throttle_trace_race(loop: asyncio.AbstractEventLoop) -> None:\n    key = (\"\", 0)\n    token: ResolveResult = {\n        \"hostname\": \"localhost\",\n        \"host\": \"127.0.0.1\",\n        \"port\": 80,\n        \"family\": socket.AF_INET,\n        \"proto\": 0,\n        \"flags\": socket.AI_NUMERICHOST,\n    }\n\n    class DummyTracer(Trace):\n        def __init__(self) -> None:\n            \"\"\"Dummy\"\"\"\n\n        async def send_dns_cache_hit(self, *args: object, **kwargs: object) -> None:\n            futures = connector._throttle_dns_futures.pop(key)\n            for fut in futures:\n                fut.set_result(None)\n            connector._cached_hosts.add(key, [token])\n\n    connector = TCPConnector()\n    connector._throttle_dns_futures[key] = set()\n    traces = [DummyTracer()]\n    assert await connector._resolve_host(\"\", 0, traces) == [token]\n\n    await connector.close()\n\n\nasync def test_connector_resolve_in_case_of_trace_cache_miss_exception(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    token: ResolveResult = {\n        \"hostname\": \"localhost\",\n        \"host\": \"127.0.0.1\",\n        \"port\": 80,\n        \"family\": socket.AF_INET,\n        \"proto\": 0,\n        \"flags\": socket.AI_NUMERICHOST,\n    }\n\n    request_count = 0\n\n    class DummyTracer(Trace):\n        def __init__(self) -> None:\n            \"\"\"Dummy\"\"\"\n\n        async def send_dns_cache_hit(self, *args: object, **kwargs: object) -> None:\n            \"\"\"Dummy send_dns_cache_hit\"\"\"\n\n        async def send_dns_resolvehost_start(\n            self, *args: object, **kwargs: object\n        ) -> None:\n            \"\"\"Dummy send_dns_resolvehost_start\"\"\"\n\n        async def send_dns_resolvehost_end(\n            self, *args: object, **kwargs: object\n        ) -> None:\n            \"\"\"Dummy send_dns_resolvehost_end\"\"\"\n\n        async def send_dns_cache_miss(self, *args: object, **kwargs: object) -> None:\n            nonlocal request_count\n            request_count += 1\n            if request_count <= 1:\n                raise Exception(\"first attempt\")\n\n    async def resolve_response(\n        *_args: object, **_kwargs: object\n    ) -> list[ResolveResult]:\n        await asyncio.sleep(0)\n        return [token]\n\n    with mock.patch(\"aiohttp.connector.DefaultResolver\") as m_resolver:\n        mock_default_resolver = mock.create_autospec(\n            AsyncResolver, instance=True, spec_set=True\n        )\n        mock_default_resolver.resolve.side_effect = resolve_response\n        m_resolver.return_value = mock_default_resolver\n\n        connector = TCPConnector()\n        traces = [DummyTracer()]\n\n        with pytest.raises(Exception):\n            await connector._resolve_host(\"\", 0, traces)\n\n        await connector._resolve_host(\"\", 0, traces) == [token]\n\n    await connector.close()\n\n\nasync def test_connector_does_not_remove_needed_waiters(\n    loop: asyncio.AbstractEventLoop,\n    key: ConnectionKey,\n    make_client_request: _RequestMaker,\n) -> None:\n    proto = create_mocked_conn(loop)\n    proto.is_connected.return_value = True\n\n    req = make_client_request(\"GET\", URL(\"https://localhost:80\"), loop=loop)\n    connection_key = req.connection_key\n\n    async def await_connection_and_check_waiters() -> None:\n        connection = await connector.connect(req, [], ClientTimeout())\n        try:\n            assert connection_key in connector._waiters\n            assert dummy_waiter in connector._waiters[connection_key]\n        finally:\n            connection.close()\n\n    async def allow_connection_and_add_dummy_waiter() -> None:\n        list(connector._waiters[connection_key])[0].set_result(None)\n        del connector._waiters[connection_key]\n        connector._waiters[connection_key][dummy_waiter] = None\n\n    connector = aiohttp.BaseConnector()\n    with mock.patch.object(\n        connector,\n        \"_available_connections\",\n        autospec=True,\n        spec_set=True,\n        side_effect=[0, 1, 1, 1],\n    ):\n        connector._conns[key] = deque([(proto, loop.time())])\n        with mock.patch.object(\n            connector,\n            \"_create_connection\",\n            autospec=True,\n            spec_set=True,\n            return_value=proto,\n        ):\n            dummy_waiter = loop.create_future()\n\n            await asyncio.gather(\n                await_connection_and_check_waiters(),\n                allow_connection_and_add_dummy_waiter(),\n            )\n\n            await connector.close()\n\n\ndef test_connector_multiple_event_loop(make_client_request: _RequestMaker) -> None:\n    \"\"\"Test the connector with multiple event loops.\"\"\"\n\n    async def async_connect() -> Literal[True]:\n        conn = aiohttp.TCPConnector()\n        loop = asyncio.get_running_loop()\n        req = make_client_request(\"GET\", URL(\"https://127.0.0.1\"), loop=loop)\n        with suppress(aiohttp.ClientConnectorError):\n            with mock.patch.object(\n                conn._loop,\n                \"create_connection\",\n                autospec=True,\n                spec_set=True,\n                side_effect=ssl.CertificateError,\n            ):\n                await conn.connect(req, [], ClientTimeout())\n        return True\n\n    def test_connect() -> Literal[True]:\n        loop = asyncio.new_event_loop()\n        try:\n            return loop.run_until_complete(async_connect())\n        finally:\n            loop.close()\n\n    with futures.ThreadPoolExecutor() as executor:\n        res_list = [executor.submit(test_connect) for _ in range(2)]\n        raw_response_list = [res.result() for res in futures.as_completed(res_list)]\n\n    assert raw_response_list == [True, True]\n\n\nasync def test_tcp_connector_socket_factory(\n    loop: asyncio.AbstractEventLoop,\n    start_connection: mock.AsyncMock,\n    make_client_request: _RequestMaker,\n) -> None:\n    \"\"\"Check that socket factory is called\"\"\"\n    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:\n        start_connection.return_value = s\n\n        local_addr = None\n        socket_factory: Callable[[AddrInfoType], socket.socket] = lambda _: s\n        happy_eyeballs_delay = 0.123\n        interleave = 3\n        conn = aiohttp.TCPConnector(\n            interleave=interleave,\n            local_addr=local_addr,\n            happy_eyeballs_delay=happy_eyeballs_delay,\n            socket_factory=socket_factory,\n        )\n\n        with mock.patch.object(\n            conn._loop,\n            \"create_connection\",\n            autospec=True,\n            spec_set=True,\n            return_value=(mock.Mock(), mock.Mock()),\n        ):\n            host = \"127.0.0.1\"\n            port = 443\n            req = make_client_request(\"GET\", URL(f\"https://{host}:{port}\"), loop=loop)\n            with closing(await conn.connect(req, [], ClientTimeout())):\n                pass\n            await conn.close()\n\n    start_connection.assert_called_with(\n        addr_infos=[\n            (socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, \"\", (host, port))\n        ],\n        local_addr_infos=local_addr,\n        happy_eyeballs_delay=happy_eyeballs_delay,\n        interleave=interleave,\n        loop=loop,\n        socket_factory=socket_factory,\n    )\n\n\ndef test_default_ssl_context_creation_without_ssl() -> None:\n    \"\"\"Verify _make_ssl_context does not raise when ssl is not available.\"\"\"\n    with mock.patch.object(connector_module, \"ssl\", None):\n        assert connector_module._make_ssl_context(False) is None\n        assert connector_module._make_ssl_context(True) is None\n\n\ndef _acquired_connection(\n    conn: aiohttp.BaseConnector, proto: ResponseHandler, key: ConnectionKey\n) -> Connection:\n    conn._acquired.add(proto)\n    conn._acquired_per_host[key].add(proto)\n    return Connection(conn, key, proto, conn._loop)\n\n\nasync def test_available_connections_with_limit_per_host(\n    key: ConnectionKey, other_host_key2: ConnectionKey\n) -> None:\n    \"\"\"Verify expected values based on active connections with host limit.\"\"\"\n    conn = aiohttp.BaseConnector(limit=3, limit_per_host=2)\n    assert conn._available_connections(key) == 2\n    assert conn._available_connections(other_host_key2) == 2\n    proto1 = create_mocked_conn()\n    connection1 = _acquired_connection(conn, proto1, key)\n    assert conn._available_connections(key) == 1\n    assert conn._available_connections(other_host_key2) == 2\n    proto2 = create_mocked_conn()\n    connection2 = _acquired_connection(conn, proto2, key)\n    assert conn._available_connections(key) == 0\n    assert conn._available_connections(other_host_key2) == 1\n    connection1.close()\n    assert conn._available_connections(key) == 1\n    assert conn._available_connections(other_host_key2) == 2\n    connection2.close()\n    other_proto1 = create_mocked_conn()\n    other_connection1 = _acquired_connection(conn, other_proto1, other_host_key2)\n    assert conn._available_connections(key) == 2\n    assert conn._available_connections(other_host_key2) == 1\n    other_connection1.close()\n    assert conn._available_connections(key) == 2\n    assert conn._available_connections(other_host_key2) == 2\n\n\n@pytest.mark.parametrize(\"limit_per_host\", [0, 10])\nasync def test_available_connections_without_limit_per_host(  # type: ignore[misc]\n    key: ConnectionKey, other_host_key2: ConnectionKey, limit_per_host: int\n) -> None:\n    \"\"\"Verify expected values based on active connections with higher host limit.\"\"\"\n    conn = aiohttp.BaseConnector(limit=3, limit_per_host=limit_per_host)\n    assert conn._available_connections(key) == 3\n    assert conn._available_connections(other_host_key2) == 3\n    proto1 = create_mocked_conn()\n    connection1 = _acquired_connection(conn, proto1, key)\n    assert conn._available_connections(key) == 2\n    assert conn._available_connections(other_host_key2) == 2\n    proto2 = create_mocked_conn()\n    connection2 = _acquired_connection(conn, proto2, key)\n    assert conn._available_connections(key) == 1\n    assert conn._available_connections(other_host_key2) == 1\n    connection1.close()\n    assert conn._available_connections(key) == 2\n    assert conn._available_connections(other_host_key2) == 2\n    connection2.close()\n    other_proto1 = create_mocked_conn()\n    other_connection1 = _acquired_connection(conn, other_proto1, other_host_key2)\n    assert conn._available_connections(key) == 2\n    assert conn._available_connections(other_host_key2) == 2\n    other_connection1.close()\n    assert conn._available_connections(key) == 3\n    assert conn._available_connections(other_host_key2) == 3\n\n\nasync def test_available_connections_no_limits(\n    key: ConnectionKey, other_host_key2: ConnectionKey\n) -> None:\n    \"\"\"Verify expected values based on active connections with no limits.\"\"\"\n    # No limits is a special case where available connections should always be 1.\n    conn = aiohttp.BaseConnector(limit=0, limit_per_host=0)\n    assert conn._available_connections(key) == 1\n    assert conn._available_connections(other_host_key2) == 1\n    proto1 = create_mocked_conn()\n    connection1 = _acquired_connection(conn, proto1, key)\n    assert conn._available_connections(key) == 1\n    assert conn._available_connections(other_host_key2) == 1\n    connection1.close()\n    assert conn._available_connections(key) == 1\n    assert conn._available_connections(other_host_key2) == 1\n\n\nasync def test_connect_tunnel_connection_release(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test _ConnectTunnelConnection.release() does not pool the connection.\"\"\"\n    connector = mock.create_autospec(\n        aiohttp.BaseConnector, spec_set=True, instance=True\n    )\n    key = mock.create_autospec(ConnectionKey, spec_set=True, instance=True)\n    protocol = mock.create_autospec(ResponseHandler, spec_set=True, instance=True)\n\n    # Create a connect tunnel connection\n    conn = _ConnectTunnelConnection(connector, key, protocol, loop)\n\n    # Verify protocol is set\n    assert conn._protocol is protocol\n\n    # Release should do nothing (not pool the connection)\n    conn.release()\n\n    # Protocol should still be there (not released to pool)\n    assert conn._protocol is protocol\n    # Connector._release should NOT have been called\n    connector._release.assert_not_called()\n\n    # Clean up to avoid resource warning\n    conn.close()\n"
  },
  {
    "path": "tests/test_cookie_helpers.py",
    "content": "\"\"\"Tests for internal cookie helper functions.\"\"\"\n\nimport logging\nimport sys\nimport time\nfrom http.cookies import (\n    CookieError,\n    Morsel,\n    SimpleCookie,\n    _unquote as simplecookie_unquote,\n)\n\nimport pytest\n\nfrom aiohttp import _cookie_helpers as helpers\nfrom aiohttp._cookie_helpers import (\n    _unquote,\n    parse_cookie_header,\n    parse_set_cookie_headers,\n    preserve_morsel_with_coded_value,\n)\n\n\ndef test_known_attrs_is_superset_of_morsel_reserved() -> None:\n    \"\"\"Test that _COOKIE_KNOWN_ATTRS contains all Morsel._reserved attributes.\"\"\"\n    # Get Morsel._reserved attributes (lowercase)\n    morsel_reserved = {attr.lower() for attr in Morsel._reserved}  # type: ignore[attr-defined]\n\n    # _COOKIE_KNOWN_ATTRS should be a superset of morsel_reserved\n    assert (\n        helpers._COOKIE_KNOWN_ATTRS >= morsel_reserved\n    ), f\"_COOKIE_KNOWN_ATTRS is missing: {morsel_reserved - helpers._COOKIE_KNOWN_ATTRS}\"\n\n\ndef test_bool_attrs_is_superset_of_morsel_flags() -> None:\n    \"\"\"Test that _COOKIE_BOOL_ATTRS contains all Morsel._flags attributes.\"\"\"\n    # Get Morsel._flags attributes (lowercase)\n    morsel_flags = {attr.lower() for attr in Morsel._flags}  # type: ignore[attr-defined]\n\n    # _COOKIE_BOOL_ATTRS should be a superset of morsel_flags\n    assert (\n        helpers._COOKIE_BOOL_ATTRS >= morsel_flags\n    ), f\"_COOKIE_BOOL_ATTRS is missing: {morsel_flags - helpers._COOKIE_BOOL_ATTRS}\"\n\n\ndef test_preserve_morsel_with_coded_value() -> None:\n    \"\"\"Test preserve_morsel_with_coded_value preserves coded_value exactly.\"\"\"\n    # Create a cookie with a coded_value different from value\n    cookie: Morsel[str] = Morsel()\n    cookie.set(\"test_cookie\", \"decoded value\", \"encoded%20value\")\n\n    # Preserve the coded_value\n    result = preserve_morsel_with_coded_value(cookie)\n\n    # Check that all values are preserved\n    assert result.key == \"test_cookie\"\n    assert result.value == \"decoded value\"\n    assert result.coded_value == \"encoded%20value\"\n\n    # Should be a different Morsel instance\n    assert result is not cookie\n\n\ndef test_preserve_morsel_with_coded_value_no_coded_value() -> None:\n    \"\"\"Test preserve_morsel_with_coded_value when coded_value is same as value.\"\"\"\n    cookie: Morsel[str] = Morsel()\n    cookie.set(\"test_cookie\", \"simple_value\", \"simple_value\")\n\n    result = preserve_morsel_with_coded_value(cookie)\n\n    assert result.key == \"test_cookie\"\n    assert result.value == \"simple_value\"\n    assert result.coded_value == \"simple_value\"\n\n\ndef test_parse_set_cookie_headers_simple() -> None:\n    \"\"\"Test parse_set_cookie_headers with simple cookies.\"\"\"\n    headers = [\"name=value\", \"session=abc123\"]\n\n    result = parse_set_cookie_headers(headers)\n\n    assert len(result) == 2\n    assert result[0][0] == \"name\"\n    assert result[0][1].key == \"name\"\n    assert result[0][1].value == \"value\"\n    assert result[1][0] == \"session\"\n    assert result[1][1].key == \"session\"\n    assert result[1][1].value == \"abc123\"\n\n\ndef test_parse_set_cookie_headers_with_attributes() -> None:\n    \"\"\"Test parse_set_cookie_headers with cookie attributes.\"\"\"\n    headers = [\n        \"sessionid=value123; Path=/; HttpOnly; Secure\",\n        \"user=john; Domain=.example.com; Max-Age=3600\",\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    assert len(result) == 2\n\n    # First cookie\n    name1, morsel1 = result[0]\n    assert name1 == \"sessionid\"\n    assert morsel1.value == \"value123\"\n    assert morsel1[\"path\"] == \"/\"\n    assert morsel1[\"httponly\"] is True\n    assert morsel1[\"secure\"] is True\n\n    # Second cookie\n    name2, morsel2 = result[1]\n    assert name2 == \"user\"\n    assert morsel2.value == \"john\"\n    assert morsel2[\"domain\"] == \".example.com\"\n    assert morsel2[\"max-age\"] == \"3600\"\n\n\ndef test_parse_set_cookie_headers_special_chars_in_names() -> None:\n    \"\"\"Test parse_set_cookie_headers accepts special characters in names (#2683).\"\"\"\n    # These should be accepted with relaxed validation\n    headers = [\n        \"ISAWPLB{A7F52349-3531-4DA9-8776-F74BC6F4F1BB}=value1\",\n        \"cookie[index]=value2\",\n        \"cookie(param)=value3\",\n        \"cookie:name=value4\",\n        \"cookie@domain=value5\",\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    assert len(result) == 5\n    expected_names = [\n        \"ISAWPLB{A7F52349-3531-4DA9-8776-F74BC6F4F1BB}\",\n        \"cookie[index]\",\n        \"cookie(param)\",\n        \"cookie:name\",\n        \"cookie@domain\",\n    ]\n\n    for i, (name, morsel) in enumerate(result):\n        assert name == expected_names[i]\n        assert morsel.key == expected_names[i]\n        assert morsel.value == f\"value{i+1}\"\n\n\ndef test_parse_set_cookie_headers_invalid_names() -> None:\n    \"\"\"Test parse_set_cookie_headers rejects truly invalid cookie names.\"\"\"\n    # These should be rejected even with relaxed validation\n    headers = [\n        \"invalid\\tcookie=value\",  # Tab character\n        \"invalid\\ncookie=value\",  # Newline\n        \"invalid\\rcookie=value\",  # Carriage return\n        \"\\x00badname=value\",  # Null character\n        \"name with spaces=value\",  # Spaces in name\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    # All should be skipped\n    assert len(result) == 0\n\n\ndef test_parse_set_cookie_headers_empty_and_invalid() -> None:\n    \"\"\"Test parse_set_cookie_headers handles empty and invalid formats.\"\"\"\n    headers = [\n        \"\",  # Empty header\n        \"   \",  # Whitespace only\n        \"=value\",  # No name\n        \"name=\",  # Empty value (should be accepted)\n        \"justname\",  # No value (should be skipped)\n        \"path=/\",  # Reserved attribute as name (should be skipped)\n        \"Domain=.com\",  # Reserved attribute as name (should be skipped)\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    # Only \"name=\" should be accepted\n    assert len(result) == 1\n    assert result[0][0] == \"name\"\n    assert result[0][1].value == \"\"\n\n\ndef test_parse_set_cookie_headers_quoted_values() -> None:\n    \"\"\"Test parse_set_cookie_headers handles quoted values correctly.\"\"\"\n    headers = [\n        'name=\"quoted value\"',\n        'session=\"with;semicolon\"',\n        'data=\"with\\\\\"escaped\\\\\"\"',\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    assert len(result) == 3\n    assert result[0][1].value == \"quoted value\"\n    assert result[1][1].value == \"with;semicolon\"\n    assert result[2][1].value == 'with\"escaped\"'\n\n\n@pytest.mark.parametrize(\n    \"header\",\n    [\n        'session=\"abc;xyz\"; token=123',\n        'data=\"value;with;multiple;semicolons\"; next=cookie',\n        'complex=\"a=b;c=d\"; simple=value',\n    ],\n)\ndef test_parse_set_cookie_headers_semicolon_in_quoted_values(header: str) -> None:\n    \"\"\"\n    Test that semicolons inside properly quoted values are handled correctly.\n\n    Cookie values can contain semicolons when properly quoted. This test ensures\n    that our parser handles these cases correctly, matching SimpleCookie behavior.\n    \"\"\"\n    # Test with SimpleCookie\n    sc = SimpleCookie()\n    sc.load(header)\n\n    # Test with our parser\n    result = parse_set_cookie_headers([header])\n\n    # Should parse the same number of cookies\n    assert len(result) == len(sc)\n\n    # Verify each cookie matches SimpleCookie\n    for (name, morsel), (sc_name, sc_morsel) in zip(result, sc.items()):\n        assert name == sc_name\n        assert morsel.value == sc_morsel.value\n\n\ndef test_parse_set_cookie_headers_multiple_cookies_same_header() -> None:\n    \"\"\"Test parse_set_cookie_headers with multiple cookies in one header.\"\"\"\n    # Note: SimpleCookie includes the comma as part of the first cookie's value\n    headers = [\"cookie1=value1, cookie2=value2\"]\n\n    result = parse_set_cookie_headers(headers)\n\n    # Should parse as two separate cookies\n    assert len(result) == 2\n    assert result[0][0] == \"cookie1\"\n    assert result[0][1].value == \"value1,\"  # Comma is included in the value\n    assert result[1][0] == \"cookie2\"\n    assert result[1][1].value == \"value2\"\n\n\n@pytest.mark.parametrize(\n    \"header\",\n    [\n        # Standard cookies\n        \"session=abc123\",\n        \"user=john; Path=/\",\n        \"token=xyz; Secure; HttpOnly\",\n        # Empty values\n        \"empty=\",\n        # Quoted values\n        'quoted=\"value with spaces\"',\n        # Multiple attributes\n        \"complex=value; Domain=.example.com; Path=/app; Max-Age=3600\",\n    ],\n)\ndef test_parse_set_cookie_headers_compatibility_with_simple_cookie(header: str) -> None:\n    \"\"\"Test parse_set_cookie_headers is bug-for-bug compatible with SimpleCookie.load.\"\"\"\n    # Parse with SimpleCookie\n    sc = SimpleCookie()\n    sc.load(header)\n\n    # Parse with our function\n    result = parse_set_cookie_headers([header])\n\n    # Should have same number of cookies\n    assert len(result) == len(sc)\n\n    # Compare each cookie\n    for name, morsel in result:\n        assert name in sc\n        sc_morsel = sc[name]\n\n        # Compare values\n        assert morsel.value == sc_morsel.value\n        assert morsel.key == sc_morsel.key\n\n        # Compare attributes (only those that SimpleCookie would set)\n        for attr in [\"path\", \"domain\", \"max-age\"]:\n            assert morsel.get(attr) == sc_morsel.get(attr)\n\n        # Boolean attributes are handled differently\n        # SimpleCookie sets them to empty string when not present, True when present\n        for bool_attr in [\"secure\", \"httponly\"]:\n            # Only check if SimpleCookie has the attribute set to True\n            if sc_morsel.get(bool_attr) is True:\n                assert morsel.get(bool_attr) is True\n\n\ndef test_parse_set_cookie_headers_relaxed_validation_differences() -> None:\n    \"\"\"Test where parse_set_cookie_headers differs from SimpleCookie (relaxed validation).\"\"\"\n    # Test cookies that SimpleCookie rejects with CookieError\n    rejected_by_simplecookie = [\n        (\"cookie{with}braces=value1\", \"cookie{with}braces\", \"value1\"),\n        (\"cookie(with)parens=value3\", \"cookie(with)parens\", \"value3\"),\n        (\"cookie@with@at=value5\", \"cookie@with@at\", \"value5\"),\n    ]\n\n    for header, expected_name, expected_value in rejected_by_simplecookie:\n        # SimpleCookie should reject these with CookieError\n        sc = SimpleCookie()\n        with pytest.raises(CookieError):\n            sc.load(header)\n\n        # Our parser should accept them\n        result = parse_set_cookie_headers([header])\n        assert len(result) == 1  # We accept\n        assert result[0][0] == expected_name\n        assert result[0][1].value == expected_value\n\n    # Test cookies that SimpleCookie accepts (but we handle more consistently)\n    accepted_by_simplecookie = [\n        (\"cookie[with]brackets=value2\", \"cookie[with]brackets\", \"value2\"),\n        (\"cookie:with:colons=value4\", \"cookie:with:colons\", \"value4\"),\n    ]\n\n    for header, expected_name, expected_value in accepted_by_simplecookie:\n        # SimpleCookie accepts these\n        sc = SimpleCookie()\n        sc.load(header)\n        # May or may not parse correctly in SimpleCookie\n\n        # Our parser should accept them consistently\n        result = parse_set_cookie_headers([header])\n        assert len(result) == 1\n        assert result[0][0] == expected_name\n        assert result[0][1].value == expected_value\n\n\ndef test_parse_set_cookie_headers_case_insensitive_attrs() -> None:\n    \"\"\"Test that known attributes are handled case-insensitively.\"\"\"\n    headers = [\n        \"cookie1=value1; PATH=/test; DOMAIN=example.com\",\n        \"cookie2=value2; Secure; HTTPONLY; max-AGE=60\",\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    assert len(result) == 2\n\n    # First cookie - attributes should be recognized despite case\n    assert result[0][1][\"path\"] == \"/test\"\n    assert result[0][1][\"domain\"] == \"example.com\"\n\n    # Second cookie\n    assert result[1][1][\"secure\"] is True\n    assert result[1][1][\"httponly\"] is True\n    assert result[1][1][\"max-age\"] == \"60\"\n\n\ndef test_parse_set_cookie_headers_unknown_attrs_ignored() -> None:\n    \"\"\"Test that unknown attributes are treated as new cookies (same as SimpleCookie).\"\"\"\n    headers = [\n        \"cookie=value; Path=/; unknownattr=ignored; HttpOnly\",\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    # SimpleCookie treats unknown attributes with values as new cookies\n    assert len(result) == 2\n\n    # First cookie\n    assert result[0][0] == \"cookie\"\n    assert result[0][1][\"path\"] == \"/\"\n    assert result[0][1][\"httponly\"] == \"\"  # Not set on first cookie\n\n    # Second cookie (the unknown attribute)\n    assert result[1][0] == \"unknownattr\"\n    assert result[1][1].value == \"ignored\"\n    assert result[1][1][\"httponly\"] is True  # HttpOnly applies to this cookie\n\n\ndef test_parse_set_cookie_headers_complex_real_world() -> None:\n    \"\"\"Test parse_set_cookie_headers with complex real-world examples.\"\"\"\n    headers = [\n        # AWS ELB cookie\n        \"AWSELB=ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890; Path=/\",\n        # Google Analytics\n        \"_ga=GA1.2.1234567890.1234567890; Domain=.example.com; Path=/; Expires=Thu, 31-Dec-2025 23:59:59 GMT\",\n        # Session with all attributes\n        \"session_id=s%3AabcXYZ123.signature123; Path=/; Secure; HttpOnly; SameSite=Strict\",\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    assert len(result) == 3\n\n    # Check each cookie parsed correctly\n    assert result[0][0] == \"AWSELB\"\n    assert result[1][0] == \"_ga\"\n    assert result[2][0] == \"session_id\"\n\n    # Session cookie should have all attributes\n    session_morsel = result[2][1]\n    assert session_morsel[\"secure\"] is True\n    assert session_morsel[\"httponly\"] is True\n    assert session_morsel.get(\"samesite\") == \"Strict\"\n\n\ndef test_parse_set_cookie_headers_boolean_attrs() -> None:\n    \"\"\"Test that boolean attributes (secure, httponly) work correctly.\"\"\"\n    # Test secure attribute variations\n    headers = [\n        \"cookie1=value1; Secure\",\n        \"cookie2=value2; Secure=\",\n        \"cookie3=value3; Secure=true\",  # Non-standard but might occur\n    ]\n\n    result = parse_set_cookie_headers(headers)\n    assert len(result) == 3\n\n    # All should have secure=True\n    for name, morsel in result:\n        assert morsel.get(\"secure\") is True, f\"{name} should have secure=True\"\n\n    # Test httponly attribute variations\n    headers = [\n        \"cookie4=value4; HttpOnly\",\n        \"cookie5=value5; HttpOnly=\",\n    ]\n\n    result = parse_set_cookie_headers(headers)\n    assert len(result) == 2\n\n    # All should have httponly=True\n    for name, morsel in result:\n        assert morsel.get(\"httponly\") is True, f\"{name} should have httponly=True\"\n\n\n@pytest.mark.skipif(\n    sys.version_info < (3, 14),\n    reason=\"Partitioned cookies support requires Python 3.14+\",\n)\ndef test_parse_set_cookie_headers_boolean_attrs_with_partitioned() -> None:\n    \"\"\"Test that boolean attributes including partitioned work correctly.\"\"\"\n    # Test secure attribute variations\n    secure_headers = [\n        \"cookie1=value1; Secure\",\n        \"cookie2=value2; Secure=\",\n        \"cookie3=value3; Secure=true\",  # Non-standard but might occur\n    ]\n\n    result = parse_set_cookie_headers(secure_headers)\n    assert len(result) == 3\n    for name, morsel in result:\n        assert morsel.get(\"secure\") is True, f\"{name} should have secure=True\"\n\n    # Test httponly attribute variations\n    httponly_headers = [\n        \"cookie4=value4; HttpOnly\",\n        \"cookie5=value5; HttpOnly=\",\n    ]\n\n    result = parse_set_cookie_headers(httponly_headers)\n    assert len(result) == 2\n    for name, morsel in result:\n        assert morsel.get(\"httponly\") is True, f\"{name} should have httponly=True\"\n\n    # Test partitioned attribute variations\n    partitioned_headers = [\n        \"cookie6=value6; Partitioned\",\n        \"cookie7=value7; Partitioned=\",\n        \"cookie8=value8; Partitioned=yes\",  # Non-standard but might occur\n    ]\n\n    result = parse_set_cookie_headers(partitioned_headers)\n    assert len(result) == 3\n    for name, morsel in result:\n        assert morsel.get(\"partitioned\") is True, f\"{name} should have partitioned=True\"\n\n\ndef test_parse_set_cookie_headers_encoded_values() -> None:\n    \"\"\"Test that parse_set_cookie_headers preserves encoded values.\"\"\"\n    headers = [\n        \"encoded=hello%20world\",\n        \"url=https%3A%2F%2Fexample.com%2Fpath\",\n        \"special=%21%40%23%24%25%5E%26*%28%29\",\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    assert len(result) == 3\n    # Values should be preserved as-is (not decoded)\n    assert result[0][1].value == \"hello%20world\"\n    assert result[1][1].value == \"https%3A%2F%2Fexample.com%2Fpath\"\n    assert result[2][1].value == \"%21%40%23%24%25%5E%26*%28%29\"\n\n\n@pytest.mark.skipif(\n    sys.version_info < (3, 14),\n    reason=\"Partitioned cookies support requires Python 3.14+\",\n)\ndef test_parse_set_cookie_headers_partitioned() -> None:\n    \"\"\"\n    Test that parse_set_cookie_headers handles partitioned attribute correctly.\n\n    This tests the fix for issue #10380 - partitioned cookies support.\n    The partitioned attribute is a boolean flag like secure and httponly.\n    \"\"\"\n    headers = [\n        \"cookie1=value1; Partitioned\",\n        \"cookie2=value2; Partitioned=\",\n        \"cookie3=value3; Partitioned=true\",  # Non-standard but might occur\n        \"cookie4=value4; Secure; Partitioned; HttpOnly\",\n        \"cookie5=value5; Domain=.example.com; Path=/; Partitioned\",\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    assert len(result) == 5\n\n    # All cookies should have partitioned=True\n    for i, (name, morsel) in enumerate(result):\n        assert (\n            morsel.get(\"partitioned\") is True\n        ), f\"Cookie {i+1} should have partitioned=True\"\n        assert name == f\"cookie{i+1}\"\n        assert morsel.value == f\"value{i+1}\"\n\n    # Cookie 4 should also have secure and httponly\n    assert result[3][1].get(\"secure\") is True\n    assert result[3][1].get(\"httponly\") is True\n\n    # Cookie 5 should also have domain and path\n    assert result[4][1].get(\"domain\") == \".example.com\"\n    assert result[4][1].get(\"path\") == \"/\"\n\n\n@pytest.mark.skipif(\n    sys.version_info < (3, 14),\n    reason=\"Partitioned cookies support requires Python 3.14+\",\n)\ndef test_parse_set_cookie_headers_partitioned_case_insensitive() -> None:\n    \"\"\"Test that partitioned attribute is recognized case-insensitively.\"\"\"\n    headers = [\n        \"cookie1=value1; partitioned\",  # lowercase\n        \"cookie2=value2; PARTITIONED\",  # uppercase\n        \"cookie3=value3; Partitioned\",  # title case\n        \"cookie4=value4; PaRtItIoNeD\",  # mixed case\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    assert len(result) == 4\n\n    # All should be recognized as partitioned\n    for i, (_, morsel) in enumerate(result):\n        assert (\n            morsel.get(\"partitioned\") is True\n        ), f\"Cookie {i+1} should have partitioned=True\"\n\n\ndef test_parse_set_cookie_headers_partitioned_not_set() -> None:\n    \"\"\"Test that cookies without partitioned attribute don't have it set.\"\"\"\n    headers = [\n        \"normal=value; Secure; HttpOnly\",\n        \"regular=cookie; Path=/\",\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    assert len(result) == 2\n\n    # Check that partitioned is not set (empty string is the default for flags in Morsel)\n    assert result[0][1].get(\"partitioned\", \"\") == \"\"\n    assert result[1][1].get(\"partitioned\", \"\") == \"\"\n\n\n# Tests that don't require partitioned support in SimpleCookie\n@pytest.mark.skipif(\n    sys.version_info >= (3, 14),\n    reason=\"Python 3.14+ has built-in partitioned cookie support\",\n)\ndef test_parse_set_cookie_headers_partitioned_not_set_if_no_support() -> None:\n    headers = [\n        \"cookie1=value1; Partitioned\",\n        \"cookie2=value2; Partitioned=\",\n        \"cookie3=value3; Partitioned=true\",\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    assert len(result) == 3\n    for i, (_, morsel) in enumerate(result):\n        assert (\n            morsel.get(\"partitioned\") is None\n        ), f\"Cookie {i+1} should not have partitioned flag\"\n\n\ndef test_parse_set_cookie_headers_partitioned_with_other_attrs_manual() -> None:\n    \"\"\"\n    Test parsing logic for partitioned cookies combined with all other attributes.\n\n    This test verifies our parsing logic handles partitioned correctly as a boolean\n    attribute regardless of SimpleCookie support.\n    \"\"\"\n    # Test that our parser recognizes partitioned in _COOKIE_KNOWN_ATTRS and _COOKIE_BOOL_ATTRS\n    assert \"partitioned\" in helpers._COOKIE_KNOWN_ATTRS\n    assert \"partitioned\" in helpers._COOKIE_BOOL_ATTRS\n\n    # Test a simple case that won't trigger SimpleCookie errors\n    headers = [\"session=abc123; Secure; HttpOnly\"]\n    result = parse_set_cookie_headers(headers)\n\n    assert len(result) == 1\n    assert result[0][0] == \"session\"\n    assert result[0][1][\"secure\"] is True\n    assert result[0][1][\"httponly\"] is True\n\n\ndef test_cookie_helpers_constants_include_partitioned() -> None:\n    \"\"\"Test that cookie helper constants include partitioned attribute.\"\"\"\n    # Test our constants include partitioned\n    assert \"partitioned\" in helpers._COOKIE_KNOWN_ATTRS\n    assert \"partitioned\" in helpers._COOKIE_BOOL_ATTRS\n\n\n@pytest.mark.parametrize(\n    \"test_string\",\n    [\n        \" Partitioned \",\n        \" partitioned \",\n        \" PARTITIONED \",\n        \" Partitioned; \",\n        \" Partitioned= \",\n        \" Partitioned=true \",\n    ],\n)\ndef test_cookie_pattern_matches_partitioned_attribute(test_string: str) -> None:\n    \"\"\"Test that the cookie pattern regex matches various partitioned attribute formats.\"\"\"\n    pattern = helpers._COOKIE_PATTERN\n    match = pattern.match(test_string)\n    assert match is not None, f\"Pattern should match '{test_string}'\"\n    assert match.group(\"key\").lower() == \"partitioned\"\n\n\ndef test_cookie_pattern_performance() -> None:\n    \"\"\"Test that the cookie pattern doesn't suffer from ReDoS issues.\"\"\"\n    COOKIE_PATTERN_TIME_THRESHOLD_SECONDS = 0.08\n    value = \"a\" + \"=\" * 21651 + \"\\x00\"\n    start = time.perf_counter()\n    match = helpers._COOKIE_PATTERN.match(value)\n    elapsed = time.perf_counter() - start\n\n    # If this is taking more time, there's probably a performance/ReDoS issue.\n    assert elapsed < COOKIE_PATTERN_TIME_THRESHOLD_SECONDS, (\n        f\"Pattern took {elapsed * 1000:.1f}ms, \"\n        f\"expected <{COOKIE_PATTERN_TIME_THRESHOLD_SECONDS * 1000:.0f}ms - potential ReDoS issue\"\n    )\n    # This example shouldn't produce a match either.\n    assert match is None\n\n\ndef test_parse_set_cookie_headers_issue_7993_double_quotes() -> None:\n    \"\"\"\n    Test that cookies with unmatched opening quotes don't break parsing of subsequent cookies.\n\n    This reproduces issue #7993 where a cookie containing an unmatched opening double quote\n    causes subsequent cookies to be silently dropped.\n    NOTE: This only fixes the specific case where a value starts with a quote but doesn't\n    end with one (e.g., 'cookie=\"value'). Other malformed quote cases still behave like\n    SimpleCookie for compatibility.\n    \"\"\"\n    # Test case from the issue\n    headers = ['foo=bar; baz=\"qux; foo2=bar2']\n\n    result = parse_set_cookie_headers(headers)\n\n    # Should parse all cookies correctly\n    assert len(result) == 3\n    assert result[0][0] == \"foo\"\n    assert result[0][1].value == \"bar\"\n    assert result[1][0] == \"baz\"\n    assert result[1][1].value == '\"qux'  # Unmatched quote included\n    assert result[2][0] == \"foo2\"\n    assert result[2][1].value == \"bar2\"\n\n\ndef test_parse_set_cookie_headers_empty_headers() -> None:\n    \"\"\"Test handling of empty headers in the sequence.\"\"\"\n    # Empty header should be skipped\n    result = parse_set_cookie_headers([\"\", \"name=value\"])\n    assert len(result) == 1\n    assert result[0][0] == \"name\"\n    assert result[0][1].value == \"value\"\n\n    # Multiple empty headers\n    result = parse_set_cookie_headers([\"\", \"\", \"\"])\n    assert result == []\n\n    # Empty headers mixed with valid cookies\n    result = parse_set_cookie_headers([\"\", \"a=1\", \"\", \"b=2\", \"\"])\n    assert len(result) == 2\n    assert result[0][0] == \"a\"\n    assert result[1][0] == \"b\"\n\n\ndef test_parse_set_cookie_headers_invalid_cookie_syntax() -> None:\n    \"\"\"Test handling of invalid cookie syntax.\"\"\"\n    # No valid cookie pattern\n    result = parse_set_cookie_headers([\"@#$%^&*()\"])\n    assert result == []\n\n    # Cookie name without value\n    result = parse_set_cookie_headers([\"name\"])\n    assert result == []\n\n    # Multiple invalid patterns\n    result = parse_set_cookie_headers([\"!!!!\", \"????\", \"name\", \"@@@\"])\n    assert result == []\n\n\ndef test_parse_set_cookie_headers_illegal_cookie_names(\n    caplog: pytest.LogCaptureFixture,\n) -> None:\n    \"\"\"\n    Test that illegal cookie names are rejected.\n\n    Note: When a known attribute name is used as a cookie name at the start,\n    parsing stops early (before any warning can be logged). Warnings are only\n    logged when illegal names appear after a valid cookie.\n    \"\"\"\n    # Cookie name that is a known attribute (illegal) - parsing stops early\n    result = parse_set_cookie_headers([\"path=value; domain=test\"])\n    assert result == []\n\n    # Cookie name that doesn't match the pattern\n    result = parse_set_cookie_headers([\"=value\"])\n    assert result == []\n\n    # Valid cookie after illegal one - parsing stops at illegal\n    result = parse_set_cookie_headers([\"domain=bad; good=value\"])\n    assert result == []\n\n    # Illegal cookie name that appears after a valid cookie triggers warning\n    result = parse_set_cookie_headers([\"good=value; Path=/; invalid,cookie=value;\"])\n    assert len(result) == 1\n    assert result[0][0] == \"good\"\n    assert \"Illegal cookie name 'invalid,cookie'\" in caplog.text\n\n\ndef test_parse_set_cookie_headers_attributes_before_cookie() -> None:\n    \"\"\"Test that attributes before any cookie are invalid.\"\"\"\n    # Path attribute before cookie\n    result = parse_set_cookie_headers([\"Path=/; name=value\"])\n    assert result == []\n\n    # Domain attribute before cookie\n    result = parse_set_cookie_headers([\"Domain=.example.com; name=value\"])\n    assert result == []\n\n    # Multiple attributes before cookie\n    result = parse_set_cookie_headers(\n        [\"Path=/; Domain=.example.com; Secure; name=value\"]\n    )\n    assert result == []\n\n\ndef test_parse_set_cookie_headers_attributes_without_values() -> None:\n    \"\"\"Test handling of attributes with missing values.\"\"\"\n    # Boolean attribute without value (valid)\n    result = parse_set_cookie_headers([\"name=value; Secure\"])\n    assert len(result) == 1\n    assert result[0][1][\"secure\"] is True\n\n    # Non-boolean attribute without value (invalid, stops parsing)\n    result = parse_set_cookie_headers([\"name=value; Path\"])\n    assert len(result) == 1\n    # Path without value stops further attribute parsing\n\n    # Multiple cookies, invalid attribute in middle\n    result = parse_set_cookie_headers([\"name=value; Path; Secure\"])\n    assert len(result) == 1\n    # Secure is not parsed because Path without value stops parsing\n\n\ndef test_parse_set_cookie_headers_dollar_prefixed_names() -> None:\n    \"\"\"Test handling of cookie names starting with $.\"\"\"\n    # $Version without preceding cookie (ignored)\n    result = parse_set_cookie_headers([\"$Version=1; name=value\"])\n    assert len(result) == 1\n    assert result[0][0] == \"name\"\n\n    # Multiple $ prefixed without cookie (all ignored)\n    result = parse_set_cookie_headers([\"$Version=1; $Path=/; $Domain=.com; name=value\"])\n    assert len(result) == 1\n    assert result[0][0] == \"name\"\n\n    # $ prefix at start is ignored, cookie follows\n    result = parse_set_cookie_headers([\"$Unknown=123; valid=cookie\"])\n    assert len(result) == 1\n    assert result[0][0] == \"valid\"\n\n\ndef test_parse_set_cookie_headers_dollar_attributes() -> None:\n    \"\"\"Test handling of $ prefixed attributes after cookies.\"\"\"\n    # Test multiple $ attributes with cookie (case-insensitive like SimpleCookie)\n    result = parse_set_cookie_headers([\"name=value; $Path=/test; $Domain=.example.com\"])\n    assert len(result) == 1\n    assert result[0][0] == \"name\"\n    assert result[0][1][\"path\"] == \"/test\"\n    assert result[0][1][\"domain\"] == \".example.com\"\n\n    # Test unknown $ attribute (should be ignored)\n    result = parse_set_cookie_headers([\"name=value; $Unknown=test\"])\n    assert len(result) == 1\n    assert result[0][0] == \"name\"\n    # $Unknown should not be set\n\n    # Test $ attribute with empty value\n    result = parse_set_cookie_headers([\"name=value; $Path=\"])\n    assert len(result) == 1\n    assert result[0][1][\"path\"] == \"\"\n\n    # Test case sensitivity compatibility with SimpleCookie\n    result = parse_set_cookie_headers([\"test=value; $path=/lower; $PATH=/upper\"])\n    assert len(result) == 1\n    # Last one wins, and it's case-insensitive\n    assert result[0][1][\"path\"] == \"/upper\"\n\n\ndef test_parse_set_cookie_headers_attributes_after_illegal_cookie() -> None:\n    \"\"\"\n    Test that attributes after an illegal cookie name are handled correctly.\n\n    This covers the branches where current_morsel is None because an illegal\n    cookie name was encountered.\n    \"\"\"\n    # Illegal cookie followed by $ attribute\n    result = parse_set_cookie_headers([\"good=value; invalid,cookie=bad; $Path=/test\"])\n    assert len(result) == 1\n    assert result[0][0] == \"good\"\n    # $Path should be ignored since current_morsel is None after illegal cookie\n\n    # Illegal cookie followed by boolean attribute\n    result = parse_set_cookie_headers([\"good=value; invalid,cookie=bad; HttpOnly\"])\n    assert len(result) == 1\n    assert result[0][0] == \"good\"\n    # HttpOnly should be ignored since current_morsel is None\n\n    # Illegal cookie followed by regular attribute with value\n    result = parse_set_cookie_headers([\"good=value; invalid,cookie=bad; Max-Age=3600\"])\n    assert len(result) == 1\n    assert result[0][0] == \"good\"\n    # Max-Age should be ignored since current_morsel is None\n\n    # Multiple attributes after illegal cookie\n    result = parse_set_cookie_headers(\n        [\"good=value; invalid,cookie=bad; $Path=/; HttpOnly; Max-Age=60; Domain=.com\"]\n    )\n    assert len(result) == 1\n    assert result[0][0] == \"good\"\n    # All attributes should be ignored after illegal cookie\n\n\ndef test_parse_set_cookie_headers_unmatched_quotes_compatibility() -> None:\n    \"\"\"\n    Test that most unmatched quote scenarios behave like SimpleCookie.\n\n    For compatibility, we only handle the specific case of unmatched opening quotes\n    (e.g., 'cookie=\"value'). Other cases behave the same as SimpleCookie.\n    \"\"\"\n    # Cases that SimpleCookie and our parser both fail to parse completely\n    incompatible_cases = [\n        'cookie1=val\"ue; cookie2=value2',  # codespell:ignore\n        'cookie1=value\"; cookie2=value2',\n        'cookie1=va\"l\"ue\"; cookie2=value2',  # codespell:ignore\n        'cookie1=value1; cookie2=val\"ue; cookie3=value3',  # codespell:ignore\n    ]\n\n    for header in incompatible_cases:\n        # Test SimpleCookie behavior\n        sc = SimpleCookie()\n        sc.load(header)\n        sc_cookies = list(sc.items())\n\n        # Test our parser behavior\n        result = parse_set_cookie_headers([header])\n\n        # Both should parse the same cookies (partial parsing)\n        assert len(result) == len(sc_cookies), (\n            f\"Header: {header}\\n\"\n            f\"SimpleCookie parsed: {len(sc_cookies)} cookies\\n\"\n            f\"Our parser parsed: {len(result)} cookies\"\n        )\n\n    # The case we specifically fix (unmatched opening quote)\n    fixed_case = 'cookie1=value1; cookie2=\"unmatched; cookie3=value3'\n\n    # SimpleCookie fails to parse cookie3\n    sc = SimpleCookie()\n    sc.load(fixed_case)\n    assert len(sc) == 1  # Only cookie1\n\n    # Our parser handles it better\n    result = parse_set_cookie_headers([fixed_case])\n    assert len(result) == 3  # All three cookies\n    assert result[0][0] == \"cookie1\"\n    assert result[0][1].value == \"value1\"\n    assert result[1][0] == \"cookie2\"\n    assert result[1][1].value == '\"unmatched'\n    assert result[2][0] == \"cookie3\"\n    assert result[2][1].value == \"value3\"\n\n\ndef test_parse_set_cookie_headers_expires_attribute() -> None:\n    \"\"\"Test parse_set_cookie_headers handles expires attribute with date formats.\"\"\"\n    headers = [\n        \"session=abc; Expires=Wed, 09 Jun 2021 10:18:14 GMT\",\n        \"user=xyz; expires=Wednesday, 09-Jun-21 10:18:14 GMT\",\n        \"token=123; EXPIRES=Wed, 09 Jun 2021 10:18:14 GMT\",\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    assert len(result) == 3\n    for _, morsel in result:\n        assert \"expires\" in morsel\n        assert \"GMT\" in morsel[\"expires\"]\n\n\ndef test_parse_set_cookie_headers_edge_cases() -> None:\n    \"\"\"Test various edge cases.\"\"\"\n    # Very long cookie values\n    long_value = \"x\" * 4096\n    result = parse_set_cookie_headers([f\"name={long_value}\"])\n    assert len(result) == 1\n    assert result[0][1].value == long_value\n\n\ndef test_parse_set_cookie_headers_various_date_formats_issue_4327() -> None:\n    \"\"\"\n    Test that parse_set_cookie_headers handles various date formats per RFC 6265.\n\n    This tests the fix for issue #4327 - support for RFC 822, RFC 850,\n    and ANSI C asctime() date formats in cookie expiration.\n    \"\"\"\n    # Test various date formats\n    headers = [\n        # RFC 822 format (preferred format)\n        \"cookie1=value1; Expires=Wed, 09 Jun 2021 10:18:14 GMT\",\n        # RFC 850 format (obsolete but still used)\n        \"cookie2=value2; Expires=Wednesday, 09-Jun-21 10:18:14 GMT\",\n        # RFC 822 with dashes\n        \"cookie3=value3; Expires=Wed, 09-Jun-2021 10:18:14 GMT\",\n        # ANSI C asctime() format (aiohttp extension - not supported by SimpleCookie)\n        \"cookie4=value4; Expires=Wed Jun  9 10:18:14 2021\",\n        # Various other formats seen in the wild\n        \"cookie5=value5; Expires=Thu, 01 Jan 2030 00:00:00 GMT\",\n        \"cookie6=value6; Expires=Mon, 31-Dec-99 23:59:59 GMT\",\n        \"cookie7=value7; Expires=Tue, 01-Jan-30 00:00:00 GMT\",\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    # All cookies should be parsed\n    assert len(result) == 7\n\n    # Check each cookie was parsed with its expires attribute\n    expected_cookies = [\n        (\"cookie1\", \"value1\", \"Wed, 09 Jun 2021 10:18:14 GMT\"),\n        (\"cookie2\", \"value2\", \"Wednesday, 09-Jun-21 10:18:14 GMT\"),\n        (\"cookie3\", \"value3\", \"Wed, 09-Jun-2021 10:18:14 GMT\"),\n        (\"cookie4\", \"value4\", \"Wed Jun  9 10:18:14 2021\"),\n        (\"cookie5\", \"value5\", \"Thu, 01 Jan 2030 00:00:00 GMT\"),\n        (\"cookie6\", \"value6\", \"Mon, 31-Dec-99 23:59:59 GMT\"),\n        (\"cookie7\", \"value7\", \"Tue, 01-Jan-30 00:00:00 GMT\"),\n    ]\n\n    for (name, morsel), (exp_name, exp_value, exp_expires) in zip(\n        result, expected_cookies\n    ):\n        assert name == exp_name\n        assert morsel.value == exp_value\n        assert morsel.get(\"expires\") == exp_expires\n\n\ndef test_parse_set_cookie_headers_ansi_c_asctime_format() -> None:\n    \"\"\"\n    Test parsing of ANSI C asctime() format.\n\n    This tests support for ANSI C asctime() format (e.g., \"Wed Jun  9 10:18:14 2021\").\n    NOTE: This is an aiohttp extension - SimpleCookie does NOT support this format.\n    \"\"\"\n    headers = [\"cookie1=value1; Expires=Wed Jun  9 10:18:14 2021\"]\n\n    result = parse_set_cookie_headers(headers)\n\n    # Should parse correctly with the expires attribute preserved\n    assert len(result) == 1\n    assert result[0][0] == \"cookie1\"\n    assert result[0][1].value == \"value1\"\n    assert result[0][1][\"expires\"] == \"Wed Jun  9 10:18:14 2021\"\n\n\ndef test_parse_set_cookie_headers_rfc2822_timezone_issue_4493() -> None:\n    \"\"\"\n    Test that parse_set_cookie_headers handles RFC 2822 timezone formats.\n\n    This tests the fix for issue #4493 - support for RFC 2822-compliant dates\n    with timezone offsets like -0000, +0100, etc.\n    NOTE: This is an aiohttp extension - SimpleCookie does NOT support this format.\n    \"\"\"\n    headers = [\n        # RFC 2822 with -0000 timezone (common in some APIs)\n        \"hello=world; expires=Wed, 15 Jan 2020 09:45:07 -0000\",\n        # RFC 2822 with positive offset\n        \"session=abc123; expires=Thu, 01 Feb 2024 14:30:00 +0100\",\n        # RFC 2822 with negative offset\n        \"token=xyz789; expires=Fri, 02 Mar 2025 08:15:30 -0500\",\n        # Standard GMT for comparison\n        \"classic=cookie; expires=Sat, 03 Apr 2026 12:00:00 GMT\",\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    # All cookies should be parsed\n    assert len(result) == 4\n\n    # Check each cookie was parsed with its expires attribute\n    assert result[0][0] == \"hello\"\n    assert result[0][1].value == \"world\"\n    assert result[0][1][\"expires\"] == \"Wed, 15 Jan 2020 09:45:07 -0000\"\n\n    assert result[1][0] == \"session\"\n    assert result[1][1].value == \"abc123\"\n    assert result[1][1][\"expires\"] == \"Thu, 01 Feb 2024 14:30:00 +0100\"\n\n    assert result[2][0] == \"token\"\n    assert result[2][1].value == \"xyz789\"\n    assert result[2][1][\"expires\"] == \"Fri, 02 Mar 2025 08:15:30 -0500\"\n\n    assert result[3][0] == \"classic\"\n    assert result[3][1].value == \"cookie\"\n    assert result[3][1][\"expires\"] == \"Sat, 03 Apr 2026 12:00:00 GMT\"\n\n\ndef test_parse_set_cookie_headers_rfc2822_with_attributes() -> None:\n    \"\"\"Test that RFC 2822 dates work correctly with other cookie attributes.\"\"\"\n    headers = [\n        \"session=abc123; expires=Wed, 15 Jan 2020 09:45:07 -0000; Path=/; HttpOnly; Secure\",\n        \"token=xyz789; expires=Thu, 01 Feb 2024 14:30:00 +0100; Domain=.example.com; SameSite=Strict\",\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    assert len(result) == 2\n\n    # First cookie\n    assert result[0][0] == \"session\"\n    assert result[0][1].value == \"abc123\"\n    assert result[0][1][\"expires\"] == \"Wed, 15 Jan 2020 09:45:07 -0000\"\n    assert result[0][1][\"path\"] == \"/\"\n    assert result[0][1][\"httponly\"] is True\n    assert result[0][1][\"secure\"] is True\n\n    # Second cookie\n    assert result[1][0] == \"token\"\n    assert result[1][1].value == \"xyz789\"\n    assert result[1][1][\"expires\"] == \"Thu, 01 Feb 2024 14:30:00 +0100\"\n    assert result[1][1][\"domain\"] == \".example.com\"\n    assert result[1][1][\"samesite\"] == \"Strict\"\n\n\ndef test_parse_set_cookie_headers_date_formats_with_attributes() -> None:\n    \"\"\"Test that date formats work correctly with other cookie attributes.\"\"\"\n    headers = [\n        \"session=abc123; Expires=Wed, 09 Jun 2030 10:18:14 GMT; Path=/; HttpOnly; Secure\",\n        \"token=xyz789; Expires=Wednesday, 09-Jun-30 10:18:14 GMT; Domain=.example.com; SameSite=Strict\",\n    ]\n\n    result = parse_set_cookie_headers(headers)\n\n    assert len(result) == 2\n\n    # First cookie\n    assert result[0][0] == \"session\"\n    assert result[0][1].value == \"abc123\"\n    assert result[0][1][\"expires\"] == \"Wed, 09 Jun 2030 10:18:14 GMT\"\n    assert result[0][1][\"path\"] == \"/\"\n    assert result[0][1][\"httponly\"] is True\n    assert result[0][1][\"secure\"] is True\n\n    # Second cookie\n    assert result[1][0] == \"token\"\n    assert result[1][1].value == \"xyz789\"\n    assert result[1][1][\"expires\"] == \"Wednesday, 09-Jun-30 10:18:14 GMT\"\n    assert result[1][1][\"domain\"] == \".example.com\"\n    assert result[1][1][\"samesite\"] == \"Strict\"\n\n\n@pytest.mark.parametrize(\n    (\"header\", \"expected_name\", \"expected_value\", \"expected_coded\"),\n    [\n        # Test cookie values with octal escape sequences\n        (r'name=\"\\012newline\\012\"', \"name\", \"\\nnewline\\n\", r'\"\\012newline\\012\"'),\n        (\n            r'tab=\"\\011separated\\011values\"',\n            \"tab\",\n            \"\\tseparated\\tvalues\",\n            r'\"\\011separated\\011values\"',\n        ),\n        (\n            r'mixed=\"hello\\040world\\041\"',\n            \"mixed\",\n            \"hello world!\",\n            r'\"hello\\040world\\041\"',\n        ),\n        (\n            r'complex=\"\\042quoted\\042 text with \\012 newline\"',\n            \"complex\",\n            '\"quoted\" text with \\n newline',\n            r'\"\\042quoted\\042 text with \\012 newline\"',\n        ),\n    ],\n)\ndef test_parse_set_cookie_headers_uses_unquote_with_octal(\n    header: str, expected_name: str, expected_value: str, expected_coded: str\n) -> None:\n    \"\"\"Test that parse_set_cookie_headers correctly unquotes values with octal sequences and preserves coded_value.\"\"\"\n    result = parse_set_cookie_headers([header])\n\n    assert len(result) == 1\n    name, morsel = result[0]\n\n    # Check that octal sequences were properly decoded in the value\n    assert name == expected_name\n    assert morsel.value == expected_value\n\n    # Check that coded_value preserves the original quoted string\n    assert morsel.coded_value == expected_coded\n\n\n# Tests for parse_cookie_header (RFC 6265 compliant Cookie header parser)\n\n\ndef test_parse_cookie_header_simple() -> None:\n    \"\"\"Test parse_cookie_header with simple cookies.\"\"\"\n    header = \"name=value; session=abc123\"\n\n    result = parse_cookie_header(header)\n\n    assert len(result) == 2\n    assert result[0][0] == \"name\"\n    assert result[0][1].value == \"value\"\n    assert result[1][0] == \"session\"\n    assert result[1][1].value == \"abc123\"\n\n\ndef test_parse_cookie_header_empty() -> None:\n    \"\"\"Test parse_cookie_header with empty header.\"\"\"\n    assert parse_cookie_header(\"\") == []\n    assert parse_cookie_header(\"   \") == []\n\n\ndef test_parse_cookie_gstate_header() -> None:\n    header = (\n        \"_ga=ga; \"\n        \"ajs_anonymous_id=0anonymous; \"\n        \"analytics_session_id=session; \"\n        \"cookies-analytics=true; \"\n        \"cookies-functional=true; \"\n        \"cookies-marketing=true; \"\n        \"cookies-preferences=true; \"\n        'g_state={\"i_l\":0,\"i_ll\":12345,\"i_b\":\"blah\"}; '\n        \"analytics_session_id.last_access=1760128947692; \"\n        \"landingPageURLRaw=landingPageURLRaw; \"\n        \"landingPageURL=landingPageURL; \"\n        \"referrerPageURLRaw=; \"\n        \"referrerPageURL=; \"\n        \"formURLRaw=formURLRaw; \"\n        \"formURL=formURL; \"\n        \"fbnAuthExpressCheckout=fbnAuthExpressCheckout; \"\n        \"is_express_checkout=1; \"\n    )\n\n    result = parse_cookie_header(header)\n    assert result[7][0] == \"g_state\"\n    assert result[8][0] == \"analytics_session_id.last_access\"\n\n\ndef test_parse_cookie_header_quoted_values() -> None:\n    \"\"\"Test parse_cookie_header handles quoted values correctly.\"\"\"\n    header = 'name=\"quoted value\"; session=\"with;semicolon\"; data=\"with\\\\\"escaped\\\\\"\"'\n\n    result = parse_cookie_header(header)\n\n    assert len(result) == 3\n    assert result[0][0] == \"name\"\n    assert result[0][1].value == \"quoted value\"\n    assert result[1][0] == \"session\"\n    assert result[1][1].value == \"with;semicolon\"\n    assert result[2][0] == \"data\"\n    assert result[2][1].value == 'with\"escaped\"'\n\n\ndef test_parse_cookie_header_special_chars() -> None:\n    \"\"\"Test parse_cookie_header accepts special characters in names.\"\"\"\n    header = (\n        \"ISAWPLB{A7F52349-3531-4DA9-8776-F74BC6F4F1BB}=value1; cookie[index]=value2\"\n    )\n\n    result = parse_cookie_header(header)\n\n    assert len(result) == 2\n    assert result[0][0] == \"ISAWPLB{A7F52349-3531-4DA9-8776-F74BC6F4F1BB}\"\n    assert result[0][1].value == \"value1\"\n    assert result[1][0] == \"cookie[index]\"\n    assert result[1][1].value == \"value2\"\n\n\ndef test_parse_cookie_header_invalid_names() -> None:\n    \"\"\"Test parse_cookie_header rejects invalid cookie names.\"\"\"\n    # Invalid names with control characters\n    header = \"invalid\\tcookie=value; valid=cookie; invalid\\ncookie=bad\"\n\n    result = parse_cookie_header(header)\n\n    # Parse_cookie_header uses same regex as parse_set_cookie_headers\n    # Tab and newline are treated as separators, not part of names\n    assert len(result) == 5\n    assert result[0][0] == \"invalid\"\n    assert result[0][1].value == \"\"\n    assert result[1][0] == \"cookie\"\n    assert result[1][1].value == \"value\"\n    assert result[2][0] == \"valid\"\n    assert result[2][1].value == \"cookie\"\n    assert result[3][0] == \"invalid\"\n    assert result[3][1].value == \"\"\n    assert result[4][0] == \"cookie\"\n    assert result[4][1].value == \"bad\"\n\n\ndef test_parse_cookie_header_no_attributes() -> None:\n    \"\"\"Test parse_cookie_header treats all pairs as cookies (no attributes).\"\"\"\n    # In Cookie headers, even reserved attribute names are treated as cookies\n    header = (\n        \"session=abc123; path=/test; domain=.example.com; secure=yes; httponly=true\"\n    )\n\n    result = parse_cookie_header(header)\n\n    assert len(result) == 5\n    assert result[0][0] == \"session\"\n    assert result[0][1].value == \"abc123\"\n    assert result[1][0] == \"path\"\n    assert result[1][1].value == \"/test\"\n    assert result[2][0] == \"domain\"\n    assert result[2][1].value == \".example.com\"\n    assert result[3][0] == \"secure\"\n    assert result[3][1].value == \"yes\"\n    assert result[4][0] == \"httponly\"\n    assert result[4][1].value == \"true\"\n\n\ndef test_parse_cookie_header_empty_value() -> None:\n    \"\"\"Test parse_cookie_header with empty cookie values.\"\"\"\n    header = \"empty=; name=value; also_empty=\"\n\n    result = parse_cookie_header(header)\n\n    assert len(result) == 3\n    assert result[0][0] == \"empty\"\n    assert result[0][1].value == \"\"\n    assert result[1][0] == \"name\"\n    assert result[1][1].value == \"value\"\n    assert result[2][0] == \"also_empty\"\n    assert result[2][1].value == \"\"\n\n\ndef test_parse_cookie_header_spaces() -> None:\n    \"\"\"Test parse_cookie_header handles spaces correctly.\"\"\"\n    header = \"name1=value1 ;  name2=value2  ; name3=value3\"\n\n    result = parse_cookie_header(header)\n\n    assert len(result) == 3\n    assert result[0][0] == \"name1\"\n    assert result[0][1].value == \"value1\"\n    assert result[1][0] == \"name2\"\n    assert result[1][1].value == \"value2\"\n    assert result[2][0] == \"name3\"\n    assert result[2][1].value == \"value3\"\n\n\ndef test_parse_cookie_header_encoded_values() -> None:\n    \"\"\"Test parse_cookie_header preserves encoded values.\"\"\"\n    header = \"encoded=hello%20world; url=https%3A%2F%2Fexample.com\"\n\n    result = parse_cookie_header(header)\n\n    assert len(result) == 2\n    assert result[0][0] == \"encoded\"\n    assert result[0][1].value == \"hello%20world\"\n    assert result[1][0] == \"url\"\n    assert result[1][1].value == \"https%3A%2F%2Fexample.com\"\n\n\ndef test_parse_cookie_header_malformed() -> None:\n    \"\"\"Test parse_cookie_header handles malformed input.\"\"\"\n    # Missing value\n    header = \"name1=value1; justname; name2=value2\"\n\n    result = parse_cookie_header(header)\n\n    # Parser accepts cookies without values (empty value)\n    assert len(result) == 3\n    assert result[0][0] == \"name1\"\n    assert result[0][1].value == \"value1\"\n    assert result[1][0] == \"justname\"\n    assert result[1][1].value == \"\"\n    assert result[2][0] == \"name2\"\n    assert result[2][1].value == \"value2\"\n\n    # Missing name\n    header = \"=value; name=value2\"\n    result = parse_cookie_header(header)\n    assert len(result) == 1\n    assert result[0][0] == \"name\"\n    assert result[0][1].value == \"value2\"\n\n\ndef test_parse_cookie_header_complex_quoted() -> None:\n    \"\"\"Test parse_cookie_header with complex quoted values.\"\"\"\n    header = 'session=\"abc;xyz\"; data=\"value;with;multiple;semicolons\"; simple=unquoted'\n\n    result = parse_cookie_header(header)\n\n    assert len(result) == 3\n    assert result[0][0] == \"session\"\n    assert result[0][1].value == \"abc;xyz\"\n    assert result[1][0] == \"data\"\n    assert result[1][1].value == \"value;with;multiple;semicolons\"\n    assert result[2][0] == \"simple\"\n    assert result[2][1].value == \"unquoted\"\n\n\ndef test_parse_cookie_header_unmatched_quotes() -> None:\n    \"\"\"Test parse_cookie_header handles unmatched quotes.\"\"\"\n    header = 'cookie1=value1; cookie2=\"unmatched; cookie3=value3'\n\n    result = parse_cookie_header(header)\n\n    # Should parse all cookies correctly\n    assert len(result) == 3\n    assert result[0][0] == \"cookie1\"\n    assert result[0][1].value == \"value1\"\n    assert result[1][0] == \"cookie2\"\n    assert result[1][1].value == '\"unmatched'\n    assert result[2][0] == \"cookie3\"\n    assert result[2][1].value == \"value3\"\n\n\ndef test_parse_cookie_header_vs_parse_set_cookie_headers() -> None:\n    \"\"\"Test difference between parse_cookie_header and parse_set_cookie_headers.\"\"\"\n    # Cookie header with attribute-like pairs\n    cookie_header = \"session=abc123; path=/test; secure=yes\"\n\n    # parse_cookie_header treats all as cookies\n    cookie_result = parse_cookie_header(cookie_header)\n    assert len(cookie_result) == 3\n    assert cookie_result[0][0] == \"session\"\n    assert cookie_result[0][1].value == \"abc123\"\n    assert cookie_result[1][0] == \"path\"\n    assert cookie_result[1][1].value == \"/test\"\n    assert cookie_result[2][0] == \"secure\"\n    assert cookie_result[2][1].value == \"yes\"\n\n    # parse_set_cookie_headers would treat path and secure as attributes\n    set_cookie_result = parse_set_cookie_headers([cookie_header])\n    assert len(set_cookie_result) == 1\n    assert set_cookie_result[0][0] == \"session\"\n    assert set_cookie_result[0][1].value == \"abc123\"\n    assert set_cookie_result[0][1][\"path\"] == \"/test\"\n    # secure with any value is treated as boolean True\n    assert set_cookie_result[0][1][\"secure\"] is True\n\n\ndef test_parse_cookie_header_compatibility_with_simple_cookie() -> None:\n    \"\"\"Test parse_cookie_header output works with SimpleCookie.\"\"\"\n    header = \"session=abc123; user=john; token=xyz789\"\n\n    # Parse with our function\n    parsed = parse_cookie_header(header)\n\n    # Create SimpleCookie and update with our results\n    sc = SimpleCookie()\n    sc.update(parsed)\n\n    # Verify all cookies are present\n    assert len(sc) == 3\n    assert sc[\"session\"].value == \"abc123\"\n    assert sc[\"user\"].value == \"john\"\n    assert sc[\"token\"].value == \"xyz789\"\n\n\ndef test_parse_cookie_header_real_world_examples() -> None:\n    \"\"\"Test parse_cookie_header with real-world Cookie headers.\"\"\"\n    # Google Analytics style\n    header = \"_ga=GA1.2.1234567890.1234567890; _gid=GA1.2.0987654321.0987654321\"\n    result = parse_cookie_header(header)\n    assert len(result) == 2\n    assert result[0][0] == \"_ga\"\n    assert result[0][1].value == \"GA1.2.1234567890.1234567890\"\n    assert result[1][0] == \"_gid\"\n    assert result[1][1].value == \"GA1.2.0987654321.0987654321\"\n\n    # Session cookies\n    header = \"PHPSESSID=abc123def456; csrf_token=xyz789; logged_in=true\"\n    result = parse_cookie_header(header)\n    assert len(result) == 3\n    assert result[0][0] == \"PHPSESSID\"\n    assert result[0][1].value == \"abc123def456\"\n    assert result[1][0] == \"csrf_token\"\n    assert result[1][1].value == \"xyz789\"\n    assert result[2][0] == \"logged_in\"\n    assert result[2][1].value == \"true\"\n\n    # Complex values with proper quoting\n    header = r'preferences=\"{\\\"theme\\\":\\\"dark\\\",\\\"lang\\\":\\\"en\\\"}\"; session_data=eyJhbGciOiJIUzI1NiJ9'\n    result = parse_cookie_header(header)\n    assert len(result) == 2\n    assert result[0][0] == \"preferences\"\n    assert result[0][1].value == '{\"theme\":\"dark\",\"lang\":\"en\"}'\n    assert result[1][0] == \"session_data\"\n    assert result[1][1].value == \"eyJhbGciOiJIUzI1NiJ9\"\n\n\ndef test_parse_cookie_header_issue_7993() -> None:\n    \"\"\"Test parse_cookie_header handles issue #7993 correctly.\"\"\"\n    # This specific case from issue #7993\n    header = 'foo=bar; baz=\"qux; foo2=bar2'\n\n    result = parse_cookie_header(header)\n\n    # All cookies should be parsed\n    assert len(result) == 3\n    assert result[0][0] == \"foo\"\n    assert result[0][1].value == \"bar\"\n    assert result[1][0] == \"baz\"\n    assert result[1][1].value == '\"qux'\n    assert result[2][0] == \"foo2\"\n    assert result[2][1].value == \"bar2\"\n\n\ndef test_parse_cookie_header_illegal_names(caplog: pytest.LogCaptureFixture) -> None:\n    \"\"\"Test parse_cookie_header warns about illegal cookie names.\"\"\"\n    # Cookie name with comma (not allowed in _COOKIE_NAME_RE)\n    header = \"good=value; invalid,cookie=bad; another=test\"\n    with caplog.at_level(logging.DEBUG):\n        result = parse_cookie_header(header)\n    # Should skip the invalid cookie but continue parsing\n    assert len(result) == 2\n    assert result[0][0] == \"good\"\n    assert result[0][1].value == \"value\"\n    assert result[1][0] == \"another\"\n    assert result[1][1].value == \"test\"\n    assert \"Cannot load cookie. Illegal cookie name\" in caplog.text\n    assert \"'invalid,cookie'\" in caplog.text\n\n\ndef test_parse_cookie_header_large_value() -> None:\n    \"\"\"Test that large cookie values don't cause DoS.\"\"\"\n    large_value = \"A\" * 8192\n    header = f\"normal=value; large={large_value}; after=cookie\"\n\n    result = parse_cookie_header(header)\n    cookie_names = [name for name, _ in result]\n\n    assert len(result) == 3\n    assert \"normal\" in cookie_names\n    assert \"large\" in cookie_names\n    assert \"after\" in cookie_names\n\n    large_cookie = next(morsel for name, morsel in result if name == \"large\")\n    assert len(large_cookie.value) == 8192\n\n\ndef test_parse_cookie_header_multiple_equals() -> None:\n    \"\"\"Test handling of multiple equals signs in cookie values.\"\"\"\n    header = \"session=abc123; data=key1=val1&key2=val2; token=xyz\"\n\n    result = parse_cookie_header(header)\n\n    assert len(result) == 3\n\n    name1, morsel1 = result[0]\n    assert name1 == \"session\"\n    assert morsel1.value == \"abc123\"\n\n    name2, morsel2 = result[1]\n    assert name2 == \"data\"\n    assert morsel2.value == \"key1=val1&key2=val2\"\n\n    name3, morsel3 = result[2]\n    assert name3 == \"token\"\n    assert morsel3.value == \"xyz\"\n\n\ndef test_parse_cookie_header_fallback_preserves_subsequent_cookies() -> None:\n    \"\"\"Test that fallback parser doesn't lose subsequent cookies.\"\"\"\n    header = 'normal=value; malformed={\"json\":\"value\"}; after1=cookie1; after2=cookie2'\n\n    result = parse_cookie_header(header)\n    cookie_names = [name for name, _ in result]\n\n    assert len(result) == 4\n    assert cookie_names == [\"normal\", \"malformed\", \"after1\", \"after2\"]\n\n    name1, morsel1 = result[0]\n    assert morsel1.value == \"value\"\n\n    name2, morsel2 = result[1]\n    assert morsel2.value == '{\"json\":\"value\"}'\n\n    name3, morsel3 = result[2]\n    assert morsel3.value == \"cookie1\"\n\n    name4, morsel4 = result[3]\n    assert morsel4.value == \"cookie2\"\n\n\ndef test_parse_cookie_header_whitespace_in_fallback() -> None:\n    \"\"\"Test that fallback parser handles whitespace correctly.\"\"\"\n    header = \"a=1; b = 2 ; c= 3; d =4\"\n\n    result = parse_cookie_header(header)\n\n    assert len(result) == 4\n    for name, morsel in result:\n        assert name in (\"a\", \"b\", \"c\", \"d\")\n        assert morsel.value in (\"1\", \"2\", \"3\", \"4\")\n\n\ndef test_parse_cookie_header_empty_value_in_fallback() -> None:\n    \"\"\"Test that fallback handles empty values correctly.\"\"\"\n    header = \"normal=value; empty=; another=test\"\n\n    result = parse_cookie_header(header)\n\n    assert len(result) == 3\n\n    name1, morsel1 = result[0]\n    assert name1 == \"normal\"\n    assert morsel1.value == \"value\"\n\n    name2, morsel2 = result[1]\n    assert name2 == \"empty\"\n    assert morsel2.value == \"\"\n\n    name3, morsel3 = result[2]\n    assert name3 == \"another\"\n    assert morsel3.value == \"test\"\n\n\ndef test_parse_cookie_header_invalid_name_in_fallback(\n    caplog: pytest.LogCaptureFixture,\n) -> None:\n    \"\"\"Test that fallback parser rejects cookies with invalid names.\"\"\"\n    header = 'normal=value; invalid,name={\"x\":\"y\"}; another=test'\n\n    with caplog.at_level(logging.DEBUG):\n        result = parse_cookie_header(header)\n\n    assert len(result) == 2\n\n    name1, morsel1 = result[0]\n    assert name1 == \"normal\"\n    assert morsel1.value == \"value\"\n\n    name2, morsel2 = result[1]\n    assert name2 == \"another\"\n    assert morsel2.value == \"test\"\n\n    assert \"Cannot load cookie. Illegal cookie name\" in caplog.text\n    assert \"'invalid,name'\" in caplog.text\n\n\ndef test_parse_cookie_header_empty_key_in_fallback(\n    caplog: pytest.LogCaptureFixture,\n) -> None:\n    \"\"\"Test that fallback parser logs warning for empty cookie names.\"\"\"\n    header = 'normal=value; ={\"malformed\":\"json\"}; another=test'\n    with caplog.at_level(logging.DEBUG):\n        result = parse_cookie_header(header)\n\n    assert len(result) == 2\n\n    name1, morsel1 = result[0]\n    assert name1 == \"normal\"\n    assert morsel1.value == \"value\"\n\n    name2, morsel2 = result[1]\n    assert name2 == \"another\"\n    assert morsel2.value == \"test\"\n\n    assert \"Cannot load cookie. Illegal cookie name\" in caplog.text\n    assert \"''\" in caplog.text\n\n\n@pytest.mark.parametrize(\n    (\"input_str\", \"expected\"),\n    [\n        # Unquoted strings should remain unchanged\n        (\"simple\", \"simple\"),\n        (\"with spaces\", \"with spaces\"),\n        (\"\", \"\"),\n        ('\"', '\"'),  # String too short to be quoted\n        ('some\"text', 'some\"text'),  # Quotes not at beginning/end\n        ('text\"with\"quotes', 'text\"with\"quotes'),\n    ],\n)\ndef test_unquote_basic(input_str: str, expected: str) -> None:\n    \"\"\"Test basic _unquote functionality.\"\"\"\n    assert _unquote(input_str) == expected\n\n\n@pytest.mark.parametrize(\n    (\"input_str\", \"expected\"),\n    [\n        # Basic quoted strings\n        ('\"quoted\"', \"quoted\"),\n        ('\"with spaces\"', \"with spaces\"),\n        ('\"\"', \"\"),  # Empty quoted string\n        # Quoted string with special characters\n        ('\"hello, world!\"', \"hello, world!\"),\n        ('\"path=/test\"', \"path=/test\"),\n    ],\n)\ndef test_unquote_quoted_strings(input_str: str, expected: str) -> None:\n    \"\"\"Test _unquote with quoted strings.\"\"\"\n    assert _unquote(input_str) == expected\n\n\n@pytest.mark.parametrize(\n    (\"input_str\", \"expected\"),\n    [\n        # Escaped quotes should be unescaped\n        (r'\"say \\\"hello\\\"\"', 'say \"hello\"'),\n        (r'\"nested \\\"quotes\\\" here\"', 'nested \"quotes\" here'),\n        # Multiple escaped quotes\n        (r'\"\\\"start\\\" middle \\\"end\\\"\"', '\"start\" middle \"end\"'),\n    ],\n)\ndef test_unquote_escaped_quotes(input_str: str, expected: str) -> None:\n    \"\"\"Test _unquote with escaped quotes.\"\"\"\n    assert _unquote(input_str) == expected\n\n\n@pytest.mark.parametrize(\n    (\"input_str\", \"expected\"),\n    [\n        # Single escaped backslash\n        (r'\"path\\\\to\\\\file\"', \"path\\\\to\\\\file\"),\n        # Backslash before quote\n        (r'\"end with slash\\\\\"', \"end with slash\\\\\"),\n        # Mixed escaped characters\n        (r'\"path\\\\to\\\\\\\"file\\\"\"', 'path\\\\to\\\\\"file\"'),\n    ],\n)\ndef test_unquote_escaped_backslashes(input_str: str, expected: str) -> None:\n    \"\"\"Test _unquote with escaped backslashes.\"\"\"\n    assert _unquote(input_str) == expected\n\n\n@pytest.mark.parametrize(\n    (\"input_str\", \"expected\"),\n    [\n        # Common octal sequences\n        (r'\"\\012\"', \"\\n\"),  # newline\n        (r'\"\\011\"', \"\\t\"),  # tab\n        (r'\"\\015\"', \"\\r\"),  # carriage return\n        (r'\"\\040\"', \" \"),  # space\n        # Octal sequences in context\n        (r'\"line1\\012line2\"', \"line1\\nline2\"),\n        (r'\"tab\\011separated\"', \"tab\\tseparated\"),\n        # Multiple octal sequences\n        (r'\"\\012\\011\\015\"', \"\\n\\t\\r\"),\n        # Mixed octal and regular text\n        (r'\"hello\\040world\\041\"', \"hello world!\"),\n    ],\n)\ndef test_unquote_octal_sequences(input_str: str, expected: str) -> None:\n    \"\"\"Test _unquote with octal escape sequences.\"\"\"\n    assert _unquote(input_str) == expected\n\n\n@pytest.mark.parametrize(\n    (\"input_str\", \"expected\"),\n    [\n        # Test boundary values\n        (r'\"\\000\"', \"\\x00\"),  # null character\n        (r'\"\\001\"', \"\\x01\"),\n        (r'\"\\177\"', \"\\x7f\"),  # DEL character\n        (r'\"\\200\"', \"\\x80\"),  # Extended ASCII\n        (r'\"\\377\"', \"\\xff\"),  # Max octal value\n        # Invalid octal sequences (not 3 digits or > 377) are treated as regular escapes\n        (r'\"\\400\"', \"400\"),  # 400 octal = 256 decimal, too large\n        (r'\"\\777\"', \"777\"),  # 777 octal = 511 decimal, too large\n    ],\n)\ndef test_unquote_octal_full_range(input_str: str, expected: str) -> None:\n    \"\"\"Test _unquote with full range of valid octal sequences.\"\"\"\n    assert _unquote(input_str) == expected\n\n\n@pytest.mark.parametrize(\n    (\"input_str\", \"expected\"),\n    [\n        # Mix of quotes, backslashes, and octal\n        (r'\"say \\\"hello\\\"\\012new line\"', 'say \"hello\"\\nnew line'),\n        (r'\"path\\\\to\\\\file\\011\\011data\"', \"path\\\\to\\\\file\\t\\tdata\"),\n        # Complex mixed example\n        (r'\"\\042quoted\\042 and \\134backslash\\134\"', '\"quoted\" and \\\\backslash\\\\'),\n        # Escaped characters that aren't special\n        (r'\"\\a\\b\\c\"', \"abc\"),  # \\a, \\b, \\c -> a, b, c\n    ],\n)\ndef test_unquote_mixed_escapes(input_str: str, expected: str) -> None:\n    \"\"\"Test _unquote with mixed escape sequences.\"\"\"\n    assert _unquote(input_str) == expected\n\n\n@pytest.mark.parametrize(\n    (\"input_str\", \"expected\"),\n    [\n        # String that starts with quote but doesn't end with one\n        ('\"not closed', '\"not closed'),\n        # String that ends with quote but doesn't start with one\n        ('not opened\"', 'not opened\"'),\n        # Multiple quotes\n        ('\"\"\"', '\"'),\n        ('\"\"\"\"', '\"\"'),\n        # Backslash at the end without anything to escape\n        (r'\"ends with\\\"', \"ends with\\\\\"),\n        # Empty escape\n        (r'\"test\\\"', \"test\\\\\"),\n        # Just escaped characters\n        (r'\"\\\"\\\"\\\"\"', '\"\"\"'),\n    ],\n)\ndef test_unquote_edge_cases(input_str: str, expected: str) -> None:\n    \"\"\"Test _unquote edge cases.\"\"\"\n    assert _unquote(input_str) == expected\n\n\n@pytest.mark.parametrize(\n    (\"input_str\", \"expected\"),\n    [\n        # JSON-like data\n        (r'\"{\\\"user\\\":\\\"john\\\",\\\"id\\\":123}\"', '{\"user\":\"john\",\"id\":123}'),\n        # URL-encoded then quoted\n        ('\"hello%20world\"', \"hello%20world\"),\n        # Path with backslashes (Windows-style)\n        (r'\"C:\\\\Users\\\\John\\\\Documents\"', \"C:\\\\Users\\\\John\\\\Documents\"),\n        # Complex session data\n        (\n            r'\"session_data=\\\"user123\\\";expires=2024\"',\n            'session_data=\"user123\";expires=2024',\n        ),\n    ],\n)\ndef test_unquote_real_world_examples(input_str: str, expected: str) -> None:\n    \"\"\"Test _unquote with real-world cookie value examples.\"\"\"\n    assert _unquote(input_str) == expected\n\n\n@pytest.mark.parametrize(\n    \"test_value\",\n    [\n        '\"\"',\n        '\"simple\"',\n        r'\"with \\\"quotes\\\"\"',\n        r'\"with \\\\backslash\\\\\"',\n        r'\"\\012newline\"',\n        r'\"complex\\042quote\\134slash\\012\"',\n        '\"not-quoted',\n        'also-not-quoted\"',\n        r'\"mixed\\011\\042\\134test\"',\n    ],\n)\ndef test_unquote_compatibility_with_simplecookie(test_value: str) -> None:\n    \"\"\"Test that _unquote behaves like SimpleCookie's unquoting.\"\"\"\n    assert _unquote(test_value) == simplecookie_unquote(test_value), (\n        f\"Mismatch for {test_value!r}: \"\n        f\"our={_unquote(test_value)!r}, \"\n        f\"SimpleCookie={simplecookie_unquote(test_value)!r}\"\n    )\n"
  },
  {
    "path": "tests/test_cookiejar.py",
    "content": "import datetime\nimport heapq\nimport itertools\nimport logging\nimport pickle\nimport sys\nfrom http.cookies import BaseCookie, Morsel, SimpleCookie\nfrom operator import not_\nfrom pathlib import Path\nfrom unittest import mock\n\nimport pytest\nfrom freezegun import freeze_time\nfrom yarl import URL\n\nfrom aiohttp import CookieJar, DummyCookieJar\nfrom aiohttp.typedefs import LooseCookies\n\n\ndef dump_cookiejar() -> bytes:  # pragma: no cover\n    \"\"\"Create pickled data for test_pickle_format().\"\"\"\n    cj = CookieJar()\n    cj.update_cookies(_cookies_to_send())\n    return pickle.dumps(cj._cookies, pickle.HIGHEST_PROTOCOL)\n\n\ndef _cookies_to_send() -> SimpleCookie:\n    return SimpleCookie(\n        \"shared-cookie=first; \"\n        \"domain-cookie=second; Domain=example.com; \"\n        \"subdomain1-cookie=third; Domain=test1.example.com; \"\n        \"subdomain2-cookie=fourth; Domain=test2.example.com; \"\n        \"dotted-domain-cookie=fifth; Domain=.example.com; \"\n        \"different-domain-cookie=sixth; Domain=different.org; \"\n        \"secure-cookie=seventh; Domain=secure.com; Secure; \"\n        \"no-path-cookie=eighth; Domain=pathtest.com; \"\n        \"path1-cookie=ninth; Domain=pathtest.com; Path=/; \"\n        \"path2-cookie=tenth; Domain=pathtest.com; Path=/one; \"\n        \"path3-cookie=eleventh; Domain=pathtest.com; Path=/one/two; \"\n        \"path4-cookie=twelfth; Domain=pathtest.com; Path=/one/two/; \"\n        \"expires-cookie=thirteenth; Domain=expirestest.com; Path=/;\"\n        \" Expires=Tue, 1 Jan 2999 12:00:00 GMT; \"\n        \"max-age-cookie=fourteenth; Domain=maxagetest.com; Path=/;\"\n        \" Max-Age=60; \"\n        \"invalid-max-age-cookie=fifteenth; Domain=invalid-values.com; \"\n        \" Max-Age=string; \"\n        \"invalid-expires-cookie=sixteenth; Domain=invalid-values.com; \"\n        \" Expires=string;\"\n    )\n\n\n@pytest.fixture\ndef cookies_to_send() -> SimpleCookie:\n    return _cookies_to_send()\n\n\n@pytest.fixture\ndef cookies_to_send_with_expired() -> SimpleCookie:\n    return SimpleCookie(\n        \"shared-cookie=first; \"\n        \"domain-cookie=second; Domain=example.com; \"\n        \"subdomain1-cookie=third; Domain=test1.example.com; \"\n        \"subdomain2-cookie=fourth; Domain=test2.example.com; \"\n        \"dotted-domain-cookie=fifth; Domain=.example.com; \"\n        \"different-domain-cookie=sixth; Domain=different.org; \"\n        \"secure-cookie=seventh; Domain=secure.com; Secure; \"\n        \"no-path-cookie=eighth; Domain=pathtest.com; \"\n        \"path1-cookie=ninth; Domain=pathtest.com; Path=/; \"\n        \"path2-cookie=tenth; Domain=pathtest.com; Path=/one; \"\n        \"path3-cookie=eleventh; Domain=pathtest.com; Path=/one/two; \"\n        \"path4-cookie=twelfth; Domain=pathtest.com; Path=/one/two/; \"\n        \"expires-cookie=thirteenth; Domain=expirestest.com; Path=/;\"\n        \" Expires=Tue, 1 Jan 1980 12:00:00 GMT; \"\n        \"max-age-cookie=fourteenth; Domain=maxagetest.com; Path=/;\"\n        \" Max-Age=60; \"\n        \"invalid-max-age-cookie=fifteenth; Domain=invalid-values.com; \"\n        \" Max-Age=string; \"\n        \"invalid-expires-cookie=sixteenth; Domain=invalid-values.com; \"\n        \" Expires=string;\"\n    )\n\n\n@pytest.fixture\ndef cookies_to_receive() -> SimpleCookie:\n    return SimpleCookie(\n        \"unconstrained-cookie=first; Path=/; \"\n        \"domain-cookie=second; Domain=example.com; Path=/; \"\n        \"subdomain1-cookie=third; Domain=test1.example.com; Path=/; \"\n        \"subdomain2-cookie=fourth; Domain=test2.example.com; Path=/; \"\n        \"dotted-domain-cookie=fifth; Domain=.example.com; Path=/; \"\n        \"different-domain-cookie=sixth; Domain=different.org; Path=/; \"\n        \"no-path-cookie=seventh; Domain=pathtest.com; \"\n        \"path-cookie=eighth; Domain=pathtest.com; Path=/somepath; \"\n        \"wrong-path-cookie=ninth; Domain=pathtest.com; Path=somepath;\"\n    )\n\n\ndef test_date_parsing() -> None:\n    parse_func = CookieJar._parse_date\n    utc = datetime.timezone.utc\n\n    assert parse_func(\"\") is None\n\n    # 70 -> 1970\n    assert (\n        parse_func(\"Tue, 1 Jan 70 00:00:00 GMT\")\n        == datetime.datetime(1970, 1, 1, tzinfo=utc).timestamp()\n    )\n\n    # 10 -> 2010\n    assert (\n        parse_func(\"Tue, 1 Jan 10 00:00:00 GMT\")\n        == datetime.datetime(2010, 1, 1, tzinfo=utc).timestamp()\n    )\n\n    # No day of week string\n    assert (\n        parse_func(\"1 Jan 1970 00:00:00 GMT\")\n        == datetime.datetime(1970, 1, 1, tzinfo=utc).timestamp()\n    )\n\n    # No timezone string\n    assert (\n        parse_func(\"Tue, 1 Jan 1970 00:00:00\")\n        == datetime.datetime(1970, 1, 1, tzinfo=utc).timestamp()\n    )\n\n    # No year\n    assert parse_func(\"Tue, 1 Jan 00:00:00 GMT\") is None\n\n    # No month\n    assert parse_func(\"Tue, 1 1970 00:00:00 GMT\") is None\n\n    # No day of month\n    assert parse_func(\"Tue, Jan 1970 00:00:00 GMT\") is None\n\n    # No time\n    assert parse_func(\"Tue, 1 Jan 1970 GMT\") is None\n\n    # Invalid day of month\n    assert parse_func(\"Tue, 0 Jan 1970 00:00:00 GMT\") is None\n\n    # Invalid year\n    assert parse_func(\"Tue, 1 Jan 1500 00:00:00 GMT\") is None\n\n    # Invalid time\n    assert parse_func(\"Tue, 1 Jan 1970 77:88:99 GMT\") is None\n\n\ndef test_domain_matching() -> None:\n    test_func = CookieJar._is_domain_match\n\n    assert test_func(\"test.com\", \"test.com\")\n    assert test_func(\"test.com\", \"sub.test.com\")\n\n    assert not test_func(\"test.com\", \"\")\n    assert not test_func(\"test.com\", \"test.org\")\n    assert not test_func(\"diff-test.com\", \"test.com\")\n    assert not test_func(\"test.com\", \"diff-test.com\")\n    assert not test_func(\"test.com\", \"127.0.0.1\")\n\n\nasync def test_constructor(\n    cookies_to_send: SimpleCookie, cookies_to_receive: SimpleCookie\n) -> None:\n    jar = CookieJar()\n    jar.update_cookies(cookies_to_send)\n    jar_cookies = SimpleCookie()\n    for cookie in jar:\n        dict.__setitem__(jar_cookies, cookie.key, cookie)\n    expected_cookies = cookies_to_send\n    assert jar_cookies == expected_cookies\n\n\nasync def test_constructor_with_expired(\n    cookies_to_send_with_expired: SimpleCookie, cookies_to_receive: SimpleCookie\n) -> None:\n    jar = CookieJar()\n    jar.update_cookies(cookies_to_send_with_expired)\n    jar_cookies = SimpleCookie()\n    for cookie in jar:\n        dict.__setitem__(jar_cookies, cookie.key, cookie)\n    expected_cookies = cookies_to_send_with_expired\n    assert jar_cookies != expected_cookies\n\n\ndef test_save_load(\n    tmp_path: Path,\n    cookies_to_send: SimpleCookie,\n    cookies_to_receive: SimpleCookie,\n) -> None:\n    file_path = Path(str(tmp_path)) / \"aiohttp.test.cookie\"\n\n    # export cookie jar\n    jar_save = CookieJar()\n    jar_save.update_cookies(cookies_to_receive)\n    jar_save.save(file_path=file_path)\n\n    jar_load = CookieJar()\n    jar_load.load(file_path=file_path)\n\n    jar_test = SimpleCookie()\n    for cookie in jar_load:\n        jar_test[cookie.key] = cookie\n\n    assert jar_test == cookies_to_receive\n\n\ndef test_save_load_partitioned_cookies(tmp_path: Path) -> None:\n    file_path = Path(str(tmp_path)) / \"aiohttp.test2.cookie\"\n    # export cookie jar\n    jar_save = CookieJar()\n    jar_save.update_cookies_from_headers(\n        [\"session=cookie; Partitioned\"], URL(\"https://example.com/\")\n    )\n    jar_save.save(file_path=file_path)\n    jar_load = CookieJar()\n    jar_load.load(file_path=file_path)\n    assert jar_save._cookies == jar_load._cookies\n\n\nasync def test_update_cookie_with_unicode_domain() -> None:\n    cookies = (\n        \"idna-domain-first=first; Domain=xn--9caa.com; Path=/;\",\n        \"idna-domain-second=second; Domain=xn--9caa.com; Path=/;\",\n    )\n\n    jar = CookieJar()\n    jar.update_cookies(SimpleCookie(cookies[0]), URL(\"http://éé.com/\"))\n    jar.update_cookies(SimpleCookie(cookies[1]), URL(\"http://xn--9caa.com/\"))\n\n    jar_test = SimpleCookie()\n    for cookie in jar:\n        jar_test[cookie.key] = cookie\n\n    assert jar_test == SimpleCookie(\" \".join(cookies))\n\n\nasync def test_filter_cookie_with_unicode_domain() -> None:\n    jar = CookieJar()\n    jar.update_cookies(\n        SimpleCookie(\"idna-domain-first=first; Domain=xn--9caa.com; Path=/; \")\n    )\n    assert len(jar.filter_cookies(URL(\"http://éé.com\"))) == 1\n    assert len(jar.filter_cookies(URL(\"http://xn--9caa.com\"))) == 1\n\n\nasync def test_filter_cookies_str_deprecated() -> None:\n    jar = CookieJar()\n    with pytest.deprecated_call(\n        match=\"The method accepts yarl.URL instances only, got <class 'str'>\",\n    ):\n        jar.filter_cookies(\"http://éé.com\")  # type: ignore[arg-type]\n\n\n@pytest.mark.parametrize(\n    (\"url\", \"expected_cookies\"),\n    (\n        (\n            \"http://pathtest.com/one/two/\",\n            (\n                \"no-path-cookie\",\n                \"path1-cookie\",\n                \"path2-cookie\",\n                \"shared-cookie\",\n                \"path3-cookie\",\n                \"path4-cookie\",\n            ),\n        ),\n        (\n            \"http://pathtest.com/one/two\",\n            (\n                \"no-path-cookie\",\n                \"path1-cookie\",\n                \"path2-cookie\",\n                \"shared-cookie\",\n                \"path3-cookie\",\n            ),\n        ),\n        (\n            \"http://pathtest.com/one/two/three/\",\n            (\n                \"no-path-cookie\",\n                \"path1-cookie\",\n                \"path2-cookie\",\n                \"shared-cookie\",\n                \"path3-cookie\",\n                \"path4-cookie\",\n            ),\n        ),\n        (\n            \"http://test1.example.com/\",\n            (\n                \"shared-cookie\",\n                \"domain-cookie\",\n                \"subdomain1-cookie\",\n                \"dotted-domain-cookie\",\n            ),\n        ),\n        (\n            \"http://pathtest.com/\",\n            (\n                \"shared-cookie\",\n                \"no-path-cookie\",\n                \"path1-cookie\",\n            ),\n        ),\n    ),\n)\nasync def test_filter_cookies_with_domain_path_lookup_multilevelpath(\n    url: str,\n    expected_cookies: set[str],\n) -> None:\n    jar = CookieJar()\n    cookie = SimpleCookie(\n        \"shared-cookie=first; \"\n        \"domain-cookie=second; Domain=example.com; \"\n        \"subdomain1-cookie=third; Domain=test1.example.com; \"\n        \"subdomain2-cookie=fourth; Domain=test2.example.com; \"\n        \"dotted-domain-cookie=fifth; Domain=.example.com; \"\n        \"different-domain-cookie=sixth; Domain=different.org; \"\n        \"secure-cookie=seventh; Domain=secure.com; Secure; \"\n        \"no-path-cookie=eighth; Domain=pathtest.com; \"\n        \"path1-cookie=ninth; Domain=pathtest.com; Path=/; \"\n        \"path2-cookie=tenth; Domain=pathtest.com; Path=/one; \"\n        \"path3-cookie=eleventh; Domain=pathtest.com; Path=/one/two; \"\n        \"path4-cookie=twelfth; Domain=pathtest.com; Path=/one/two/; \"\n        \"expires-cookie=thirteenth; Domain=expirestest.com; Path=/;\"\n        \" Expires=Tue, 1 Jan 1980 12:00:00 GMT; \"\n        \"max-age-cookie=fourteenth; Domain=maxagetest.com; Path=/;\"\n        \" Max-Age=60; \"\n        \"invalid-max-age-cookie=fifteenth; Domain=invalid-values.com; \"\n        \" Max-Age=string; \"\n        \"invalid-expires-cookie=sixteenth; Domain=invalid-values.com; \"\n        \" Expires=string;\"\n    )\n    jar.update_cookies(cookie)\n    cookies = jar.filter_cookies(URL(url))\n\n    assert len(cookies) == len(expected_cookies)\n    for c in cookies:\n        assert c in expected_cookies\n\n\nasync def test_domain_filter_ip_cookie_send() -> None:\n    jar = CookieJar()\n    cookies = SimpleCookie(\n        \"shared-cookie=first; \"\n        \"domain-cookie=second; Domain=example.com; \"\n        \"subdomain1-cookie=third; Domain=test1.example.com; \"\n        \"subdomain2-cookie=fourth; Domain=test2.example.com; \"\n        \"dotted-domain-cookie=fifth; Domain=.example.com; \"\n        \"different-domain-cookie=sixth; Domain=different.org; \"\n        \"secure-cookie=seventh; Domain=secure.com; Secure; \"\n        \"no-path-cookie=eighth; Domain=pathtest.com; \"\n        \"path1-cookie=ninth; Domain=pathtest.com; Path=/; \"\n        \"path2-cookie=tenth; Domain=pathtest.com; Path=/one; \"\n        \"path3-cookie=eleventh; Domain=pathtest.com; Path=/one/two; \"\n        \"path4-cookie=twelfth; Domain=pathtest.com; Path=/one/two/; \"\n        \"expires-cookie=thirteenth; Domain=expirestest.com; Path=/;\"\n        \" Expires=Tue, 1 Jan 1980 12:00:00 GMT; \"\n        \"max-age-cookie=fourteenth; Domain=maxagetest.com; Path=/;\"\n        \" Max-Age=60; \"\n        \"invalid-max-age-cookie=fifteenth; Domain=invalid-values.com; \"\n        \" Max-Age=string; \"\n        \"invalid-expires-cookie=sixteenth; Domain=invalid-values.com; \"\n        \" Expires=string;\"\n    )\n\n    jar.update_cookies(cookies)\n    cookies_sent = jar.filter_cookies(URL(\"http://1.2.3.4/\")).output(header=\"Cookie:\")\n    assert cookies_sent == \"Cookie: shared-cookie=first\"\n\n\nasync def test_domain_filter_ip_cookie_receive(\n    cookies_to_receive: SimpleCookie,\n) -> None:\n    jar = CookieJar()\n\n    jar.update_cookies(cookies_to_receive, URL(\"http://1.2.3.4/\"))\n    assert len(jar) == 0\n\n\n@pytest.mark.parametrize(\n    (\"cookies\", \"expected\", \"quote_bool\"),\n    [\n        (\n            \"shared-cookie=first; ip-cookie=second; Domain=127.0.0.1;\",\n            \"Cookie: ip-cookie=second\\r\\nCookie: shared-cookie=first\",\n            True,\n        ),\n        ('ip-cookie=\"second\"; Domain=127.0.0.1;', 'Cookie: ip-cookie=\"second\"', True),\n        (\"custom-cookie=value/one;\", 'Cookie: custom-cookie=\"value/one\"', True),\n        (\"custom-cookie=value1;\", \"Cookie: custom-cookie=value1\", True),\n        (\"custom-cookie=value/one;\", \"Cookie: custom-cookie=value/one\", False),\n        ('foo=\"quoted_value\"', 'Cookie: foo=\"quoted_value\"', True),\n        ('foo=\"quoted_value\"; domain=127.0.0.1', 'Cookie: foo=\"quoted_value\"', True),\n    ],\n    ids=(\n        \"IP domain preserved\",\n        \"no shared cookie\",\n        \"quoted cookie with special char\",\n        \"quoted cookie w/o special char\",\n        \"unquoted cookie with special char\",\n        \"pre-quoted cookie\",\n        \"pre-quoted cookie with domain\",\n    ),\n)\nasync def test_quotes_correctly_based_on_input(\n    cookies: str, expected: str, quote_bool: bool\n) -> None:\n    jar = CookieJar(unsafe=True, quote_cookie=quote_bool)\n    jar.update_cookies(SimpleCookie(cookies))\n    cookies_sent = jar.filter_cookies(URL(\"http://127.0.0.1/\")).output(header=\"Cookie:\")\n    assert cookies_sent == expected\n\n\nasync def test_ignore_domain_ending_with_dot() -> None:\n    jar = CookieJar(unsafe=True)\n    jar.update_cookies(\n        SimpleCookie(\"cookie=val; Domain=example.com.;\"), URL(\"http://www.example.com\")\n    )\n    cookies_sent = jar.filter_cookies(URL(\"http://www.example.com/\"))\n    assert cookies_sent.output(header=\"Cookie:\") == \"Cookie: cookie=val\"\n    cookies_sent = jar.filter_cookies(URL(\"http://example.com/\"))\n    assert cookies_sent.output(header=\"Cookie:\") == \"\"\n\n\nclass TestCookieJarSafe:\n    @pytest.fixture(autouse=True)\n    def setup_cookies(\n        self,\n        cookies_to_send_with_expired: SimpleCookie,\n        cookies_to_receive: SimpleCookie,\n    ) -> None:\n        self.cookies_to_send = cookies_to_send_with_expired\n        self.cookies_to_receive = cookies_to_receive\n\n    def request_reply_with_same_url(\n        self, url: str\n    ) -> tuple[\"BaseCookie[str]\", SimpleCookie]:\n        jar = CookieJar()\n        jar.update_cookies(self.cookies_to_send)\n        cookies_sent = jar.filter_cookies(URL(url))\n\n        jar.clear()\n\n        jar.update_cookies(self.cookies_to_receive, URL(url))\n        cookies_received = SimpleCookie()\n        for cookie in jar:\n            dict.__setitem__(cookies_received, cookie.key, cookie)\n\n        jar.clear()\n\n        return cookies_sent, cookies_received\n\n    def timed_request(\n        self, url: str, update_time: float, send_time: float\n    ) -> \"BaseCookie[str]\":\n        jar = CookieJar()\n        freeze_update_time: datetime.datetime | datetime.timedelta\n        freeze_send_time: datetime.datetime | datetime.timedelta\n        if isinstance(update_time, int):\n            freeze_update_time = datetime.timedelta(seconds=update_time)\n        else:\n            freeze_update_time = datetime.datetime.fromtimestamp(update_time)\n        if isinstance(send_time, int):\n            freeze_send_time = datetime.timedelta(seconds=send_time)\n        else:\n            freeze_send_time = datetime.datetime.fromtimestamp(send_time)\n\n        with freeze_time(freeze_update_time):\n            jar.update_cookies(self.cookies_to_send)\n\n        with freeze_time(freeze_send_time):\n            cookies_sent = jar.filter_cookies(URL(url))\n\n        jar.clear()\n\n        return cookies_sent\n\n    def test_domain_filter_same_host(self) -> None:\n        cookies_sent, cookies_received = self.request_reply_with_same_url(\n            \"http://example.com/\"\n        )\n\n        assert set(cookies_sent.keys()) == {\n            \"shared-cookie\",\n            \"domain-cookie\",\n            \"dotted-domain-cookie\",\n        }\n\n        assert set(cookies_received.keys()) == {\n            \"unconstrained-cookie\",\n            \"domain-cookie\",\n            \"dotted-domain-cookie\",\n        }\n\n    def test_domain_filter_same_host_and_subdomain(self) -> None:\n        cookies_sent, cookies_received = self.request_reply_with_same_url(\n            \"http://test1.example.com/\"\n        )\n\n        assert set(cookies_sent.keys()) == {\n            \"shared-cookie\",\n            \"domain-cookie\",\n            \"subdomain1-cookie\",\n            \"dotted-domain-cookie\",\n        }\n\n        assert set(cookies_received.keys()) == {\n            \"unconstrained-cookie\",\n            \"domain-cookie\",\n            \"subdomain1-cookie\",\n            \"dotted-domain-cookie\",\n        }\n\n    def test_domain_filter_same_host_diff_subdomain(self) -> None:\n        cookies_sent, cookies_received = self.request_reply_with_same_url(\n            \"http://different.example.com/\"\n        )\n\n        assert set(cookies_sent.keys()) == {\n            \"shared-cookie\",\n            \"domain-cookie\",\n            \"dotted-domain-cookie\",\n        }\n\n        assert set(cookies_received.keys()) == {\n            \"unconstrained-cookie\",\n            \"domain-cookie\",\n            \"dotted-domain-cookie\",\n        }\n\n    def test_domain_filter_diff_host(self) -> None:\n        cookies_sent, cookies_received = self.request_reply_with_same_url(\n            \"http://different.org/\"\n        )\n\n        assert set(cookies_sent.keys()) == {\"shared-cookie\", \"different-domain-cookie\"}\n\n        assert set(cookies_received.keys()) == {\n            \"unconstrained-cookie\",\n            \"different-domain-cookie\",\n        }\n\n    def test_domain_filter_host_only(self, cookies_to_receive: SimpleCookie) -> None:\n        jar = CookieJar()\n        jar.update_cookies(cookies_to_receive, URL(\"http://example.com/\"))\n        sub_cookie = SimpleCookie(\"subdomain=spam; Path=/;\")\n        jar.update_cookies(sub_cookie, URL(\"http://foo.example.com/\"))\n\n        cookies_sent = jar.filter_cookies(URL(\"http://foo.example.com/\"))\n        assert \"subdomain\" in set(cookies_sent.keys())\n        assert \"unconstrained-cookie\" not in set(cookies_sent.keys())\n\n    def test_secure_filter(self) -> None:\n        cookies_sent, _ = self.request_reply_with_same_url(\"http://secure.com/\")\n\n        assert set(cookies_sent.keys()) == {\"shared-cookie\"}\n\n        cookies_sent, _ = self.request_reply_with_same_url(\"https://secure.com/\")\n\n        assert set(cookies_sent.keys()) == {\"shared-cookie\", \"secure-cookie\"}\n\n    def test_path_filter_root(self) -> None:\n        cookies_sent, _ = self.request_reply_with_same_url(\"http://pathtest.com/\")\n\n        assert set(cookies_sent.keys()) == {\n            \"shared-cookie\",\n            \"no-path-cookie\",\n            \"path1-cookie\",\n        }\n\n    def test_path_filter_folder(self) -> None:\n        cookies_sent, _ = self.request_reply_with_same_url(\"http://pathtest.com/one/\")\n\n        assert set(cookies_sent.keys()) == {\n            \"shared-cookie\",\n            \"no-path-cookie\",\n            \"path1-cookie\",\n            \"path2-cookie\",\n        }\n\n    def test_path_filter_file(self) -> None:\n        cookies_sent, _ = self.request_reply_with_same_url(\n            \"http://pathtest.com/one/two\"\n        )\n\n        assert set(cookies_sent.keys()) == {\n            \"shared-cookie\",\n            \"no-path-cookie\",\n            \"path1-cookie\",\n            \"path2-cookie\",\n            \"path3-cookie\",\n        }\n\n    def test_path_filter_subfolder(self) -> None:\n        cookies_sent, _ = self.request_reply_with_same_url(\n            \"http://pathtest.com/one/two/\"\n        )\n\n        assert set(cookies_sent.keys()) == {\n            \"shared-cookie\",\n            \"no-path-cookie\",\n            \"path1-cookie\",\n            \"path2-cookie\",\n            \"path3-cookie\",\n            \"path4-cookie\",\n        }\n\n    def test_path_filter_subsubfolder(self) -> None:\n        cookies_sent, _ = self.request_reply_with_same_url(\n            \"http://pathtest.com/one/two/three/\"\n        )\n\n        assert set(cookies_sent.keys()) == {\n            \"shared-cookie\",\n            \"no-path-cookie\",\n            \"path1-cookie\",\n            \"path2-cookie\",\n            \"path3-cookie\",\n            \"path4-cookie\",\n        }\n\n    def test_path_filter_different_folder(self) -> None:\n        cookies_sent, _ = self.request_reply_with_same_url(\n            \"http://pathtest.com/hundred/\"\n        )\n\n        assert set(cookies_sent.keys()) == {\n            \"shared-cookie\",\n            \"no-path-cookie\",\n            \"path1-cookie\",\n        }\n\n    def test_path_value(self) -> None:\n        _, cookies_received = self.request_reply_with_same_url(\"http://pathtest.com/\")\n\n        assert set(cookies_received.keys()) == {\n            \"unconstrained-cookie\",\n            \"no-path-cookie\",\n            \"path-cookie\",\n            \"wrong-path-cookie\",\n        }\n\n        assert cookies_received[\"no-path-cookie\"][\"path\"] == \"/\"\n        assert cookies_received[\"path-cookie\"][\"path\"] == \"/somepath\"\n        assert cookies_received[\"wrong-path-cookie\"][\"path\"] == \"/\"\n\n    def test_expires(self) -> None:\n        ts_before = datetime.datetime(\n            1975, 1, 1, tzinfo=datetime.timezone.utc\n        ).timestamp()\n\n        ts_after = datetime.datetime(\n            2030, 1, 1, tzinfo=datetime.timezone.utc\n        ).timestamp()\n\n        cookies_sent = self.timed_request(\n            \"http://expirestest.com/\", ts_before, ts_before\n        )\n\n        assert set(cookies_sent.keys()) == {\"shared-cookie\", \"expires-cookie\"}\n\n        cookies_sent = self.timed_request(\n            \"http://expirestest.com/\", ts_before, ts_after\n        )\n\n        assert set(cookies_sent.keys()) == {\"shared-cookie\"}\n\n    def test_max_age(self) -> None:\n        cookies_sent = self.timed_request(\"http://maxagetest.com/\", 1000, 1000)\n\n        assert set(cookies_sent.keys()) == {\"shared-cookie\", \"max-age-cookie\"}\n\n        cookies_sent = self.timed_request(\"http://maxagetest.com/\", 1000, 2000)\n\n        assert set(cookies_sent.keys()) == {\"shared-cookie\"}\n\n    def test_invalid_values(self) -> None:\n        cookies_sent, cookies_received = self.request_reply_with_same_url(\n            \"http://invalid-values.com/\"\n        )\n\n        assert set(cookies_sent.keys()) == {\n            \"shared-cookie\",\n            \"invalid-max-age-cookie\",\n            \"invalid-expires-cookie\",\n        }\n\n        cookie = cookies_sent[\"invalid-max-age-cookie\"]\n        assert cookie[\"max-age\"] == \"\"\n\n        cookie = cookies_sent[\"invalid-expires-cookie\"]\n        assert cookie[\"expires\"] == \"\"\n\n    async def test_cookie_not_expired_when_added_after_removal(self) -> None:\n        # Test case for https://github.com/aio-libs/aiohttp/issues/2084\n        timestamps = [\n            533588.993,\n            533588.993,\n            533588.993,\n            533588.993,\n            533589.093,\n            533589.093,\n        ]\n\n        loop = mock.Mock()\n        loop.time.side_effect = itertools.chain(\n            timestamps, itertools.cycle([timestamps[-1]])\n        )\n\n        jar = CookieJar(unsafe=True)\n        # Remove `foo` cookie.\n        jar.update_cookies(SimpleCookie('foo=\"\"; Max-Age=0'))\n        # Set `foo` cookie to `bar`.\n        jar.update_cookies(SimpleCookie('foo=\"bar\"'))\n\n        # Assert that there is a cookie.\n        assert len(jar) == 1\n\n    async def test_path_filter_diff_folder_same_name(self) -> None:\n        jar = CookieJar(unsafe=True)\n\n        jar.update_cookies(\n            SimpleCookie(\"path-cookie=zero; Domain=pathtest.com; Path=/; \")\n        )\n        jar.update_cookies(\n            SimpleCookie(\"path-cookie=one; Domain=pathtest.com; Path=/one; \")\n        )\n        assert len(jar) == 2\n\n        jar_filtered = jar.filter_cookies(URL(\"http://pathtest.com/\"))\n        assert len(jar_filtered) == 1\n        assert jar_filtered[\"path-cookie\"].value == \"zero\"\n\n        jar_filtered = jar.filter_cookies(URL(\"http://pathtest.com/one\"))\n        assert len(jar_filtered) == 1\n        assert jar_filtered[\"path-cookie\"].value == \"one\"\n\n    async def test_path_filter_diff_folder_same_name_return_best_match_independent_from_put_order(\n        self,\n    ) -> None:\n        jar = CookieJar(unsafe=True)\n        jar.update_cookies(\n            SimpleCookie(\"path-cookie=one; Domain=pathtest.com; Path=/one; \")\n        )\n        jar.update_cookies(\n            SimpleCookie(\"path-cookie=zero; Domain=pathtest.com; Path=/; \")\n        )\n        jar.update_cookies(\n            SimpleCookie(\"path-cookie=two; Domain=pathtest.com; Path=/second; \")\n        )\n        assert len(jar) == 3\n\n        jar_filtered = jar.filter_cookies(URL(\"http://pathtest.com/\"))\n        assert len(jar_filtered) == 1\n        assert jar_filtered[\"path-cookie\"].value == \"zero\"\n\n        jar_filtered = jar.filter_cookies(URL(\"http://pathtest.com/second\"))\n        assert len(jar_filtered) == 1\n        assert jar_filtered[\"path-cookie\"].value == \"two\"\n\n        jar_filtered = jar.filter_cookies(URL(\"http://pathtest.com/one\"))\n        assert len(jar_filtered) == 1\n        assert jar_filtered[\"path-cookie\"].value == \"one\"\n\n\nasync def test_dummy_cookie_jar() -> None:\n    cookie = SimpleCookie(\"foo=bar; Domain=example.com;\")\n    dummy_jar = DummyCookieJar()\n    assert dummy_jar.quote_cookie is True\n    assert len(dummy_jar) == 0\n    dummy_jar.update_cookies(cookie)\n    assert len(dummy_jar) == 0\n    with pytest.raises(StopIteration):\n        next(iter(dummy_jar))\n    assert not dummy_jar.filter_cookies(URL(\"http://example.com/\"))\n    dummy_jar.clear()\n\n\nasync def test_loose_cookies_types() -> None:\n    jar = CookieJar()\n\n    accepted_types: tuple[LooseCookies, ...] = (\n        [(\"str\", BaseCookie())],\n        [(\"str\", Morsel())],\n        [(\"str\", \"str\")],\n        {\"str\": BaseCookie()},\n        {\"str\": Morsel()},\n        {\"str\": \"str\"},\n        SimpleCookie(),\n    )\n\n    for loose_cookies_type in accepted_types:\n        jar.update_cookies(cookies=loose_cookies_type)\n\n\nasync def test_cookie_jar_clear_all() -> None:\n    sut = CookieJar()\n    cookie = SimpleCookie()\n    cookie[\"foo\"] = \"bar\"\n    sut.update_cookies(cookie)\n\n    sut.clear()\n    assert len(sut) == 0\n\n\nasync def test_cookie_jar_clear_expired() -> None:\n    sut = CookieJar()\n\n    cookie = SimpleCookie()\n\n    cookie[\"foo\"] = \"bar\"\n    cookie[\"foo\"][\"expires\"] = \"Tue, 1 Jan 1990 12:00:00 GMT\"\n\n    with freeze_time(\"1980-01-01\"):\n        sut.update_cookies(cookie)\n\n    for _ in range(2):\n        sut.clear(not_)\n        with freeze_time(\"1980-01-01\"):\n            assert len(sut) == 0\n\n\nasync def test_cookie_jar_expired_changes() -> None:\n    \"\"\"Test that expire time changes are handled as expected.\"\"\"\n    jar = CookieJar()\n\n    cookie_eleven_am = SimpleCookie()\n    cookie_eleven_am[\"foo\"] = \"bar\"\n    cookie_eleven_am[\"foo\"][\"expires\"] = \"Tue, 1 Jan 1990 11:00:00 GMT\"\n\n    cookie_noon = SimpleCookie()\n    cookie_noon[\"foo\"] = \"bar\"\n    cookie_noon[\"foo\"][\"expires\"] = \"Tue, 1 Jan 1990 12:00:00 GMT\"\n\n    cookie_one_pm = SimpleCookie()\n    cookie_one_pm[\"foo\"] = \"bar\"\n    cookie_one_pm[\"foo\"][\"expires\"] = \"Tue, 1 Jan 1990 13:00:00 GMT\"\n\n    cookie_two_pm = SimpleCookie()\n    cookie_two_pm[\"foo\"] = \"bar\"\n    cookie_two_pm[\"foo\"][\"expires\"] = \"Tue, 1 Jan 1990 14:00:00 GMT\"\n\n    with freeze_time() as freezer:\n        freezer.move_to(\"1990-01-01 10:00:00+00:00\")\n        jar.update_cookies(cookie_noon)\n        assert len(jar) == 1\n        matched_cookies = jar.filter_cookies(URL(\"/\"))\n        assert len(matched_cookies) == 1\n        assert \"foo\" in matched_cookies\n\n        jar.update_cookies(cookie_eleven_am)\n        matched_cookies = jar.filter_cookies(URL(\"/\"))\n        assert len(matched_cookies) == 1\n        assert \"foo\" in matched_cookies\n\n        jar.update_cookies(cookie_one_pm)\n        matched_cookies = jar.filter_cookies(URL(\"/\"))\n        assert len(matched_cookies) == 1\n        assert \"foo\" in matched_cookies\n\n        jar.update_cookies(cookie_two_pm)\n        matched_cookies = jar.filter_cookies(URL(\"/\"))\n        assert len(matched_cookies) == 1\n        assert \"foo\" in matched_cookies\n\n        freezer.move_to(\"1990-01-01 13:00:00+00:00\")\n        matched_cookies = jar.filter_cookies(URL(\"/\"))\n        assert len(matched_cookies) == 1\n        assert \"foo\" in matched_cookies\n\n        freezer.move_to(\"1990-01-01 14:00:00+00:00\")\n        matched_cookies = jar.filter_cookies(URL(\"/\"))\n        assert len(matched_cookies) == 0\n\n\nasync def test_cookie_jar_duplicates_with_expire_heap() -> None:\n    \"\"\"Test that duplicate cookies do not grow the expires heap.\"\"\"\n    jar = CookieJar()\n\n    cookie_eleven_am = SimpleCookie()\n    cookie_eleven_am[\"foo\"] = \"bar\"\n    cookie_eleven_am[\"foo\"][\"expires\"] = \"Tue, 1 Jan 1990 11:00:00 GMT\"\n\n    cookie_two_pm = SimpleCookie()\n    cookie_two_pm[\"foo\"] = \"bar\"\n    cookie_two_pm[\"foo\"][\"expires\"] = \"Tue, 1 Jan 1990 14:00:00 GMT\"\n\n    with freeze_time() as freezer:\n        freezer.move_to(\"1990-01-01 10:00:00+00:00\")\n\n        for _ in range(10):\n            jar.update_cookies(cookie_eleven_am)\n\n        assert len(jar) == 1\n        matched_cookies = jar.filter_cookies(URL(\"/\"))\n        assert len(matched_cookies) == 1\n        assert \"foo\" in matched_cookies\n\n        assert len(jar._expire_heap) == 1\n\n        freezer.move_to(\"1990-01-01 16:00:00+00:00\")\n        jar.update_cookies(cookie_two_pm)\n        matched_cookies = jar.filter_cookies(URL(\"/\"))\n        assert len(matched_cookies) == 0\n        assert len(jar._expire_heap) == 0\n\n\nasync def test_cookie_jar_filter_cookies_expires() -> None:\n    \"\"\"Test that calling filter_cookies will expire stale cookies.\"\"\"\n    jar = CookieJar()\n    assert len(jar) == 0\n\n    cookie = SimpleCookie()\n\n    cookie[\"foo\"] = \"bar\"\n    cookie[\"foo\"][\"expires\"] = \"Tue, 1 Jan 1990 12:00:00 GMT\"\n\n    with freeze_time(\"1980-01-01\"):\n        jar.update_cookies(cookie)\n\n    assert len(jar) == 1\n\n    # filter_cookies should expire stale cookies\n    jar.filter_cookies(URL(\"http://any.com/\"))\n\n    assert len(jar) == 0\n\n\nasync def test_cookie_jar_heap_cleanup() -> None:\n    \"\"\"Test that the heap gets cleaned up when there are many old expirations.\"\"\"\n    jar = CookieJar()\n    # The heap should not be cleaned up when there are less than 100 expiration changes\n    min_cookies_to_cleanup = 100\n\n    with freeze_time() as freezer:\n        freezer.move_to(\"1990-01-01 09:00:00+00:00\")\n\n        start_time = datetime.datetime(\n            1990, 1, 1, 10, 0, 0, tzinfo=datetime.timezone.utc\n        )\n        for i in range(min_cookies_to_cleanup):\n            cookie = SimpleCookie()\n            cookie[\"foo\"] = \"bar\"\n            cookie[\"foo\"][\"expires\"] = (\n                start_time + datetime.timedelta(seconds=i)\n            ).strftime(\"%a, %d %b %Y %H:%M:%S GMT\")\n            jar.update_cookies(cookie)\n            assert len(jar._expire_heap) == i + 1\n\n        assert len(jar._expire_heap) == min_cookies_to_cleanup\n\n        # Now that we reached the minimum number of cookies to cleanup,\n        # add one more cookie to trigger the cleanup\n        cookie = SimpleCookie()\n        cookie[\"foo\"] = \"bar\"\n        cookie[\"foo\"][\"expires\"] = (\n            start_time + datetime.timedelta(seconds=i + 1)\n        ).strftime(\"%a, %d %b %Y %H:%M:%S GMT\")\n        jar.update_cookies(cookie)\n\n        # Verify that the heap has been cleaned up\n        assert len(jar) == 1\n        matched_cookies = jar.filter_cookies(URL(\"/\"))\n        assert len(matched_cookies) == 1\n        assert \"foo\" in matched_cookies\n        # The heap should have been cleaned up\n        assert len(jar._expire_heap) == 1\n\n\nasync def test_cookie_jar_heap_maintains_order_after_cleanup() -> None:\n    \"\"\"Test that order is maintained after cleanup.\"\"\"\n    jar = CookieJar()\n    # The heap should not be cleaned up when there are less than 100 expiration changes\n    min_cookies_to_cleanup = 100\n\n    with freeze_time() as freezer:\n        freezer.move_to(\"1990-01-01 09:00:00+00:00\")\n\n        for hour in (12, 13):\n            for i in range(min_cookies_to_cleanup):\n                cookie = SimpleCookie()\n                cookie[\"foo\"] = \"bar\"\n                cookie[\"foo\"][\"domain\"] = f\"example{i}.com\"\n                cookie[\"foo\"][\"expires\"] = f\"Tue, 1 Jan 1990 {hour}:00:00 GMT\"\n                jar.update_cookies(cookie)\n\n        # Get the jar into a state where the next cookie will trigger the cleanup\n        assert len(jar._expire_heap) == min_cookies_to_cleanup * 2\n        assert len(jar._expirations) == min_cookies_to_cleanup\n\n        cookie = SimpleCookie()\n        cookie[\"foo\"] = \"bar\"\n        cookie[\"foo\"][\"domain\"] = \"example0.com\"\n        cookie[\"foo\"][\"expires\"] = \"Tue, 1 Jan 1990 14:00:00 GMT\"\n        jar.update_cookies(cookie)\n\n        assert len(jar) == 100\n        # The heap should have been cleaned up\n        assert len(jar._expire_heap) == 100\n\n        # Verify that the heap is still ordered\n        heap_before = jar._expire_heap.copy()\n        heapq.heapify(jar._expire_heap)\n        assert heap_before == jar._expire_heap\n\n\nasync def test_cookie_jar_clear_domain() -> None:\n    sut = CookieJar()\n    cookie = SimpleCookie()\n    cookie[\"foo\"] = \"bar\"\n    cookie[\"domain_cookie\"] = \"value\"\n    cookie[\"domain_cookie\"][\"domain\"] = \"example.com\"\n    cookie[\"subdomain_cookie\"] = \"value\"\n    cookie[\"subdomain_cookie\"][\"domain\"] = \"test.example.com\"\n    sut.update_cookies(cookie)\n\n    sut.clear_domain(\"example.com\")\n    iterator = iter(sut)\n    morsel = next(iterator)\n    assert morsel.key == \"foo\"\n    assert morsel.value == \"bar\"\n    with pytest.raises(StopIteration):\n        next(iterator)\n\n\ndef test_pickle_format(cookies_to_send: SimpleCookie) -> None:\n    \"\"\"Test if cookiejar pickle format breaks.\n\n    If this test fails, it may indicate that saved cookiejars will stop working.\n    If that happens then:\n        1. Avoid releasing the change in a bugfix release.\n        2. Try to include a migration script in the release notes (example below).\n        3. Use dump_cookiejar() at the top of this file to update `pickled`.\n\n    Depending on the changes made, a migration script might look like:\n        import pickle\n        with file_path.open(\"rb\") as f:\n            cookies = pickle.load(f)\n\n        morsels = [(name, m) for c in cookies.values() for name, m in c.items()]\n        cookies.clear()\n        for name, m in morsels:\n            cookies[(m[\"domain\"], m[\"path\"])][name] = m\n\n        with file_path.open(\"wb\") as f:\n            pickle.dump(cookies, f, pickle.HIGHEST_PROTOCOL)\n    \"\"\"\n    if sys.version_info < (3, 14):\n        pickled = b\"\\x80\\x04\\x95\\xc8\\x0b\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x0bcollections\\x94\\x8c\\x0bdefaultdict\\x94\\x93\\x94\\x8c\\x0chttp.cookies\\x94\\x8c\\x0cSimpleCookie\\x94\\x93\\x94\\x85\\x94R\\x94(\\x8c\\x00\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94\\x8c\\rshared-cookie\\x94h\\x03\\x8c\\x06Morsel\\x94\\x93\\x94)\\x81\\x94(\\x8c\\x07expires\\x94h\\x08\\x8c\\x04path\\x94\\x8c\\x01/\\x94\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94h\\x08\\x8c\\x07max-age\\x94h\\x08\\x8c\\x06secure\\x94h\\x08\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08u}\\x94(\\x8c\\x03key\\x94h\\x0b\\x8c\\x05value\\x94\\x8c\\x05first\\x94\\x8c\\x0bcoded_value\\x94h\\x1cubs\\x8c\\x0bexample.com\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94(\\x8c\\rdomain-cookie\\x94h\\r)\\x81\\x94(\\x8c\\x07expires\\x94h\\x08\\x8c\\x04path\\x94h\\x11\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94h\\x1e\\x8c\\x07max-age\\x94h\\x08\\x8c\\x06secure\\x94h\\x08\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08u}\\x94(h\\x1ah!h\\x1b\\x8c\\x06second\\x94h\\x1dh-ub\\x8c\\x14dotted-domain-cookie\\x94h\\r)\\x81\\x94(\\x8c\\x07expires\\x94h\\x08\\x8c\\x04path\\x94h\\x11\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94\\x8c\\x0bexample.com\\x94\\x8c\\x07max-age\\x94h\\x08\\x8c\\x06secure\\x94h\\x08\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08u}\\x94(h\\x1ah.h\\x1b\\x8c\\x05fifth\\x94h\\x1dh;ubu\\x8c\\x11test1.example.com\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94\\x8c\\x11subdomain1-cookie\\x94h\\r)\\x81\\x94(\\x8c\\x07expires\\x94h\\x08\\x8c\\x04path\\x94h\\x11\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94h<\\x8c\\x07max-age\\x94h\\x08\\x8c\\x06secure\\x94h\\x08\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08u}\\x94(h\\x1ah?h\\x1b\\x8c\\x05third\\x94h\\x1dhKubs\\x8c\\x11test2.example.com\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94\\x8c\\x11subdomain2-cookie\\x94h\\r)\\x81\\x94(\\x8c\\x07expires\\x94h\\x08\\x8c\\x04path\\x94h\\x11\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94hL\\x8c\\x07max-age\\x94h\\x08\\x8c\\x06secure\\x94h\\x08\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08u}\\x94(h\\x1ahOh\\x1b\\x8c\\x06fourth\\x94h\\x1dh[ubs\\x8c\\rdifferent.org\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94\\x8c\\x17different-domain-cookie\\x94h\\r)\\x81\\x94(\\x8c\\x07expires\\x94h\\x08\\x8c\\x04path\\x94h\\x11\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94h\\\\\\x8c\\x07max-age\\x94h\\x08\\x8c\\x06secure\\x94h\\x08\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08u}\\x94(h\\x1ah_h\\x1b\\x8c\\x05sixth\\x94h\\x1dhkubs\\x8c\\nsecure.com\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94\\x8c\\rsecure-cookie\\x94h\\r)\\x81\\x94(\\x8c\\x07expires\\x94h\\x08\\x8c\\x04path\\x94h\\x11\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94hl\\x8c\\x07max-age\\x94h\\x08\\x8c\\x06secure\\x94\\x88\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08u}\\x94(h\\x1ahoh\\x1b\\x8c\\x07seventh\\x94h\\x1dh{ubs\\x8c\\x0cpathtest.com\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94(\\x8c\\x0eno-path-cookie\\x94h\\r)\\x81\\x94(\\x8c\\x07expires\\x94h\\x08\\x8c\\x04path\\x94h\\x11\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94h|\\x8c\\x07max-age\\x94h\\x08\\x8c\\x06secure\\x94h\\x08\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08u}\\x94(h\\x1ah\\x7fh\\x1b\\x8c\\x06eighth\\x94h\\x1dh\\x8bub\\x8c\\x0cpath1-cookie\\x94h\\r)\\x81\\x94(\\x8c\\x07expires\\x94h\\x08\\x8c\\x04path\\x94h\\x11\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94\\x8c\\x0cpathtest.com\\x94\\x8c\\x07max-age\\x94h\\x08\\x8c\\x06secure\\x94h\\x08\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08u}\\x94(h\\x1ah\\x8ch\\x1b\\x8c\\x05ninth\\x94h\\x1dh\\x99ubu\\x8c\\x0cpathtest.com\\x94\\x8c\\x04/one\\x94\\x86\\x94h\\x05)\\x81\\x94\\x8c\\x0cpath2-cookie\\x94h\\r)\\x81\\x94(\\x8c\\x07expires\\x94h\\x08\\x8c\\x04path\\x94h\\x9b\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94h\\x9a\\x8c\\x07max-age\\x94h\\x08\\x8c\\x06secure\\x94h\\x08\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08u}\\x94(h\\x1ah\\x9eh\\x1b\\x8c\\x05tenth\\x94h\\x1dh\\xaaubs\\x8c\\x0cpathtest.com\\x94\\x8c\\x08/one/two\\x94\\x86\\x94h\\x05)\\x81\\x94(\\x8c\\x0cpath3-cookie\\x94h\\r)\\x81\\x94(\\x8c\\x07expires\\x94h\\x08\\x8c\\x04path\\x94h\\xac\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94h\\xab\\x8c\\x07max-age\\x94h\\x08\\x8c\\x06secure\\x94h\\x08\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08u}\\x94(h\\x1ah\\xafh\\x1b\\x8c\\x08eleventh\\x94h\\x1dh\\xbbub\\x8c\\x0cpath4-cookie\\x94h\\r)\\x81\\x94(\\x8c\\x07expires\\x94h\\x08\\x8c\\x04path\\x94\\x8c\\t/one/two/\\x94\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94\\x8c\\x0cpathtest.com\\x94\\x8c\\x07max-age\\x94h\\x08\\x8c\\x06secure\\x94h\\x08\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08u}\\x94(h\\x1ah\\xbch\\x1b\\x8c\\x07twelfth\\x94h\\x1dh\\xcaubu\\x8c\\x0fexpirestest.com\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94\\x8c\\x0eexpires-cookie\\x94h\\r)\\x81\\x94(\\x8c\\x07expires\\x94\\x8c\\x1cTue, 1 Jan 2999 12:00:00 GMT\\x94\\x8c\\x04path\\x94h\\x11\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94h\\xcb\\x8c\\x07max-age\\x94h\\x08\\x8c\\x06secure\\x94h\\x08\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08u}\\x94(h\\x1ah\\xceh\\x1b\\x8c\\nthirteenth\\x94h\\x1dh\\xdbubs\\x8c\\x0emaxagetest.com\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94\\x8c\\x0emax-age-cookie\\x94h\\r)\\x81\\x94(\\x8c\\x07expires\\x94h\\x08\\x8c\\x04path\\x94h\\x11\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94h\\xdc\\x8c\\x07max-age\\x94\\x8c\\x0260\\x94\\x8c\\x06secure\\x94h\\x08\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08u}\\x94(h\\x1ah\\xdfh\\x1b\\x8c\\nfourteenth\\x94h\\x1dh\\xecubs\\x8c\\x12invalid-values.com\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94(\\x8c\\x16invalid-max-age-cookie\\x94h\\r)\\x81\\x94(\\x8c\\x07expires\\x94h\\x08\\x8c\\x04path\\x94h\\x11\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94h\\xed\\x8c\\x07max-age\\x94h\\x08\\x8c\\x06secure\\x94h\\x08\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08u}\\x94(h\\x1ah\\xf0h\\x1b\\x8c\\tfifteenth\\x94h\\x1dh\\xfcub\\x8c\\x16invalid-expires-cookie\\x94h\\r)\\x81\\x94(\\x8c\\x07expires\\x94h\\x08\\x8c\\x04path\\x94h\\x11\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94\\x8c\\x12invalid-values.com\\x94\\x8c\\x07max-age\\x94h\\x08\\x8c\\x06secure\\x94h\\x08\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08u}\\x94(h\\x1ah\\xfdh\\x1b\\x8c\\tsixteenth\\x94h\\x1dj\\n\\x01\\x00\\x00ubuu.\"\n    else:\n        pickled = b'\\x80\\x05\\x95\\x06\\x08\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x0bcollections\\x94\\x8c\\x0bdefaultdict\\x94\\x93\\x94\\x8c\\x0chttp.cookies\\x94\\x8c\\x0cSimpleCookie\\x94\\x93\\x94\\x85\\x94R\\x94(\\x8c\\x00\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94\\x8c\\rshared-cookie\\x94h\\x03\\x8c\\x06Morsel\\x94\\x93\\x94)\\x81\\x94(\\x8c\\x07expires\\x94h\\x08\\x8c\\x04path\\x94\\x8c\\x01/\\x94\\x8c\\x07comment\\x94h\\x08\\x8c\\x06domain\\x94h\\x08\\x8c\\x07max-age\\x94h\\x08\\x8c\\x06secure\\x94h\\x08\\x8c\\x08httponly\\x94h\\x08\\x8c\\x07version\\x94h\\x08\\x8c\\x08samesite\\x94h\\x08\\x8c\\x0bpartitioned\\x94h\\x08u}\\x94(\\x8c\\x03key\\x94h\\x0b\\x8c\\x05value\\x94\\x8c\\x05first\\x94\\x8c\\x0bcoded_value\\x94h\\x1dubs\\x8c\\x0bexample.com\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94(\\x8c\\rdomain-cookie\\x94h\\r)\\x81\\x94(h\\x0fh\\x08h\\x10h\\x11h\\x12h\\x08h\\x13h\\x1fh\\x14h\\x08h\\x15h\\x08h\\x16h\\x08h\\x17h\\x08h\\x18h\\x08h\\x19h\\x08u}\\x94(h\\x1bh\"h\\x1c\\x8c\\x06second\\x94h\\x1eh%ub\\x8c\\x14dotted-domain-cookie\\x94h\\r)\\x81\\x94(h\\x0fh\\x08h\\x10h\\x11h\\x12h\\x08h\\x13\\x8c\\x0bexample.com\\x94h\\x14h\\x08h\\x15h\\x08h\\x16h\\x08h\\x17h\\x08h\\x18h\\x08h\\x19h\\x08u}\\x94(h\\x1bh&h\\x1c\\x8c\\x05fifth\\x94h\\x1eh*ubu\\x8c\\x11test1.example.com\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94\\x8c\\x11subdomain1-cookie\\x94h\\r)\\x81\\x94(h\\x0fh\\x08h\\x10h\\x11h\\x12h\\x08h\\x13h+h\\x14h\\x08h\\x15h\\x08h\\x16h\\x08h\\x17h\\x08h\\x18h\\x08h\\x19h\\x08u}\\x94(h\\x1bh.h\\x1c\\x8c\\x05third\\x94h\\x1eh1ubs\\x8c\\x11test2.example.com\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94\\x8c\\x11subdomain2-cookie\\x94h\\r)\\x81\\x94(h\\x0fh\\x08h\\x10h\\x11h\\x12h\\x08h\\x13h2h\\x14h\\x08h\\x15h\\x08h\\x16h\\x08h\\x17h\\x08h\\x18h\\x08h\\x19h\\x08u}\\x94(h\\x1bh5h\\x1c\\x8c\\x06fourth\\x94h\\x1eh8ubs\\x8c\\rdifferent.org\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94\\x8c\\x17different-domain-cookie\\x94h\\r)\\x81\\x94(h\\x0fh\\x08h\\x10h\\x11h\\x12h\\x08h\\x13h9h\\x14h\\x08h\\x15h\\x08h\\x16h\\x08h\\x17h\\x08h\\x18h\\x08h\\x19h\\x08u}\\x94(h\\x1bh<h\\x1c\\x8c\\x05sixth\\x94h\\x1eh?ubs\\x8c\\nsecure.com\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94\\x8c\\rsecure-cookie\\x94h\\r)\\x81\\x94(h\\x0fh\\x08h\\x10h\\x11h\\x12h\\x08h\\x13h@h\\x14h\\x08h\\x15\\x88h\\x16h\\x08h\\x17h\\x08h\\x18h\\x08h\\x19h\\x08u}\\x94(h\\x1bhCh\\x1c\\x8c\\x07seventh\\x94h\\x1ehFubs\\x8c\\x0cpathtest.com\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94(\\x8c\\x0eno-path-cookie\\x94h\\r)\\x81\\x94(h\\x0fh\\x08h\\x10h\\x11h\\x12h\\x08h\\x13hGh\\x14h\\x08h\\x15h\\x08h\\x16h\\x08h\\x17h\\x08h\\x18h\\x08h\\x19h\\x08u}\\x94(h\\x1bhJh\\x1c\\x8c\\x06eighth\\x94h\\x1ehMub\\x8c\\x0cpath1-cookie\\x94h\\r)\\x81\\x94(h\\x0fh\\x08h\\x10h\\x11h\\x12h\\x08h\\x13\\x8c\\x0cpathtest.com\\x94h\\x14h\\x08h\\x15h\\x08h\\x16h\\x08h\\x17h\\x08h\\x18h\\x08h\\x19h\\x08u}\\x94(h\\x1bhNh\\x1c\\x8c\\x05ninth\\x94h\\x1ehRubu\\x8c\\x0cpathtest.com\\x94\\x8c\\x04/one\\x94\\x86\\x94h\\x05)\\x81\\x94\\x8c\\x0cpath2-cookie\\x94h\\r)\\x81\\x94(h\\x0fh\\x08h\\x10hTh\\x12h\\x08h\\x13hSh\\x14h\\x08h\\x15h\\x08h\\x16h\\x08h\\x17h\\x08h\\x18h\\x08h\\x19h\\x08u}\\x94(h\\x1bhWh\\x1c\\x8c\\x05tenth\\x94h\\x1ehZubs\\x8c\\x0cpathtest.com\\x94\\x8c\\x08/one/two\\x94\\x86\\x94h\\x05)\\x81\\x94(\\x8c\\x0cpath3-cookie\\x94h\\r)\\x81\\x94(h\\x0fh\\x08h\\x10h\\\\h\\x12h\\x08h\\x13h[h\\x14h\\x08h\\x15h\\x08h\\x16h\\x08h\\x17h\\x08h\\x18h\\x08h\\x19h\\x08u}\\x94(h\\x1bh_h\\x1c\\x8c\\x08eleventh\\x94h\\x1ehbub\\x8c\\x0cpath4-cookie\\x94h\\r)\\x81\\x94(h\\x0fh\\x08h\\x10\\x8c\\t/one/two/\\x94h\\x12h\\x08h\\x13\\x8c\\x0cpathtest.com\\x94h\\x14h\\x08h\\x15h\\x08h\\x16h\\x08h\\x17h\\x08h\\x18h\\x08h\\x19h\\x08u}\\x94(h\\x1bhch\\x1c\\x8c\\x07twelfth\\x94h\\x1ehhubu\\x8c\\x0fexpirestest.com\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94\\x8c\\x0eexpires-cookie\\x94h\\r)\\x81\\x94(h\\x0f\\x8c\\x1cTue, 1 Jan 2999 12:00:00 GMT\\x94h\\x10h\\x11h\\x12h\\x08h\\x13hih\\x14h\\x08h\\x15h\\x08h\\x16h\\x08h\\x17h\\x08h\\x18h\\x08h\\x19h\\x08u}\\x94(h\\x1bhlh\\x1c\\x8c\\nthirteenth\\x94h\\x1ehpubs\\x8c\\x0emaxagetest.com\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94\\x8c\\x0emax-age-cookie\\x94h\\r)\\x81\\x94(h\\x0fh\\x08h\\x10h\\x11h\\x12h\\x08h\\x13hqh\\x14\\x8c\\x0260\\x94h\\x15h\\x08h\\x16h\\x08h\\x17h\\x08h\\x18h\\x08h\\x19h\\x08u}\\x94(h\\x1bhth\\x1c\\x8c\\nfourteenth\\x94h\\x1ehxubs\\x8c\\x12invalid-values.com\\x94h\\x08\\x86\\x94h\\x05)\\x81\\x94(\\x8c\\x16invalid-max-age-cookie\\x94h\\r)\\x81\\x94(h\\x0fh\\x08h\\x10h\\x11h\\x12h\\x08h\\x13hyh\\x14h\\x08h\\x15h\\x08h\\x16h\\x08h\\x17h\\x08h\\x18h\\x08h\\x19h\\x08u}\\x94(h\\x1bh|h\\x1c\\x8c\\tfifteenth\\x94h\\x1eh\\x7fub\\x8c\\x16invalid-expires-cookie\\x94h\\r)\\x81\\x94(h\\x0fh\\x08h\\x10h\\x11h\\x12h\\x08h\\x13\\x8c\\x12invalid-values.com\\x94h\\x14h\\x08h\\x15h\\x08h\\x16h\\x08h\\x17h\\x08h\\x18h\\x08h\\x19h\\x08u}\\x94(h\\x1bh\\x80h\\x1c\\x8c\\tsixteenth\\x94h\\x1eh\\x84ubuu.'\n\n    cookies = pickle.loads(pickled)\n\n    cj = CookieJar()\n    cj.update_cookies(cookies_to_send)\n\n    assert cookies == cj._cookies\n\n\n@pytest.mark.parametrize(\n    \"url\",\n    [\n        \"http://127.0.0.1/index.html\",\n        URL(\"http://127.0.0.1/index.html\"),\n        [\"http://127.0.0.1/index.html\"],\n        [URL(\"http://127.0.0.1/index.html\")],\n    ],\n)\nasync def test_treat_as_secure_origin_init(\n    url: str | URL | list[str] | list[URL],\n) -> None:\n    jar = CookieJar(unsafe=True, treat_as_secure_origin=url)\n    assert jar._treat_as_secure_origin == frozenset({URL(\"http://127.0.0.1\")})\n\n\nasync def test_treat_as_secure_origin() -> None:\n    endpoint = URL(\"http://127.0.0.1/\")\n\n    jar = CookieJar(unsafe=True, treat_as_secure_origin=[endpoint])\n    secure_cookie = SimpleCookie(\n        \"cookie-key=cookie-value; HttpOnly; Path=/; Secure\",\n    )\n\n    jar.update_cookies(\n        secure_cookie,\n        endpoint,\n    )\n\n    assert len(jar) == 1\n    filtered_cookies = jar.filter_cookies(request_url=endpoint)\n    assert len(filtered_cookies) == 1\n\n\nasync def test_filter_cookies_does_not_leak_memory() -> None:\n    \"\"\"Test that filter_cookies doesn't create empty cookie entries.\n\n    Regression test for https://github.com/aio-libs/aiohttp/issues/11052\n    \"\"\"\n    jar = CookieJar()\n\n    # Set a cookie with Path=/\n    jar.update_cookies({\"test_cookie\": \"value; Path=/\"}, URL(\"http://example.com/\"))\n\n    # Check initial state\n    assert len(jar) == 1\n    initial_storage_size = len(jar._cookies)\n    initial_morsel_cache_size = len(jar._morsel_cache)\n\n    # Make multiple requests with different paths\n    paths = [\n        \"/\",\n        \"/api\",\n        \"/api/v1\",\n        \"/api/v1/users\",\n        \"/api/v1/users/123\",\n        \"/static/css/style.css\",\n        \"/images/logo.png\",\n    ]\n\n    for path in paths:\n        url = URL(f\"http://example.com{path}\")\n        filtered = jar.filter_cookies(url)\n        # Should still get the cookie\n        assert len(filtered) == 1\n        assert \"test_cookie\" in filtered\n\n    # Storage size should not grow significantly\n    # Only the shared cookie entry ('', '') may be added\n    final_storage_size = len(jar._cookies)\n    assert final_storage_size <= initial_storage_size + 1\n\n    # Verify _morsel_cache doesn't leak either\n    # It should only have entries for domains/paths where cookies exist\n    final_morsel_cache_size = len(jar._morsel_cache)\n    assert final_morsel_cache_size <= initial_morsel_cache_size + 1\n\n    # Verify no empty entries were created for domain-path combinations\n    for key, cookies in jar._cookies.items():\n        if key != (\"\", \"\"):  # Skip the shared cookie entry\n            assert len(cookies) > 0, f\"Empty cookie entry found for {key}\"\n\n    # Verify _morsel_cache entries correspond to actual cookies\n    for key, morsels in jar._morsel_cache.items():\n        assert key in jar._cookies, f\"Orphaned morsel cache entry for {key}\"\n        assert len(morsels) > 0, f\"Empty morsel cache entry found for {key}\"\n\n\ndef test_update_cookies_from_headers() -> None:\n    \"\"\"Test update_cookies_from_headers method.\"\"\"\n    jar: CookieJar = CookieJar()\n    url: URL = URL(\"http://example.com/path\")\n\n    # Test with simple cookies\n    headers = [\n        \"session-id=123456; Path=/\",\n        \"user-pref=dark-mode; Domain=.example.com\",\n        \"tracking=xyz789; Secure; HttpOnly\",\n    ]\n\n    jar.update_cookies_from_headers(headers, url)\n\n    # Verify all cookies were added to the jar\n    assert len(jar) == 3\n\n    # Check cookies available for HTTP URL (secure cookie should be filtered out)\n    filtered_http: BaseCookie[str] = jar.filter_cookies(url)\n    assert len(filtered_http) == 2\n    assert \"session-id\" in filtered_http\n    assert filtered_http[\"session-id\"].value == \"123456\"\n    assert \"user-pref\" in filtered_http\n    assert filtered_http[\"user-pref\"].value == \"dark-mode\"\n    assert \"tracking\" not in filtered_http  # Secure cookie not available on HTTP\n\n    # Check cookies available for HTTPS URL (all cookies should be available)\n    url_https: URL = URL(\"https://example.com/path\")\n    filtered_https: BaseCookie[str] = jar.filter_cookies(url_https)\n    assert len(filtered_https) == 3\n    assert \"tracking\" in filtered_https\n    assert filtered_https[\"tracking\"].value == \"xyz789\"\n\n\ndef test_update_cookies_from_headers_duplicate_names() -> None:\n    \"\"\"Test that duplicate cookie names with different domains are preserved.\"\"\"\n    jar: CookieJar = CookieJar()\n    url: URL = URL(\"http://www.example.com/\")\n\n    # Headers with duplicate names but different domains\n    headers = [\n        \"session-id=123456; Domain=.example.com; Path=/\",\n        \"session-id=789012; Domain=.www.example.com; Path=/\",\n        \"user-pref=light; Domain=.example.com\",\n        \"user-pref=dark; Domain=sub.example.com\",\n    ]\n\n    jar.update_cookies_from_headers(headers, url)\n\n    # Should have 3 cookies (user-pref=dark for sub.example.com is rejected)\n    assert len(jar) == 3\n\n    # Verify we have both session-id cookies\n    all_cookies: list[Morsel[str]] = list(jar)\n    session_ids: list[Morsel[str]] = [c for c in all_cookies if c.key == \"session-id\"]\n    assert len(session_ids) == 2\n\n    # Check their domains are different\n    domains: set[str] = {c[\"domain\"] for c in session_ids}\n    assert domains == {\"example.com\", \"www.example.com\"}\n\n\ndef test_update_cookies_from_headers_invalid_cookies(\n    caplog: pytest.LogCaptureFixture,\n) -> None:\n    \"\"\"Test that invalid cookies are logged and skipped.\"\"\"\n    jar: CookieJar = CookieJar()\n    url: URL = URL(\"http://example.com/\")\n\n    # Mix of valid and invalid cookies\n    headers = [\n        \"valid-cookie=value123\",\n        \"invalid,cookie=value; \"  # Comma character is not allowed\n        \"HttpOnly; Path=/\",\n        \"another-valid=value456\",\n    ]\n\n    # Enable logging for the client logger\n    with caplog.at_level(logging.WARNING, logger=\"aiohttp.client\"):\n        jar.update_cookies_from_headers(headers, url)\n\n    # Check that we logged warnings for invalid cookies\n    assert \"Can not load cookies\" in caplog.text\n\n    # Valid cookies should still be added\n    assert len(jar) >= 2  # At least the two clearly valid cookies\n    filtered: BaseCookie[str] = jar.filter_cookies(url)\n    assert \"valid-cookie\" in filtered\n    assert \"another-valid\" in filtered\n\n\ndef test_update_cookies_from_headers_with_curly_braces() -> None:\n    \"\"\"Test that cookies with curly braces in names are now accepted (#2683).\"\"\"\n    jar: CookieJar = CookieJar()\n    url: URL = URL(\"http://example.com/\")\n\n    # Cookie names with curly braces should now be accepted\n    headers = [\n        \"ISAWPLB{A7F52349-3531-4DA9-8776-F74BC6F4F1BB}=\"\n        \"{925EC0B8-CB17-4BEB-8A35-1033813B0523}; \"\n        \"HttpOnly; Path=/\",\n        \"regular-cookie=value123\",\n    ]\n\n    jar.update_cookies_from_headers(headers, url)\n\n    # Both cookies should be added\n    assert len(jar) == 2\n    filtered: BaseCookie[str] = jar.filter_cookies(url)\n    assert \"ISAWPLB{A7F52349-3531-4DA9-8776-F74BC6F4F1BB}\" in filtered\n    assert \"regular-cookie\" in filtered\n\n\ndef test_update_cookies_from_headers_with_special_chars() -> None:\n    \"\"\"Test that cookies with various special characters are accepted.\"\"\"\n    jar: CookieJar = CookieJar()\n    url: URL = URL(\"http://example.com/\")\n\n    # Various special characters that should now be accepted\n    headers = [\n        \"cookie_with_parens=(value)=test123\",\n        \"cookie-with-brackets[index]=value456\",\n        \"cookie@with@at=value789\",\n        \"cookie:with:colons=value000\",\n    ]\n\n    jar.update_cookies_from_headers(headers, url)\n\n    # All cookies should be added\n    assert len(jar) == 4\n    filtered: BaseCookie[str] = jar.filter_cookies(url)\n    assert \"cookie_with_parens\" in filtered\n    assert \"cookie-with-brackets[index]\" in filtered\n    assert \"cookie@with@at\" in filtered\n    assert \"cookie:with:colons\" in filtered\n\n\ndef test_update_cookies_from_headers_empty_list() -> None:\n    \"\"\"Test that empty header list is handled gracefully.\"\"\"\n    jar: CookieJar = CookieJar()\n    url: URL = URL(\"http://example.com/\")\n\n    # Should not raise any errors\n    jar.update_cookies_from_headers([], url)\n\n    assert len(jar) == 0\n\n\ndef test_update_cookies_from_headers_with_attributes() -> None:\n    \"\"\"Test cookies with various attributes are handled correctly.\"\"\"\n    jar: CookieJar = CookieJar()\n    url: URL = URL(\"https://secure.example.com/app/page\")\n\n    headers = [\n        \"secure-cookie=value1; Secure; HttpOnly; SameSite=Strict\",\n        \"expiring-cookie=value2; Max-Age=3600; Path=/app\",\n        \"domain-cookie=value3; Domain=.example.com; Path=/\",\n        \"dated-cookie=value4; Expires=Wed, 09 Jun 3024 10:18:14 GMT\",\n    ]\n\n    jar.update_cookies_from_headers(headers, url)\n\n    # All cookies should be stored\n    assert len(jar) == 4\n\n    # Verify secure cookie (should work on HTTPS subdomain)\n    # Note: cookies without explicit path get path from URL (/app)\n    filtered_https_root: BaseCookie[str] = jar.filter_cookies(\n        URL(\"https://secure.example.com/\")\n    )\n    assert len(filtered_https_root) == 1  # Only domain-cookie has Path=/\n    assert \"domain-cookie\" in filtered_https_root\n\n    # Check app path\n    filtered_https_app: BaseCookie[str] = jar.filter_cookies(\n        URL(\"https://secure.example.com/app/\")\n    )\n    assert len(filtered_https_app) == 4  # All cookies match\n    assert \"secure-cookie\" in filtered_https_app\n    assert \"expiring-cookie\" in filtered_https_app\n    assert \"domain-cookie\" in filtered_https_app\n    assert \"dated-cookie\" in filtered_https_app\n\n    # Secure cookie should not be available on HTTP\n    filtered_http_app: BaseCookie[str] = jar.filter_cookies(\n        URL(\"http://secure.example.com/app/\")\n    )\n    assert \"secure-cookie\" not in filtered_http_app\n    assert \"expiring-cookie\" in filtered_http_app  # Non-secure cookies still available\n    assert \"domain-cookie\" in filtered_http_app\n    assert \"dated-cookie\" in filtered_http_app\n\n\ndef test_update_cookies_from_headers_preserves_existing() -> None:\n    \"\"\"Test that update_cookies_from_headers preserves existing cookies.\"\"\"\n    jar: CookieJar = CookieJar()\n    url: URL = URL(\"http://example.com/\")\n\n    # Add some initial cookies\n    jar.update_cookies(\n        {\n            \"existing1\": \"value1\",\n            \"existing2\": \"value2\",\n        },\n        url,\n    )\n\n    # Add more cookies via headers\n    headers = [\n        \"new-cookie1=value3\",\n        \"new-cookie2=value4\",\n    ]\n\n    jar.update_cookies_from_headers(headers, url)\n\n    # Should have all 4 cookies\n    assert len(jar) == 4\n    filtered: BaseCookie[str] = jar.filter_cookies(url)\n    assert \"existing1\" in filtered\n    assert \"existing2\" in filtered\n    assert \"new-cookie1\" in filtered\n    assert \"new-cookie2\" in filtered\n\n\ndef test_update_cookies_from_headers_overwrites_same_cookie() -> None:\n    \"\"\"Test that cookies with same name/domain/path are overwritten.\"\"\"\n    jar: CookieJar = CookieJar()\n    url: URL = URL(\"http://example.com/\")\n\n    # Add initial cookie\n    jar.update_cookies({\"session\": \"old-value\"}, url)\n\n    # Update with new value via headers\n    headers = [\"session=new-value\"]\n    jar.update_cookies_from_headers(headers, url)\n\n    # Should still have just 1 cookie with updated value\n    assert len(jar) == 1\n    filtered: BaseCookie[str] = jar.filter_cookies(url)\n    assert filtered[\"session\"].value == \"new-value\"\n\n\ndef test_dummy_cookie_jar_update_cookies_from_headers() -> None:\n    \"\"\"Test that DummyCookieJar ignores update_cookies_from_headers.\"\"\"\n    jar: DummyCookieJar = DummyCookieJar()\n    url: URL = URL(\"http://example.com/\")\n\n    headers = [\n        \"cookie1=value1\",\n        \"cookie2=value2\",\n    ]\n\n    # Should not raise and should not store anything\n    jar.update_cookies_from_headers(headers, url)\n\n    assert len(jar) == 0\n    filtered: BaseCookie[str] = jar.filter_cookies(url)\n    assert len(filtered) == 0\n\n\nasync def test_shared_cookie_cache_population() -> None:\n    \"\"\"Test that shared cookies are cached correctly.\"\"\"\n    jar = CookieJar(unsafe=True)\n\n    # Create a shared cookie (no domain/path restrictions)\n    sc = SimpleCookie()\n    sc[\"shared\"] = \"value\"\n    sc[\"shared\"][\"path\"] = \"/\"  # Will be stripped to \"\"\n\n    # Update with empty URL to avoid domain being set\n    jar.update_cookies(sc, URL())\n\n    # Verify cookie is stored at shared key\n    assert (\"\", \"\") in jar._cookies\n    assert \"shared\" in jar._cookies[(\"\", \"\")]\n\n    # Filter cookies to populate cache\n    filtered = jar.filter_cookies(URL(\"http://example.com/\"))\n    assert \"shared\" in filtered\n    assert filtered[\"shared\"].value == \"value\"\n\n    # Verify cache was populated\n    assert (\"\", \"\") in jar._morsel_cache\n    assert \"shared\" in jar._morsel_cache[(\"\", \"\")]\n\n    # Verify the cached morsel is the same one returned\n    cached_morsel = jar._morsel_cache[(\"\", \"\")][\"shared\"]\n    assert cached_morsel is filtered[\"shared\"]\n\n\nasync def test_shared_cookie_cache_clearing_on_update() -> None:\n    \"\"\"Test that shared cookie cache is cleared when cookie is updated.\"\"\"\n    jar = CookieJar(unsafe=True)\n\n    # Create initial shared cookie\n    sc = SimpleCookie()\n    sc[\"shared\"] = \"value1\"\n    sc[\"shared\"][\"path\"] = \"/\"\n    jar.update_cookies(sc, URL())\n\n    # Filter to populate cache\n    filtered1 = jar.filter_cookies(URL(\"http://example.com/\"))\n    assert filtered1[\"shared\"].value == \"value1\"\n    assert \"shared\" in jar._morsel_cache[(\"\", \"\")]\n\n    # Update the cookie with new value\n    sc2 = SimpleCookie()\n    sc2[\"shared\"] = \"value2\"\n    sc2[\"shared\"][\"path\"] = \"/\"\n    jar.update_cookies(sc2, URL())\n\n    # Verify cache was cleared\n    assert \"shared\" not in jar._morsel_cache[(\"\", \"\")]\n\n    # Filter again to verify new value\n    filtered2 = jar.filter_cookies(URL(\"http://example.com/\"))\n    assert filtered2[\"shared\"].value == \"value2\"\n\n    # Verify cache was repopulated with new value\n    assert \"shared\" in jar._morsel_cache[(\"\", \"\")]\n\n\nasync def test_shared_cookie_cache_clearing_on_delete() -> None:\n    \"\"\"Test that shared cookie cache is cleared when cookies are deleted.\"\"\"\n    jar = CookieJar(unsafe=True)\n\n    # Create multiple shared cookies\n    sc = SimpleCookie()\n    sc[\"shared1\"] = \"value1\"\n    sc[\"shared1\"][\"path\"] = \"/\"\n    sc[\"shared2\"] = \"value2\"\n    sc[\"shared2\"][\"path\"] = \"/\"\n    jar.update_cookies(sc, URL())\n\n    # Filter to populate cache\n    jar.filter_cookies(URL(\"http://example.com/\"))\n    assert \"shared1\" in jar._morsel_cache[(\"\", \"\")]\n    assert \"shared2\" in jar._morsel_cache[(\"\", \"\")]\n\n    # Delete one cookie using internal method\n    jar._delete_cookies([(\"\", \"\", \"shared1\")])\n\n    # Verify cookie and its cache entry were removed\n    assert \"shared1\" not in jar._cookies[(\"\", \"\")]\n    assert \"shared1\" not in jar._morsel_cache[(\"\", \"\")]\n\n    # Verify other cookie remains\n    assert \"shared2\" in jar._cookies[(\"\", \"\")]\n    assert \"shared2\" in jar._morsel_cache[(\"\", \"\")]\n\n\nasync def test_shared_cookie_cache_clearing_on_clear() -> None:\n    \"\"\"Test that shared cookie cache is cleared when jar is cleared.\"\"\"\n    jar = CookieJar(unsafe=True)\n\n    # Create shared and domain-specific cookies\n    # Shared cookie\n    sc1 = SimpleCookie()\n    sc1[\"shared\"] = \"shared_value\"\n    sc1[\"shared\"][\"path\"] = \"/\"\n    jar.update_cookies(sc1, URL())\n\n    # Domain-specific cookie\n    sc2 = SimpleCookie()\n    sc2[\"domain_cookie\"] = \"domain_value\"\n    jar.update_cookies(sc2, URL(\"http://example.com/\"))\n\n    # Filter to populate caches\n    jar.filter_cookies(URL(\"http://example.com/\"))\n\n    # Verify caches are populated\n    assert (\"\", \"\") in jar._morsel_cache\n    assert \"shared\" in jar._morsel_cache[(\"\", \"\")]\n    assert (\"example.com\", \"\") in jar._morsel_cache\n    assert \"domain_cookie\" in jar._morsel_cache[(\"example.com\", \"\")]\n\n    # Clear all cookies\n    jar.clear()\n\n    # Verify all caches are cleared\n    assert len(jar._morsel_cache) == 0\n    assert len(jar._cookies) == 0\n\n    # Verify filtering returns no cookies\n    filtered = jar.filter_cookies(URL(\"http://example.com/\"))\n    assert len(filtered) == 0\n\n\nasync def test_shared_cookie_with_multiple_domains() -> None:\n    \"\"\"Test that shared cookies work across different domains.\"\"\"\n    jar = CookieJar(unsafe=True)\n\n    # Create a truly shared cookie\n    sc = SimpleCookie()\n    sc[\"universal\"] = \"everywhere\"\n    sc[\"universal\"][\"path\"] = \"/\"\n    jar.update_cookies(sc, URL())\n\n    # Test filtering for different domains\n    domains = [\n        \"http://example.com/\",\n        \"http://test.org/\",\n        \"http://localhost/\",\n        \"http://192.168.1.1/\",  # IP address (requires unsafe=True)\n    ]\n\n    for domain_url in domains:\n        filtered = jar.filter_cookies(URL(domain_url))\n        assert \"universal\" in filtered\n        assert filtered[\"universal\"].value == \"everywhere\"\n\n    # Verify cache is reused efficiently\n    assert (\"\", \"\") in jar._morsel_cache\n    assert \"universal\" in jar._morsel_cache[(\"\", \"\")]\n\n\n# === Security tests for restricted unpickler and JSON save/load ===\n\n\ndef test_load_rejects_malicious_pickle(tmp_path: Path) -> None:\n    \"\"\"Verify CookieJar.load() blocks arbitrary code execution via pickle.\n\n    A crafted pickle payload using os.system (or any non-cookie class)\n    must be rejected by the restricted unpickler.\n    \"\"\"\n    import os\n\n    file_path = tmp_path / \"malicious.pkl\"\n\n    class RCEPayload:\n        def __reduce__(self) -> tuple[object, ...]:\n            return (os.system, (\"echo PWNED\",))\n\n    with open(file_path, \"wb\") as f:\n        pickle.dump(RCEPayload(), f, pickle.HIGHEST_PROTOCOL)\n\n    jar = CookieJar()\n    with pytest.raises(pickle.UnpicklingError, match=\"Forbidden class\"):\n        jar.load(file_path)\n\n\ndef test_load_rejects_eval_payload(tmp_path: Path) -> None:\n    \"\"\"Verify CookieJar.load() blocks eval-based pickle payloads.\"\"\"\n    file_path = tmp_path / \"eval_payload.pkl\"\n\n    class EvalPayload:\n        def __reduce__(self) -> tuple[object, ...]:\n            return (eval, (\"__import__('os').system('echo PWNED')\",))\n\n    with open(file_path, \"wb\") as f:\n        pickle.dump(EvalPayload(), f, pickle.HIGHEST_PROTOCOL)\n\n    jar = CookieJar()\n    with pytest.raises(pickle.UnpicklingError, match=\"Forbidden class\"):\n        jar.load(file_path)\n\n\ndef test_load_rejects_subprocess_payload(tmp_path: Path) -> None:\n    \"\"\"Verify CookieJar.load() blocks subprocess-based pickle payloads.\"\"\"\n    import subprocess\n\n    file_path = tmp_path / \"subprocess_payload.pkl\"\n\n    class SubprocessPayload:\n        def __reduce__(self) -> tuple[object, ...]:\n            return (subprocess.call, ([\"echo\", \"PWNED\"],))\n\n    with open(file_path, \"wb\") as f:\n        pickle.dump(SubprocessPayload(), f, pickle.HIGHEST_PROTOCOL)\n\n    jar = CookieJar()\n    with pytest.raises(pickle.UnpicklingError, match=\"Forbidden class\"):\n        jar.load(file_path)\n\n\ndef test_load_falls_back_to_pickle(\n    tmp_path: Path,\n    cookies_to_receive: SimpleCookie,\n) -> None:\n    \"\"\"Verify load() falls back to restricted pickle for legacy cookie files.\n\n    Existing cookie files saved with older versions of aiohttp used pickle.\n    load() should detect that the file is not JSON and fall back to the\n    restricted pickle unpickler for backward compatibility.\n    \"\"\"\n    file_path = tmp_path / \"legit.pkl\"\n\n    # Write a legacy pickle file directly (as old aiohttp save() would)\n    jar_save = CookieJar()\n    jar_save.update_cookies(cookies_to_receive)\n    with file_path.open(mode=\"wb\") as f:\n        pickle.dump(jar_save._cookies, f, pickle.HIGHEST_PROTOCOL)\n\n    jar_load = CookieJar()\n    jar_load.load(file_path=file_path)\n\n    jar_test = SimpleCookie()\n    for cookie in jar_load:\n        jar_test[cookie.key] = cookie\n\n    assert jar_test == cookies_to_receive\n\n\ndef test_save_load_json_roundtrip(\n    tmp_path: Path,\n    cookies_to_receive: SimpleCookie,\n) -> None:\n    \"\"\"Verify save/load roundtrip preserves cookies via JSON format.\"\"\"\n    file_path = tmp_path / \"cookies.json\"\n\n    jar_save = CookieJar()\n    jar_save.update_cookies(cookies_to_receive)\n    jar_save.save(file_path=file_path)\n\n    jar_load = CookieJar()\n    jar_load.load(file_path=file_path)\n\n    saved_cookies = SimpleCookie()\n    for cookie in jar_save:\n        saved_cookies[cookie.key] = cookie\n\n    loaded_cookies = SimpleCookie()\n    for cookie in jar_load:\n        loaded_cookies[cookie.key] = cookie\n\n    assert saved_cookies == loaded_cookies\n\n\ndef test_save_load_json_partitioned_cookies(tmp_path: Path) -> None:\n    \"\"\"Verify save/load roundtrip works with partitioned cookies.\"\"\"\n    file_path = tmp_path / \"partitioned.json\"\n\n    jar_save = CookieJar()\n    jar_save.update_cookies_from_headers(\n        [\"session=cookie; Partitioned\"], URL(\"https://example.com/\")\n    )\n    jar_save.save(file_path=file_path)\n\n    jar_load = CookieJar()\n    jar_load.load(file_path=file_path)\n\n    # Compare individual cookie values (same approach as test_save_load_partitioned_cookies)\n    saved = list(jar_save)\n    loaded = list(jar_load)\n    assert len(saved) == len(loaded)\n    for s, lo in zip(saved, loaded):\n        assert s.key == lo.key\n        assert s.value == lo.value\n        assert s[\"domain\"] == lo[\"domain\"]\n        assert s[\"path\"] == lo[\"path\"]\n\n\ndef test_json_format_is_safe(tmp_path: Path) -> None:\n    \"\"\"Verify the JSON file format cannot execute code on load.\"\"\"\n    import json\n\n    file_path = tmp_path / \"safe.json\"\n\n    # Write something that might look dangerous but is just data\n    malicious_data = {\n        \"evil.com|/\": {\n            \"session\": {\n                \"key\": \"session\",\n                \"value\": \"__import__('os').system('echo PWNED')\",\n                \"coded_value\": \"__import__('os').system('echo PWNED')\",\n            }\n        }\n    }\n    with open(file_path, \"w\") as f:\n        json.dump(malicious_data, f)\n\n    jar = CookieJar()\n    jar.load(file_path=file_path)\n\n    # The \"malicious\" string is just a cookie value, not executed code\n    cookies = list(jar)\n    assert len(cookies) == 1\n    assert cookies[0].value == \"__import__('os').system('echo PWNED')\"\n\n\ndef test_save_load_json_secure_cookies(tmp_path: Path) -> None:\n    \"\"\"Verify save/load preserves Secure and HttpOnly flags.\"\"\"\n    file_path = tmp_path / \"secure.json\"\n\n    jar_save = CookieJar()\n    jar_save.update_cookies_from_headers(\n        [\"token=abc123; Secure; HttpOnly; Path=/; Domain=example.com\"],\n        URL(\"https://example.com/\"),\n    )\n    jar_save.save(file_path=file_path)\n\n    jar_load = CookieJar()\n    jar_load.load(file_path=file_path)\n\n    loaded_cookies = list(jar_load)\n    assert len(loaded_cookies) == 1\n    cookie = loaded_cookies[0]\n    assert cookie.key == \"token\"\n    assert cookie.value == \"abc123\"\n    assert cookie[\"secure\"] is True\n    assert cookie[\"httponly\"] is True\n    assert cookie[\"domain\"] == \"example.com\"\n"
  },
  {
    "path": "tests/test_flowcontrol_streams.py",
    "content": "import asyncio\nfrom unittest import mock\n\nimport pytest\n\nfrom aiohttp import streams\nfrom aiohttp.base_protocol import BaseProtocol\n\n\n@pytest.fixture\ndef protocol() -> BaseProtocol:\n    return mock.create_autospec(BaseProtocol, spec_set=True, instance=True, _reading_paused=False)  # type: ignore[no-any-return]\n\n\n@pytest.fixture\ndef stream(\n    loop: asyncio.AbstractEventLoop, protocol: BaseProtocol\n) -> streams.StreamReader:\n    return streams.StreamReader(protocol, limit=1, loop=loop)\n\n\nclass TestFlowControlStreamReader:\n    async def test_read(self, stream: streams.StreamReader) -> None:\n        stream.feed_data(b\"da\")\n        res = await stream.read(1)\n        assert res == b\"d\"\n        assert not stream._protocol.resume_reading.called  # type: ignore[attr-defined]\n\n    async def test_read_resume_paused(self, stream: streams.StreamReader) -> None:\n        stream.feed_data(b\"test\")\n        stream._protocol._reading_paused = True\n\n        res = await stream.read(1)\n        assert res == b\"t\"\n        assert stream._protocol.pause_reading.called  # type: ignore[attr-defined]\n\n    async def test_readline(self, stream: streams.StreamReader) -> None:\n        stream.feed_data(b\"d\\n\")\n        res = await stream.readline()\n        assert res == b\"d\\n\"\n        assert not stream._protocol.resume_reading.called  # type: ignore[attr-defined]\n\n    async def test_readline_resume_paused(self, stream: streams.StreamReader) -> None:\n        stream._protocol._reading_paused = True\n        stream.feed_data(b\"d\\n\")\n        res = await stream.readline()\n        assert res == b\"d\\n\"\n        assert stream._protocol.resume_reading.called  # type: ignore[attr-defined]\n\n    async def test_readany(self, stream: streams.StreamReader) -> None:\n        stream.feed_data(b\"data\")\n        res = await stream.readany()\n        assert res == b\"data\"\n        assert not stream._protocol.resume_reading.called  # type: ignore[attr-defined]\n\n    async def test_readany_resume_paused(self, stream: streams.StreamReader) -> None:\n        stream._protocol._reading_paused = True\n        stream.feed_data(b\"data\")\n        res = await stream.readany()\n        assert res == b\"data\"\n        assert stream._protocol.resume_reading.called  # type: ignore[attr-defined]\n\n    async def test_readchunk(self, stream: streams.StreamReader) -> None:\n        stream.feed_data(b\"data\")\n        res, end_of_http_chunk = await stream.readchunk()\n        assert res == b\"data\"\n        assert not end_of_http_chunk\n        assert not stream._protocol.resume_reading.called  # type: ignore[attr-defined]\n\n    async def test_readchunk_resume_paused(self, stream: streams.StreamReader) -> None:\n        stream._protocol._reading_paused = True\n        stream.feed_data(b\"data\")\n        res, end_of_http_chunk = await stream.readchunk()\n        assert res == b\"data\"\n        assert not end_of_http_chunk\n        assert stream._protocol.resume_reading.called  # type: ignore[attr-defined]\n\n    async def test_readexactly(self, stream: streams.StreamReader) -> None:\n        stream.feed_data(b\"data\")\n        res = await stream.readexactly(3)\n        assert res == b\"dat\"\n        assert not stream._protocol.resume_reading.called  # type: ignore[attr-defined]\n\n    async def test_feed_data(self, stream: streams.StreamReader) -> None:\n        stream._protocol._reading_paused = False\n        stream.feed_data(b\"datadata\")\n        assert stream._protocol.pause_reading.called  # type: ignore[attr-defined]\n\n    async def test_read_nowait(self, stream: streams.StreamReader) -> None:\n        stream._protocol._reading_paused = True\n        stream.feed_data(b\"data1\")\n        stream.feed_data(b\"data2\")\n        stream.feed_data(b\"data3\")\n        res = await stream.read(5)\n        assert res == b\"data1\"\n        assert stream._protocol.resume_reading.call_count == 0  # type: ignore[attr-defined]\n\n        res = stream.read_nowait(5)\n        assert res == b\"data2\"\n        assert stream._protocol.resume_reading.call_count == 0  # type: ignore[attr-defined]\n\n        res = stream.read_nowait(5)\n        assert res == b\"data3\"\n        assert stream._protocol.resume_reading.call_count == 1  # type: ignore[attr-defined]\n\n        stream._protocol._reading_paused = False\n        res = stream.read_nowait(5)\n        assert res == b\"\"\n        assert stream._protocol.resume_reading.call_count == 1  # type: ignore[attr-defined]\n\n    async def test_resumed_on_eof(self, stream: streams.StreamReader) -> None:\n        stream.feed_data(b\"data\")\n        assert stream._protocol.pause_reading.call_count == 1  # type: ignore[attr-defined]\n        assert stream._protocol.resume_reading.call_count == 0  # type: ignore[attr-defined]\n        stream._protocol._reading_paused = True\n\n        stream.feed_eof()\n        assert stream._protocol.resume_reading.call_count == 1  # type: ignore[attr-defined]\n\n\nasync def test_stream_reader_eof_when_full() -> None:\n    loop = asyncio.get_event_loop()\n    protocol = BaseProtocol(loop=loop)\n    protocol.transport = asyncio.Transport()\n    stream = streams.StreamReader(protocol, 1024, loop=loop)\n\n    data_len = stream._high_water + 1\n    stream.feed_data(b\"0\" * data_len)\n    assert protocol._reading_paused\n    stream.feed_eof()\n    assert not protocol._reading_paused\n"
  },
  {
    "path": "tests/test_formdata.py",
    "content": "import io\nfrom unittest import mock\n\nimport pytest\n\nfrom aiohttp import FormData, web\nfrom aiohttp.http_writer import StreamWriter\nfrom aiohttp.pytest_plugin import AiohttpClient\n\n\n@pytest.fixture\ndef buf() -> bytearray:\n    return bytearray()\n\n\n@pytest.fixture\ndef writer(buf: bytearray) -> StreamWriter:\n    writer = mock.create_autospec(StreamWriter, spec_set=True)\n\n    async def write(chunk: bytes) -> None:\n        buf.extend(chunk)\n\n    writer.write.side_effect = write\n    return writer  # type: ignore[no-any-return]\n\n\ndef test_formdata_multipart(buf: bytearray) -> None:\n    form = FormData(default_to_multipart=False)\n    assert not form.is_multipart\n\n    form.add_field(\"test\", b\"test\", filename=\"test.txt\")\n    assert form.is_multipart\n\n\ndef test_form_data_is_multipart_param(buf: bytearray) -> None:\n    form = FormData(default_to_multipart=True)\n    assert form.is_multipart\n\n    form.add_field(\"test\", \"test\")\n    assert form.is_multipart\n\n\n@pytest.mark.parametrize(\"obj\", (object(), None))\ndef test_invalid_formdata_payload_multipart(obj: object) -> None:\n    form = FormData()\n    form.add_field(\"test\", obj, filename=\"test.txt\")\n    with pytest.raises(TypeError, match=\"Can not serialize value\"):\n        form()\n\n\n@pytest.mark.parametrize(\"obj\", (object(), None))\ndef test_invalid_formdata_payload_urlencoded(obj: object) -> None:\n    form = FormData({\"test\": obj})\n    with pytest.raises(TypeError, match=\"expected str\"):\n        form()\n\n\ndef test_invalid_formdata_params() -> None:\n    with pytest.raises(TypeError):\n        FormData(\"asdasf\")\n\n\ndef test_invalid_formdata_params2() -> None:\n    with pytest.raises(TypeError):\n        FormData(\"as\")  # 2-char str is not allowed\n\n\nasync def test_formdata_textio_charset(buf: bytearray, writer: StreamWriter) -> None:\n    form = FormData()\n    body = io.TextIOWrapper(io.BytesIO(b\"\\xe6\\x97\\xa5\\xe6\\x9c\\xac\"), encoding=\"utf-8\")\n    form.add_field(\"foo\", body, content_type=\"text/plain; charset=shift-jis\")\n    payload = form()\n    await payload.write(writer)\n    assert b\"charset=shift-jis\" in buf\n    assert b\"\\x93\\xfa\\x96{\" in buf\n\n\n@pytest.mark.parametrize(\"val\", (0, 0.1, {}, [], b\"foo\"))\ndef test_invalid_type_formdata_content_type(val: object) -> None:\n    form = FormData()\n    with pytest.raises(TypeError):\n        form.add_field(\"foo\", \"bar\", content_type=val)  # type: ignore[arg-type]\n\n\n@pytest.mark.parametrize(\"val\", (\"\\r\", \"\\n\", \"a\\ra\\n\", \"a\\na\\r\"))\ndef test_invalid_value_formdata_content_type(val: str) -> None:\n    form = FormData()\n    with pytest.raises(ValueError):\n        form.add_field(\"foo\", \"bar\", content_type=val)\n\n\ndef test_invalid_formdata_filename() -> None:\n    form = FormData()\n    invalid_vals = [0, 0.1, {}, [], b\"foo\"]\n    for invalid_val in invalid_vals:\n        with pytest.raises(TypeError):\n            form.add_field(\"foo\", \"bar\", filename=invalid_val)  # type: ignore[arg-type]\n\n\nasync def test_formdata_field_name_is_quoted(\n    buf: bytearray, writer: StreamWriter\n) -> None:\n    form = FormData(charset=\"ascii\")\n    form.add_field(\"email 1\", \"xxx@x.co\", content_type=\"multipart/form-data\")\n    payload = form()\n    await payload.write(writer)\n    assert b'name=\"email\\\\ 1\"' in buf\n\n\nasync def test_formdata_field_name_is_not_quoted(\n    buf: bytearray, writer: StreamWriter\n) -> None:\n    form = FormData(quote_fields=False, charset=\"ascii\")\n    form.add_field(\"email 1\", \"xxx@x.co\", content_type=\"multipart/form-data\")\n    payload = form()\n    await payload.write(writer)\n    assert b'name=\"email 1\"' in buf\n\n\nasync def test_formdata_is_reusable(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.add_routes([web.post(\"/\", handler)])\n\n    client = await aiohttp_client(app)\n\n    data = FormData()\n    data.add_field(\"test\", \"test_value\", content_type=\"application/json\")\n\n    # First request\n    resp1 = await client.post(\"/\", data=data)\n    assert resp1.status == 200\n    resp1.release()\n\n    # Second request - should work without RuntimeError\n    resp2 = await client.post(\"/\", data=data)\n    assert resp2.status == 200\n    resp2.release()\n\n    # Third request to ensure continued reusability\n    resp3 = await client.post(\"/\", data=data)\n    assert resp3.status == 200\n    resp3.release()\n\n\nasync def test_formdata_boundary_param() -> None:\n    boundary = \"some_boundary\"\n    form = FormData(boundary=boundary)\n    assert form._writer.boundary == boundary\n\n\nasync def test_formdata_reusability_multipart(\n    writer: StreamWriter, buf: bytearray\n) -> None:\n    form = FormData()\n    form.add_field(\"name\", \"value\")\n    form.add_field(\"file\", b\"content\", filename=\"test.txt\", content_type=\"text/plain\")\n\n    # First call - should generate multipart payload\n    payload1 = form()\n    assert form.is_multipart\n    buf.clear()\n    await payload1.write(writer)\n    result1 = bytes(buf)\n\n    # Verify first result contains expected content\n    assert b\"name\" in result1\n    assert b\"value\" in result1\n    assert b\"test.txt\" in result1\n    assert b\"content\" in result1\n    assert b\"text/plain\" in result1\n\n    # Second call - should generate identical multipart payload\n    payload2 = form()\n    buf.clear()\n    await payload2.write(writer)\n    result2 = bytes(buf)\n\n    # Results should be identical (same boundary and content)\n    assert result1 == result2\n\n    # Third call to ensure continued reusability\n    payload3 = form()\n    buf.clear()\n    await payload3.write(writer)\n    result3 = bytes(buf)\n\n    assert result1 == result3\n\n\nasync def test_formdata_reusability_urlencoded(\n    writer: StreamWriter, buf: bytearray\n) -> None:\n    form = FormData()\n    form.add_field(\"key1\", \"value1\")\n    form.add_field(\"key2\", \"value2\")\n\n    # First call - should generate urlencoded payload\n    payload1 = form()\n    assert not form.is_multipart\n    buf.clear()\n    await payload1.write(writer)\n    result1 = bytes(buf)\n\n    # Verify first result contains expected content\n    assert b\"key1=value1\" in result1\n    assert b\"key2=value2\" in result1\n\n    # Second call - should generate identical urlencoded payload\n    payload2 = form()\n    buf.clear()\n    await payload2.write(writer)\n    result2 = bytes(buf)\n\n    # Results should be identical\n    assert result1 == result2\n\n    # Third call to ensure continued reusability\n    payload3 = form()\n    buf.clear()\n    await payload3.write(writer)\n    result3 = bytes(buf)\n\n    assert result1 == result3\n\n\nasync def test_formdata_reusability_after_adding_fields(\n    writer: StreamWriter, buf: bytearray\n) -> None:\n    form = FormData()\n    form.add_field(\"field1\", \"value1\")\n\n    # First call\n    payload1 = form()\n    buf.clear()\n    await payload1.write(writer)\n    result1 = bytes(buf)\n\n    # Add more fields after first call\n    form.add_field(\"field2\", \"value2\")\n\n    # Second call should include new field\n    payload2 = form()\n    buf.clear()\n    await payload2.write(writer)\n    result2 = bytes(buf)\n\n    # Results should be different\n    assert result1 != result2\n    assert b\"field1=value1\" in result2\n    assert b\"field2=value2\" in result2\n    assert b\"field2=value2\" not in result1\n\n    # Third call should be same as second\n    payload3 = form()\n    buf.clear()\n    await payload3.write(writer)\n    result3 = bytes(buf)\n\n    assert result2 == result3\n\n\nasync def test_formdata_reusability_with_io_fields(\n    writer: StreamWriter, buf: bytearray\n) -> None:\n    form = FormData()\n\n    # Create BytesIO and StringIO objects\n    bytes_io = io.BytesIO(b\"bytes content\")\n    string_io = io.StringIO(\"string content\")\n\n    form.add_field(\n        \"bytes_field\",\n        bytes_io,\n        filename=\"bytes.bin\",\n        content_type=\"application/octet-stream\",\n    )\n    form.add_field(\n        \"string_field\", string_io, filename=\"text.txt\", content_type=\"text/plain\"\n    )\n\n    # First call\n    payload1 = form()\n    buf.clear()\n    await payload1.write(writer)\n    result1 = bytes(buf)\n\n    assert b\"bytes content\" in result1\n    assert b\"string content\" in result1\n\n    # Reset IO objects for reuse\n    bytes_io.seek(0)\n    string_io.seek(0)\n\n    # Second call - should work with reset IO objects\n    payload2 = form()\n    buf.clear()\n    await payload2.write(writer)\n    result2 = bytes(buf)\n\n    # Should produce identical results\n    assert result1 == result2\n"
  },
  {
    "path": "tests/test_helpers.py",
    "content": "import asyncio\nimport base64\nimport datetime\nimport gc\nimport sys\nimport weakref\nfrom collections.abc import Iterator\nfrom math import ceil, modf\nfrom pathlib import Path\nfrom types import MappingProxyType\nfrom unittest import mock\nfrom urllib.request import getproxies_environment\n\nimport pytest\nfrom multidict import CIMultiDict, MultiDict, MultiDictProxy\nfrom yarl import URL\n\nfrom aiohttp import helpers, web\nfrom aiohttp.helpers import (\n    EMPTY_BODY_METHODS,\n    is_expected_content_type,\n    must_be_empty_body,\n    parse_http_date,\n    should_remove_content_length,\n)\n\n# ------------------- parse_mimetype ----------------------------------\n\n\n@pytest.mark.parametrize(\n    \"mimetype, expected\",\n    [\n        (\"\", helpers.MimeType(\"\", \"\", \"\", MultiDictProxy(MultiDict()))),\n        (\"*\", helpers.MimeType(\"*\", \"*\", \"\", MultiDictProxy(MultiDict()))),\n        (\n            \"application/json\",\n            helpers.MimeType(\"application\", \"json\", \"\", MultiDictProxy(MultiDict())),\n        ),\n        (\n            \"application/json;  charset=utf-8\",\n            helpers.MimeType(\n                \"application\",\n                \"json\",\n                \"\",\n                MultiDictProxy(MultiDict({\"charset\": \"utf-8\"})),\n            ),\n        ),\n        (\n            \"\"\"application/json; charset=utf-8;\"\"\",\n            helpers.MimeType(\n                \"application\",\n                \"json\",\n                \"\",\n                MultiDictProxy(MultiDict({\"charset\": \"utf-8\"})),\n            ),\n        ),\n        (\n            'ApPlIcAtIoN/JSON;ChaRseT=\"UTF-8\"',\n            helpers.MimeType(\n                \"application\",\n                \"json\",\n                \"\",\n                MultiDictProxy(MultiDict({\"charset\": \"UTF-8\"})),\n            ),\n        ),\n        (\n            \"application/rss+xml\",\n            helpers.MimeType(\"application\", \"rss\", \"xml\", MultiDictProxy(MultiDict())),\n        ),\n        (\n            \"text/plain;base64\",\n            helpers.MimeType(\n                \"text\", \"plain\", \"\", MultiDictProxy(MultiDict({\"base64\": \"\"}))\n            ),\n        ),\n    ],\n)\ndef test_parse_mimetype(mimetype: str, expected: helpers.MimeType) -> None:\n    result = helpers.parse_mimetype(mimetype)\n\n    assert isinstance(result, helpers.MimeType)\n    assert result == expected\n\n\n# ------------------- parse_content_type ------------------------------\n\n\n@pytest.mark.parametrize(\n    \"content_type, expected\",\n    [\n        (\n            \"text/plain\",\n            (\"text/plain\", MultiDictProxy(MultiDict())),\n        ),\n        (\n            \"wrong\",\n            (\"application/octet-stream\", MultiDictProxy(MultiDict())),\n        ),\n    ],\n)\ndef test_parse_content_type(\n    content_type: str, expected: tuple[str, MappingProxyType[str, str]]\n) -> None:\n    result = helpers.parse_content_type(content_type)\n\n    assert result == expected\n\n\n# ------------------- guess_filename ----------------------------------\n\n\ndef test_guess_filename_with_file_object(tmp_path: Path) -> None:\n    file_path = tmp_path / \"test_guess_filename\"\n    with file_path.open(\"w+b\") as fp:\n        assert helpers.guess_filename(fp, \"no-throw\") is not None\n\n\ndef test_guess_filename_with_path(tmp_path: Path) -> None:\n    file_path = tmp_path / \"test_guess_filename\"\n    assert helpers.guess_filename(file_path, \"no-throw\") is not None\n\n\ndef test_guess_filename_with_default() -> None:\n    assert helpers.guess_filename(None, \"no-throw\") == \"no-throw\"\n\n\n# ------------------- BasicAuth -----------------------------------\n\n\ndef test_basic_auth1() -> None:\n    # missing password here\n    with pytest.raises(ValueError):\n        helpers.BasicAuth(None)  # type: ignore[arg-type]\n\n\ndef test_basic_auth2() -> None:\n    with pytest.raises(ValueError):\n        helpers.BasicAuth(\"nkim\", None)  # type: ignore[arg-type]\n\n\ndef test_basic_with_auth_colon_in_login() -> None:\n    with pytest.raises(ValueError):\n        helpers.BasicAuth(\"nkim:1\", \"pwd\")\n\n\ndef test_basic_auth3() -> None:\n    auth = helpers.BasicAuth(\"nkim\")\n    assert auth.login == \"nkim\"\n    assert auth.password == \"\"\n\n\ndef test_basic_auth4() -> None:\n    auth = helpers.BasicAuth(\"nkim\", \"pwd\")\n    assert auth.login == \"nkim\"\n    assert auth.password == \"pwd\"\n    assert auth.encode() == \"Basic bmtpbTpwd2Q=\"\n\n\n@pytest.mark.parametrize(\n    \"header\",\n    (\n        \"Basic bmtpbTpwd2Q=\",\n        \"basic bmtpbTpwd2Q=\",\n    ),\n)\ndef test_basic_auth_decode(header: str) -> None:\n    auth = helpers.BasicAuth.decode(header)\n    assert auth.login == \"nkim\"\n    assert auth.password == \"pwd\"\n\n\ndef test_basic_auth_invalid() -> None:\n    with pytest.raises(ValueError):\n        helpers.BasicAuth.decode(\"bmtpbTpwd2Q=\")\n\n\ndef test_basic_auth_decode_not_basic() -> None:\n    with pytest.raises(ValueError):\n        helpers.BasicAuth.decode(\"Complex bmtpbTpwd2Q=\")\n\n\ndef test_basic_auth_decode_bad_base64() -> None:\n    with pytest.raises(ValueError):\n        helpers.BasicAuth.decode(\"Basic bmtpbTpwd2Q\")\n\n\n@pytest.mark.parametrize(\"header\", (\"Basic ???\", \"Basic   \"))\ndef test_basic_auth_decode_illegal_chars_base64(header: str) -> None:\n    with pytest.raises(ValueError, match=\"Invalid base64 encoding.\"):\n        helpers.BasicAuth.decode(header)\n\n\ndef test_basic_auth_decode_invalid_credentials() -> None:\n    with pytest.raises(ValueError, match=\"Invalid credentials.\"):\n        header = \"Basic {}\".format(base64.b64encode(b\"username\").decode())\n        helpers.BasicAuth.decode(header)\n\n\n@pytest.mark.parametrize(\n    \"credentials, expected_auth\",\n    (\n        (\":\", helpers.BasicAuth(login=\"\", password=\"\", encoding=\"latin1\")),\n        (\n            \"username:\",\n            helpers.BasicAuth(login=\"username\", password=\"\", encoding=\"latin1\"),\n        ),\n        (\n            \":password\",\n            helpers.BasicAuth(login=\"\", password=\"password\", encoding=\"latin1\"),\n        ),\n        (\n            \"username:password\",\n            helpers.BasicAuth(login=\"username\", password=\"password\", encoding=\"latin1\"),\n        ),\n    ),\n)\ndef test_basic_auth_decode_blank_username(  # type: ignore[misc]\n    credentials: str, expected_auth: helpers.BasicAuth\n) -> None:\n    header = f\"Basic {base64.b64encode(credentials.encode()).decode()}\"\n    assert helpers.BasicAuth.decode(header) == expected_auth\n\n\ndef test_basic_auth_from_url() -> None:\n    url = URL(\"http://user:pass@example.com\")\n    auth = helpers.BasicAuth.from_url(url)\n    assert auth is not None\n    assert auth.login == \"user\"\n    assert auth.password == \"pass\"\n\n\ndef test_basic_auth_no_user_from_url() -> None:\n    url = URL(\"http://:pass@example.com\")\n    auth = helpers.BasicAuth.from_url(url)\n    assert auth is not None\n    assert auth.login == \"\"\n    assert auth.password == \"pass\"\n\n\ndef test_basic_auth_no_auth_from_url() -> None:\n    url = URL(\"http://example.com\")\n    auth = helpers.BasicAuth.from_url(url)\n    assert auth is None\n\n\ndef test_basic_auth_from_not_url() -> None:\n    with pytest.raises(TypeError):\n        helpers.BasicAuth.from_url(\"http://user:pass@example.com\")  # type: ignore[arg-type]\n\n\n# ----------------------------------- is_ip_address() ----------------------\n\n\ndef test_is_ip_address() -> None:\n    assert helpers.is_ip_address(\"127.0.0.1\")\n    assert helpers.is_ip_address(\"::1\")\n    assert helpers.is_ip_address(\"FE80:0000:0000:0000:0202:B3FF:FE1E:8329\")\n\n    # Hostnames\n    assert not helpers.is_ip_address(\"localhost\")\n    assert not helpers.is_ip_address(\"www.example.com\")\n\n\ndef test_ipv4_addresses() -> None:\n    ip_addresses = [\n        \"0.0.0.0\",\n        \"127.0.0.1\",\n        \"255.255.255.255\",\n    ]\n    for address in ip_addresses:\n        assert helpers.is_ip_address(address)\n\n\ndef test_ipv6_addresses() -> None:\n    ip_addresses = [\n        \"0:0:0:0:0:0:0:0\",\n        \"FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF\",\n        \"00AB:0002:3008:8CFD:00AB:0002:3008:8CFD\",\n        \"00ab:0002:3008:8cfd:00ab:0002:3008:8cfd\",\n        \"AB:02:3008:8CFD:AB:02:3008:8CFD\",\n        \"AB:02:3008:8CFD::02:3008:8CFD\",\n        \"::\",\n        \"1::1\",\n    ]\n    for address in ip_addresses:\n        assert helpers.is_ip_address(address)\n\n\ndef test_host_addresses() -> None:\n    hosts = [\n        \"www.four.part.host\",\n        \"www.python.org\",\n        \"foo.bar\",\n        \"localhost\",\n    ]\n    for host in hosts:\n        assert not helpers.is_ip_address(host)\n\n\ndef test_is_ip_address_invalid_type() -> None:\n    with pytest.raises(TypeError):\n        helpers.is_ip_address(123)  # type: ignore[arg-type]\n\n    with pytest.raises(TypeError):\n        helpers.is_ip_address(object())  # type: ignore[arg-type]\n\n\n# ----------------------------------- TimeoutHandle -------------------\n\n\ndef test_timeout_handle(loop: asyncio.AbstractEventLoop) -> None:\n    handle = helpers.TimeoutHandle(loop, 10.2)\n    cb = mock.Mock()\n    handle.register(cb)\n    assert cb == handle._callbacks[0][0]\n    handle.close()\n    assert not handle._callbacks\n\n\ndef test_when_timeout_smaller_second(loop: asyncio.AbstractEventLoop) -> None:\n    timeout = 0.1\n\n    handle = helpers.TimeoutHandle(loop, timeout)\n    timer = loop.time() + timeout\n    start_handle = handle.start()\n    assert start_handle is not None\n    when = start_handle.when()\n    handle.close()\n\n    assert isinstance(when, float)\n    assert when - timer == pytest.approx(0, abs=0.001)\n\n\ndef test_when_timeout_smaller_second_with_low_threshold(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    timeout = 0.1\n\n    handle = helpers.TimeoutHandle(loop, timeout, 0.01)\n    timer = loop.time() + timeout\n    start_handle = handle.start()\n    assert start_handle is not None\n    when = start_handle.when()\n    handle.close()\n\n    assert isinstance(when, int)\n    assert when == ceil(timer)\n\n\ndef test_timeout_handle_cb_exc(loop: asyncio.AbstractEventLoop) -> None:\n    handle = helpers.TimeoutHandle(loop, 10.2)\n    cb = mock.Mock()\n    handle.register(cb)\n    cb.side_effect = ValueError()\n    handle()\n    assert cb.called\n    assert not handle._callbacks\n\n\ndef test_timer_context_not_cancelled() -> None:\n    with mock.patch(\"aiohttp.helpers.asyncio\") as m_asyncio:\n        m_asyncio.TimeoutError = asyncio.TimeoutError\n        loop = mock.Mock()\n        ctx = helpers.TimerContext(loop)\n        ctx.timeout()\n\n        with pytest.raises(asyncio.TimeoutError):\n            with ctx:\n                pass\n\n        assert not m_asyncio.current_task.return_value.cancel.called\n\n\n@pytest.mark.skipif(\n    sys.version_info < (3, 11), reason=\"Python 3.11+ is required for .cancelling()\"\n)\nasync def test_timer_context_timeout_does_not_leak_upward() -> None:\n    \"\"\"Verify that the TimerContext does not leak cancellation outside the context manager.\"\"\"\n    loop = asyncio.get_running_loop()\n    ctx = helpers.TimerContext(loop)\n    current_task = asyncio.current_task()\n    assert current_task is not None\n    with pytest.raises(asyncio.TimeoutError):\n        with ctx:\n            assert current_task.cancelling() == 0\n            loop.call_soon(ctx.timeout)\n            await asyncio.sleep(1)\n\n    # After the context manager exits, the task should no longer be cancelling\n    assert current_task.cancelling() == 0\n\n\n@pytest.mark.skipif(\n    sys.version_info < (3, 11), reason=\"Python 3.11+ is required for .cancelling()\"\n)\nasync def test_timer_context_timeout_does_swallow_cancellation() -> None:\n    \"\"\"Verify that the TimerContext does not swallow cancellation.\"\"\"\n    loop = asyncio.get_running_loop()\n    current_task = asyncio.current_task()\n    assert current_task is not None\n    ctx = helpers.TimerContext(loop)\n\n    async def task_with_timeout() -> None:\n        new_task = asyncio.current_task()\n        assert new_task is not None\n        with pytest.raises(asyncio.TimeoutError):\n            with ctx:\n                assert new_task.cancelling() == 0\n                await asyncio.sleep(1)\n\n    task = asyncio.create_task(task_with_timeout())\n    await asyncio.sleep(0)\n    task.cancel()\n    assert task.cancelling() == 1\n    ctx.timeout()\n\n    # Cancellation should not leak into the current task\n    assert current_task.cancelling() == 0\n    # Cancellation should not be swallowed if the task is cancelled\n    # and it also times out\n    await asyncio.sleep(0)\n    with pytest.raises(asyncio.CancelledError):\n        await task\n    assert task.cancelling() == 1\n\n\ndef test_timer_context_no_task(loop: asyncio.AbstractEventLoop) -> None:\n    with pytest.raises(RuntimeError):\n        with helpers.TimerContext(loop):\n            pass\n\n\nasync def test_weakref_handle(loop: asyncio.AbstractEventLoop) -> None:\n    cb = mock.Mock()\n    helpers.weakref_handle(cb, \"test\", 0.01, loop)\n    await asyncio.sleep(0.1)\n    assert cb.test.called\n\n\nasync def test_weakref_handle_with_small_threshold(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    cb = mock.Mock()\n    loop = mock.Mock()\n    loop.time.return_value = 10\n    helpers.weakref_handle(cb, \"test\", 0.1, loop, 0.01)\n    loop.call_at.assert_called_with(\n        11, helpers._weakref_handle, (weakref.ref(cb), \"test\")\n    )\n\n\nasync def test_weakref_handle_weak(loop: asyncio.AbstractEventLoop) -> None:\n    cb = mock.Mock()\n    helpers.weakref_handle(cb, \"test\", 0.01, loop)\n    del cb\n    gc.collect()\n    await asyncio.sleep(0.1)\n\n\n# -------------------- ceil math -------------------------\n\n\ndef test_ceil_call_later() -> None:\n    cb = mock.Mock()\n    loop = mock.Mock()\n    loop.time.return_value = 10.1\n    helpers.call_later(cb, 10.1, loop)\n    loop.call_at.assert_called_with(21.0, cb)\n\n\nasync def test_ceil_timeout_round(loop: asyncio.AbstractEventLoop) -> None:\n    async with helpers.ceil_timeout(7.5) as cm:\n        if sys.version_info >= (3, 11):\n            w = cm.when()\n            assert w is not None\n            frac, integer = modf(w)\n        else:\n            assert cm.deadline is not None\n            frac, integer = modf(cm.deadline)\n        assert frac == 0\n\n\nasync def test_ceil_timeout_small(loop: asyncio.AbstractEventLoop) -> None:\n    async with helpers.ceil_timeout(1.1) as cm:\n        if sys.version_info >= (3, 11):\n            w = cm.when()\n            assert w is not None\n            frac, integer = modf(w)\n        else:\n            assert cm.deadline is not None\n            frac, integer = modf(cm.deadline)\n        # a chance for exact integer with zero fraction is negligible\n        assert frac != 0\n\n\ndef test_ceil_call_later_with_small_threshold() -> None:\n    cb = mock.Mock()\n    loop = mock.Mock()\n    loop.time.return_value = 10.1\n    helpers.call_later(cb, 4.5, loop, 1)\n    loop.call_at.assert_called_with(15, cb)\n\n\ndef test_ceil_call_later_no_timeout() -> None:\n    cb = mock.Mock()\n    loop = mock.Mock()\n    helpers.call_later(cb, 0, loop)\n    assert not loop.call_at.called\n\n\nasync def test_ceil_timeout_none(loop: asyncio.AbstractEventLoop) -> None:\n    async with helpers.ceil_timeout(None) as cm:\n        if sys.version_info >= (3, 11):\n            assert cm.when() is None\n        else:\n            assert cm.deadline is None\n\n\nasync def test_ceil_timeout_small_with_overriden_threshold(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    async with helpers.ceil_timeout(1.5, ceil_threshold=1) as cm:\n        if sys.version_info >= (3, 11):\n            w = cm.when()\n            assert w is not None\n            frac, integer = modf(w)\n        else:\n            assert cm.deadline is not None\n            frac, integer = modf(cm.deadline)\n        assert frac == 0\n\n\n# -------------------------------- ContentDisposition -------------------\n\n\n@pytest.mark.parametrize(\n    \"params, quote_fields, _charset, expected\",\n    [\n        (dict(foo=\"bar\"), True, \"utf-8\", 'attachment; foo=\"bar\"'),\n        (dict(foo=\"bar[]\"), True, \"utf-8\", 'attachment; foo=\"bar[]\"'),\n        (dict(foo=' a\"\"b\\\\'), True, \"utf-8\", 'attachment; foo=\"\\\\ a\\\\\"\\\\\"b\\\\\\\\\"'),\n        (dict(foo=\"bär\"), True, \"utf-8\", \"attachment; foo*=utf-8''b%C3%A4r\"),\n        (dict(foo='bär \"\\\\'), False, \"utf-8\", 'attachment; foo=\"bär \\\\\"\\\\\\\\\"'),\n        (dict(foo=\"bär\"), True, \"latin-1\", \"attachment; foo*=latin-1''b%E4r\"),\n        (dict(filename=\"bär\"), True, \"utf-8\", 'attachment; filename=\"b%C3%A4r\"'),\n        (dict(filename=\"bär\"), True, \"latin-1\", 'attachment; filename=\"b%E4r\"'),\n        (\n            dict(filename='bär \"\\\\'),\n            False,\n            \"utf-8\",\n            'attachment; filename=\"bär \\\\\"\\\\\\\\\"',\n        ),\n    ],\n)\ndef test_content_disposition(\n    params: dict[str, str], quote_fields: bool, _charset: str, expected: str\n) -> None:\n    result = helpers.content_disposition_header(\n        \"attachment\", quote_fields=quote_fields, _charset=_charset, params=params\n    )\n    assert result == expected\n\n\ndef test_content_disposition_bad_type() -> None:\n    with pytest.raises(ValueError):\n        helpers.content_disposition_header(\"foo bar\")\n    with pytest.raises(ValueError):\n        helpers.content_disposition_header(\"—Ç–µ—Å—Ç\")\n    with pytest.raises(ValueError):\n        helpers.content_disposition_header(\"foo\\x00bar\")\n    with pytest.raises(ValueError):\n        helpers.content_disposition_header(\"\")\n\n\ndef test_set_content_disposition_bad_param() -> None:\n    with pytest.raises(ValueError):\n        helpers.content_disposition_header(\"inline\", params={\"foo bar\": \"baz\"})\n    with pytest.raises(ValueError):\n        helpers.content_disposition_header(\"inline\", params={\"—Ç–µ—Å—Ç\": \"baz\"})\n    with pytest.raises(ValueError):\n        helpers.content_disposition_header(\"inline\", params={\"\": \"baz\"})\n    with pytest.raises(ValueError):\n        helpers.content_disposition_header(\"inline\", params={\"foo\\x00bar\": \"baz\"})\n\n\n# --------------------- proxies_from_env ------------------------------\n\n\n@pytest.mark.parametrize(\n    (\"proxy_env_vars\", \"url_input\", \"expected_scheme\"),\n    (\n        ({\"http_proxy\": \"http://aiohttp.io/path\"}, \"http://aiohttp.io/path\", \"http\"),\n        ({\"https_proxy\": \"http://aiohttp.io/path\"}, \"http://aiohttp.io/path\", \"https\"),\n        ({\"ws_proxy\": \"http://aiohttp.io/path\"}, \"http://aiohttp.io/path\", \"ws\"),\n        ({\"wss_proxy\": \"http://aiohttp.io/path\"}, \"http://aiohttp.io/path\", \"wss\"),\n    ),\n    indirect=[\"proxy_env_vars\"],\n    ids=(\"http\", \"https\", \"ws\", \"wss\"),\n)\n@pytest.mark.usefixtures(\"proxy_env_vars\")\ndef test_proxies_from_env(url_input: str, expected_scheme: str) -> None:\n    url = URL(url_input)\n    ret = helpers.proxies_from_env()\n    assert ret.keys() == {expected_scheme}\n    assert ret[expected_scheme].proxy == url\n    assert ret[expected_scheme].proxy_auth is None\n\n\n@pytest.mark.parametrize(\n    (\"proxy_env_vars\", \"url_input\", \"expected_scheme\"),\n    (\n        (\n            {\"https_proxy\": \"https://aiohttp.io/path\"},\n            \"https://aiohttp.io/path\",\n            \"https\",\n        ),\n        ({\"wss_proxy\": \"wss://aiohttp.io/path\"}, \"wss://aiohttp.io/path\", \"wss\"),\n    ),\n    indirect=[\"proxy_env_vars\"],\n    ids=(\"https\", \"wss\"),\n)\n@pytest.mark.usefixtures(\"proxy_env_vars\")\ndef test_proxies_from_env_skipped(\n    caplog: pytest.LogCaptureFixture, url_input: str, expected_scheme: str\n) -> None:\n    url = URL(url_input)\n    assert helpers.proxies_from_env() == {}\n    assert len(caplog.records) == 1\n    log_message = (\n        f\"{expected_scheme.upper()!s} proxies {url!s} are not supported, ignoring\"\n    )\n    assert caplog.record_tuples == [(\"aiohttp.client\", 30, log_message)]\n\n\n@pytest.mark.parametrize(\n    (\"proxy_env_vars\", \"url_input\", \"expected_scheme\"),\n    (\n        (\n            {\"http_proxy\": \"http://user:pass@aiohttp.io/path\"},\n            \"http://user:pass@aiohttp.io/path\",\n            \"http\",\n        ),\n    ),\n    indirect=[\"proxy_env_vars\"],\n    ids=(\"http\",),\n)\n@pytest.mark.usefixtures(\"proxy_env_vars\")\ndef test_proxies_from_env_http_with_auth(url_input: str, expected_scheme: str) -> None:\n    url = URL(\"http://user:pass@aiohttp.io/path\")\n    ret = helpers.proxies_from_env()\n    assert ret.keys() == {expected_scheme}\n    assert ret[expected_scheme].proxy == url.with_user(None)\n    proxy_auth = ret[expected_scheme].proxy_auth\n    assert proxy_auth is not None\n    assert proxy_auth.login == \"user\"\n    assert proxy_auth.password == \"pass\"\n    assert proxy_auth.encoding == \"latin1\"\n\n\n# --------------------- get_env_proxy_for_url ------------------------------\n\n\n@pytest.fixture\ndef proxy_env_vars(\n    monkeypatch: pytest.MonkeyPatch, request: pytest.FixtureRequest\n) -> object:\n    for schema in getproxies_environment().keys():\n        monkeypatch.delenv(f\"{schema}_proxy\", False)\n\n    for proxy_type, proxy_list in request.param.items():\n        monkeypatch.setenv(proxy_type, proxy_list)\n\n    return request.param\n\n\n@pytest.mark.parametrize(\n    (\"proxy_env_vars\", \"url_input\", \"expected_err_msg\"),\n    (\n        (\n            {\"no_proxy\": \"aiohttp.io\"},\n            \"http://aiohttp.io/path\",\n            r\"Proxying is disallowed for `'aiohttp.io'`\",\n        ),\n        (\n            {\"no_proxy\": \"aiohttp.io,proxy.com\"},\n            \"http://aiohttp.io/path\",\n            r\"Proxying is disallowed for `'aiohttp.io'`\",\n        ),\n        (\n            {\"http_proxy\": \"http://example.com\"},\n            \"https://aiohttp.io/path\",\n            r\"No proxies found for `https://aiohttp.io/path` in the env\",\n        ),\n        (\n            {\"https_proxy\": \"https://example.com\"},\n            \"http://aiohttp.io/path\",\n            r\"No proxies found for `http://aiohttp.io/path` in the env\",\n        ),\n        (\n            {},\n            \"https://aiohttp.io/path\",\n            r\"No proxies found for `https://aiohttp.io/path` in the env\",\n        ),\n        (\n            {\"https_proxy\": \"https://example.com\"},\n            \"\",\n            r\"No proxies found for `` in the env\",\n        ),\n    ),\n    indirect=[\"proxy_env_vars\"],\n    ids=(\n        \"url_matches_the_no_proxy_list\",\n        \"url_matches_the_no_proxy_list_multiple\",\n        \"url_scheme_does_not_match_http_proxy_list\",\n        \"url_scheme_does_not_match_https_proxy_list\",\n        \"no_proxies_are_set\",\n        \"url_is_empty\",\n    ),\n)\n@pytest.mark.usefixtures(\"proxy_env_vars\")\ndef test_get_env_proxy_for_url_negative(url_input: str, expected_err_msg: str) -> None:\n    url = URL(url_input)\n    with pytest.raises(LookupError, match=expected_err_msg):\n        helpers.get_env_proxy_for_url(url)\n\n\n@pytest.mark.parametrize(\n    (\"proxy_env_vars\", \"url_input\"),\n    (\n        ({\"http_proxy\": \"http://example.com\"}, \"http://aiohttp.io/path\"),\n        ({\"https_proxy\": \"http://example.com\"}, \"https://aiohttp.io/path\"),\n        (\n            {\"http_proxy\": \"http://example.com,http://proxy.org\"},\n            \"http://aiohttp.io/path\",\n        ),\n    ),\n    indirect=[\"proxy_env_vars\"],\n    ids=(\n        \"url_scheme_match_http_proxy_list\",\n        \"url_scheme_match_https_proxy_list\",\n        \"url_scheme_match_http_proxy_list_multiple\",\n    ),\n)\ndef test_get_env_proxy_for_url(proxy_env_vars: dict[str, str], url_input: str) -> None:\n    url = URL(url_input)\n    proxy, proxy_auth = helpers.get_env_proxy_for_url(url)\n    proxy_list = proxy_env_vars[url.scheme + \"_proxy\"]\n    assert proxy == URL(proxy_list)\n    assert proxy_auth is None\n\n\n# ------------- set_result / set_exception ----------------------\n\n\nasync def test_set_result(loop: asyncio.AbstractEventLoop) -> None:\n    fut = loop.create_future()\n    helpers.set_result(fut, 123)\n    assert 123 == await fut\n\n\nasync def test_set_result_cancelled(loop: asyncio.AbstractEventLoop) -> None:\n    fut = loop.create_future()\n    fut.cancel()\n    helpers.set_result(fut, 123)\n\n    with pytest.raises(asyncio.CancelledError):\n        await fut\n\n\nasync def test_set_exception(loop: asyncio.AbstractEventLoop) -> None:\n    fut = loop.create_future()\n    helpers.set_exception(fut, RuntimeError())\n    with pytest.raises(RuntimeError):\n        await fut\n\n\nasync def test_set_exception_cancelled(loop: asyncio.AbstractEventLoop) -> None:\n    fut = loop.create_future()\n    fut.cancel()\n    helpers.set_exception(fut, RuntimeError())\n\n    with pytest.raises(asyncio.CancelledError):\n        await fut\n\n\n# ----------- ChainMapProxy --------------------------\n\nAppKeyDict = dict[str | web.AppKey[object], object]\n\n\nclass TestChainMapProxy:\n    def test_inheritance(self) -> None:\n        with pytest.raises(TypeError):\n\n            class A(helpers.ChainMapProxy):  # type: ignore[misc]\n                pass\n\n    def test_getitem(self) -> None:\n        d1: AppKeyDict = {\"a\": 2, \"b\": 3}\n        d2: AppKeyDict = {\"a\": 1}\n        cp = helpers.ChainMapProxy([d1, d2])\n        assert cp[\"a\"] == 2\n        assert cp[\"b\"] == 3\n\n    def test_getitem_not_found(self) -> None:\n        d: AppKeyDict = {\"a\": 1}\n        cp = helpers.ChainMapProxy([d])\n        with pytest.raises(KeyError):\n            cp[\"b\"]\n\n    def test_get(self) -> None:\n        d1: AppKeyDict = {\"a\": 2, \"b\": 3}\n        d2: AppKeyDict = {\"a\": 1}\n        cp = helpers.ChainMapProxy([d1, d2])\n        assert cp.get(\"a\") == 2\n\n    def test_get_default(self) -> None:\n        d1: AppKeyDict = {\"a\": 2, \"b\": 3}\n        d2: AppKeyDict = {\"a\": 1}\n        cp = helpers.ChainMapProxy([d1, d2])\n        assert cp.get(\"c\", 4) == 4\n\n    def test_get_non_default(self) -> None:\n        d1: AppKeyDict = {\"a\": 2, \"b\": 3}\n        d2: AppKeyDict = {\"a\": 1}\n        cp = helpers.ChainMapProxy([d1, d2])\n        assert cp.get(\"a\", 4) == 2\n\n    def test_len(self) -> None:\n        d1: AppKeyDict = {\"a\": 2, \"b\": 3}\n        d2: AppKeyDict = {\"a\": 1}\n        cp = helpers.ChainMapProxy([d1, d2])\n        assert len(cp) == 2\n\n    def test_iter(self) -> None:\n        d1: AppKeyDict = {\"a\": 2, \"b\": 3}\n        d2: AppKeyDict = {\"a\": 1}\n        cp = helpers.ChainMapProxy([d1, d2])\n        assert set(cp) == {\"a\", \"b\"}\n\n    def test_contains(self) -> None:\n        d1: AppKeyDict = {\"a\": 2, \"b\": 3}\n        d2: AppKeyDict = {\"a\": 1}\n        cp = helpers.ChainMapProxy([d1, d2])\n        assert \"a\" in cp\n        assert \"b\" in cp\n        assert \"c\" not in cp\n\n    def test_bool(self) -> None:\n        assert helpers.ChainMapProxy([{\"a\": 1}])\n        assert not helpers.ChainMapProxy([{}, {}])\n        assert not helpers.ChainMapProxy([])\n\n    def test_repr(self) -> None:\n        d1: AppKeyDict = {\"a\": 2, \"b\": 3}\n        d2: AppKeyDict = {\"a\": 1}\n        cp = helpers.ChainMapProxy([d1, d2])\n        expected = f\"ChainMapProxy({d1!r}, {d2!r})\"\n        assert expected == repr(cp)\n\n\ndef test_is_expected_content_type_json_match_exact() -> None:\n    expected_ct = \"application/json\"\n    response_ct = \"application/json\"\n    assert is_expected_content_type(\n        response_content_type=response_ct, expected_content_type=expected_ct\n    )\n\n\ndef test_is_expected_content_type_json_match_partially() -> None:\n    expected_ct = \"application/json\"\n    response_ct = \"application/alto-costmap+json\"  # mime-type from rfc7285\n    assert is_expected_content_type(\n        response_content_type=response_ct, expected_content_type=expected_ct\n    )\n\n\ndef test_is_expected_content_type_non_application_json_suffix() -> None:\n    expected_ct = \"application/json\"\n    response_ct = \"model/gltf+json\"  # rfc 6839\n    assert is_expected_content_type(\n        response_content_type=response_ct, expected_content_type=expected_ct\n    )\n\n\ndef test_is_expected_content_type_non_application_json_private_suffix() -> None:\n    expected_ct = \"application/json\"\n    response_ct = \"x-foo/bar+json\"  # rfc 6839\n    assert is_expected_content_type(\n        response_content_type=response_ct, expected_content_type=expected_ct\n    )\n\n\ndef test_is_expected_content_type_json_non_lowercase() -> None:\n    \"\"\"Per RFC 2045, media type matching is case insensitive.\"\"\"\n    expected_ct = \"application/json\"\n    response_ct = \"Application/JSON\"\n    assert is_expected_content_type(\n        response_content_type=response_ct, expected_content_type=expected_ct\n    )\n\n\ndef test_is_expected_content_type_json_trailing_chars() -> None:\n    expected_ct = \"application/json\"\n    response_ct = \"application/json-seq\"\n    assert not is_expected_content_type(\n        response_content_type=response_ct, expected_content_type=expected_ct\n    )\n\n\ndef test_is_expected_content_type_non_json_match_exact() -> None:\n    expected_ct = \"text/javascript\"\n    response_ct = \"text/javascript\"\n    assert is_expected_content_type(\n        response_content_type=response_ct, expected_content_type=expected_ct\n    )\n\n\ndef test_is_expected_content_type_non_json_not_match() -> None:\n    expected_ct = \"application/json\"\n    response_ct = \"text/plain\"\n    assert not is_expected_content_type(\n        response_content_type=response_ct, expected_content_type=expected_ct\n    )\n\n\n# It's necessary to subclass CookieMixin before using it.\n# See the comments on its __slots__.\nclass CookieImplementation(helpers.CookieMixin):\n    pass\n\n\ndef test_cookies_mixin() -> None:\n    sut = CookieImplementation()\n\n    assert sut.cookies == {}\n    assert str(sut.cookies) == \"\"\n\n    sut.set_cookie(\"name\", \"value\")\n    assert str(sut.cookies) == \"Set-Cookie: name=value; Path=/\"\n    sut.set_cookie(\"name\", \"\")\n    assert str(sut.cookies) == 'Set-Cookie: name=\"\"; Path=/'\n    sut.set_cookie(\"name\", \"value\")\n    assert str(sut.cookies) == \"Set-Cookie: name=value; Path=/\"\n\n    sut.set_cookie(\"name\", \"other_value\")\n    assert str(sut.cookies) == \"Set-Cookie: name=other_value; Path=/\"\n\n    sut.cookies[\"name\"] = \"another_other_value\"\n    sut.cookies[\"name\"][\"max-age\"] = 10\n    assert (\n        str(sut.cookies) == \"Set-Cookie: name=another_other_value; Max-Age=10; Path=/\"\n    )\n\n    sut.del_cookie(\"name\")\n    expected = (\n        'Set-Cookie: name=\"\"; '\n        \"expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/\"\n    )\n    assert str(sut.cookies) == expected\n    sut.del_cookie(\"name\")\n    assert str(sut.cookies) == expected\n\n    sut.set_cookie(\"name\", \"value\", domain=\"local.host\")\n    expected = \"Set-Cookie: name=value; Domain=local.host; Path=/\"\n    assert str(sut.cookies) == expected\n\n\ndef test_cookies_mixin_path() -> None:\n    sut = CookieImplementation()\n\n    assert sut.cookies == {}\n\n    sut.set_cookie(\"name\", \"value\", path=\"/some/path\")\n    assert str(sut.cookies) == \"Set-Cookie: name=value; Path=/some/path\"\n    sut.set_cookie(\"name\", \"value\", expires=\"123\")\n    assert str(sut.cookies) == \"Set-Cookie: name=value; expires=123; Path=/\"\n    sut.set_cookie(\n        \"name\",\n        \"value\",\n        domain=\"example.com\",\n        path=\"/home\",\n        expires=\"123\",\n        max_age=\"10\",\n        secure=True,\n        httponly=True,\n        samesite=\"lax\",\n    )\n    assert (\n        str(sut.cookies).lower() == \"set-cookie: name=value; \"\n        \"domain=example.com; \"\n        \"expires=123; \"\n        \"httponly; \"\n        \"max-age=10; \"\n        \"path=/home; \"\n        \"samesite=lax; \"\n        \"secure\"\n    )\n\n\n@pytest.mark.skipif(sys.version_info < (3, 14), reason=\"No partitioned support\")\ndef test_cookies_mixin_partitioned() -> None:\n    sut = CookieImplementation()\n\n    assert sut.cookies == {}\n\n    sut.set_cookie(\"name\", \"value\", partitioned=False)\n    assert str(sut.cookies) == \"Set-Cookie: name=value; Path=/\"\n\n    sut.set_cookie(\"name\", \"value\", partitioned=True)\n    assert str(sut.cookies) == \"Set-Cookie: name=value; Partitioned; Path=/\"\n\n\ndef test_sutonse_cookie__issue_del_cookie() -> None:\n    sut = CookieImplementation()\n\n    assert sut.cookies == {}\n    assert str(sut.cookies) == \"\"\n\n    sut.del_cookie(\"name\")\n    expected = (\n        'Set-Cookie: name=\"\"; '\n        \"expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/\"\n    )\n    assert str(sut.cookies) == expected\n\n\ndef test_cookie_set_after_del() -> None:\n    sut = CookieImplementation()\n\n    sut.del_cookie(\"name\")\n    sut.set_cookie(\"name\", \"val\")\n    # check for Max-Age dropped\n    expected = \"Set-Cookie: name=val; Path=/\"\n    assert str(sut.cookies) == expected\n\n\ndef test_populate_with_cookies() -> None:\n    cookies_mixin = CookieImplementation()\n    cookies_mixin.set_cookie(\"name\", \"value\")\n    headers = CIMultiDict[str]()\n\n    helpers.populate_with_cookies(headers, cookies_mixin.cookies)\n    assert headers == CIMultiDict({\"Set-Cookie\": \"name=value; Path=/\"})\n\n\n@pytest.mark.parametrize(\n    [\"value\", \"expected\"],\n    [\n        # email.utils.parsedate returns None\n        pytest.param(\"xxyyzz\", None),\n        # datetime.datetime fails with ValueError(\"year 4446413 is out of range\")\n        pytest.param(\"Tue, 08 Oct 4446413 00:56:40 GMT\", None),\n        # datetime.datetime fails with ValueError(\"second must be in 0..59\")\n        pytest.param(\"Tue, 08 Oct 2000 00:56:80 GMT\", None),\n        # OK\n        pytest.param(\n            \"Tue, 08 Oct 2000 00:56:40 GMT\",\n            datetime.datetime(2000, 10, 8, 0, 56, 40, tzinfo=datetime.timezone.utc),\n        ),\n        # OK (ignore timezone and overwrite to UTC)\n        pytest.param(\n            \"Tue, 08 Oct 2000 00:56:40 +0900\",\n            datetime.datetime(2000, 10, 8, 0, 56, 40, tzinfo=datetime.timezone.utc),\n        ),\n    ],\n)\ndef test_parse_http_date(value: str, expected: datetime.datetime | None) -> None:\n    assert parse_http_date(value) == expected\n\n\n@pytest.mark.parametrize(\n    [\"netrc_contents\", \"expected_username\"],\n    [\n        (\n            \"machine example.com login username password pass\\n\",\n            \"username\",\n        ),\n    ],\n    indirect=(\"netrc_contents\",),\n)\n@pytest.mark.usefixtures(\"netrc_contents\")\ndef test_netrc_from_env(expected_username: str) -> None:\n    \"\"\"Test that reading netrc files from env works as expected\"\"\"\n    netrc_obj = helpers.netrc_from_env()\n    assert netrc_obj is not None\n    auth = netrc_obj.authenticators(\"example.com\")\n    assert auth is not None\n    assert auth[0] == expected_username\n\n\n@pytest.fixture\ndef protected_dir(tmp_path: Path) -> Iterator[Path]:\n    protected_dir = tmp_path / \"protected\"\n    protected_dir.mkdir()\n    try:\n        protected_dir.chmod(0o600)\n        yield protected_dir\n    finally:\n        protected_dir.rmdir()\n\n\ndef test_netrc_from_home_does_not_raise_if_access_denied(\n    protected_dir: Path, monkeypatch: pytest.MonkeyPatch\n) -> None:\n    monkeypatch.setattr(Path, \"home\", lambda: protected_dir)\n    monkeypatch.delenv(\"NETRC\", raising=False)\n\n    helpers.netrc_from_env()\n\n\n@pytest.mark.parametrize(\n    [\"netrc_contents\", \"expected_auth\"],\n    [\n        (\n            \"machine example.com login username password pass\\n\",\n            helpers.BasicAuth(\"username\", \"pass\"),\n        ),\n        (\n            \"machine example.com account username password pass\\n\",\n            helpers.BasicAuth(\"username\", \"pass\"),\n        ),\n        (\n            \"machine example.com password pass\\n\",\n            helpers.BasicAuth(\"\", \"pass\"),\n        ),\n    ],\n    indirect=(\"netrc_contents\",),\n)\n@pytest.mark.usefixtures(\"netrc_contents\")\ndef test_basicauth_present_in_netrc(  # type: ignore[misc]\n    expected_auth: helpers.BasicAuth,\n) -> None:\n    \"\"\"Test that netrc file contents are properly parsed into BasicAuth tuples\"\"\"\n    netrc_obj = helpers.netrc_from_env()\n\n    assert expected_auth == helpers.basicauth_from_netrc(netrc_obj, \"example.com\")\n\n\n@pytest.mark.parametrize(\n    [\"netrc_contents\"],\n    [\n        (\"\",),\n    ],\n    indirect=(\"netrc_contents\",),\n)\n@pytest.mark.usefixtures(\"netrc_contents\")\ndef test_read_basicauth_from_empty_netrc() -> None:\n    \"\"\"Test that an error is raised if netrc doesn't have an entry for our host\"\"\"\n    netrc_obj = helpers.netrc_from_env()\n\n    with pytest.raises(\n        LookupError, match=\"No entry for example.com found in the `.netrc` file.\"\n    ):\n        helpers.basicauth_from_netrc(netrc_obj, \"example.com\")\n\n\ndef test_method_must_be_empty_body() -> None:\n    \"\"\"Test that HEAD is the only method that unequivocally must have an empty body.\"\"\"\n    assert \"HEAD\" in EMPTY_BODY_METHODS\n    # CONNECT is only empty on a successful response\n    assert \"CONNECT\" not in EMPTY_BODY_METHODS\n\n\ndef test_should_remove_content_length_is_subset_of_must_be_empty_body() -> None:\n    \"\"\"Test should_remove_content_length is always a subset of must_be_empty_body.\"\"\"\n    assert should_remove_content_length(\"GET\", 101) is True\n    assert must_be_empty_body(\"GET\", 101) is True\n\n    assert should_remove_content_length(\"GET\", 102) is True\n    assert must_be_empty_body(\"GET\", 102) is True\n\n    assert should_remove_content_length(\"GET\", 204) is True\n    assert must_be_empty_body(\"GET\", 204) is True\n\n    assert should_remove_content_length(\"GET\", 204) is True\n    assert must_be_empty_body(\"GET\", 204) is True\n\n    assert should_remove_content_length(\"GET\", 200) is False\n    assert must_be_empty_body(\"GET\", 200) is False\n\n    assert should_remove_content_length(\"HEAD\", 200) is False\n    assert must_be_empty_body(\"HEAD\", 200) is True\n\n    # CONNECT is only empty on a successful response\n    assert should_remove_content_length(\"CONNECT\", 200) is True\n    assert must_be_empty_body(\"CONNECT\", 200) is True\n\n    assert should_remove_content_length(\"CONNECT\", 201) is True\n    assert must_be_empty_body(\"CONNECT\", 201) is True\n\n    assert should_remove_content_length(\"CONNECT\", 300) is False\n    assert must_be_empty_body(\"CONNECT\", 300) is False\n"
  },
  {
    "path": "tests/test_http_exceptions.py",
    "content": "# Tests for http_exceptions.py\n\nimport pickle\n\nfrom multidict import CIMultiDict\n\nfrom aiohttp import http_exceptions\n\n\nclass TestHttpProcessingError:\n    def test_ctor(self) -> None:\n        err = http_exceptions.HttpProcessingError(\n            code=500, message=\"Internal error\", headers=CIMultiDict()\n        )\n        assert err.code == 500\n        assert err.message == \"Internal error\"\n        assert err.headers == CIMultiDict()\n\n    def test_pickle(self) -> None:\n        err = http_exceptions.HttpProcessingError(\n            code=500, message=\"Internal error\", headers=CIMultiDict()\n        )\n        err.foo = \"bar\"  # type: ignore[attr-defined]\n        for proto in range(pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(err, proto)\n            err2 = pickle.loads(pickled)\n            assert err2.code == 500\n            assert err2.message == \"Internal error\"\n            assert err2.headers == CIMultiDict()\n            assert err2.foo == \"bar\"\n\n    def test_str(self) -> None:\n        err = http_exceptions.HttpProcessingError(\n            code=500, message=\"Internal error\", headers=CIMultiDict()\n        )\n        assert str(err) == \"500, message:\\n  Internal error\"\n\n    def test_repr(self) -> None:\n        err = http_exceptions.HttpProcessingError(\n            code=500, message=\"Internal error\", headers=CIMultiDict()\n        )\n        assert repr(err) == (\"<HttpProcessingError: 500, message='Internal error'>\")\n\n\nclass TestBadHttpMessage:\n    def test_ctor(self) -> None:\n        err = http_exceptions.BadHttpMessage(\"Bad HTTP message\", headers=CIMultiDict())\n        assert err.code == 400\n        assert err.message == \"Bad HTTP message\"\n        assert err.headers == CIMultiDict()\n\n    def test_pickle(self) -> None:\n        err = http_exceptions.BadHttpMessage(\n            message=\"Bad HTTP message\", headers=CIMultiDict()\n        )\n        err.foo = \"bar\"  # type: ignore[attr-defined]\n        for proto in range(pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(err, proto)\n            err2 = pickle.loads(pickled)\n            assert err2.code == 400\n            assert err2.message == \"Bad HTTP message\"\n            assert err2.headers == {}\n            assert err2.foo == \"bar\"\n\n    def test_str(self) -> None:\n        err = http_exceptions.BadHttpMessage(\n            message=\"Bad HTTP message\", headers=CIMultiDict()\n        )\n        assert str(err) == \"400, message:\\n  Bad HTTP message\"\n\n    def test_repr(self) -> None:\n        err = http_exceptions.BadHttpMessage(\n            message=\"Bad HTTP message\", headers=CIMultiDict()\n        )\n        assert repr(err) == \"<BadHttpMessage: 400, message='Bad HTTP message'>\"\n\n\nclass TestLineTooLong:\n    def test_ctor(self) -> None:\n        err = http_exceptions.LineTooLong(b\"spam\", 10)\n        assert err.code == 400\n        assert err.message == \"Got more than 10 bytes when reading: b'spam'.\"\n        assert err.headers is None\n\n    def test_pickle(self) -> None:\n        err = http_exceptions.LineTooLong(line=b\"spam\", limit=10)\n        err.foo = \"bar\"  # type: ignore[attr-defined]\n        for proto in range(pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(err, proto)\n            err2 = pickle.loads(pickled)\n            assert err2.code == 400\n            assert err2.message == (\"Got more than 10 bytes when reading: b'spam'.\")\n            assert err2.headers is None\n            assert err2.foo == \"bar\"\n\n    def test_str(self) -> None:\n        err = http_exceptions.LineTooLong(line=b\"spam\", limit=10)\n        expected = \"400, message:\\n  Got more than 10 bytes when reading: b'spam'.\"\n        assert str(err) == expected\n\n    def test_repr(self) -> None:\n        err = http_exceptions.LineTooLong(line=b\"spam\", limit=10)\n        assert repr(err) == (\n            '<LineTooLong: 400, message=\"Got more than '\n            \"10 bytes when reading: b'spam'.\\\">\"\n        )\n\n\nclass TestInvalidHeader:\n    def test_ctor(self) -> None:\n        err = http_exceptions.InvalidHeader(\"X-Spam\")\n        assert err.code == 400\n        assert err.message == \"Invalid HTTP header: 'X-Spam'\"\n        assert err.headers is None\n\n    def test_pickle(self) -> None:\n        err = http_exceptions.InvalidHeader(hdr=\"X-Spam\")\n        err.foo = \"bar\"  # type: ignore[attr-defined]\n        for proto in range(pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(err, proto)\n            err2 = pickle.loads(pickled)\n            assert err2.code == 400\n            assert err2.message == \"Invalid HTTP header: 'X-Spam'\"\n            assert err2.headers is None\n            assert err2.foo == \"bar\"\n\n    def test_str(self) -> None:\n        err = http_exceptions.InvalidHeader(hdr=\"X-Spam\")\n        assert str(err) == \"400, message:\\n  Invalid HTTP header: 'X-Spam'\"\n\n    def test_repr(self) -> None:\n        err = http_exceptions.InvalidHeader(hdr=\"X-Spam\")\n        expected = \"<InvalidHeader: 400, message=\\\"Invalid HTTP header: 'X-Spam'\\\">\"\n        assert repr(err) == expected\n\n\nclass TestBadStatusLine:\n    def test_ctor(self) -> None:\n        err = http_exceptions.BadStatusLine(\"Test\")\n        assert err.line == \"Test\"\n        assert str(err) == \"400, message:\\n  Bad status line 'Test'\"\n\n    def test_ctor2(self) -> None:\n        err = http_exceptions.BadStatusLine(\"\")\n        assert err.line == \"\"\n        assert str(err) == \"400, message:\\n  Bad status line ''\"\n\n    def test_pickle(self) -> None:\n        err = http_exceptions.BadStatusLine(\"Test\")\n        err.foo = \"bar\"  # type: ignore[attr-defined]\n        for proto in range(pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(err, proto)\n            err2 = pickle.loads(pickled)\n            assert err2.line == \"Test\"\n            assert err2.foo == \"bar\"\n"
  },
  {
    "path": "tests/test_http_parser.py",
    "content": "# Tests for aiohttp/protocol.py\n\nimport asyncio\nimport re\nimport sys\nimport zlib\nfrom collections.abc import Iterable\nfrom contextlib import suppress\nfrom typing import Any\nfrom unittest import mock\nfrom urllib.parse import quote\n\nimport pytest\nfrom multidict import CIMultiDict\nfrom yarl import URL\n\nimport aiohttp\nfrom aiohttp import http_exceptions, streams\nfrom aiohttp.base_protocol import BaseProtocol\nfrom aiohttp.helpers import NO_EXTENSIONS\nfrom aiohttp.http_parser import (\n    DeflateBuffer,\n    HeadersParser,\n    HttpParser,\n    HttpPayloadParser,\n    HttpRequestParser,\n    HttpRequestParserPy,\n    HttpResponseParser,\n    HttpResponseParserPy,\n)\nfrom aiohttp.http_writer import HttpVersion\n\ntry:\n    try:\n        import brotlicffi as brotli\n    except ImportError:\n        import brotli\nexcept ImportError:  # pragma: no cover\n    brotli = None\n\ntry:\n    if sys.version_info >= (3, 14):\n        import compression.zstd as zstandard  # noqa: I900\n    else:\n        import backports.zstd as zstandard\nexcept ImportError:\n    zstandard = None  # type: ignore[assignment]\n\nREQUEST_PARSERS = [HttpRequestParserPy]\nRESPONSE_PARSERS = [HttpResponseParserPy]\n\nwith suppress(ImportError):\n    from aiohttp.http_parser import HttpRequestParserC, HttpResponseParserC\n\n    REQUEST_PARSERS.append(HttpRequestParserC)\n    RESPONSE_PARSERS.append(HttpResponseParserC)\n\n\n@pytest.fixture\ndef protocol() -> Any:\n    return mock.create_autospec(BaseProtocol, spec_set=True, instance=True)\n\n\ndef _gen_ids(parsers: Iterable[type[HttpParser[Any]]]) -> list[str]:\n    return [\n        \"py-parser\" if parser.__module__ == \"aiohttp.http_parser\" else \"c-parser\"\n        for parser in parsers\n    ]\n\n\n@pytest.fixture(params=REQUEST_PARSERS, ids=_gen_ids(REQUEST_PARSERS))\ndef parser(\n    loop: asyncio.AbstractEventLoop,\n    protocol: BaseProtocol,\n    request: pytest.FixtureRequest,\n) -> HttpRequestParser:\n    # Parser implementations\n    return request.param(  # type: ignore[no-any-return]\n        protocol,\n        loop,\n        2**16,\n        max_line_size=8190,\n        max_headers=128,\n        max_field_size=8190,\n    )\n\n\n@pytest.fixture(params=REQUEST_PARSERS, ids=_gen_ids(REQUEST_PARSERS))\ndef request_cls(request: pytest.FixtureRequest) -> type[HttpRequestParser]:\n    # Request Parser class\n    return request.param  # type: ignore[no-any-return]\n\n\n@pytest.fixture(params=RESPONSE_PARSERS, ids=_gen_ids(RESPONSE_PARSERS))\ndef response(\n    loop: asyncio.AbstractEventLoop,\n    protocol: BaseProtocol,\n    request: pytest.FixtureRequest,\n) -> HttpResponseParser:\n    # Parser implementations\n    return request.param(  # type: ignore[no-any-return]\n        protocol,\n        loop,\n        2**16,\n        max_line_size=8190,\n        max_headers=128,\n        max_field_size=8190,\n        read_until_eof=True,\n    )\n\n\n@pytest.fixture(params=RESPONSE_PARSERS, ids=_gen_ids(RESPONSE_PARSERS))\ndef response_cls(request: pytest.FixtureRequest) -> type[HttpResponseParser]:\n    # Parser implementations\n    return request.param  # type: ignore[no-any-return]\n\n\n@pytest.mark.skipif(NO_EXTENSIONS, reason=\"Extensions available but not imported\")\ndef test_c_parser_loaded() -> None:\n    assert \"HttpRequestParserC\" in dir(aiohttp.http_parser)\n    assert \"HttpResponseParserC\" in dir(aiohttp.http_parser)\n    assert \"RawRequestMessageC\" in dir(aiohttp.http_parser)\n    assert \"RawResponseMessageC\" in dir(aiohttp.http_parser)\n\n\ndef test_parse_headers(parser: HttpRequestParser) -> None:\n    text = b\"\"\"GET /test HTTP/1.1\\r\ntest: a line\\r\ntest2: data\\r\n\\r\n\"\"\"\n    messages, upgrade, tail = parser.feed_data(text)\n    assert len(messages) == 1\n    msg = messages[0][0]\n\n    assert list(msg.headers.items()) == [(\"test\", \"a line\"), (\"test2\", \"data\")]\n    assert msg.raw_headers == ((b\"test\", b\"a line\"), (b\"test2\", b\"data\"))\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n\n\ndef test_reject_obsolete_line_folding(parser: HttpRequestParser) -> None:\n    text = b\"\"\"GET /test HTTP/1.1\\r\ntest: line\\r\n Content-Length: 48\\r\ntest2: data\\r\n\\r\n\"\"\"\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(text)\n\n\n@pytest.mark.skipif(NO_EXTENSIONS, reason=\"Only tests C parser.\")\ndef test_invalid_character(\n    loop: asyncio.AbstractEventLoop,\n    protocol: BaseProtocol,\n    request: pytest.FixtureRequest,\n) -> None:\n    parser = HttpRequestParserC(\n        protocol,\n        loop,\n        2**16,\n        max_line_size=8190,\n        max_field_size=8190,\n    )\n    text = b\"POST / HTTP/1.1\\r\\nHost: localhost:8080\\r\\nSet-Cookie: abc\\x01def\\r\\n\\r\\n\"\n    error_detail = re.escape(r\"\"\":\n\n    b'Set-Cookie: abc\\x01def'\n                     ^\"\"\")\n    with pytest.raises(http_exceptions.BadHttpMessage, match=error_detail):\n        parser.feed_data(text)\n\n\n@pytest.mark.skipif(NO_EXTENSIONS, reason=\"Only tests C parser.\")\ndef test_invalid_linebreak(\n    loop: asyncio.AbstractEventLoop,\n    protocol: BaseProtocol,\n    request: pytest.FixtureRequest,\n) -> None:\n    parser = HttpRequestParserC(\n        protocol,\n        loop,\n        2**16,\n        max_line_size=8190,\n        max_field_size=8190,\n    )\n    text = b\"GET /world HTTP/1.1\\r\\nHost: 127.0.0.1\\n\\r\\n\"\n    error_detail = re.escape(r\"\"\":\n\n    b'Host: 127.0.0.1\\n'\n                     ^\"\"\")\n    with pytest.raises(http_exceptions.BadHttpMessage, match=error_detail):\n        parser.feed_data(text)\n\n\ndef test_cve_2023_37276(parser: HttpRequestParser) -> None:\n    text = b\"\"\"POST / HTTP/1.1\\r\\nHost: localhost:8080\\r\\nX-Abc: \\rxTransfer-Encoding: chunked\\r\\n\\r\\n\"\"\"\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(text)\n\n\n@pytest.mark.parametrize(\n    \"rfc9110_5_6_2_token_delim\",\n    r'\"(),/:;<=>?@[\\]{}',\n)\ndef test_bad_header_name(\n    parser: HttpRequestParser, rfc9110_5_6_2_token_delim: str\n) -> None:\n    text = f\"POST / HTTP/1.1\\r\\nhead{rfc9110_5_6_2_token_delim}er: val\\r\\n\\r\\n\".encode()\n    if rfc9110_5_6_2_token_delim == \":\":\n        # Inserting colon into header just splits name/value earlier.\n        parser.feed_data(text)\n        return\n\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(text)\n\n\n@pytest.mark.parametrize(\n    \"hdr\",\n    (\n        \"Content-Length: -5\",  # https://www.rfc-editor.org/rfc/rfc9110.html#name-content-length\n        \"Content-Length: +256\",\n        \"Content-Length: \\N{SUPERSCRIPT ONE}\",\n        \"Content-Length: \\N{MATHEMATICAL DOUBLE-STRUCK DIGIT ONE}\",\n        \"Foo: abc\\rdef\",  # https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5-5\n        \"Bar: abc\\ndef\",\n        \"Baz: abc\\x00def\",\n        \"Foo : bar\",  # https://www.rfc-editor.org/rfc/rfc9112.html#section-5.1-2\n        \"Foo\\t: bar\",\n        \"\\xffoo: bar\",\n        \"Foo: abc\\x01def\",  # CTL bytes forbidden per RFC 9110 §5.5\n        \"Foo: abc\\x7fdef\",  # DEL is also a CTL byte\n        \"Foo: abc\\x1fdef\",\n    ),\n)\ndef test_bad_headers(parser: HttpRequestParser, hdr: str) -> None:\n    text = f\"POST / HTTP/1.1\\r\\n{hdr}\\r\\n\\r\\n\".encode()\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(text)\n\n\ndef test_ctl_host_header_bad_characters(parser: HttpRequestParser) -> None:\n    \"\"\"CTL byte in Host header must be rejected.\"\"\"\n    text = b\"GET /test HTTP/1.1\\r\\nHost: trusted.example\\x01@bad.test\\r\\n\\r\\n\"\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(text)\n\n\ndef test_unpaired_surrogate_in_header_py(\n    loop: asyncio.AbstractEventLoop, protocol: BaseProtocol\n) -> None:\n    parser = HttpRequestParserPy(\n        protocol,\n        loop,\n        2**16,\n        max_line_size=8190,\n        max_field_size=8190,\n    )\n    text = b\"POST / HTTP/1.1\\r\\n\\xff\\r\\n\\r\\n\"\n    message = None\n    try:\n        parser.feed_data(text)\n    except http_exceptions.InvalidHeader as e:\n        message = e.message.encode(\"utf-8\")\n    assert message is not None\n\n\ndef test_content_length_transfer_encoding(parser: HttpRequestParser) -> None:\n    text = (\n        b\"GET / HTTP/1.1\\r\\nHost: a\\r\\nContent-Length: 5\\r\\nTransfer-Encoding: a\\r\\n\\r\\n\"\n        + b\"apple\\r\\n\"\n    )\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(text)\n\n\n@pytest.mark.parametrize(\n    \"hdr\",\n    (\n        \"Content-Length\",\n        \"Content-Location\",\n        \"Content-Range\",\n        \"Content-Type\",\n        \"ETag\",\n        \"Host\",\n        \"Max-Forwards\",\n        \"Server\",\n        \"Transfer-Encoding\",\n        \"User-Agent\",\n    ),\n)\ndef test_duplicate_singleton_header_rejected(\n    parser: HttpRequestParser, hdr: str\n) -> None:\n    val1, val2 = (\"1\", \"2\") if hdr == \"Content-Length\" else (\"value1\", \"value2\")\n    text = (\n        f\"GET /test HTTP/1.1\\r\\n\"\n        f\"Host: example.com\\r\\n\"\n        f\"{hdr}: {val1}\\r\\n\"\n        f\"{hdr}: {val2}\\r\\n\"\n        f\"\\r\\n\"\n    ).encode()\n    with pytest.raises(http_exceptions.BadHttpMessage, match=\"Duplicate\"):\n        parser.feed_data(text)\n\n\ndef test_duplicate_host_header_rejected(parser: HttpRequestParser) -> None:\n    text = (\n        b\"GET /admin HTTP/1.1\\r\\n\"\n        b\"Host: admin.example\\r\\n\"\n        b\"Host: public.example\\r\\n\"\n        b\"\\r\\n\"\n    )\n    with pytest.raises(http_exceptions.BadHttpMessage, match=\"Duplicate.*Host\"):\n        parser.feed_data(text)\n\n\ndef test_bad_chunked(parser: HttpRequestParser) -> None:\n    \"\"\"Test that invalid chunked encoding doesn't allow content-length to be used.\"\"\"\n    text = (\n        b\"GET / HTTP/1.1\\r\\nHost: a\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n0_2e\\r\\n\\r\\n\"\n        + b\"GET / HTTP/1.1\\r\\nHost: a\\r\\nContent-Length: 5\\r\\n\\r\\n0\\r\\n\\r\\n\"\n    )\n    with pytest.raises(http_exceptions.BadHttpMessage, match=\"0_2e\"):\n        parser.feed_data(text)\n\n\ndef test_whitespace_before_header(parser: HttpRequestParser) -> None:\n    text = b\"GET / HTTP/1.1\\r\\n\\tContent-Length: 1\\r\\n\\r\\nX\"\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(text)\n\n\n@pytest.fixture\ndef xfail_c_parser_status(request: pytest.FixtureRequest) -> None:\n    if isinstance(request.getfixturevalue(\"parser\"), HttpRequestParserPy):\n        return\n    request.node.add_marker(\n        pytest.mark.xfail(\n            reason=\"Regression test for Py parser. May match C behaviour later.\",\n            raises=http_exceptions.BadStatusLine,\n        )\n    )\n\n\n@pytest.mark.usefixtures(\"xfail_c_parser_status\")\ndef test_parse_unusual_request_line(parser: HttpRequestParser) -> None:\n    text = b\"#smol //a HTTP/1.3\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    assert len(messages) == 1\n    msg, _ = messages[0]\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert msg.method == \"#smol\"\n    assert msg.path == \"//a\"\n    assert msg.version == (1, 3)\n\n\ndef test_parse(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    assert len(messages) == 1\n    msg, _ = messages[0]\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert msg.method == \"GET\"\n    assert msg.path == \"/test\"\n    assert msg.version == (1, 1)\n\n\nasync def test_parse_body(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\nContent-Length: 4\\r\\n\\r\\nbody\"\n    messages, upgrade, tail = parser.feed_data(text)\n    assert len(messages) == 1\n    _, payload = messages[0]\n    body = await payload.read(4)\n    assert body == b\"body\"\n\n\nasync def test_parse_body_with_CRLF(parser: HttpRequestParser) -> None:\n    text = b\"\\r\\nGET /test HTTP/1.1\\r\\nContent-Length: 4\\r\\n\\r\\nbody\"\n    messages, upgrade, tail = parser.feed_data(text)\n    assert len(messages) == 1\n    _, payload = messages[0]\n    body = await payload.read(4)\n    assert body == b\"body\"\n\n\ndef test_parse_delayed(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    assert len(messages) == 0\n    assert not upgrade\n\n    messages, upgrade, tail = parser.feed_data(b\"\\r\\n\")\n    assert len(messages) == 1\n    msg = messages[0][0]\n    assert msg.method == \"GET\"\n\n\ndef test_headers_multi_feed(parser: HttpRequestParser) -> None:\n    text1 = b\"GET /test HTTP/1.1\\r\\n\"\n    text2 = b\"test: line\"\n    text3 = b\" continue\\r\\n\\r\\n\"\n\n    messages, upgrade, tail = parser.feed_data(text1)\n    assert len(messages) == 0\n\n    messages, upgrade, tail = parser.feed_data(text2)\n    assert len(messages) == 0\n\n    messages, upgrade, tail = parser.feed_data(text3)\n    assert len(messages) == 1\n\n    msg = messages[0][0]\n    assert list(msg.headers.items()) == [(\"test\", \"line continue\")]\n    assert msg.raw_headers == ((b\"test\", b\"line continue\"),)\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n\n\ndef test_headers_split_field(parser: HttpRequestParser) -> None:\n    text1 = b\"GET /test HTTP/1.1\\r\\n\"\n    text2 = b\"t\"\n    text3 = b\"es\"\n    text4 = b\"t: value\\r\\n\\r\\n\"\n\n    messages, upgrade, tail = parser.feed_data(text1)\n    messages, upgrade, tail = parser.feed_data(text2)\n    messages, upgrade, tail = parser.feed_data(text3)\n    assert len(messages) == 0\n    messages, upgrade, tail = parser.feed_data(text4)\n    assert len(messages) == 1\n\n    msg = messages[0][0]\n    assert list(msg.headers.items()) == [(\"test\", \"value\")]\n    assert msg.raw_headers == ((b\"test\", b\"value\"),)\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n\n\ndef test_parse_headers_multi(parser: HttpRequestParser) -> None:\n    text = (\n        b\"GET /test HTTP/1.1\\r\\n\"\n        b\"Set-Cookie: c1=cookie1\\r\\n\"\n        b\"Set-Cookie: c2=cookie2\\r\\n\\r\\n\"\n    )\n\n    messages, upgrade, tail = parser.feed_data(text)\n    assert len(messages) == 1\n    msg = messages[0][0]\n\n    assert list(msg.headers.items()) == [\n        (\"Set-Cookie\", \"c1=cookie1\"),\n        (\"Set-Cookie\", \"c2=cookie2\"),\n    ]\n    assert msg.raw_headers == (\n        (b\"Set-Cookie\", b\"c1=cookie1\"),\n        (b\"Set-Cookie\", b\"c2=cookie2\"),\n    )\n    assert not msg.should_close\n    assert msg.compression is None\n\n\ndef test_conn_default_1_0(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.0\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert msg.should_close\n\n\ndef test_conn_default_1_1(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert not msg.should_close\n\n\ndef test_conn_close(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\nconnection: close\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert msg.should_close\n\n\ndef test_conn_close_1_0(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.0\\r\\nconnection: close\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert msg.should_close\n\n\ndef test_conn_keep_alive_1_0(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.0\\r\\nconnection: keep-alive\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert not msg.should_close\n\n\ndef test_conn_keep_alive_1_1(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\nconnection: keep-alive\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert not msg.should_close\n\n\ndef test_conn_close_comma_list(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\nconnection: close, keep-alive\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert msg.should_close\n\n\ndef test_conn_close_multiple_headers(parser: HttpRequestParser) -> None:\n    text = (\n        b\"GET /test HTTP/1.1\\r\\n\"\n        b\"connection: keep-alive\\r\\n\"\n        b\"connection: close\\r\\n\\r\\n\"\n    )\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert msg.should_close\n\n\ndef test_conn_other_1_0(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.0\\r\\nconnection: test\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert msg.should_close\n\n\ndef test_conn_other_1_1(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\nconnection: test\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert not msg.should_close\n\n\ndef test_request_chunked(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ntransfer-encoding: chunked\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg, payload = messages[0]\n    assert msg.chunked\n    assert not upgrade\n    assert isinstance(payload, streams.StreamReader)\n\n\ndef test_te_header_non_ascii(parser: HttpRequestParser) -> None:\n    # K = Kelvin sign, not valid ascii.\n    text = \"GET /test HTTP/1.1\\r\\nTransfer-Encoding: chunKed\\r\\n\\r\\n\"\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(text.encode())\n\n\ndef test_upgrade_header_non_ascii(parser: HttpRequestParser) -> None:\n    # K = Kelvin sign, not valid ascii.\n    text = \"GET /test HTTP/1.1\\r\\nUpgrade: websocKet\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text.encode())\n    assert not upgrade\n\n\ndef test_request_te_chunked_with_content_length(parser: HttpRequestParser) -> None:\n    text = (\n        b\"GET /test HTTP/1.1\\r\\n\"\n        b\"content-length: 1234\\r\\n\"\n        b\"transfer-encoding: chunked\\r\\n\\r\\n\"\n    )\n    with pytest.raises(\n        http_exceptions.BadHttpMessage,\n        match=\"Transfer-Encoding can't be present with Content-Length\",\n    ):\n        parser.feed_data(text)\n\n\ndef test_request_te_chunked123(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ntransfer-encoding: chunked123\\r\\n\\r\\n\"\n    with pytest.raises(\n        http_exceptions.BadHttpMessage,\n        match=\"Request has invalid `Transfer-Encoding`\",\n    ):\n        parser.feed_data(text)\n\n\nasync def test_request_te_last_chunked(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\nTransfer-Encoding: not, chunked\\r\\n\\r\\n1\\r\\nT\\r\\n3\\r\\nest\\r\\n0\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    # https://www.rfc-editor.org/rfc/rfc9112#section-6.3-2.4.3\n    assert await messages[0][1].read() == b\"Test\"\n\n\ndef test_request_te_first_chunked(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\nTransfer-Encoding: chunked, not\\r\\n\\r\\n1\\r\\nT\\r\\n3\\r\\nest\\r\\n0\\r\\n\\r\\n\"\n    # https://www.rfc-editor.org/rfc/rfc9112#section-6.3-2.4.3\n    with pytest.raises(\n        http_exceptions.BadHttpMessage,\n        match=\"nvalid `Transfer-Encoding`\",\n    ):\n        parser.feed_data(text)\n\n\ndef test_request_te_duplicate_chunked(parser: HttpRequestParser) -> None:\n    \"\"\"Reject duplicate chunked Transfer-Encoding per RFC 9112 section 7.1.\"\"\"\n    text = b\"POST / HTTP/1.1\\r\\nHost: a\\r\\nTransfer-Encoding: chunked, chunked\\r\\n\\r\\n0\\r\\n\\r\\n\"\n    # https://www.rfc-editor.org/rfc/rfc9112#section-7.1-3\n    with pytest.raises(\n        http_exceptions.BadHttpMessage,\n        match=\"duplicate `chunked` Transfer-Encoding|nvalid `Transfer-Encoding`\",\n    ):\n        parser.feed_data(text)\n\n\ndef test_conn_upgrade(parser: HttpRequestParser) -> None:\n    text = (\n        b\"GET /test HTTP/1.1\\r\\n\"\n        b\"connection: upgrade\\r\\n\"\n        b\"upgrade: websocket\\r\\n\\r\\n\"\n    )\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert not msg.should_close\n    assert msg.upgrade\n    assert upgrade\n\n\ndef test_conn_upgrade_comma_list(parser: HttpRequestParser) -> None:\n    text = (\n        b\"GET /test HTTP/1.1\\r\\n\"\n        b\"connection: keep-alive, upgrade\\r\\n\"\n        b\"upgrade: websocket\\r\\n\\r\\n\"\n    )\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert not msg.should_close\n    assert msg.upgrade\n    assert upgrade\n\n\ndef test_conn_upgrade_multiple_headers(parser: HttpRequestParser) -> None:\n    text = (\n        b\"GET /test HTTP/1.1\\r\\n\"\n        b\"connection: keep-alive\\r\\n\"\n        b\"connection: upgrade\\r\\n\"\n        b\"upgrade: websocket\\r\\n\\r\\n\"\n    )\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert not msg.should_close\n    assert msg.upgrade\n    assert upgrade\n\n\ndef test_bad_upgrade(parser: HttpRequestParser) -> None:\n    \"\"\"Test not upgraded if missing Upgrade header.\"\"\"\n    text = b\"GET /test HTTP/1.1\\r\\nconnection: upgrade\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert not msg.upgrade\n    assert not upgrade\n\n\ndef test_compression_empty(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ncontent-encoding: \\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert msg.compression is None\n\n\ndef test_compression_deflate(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ncontent-encoding: deflate\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert msg.compression == \"deflate\"\n\n\ndef test_compression_gzip(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ncontent-encoding: gzip\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert msg.compression == \"gzip\"\n\n\n@pytest.mark.skipif(brotli is None, reason=\"brotli is not installed\")\ndef test_compression_brotli(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ncontent-encoding: br\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert msg.compression == \"br\"\n\n\n@pytest.mark.skipif(zstandard is None, reason=\"zstandard is not installed\")\ndef test_compression_zstd(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ncontent-encoding: zstd\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert msg.compression == \"zstd\"\n\n\n@pytest.mark.parametrize(\n    \"enc\",\n    (\n        \"zﬆd\".encode(),  # \"ﬆ\".upper() == \"ST\"\n        \"deﬂate\".encode(),  # \"ﬂ\".upper() == \"FL\"\n    ),\n)\ndef test_compression_non_ascii(parser: HttpRequestParser, enc: bytes) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ncontent-encoding: \" + enc + b\"\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    # Non-ascii input should not evaluate to a valid encoding scheme.\n    assert msg.compression is None\n\n\ndef test_compression_unknown(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ncontent-encoding: compress\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert msg.compression is None\n\n\ndef test_url_connect(parser: HttpRequestParser) -> None:\n    text = b\"CONNECT www.google.com HTTP/1.1\\r\\ncontent-length: 0\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg, payload = messages[0]\n    assert upgrade\n    assert msg.url == URL.build(authority=\"www.google.com\")\n\n\ndef test_headers_connect(parser: HttpRequestParser) -> None:\n    text = b\"CONNECT www.google.com HTTP/1.1\\r\\ncontent-length: 0\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg, payload = messages[0]\n    assert upgrade\n    assert isinstance(payload, streams.StreamReader)\n\n\ndef test_url_absolute(parser: HttpRequestParser) -> None:\n    text = (\n        b\"GET https://www.google.com/path/to.html HTTP/1.1\\r\\n\"\n        b\"content-length: 0\\r\\n\\r\\n\"\n    )\n    messages, upgrade, tail = parser.feed_data(text)\n    msg, payload = messages[0]\n    assert not upgrade\n    assert msg.method == \"GET\"\n    assert msg.url == URL(\"https://www.google.com/path/to.html\")\n\n\ndef test_headers_old_websocket_key1(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\nSEC-WEBSOCKET-KEY1: line\\r\\n\\r\\n\"\n\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(text)\n\n\ndef test_headers_content_length_err_1(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ncontent-length: line\\r\\n\\r\\n\"\n\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(text)\n\n\ndef test_headers_content_length_err_2(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ncontent-length: -1\\r\\n\\r\\n\"\n\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(text)\n\n\n_pad: dict[bytes, str] = {\n    b\"\": \"empty\",\n    # not a typo. Python likes triple zero\n    b\"\\000\": \"NUL\",\n    b\" \": \"SP\",\n    b\"  \": \"SPSP\",\n    # not a typo: both 0xa0 and 0x0a in case of 8-bit fun\n    b\"\\n\": \"LF\",\n    b\"\\xa0\": \"NBSP\",\n    b\"\\t \": \"TABSP\",\n}\n\n\n@pytest.mark.parametrize(\"hdr\", [b\"\", b\"foo\"], ids=[\"name-empty\", \"with-name\"])\n@pytest.mark.parametrize(\"pad2\", _pad.keys(), ids=[\"post-\" + n for n in _pad.values()])\n@pytest.mark.parametrize(\"pad1\", _pad.keys(), ids=[\"pre-\" + n for n in _pad.values()])\ndef test_invalid_header_spacing(\n    parser: HttpRequestParser, pad1: bytes, pad2: bytes, hdr: bytes\n) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\n%s%s%s: value\\r\\n\\r\\n\" % (pad1, hdr, pad2)\n    if pad1 == pad2 == b\"\" and hdr != b\"\":\n        # one entry in param matrix is correct: non-empty name, not padded\n        parser.feed_data(text)\n        return\n\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(text)\n\n\ndef test_empty_header_name(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\n:test\\r\\n\\r\\n\"\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(text)\n\n\ndef test_invalid_header(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ntest line\\r\\n\\r\\n\"\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(text)\n\n\ndef test_invalid_name(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ntest[]: line\\r\\n\\r\\n\"\n\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(text)\n\n\n@pytest.mark.parametrize(\"size\", [40960, 8191])\ndef test_max_header_field_size(parser: HttpRequestParser, size: int) -> None:\n    name = b\"t\" * size\n    text = b\"GET /test HTTP/1.1\\r\\n\" + name + b\":data\\r\\n\\r\\n\"\n\n    match = \"400, message:\\n  Got more than 8190 bytes when reading\"\n    with pytest.raises(http_exceptions.LineTooLong, match=match):\n        for i in range(0, len(text), 5000):  # pragma: no branch\n            parser.feed_data(text[i : i + 5000])\n\n\ndef test_max_header_size_under_limit(parser: HttpRequestParser) -> None:\n    name = b\"t\" * 8185\n    text = b\"GET /test HTTP/1.1\\r\\n\" + name + b\":data\\r\\n\\r\\n\"\n\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert msg.method == \"GET\"\n    assert msg.path == \"/test\"\n    assert msg.version == (1, 1)\n    assert msg.headers == CIMultiDict({name.decode(): \"data\"})\n    assert msg.raw_headers == ((name, b\"data\"),)\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert not msg.chunked\n    assert msg.url == URL(\"/test\")\n\n\n@pytest.mark.parametrize(\"size\", [40960, 8191])\ndef test_max_header_value_size(parser: HttpRequestParser, size: int) -> None:\n    name = b\"t\" * size\n    text = b\"GET /test HTTP/1.1\\r\\ndata:\" + name + b\"\\r\\n\\r\\n\"\n\n    match = \"400, message:\\n  Got more than 8190 bytes when reading\"\n    with pytest.raises(http_exceptions.LineTooLong, match=match):\n        for i in range(0, len(text), 4000):  # pragma: no branch\n            parser.feed_data(text[i : i + 4000])\n\n\ndef test_max_header_combined_size(parser: HttpRequestParser) -> None:\n    k = b\"t\" * 4100\n    text = b\"GET /test HTTP/1.1\\r\\n\" + k + b\":\" + k + b\"\\r\\n\\r\\n\"\n\n    match = \"400, message:\\n  Got more than 8190 bytes when reading\"\n    with pytest.raises(http_exceptions.LineTooLong, match=match):\n        parser.feed_data(text)\n\n\n@pytest.mark.parametrize(\"size\", [40960, 8191])\nasync def test_max_trailer_size(parser: HttpRequestParser, size: int) -> None:\n    value = b\"t\" * size\n    text = (\n        b\"GET /test HTTP/1.1\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n\"\n        + hex(4000)[2:].encode()\n        + b\"\\r\\n\"\n        + b\"b\" * 4000\n        + b\"\\r\\n0\\r\\ntest: \"\n        + value\n        + b\"\\r\\n\\r\\n\"\n    )\n\n    match = \"400, message:\\n  Got more than 8190 bytes when reading\"\n    with pytest.raises(http_exceptions.LineTooLong, match=match):\n        payload = None\n        for i in range(0, len(text), 3000):  # pragma: no branch\n            messages, upgrade, tail = parser.feed_data(text[i : i + 3000])\n            if messages:\n                payload = messages[0][-1]\n        # Trailers are not seen until payload is read.\n        assert payload is not None\n        await payload.read()\n\n\n@pytest.mark.parametrize(\"headers,trailers\", ((129, 0), (0, 129), (64, 65)))\nasync def test_max_headers(\n    parser: HttpRequestParser, headers: int, trailers: int\n) -> None:\n    text = (\n        b\"GET /test HTTP/1.1\\r\\nTransfer-Encoding: chunked\"\n        + b\"\".join(b\"\\r\\nHeader-%d: Value\" % i for i in range(headers))\n        + b\"\\r\\n\\r\\n4\\r\\ntest\\r\\n0\"\n        + b\"\".join(b\"\\r\\nTrailer-%d: Value\" % i for i in range(trailers))\n        + b\"\\r\\n\\r\\n\"\n    )\n\n    match = \"Too many (headers|trailers) received\"\n    with pytest.raises(http_exceptions.BadHttpMessage, match=match):\n        messages, upgrade, tail = parser.feed_data(text)\n        # Trailers are not seen until payload is read.\n        await messages[0][-1].read()\n\n\ndef test_max_header_value_size_under_limit(parser: HttpRequestParser) -> None:\n    value = b\"A\" * 8185\n    text = b\"GET /test HTTP/1.1\\r\\ndata:\" + value + b\"\\r\\n\\r\\n\"\n\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert msg.method == \"GET\"\n    assert msg.path == \"/test\"\n    assert msg.version == (1, 1)\n    assert msg.headers == CIMultiDict({\"data\": value.decode()})\n    assert msg.raw_headers == ((b\"data\", value),)\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert not msg.chunked\n    assert msg.url == URL(\"/test\")\n\n\n@pytest.mark.parametrize(\"size\", [40965, 8191])\ndef test_max_header_value_size_continuation(\n    response: HttpResponseParser, size: int\n) -> None:\n    name = b\"T\" * (size - 5)\n    text = b\"HTTP/1.1 200 Ok\\r\\ndata: test\\r\\n \" + name + b\"\\r\\n\\r\\n\"\n\n    match = \"400, message:\\n  Got more than 8190 bytes when reading\"\n    with pytest.raises(http_exceptions.LineTooLong, match=match):\n        for i in range(0, len(text), 9000):  # pragma: no branch\n            response.feed_data(text[i : i + 9000])\n\n\ndef test_max_header_value_size_continuation_under_limit(\n    response: HttpResponseParser,\n) -> None:\n    value = b\"A\" * 8179\n    text = b\"HTTP/1.1 200 Ok\\r\\ndata: test\\r\\n \" + value + b\"\\r\\n\\r\\n\"\n\n    messages, upgrade, tail = response.feed_data(text)\n    msg = messages[0][0]\n    assert msg.code == 200\n    assert msg.reason == \"Ok\"\n    assert msg.version == (1, 1)\n    assert msg.headers == CIMultiDict({\"data\": \"test \" + value.decode()})\n    assert msg.raw_headers == ((b\"data\", b\"test \" + value),)\n    assert msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert not msg.chunked\n\n\ndef test_http_request_parser(parser: HttpRequestParser) -> None:\n    text = b\"GET /path HTTP/1.1\\r\\n\\r\\n\"\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n\n    assert msg.method == \"GET\"\n    assert msg.path == \"/path\"\n    assert msg.version == (1, 1)\n    assert msg.headers == CIMultiDict()\n    assert msg.raw_headers == ()\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert not msg.chunked\n    assert msg.url == URL(\"/path\")\n\n\ndef test_http_request_bad_status_line(parser: HttpRequestParser) -> None:\n    text = b\"getpath \\r\\n\\r\\n\"\n    with pytest.raises(http_exceptions.BadStatusLine) as exc_info:\n        parser.feed_data(text)\n    # Check for accidentally escaped message.\n    assert r\"\\n\" not in exc_info.value.message\n\n\n_num: dict[bytes, str] = {\n    # dangerous: accepted by Python int()\n    # unicodedata.category(\"\\U0001D7D9\") == 'Nd'\n    \"\\N{MATHEMATICAL DOUBLE-STRUCK DIGIT ONE}\".encode(): \"utf8digit\",\n    # only added for interop tests, refused by Python int()\n    # unicodedata.category(\"\\U000000B9\") == 'No'\n    \"\\N{SUPERSCRIPT ONE}\".encode(): \"utf8number\",\n    \"\\N{SUPERSCRIPT ONE}\".encode(\"latin-1\"): \"latin1number\",\n}\n\n\n@pytest.mark.parametrize(\"nonascii_digit\", _num.keys(), ids=_num.values())\ndef test_http_request_bad_status_line_number(\n    parser: HttpRequestParser, nonascii_digit: bytes\n) -> None:\n    text = b\"GET /digit HTTP/1.\" + nonascii_digit + b\"\\r\\n\\r\\n\"\n    with pytest.raises(http_exceptions.BadStatusLine):\n        parser.feed_data(text)\n\n\ndef test_http_request_bad_status_line_separator(parser: HttpRequestParser) -> None:\n    # single code point, old, multibyte NFKC, multibyte NFKD\n    utf8sep = \"\\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}\".encode()\n    text = b\"GET /ligature HTTP/1\" + utf8sep + b\"1\\r\\n\\r\\n\"\n    with pytest.raises(http_exceptions.BadStatusLine):\n        parser.feed_data(text)\n\n\ndef test_http_request_bad_status_line_whitespace(parser: HttpRequestParser) -> None:\n    text = b\"GET\\n/path\\fHTTP/1.1\\r\\n\\r\\n\"\n    with pytest.raises(http_exceptions.BadStatusLine):\n        parser.feed_data(text)\n\n\ndef test_http_request_message_after_close(parser: HttpRequestParser) -> None:\n    text = b\"GET / HTTP/1.1\\r\\nConnection: close\\r\\n\\r\\nInvalid\\r\\n\\r\\n\"\n    with pytest.raises(\n        http_exceptions.BadHttpMessage, match=\"Data after `Connection: close`\"\n    ):\n        parser.feed_data(text)\n\n\ndef test_http_request_message_after_close_comma_list(parser: HttpRequestParser) -> None:\n    text = b\"GET / HTTP/1.1\\r\\nConnection: close, keep-alive\\r\\n\\r\\nInvalid\\r\\n\\r\\n\"\n    with pytest.raises(\n        http_exceptions.BadHttpMessage, match=\"Data after `Connection: close`\"\n    ):\n        parser.feed_data(text)\n\n\ndef test_http_request_upgrade(parser: HttpRequestParser) -> None:\n    text = (\n        b\"GET /test HTTP/1.1\\r\\n\"\n        b\"connection: upgrade\\r\\n\"\n        b\"upgrade: websocket\\r\\n\\r\\n\"\n        b\"some raw data\"\n    )\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n    assert not msg.should_close\n    assert msg.upgrade\n    assert upgrade\n    assert tail == b\"some raw data\"\n\n\nasync def test_http_request_upgrade_unknown(parser: HttpRequestParser) -> None:\n    text = (\n        b\"POST / HTTP/1.1\\r\\n\"\n        b\"Connection: Upgrade\\r\\n\"\n        b\"Content-Length: 2\\r\\n\"\n        b\"Upgrade: unknown\\r\\n\"\n        b\"Content-Type: application/json\\r\\n\\r\\n\"\n        b\"{}\"\n    )\n    messages, upgrade, tail = parser.feed_data(text)\n\n    msg = messages[0][0]\n    assert not msg.should_close\n    assert msg.upgrade\n    assert not upgrade\n    assert not msg.chunked\n    assert tail == b\"\"\n    assert await messages[0][-1].read() == b\"{}\"\n\n\n@pytest.fixture\ndef xfail_c_parser_url(request: pytest.FixtureRequest) -> None:\n    if isinstance(request.getfixturevalue(\"parser\"), HttpRequestParserPy):\n        return\n    request.node.add_marker(\n        pytest.mark.xfail(\n            reason=\"Regression test for Py parser. May match C behaviour later.\",\n            raises=http_exceptions.InvalidURLError,\n        )\n    )\n\n\n@pytest.mark.usefixtures(\"xfail_c_parser_url\")\ndef test_http_request_parser_utf8_request_line(parser: HttpRequestParser) -> None:\n    messages, upgrade, tail = parser.feed_data(\n        # note the truncated unicode sequence\n        b\"GET /P\\xc3\\xbcnktchen\\xa0\\xef\\xb7 HTTP/1.1\\r\\n\" +\n        # for easier grep: ASCII 0xA0 more commonly known as non-breaking space\n        # note the leading and trailing spaces\n        \"sTeP:  \\N{LATIN SMALL LETTER SHARP S}nek\\t\\N{NO-BREAK SPACE}  \"\n        \"\\r\\n\\r\\n\".encode()\n    )\n    msg = messages[0][0]\n\n    assert msg.method == \"GET\"\n    assert msg.path == \"/Pünktchen\\udca0\\udcef\\udcb7\"\n    assert msg.version == (1, 1)\n    assert msg.headers == CIMultiDict([(\"STEP\", \"ßnek\\t\\xa0\")])\n    assert msg.raw_headers == ((b\"sTeP\", \"ßnek\\t\\xa0\".encode()),)\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert not msg.chunked\n    # python HTTP parser depends on Cython and CPython URL to match\n    # .. but yarl.URL(\"/abs\") is not equal to URL.build(path=\"/abs\"), see #6409\n    assert msg.url == URL.build(path=\"/Pünktchen\\udca0\\udcef\\udcb7\", encoded=True)\n\n\ndef test_http_request_parser_utf8(parser: HttpRequestParser) -> None:\n    text = \"GET /path HTTP/1.1\\r\\nx-test:тест\\r\\n\\r\\n\".encode()\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n\n    assert msg.method == \"GET\"\n    assert msg.path == \"/path\"\n    assert msg.version == (1, 1)\n    assert msg.headers == CIMultiDict([(\"X-TEST\", \"тест\")])\n    assert msg.raw_headers == ((b\"x-test\", \"тест\".encode()),)\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert not msg.chunked\n    assert msg.url == URL(\"/path\")\n\n\ndef test_http_request_parser_non_utf8(parser: HttpRequestParser) -> None:\n    text = \"GET /path HTTP/1.1\\r\\nx-test:тест\\r\\n\\r\\n\".encode(\"cp1251\")\n    msg = parser.feed_data(text)[0][0][0]\n\n    assert msg.method == \"GET\"\n    assert msg.path == \"/path\"\n    assert msg.version == (1, 1)\n    assert msg.headers == CIMultiDict(\n        [(\"X-TEST\", \"тест\".encode(\"cp1251\").decode(\"utf8\", \"surrogateescape\"))]\n    )\n    assert msg.raw_headers == ((b\"x-test\", \"тест\".encode(\"cp1251\")),)\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert not msg.chunked\n    assert msg.url == URL(\"/path\")\n\n\ndef test_http_request_parser_two_slashes(parser: HttpRequestParser) -> None:\n    text = b\"GET //path HTTP/1.1\\r\\n\\r\\n\"\n    msg = parser.feed_data(text)[0][0][0]\n\n    assert msg.method == \"GET\"\n    assert msg.path == \"//path\"\n    assert msg.url.path == \"//path\"\n    assert msg.version == (1, 1)\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert not msg.chunked\n\n\n@pytest.mark.parametrize(\n    \"rfc9110_5_6_2_token_delim\",\n    [bytes([i]) for i in rb'\"(),/:;<=>?@[\\]{}'],\n)\ndef test_http_request_parser_bad_method(\n    parser: HttpRequestParser, rfc9110_5_6_2_token_delim: bytes\n) -> None:\n    with pytest.raises(http_exceptions.BadHttpMethod):\n        parser.feed_data(rfc9110_5_6_2_token_delim + b'ET\" /get HTTP/1.1\\r\\n\\r\\n')\n\n\ndef test_http_request_parser_bad_version(parser: HttpRequestParser) -> None:\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(b\"GET //get HT/11\\r\\n\\r\\n\")\n\n\ndef test_http_request_parser_bad_version_number(parser: HttpRequestParser) -> None:\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        parser.feed_data(b\"GET /test HTTP/1.32\\r\\n\\r\\n\")\n\n\ndef test_http_request_parser_bad_ascii_uri(parser: HttpRequestParser) -> None:\n    with pytest.raises(http_exceptions.InvalidURLError):\n        parser.feed_data(b\"GET ! HTTP/1.1\\r\\n\\r\\n\")\n\n\ndef test_http_request_parser_bad_nonascii_uri(parser: HttpRequestParser) -> None:\n    with pytest.raises(http_exceptions.InvalidURLError):\n        parser.feed_data(b\"GET \\xff HTTP/1.1\\r\\n\\r\\n\")\n\n\n@pytest.mark.parametrize(\"size\", [40965, 8191])\ndef test_http_request_max_status_line(parser: HttpRequestParser, size: int) -> None:\n    path = b\"t\" * (size - 5)\n    match = \"400, message:\\n  Got more than 8190 bytes when reading\"\n    with pytest.raises(http_exceptions.LineTooLong, match=match):\n        parser.feed_data(b\"GET /path\" + path + b\" HTTP/1.1\\r\\n\\r\\n\")\n\n\ndef test_http_request_max_status_line_under_limit(parser: HttpRequestParser) -> None:\n    path = b\"t\" * 8172\n    messages, upgraded, tail = parser.feed_data(\n        b\"GET /path\" + path + b\" HTTP/1.1\\r\\n\\r\\n\"\n    )\n    msg = messages[0][0]\n\n    assert msg.method == \"GET\"\n    assert msg.path == \"/path\" + path.decode()\n    assert msg.version == (1, 1)\n    assert msg.headers == CIMultiDict()\n    assert msg.raw_headers == ()\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert not msg.chunked\n    assert msg.url == URL(\"/path\" + path.decode())\n\n\ndef test_http_response_parser_utf8(response: HttpResponseParser) -> None:\n    text = \"HTTP/1.1 200 Ok\\r\\nx-test:тест\\r\\n\\r\\n\".encode()\n\n    messages, upgraded, tail = response.feed_data(text)\n    assert len(messages) == 1\n    msg = messages[0][0]\n\n    assert msg.version == (1, 1)\n    assert msg.code == 200\n    assert msg.reason == \"Ok\"\n    assert msg.headers == CIMultiDict([(\"X-TEST\", \"тест\")])\n    assert msg.raw_headers == ((b\"x-test\", \"тест\".encode()),)\n    assert not upgraded\n    assert not tail\n\n\ndef test_http_response_parser_utf8_without_reason(response: HttpResponseParser) -> None:\n    text = \"HTTP/1.1 200 \\r\\nx-test:тест\\r\\n\\r\\n\".encode()\n\n    messages, upgraded, tail = response.feed_data(text)\n    assert len(messages) == 1\n    msg = messages[0][0]\n\n    assert msg.version == (1, 1)\n    assert msg.code == 200\n    assert msg.reason == \"\"\n    assert msg.headers == CIMultiDict([(\"X-TEST\", \"тест\")])\n    assert msg.raw_headers == ((b\"x-test\", \"тест\".encode()),)\n    assert not upgraded\n    assert not tail\n\n\ndef test_http_response_parser_obs_line_folding(response: HttpResponseParser) -> None:\n    text = b\"HTTP/1.1 200 Ok\\r\\ntest: line\\r\\n continue\\r\\n\\r\\n\"\n\n    messages, upgraded, tail = response.feed_data(text)\n    assert len(messages) == 1\n    msg = messages[0][0]\n\n    assert msg.version == (1, 1)\n    assert msg.code == 200\n    assert msg.reason == \"Ok\"\n    assert msg.headers == CIMultiDict([(\"TEST\", \"line continue\")])\n    assert msg.raw_headers == ((b\"test\", b\"line continue\"),)\n    assert not upgraded\n    assert not tail\n\n\n@pytest.mark.dev_mode\ndef test_http_response_parser_strict_obs_line_folding(\n    response: HttpResponseParser,\n) -> None:\n    text = b\"HTTP/1.1 200 Ok\\r\\ntest: line\\r\\n continue\\r\\n\\r\\n\"\n\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        response.feed_data(text)\n\n\n@pytest.mark.parametrize(\"size\", [40962, 8191])\ndef test_http_response_parser_bad_status_line_too_long(\n    response: HttpResponseParser, size: int\n) -> None:\n    reason = b\"t\" * (size - 2)\n    match = \"400, message:\\n  Got more than 8190 bytes when reading\"\n    with pytest.raises(http_exceptions.LineTooLong, match=match):\n        response.feed_data(b\"HTTP/1.1 200 Ok\" + reason + b\"\\r\\n\\r\\n\")\n\n\ndef test_http_response_parser_status_line_under_limit(\n    response: HttpResponseParser,\n) -> None:\n    reason = b\"O\" * 8177\n    messages, upgraded, tail = response.feed_data(\n        b\"HTTP/1.1 200 \" + reason + b\"\\r\\n\\r\\n\"\n    )\n    msg = messages[0][0]\n    assert msg.version == (1, 1)\n    assert msg.code == 200\n    assert msg.reason == reason.decode()\n\n\ndef test_http_response_parser_bad_version(response: HttpResponseParser) -> None:\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        response.feed_data(b\"HT/11 200 Ok\\r\\n\\r\\n\")\n\n\ndef test_http_response_parser_bad_version_number(response: HttpResponseParser) -> None:\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        response.feed_data(b\"HTTP/12.3 200 Ok\\r\\n\\r\\n\")\n\n\ndef test_http_response_parser_no_reason(response: HttpResponseParser) -> None:\n    msg = response.feed_data(b\"HTTP/1.1 200\\r\\n\\r\\n\")[0][0][0]\n\n    assert msg.version == (1, 1)\n    assert msg.code == 200\n    assert msg.reason == \"\"\n\n\ndef test_http_response_parser_lenient_headers(response: HttpResponseParser) -> None:\n    messages, upgrade, tail = response.feed_data(\n        b\"HTTP/1.1 200 test\\r\\nFoo: abc\\x01def\\r\\n\\r\\n\"\n    )\n    msg = messages[0][0]\n\n    assert msg.headers[\"Foo\"] == \"abc\\x01def\"\n\n\n@pytest.mark.dev_mode\ndef test_http_response_parser_strict_headers(response: HttpResponseParser) -> None:\n    if isinstance(response, HttpResponseParserPy):\n        pytest.xfail(\"Py parser is lenient. May update py-parser later.\")\n    with pytest.raises(http_exceptions.BadHttpMessage):  # type: ignore[unreachable]\n        response.feed_data(b\"HTTP/1.1 200 test\\r\\nFoo: abc\\x01def\\r\\n\\r\\n\")\n\n\ndef test_http_response_parser_null_byte_in_header_value(\n    response: HttpResponseParser,\n) -> None:\n    with pytest.raises(http_exceptions.InvalidHeader):\n        response.feed_data(b\"HTTP/1.1 200 OK\\r\\nFoo: abc\\x00def\\r\\n\\r\\n\")\n\n\ndef test_http_response_parser_bad_crlf(response: HttpResponseParser) -> None:\n    \"\"\"Still a lot of dodgy servers sending bad requests like this.\"\"\"\n    messages, upgrade, tail = response.feed_data(\n        b\"HTTP/1.0 200 OK\\nFoo: abc\\nBar: def\\n\\nBODY\\n\"\n    )\n    msg = messages[0][0]\n\n    assert msg.headers[\"Foo\"] == \"abc\"\n    assert msg.headers[\"Bar\"] == \"def\"\n\n\nasync def test_http_response_parser_bad_chunked_lax(\n    response: HttpResponseParser,\n) -> None:\n    text = (\n        b\"HTTP/1.1 200 OK\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n5 \\r\\nabcde\\r\\n0\\r\\n\\r\\n\"\n    )\n    messages, upgrade, tail = response.feed_data(text)\n\n    assert await messages[0][1].read(5) == b\"abcde\"\n\n\n@pytest.mark.dev_mode\nasync def test_http_response_parser_bad_chunked_strict_py(\n    loop: asyncio.AbstractEventLoop, protocol: BaseProtocol\n) -> None:\n    response = HttpResponseParserPy(\n        protocol,\n        loop,\n        2**16,\n        max_line_size=8190,\n        max_field_size=8190,\n    )\n    text = (\n        b\"HTTP/1.1 200 OK\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n5 \\r\\nabcde\\r\\n0\\r\\n\\r\\n\"\n    )\n    with pytest.raises(http_exceptions.TransferEncodingError, match=\"5\"):\n        response.feed_data(text)\n\n\n@pytest.mark.dev_mode\n@pytest.mark.skipif(\n    \"HttpRequestParserC\" not in dir(aiohttp.http_parser),\n    reason=\"C based HTTP parser not available\",\n)\nasync def test_http_response_parser_bad_chunked_strict_c(\n    loop: asyncio.AbstractEventLoop, protocol: BaseProtocol\n) -> None:\n    response = HttpResponseParserC(\n        protocol,\n        loop,\n        2**16,\n        max_line_size=8190,\n        max_field_size=8190,\n    )\n    text = (\n        b\"HTTP/1.1 200 OK\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n5 \\r\\nabcde\\r\\n0\\r\\n\\r\\n\"\n    )\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        response.feed_data(text)\n\n\nasync def test_http_response_parser_notchunked(\n    response: HttpResponseParser,\n) -> None:\n    text = b\"HTTP/1.1 200 OK\\r\\nTransfer-Encoding: notchunked\\r\\n\\r\\n1\\r\\nT\\r\\n3\\r\\nest\\r\\n0\\r\\n\\r\\n\"\n    messages, upgrade, tail = response.feed_data(text)\n    response.feed_eof()\n\n    # https://www.rfc-editor.org/rfc/rfc9112#section-6.3-2.4.2\n    assert await messages[0][1].read() == b\"1\\r\\nT\\r\\n3\\r\\nest\\r\\n0\\r\\n\\r\\n\"\n\n\nasync def test_http_response_parser_last_chunked(\n    response: HttpResponseParser,\n) -> None:\n    text = b\"HTTP/1.1 200 OK\\r\\nTransfer-Encoding: not, chunked\\r\\n\\r\\n1\\r\\nT\\r\\n3\\r\\nest\\r\\n0\\r\\n\\r\\n\"\n    messages, upgrade, tail = response.feed_data(text)\n\n    # https://www.rfc-editor.org/rfc/rfc9112#section-6.3-2.4.2\n    assert await messages[0][1].read() == b\"Test\"\n\n\ndef test_http_response_parser_bad(response: HttpResponseParser) -> None:\n    with pytest.raises(http_exceptions.BadHttpMessage):\n        response.feed_data(b\"HTT/1\\r\\n\\r\\n\")\n\n\ndef test_http_response_parser_code_under_100(response: HttpResponseParser) -> None:\n    with pytest.raises(http_exceptions.BadStatusLine):\n        response.feed_data(b\"HTTP/1.1 99 test\\r\\n\\r\\n\")\n\n\ndef test_http_response_parser_code_above_999(response: HttpResponseParser) -> None:\n    with pytest.raises(http_exceptions.BadStatusLine):\n        response.feed_data(b\"HTTP/1.1 9999 test\\r\\n\\r\\n\")\n\n\ndef test_http_response_parser_code_not_int(response: HttpResponseParser) -> None:\n    with pytest.raises(http_exceptions.BadStatusLine):\n        response.feed_data(b\"HTTP/1.1 ttt test\\r\\n\\r\\n\")\n\n\n@pytest.mark.parametrize(\"nonascii_digit\", _num.keys(), ids=_num.values())\ndef test_http_response_parser_code_not_ascii(\n    response: HttpResponseParser, nonascii_digit: bytes\n) -> None:\n    with pytest.raises(http_exceptions.BadStatusLine):\n        response.feed_data(b\"HTTP/1.1 20\" + nonascii_digit + b\" test\\r\\n\\r\\n\")\n\n\ndef test_http_request_chunked_payload(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ntransfer-encoding: chunked\\r\\n\\r\\n\"\n    msg, payload = parser.feed_data(text)[0][0]\n\n    assert msg.chunked\n    assert not payload.is_eof()\n    assert isinstance(payload, streams.StreamReader)\n\n    parser.feed_data(b\"4\\r\\ndata\\r\\n4\\r\\nline\\r\\n0\\r\\n\\r\\n\")\n\n    assert b\"dataline\" == b\"\".join(d for d in payload._buffer)\n    assert payload._http_chunk_splits is not None\n    assert [4, 8] == list(payload._http_chunk_splits)\n    assert payload.is_eof()\n\n\ndef test_http_request_chunked_payload_and_next_message(\n    parser: HttpRequestParser,\n) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ntransfer-encoding: chunked\\r\\n\\r\\n\"\n    msg, payload = parser.feed_data(text)[0][0]\n\n    messages, upgraded, tail = parser.feed_data(\n        b\"4\\r\\ndata\\r\\n4\\r\\nline\\r\\n0\\r\\n\\r\\n\"\n        b\"POST /test2 HTTP/1.1\\r\\n\"\n        b\"transfer-encoding: chunked\\r\\n\\r\\n\"\n    )\n\n    assert b\"dataline\" == b\"\".join(d for d in payload._buffer)\n    assert payload._http_chunk_splits is not None\n    assert [4, 8] == list(payload._http_chunk_splits)\n    assert payload.is_eof()\n\n    assert len(messages) == 1\n    msg2, payload2 = messages[0]\n\n    assert msg2.method == \"POST\"\n    assert msg2.chunked\n    assert not payload2.is_eof()\n\n\ndef test_http_request_chunked_payload_chunks(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ntransfer-encoding: chunked\\r\\n\\r\\n\"\n    msg, payload = parser.feed_data(text)[0][0]\n\n    parser.feed_data(b\"4\\r\\ndata\\r\")\n    parser.feed_data(b\"\\n4\")\n    parser.feed_data(b\"\\r\")\n    parser.feed_data(b\"\\n\")\n    parser.feed_data(b\"li\")\n    parser.feed_data(b\"ne\\r\\n0\\r\\n\")\n    parser.feed_data(b\"test: test\\r\\n\")\n\n    assert b\"dataline\" == b\"\".join(d for d in payload._buffer)\n    assert payload._http_chunk_splits is not None\n    assert [4, 8] == list(payload._http_chunk_splits)\n    assert not payload.is_eof()\n\n    parser.feed_data(b\"\\r\\n\")\n    assert b\"dataline\" == b\"\".join(d for d in payload._buffer)\n    assert [4, 8] == list(payload._http_chunk_splits)\n    assert payload.is_eof()\n\n\ndef test_parse_chunked_payload_chunk_extension(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\ntransfer-encoding: chunked\\r\\n\\r\\n\"\n    msg, payload = parser.feed_data(text)[0][0]\n\n    parser.feed_data(b\"4;test\\r\\ndata\\r\\n4\\r\\nline\\r\\n0\\r\\ntest: test\\r\\n\\r\\n\")\n\n    assert b\"dataline\" == b\"\".join(d for d in payload._buffer)\n    assert payload._http_chunk_splits is not None\n    assert [4, 8] == list(payload._http_chunk_splits)\n    assert payload.is_eof()\n\n\nasync def test_request_chunked_with_trailer(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n4\\r\\ntest\\r\\n0\\r\\ntest: trailer\\r\\nsecond: test trailer\\r\\n\\r\\n\"\n    messages, upgraded, tail = parser.feed_data(text)\n    assert not tail\n    msg, payload = messages[0]\n    assert await payload.read() == b\"test\"\n\n    # TODO: Add assertion of trailers when API added.\n\n\nasync def test_request_chunked_reject_bad_trailer(parser: HttpRequestParser) -> None:\n    text = b\"GET /test HTTP/1.1\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n0\\r\\nbad\\ntrailer\\r\\n\\r\\n\"\n    with pytest.raises(http_exceptions.BadHttpMessage, match=r\"b'bad\\\\ntrailer'\"):\n        parser.feed_data(text)\n\n\ndef test_parse_no_length_or_te_on_post(\n    loop: asyncio.AbstractEventLoop,\n    protocol: BaseProtocol,\n    request_cls: type[HttpRequestParser],\n) -> None:\n    parser = request_cls(protocol, loop, limit=2**16)\n    text = b\"POST /test HTTP/1.1\\r\\n\\r\\n\"\n    msg, payload = parser.feed_data(text)[0][0]\n\n    assert payload.is_eof()\n\n\ndef test_parse_payload_response_without_body(\n    loop: asyncio.AbstractEventLoop,\n    protocol: BaseProtocol,\n    response_cls: type[HttpResponseParser],\n) -> None:\n    parser = response_cls(protocol, loop, 2**16, response_with_body=False)\n    text = b\"HTTP/1.1 200 Ok\\r\\ncontent-length: 10\\r\\n\\r\\n\"\n    msg, payload = parser.feed_data(text)[0][0]\n\n    assert payload.is_eof()\n\n\ndef test_parse_length_payload(response: HttpResponseParser) -> None:\n    text = b\"HTTP/1.1 200 Ok\\r\\ncontent-length: 4\\r\\n\\r\\n\"\n    msg, payload = response.feed_data(text)[0][0]\n    assert not payload.is_eof()\n\n    response.feed_data(b\"da\")\n    response.feed_data(b\"t\")\n    response.feed_data(b\"aHT\")\n\n    assert payload.is_eof()\n    assert b\"data\" == b\"\".join(d for d in payload._buffer)\n\n\ndef test_parse_no_length_payload(parser: HttpRequestParser) -> None:\n    text = b\"PUT / HTTP/1.1\\r\\n\\r\\n\"\n    msg, payload = parser.feed_data(text)[0][0]\n    assert payload.is_eof()\n\n\ndef test_parse_content_length_payload_multiple(response: HttpResponseParser) -> None:\n    text = b\"HTTP/1.1 200 OK\\r\\ncontent-length: 5\\r\\n\\r\\nfirst\"\n    msg, payload = response.feed_data(text)[0][0]\n    assert msg.version == HttpVersion(major=1, minor=1)\n    assert msg.code == 200\n    assert msg.reason == \"OK\"\n    assert msg.headers == CIMultiDict(\n        [\n            (\"Content-Length\", \"5\"),\n        ]\n    )\n    assert msg.raw_headers == ((b\"content-length\", b\"5\"),)\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert not msg.chunked\n    assert payload.is_eof()\n    assert b\"first\" == b\"\".join(d for d in payload._buffer)\n\n    text = b\"HTTP/1.1 200 OK\\r\\ncontent-length: 6\\r\\n\\r\\nsecond\"\n    msg, payload = response.feed_data(text)[0][0]\n    assert msg.version == HttpVersion(major=1, minor=1)\n    assert msg.code == 200\n    assert msg.reason == \"OK\"\n    assert msg.headers == CIMultiDict(\n        [\n            (\"Content-Length\", \"6\"),\n        ]\n    )\n    assert msg.raw_headers == ((b\"content-length\", b\"6\"),)\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert not msg.chunked\n    assert payload.is_eof()\n    assert b\"second\" == b\"\".join(d for d in payload._buffer)\n\n\ndef test_parse_content_length_than_chunked_payload(\n    response: HttpResponseParser,\n) -> None:\n    text = b\"HTTP/1.1 200 OK\\r\\ncontent-length: 5\\r\\n\\r\\nfirst\"\n    msg, payload = response.feed_data(text)[0][0]\n    assert msg.version == HttpVersion(major=1, minor=1)\n    assert msg.code == 200\n    assert msg.reason == \"OK\"\n    assert msg.headers == CIMultiDict(\n        [\n            (\"Content-Length\", \"5\"),\n        ]\n    )\n    assert msg.raw_headers == ((b\"content-length\", b\"5\"),)\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert not msg.chunked\n    assert payload.is_eof()\n    assert b\"first\" == b\"\".join(d for d in payload._buffer)\n\n    text = (\n        b\"HTTP/1.1 200 OK\\r\\n\"\n        b\"transfer-encoding: chunked\\r\\n\\r\\n\"\n        b\"6\\r\\nsecond\\r\\n0\\r\\n\\r\\n\"\n    )\n    msg, payload = response.feed_data(text)[0][0]\n    assert msg.version == HttpVersion(major=1, minor=1)\n    assert msg.code == 200\n    assert msg.reason == \"OK\"\n    assert msg.headers == CIMultiDict(\n        [\n            (\"Transfer-Encoding\", \"chunked\"),\n        ]\n    )\n    assert msg.raw_headers == ((b\"transfer-encoding\", b\"chunked\"),)\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert msg.chunked\n    assert payload.is_eof()\n    assert b\"second\" == b\"\".join(d for d in payload._buffer)\n\n\n@pytest.mark.parametrize(\"code\", (204, 304, 101, 102))\ndef test_parse_chunked_payload_empty_body_than_another_chunked(\n    response: HttpResponseParser, code: int\n) -> None:\n    head = f\"HTTP/1.1 {code} OK\\r\\n\".encode()\n    text = head + b\"transfer-encoding: chunked\\r\\n\\r\\n\"\n    msg, payload = response.feed_data(text)[0][0]\n    assert msg.version == HttpVersion(major=1, minor=1)\n    assert msg.code == code\n    assert msg.reason == \"OK\"\n    assert msg.headers == CIMultiDict(\n        [\n            (\"Transfer-Encoding\", \"chunked\"),\n        ]\n    )\n    assert msg.raw_headers == ((b\"transfer-encoding\", b\"chunked\"),)\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert msg.chunked\n    assert payload.is_eof()\n\n    text = (\n        b\"HTTP/1.1 200 OK\\r\\n\"\n        b\"transfer-encoding: chunked\\r\\n\\r\\n\"\n        b\"6\\r\\nsecond\\r\\n0\\r\\n\\r\\n\"\n    )\n    msg, payload = response.feed_data(text)[0][0]\n    assert msg.version == HttpVersion(major=1, minor=1)\n    assert msg.code == 200\n    assert msg.reason == \"OK\"\n    assert msg.headers == CIMultiDict(\n        [\n            (\"Transfer-Encoding\", \"chunked\"),\n        ]\n    )\n    assert msg.raw_headers == ((b\"transfer-encoding\", b\"chunked\"),)\n    assert not msg.should_close\n    assert msg.compression is None\n    assert not msg.upgrade\n    assert msg.chunked\n    assert payload.is_eof()\n    assert b\"second\" == b\"\".join(d for d in payload._buffer)\n\n\nasync def test_parse_chunked_payload_split_chunks(response: HttpResponseParser) -> None:\n    network_chunks = (\n        b\"HTTP/1.1 200 OK\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n\",\n        b\"5\\r\\nfi\",\n        b\"rst\",\n        # This simulates a bug in lax mode caused when the \\r\\n separator, before the\n        # next HTTP chunk, appears at the start of the next network chunk.\n        b\"\\r\\n\",\n        b\"6\",\n        b\"\\r\",\n        b\"\\n\",\n        b\"second\\r\",\n        b\"\\n0\\r\\n\\r\\n\",\n    )\n    reader = response.feed_data(network_chunks[0])[0][0][1]\n    for c in network_chunks[1:]:\n        response.feed_data(c)\n\n    assert response.feed_eof() is None\n    assert reader.is_eof()\n    assert await reader.read() == b\"firstsecond\"\n\n\nasync def test_parse_chunked_payload_with_lf_in_extensions(\n    parser: HttpRequestParser,\n) -> None:\n    \"\"\"Test chunked payload that has a LF in the chunk extensions.\"\"\"\n    payload = (\n        b\"GET / HTTP/1.1\\r\\nHost: localhost:5001\\r\\n\"\n        b\"Transfer-Encoding: chunked\\r\\n\\r\\n2;\\nxx\\r\\n4c\\r\\n0\\r\\n\\r\\n\"\n        b\"GET /admin HTTP/1.1\\r\\nHost: localhost:5001\\r\\n\"\n        b\"Transfer-Encoding: chunked\\r\\n\\r\\n0\\r\\n\\r\\n\"\n    )\n    with pytest.raises(http_exceptions.BadHttpMessage, match=\"\\\\\\\\nxx\"):\n        parser.feed_data(payload)\n\n\ndef test_partial_url(parser: HttpRequestParser) -> None:\n    messages, upgrade, tail = parser.feed_data(b\"GET /te\")\n    assert len(messages) == 0\n    messages, upgrade, tail = parser.feed_data(b\"st HTTP/1.1\\r\\n\\r\\n\")\n    assert len(messages) == 1\n\n    msg, payload = messages[0]\n\n    assert msg.method == \"GET\"\n    assert msg.path == \"/test\"\n    assert msg.version == (1, 1)\n    assert payload.is_eof()\n\n\n@pytest.mark.parametrize(\n    (\"uri\", \"path\", \"query\", \"fragment\"),\n    [\n        (\"/path%23frag\", \"/path#frag\", {}, \"\"),\n        (\"/path%2523frag\", \"/path%23frag\", {}, \"\"),\n        (\"/path?key=value%23frag\", \"/path\", {\"key\": \"value#frag\"}, \"\"),\n        (\"/path?key=value%2523frag\", \"/path\", {\"key\": \"value%23frag\"}, \"\"),\n        (\"/path#frag%20\", \"/path\", {}, \"frag \"),\n        (\"/path#frag%2520\", \"/path\", {}, \"frag%20\"),\n    ],\n)\ndef test_parse_uri_percent_encoded(\n    parser: HttpRequestParser, uri: str, path: str, query: dict[str, str], fragment: str\n) -> None:\n    text = (f\"GET {uri} HTTP/1.1\\r\\n\\r\\n\").encode()\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n\n    assert msg.path == uri\n    assert msg.url == URL(uri)\n    assert msg.url.path == path\n    assert msg.url.query == query\n    assert msg.url.fragment == fragment\n\n\ndef test_parse_uri_utf8(parser: HttpRequestParser) -> None:\n    if not isinstance(parser, HttpRequestParserPy):\n        pytest.xfail(\"Not valid HTTP. Maybe update py-parser to reject later.\")\n    text = (\"GET /путь?ключ=знач#фраг HTTP/1.1\\r\\n\\r\\n\").encode()\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n\n    assert msg.path == \"/путь?ключ=знач#фраг\"\n    assert msg.url.path == \"/путь\"\n    assert msg.url.query == {\"ключ\": \"знач\"}\n    assert msg.url.fragment == \"фраг\"\n\n\ndef test_parse_uri_utf8_percent_encoded(parser: HttpRequestParser) -> None:\n    text = (\n        \"GET %s HTTP/1.1\\r\\n\\r\\n\" % quote(\"/путь?ключ=знач#фраг\", safe=\"/?=#\")\n    ).encode()\n    messages, upgrade, tail = parser.feed_data(text)\n    msg = messages[0][0]\n\n    assert msg.path == quote(\"/путь?ключ=знач#фраг\", safe=\"/?=#\")\n    assert msg.url == URL(\"/путь?ключ=знач#фраг\")\n    assert msg.url.path == \"/путь\"\n    assert msg.url.query == {\"ключ\": \"знач\"}\n    assert msg.url.fragment == \"фраг\"\n\n\n@pytest.mark.skipif(\n    \"HttpRequestParserC\" not in dir(aiohttp.http_parser),\n    reason=\"C based HTTP parser not available\",\n)\ndef test_parse_bad_method_for_c_parser_raises(\n    loop: asyncio.AbstractEventLoop, protocol: BaseProtocol\n) -> None:\n    payload = b\"GET1 /test HTTP/1.1\\r\\n\\r\\n\"\n    parser = HttpRequestParserC(\n        protocol,\n        loop,\n        2**16,\n        max_line_size=8190,\n        max_headers=128,\n        max_field_size=8190,\n    )\n\n    with pytest.raises(aiohttp.http_exceptions.BadStatusLine):\n        messages, upgrade, tail = parser.feed_data(payload)\n\n\nclass TestParsePayload:\n    async def test_parse_eof_payload(self, protocol: BaseProtocol) -> None:\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(out, headers_parser=HeadersParser())\n        p.feed_data(b\"data\")\n        p.feed_eof()\n\n        assert out.is_eof()\n        assert [bytearray(b\"data\")] == list(out._buffer)\n\n    async def test_parse_length_payload_eof(self, protocol: BaseProtocol) -> None:\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n\n        p = HttpPayloadParser(out, length=4, headers_parser=HeadersParser())\n        p.feed_data(b\"da\")\n\n        with pytest.raises(http_exceptions.ContentLengthError):\n            p.feed_eof()\n\n    async def test_parse_chunked_payload_size_error(\n        self, protocol: BaseProtocol\n    ) -> None:\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(out, chunked=True, headers_parser=HeadersParser())\n        with pytest.raises(http_exceptions.TransferEncodingError):\n            p.feed_data(b\"blah\\r\\n\")\n        assert isinstance(out.exception(), http_exceptions.TransferEncodingError)\n\n    async def test_parse_chunked_payload_size_data_mismatch(\n        self, protocol: BaseProtocol\n    ) -> None:\n        \"\"\"Chunk-size does not match actual data: should raise, not hang.\n\n        Regression test for #10596.\n        \"\"\"\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(out, chunked=True, headers_parser=HeadersParser())\n        # Declared chunk-size is 4 but actual data is \"Hello\" (5 bytes).\n        # After consuming 4 bytes, remaining starts with \"o\" not \"\\r\\n\".\n        with pytest.raises(http_exceptions.TransferEncodingError):\n            p.feed_data(b\"4\\r\\nHello\\r\\n0\\r\\n\\r\\n\")\n        assert isinstance(out.exception(), http_exceptions.TransferEncodingError)\n\n    async def test_parse_chunked_payload_size_data_mismatch_too_short(\n        self, protocol: BaseProtocol\n    ) -> None:\n        \"\"\"Chunk-size larger than data: declared 6 but only 5 bytes before CRLF.\n\n        Regression test for #10596.\n        \"\"\"\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(out, chunked=True, headers_parser=HeadersParser())\n        # Declared chunk-size is 6 but actual data before CRLF is \"Hello\" (5 bytes).\n        # Parser reads 6 bytes: \"Hello\\r\", then expects \\r\\n but sees \"\\n0\\r\\n...\"\n        with pytest.raises(http_exceptions.TransferEncodingError):\n            p.feed_data(b\"6\\r\\nHello\\r\\n0\\r\\n\\r\\n\")\n        assert isinstance(out.exception(), http_exceptions.TransferEncodingError)\n\n    async def test_parse_chunked_payload_split_end(\n        self, protocol: BaseProtocol\n    ) -> None:\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(out, chunked=True, headers_parser=HeadersParser())\n        p.feed_data(b\"4\\r\\nasdf\\r\\n0\\r\\n\")\n        p.feed_data(b\"\\r\\n\")\n\n        assert out.is_eof()\n        assert b\"asdf\" == b\"\".join(out._buffer)\n\n    async def test_parse_chunked_payload_split_end2(\n        self, protocol: BaseProtocol\n    ) -> None:\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(out, chunked=True, headers_parser=HeadersParser())\n        p.feed_data(b\"4\\r\\nasdf\\r\\n0\\r\\n\\r\")\n        p.feed_data(b\"\\n\")\n\n        assert out.is_eof()\n        assert b\"asdf\" == b\"\".join(out._buffer)\n\n    async def test_parse_chunked_payload_split_end_trailers(\n        self, protocol: BaseProtocol\n    ) -> None:\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(out, chunked=True, headers_parser=HeadersParser())\n        p.feed_data(b\"4\\r\\nasdf\\r\\n0\\r\\n\")\n        p.feed_data(b\"Content-MD5: 912ec803b2ce49e4a541068d495ab570\\r\\n\")\n        p.feed_data(b\"\\r\\n\")\n\n        assert out.is_eof()\n        assert b\"asdf\" == b\"\".join(out._buffer)\n\n    async def test_parse_chunked_payload_split_end_trailers2(\n        self, protocol: BaseProtocol\n    ) -> None:\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(out, chunked=True, headers_parser=HeadersParser())\n        p.feed_data(b\"4\\r\\nasdf\\r\\n0\\r\\n\")\n        p.feed_data(b\"Content-MD5: 912ec803b2ce49e4a541068d495ab570\\r\\n\\r\")\n        p.feed_data(b\"\\n\")\n\n        assert out.is_eof()\n        assert b\"asdf\" == b\"\".join(out._buffer)\n\n    async def test_parse_chunked_payload_split_end_trailers3(\n        self, protocol: BaseProtocol\n    ) -> None:\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(out, chunked=True, headers_parser=HeadersParser())\n        p.feed_data(b\"4\\r\\nasdf\\r\\n0\\r\\nContent-MD5: \")\n        p.feed_data(b\"912ec803b2ce49e4a541068d495ab570\\r\\n\\r\\n\")\n\n        assert out.is_eof()\n        assert b\"asdf\" == b\"\".join(out._buffer)\n\n    async def test_parse_chunked_payload_split_end_trailers4(\n        self, protocol: BaseProtocol\n    ) -> None:\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(out, chunked=True, headers_parser=HeadersParser())\n        p.feed_data(b\"4\\r\\nasdf\\r\\n0\\r\\nC\")\n        p.feed_data(b\"ontent-MD5: 912ec803b2ce49e4a541068d495ab570\\r\\n\\r\\n\")\n\n        assert out.is_eof()\n        assert b\"asdf\" == b\"\".join(out._buffer)\n\n    async def test_http_payload_parser_length(self, protocol: BaseProtocol) -> None:\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(out, length=2, headers_parser=HeadersParser())\n        eof, tail = p.feed_data(b\"1245\")\n        assert eof\n\n        assert b\"12\" == out._buffer[0]\n        assert b\"45\" == tail\n\n    async def test_http_payload_parser_deflate(self, protocol: BaseProtocol) -> None:\n        # c=compressobj(wbits=15); b''.join([c.compress(b'data'), c.flush()])\n        COMPRESSED = b\"x\\x9cKI,I\\x04\\x00\\x04\\x00\\x01\\x9b\"\n\n        length = len(COMPRESSED)\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(\n            out, length=length, compression=\"deflate\", headers_parser=HeadersParser()\n        )\n        p.feed_data(COMPRESSED)\n        assert b\"data\" == out._buffer[0]\n        assert out.is_eof()\n\n    async def test_http_payload_parser_deflate_no_hdrs(\n        self, protocol: BaseProtocol\n    ) -> None:\n        \"\"\"Tests incorrectly formed data (no zlib headers).\"\"\"\n        # c=compressobj(wbits=-15); b''.join([c.compress(b'data'), c.flush()])\n        COMPRESSED = b\"KI,I\\x04\\x00\"\n\n        length = len(COMPRESSED)\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(\n            out, length=length, compression=\"deflate\", headers_parser=HeadersParser()\n        )\n        p.feed_data(COMPRESSED)\n        assert b\"data\" == out._buffer[0]\n        assert out.is_eof()\n\n    async def test_http_payload_parser_deflate_light(\n        self, protocol: BaseProtocol\n    ) -> None:\n        # c=compressobj(wbits=9); b''.join([c.compress(b'data'), c.flush()])\n        COMPRESSED = b\"\\x18\\x95KI,I\\x04\\x00\\x04\\x00\\x01\\x9b\"\n\n        length = len(COMPRESSED)\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(\n            out, length=length, compression=\"deflate\", headers_parser=HeadersParser()\n        )\n        p.feed_data(COMPRESSED)\n\n        assert b\"data\" == out._buffer[0]\n        assert out.is_eof()\n\n    async def test_http_payload_parser_deflate_split(\n        self, protocol: BaseProtocol\n    ) -> None:\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(\n            out, compression=\"deflate\", headers_parser=HeadersParser()\n        )\n        # Feeding one correct byte should be enough to choose exact\n        # deflate decompressor\n        p.feed_data(b\"x\")\n        p.feed_data(b\"\\x9cKI,I\\x04\\x00\\x04\\x00\\x01\\x9b\")\n        p.feed_eof()\n        assert b\"data\" == out._buffer[0]\n\n    async def test_http_payload_parser_deflate_split_err(\n        self, protocol: BaseProtocol\n    ) -> None:\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(\n            out, compression=\"deflate\", headers_parser=HeadersParser()\n        )\n        # Feeding one wrong byte should be enough to choose exact\n        # deflate decompressor\n        p.feed_data(b\"K\")\n        p.feed_data(b\"I,I\\x04\\x00\")\n        p.feed_eof()\n        assert b\"data\" == out._buffer[0]\n\n    async def test_http_payload_parser_length_zero(\n        self, protocol: BaseProtocol\n    ) -> None:\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(out, length=0, headers_parser=HeadersParser())\n        assert p.done\n        assert out.is_eof()\n\n    @pytest.mark.skipif(brotli is None, reason=\"brotli is not installed\")\n    async def test_http_payload_brotli(self, protocol: BaseProtocol) -> None:\n        compressed = brotli.compress(b\"brotli data\")\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(\n            out,\n            length=len(compressed),\n            compression=\"br\",\n            headers_parser=HeadersParser(),\n        )\n        p.feed_data(compressed)\n        assert b\"brotli data\" == out._buffer[0]\n        assert out.is_eof()\n\n    @pytest.mark.skipif(zstandard is None, reason=\"zstandard is not installed\")\n    async def test_http_payload_zstandard(self, protocol: BaseProtocol) -> None:\n        compressed = zstandard.compress(b\"zstd data\")\n        out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        p = HttpPayloadParser(\n            out,\n            length=len(compressed),\n            compression=\"zstd\",\n            headers_parser=HeadersParser(),\n        )\n        p.feed_data(compressed)\n        assert b\"zstd data\" == out._buffer[0]\n        assert out.is_eof()\n\n\nclass TestDeflateBuffer:\n    async def test_feed_data(self, protocol: BaseProtocol) -> None:\n        buf = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        dbuf = DeflateBuffer(buf, \"deflate\")\n\n        dbuf.decompressor = mock.Mock()\n        dbuf.decompressor.decompress_sync.return_value = b\"line\"\n\n        # First byte should be b'x' in order code not to change the decoder.\n        dbuf.feed_data(b\"xxxx\")\n        assert [b\"line\"] == list(buf._buffer)\n\n    async def test_feed_data_err(self, protocol: BaseProtocol) -> None:\n        buf = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        dbuf = DeflateBuffer(buf, \"deflate\")\n\n        exc = ValueError()\n        dbuf.decompressor = mock.Mock()\n        dbuf.decompressor.decompress_sync.side_effect = exc\n\n        with pytest.raises(http_exceptions.ContentEncodingError):\n            # Should be more than 4 bytes to trigger deflate FSM error.\n            # Should start with b'x', otherwise code switch mocked decoder.\n            dbuf.feed_data(b\"xsomedata\")\n\n    async def test_feed_eof(self, protocol: BaseProtocol) -> None:\n        buf = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        dbuf = DeflateBuffer(buf, \"deflate\")\n\n        dbuf.decompressor = mock.Mock()\n        dbuf.decompressor.flush.return_value = b\"line\"\n\n        dbuf.feed_eof()\n        assert [b\"line\"] == list(buf._buffer)\n        assert buf._eof\n\n    async def test_feed_eof_err_deflate(self, protocol: BaseProtocol) -> None:\n        buf = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        dbuf = DeflateBuffer(buf, \"deflate\")\n\n        dbuf.decompressor = mock.Mock()\n        dbuf.decompressor.flush.return_value = b\"line\"\n        dbuf.decompressor.eof = False\n\n        with pytest.raises(http_exceptions.ContentEncodingError):\n            dbuf.feed_eof()\n\n    async def test_feed_eof_no_err_gzip(self, protocol: BaseProtocol) -> None:\n        buf = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        dbuf = DeflateBuffer(buf, \"gzip\")\n\n        dbuf.decompressor = mock.Mock()\n        dbuf.decompressor.flush.return_value = b\"line\"\n        dbuf.decompressor.eof = False\n\n        dbuf.feed_eof()\n        assert [b\"line\"] == list(buf._buffer)\n\n    async def test_feed_eof_no_err_brotli(self, protocol: BaseProtocol) -> None:\n        buf = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        dbuf = DeflateBuffer(buf, \"br\")\n\n        dbuf.decompressor = mock.Mock()\n        dbuf.decompressor.flush.return_value = b\"line\"\n        dbuf.decompressor.eof = False\n\n        dbuf.feed_eof()\n        assert [b\"line\"] == list(buf._buffer)\n\n    @pytest.mark.skipif(zstandard is None, reason=\"zstandard is not installed\")\n    async def test_feed_eof_no_err_zstandard(self, protocol: BaseProtocol) -> None:\n        buf = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        dbuf = DeflateBuffer(buf, \"zstd\")\n\n        dbuf.decompressor = mock.Mock()\n        dbuf.decompressor.flush.return_value = b\"line\"\n        dbuf.decompressor.eof = False\n\n        dbuf.feed_eof()\n        assert [b\"line\"] == list(buf._buffer)\n\n    async def test_empty_body(self, protocol: BaseProtocol) -> None:\n        buf = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        dbuf = DeflateBuffer(buf, \"deflate\")\n        dbuf.feed_eof()\n\n        assert buf.at_eof()\n\n    @pytest.mark.parametrize(\n        \"chunk_size\",\n        [1024, 2**14, 2**16],  # 1KB, 16KB, 64KB\n        ids=[\"1KB\", \"16KB\", \"64KB\"],\n    )\n    async def test_streaming_decompress_large_payload(\n        self, protocol: BaseProtocol, chunk_size: int\n    ) -> None:\n        \"\"\"Test that large payloads decompress correctly when streamed in chunks.\n\n        This simulates real HTTP streaming where compressed data arrives in\n        small network chunks. Each chunk's decompressed output should be within\n        the max_decompress_size limit, allowing full recovery of the original data.\n        \"\"\"\n        # Create a large payload (3MiB) that compresses well\n        original = b\"A\" * (3 * 2**20)\n        compressed = zlib.compress(original)\n\n        buf = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n        dbuf = DeflateBuffer(buf, \"deflate\")\n\n        # Feed compressed data in chunks (simulating network streaming)\n        for i in range(0, len(compressed), chunk_size):  # pragma: no branch\n            chunk = compressed[i : i + chunk_size]\n            dbuf.feed_data(chunk)\n\n        dbuf.feed_eof()\n\n        # Read all decompressed data\n        result = b\"\".join(buf._buffer)\n        assert len(result) == len(original)\n        assert result == original\n"
  },
  {
    "path": "tests/test_http_writer.py",
    "content": "# Tests for aiohttp/http_writer.py\nimport array\nimport asyncio\nimport zlib\nfrom collections.abc import Generator, Iterable\nfrom typing import Any\nfrom unittest import mock\n\nimport pytest\nfrom multidict import CIMultiDict\n\nfrom aiohttp import ClientConnectionResetError, hdrs, http\nfrom aiohttp.base_protocol import BaseProtocol\nfrom aiohttp.compression_utils import ZLibBackend\nfrom aiohttp.http_writer import _serialize_headers\n\n\n@pytest.fixture\ndef enable_writelines() -> Generator[None, None, None]:\n    with mock.patch(\"aiohttp.http_writer.SKIP_WRITELINES\", False):\n        yield\n\n\n@pytest.fixture\ndef disable_writelines() -> Generator[None, None, None]:\n    with mock.patch(\"aiohttp.http_writer.SKIP_WRITELINES\", True):\n        yield\n\n\n@pytest.fixture\ndef force_writelines_small_payloads() -> Generator[None, None, None]:\n    with mock.patch(\"aiohttp.http_writer.MIN_PAYLOAD_FOR_WRITELINES\", 1):\n        yield\n\n\n@pytest.fixture\ndef buf() -> bytearray:\n    return bytearray()\n\n\n@pytest.fixture\ndef transport(buf: bytearray) -> Any:\n    transport = mock.create_autospec(asyncio.Transport, spec_set=True, instance=True)\n\n    def write(chunk: bytes) -> None:\n        buf.extend(chunk)\n\n    def writelines(chunks: Iterable[bytes]) -> None:\n        for chunk in chunks:\n            buf.extend(chunk)\n\n    transport.write.side_effect = write\n    transport.writelines.side_effect = writelines\n    transport.is_closing.return_value = False\n    return transport\n\n\n@pytest.fixture\ndef protocol(loop: asyncio.AbstractEventLoop, transport: asyncio.Transport) -> Any:\n    return mock.create_autospec(\n        BaseProtocol, spec_set=True, instance=True, transport=transport\n    )\n\n\ndef decompress(data: bytes) -> bytes:\n    d = ZLibBackend.decompressobj()\n    return d.decompress(data)\n\n\ndef decode_chunked(chunked: bytes | bytearray) -> bytes:\n    i = 0\n    out = b\"\"\n    while i < len(chunked):\n        j = chunked.find(b\"\\r\\n\", i)\n        assert j != -1, \"Malformed chunk\"\n        size = int(chunked[i:j], 16)\n        if size == 0:\n            break\n        i = j + 2\n        out += chunked[i : i + size]\n        i += size + 2  # skip \\r\\n after the chunk\n    return out\n\n\ndef test_payloadwriter_properties(\n    transport: asyncio.Transport,\n    protocol: BaseProtocol,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    writer = http.StreamWriter(protocol, loop)\n    assert writer.protocol == protocol\n    assert writer.transport == transport\n\n\nasync def test_write_headers_buffered_small_payload(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    headers = CIMultiDict({\"Content-Length\": \"11\", \"Host\": \"example.com\"})\n\n    # Write headers - should be buffered\n    await msg.write_headers(\"GET / HTTP/1.1\", headers)\n    assert len(buf) == 0  # Headers not sent yet\n\n    # Write small body - should coalesce with headers\n    await msg.write(b\"Hello World\", drain=False)\n\n    # Verify content\n    assert b\"GET / HTTP/1.1\\r\\n\" in buf\n    assert b\"Host: example.com\\r\\n\" in buf\n    assert b\"Content-Length: 11\\r\\n\" in buf\n    assert b\"\\r\\n\\r\\nHello World\" in buf\n\n\nasync def test_write_headers_chunked_coalescing(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_chunking()\n    headers = CIMultiDict({\"Transfer-Encoding\": \"chunked\", \"Host\": \"example.com\"})\n\n    # Write headers - should be buffered\n    await msg.write_headers(\"POST /upload HTTP/1.1\", headers)\n    assert len(buf) == 0  # Headers not sent yet\n\n    # Write first chunk - should coalesce with headers\n    await msg.write(b\"First chunk\", drain=False)\n\n    # Verify content\n    assert b\"POST /upload HTTP/1.1\\r\\n\" in buf\n    assert b\"Transfer-Encoding: chunked\\r\\n\" in buf\n    # \"b\" is hex for 11 (length of \"First chunk\")\n    assert b\"\\r\\n\\r\\nb\\r\\nFirst chunk\\r\\n\" in buf\n\n\nasync def test_write_eof_with_buffered_headers(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    headers = CIMultiDict({\"Content-Length\": \"9\", \"Host\": \"example.com\"})\n\n    # Write headers - should be buffered\n    await msg.write_headers(\"POST /data HTTP/1.1\", headers)\n    assert len(buf) == 0\n\n    # Call write_eof with body - should coalesce\n    await msg.write_eof(b\"Last data\")\n\n    # Verify content\n    assert b\"POST /data HTTP/1.1\\r\\n\" in buf\n    assert b\"\\r\\n\\r\\nLast data\" in buf\n\n\nasync def test_set_eof_sends_buffered_headers(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    headers = CIMultiDict({\"Host\": \"example.com\"})\n\n    # Write headers - should be buffered\n    await msg.write_headers(\"GET /empty HTTP/1.1\", headers)\n    assert len(buf) == 0\n\n    # Call set_eof without body - headers should be sent\n    msg.set_eof()\n\n    # Headers should be sent\n    assert len(buf) > 0\n    assert b\"GET /empty HTTP/1.1\\r\\n\" in buf\n\n\nasync def test_write_payload_eof(\n    transport: asyncio.Transport,\n    protocol: BaseProtocol,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n\n    await msg.write(b\"data1\")\n    await msg.write(b\"data2\")\n    await msg.write_eof()\n\n    content = b\"\".join([c[1][0] for c in list(transport.write.mock_calls)])  # type: ignore[attr-defined]\n    assert b\"data1data2\" == content.split(b\"\\r\\n\\r\\n\", 1)[-1]\n\n\nasync def test_write_payload_chunked(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_chunking()\n    await msg.write(b\"data\")\n    await msg.write_eof()\n\n    assert b\"4\\r\\ndata\\r\\n0\\r\\n\\r\\n\" == buf\n\n\nasync def test_write_payload_chunked_multiple(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_chunking()\n    await msg.write(b\"data1\")\n    await msg.write(b\"data2\")\n    await msg.write_eof()\n\n    assert b\"5\\r\\ndata1\\r\\n5\\r\\ndata2\\r\\n0\\r\\n\\r\\n\" == buf\n\n\nasync def test_write_payload_length(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.length = 2\n    await msg.write(b\"d\")\n    await msg.write(b\"ata\")\n    await msg.write_eof()\n\n    content = b\"\".join([c[1][0] for c in list(transport.write.mock_calls)])  # type: ignore[attr-defined]\n    assert b\"da\" == content.split(b\"\\r\\n\\r\\n\", 1)[-1]\n\n\n@pytest.mark.usefixtures(\"disable_writelines\")\n@pytest.mark.internal  # Used for performance benchmarking\nasync def test_write_large_payload_deflate_compression_data_in_eof(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n\n    await msg.write(b\"data\" * 4096)\n    assert transport.write.called  # type: ignore[attr-defined]\n    chunks = [c[1][0] for c in list(transport.write.mock_calls)]  # type: ignore[attr-defined]\n    transport.write.reset_mock()  # type: ignore[attr-defined]\n\n    # This payload compresses to 20447 bytes\n    payload = b\"\".join(\n        [bytes((*range(0, i), *range(i, 0, -1))) for i in range(255) for _ in range(64)]\n    )\n    await msg.write_eof(payload)\n    chunks.extend([c[1][0] for c in list(transport.write.mock_calls)])  # type: ignore[attr-defined]\n\n    assert all(chunks)\n    content = b\"\".join(chunks)\n    assert zlib.decompress(content) == (b\"data\" * 4096) + payload\n\n\n@pytest.mark.usefixtures(\"disable_writelines\")\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_write_large_payload_deflate_compression_data_in_eof_all_zlib(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n\n    await msg.write(b\"data\" * 4096)\n    # Behavior depends on zlib backend, isal compress() returns b'' initially\n    # and the entire compressed bytes at flush() for this data\n    backend_to_write_called = {\n        \"isal.isal_zlib\": False,\n        \"zlib\": True,\n        \"zlib_ng.zlib_ng\": True,\n    }\n    assert transport.write.called == backend_to_write_called[ZLibBackend.name]  # type: ignore[attr-defined]\n    chunks = [c[1][0] for c in list(transport.write.mock_calls)]  # type: ignore[attr-defined]\n    transport.write.reset_mock()  # type: ignore[attr-defined]\n\n    # This payload compresses to 20447 bytes\n    payload = b\"\".join(\n        [bytes((*range(0, i), *range(i, 0, -1))) for i in range(255) for _ in range(64)]\n    )\n    await msg.write_eof(payload)\n    chunks.extend([c[1][0] for c in list(transport.write.mock_calls)])  # type: ignore[attr-defined]\n\n    assert all(chunks)\n    content = b\"\".join(chunks)\n    assert ZLibBackend.decompress(content) == (b\"data\" * 4096) + payload\n\n\n@pytest.mark.usefixtures(\"enable_writelines\")\n@pytest.mark.internal  # Used for performance benchmarking\nasync def test_write_large_payload_deflate_compression_data_in_eof_writelines(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n\n    await msg.write(b\"data\" * 4096)\n    assert transport.write.called  # type: ignore[attr-defined]\n    chunks = [c[1][0] for c in list(transport.write.mock_calls)]  # type: ignore[attr-defined]\n    transport.write.reset_mock()  # type: ignore[attr-defined]\n    assert not transport.writelines.called  # type: ignore[attr-defined]\n\n    # This payload compresses to 20447 bytes\n    payload = b\"\".join(\n        [bytes((*range(0, i), *range(i, 0, -1))) for i in range(255) for _ in range(64)]\n    )\n    await msg.write_eof(payload)\n    assert not transport.write.called  # type: ignore[attr-defined]\n    assert transport.writelines.called  # type: ignore[attr-defined]\n    chunks.extend(transport.writelines.mock_calls[0][1][0])  # type: ignore[attr-defined]\n    content = b\"\".join(chunks)\n    assert zlib.decompress(content) == (b\"data\" * 4096) + payload\n\n\n@pytest.mark.usefixtures(\"enable_writelines\")\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_write_large_payload_deflate_compression_data_in_eof_writelines_all_zlib(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n\n    await msg.write(b\"data\" * 4096)\n    # Behavior depends on zlib backend, isal compress() returns b'' initially\n    # and the entire compressed bytes at flush() for this data\n    backend_to_write_called = {\n        \"isal.isal_zlib\": False,\n        \"zlib\": True,\n        \"zlib_ng.zlib_ng\": True,\n    }\n    assert transport.write.called == backend_to_write_called[ZLibBackend.name]  # type: ignore[attr-defined]\n    chunks = [c[1][0] for c in list(transport.write.mock_calls)]  # type: ignore[attr-defined]\n    transport.write.reset_mock()  # type: ignore[attr-defined]\n    assert not transport.writelines.called  # type: ignore[attr-defined]\n\n    # This payload compresses to 20447 bytes\n    payload = b\"\".join(\n        [bytes((*range(0, i), *range(i, 0, -1))) for i in range(255) for _ in range(64)]\n    )\n    await msg.write_eof(payload)\n    assert transport.writelines.called != transport.write.called  # type: ignore[attr-defined]\n    if transport.writelines.called:  # type: ignore[attr-defined]\n        chunks.extend(transport.writelines.mock_calls[0][1][0])  # type: ignore[attr-defined]\n    else:  # transport.write.called:  # type: ignore[attr-defined]\n        chunks.extend([c[1][0] for c in list(transport.write.mock_calls)])  # type: ignore[attr-defined]\n    content = b\"\".join(chunks)\n    assert ZLibBackend.decompress(content) == (b\"data\" * 4096) + payload\n\n\nasync def test_write_payload_chunked_filter(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_chunking()\n    await msg.write(b\"da\")\n    await msg.write(b\"ta\")\n    await msg.write_eof()\n\n    content = b\"\".join([b\"\".join(c[1][0]) for c in list(transport.writelines.mock_calls)])  # type: ignore[attr-defined]\n    content += b\"\".join([c[1][0] for c in list(transport.write.mock_calls)])  # type: ignore[attr-defined]\n    assert content.endswith(b\"2\\r\\nda\\r\\n2\\r\\nta\\r\\n0\\r\\n\\r\\n\")\n\n\nasync def test_write_payload_chunked_filter_multiple_chunks(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_chunking()\n    await msg.write(b\"da\")\n    await msg.write(b\"ta\")\n    await msg.write(b\"1d\")\n    await msg.write(b\"at\")\n    await msg.write(b\"a2\")\n    await msg.write_eof()\n    content = b\"\".join([b\"\".join(c[1][0]) for c in list(transport.writelines.mock_calls)])  # type: ignore[attr-defined]\n    content += b\"\".join([c[1][0] for c in list(transport.write.mock_calls)])  # type: ignore[attr-defined]\n    assert content.endswith(\n        b\"2\\r\\nda\\r\\n2\\r\\nta\\r\\n2\\r\\n1d\\r\\n2\\r\\nat\\r\\n2\\r\\na2\\r\\n0\\r\\n\\r\\n\"\n    )\n\n\n@pytest.mark.internal  # Used for performance benchmarking\nasync def test_write_payload_deflate_compression(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    COMPRESSED = b\"x\\x9cKI,I\\x04\\x00\\x04\\x00\\x01\\x9b\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    await msg.write(b\"data\")\n    await msg.write_eof()\n\n    chunks = [c[1][0] for c in list(transport.write.mock_calls)]  # type: ignore[attr-defined]\n    assert all(chunks)\n    content = b\"\".join(chunks)\n    assert COMPRESSED == content.split(b\"\\r\\n\\r\\n\", 1)[-1]\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_write_payload_deflate_compression_all_zlib(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    await msg.write(b\"data\")\n    await msg.write_eof()\n\n    chunks = [c[1][0] for c in list(transport.write.mock_calls)]  # type: ignore[attr-defined]\n    assert all(chunks)\n    content = b\"\".join(chunks)\n    assert b\"data\" == decompress(content)\n\n\n@pytest.mark.internal  # Used for performance benchmarking\nasync def test_write_payload_deflate_compression_chunked(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    expected = b\"2\\r\\nx\\x9c\\r\\na\\r\\nKI,I\\x04\\x00\\x04\\x00\\x01\\x9b\\r\\n0\\r\\n\\r\\n\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n    await msg.write(b\"data\")\n    await msg.write_eof()\n\n    chunks = [c[1][0] for c in list(transport.write.mock_calls)]  # type: ignore[attr-defined]\n    assert all(chunks)\n    content = b\"\".join(chunks)\n    assert content == expected\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_write_payload_deflate_compression_chunked_all_zlib(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n    await msg.write(b\"data\")\n    await msg.write_eof()\n\n    chunks = [c[1][0] for c in list(transport.write.mock_calls)]  # type: ignore[attr-defined]\n    assert all(chunks)\n    content = b\"\".join(chunks)\n    assert b\"data\" == decompress(decode_chunked(content))\n\n\n@pytest.mark.usefixtures(\"enable_writelines\")\n@pytest.mark.usefixtures(\"force_writelines_small_payloads\")\n@pytest.mark.internal  # Used for performance benchmarking\nasync def test_write_payload_deflate_compression_chunked_writelines(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    expected = b\"2\\r\\nx\\x9c\\r\\na\\r\\nKI,I\\x04\\x00\\x04\\x00\\x01\\x9b\\r\\n0\\r\\n\\r\\n\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n    await msg.write(b\"data\")\n    await msg.write_eof()\n\n    chunks = [b\"\".join(c[1][0]) for c in list(transport.writelines.mock_calls)]  # type: ignore[attr-defined]\n    assert all(chunks)\n    content = b\"\".join(chunks)\n    assert content == expected\n\n\n@pytest.mark.usefixtures(\"enable_writelines\")\n@pytest.mark.usefixtures(\"force_writelines_small_payloads\")\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_write_payload_deflate_compression_chunked_writelines_all_zlib(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n    await msg.write(b\"data\")\n    await msg.write_eof()\n\n    chunks = [b\"\".join(c[1][0]) for c in list(transport.writelines.mock_calls)]  # type: ignore[attr-defined]\n    assert all(chunks)\n    content = b\"\".join(chunks)\n    assert b\"data\" == decompress(decode_chunked(content))\n\n\n@pytest.mark.internal  # Used for performance benchmarking\nasync def test_write_payload_deflate_and_chunked(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n\n    await msg.write(b\"da\")\n    await msg.write(b\"ta\")\n    await msg.write_eof()\n\n    thing = b\"2\\r\\nx\\x9c\\r\\na\\r\\nKI,I\\x04\\x00\\x04\\x00\\x01\\x9b\\r\\n0\\r\\n\\r\\n\"\n    assert thing == buf\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_write_payload_deflate_and_chunked_all_zlib(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n\n    await msg.write(b\"da\")\n    await msg.write(b\"ta\")\n    await msg.write_eof()\n\n    assert b\"data\" == decompress(decode_chunked(buf))\n\n\n@pytest.mark.internal  # Used for performance benchmarking\nasync def test_write_payload_deflate_compression_chunked_data_in_eof(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    expected = b\"2\\r\\nx\\x9c\\r\\nd\\r\\nKI,IL\\xcdK\\x01\\x00\\x0b@\\x02\\xd2\\r\\n0\\r\\n\\r\\n\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n    await msg.write(b\"data\")\n    await msg.write_eof(b\"end\")\n\n    chunks = [c[1][0] for c in list(transport.write.mock_calls)]  # type: ignore[attr-defined]\n    assert all(chunks)\n    content = b\"\".join(chunks)\n    assert content == expected\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_write_payload_deflate_compression_chunked_data_in_eof_all_zlib(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n    await msg.write(b\"data\")\n    await msg.write_eof(b\"end\")\n\n    chunks = [c[1][0] for c in list(transport.write.mock_calls)]  # type: ignore[attr-defined]\n    assert all(chunks)\n    content = b\"\".join(chunks)\n    assert b\"dataend\" == decompress(decode_chunked(content))\n\n\n@pytest.mark.usefixtures(\"enable_writelines\")\n@pytest.mark.usefixtures(\"force_writelines_small_payloads\")\n@pytest.mark.internal  # Used for performance benchmarking\nasync def test_write_payload_deflate_compression_chunked_data_in_eof_writelines(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    expected = b\"2\\r\\nx\\x9c\\r\\nd\\r\\nKI,IL\\xcdK\\x01\\x00\\x0b@\\x02\\xd2\\r\\n0\\r\\n\\r\\n\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n    await msg.write(b\"data\")\n    await msg.write_eof(b\"end\")\n\n    chunks = [b\"\".join(c[1][0]) for c in list(transport.writelines.mock_calls)]  # type: ignore[attr-defined]\n    assert all(chunks)\n    content = b\"\".join(chunks)\n    assert content == expected\n\n\n@pytest.mark.usefixtures(\"enable_writelines\")\n@pytest.mark.usefixtures(\"force_writelines_small_payloads\")\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_write_payload_deflate_compression_chunked_data_in_eof_writelines_all_zlib(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n    await msg.write(b\"data\")\n    await msg.write_eof(b\"end\")\n\n    chunks = [b\"\".join(c[1][0]) for c in list(transport.writelines.mock_calls)]  # type: ignore[attr-defined]\n    assert all(chunks)\n    content = b\"\".join(chunks)\n    assert b\"dataend\" == decompress(decode_chunked(content))\n\n\n@pytest.mark.internal  # Used for performance benchmarking\nasync def test_write_large_payload_deflate_compression_chunked_data_in_eof(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n\n    await msg.write(b\"data\" * 4096)\n    # This payload compresses to 1111 bytes\n    payload = b\"\".join([bytes((*range(0, i), *range(i, 0, -1))) for i in range(255)])\n    await msg.write_eof(payload)\n\n    compressed = []\n    chunks = [c[1][0] for c in list(transport.write.mock_calls)]  # type: ignore[attr-defined]\n    chunked_body = b\"\".join(chunks)\n    split_body = chunked_body.split(b\"\\r\\n\")\n    while split_body:\n        if split_body.pop(0):\n            compressed.append(split_body.pop(0))\n\n    content = b\"\".join(compressed)\n    assert zlib.decompress(content) == (b\"data\" * 4096) + payload\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_write_large_payload_deflate_compression_chunked_data_in_eof_all_zlib(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n\n    await msg.write(b\"data\" * 4096)\n    # This payload compresses to 1111 bytes\n    payload = b\"\".join([bytes((*range(0, i), *range(i, 0, -1))) for i in range(255)])\n    await msg.write_eof(payload)\n\n    compressed = []\n    chunks = [c[1][0] for c in list(transport.write.mock_calls)]  # type: ignore[attr-defined]\n    chunked_body = b\"\".join(chunks)\n    split_body = chunked_body.split(b\"\\r\\n\")\n    while split_body:\n        if split_body.pop(0):\n            compressed.append(split_body.pop(0))\n\n    content = b\"\".join(compressed)\n    assert ZLibBackend.decompress(content) == (b\"data\" * 4096) + payload\n\n\n@pytest.mark.usefixtures(\"enable_writelines\")\n@pytest.mark.usefixtures(\"force_writelines_small_payloads\")\n@pytest.mark.internal  # Used for performance benchmarking\nasync def test_write_large_payload_deflate_compression_chunked_data_in_eof_writelines(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n\n    await msg.write(b\"data\" * 4096)\n    # This payload compresses to 1111 bytes\n    payload = b\"\".join([bytes((*range(0, i), *range(i, 0, -1))) for i in range(255)])\n    await msg.write_eof(payload)\n    assert not transport.write.called  # type: ignore[attr-defined]\n\n    chunks = []\n    for write_lines_call in transport.writelines.mock_calls:  # type: ignore[attr-defined]\n        chunked_payload = list(write_lines_call[1][0])[1:]\n        chunked_payload.pop()\n        chunks.extend(chunked_payload)\n\n    assert all(chunks)\n    content = b\"\".join(chunks)\n    assert zlib.decompress(content) == (b\"data\" * 4096) + payload\n\n\n@pytest.mark.usefixtures(\"enable_writelines\")\n@pytest.mark.usefixtures(\"force_writelines_small_payloads\")\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_write_large_payload_deflate_compression_chunked_data_in_eof_writelines_all_zlib(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n\n    await msg.write(b\"data\" * 4096)\n    # This payload compresses to 1111 bytes\n    payload = b\"\".join([bytes((*range(0, i), *range(i, 0, -1))) for i in range(255)])\n    await msg.write_eof(payload)\n    assert not transport.write.called  # type: ignore[attr-defined]\n\n    chunks = []\n    for write_lines_call in transport.writelines.mock_calls:  # type: ignore[attr-defined]\n        chunked_payload = list(write_lines_call[1][0])[1:]\n        chunked_payload.pop()\n        chunks.extend(chunked_payload)\n\n    assert all(chunks)\n    content = b\"\".join(chunks)\n    assert ZLibBackend.decompress(content) == (b\"data\" * 4096) + payload\n\n\n@pytest.mark.internal  # Used for performance benchmarking\nasync def test_write_payload_deflate_compression_chunked_connection_lost(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n    await msg.write(b\"data\")\n    with (\n        pytest.raises(\n            ClientConnectionResetError, match=\"Cannot write to closing transport\"\n        ),\n        mock.patch.object(transport, \"is_closing\", return_value=True),\n    ):\n        await msg.write_eof(b\"end\")\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_write_payload_deflate_compression_chunked_connection_lost_all_zlib(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n    await msg.write(b\"data\")\n    with (\n        pytest.raises(\n            ClientConnectionResetError, match=\"Cannot write to closing transport\"\n        ),\n        mock.patch.object(transport, \"is_closing\", return_value=True),\n    ):\n        await msg.write_eof(b\"end\")\n\n\nasync def test_write_payload_bytes_memoryview(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n\n    mv = memoryview(b\"abcd\")\n\n    await msg.write(mv)\n    await msg.write_eof()\n\n    thing = b\"abcd\"\n    assert thing == buf\n\n\nasync def test_write_payload_short_ints_memoryview(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_chunking()\n\n    payload = memoryview(array.array(\"H\", [65, 66, 67]))\n\n    await msg.write(payload)\n    await msg.write_eof()\n\n    endians = (\n        (b\"6\\r\\n\\x00A\\x00B\\x00C\\r\\n0\\r\\n\\r\\n\"),\n        (b\"6\\r\\nA\\x00B\\x00C\\x00\\r\\n0\\r\\n\\r\\n\"),\n    )\n    assert buf in endians\n\n\nasync def test_write_payload_2d_shape_memoryview(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_chunking()\n\n    mv = memoryview(b\"ABCDEF\")\n    payload = mv.cast(\"c\", [3, 2])\n\n    await msg.write(payload)\n    await msg.write_eof()\n\n    thing = b\"6\\r\\nABCDEF\\r\\n0\\r\\n\\r\\n\"\n    assert thing == buf\n\n\nasync def test_write_payload_slicing_long_memoryview(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.length = 4\n\n    mv = memoryview(b\"ABCDEF\")\n    payload = mv.cast(\"c\", [3, 2])\n\n    await msg.write(payload)\n    await msg.write_eof()\n\n    thing = b\"ABCD\"\n    assert thing == buf\n\n\nasync def test_write_drain(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    with mock.patch.object(msg, \"drain\", autospec=True, spec_set=True) as m:\n        await msg.write(b\"1\" * (64 * 1024 * 2), drain=False)\n        assert not m.called\n\n        await msg.write(b\"1\", drain=True)\n        assert m.called\n        assert msg.buffer_size == 0  # type: ignore[unreachable]\n\n\nasync def test_write_calls_callback(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n\n    async def on_chunk_sent(chunk: bytes) -> None:\n        \"\"\"Mock signature\"\"\"\n\n    on_chunk_sent_mock = mock.create_autospec(on_chunk_sent, spec_set=True)\n    msg = http.StreamWriter(protocol, loop, on_chunk_sent=on_chunk_sent_mock)\n    chunk = b\"1\"\n    await msg.write(chunk)\n    assert on_chunk_sent_mock.called\n    assert on_chunk_sent_mock.call_args == mock.call(chunk)\n\n\nasync def test_write_eof_calls_callback(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    async def on_chunk_sent(chunk: bytes) -> None:\n        \"\"\"Mock signature\"\"\"\n\n    on_chunk_sent_mock = mock.create_autospec(on_chunk_sent, spec_set=True)\n    msg = http.StreamWriter(protocol, loop, on_chunk_sent=on_chunk_sent_mock)\n    chunk = b\"1\"\n    await msg.write_eof(chunk=chunk)\n    assert on_chunk_sent_mock.called\n    assert on_chunk_sent_mock.call_args == mock.call(chunk)\n\n\nasync def test_write_to_closing_transport(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n\n    await msg.write(b\"Before closing\")\n    transport.is_closing.return_value = True  # type: ignore[attr-defined]\n\n    with pytest.raises(ClientConnectionResetError):\n        await msg.write(b\"After closing\")\n\n\nasync def test_write_to_closed_transport(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that writing to a closed transport raises ClientConnectionResetError.\n\n    The StreamWriter checks to see if protocol.transport is None before\n    writing to the transport. If it is None, it raises ConnectionResetError.\n    \"\"\"\n    msg = http.StreamWriter(protocol, loop)\n\n    await msg.write(b\"Before transport close\")\n    protocol.transport = None\n\n    with pytest.raises(\n        ClientConnectionResetError, match=\"Cannot write to closing transport\"\n    ):\n        await msg.write(b\"After transport closed\")\n\n\nasync def test_drain(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    await msg.drain()\n    assert protocol._drain_helper.called  # type: ignore[attr-defined]\n\n\nasync def test_drain_no_transport(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg._protocol.transport = None\n    await msg.drain()\n    assert not protocol._drain_helper.called  # type: ignore[attr-defined]\n\n\nasync def test_write_headers_prevents_injection(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    status_line = \"HTTP/1.1 200 OK\"\n    wrong_headers = CIMultiDict({\"Set-Cookie: abc=123\\r\\nContent-Length\": \"256\"})\n    with pytest.raises(ValueError):\n        await msg.write_headers(status_line, wrong_headers)\n    wrong_headers = CIMultiDict({\"Content-Length\": \"256\\r\\nSet-Cookie: abc=123\"})\n    with pytest.raises(ValueError):\n        await msg.write_headers(status_line, wrong_headers)\n\n\nasync def test_set_eof_after_write_headers(\n    protocol: BaseProtocol,\n    transport: mock.Mock,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    status_line = \"HTTP/1.1 200 OK\"\n    good_headers = CIMultiDict({\"Set-Cookie\": \"abc=123\"})\n\n    # Write headers - should be buffered\n    await msg.write_headers(status_line, good_headers)\n    assert not transport.write.called  # Headers are buffered\n\n    # set_eof should send the buffered headers\n    msg.set_eof()\n    assert transport.write.called\n\n    # Subsequent write_eof should do nothing\n    transport.write.reset_mock()\n    await msg.write_eof()\n    assert not transport.write.called\n\n\nasync def test_write_headers_does_not_write_immediately(\n    protocol: BaseProtocol,\n    transport: mock.Mock,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    status_line = \"HTTP/1.1 200 OK\"\n    headers = CIMultiDict({\"Content-Type\": \"text/plain\"})\n\n    # write_headers should buffer, not write immediately\n    await msg.write_headers(status_line, headers)\n    assert not transport.write.called\n    assert not transport.writelines.called\n\n    # Headers should be sent when set_eof is called\n    msg.set_eof()\n    assert transport.write.called\n\n\nasync def test_write_headers_with_compression_coalescing(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    headers = CIMultiDict({\"Content-Encoding\": \"deflate\", \"Host\": \"example.com\"})\n\n    # Write headers - should be buffered\n    await msg.write_headers(\"POST /data HTTP/1.1\", headers)\n    assert len(buf) == 0\n\n    # Write compressed data via write_eof - should coalesce\n    await msg.write_eof(b\"Hello World\")\n\n    # Verify headers are present\n    assert b\"POST /data HTTP/1.1\\r\\n\" in buf\n    assert b\"Content-Encoding: deflate\\r\\n\" in buf\n\n    # Verify compressed data is present\n    # The data should contain headers + compressed payload\n    assert len(buf) > 50  # Should have headers + some compressed data\n\n\n@pytest.mark.parametrize(\n    \"char\",\n    [\n        \"\\n\",\n        \"\\r\",\n    ],\n)\ndef test_serialize_headers_raises_on_new_line_or_carriage_return(char: str) -> None:\n    \"\"\"Verify serialize_headers raises on cr or nl in the headers.\"\"\"\n    status_line = \"HTTP/1.1 200 OK\"\n    headers = CIMultiDict(\n        {\n            hdrs.CONTENT_TYPE: f\"text/plain{char}\",\n        }\n    )\n\n    with pytest.raises(\n        ValueError,\n        match=\"detected in headers\",\n    ):\n        _serialize_headers(status_line, headers)\n\n\ndef test_serialize_headers_raises_on_null_byte() -> None:\n    status_line = \"HTTP/1.1 200 OK\"\n    headers = CIMultiDict(\n        {\n            hdrs.CONTENT_TYPE: \"text/plain\\x00\",\n        }\n    )\n\n    with pytest.raises(\n        ValueError,\n        match=\"null byte detected in headers\",\n    ):\n        _serialize_headers(status_line, headers)\n\n\nasync def test_write_compressed_data_with_headers_coalescing(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that headers are coalesced with compressed data in write() method.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    headers = CIMultiDict({\"Content-Encoding\": \"deflate\", \"Host\": \"example.com\"})\n\n    # Write headers - should be buffered\n    await msg.write_headers(\"POST /data HTTP/1.1\", headers)\n    assert len(buf) == 0\n\n    # Write compressed data - should coalesce with headers\n    await msg.write(b\"Hello World\")\n\n    # Headers and compressed data should be written together\n    assert b\"POST /data HTTP/1.1\\r\\n\" in buf\n    assert b\"Content-Encoding: deflate\\r\\n\" in buf\n    assert len(buf) > 50  # Headers + compressed data\n\n\nasync def test_write_compressed_chunked_with_headers_coalescing(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test headers coalescing with compressed chunked data.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n    headers = CIMultiDict(\n        {\"Content-Encoding\": \"deflate\", \"Transfer-Encoding\": \"chunked\"}\n    )\n\n    # Write headers - should be buffered\n    await msg.write_headers(\"POST /data HTTP/1.1\", headers)\n    assert len(buf) == 0\n\n    # Write compressed chunked data - should coalesce\n    await msg.write(b\"Hello World\")\n\n    # Check headers are present\n    assert b\"POST /data HTTP/1.1\\r\\n\" in buf\n    assert b\"Transfer-Encoding: chunked\\r\\n\" in buf\n\n    # Should have chunk size marker for compressed data\n    output = buf.decode(\"latin-1\", errors=\"ignore\")\n    assert \"\\r\\n\" in output  # Should have chunk markers\n\n\nasync def test_write_multiple_compressed_chunks_after_headers_sent(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test multiple compressed writes after headers are already sent.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    headers = CIMultiDict({\"Content-Encoding\": \"deflate\"})\n\n    # Write headers and send them immediately by writing first chunk\n    await msg.write_headers(\"POST /data HTTP/1.1\", headers)\n    assert len(buf) == 0  # Headers buffered\n\n    # Write first chunk - this will send headers + compressed data\n    await msg.write(b\"First chunk of data that should compress\")\n    len_after_first = len(buf)\n    assert len_after_first > 0  # Headers + first chunk written\n\n    # Write second chunk and force flush via EOF\n    await msg.write(b\"Second chunk of data that should also compress well\")\n    await msg.write_eof()\n\n    # After EOF, all compressed data should be flushed\n    final_len = len(buf)\n    assert final_len > len_after_first\n\n\nasync def test_write_eof_empty_compressed_with_buffered_headers(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test write_eof with no data but compression enabled and buffered headers.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    headers = CIMultiDict({\"Content-Encoding\": \"deflate\"})\n\n    # Write headers - should be buffered\n    await msg.write_headers(\"GET /data HTTP/1.1\", headers)\n    assert len(buf) == 0\n\n    # Write EOF with no data - should still coalesce headers with compression flush\n    await msg.write_eof()\n\n    # Headers should be present\n    assert b\"GET /data HTTP/1.1\\r\\n\" in buf\n    assert b\"Content-Encoding: deflate\\r\\n\" in buf\n    # Should have compression flush data\n    assert len(buf) > 40\n\n\nasync def test_write_compressed_gzip_with_headers_coalescing(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test gzip compression with header coalescing.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"gzip\")\n    headers = CIMultiDict({\"Content-Encoding\": \"gzip\"})\n\n    # Write headers - should be buffered\n    await msg.write_headers(\"POST /data HTTP/1.1\", headers)\n    assert len(buf) == 0\n\n    # Write gzip compressed data via write_eof\n    await msg.write_eof(b\"Test gzip compression\")\n\n    # Verify coalescing happened\n    assert b\"POST /data HTTP/1.1\\r\\n\" in buf\n    assert b\"Content-Encoding: gzip\\r\\n\" in buf\n    # Gzip typically produces more overhead than deflate\n    assert len(buf) > 60\n\n\nasync def test_compression_with_content_length_constraint(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test compression respects content length constraints.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.length = 5  # Set small content length\n    headers = CIMultiDict({\"Content-Length\": \"5\"})\n\n    await msg.write_headers(\"POST /data HTTP/1.1\", headers)\n    # Write some initial data to trigger headers to be sent\n    await msg.write(b\"12345\")  # This matches our content length of 5\n    headers_and_first_chunk_len = len(buf)\n\n    # Try to write more data than content length allows\n    await msg.write(b\"This is a longer message\")\n\n    # The second write should not add any data since content length is exhausted\n    # After writing 5 bytes, length becomes 0, so additional writes are ignored\n    assert len(buf) == headers_and_first_chunk_len  # No additional data written\n\n\nasync def test_write_compressed_zero_length_chunk(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test writing empty chunk with compression.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n\n    await msg.write_headers(\"POST /data HTTP/1.1\", CIMultiDict())\n    # Force headers to be sent by writing something\n    await msg.write(b\"x\")  # Write something to trigger header send\n    buf.clear()\n\n    # Write empty chunk - compression may still produce output\n    await msg.write(b\"\")\n\n    # With compression, even empty input might produce small output\n    # due to compression state, but it should be minimal\n    assert len(buf) < 10  # Should be very small if anything\n\n\nasync def test_chunked_compressed_eof_coalescing(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test chunked compressed data with EOF marker coalescing.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_compression(\"deflate\")\n    msg.enable_chunking()\n    headers = CIMultiDict(\n        {\"Content-Encoding\": \"deflate\", \"Transfer-Encoding\": \"chunked\"}\n    )\n\n    # Buffer headers\n    await msg.write_headers(\"POST /data HTTP/1.1\", headers)\n    assert len(buf) == 0\n\n    # Write compressed chunked data with EOF\n    await msg.write_eof(b\"Final compressed chunk\")\n\n    # Should have headers\n    assert b\"POST /data HTTP/1.1\\r\\n\" in buf\n\n    # Should end with chunked EOF marker\n    assert buf.endswith(b\"0\\r\\n\\r\\n\")\n\n    # Should have chunk size in hex before the compressed data\n    output = buf\n    # Verify we have chunk markers - look for \\r\\n followed by hex digits\n    # The chunk size should be between the headers and the compressed data\n    assert b\"\\r\\n\\r\\n\" in output  # End of headers\n    # After headers, we should have a hex chunk size\n    headers_end = output.find(b\"\\r\\n\\r\\n\") + 4\n    chunk_data = output[headers_end:]\n    # Should start with hex digits followed by \\r\\n\n    assert (\n        chunk_data[:10]\n        .strip()\n        .decode(\"ascii\", errors=\"ignore\")\n        .replace(\"\\r\\n\", \"\")\n        .isalnum()\n    )\n\n\nasync def test_compression_different_strategies(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test compression with different strategies.\"\"\"\n    # Test with best speed strategy (default)\n    msg1 = http.StreamWriter(protocol, loop)\n    msg1.enable_compression(\"deflate\")  # Default strategy\n\n    await msg1.write_headers(\"POST /fast HTTP/1.1\", CIMultiDict())\n    await msg1.write_eof(b\"Test data for compression test data for compression\")\n\n    buf1_len = len(buf)\n\n    # Both should produce output\n    assert buf1_len > 0\n    # Headers should be present\n    assert b\"POST /fast HTTP/1.1\\r\\n\" in buf\n\n    # Since we can't easily test different compression strategies\n    # (the compressor initialization might not support strategy parameter),\n    # we just verify that compression works\n\n\nasync def test_chunked_headers_single_write_with_set_eof(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that set_eof combines headers and chunked EOF in single write.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_chunking()\n\n    # Write headers - should be buffered\n    headers = CIMultiDict({\"Transfer-Encoding\": \"chunked\", \"Host\": \"example.com\"})\n    await msg.write_headers(\"GET /test HTTP/1.1\", headers)\n    assert len(buf) == 0  # Headers not sent yet\n    assert not transport.writelines.called  # type: ignore[attr-defined]  # No writelines calls yet\n\n    # Call set_eof - should send headers + chunked EOF in single write call\n    msg.set_eof()\n\n    # Should have exactly one write call (since payload is small, writelines falls back to write)\n    assert transport.write.call_count == 1  # type: ignore[attr-defined]\n    assert transport.writelines.call_count == 0  # type: ignore[attr-defined]  # Not called for small payloads\n\n    # The write call should have the combined headers and chunked EOF marker\n    write_data = transport.write.call_args[0][0]  # type: ignore[attr-defined]\n    assert write_data.startswith(b\"GET /test HTTP/1.1\\r\\n\")\n    assert b\"Transfer-Encoding: chunked\\r\\n\" in write_data\n    assert write_data.endswith(b\"\\r\\n\\r\\n0\\r\\n\\r\\n\")  # Headers end + chunked EOF\n\n    # Verify final output\n    assert b\"GET /test HTTP/1.1\\r\\n\" in buf\n    assert b\"Transfer-Encoding: chunked\\r\\n\" in buf\n    assert buf.endswith(b\"0\\r\\n\\r\\n\")\n\n\nasync def test_send_headers_forces_header_write(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that send_headers() forces writing buffered headers.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n    headers = CIMultiDict({\"Content-Length\": \"10\", \"Host\": \"example.com\"})\n\n    # Write headers (should be buffered)\n    await msg.write_headers(\"GET /test HTTP/1.1\", headers)\n    assert len(buf) == 0  # Headers buffered\n\n    # Force send headers\n    msg.send_headers()\n\n    # Headers should now be written\n    assert b\"GET /test HTTP/1.1\\r\\n\" in buf\n    assert b\"Content-Length: 10\\r\\n\" in buf\n    assert b\"Host: example.com\\r\\n\" in buf\n\n    # Writing body should not resend headers\n    buf.clear()\n    await msg.write(b\"0123456789\")\n    assert b\"GET /test\" not in buf  # Headers not repeated\n    assert buf == b\"0123456789\"  # Just the body\n\n\nasync def test_send_headers_idempotent(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that send_headers() is idempotent and safe to call multiple times.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n    headers = CIMultiDict({\"Content-Length\": \"5\", \"Host\": \"example.com\"})\n\n    # Write headers (should be buffered)\n    await msg.write_headers(\"GET /test HTTP/1.1\", headers)\n    assert len(buf) == 0  # Headers buffered\n\n    # Force send headers\n    msg.send_headers()\n    headers_output = bytes(buf)\n\n    # Call send_headers again - should be no-op\n    msg.send_headers()\n    assert buf == headers_output  # No additional output\n\n    # Call send_headers after headers already sent - should be no-op\n    await msg.write(b\"hello\")\n    msg.send_headers()\n    assert buf[len(headers_output) :] == b\"hello\"  # Only body added\n\n\nasync def test_send_headers_no_buffered_headers(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that send_headers() is safe when no headers are buffered.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n\n    # Call send_headers without writing headers first\n    msg.send_headers()  # Should not crash\n    assert len(buf) == 0  # No output\n\n\nasync def test_write_drain_condition_with_small_buffer(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that drain is not called when buffer_size <= LIMIT.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n\n    # Write headers first\n    await msg.write_headers(\"GET /test HTTP/1.1\", CIMultiDict())\n    msg.send_headers()  # Send headers to start with clean state\n\n    # Reset buffer size manually since send_headers doesn't do it\n    msg.buffer_size = 0\n\n    # Reset drain helper mock\n    protocol._drain_helper.reset_mock()  # type: ignore[attr-defined]\n\n    # Write small amount of data with drain=True but buffer under limit\n    small_data = b\"x\" * 100  # Much less than LIMIT (2**16)\n    await msg.write(small_data, drain=True)\n\n    # Drain should NOT be called because buffer_size <= LIMIT\n    assert not protocol._drain_helper.called  # type: ignore[attr-defined]\n    assert msg.buffer_size == 100\n    assert small_data in buf\n\n\nasync def test_write_drain_condition_with_large_buffer(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that drain is called only when drain=True AND buffer_size > LIMIT.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n\n    # Write headers first\n    await msg.write_headers(\"GET /test HTTP/1.1\", CIMultiDict())\n    msg.send_headers()  # Send headers to start with clean state\n\n    # Reset buffer size manually since send_headers doesn't do it\n    msg.buffer_size = 0\n\n    # Reset drain helper mock\n    protocol._drain_helper.reset_mock()  # type: ignore[attr-defined]\n\n    # Write large amount of data with drain=True\n    large_data = b\"x\" * (2**16 + 1)  # Just over LIMIT\n    await msg.write(large_data, drain=True)\n\n    # Drain should be called because drain=True AND buffer_size > LIMIT\n    assert protocol._drain_helper.called  # type: ignore[attr-defined]\n    assert msg.buffer_size == 0  # Buffer reset after drain\n    assert large_data in buf\n\n\nasync def test_write_no_drain_with_large_buffer(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that drain is not called when drain=False even with large buffer.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n\n    # Write headers first\n    await msg.write_headers(\"GET /test HTTP/1.1\", CIMultiDict())\n    msg.send_headers()  # Send headers to start with clean state\n\n    # Reset buffer size manually since send_headers doesn't do it\n    msg.buffer_size = 0\n\n    # Reset drain helper mock\n    protocol._drain_helper.reset_mock()  # type: ignore[attr-defined]\n\n    # Write large amount of data with drain=False\n    large_data = b\"x\" * (2**16 + 1)  # Just over LIMIT\n    await msg.write(large_data, drain=False)\n\n    # Drain should NOT be called because drain=False\n    assert not protocol._drain_helper.called  # type: ignore[attr-defined]\n    assert msg.buffer_size == (2**16 + 1)  # Buffer not reset\n    assert large_data in buf\n\n\nasync def test_set_eof_idempotent(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test that set_eof() is idempotent and can be called multiple times safely.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n\n    # Test 1: Multiple set_eof calls with buffered headers\n    headers = CIMultiDict({\"Content-Length\": \"0\"})\n    await msg.write_headers(\"GET /test HTTP/1.1\", headers)\n\n    # First set_eof should send headers\n    msg.set_eof()\n    first_output = buf\n    assert b\"GET /test HTTP/1.1\\r\\n\" in first_output\n    assert b\"Content-Length: 0\\r\\n\" in first_output\n\n    # Second set_eof should be no-op\n    msg.set_eof()\n    assert bytes(buf) == first_output  # No additional output\n\n    # Third set_eof should also be no-op\n    msg.set_eof()\n    assert bytes(buf) == first_output  # Still no additional output\n\n    # Test 2: set_eof with chunked encoding\n    buf.clear()\n    msg2 = http.StreamWriter(protocol, loop)\n    msg2.enable_chunking()\n\n    headers2 = CIMultiDict({\"Transfer-Encoding\": \"chunked\"})\n    await msg2.write_headers(\"POST /data HTTP/1.1\", headers2)\n\n    # First set_eof should send headers + chunked EOF\n    msg2.set_eof()\n    chunked_output = buf\n    assert b\"POST /data HTTP/1.1\\r\\n\" in buf\n    assert b\"Transfer-Encoding: chunked\\r\\n\" in buf\n    assert b\"0\\r\\n\\r\\n\" in buf  # Chunked EOF marker\n\n    # Second set_eof should be no-op\n    msg2.set_eof()\n    assert buf == chunked_output  # No additional output\n\n    # Test 3: set_eof after headers already sent\n    buf.clear()\n    msg3 = http.StreamWriter(protocol, loop)\n\n    headers3 = CIMultiDict({\"Content-Length\": \"5\"})\n    await msg3.write_headers(\"PUT /update HTTP/1.1\", headers3)\n\n    # Send headers by writing some data\n    await msg3.write(b\"hello\")\n    headers_and_body = buf\n\n    # set_eof after headers sent should be no-op\n    msg3.set_eof()\n    assert buf == headers_and_body  # No additional output\n\n    # Another set_eof should still be no-op\n    msg3.set_eof()\n    assert buf == headers_and_body  # Still no additional output\n\n\nasync def test_non_chunked_write_empty_body(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: mock.Mock,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test non-chunked response with empty body.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n\n    # Non-chunked response with Content-Length: 0\n    headers = CIMultiDict({\"Content-Length\": \"0\"})\n    await msg.write_headers(\"GET /empty HTTP/1.1\", headers)\n\n    # Write empty body\n    await msg.write(b\"\")\n\n    # Check the output\n    assert b\"GET /empty HTTP/1.1\\r\\n\" in buf\n    assert b\"Content-Length: 0\\r\\n\" in buf\n\n\nasync def test_chunked_headers_sent_with_empty_chunk_not_eof(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test chunked encoding where headers are sent without data and not EOF.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_chunking()\n\n    headers = CIMultiDict({\"Transfer-Encoding\": \"chunked\"})\n    await msg.write_headers(\"POST /upload HTTP/1.1\", headers)\n\n    # This should trigger the else case in _send_headers_with_payload\n    # by having no chunk data and is_eof=False\n    await msg.write(b\"\")\n\n    # Headers should be sent alone\n    assert b\"POST /upload HTTP/1.1\\r\\n\" in buf\n    assert b\"Transfer-Encoding: chunked\\r\\n\" in buf\n    # Should not have any chunk markers yet\n    assert b\"0\\r\\n\" not in buf\n\n\nasync def test_chunked_set_eof_after_headers_sent(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test chunked encoding where set_eof is called after headers already sent.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_chunking()\n\n    headers = CIMultiDict({\"Transfer-Encoding\": \"chunked\"})\n    await msg.write_headers(\"POST /data HTTP/1.1\", headers)\n\n    # Send headers by writing some data\n    await msg.write(b\"test data\")\n    buf.clear()  # Clear buffer to check only what set_eof writes\n\n    # This should trigger writing chunked EOF when headers already sent\n    msg.set_eof()\n\n    # Should only have the chunked EOF marker\n    assert buf == b\"0\\r\\n\\r\\n\"\n\n\n@pytest.mark.usefixtures(\"enable_writelines\")\n@pytest.mark.usefixtures(\"force_writelines_small_payloads\")\nasync def test_write_eof_chunked_with_data_using_writelines(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test write_eof with chunked data that uses writelines (line 336).\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_chunking()\n\n    headers = CIMultiDict({\"Transfer-Encoding\": \"chunked\"})\n    await msg.write_headers(\"POST /data HTTP/1.1\", headers)\n\n    # Send headers first\n    await msg.write(b\"initial\")\n    transport.writelines.reset_mock()  # type: ignore[attr-defined]\n\n    # This should trigger writelines for final chunk with EOF\n    await msg.write_eof(b\"final chunk data\")\n\n    # Should have used writelines\n    assert transport.writelines.called  # type: ignore[attr-defined]\n    # Get the data from writelines call\n    writelines_data = transport.writelines.call_args[0][0]  # type: ignore[attr-defined]\n    combined = b\"\".join(writelines_data)\n\n    # Should have chunk size, data, and EOF marker\n    assert b\"10\\r\\n\" in combined  # hex for 16 (length of \"final chunk data\")\n    assert b\"final chunk data\" in combined\n    assert b\"0\\r\\n\\r\\n\" in combined\n\n\nasync def test_send_headers_with_payload_chunked_eof_no_data(\n    buf: bytearray,\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Test _send_headers_with_payload with chunked, is_eof=True but no chunk data.\"\"\"\n    msg = http.StreamWriter(protocol, loop)\n    msg.enable_chunking()\n\n    headers = CIMultiDict({\"Transfer-Encoding\": \"chunked\"})\n    await msg.write_headers(\"GET /test HTTP/1.1\", headers)\n\n    # This triggers the elif is_eof branch in _send_headers_with_payload\n    # by calling write_eof with empty chunk\n    await msg.write_eof(b\"\")\n\n    # Should have headers and chunked EOF marker together\n    assert b\"GET /test HTTP/1.1\\r\\n\" in buf\n    assert b\"Transfer-Encoding: chunked\\r\\n\" in buf\n    assert buf.endswith(b\"0\\r\\n\\r\\n\")\n"
  },
  {
    "path": "tests/test_imports.py",
    "content": "import os\nimport platform\nimport sys\nfrom pathlib import Path\n\nimport pytest\n\n\ndef test___all__(pytester: pytest.Pytester) -> None:\n    \"\"\"See https://github.com/aio-libs/aiohttp/issues/6197\"\"\"\n    pytester.makepyfile(test_a=\"\"\"\n            from aiohttp import *\n            assert 'GunicornWebWorker' in globals()\n        \"\"\")\n    result = pytester.runpytest(\"-vv\")\n    result.assert_outcomes(passed=0, errors=0)\n\n\ndef test_web___all__(pytester: pytest.Pytester) -> None:\n    pytester.makepyfile(test_b=\"\"\"\n            from aiohttp.web import *\n        \"\"\")\n    result = pytester.runpytest(\"-vv\")\n    result.assert_outcomes(passed=0, errors=0)\n\n\n@pytest.mark.internal\n@pytest.mark.dev_mode\n@pytest.mark.skipif(\n    not sys.platform.startswith(\"linux\") or platform.python_implementation() == \"PyPy\",\n    reason=\"Timing is more reliable on Linux\",\n)\ndef test_import_time(pytester: pytest.Pytester) -> None:\n    \"\"\"Check that importing aiohttp doesn't take too long.\n\n    Obviously, the time may vary on different machines and may need to be adjusted\n    from time to time, but this should provide an early warning if something is\n    added that significantly increases import time.\n\n    Runs 3 times and keeps the minimum time to reduce flakiness.\n    \"\"\"\n    IMPORT_TIME_THRESHOLD_MS = 300 if sys.version_info >= (3, 12) else 200\n    root = Path(__file__).parent.parent\n    old_path = os.environ.get(\"PYTHONPATH\")\n    os.environ[\"PYTHONPATH\"] = os.pathsep.join([str(root)] + sys.path)\n\n    best_time_ms = 1000\n    cmd = \"import timeit; print(int(timeit.timeit('import aiohttp', number=1) * 1000))\"\n    try:\n        for _ in range(3):\n            r = pytester.run(sys.executable, \"-We\", \"-c\", cmd)\n            assert not r.stderr.str(), r.stderr.str()\n            best_time_ms = min(best_time_ms, int(r.stdout.str()))\n    finally:\n        if old_path is None:\n            os.environ.pop(\"PYTHONPATH\")\n        else:  # pragma: no cover\n            os.environ[\"PYTHONPATH\"] = old_path\n\n    assert best_time_ms < IMPORT_TIME_THRESHOLD_MS\n"
  },
  {
    "path": "tests/test_leaks.py",
    "content": "import pathlib\nimport platform\nimport subprocess\nimport sys\n\nimport pytest\n\nIS_PYPY = platform.python_implementation() == \"PyPy\"\n\n\n@pytest.mark.skipif(IS_PYPY, reason=\"gc.DEBUG_LEAK not available on PyPy\")\n@pytest.mark.parametrize(\n    (\"script\", \"message\"),\n    [\n        (\n            # Test that ClientResponse is collected after server disconnects.\n            # https://github.com/aio-libs/aiohttp/issues/10535\n            \"check_for_client_response_leak.py\",\n            \"ClientResponse leaked\",\n        ),\n        (\n            # Test that Request object is collected when the handler raises.\n            # https://github.com/aio-libs/aiohttp/issues/10548\n            \"check_for_request_leak.py\",\n            \"Request leaked\",\n        ),\n    ],\n)\ndef test_leak(script: str, message: str) -> None:\n    \"\"\"Run isolated leak test script and check for leaks.\"\"\"\n    leak_test_script = pathlib.Path(__file__).parent.joinpath(\"isolated\", script)\n\n    with subprocess.Popen(\n        [sys.executable, \"-u\", str(leak_test_script)],\n        stdout=subprocess.PIPE,\n    ) as proc:\n        assert proc.wait() == 0, message\n"
  },
  {
    "path": "tests/test_loop.py",
    "content": "import asyncio\nimport platform\nimport threading\n\nimport pytest\n\nfrom aiohttp import web\nfrom aiohttp.test_utils import AioHTTPTestCase, loop_context\n\n\n@pytest.mark.skipif(\n    platform.system() == \"Windows\", reason=\"the test is not valid for Windows\"\n)\nasync def test_subprocess_co(loop: asyncio.AbstractEventLoop) -> None:\n    proc = await asyncio.create_subprocess_shell(\n        \"exit 0\",\n        stdin=asyncio.subprocess.DEVNULL,\n        stdout=asyncio.subprocess.DEVNULL,\n        stderr=asyncio.subprocess.DEVNULL,\n    )\n    await proc.wait()\n\n\nclass TestCase(AioHTTPTestCase):\n    on_startup_called: bool\n\n    async def get_application(self) -> web.Application:\n        app = web.Application()\n        app.on_startup.append(self.on_startup_hook)\n        return app\n\n    async def on_startup_hook(self, app: web.Application) -> None:\n        self.on_startup_called = True\n\n    async def test_on_startup_hook(self) -> None:\n        self.assertTrue(self.on_startup_called)\n\n\ndef test_default_loop(loop: asyncio.AbstractEventLoop) -> None:\n    assert asyncio.get_event_loop() is loop\n\n\ndef test_setup_loop_non_main_thread() -> None:\n    child_exc = None\n\n    def target() -> None:\n        try:\n            with loop_context() as loop:\n                assert asyncio.get_event_loop() is loop\n                loop.run_until_complete(test_subprocess_co(loop))\n        except Exception as exc:  # pragma: no cover\n            nonlocal child_exc\n            child_exc = exc\n\n    # Ensures setup_test_loop can be called by pytest-xdist in non-main thread.\n    t = threading.Thread(target=target)\n    t.start()\n    t.join()\n\n    assert child_exc is None\n"
  },
  {
    "path": "tests/test_multipart.py",
    "content": "import asyncio\nimport io\nimport json\nimport pathlib\nimport sys\nfrom types import TracebackType\nfrom unittest import mock\n\nimport pytest\nfrom multidict import CIMultiDict, CIMultiDictProxy\n\nimport aiohttp\nfrom aiohttp import payload\nfrom aiohttp.abc import AbstractStreamWriter\nfrom aiohttp.compression_utils import ZLibBackend\nfrom aiohttp.hdrs import (\n    CONTENT_DISPOSITION,\n    CONTENT_ENCODING,\n    CONTENT_TRANSFER_ENCODING,\n    CONTENT_TYPE,\n)\nfrom aiohttp.helpers import parse_mimetype\nfrom aiohttp.multipart import (\n    BodyPartReader,\n    BodyPartReaderPayload,\n    MultipartReader,\n    MultipartResponseWrapper,\n)\nfrom aiohttp.streams import StreamReader\n\nif sys.version_info >= (3, 11):\n    from typing import Self\nelse:\n    from typing import TypeVar\n\n    Self = TypeVar(\"Self\", bound=\"Stream\")\n\nBOUNDARY: bytes = b\"--:\"\n\n\n@pytest.fixture\ndef buf() -> bytearray:\n    return bytearray()\n\n\n@pytest.fixture\ndef stream(buf: bytearray) -> AbstractStreamWriter:\n    writer = mock.create_autospec(AbstractStreamWriter, instance=True, spec_set=True)\n\n    async def write(chunk: bytes) -> None:\n        buf.extend(chunk)\n\n    writer.write.side_effect = write\n    return writer  # type: ignore[no-any-return]\n\n\n@pytest.fixture\ndef buf2() -> bytearray:\n    return bytearray()\n\n\n@pytest.fixture\ndef stream2(buf2: bytearray) -> mock.Mock:\n    writer = mock.Mock()\n\n    async def write(chunk: bytes) -> None:\n        buf2.extend(chunk)\n\n    writer.write.side_effect = write\n    return writer\n\n\n@pytest.fixture\ndef writer() -> aiohttp.MultipartWriter:\n    return aiohttp.MultipartWriter(boundary=\":\")\n\n\nclass Stream(StreamReader):\n    def __init__(self, content: bytes) -> None:\n        self.content = io.BytesIO(content)\n\n    async def read(self, size: int | None = None) -> bytes:\n        return self.content.read(size)\n\n    def at_eof(self) -> bool:\n        return self.content.tell() == len(self.content.getbuffer())\n\n    async def readline(self, *, max_line_length: int | None = None) -> bytes:\n        return self.content.readline()\n\n    def unread_data(self, data: bytes) -> None:\n        self.content = io.BytesIO(data + self.content.read())\n\n    def __enter__(self) -> Self:\n        return self\n\n    def __exit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc_value: BaseException | None,\n        traceback: TracebackType | None,\n    ) -> None:\n        self.content.close()\n\n\nclass Response:\n    def __init__(self, headers: CIMultiDictProxy[str], content: Stream) -> None:\n        self.headers = headers\n        self.content = content\n\n\nclass StreamWithShortenRead(Stream):\n    def __init__(self, content: bytes) -> None:\n        self._first = True\n        super().__init__(content)\n\n    async def read(self, size: int | None = None) -> bytes:\n        if size is not None and self._first:\n            self._first = False\n            size = size // 2\n        return await super().read(size)\n\n\nclass TestMultipartResponseWrapper:\n    def test_at_eof(self) -> None:\n        m_resp = mock.create_autospec(aiohttp.ClientResponse, spec_set=True)\n        m_stream = mock.create_autospec(MultipartReader, spec_set=True)\n        wrapper = MultipartResponseWrapper(m_resp, m_stream)\n        wrapper.at_eof()\n        assert m_resp.content.at_eof.called\n\n    async def test_next(self) -> None:\n        m_resp = mock.create_autospec(aiohttp.ClientResponse, spec_set=True)\n        m_stream = mock.create_autospec(MultipartReader, spec_set=True)\n        wrapper = MultipartResponseWrapper(m_resp, m_stream)\n        m_stream.next.return_value = b\"\"\n        m_stream.at_eof.return_value = False\n        await wrapper.next()\n        assert m_stream.next.called\n\n    async def test_release(self) -> None:\n        m_resp = mock.create_autospec(aiohttp.ClientResponse, spec_set=True)\n        m_stream = mock.create_autospec(MultipartReader, spec_set=True)\n        wrapper = MultipartResponseWrapper(m_resp, m_stream)\n        await wrapper.release()\n        assert m_resp.release.called\n\n    async def test_release_when_stream_at_eof(self) -> None:\n        m_resp = mock.create_autospec(aiohttp.ClientResponse, spec_set=True)\n        m_stream = mock.create_autospec(MultipartReader, spec_set=True)\n        wrapper = MultipartResponseWrapper(m_resp, m_stream)\n        m_stream.next.return_value = b\"\"\n        m_stream.at_eof.return_value = True\n        await wrapper.next()\n        assert m_stream.next.called\n        assert m_resp.release.called\n\n\nclass TestPartReader:\n    async def test_next(self) -> None:\n        with Stream(b\"Hello, world!\\r\\n--:\") as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            result = await obj.next()\n            assert b\"Hello, world!\" == result\n            assert obj.at_eof()\n\n    async def test_next_next(self) -> None:\n        with Stream(b\"Hello, world!\\r\\n--:\") as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            result = await obj.next()\n            assert b\"Hello, world!\" == result\n            assert obj.at_eof()\n            result = await obj.next()\n            assert result is None\n\n    async def test_read(self) -> None:\n        with Stream(b\"Hello, world!\\r\\n--:\") as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            result = await obj.read()\n            assert b\"Hello, world!\" == result\n            assert obj.at_eof()\n\n    async def test_read_chunk_at_eof(self) -> None:\n        with Stream(b\"--:\") as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            obj._at_eof = True\n            result = await obj.read_chunk()\n        assert b\"\" == result\n\n    async def test_read_chunk_without_content_length(self) -> None:\n        with Stream(b\"Hello, world!\\r\\n--:\") as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            c1 = await obj.read_chunk(8)\n            c2 = await obj.read_chunk(8)\n            c3 = await obj.read_chunk(8)\n        assert c1 + c2 == b\"Hello, world!\"\n        assert c3 == b\"\"\n\n    async def test_read_incomplete_chunk(self) -> None:\n        with Stream(b\"\") as stream:\n\n            def prepare(data: bytes) -> bytes:\n                return data\n\n            with mock.patch.object(\n                stream,\n                \"read\",\n                side_effect=[\n                    prepare(b\"Hello, \"),\n                    prepare(b\"World\"),\n                    prepare(b\"!\\r\\n--:\"),\n                    prepare(b\"\"),\n                ],\n            ):\n                d = CIMultiDictProxy[str](CIMultiDict())\n                obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n                c1 = await obj.read_chunk(8)\n                assert c1 == b\"Hello, \"\n                c2 = await obj.read_chunk(8)\n                assert c2 == b\"World\"\n                c3 = await obj.read_chunk(8)\n                assert c3 == b\"!\"\n\n    async def test_read_all_at_once(self) -> None:\n        with Stream(b\"Hello, World!\\r\\n--:--\\r\\n\") as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            result = await obj.read_chunk()\n            assert b\"Hello, World!\" == result\n            result = await obj.read_chunk()\n            assert b\"\" == result\n            assert obj.at_eof()\n\n    async def test_read_incomplete_body_chunked(self) -> None:\n        with Stream(b\"Hello, World!\\r\\n-\") as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            result = b\"\"\n            with pytest.raises(ValueError):\n                for _ in range(4):  # pragma: no branch\n                    result += await obj.read_chunk(7)\n        assert b\"Hello, World!\\r\\n-\" == result\n\n    async def test_read_with_content_length_malformed_crlf(self) -> None:\n        # Content-Length is correct but data after content is not \\r\\n\n        content = b\"Hello\"\n        h = CIMultiDictProxy(CIMultiDict({\"CONTENT-LENGTH\": str(len(content))}))\n        # Malformed: \"XX\" instead of \"\\r\\n\" after content\n        with Stream(content + b\"XX--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            with pytest.raises(ValueError, match=\"malformed\"):\n                await obj.read()\n\n    async def test_read_boundary_with_incomplete_chunk(self) -> None:\n        with Stream(b\"\") as stream:\n\n            def prepare(data: bytes) -> bytes:\n                return data\n\n            with mock.patch.object(\n                stream,\n                \"read\",\n                side_effect=[\n                    prepare(b\"Hello, World\"),\n                    prepare(b\"!\\r\\n\"),\n                    prepare(b\"--:\"),\n                    prepare(b\"\"),\n                ],\n            ):\n                d = CIMultiDictProxy[str](CIMultiDict())\n                obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n                c1 = await obj.read_chunk(12)\n                assert c1 == b\"Hello, World\"\n                c2 = await obj.read_chunk(8)\n                assert c2 == b\"!\"\n                c3 = await obj.read_chunk(8)\n                assert c3 == b\"\"\n\n    async def test_multi_read_chunk(self) -> None:\n        with Stream(b\"Hello,\\r\\n--:\\r\\n\\r\\nworld!\\r\\n--:--\") as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            result = await obj.read_chunk(8)\n            assert b\"Hello,\" == result\n            result = await obj.read_chunk(8)\n            assert b\"\" == result\n            assert obj.at_eof()\n\n    async def test_read_chunk_properly_counts_read_bytes(self) -> None:\n        expected = b\".\" * 10\n        size = len(expected)\n        h = CIMultiDictProxy(CIMultiDict({\"CONTENT-LENGTH\": str(size)}))\n        with StreamWithShortenRead(expected + b\"\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = bytearray()\n            while True:\n                chunk = await obj.read_chunk()\n                if not chunk:\n                    break\n                result.extend(chunk)\n        assert size == len(result)\n        assert b\".\" * size == result\n        assert obj.at_eof()\n\n    async def test_read_does_not_read_boundary(self) -> None:\n        with Stream(b\"Hello, world!\\r\\n--:\") as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            result = await obj.read()\n            assert b\"Hello, world!\" == result\n            assert b\"--:\" == (await stream.read())\n\n    async def test_multiread(self) -> None:\n        with Stream(b\"Hello,\\r\\n--:\\r\\n\\r\\nworld!\\r\\n--:--\") as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            result = await obj.read()\n            assert b\"Hello,\" == result\n            result = await obj.read()\n            assert b\"\" == result\n            assert obj.at_eof()\n\n    async def test_read_multiline(self) -> None:\n        with Stream(b\"Hello\\n,\\r\\nworld!\\r\\n--:--\") as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            result = await obj.read()\n            assert b\"Hello\\n,\\r\\nworld!\" == result\n            result = await obj.read()\n            assert b\"\" == result\n            assert obj.at_eof()\n\n    async def test_read_respects_content_length(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({\"CONTENT-LENGTH\": \"100500\"}))\n        with Stream(b\".\" * 100500 + b\"\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = await obj.read()\n            assert b\".\" * 100500 == result\n            assert obj.at_eof()\n\n    async def test_read_with_content_encoding_gzip(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_ENCODING: \"gzip\"}))\n        with Stream(\n            b\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x00\\x03\\x0b\\xc9\\xccMU\"\n            b\"(\\xc9W\\x08J\\xcdI\\xacP\\x04\\x00$\\xfb\\x9eV\\x0e\\x00\\x00\\x00\"\n            b\"\\r\\n--:--\"\n        ) as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = await obj.read(decode=True)\n        assert b\"Time to Relax!\" == result\n\n    async def test_read_with_content_encoding_deflate(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_ENCODING: \"deflate\"}))\n        with Stream(b\"\\x0b\\xc9\\xccMU(\\xc9W\\x08J\\xcdI\\xacP\\x04\\x00\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = await obj.read(decode=True)\n        assert b\"Time to Relax!\" == result\n\n    async def test_read_with_content_encoding_identity(self) -> None:\n        thing = (\n            b\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x00\\x03\\x0b\\xc9\\xccMU\"\n            b\"(\\xc9W\\x08J\\xcdI\\xacP\\x04\\x00$\\xfb\\x9eV\\x0e\\x00\\x00\\x00\"\n            b\"\\r\\n\"\n        )\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_ENCODING: \"identity\"}))\n        with Stream(thing + b\"--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = await obj.read(decode=True)\n        assert thing[:-2] == result\n\n    async def test_read_with_content_encoding_unknown(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_ENCODING: \"snappy\"}))\n        with Stream(b\"\\x0e4Time to Relax!\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            with pytest.raises(RuntimeError):\n                await obj.read(decode=True)\n\n    async def test_read_with_content_transfer_encoding_base64(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_TRANSFER_ENCODING: \"base64\"}))\n        with Stream(b\"VGltZSB0byBSZWxheCE=\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = await obj.read(decode=True)\n        assert b\"Time to Relax!\" == result\n\n    async def test_decode_with_content_transfer_encoding_base64(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_TRANSFER_ENCODING: \"base64\"}))\n        with Stream(b\"VG\\r\\r\\nltZSB0byBSZ\\r\\nWxheCE=\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = b\"\"\n            while not obj.at_eof():\n                chunk = await obj.read_chunk(size=6)\n                result += obj.decode(chunk)\n        assert b\"Time to Relax!\" == result\n\n    async def test_decode_iter_with_content_transfer_encoding_base64(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_TRANSFER_ENCODING: \"base64\"}))\n        with Stream(b\"VG\\r\\r\\nltZSB0byBSZ\\r\\nWxheCE=\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = b\"\"\n            while not obj.at_eof():\n                chunk = await obj.read_chunk(size=6)\n                async for decoded_chunk in obj.decode_iter(chunk):\n                    result += decoded_chunk\n        assert b\"Time to Relax!\" == result\n\n    async def test_decode_with_content_encoding_deflate(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_ENCODING: \"deflate\"}))\n        data = b\"\\x0b\\xc9\\xccMU(\\xc9W\\x08J\\xcdI\\xacP\\x04\\x00\"\n        with Stream(data + b\"\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            chunk = await obj.read_chunk(size=len(data))\n            result = obj.decode(chunk)\n        assert b\"Time to Relax!\" == result\n\n    async def test_decode_with_content_encoding_identity(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_ENCODING: \"identity\"}))\n        data = b\"Time to Relax!\"\n        with Stream(data + b\"\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            chunk = await obj.read_chunk(size=len(data))\n            result = obj.decode(chunk)\n        assert data == result\n\n    async def test_decode_with_content_encoding_unknown(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_ENCODING: \"snappy\"}))\n        data = b\"Time to Relax!\"\n        with Stream(data + b\"\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            chunk = await obj.read_chunk(size=len(data))\n            with pytest.raises(RuntimeError, match=\"unknown content encoding\"):\n                obj.decode(chunk)\n\n    async def test_read_with_content_transfer_encoding_quoted_printable(self) -> None:\n        h = CIMultiDictProxy(\n            CIMultiDict({CONTENT_TRANSFER_ENCODING: \"quoted-printable\"})\n        )\n        with Stream(\n            b\"=D0=9F=D1=80=D0=B8=D0=B2=D0=B5=D1=82, =D0=BC=D0=B8=D1=80!\\r\\n--:--\"\n        ) as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = await obj.read(decode=True)\n        expected = (\n            b\"\\xd0\\x9f\\xd1\\x80\\xd0\\xb8\\xd0\\xb2\\xd0\\xb5\\xd1\\x82,\"\n            b\" \\xd0\\xbc\\xd0\\xb8\\xd1\\x80!\"\n        )\n        assert result == expected\n\n    @pytest.mark.parametrize(\"encoding\", (\"binary\", \"8bit\", \"7bit\"))\n    async def test_read_with_content_transfer_encoding_binary(\n        self, encoding: str\n    ) -> None:\n        data = (\n            b\"\\xd0\\x9f\\xd1\\x80\\xd0\\xb8\\xd0\\xb2\\xd0\\xb5\\xd1\\x82,\"\n            b\" \\xd0\\xbc\\xd0\\xb8\\xd1\\x80!\"\n        )\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_TRANSFER_ENCODING: encoding}))\n        with Stream(data + b\"\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = await obj.read(decode=True)\n        assert data == result\n\n    async def test_read_with_content_transfer_encoding_unknown(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_TRANSFER_ENCODING: \"unknown\"}))\n        with Stream(b\"\\x0e4Time to Relax!\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            with pytest.raises(RuntimeError):\n                await obj.read(decode=True)\n\n    async def test_read_text(self) -> None:\n        with Stream(b\"Hello, world!\\r\\n--:--\") as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            result = await obj.text()\n        assert \"Hello, world!\" == result\n\n    async def test_read_text_default_encoding(self) -> None:\n        with Stream(\"Привет, Мир!\\r\\n--:--\".encode()) as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            result = await obj.text()\n        assert \"Привет, Мир!\" == result\n\n    async def test_read_text_encoding(self) -> None:\n        with Stream(\"Привет, Мир!\\r\\n--:--\".encode(\"cp1251\")) as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            result = await obj.text(encoding=\"cp1251\")\n        assert \"Привет, Мир!\" == result\n\n    async def test_read_text_guess_encoding(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_TYPE: \"text/plain;charset=cp1251\"}))\n        with Stream(\"Привет, Мир!\\r\\n--:--\".encode(\"cp1251\")) as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = await obj.text()\n        assert \"Привет, Мир!\" == result\n\n    async def test_read_text_compressed(self) -> None:\n        h = CIMultiDictProxy(\n            CIMultiDict({CONTENT_ENCODING: \"deflate\", CONTENT_TYPE: \"text/plain\"})\n        )\n        with Stream(b\"\\x0b\\xc9\\xccMU(\\xc9W\\x08J\\xcdI\\xacP\\x04\\x00\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = await obj.text()\n        assert \"Time to Relax!\" == result\n\n    async def test_read_text_while_closed(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_TYPE: \"text/plain\"}))\n        with Stream(b\"\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            obj._at_eof = True\n            result = await obj.text()\n        assert \"\" == result\n\n    async def test_read_json(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_TYPE: \"application/json\"}))\n        with Stream(b'{\"test\": \"passed\"}\\r\\n--:--') as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = await obj.json()\n        assert {\"test\": \"passed\"} == result\n\n    async def test_read_json_encoding(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_TYPE: \"application/json\"}))\n        with Stream('{\"тест\": \"пассед\"}\\r\\n--:--'.encode(\"cp1251\")) as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = await obj.json(encoding=\"cp1251\")\n        assert {\"тест\": \"пассед\"} == result\n\n    async def test_read_json_guess_encoding(self) -> None:\n        h = CIMultiDictProxy(\n            CIMultiDict({CONTENT_TYPE: \"application/json; charset=cp1251\"})\n        )\n        with Stream('{\"тест\": \"пассед\"}\\r\\n--:--'.encode(\"cp1251\")) as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = await obj.json()\n        assert {\"тест\": \"пассед\"} == result\n\n    async def test_read_json_compressed(self) -> None:\n        h = CIMultiDictProxy(\n            CIMultiDict({CONTENT_ENCODING: \"deflate\", CONTENT_TYPE: \"application/json\"})\n        )\n        with Stream(b\"\\xabV*I-.Q\\xb2RP*H,.NMQ\\xaa\\x05\\x00\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = await obj.json()\n        assert {\"test\": \"passed\"} == result\n\n    async def test_read_json_while_closed(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_TYPE: \"application/json\"}))\n        with Stream(b\"\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            obj._at_eof = True\n            result = await obj.json()\n        assert result is None\n\n    async def test_read_form(self) -> None:\n        h = CIMultiDictProxy(\n            CIMultiDict({CONTENT_TYPE: \"application/x-www-form-urlencoded\"})\n        )\n        with Stream(b\"foo=bar&foo=baz&boo=\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = await obj.form()\n        assert [(\"foo\", \"bar\"), (\"foo\", \"baz\"), (\"boo\", \"\")] == result\n\n    async def test_read_form_invalid_utf8(self) -> None:\n        h = CIMultiDictProxy(\n            CIMultiDict({CONTENT_TYPE: \"application/x-www-form-urlencoded\"})\n        )\n        with Stream(b\"\\xff\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            with pytest.raises(\n                ValueError, match=\"data cannot be decoded with utf-8 encoding\"\n            ):\n                await obj.form()\n\n    async def test_read_form_encoding(self) -> None:\n        h = CIMultiDictProxy(\n            CIMultiDict({CONTENT_TYPE: \"application/x-www-form-urlencoded\"})\n        )\n        with Stream(\"foo=bar&foo=baz&boo=\\r\\n--:--\".encode(\"cp1251\")) as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = await obj.form(encoding=\"cp1251\")\n        assert [(\"foo\", \"bar\"), (\"foo\", \"baz\"), (\"boo\", \"\")] == result\n\n    async def test_read_form_guess_encoding(self) -> None:\n        h = CIMultiDictProxy(\n            CIMultiDict(\n                {CONTENT_TYPE: \"application/x-www-form-urlencoded; charset=utf-8\"}\n            )\n        )\n        with Stream(b\"foo=bar&foo=baz&boo=\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            result = await obj.form()\n        assert [(\"foo\", \"bar\"), (\"foo\", \"baz\"), (\"boo\", \"\")] == result\n\n    async def test_read_form_while_closed(self) -> None:\n        h = CIMultiDictProxy(\n            CIMultiDict({CONTENT_TYPE: \"application/x-www-form-urlencoded\"})\n        )\n        with Stream(b\"\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            obj._at_eof = True\n            result = await obj.form()\n        assert not result\n\n    async def test_readline(self) -> None:\n        with Stream(b\"Hello\\n,\\r\\nworld!\\r\\n--:--\") as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            result = await obj.readline()\n            assert b\"Hello\\n\" == result\n            result = await obj.readline()\n            assert b\",\\r\\n\" == result\n            result = await obj.readline()\n            assert b\"world!\" == result\n            result = await obj.readline()\n            assert b\"\" == result\n            assert obj.at_eof()\n\n    async def test_release(self) -> None:\n        with Stream(b\"Hello,\\r\\n--:\\r\\n\\r\\nworld!\\r\\n--:--\") as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            await obj.release()\n            assert obj.at_eof()\n            assert b\"--:\\r\\n\\r\\nworld!\\r\\n--:--\" == stream.content.read()\n\n    async def test_release_respects_content_length(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({\"CONTENT-LENGTH\": \"100500\"}))\n        with Stream(b\".\" * 100500 + b\"\\r\\n--:--\") as stream:\n            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)\n            await obj.release()\n            assert obj.at_eof()\n\n    async def test_release_release(self) -> None:\n        with Stream(b\"Hello,\\r\\n--:\\r\\n\\r\\nworld!\\r\\n--:--\") as stream:\n            d = CIMultiDictProxy[str](CIMultiDict())\n            obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n            await obj.release()\n            await obj.release()\n            assert b\"--:\\r\\n\\r\\nworld!\\r\\n--:--\" == stream.content.read()\n\n    async def test_filename(self) -> None:\n        h = CIMultiDictProxy(\n            CIMultiDict({CONTENT_DISPOSITION: \"attachment; filename=foo.html\"})\n        )\n        part = aiohttp.BodyPartReader(BOUNDARY, h, mock.Mock())\n        assert \"foo.html\" == part.filename\n\n    async def test_reading_long_part(self) -> None:\n        size = 2 * 2**16\n        protocol = mock.Mock(_reading_paused=False)\n        stream = StreamReader(protocol, 2**16, loop=asyncio.get_event_loop())\n        stream.feed_data(b\"0\" * size + b\"\\r\\n--:--\")\n        stream.feed_eof()\n        d = CIMultiDictProxy[str](CIMultiDict())\n        obj = aiohttp.BodyPartReader(BOUNDARY, d, stream)\n        data = await obj.read()\n        assert len(data) == size\n\n\nclass TestMultipartReader:\n    def test_from_response(self) -> None:\n        h = CIMultiDictProxy(\n            CIMultiDict({CONTENT_TYPE: 'multipart/related;boundary=\":\"'})\n        )\n        with Stream(b\"--:\\r\\n\\r\\nhello\\r\\n--:--\") as stream:\n            resp = Response(h, stream)\n            res = aiohttp.MultipartReader.from_response(resp)  # type: ignore[arg-type]\n        assert isinstance(res, MultipartResponseWrapper)\n        assert isinstance(res.stream, aiohttp.MultipartReader)\n\n    def test_bad_boundary(self) -> None:\n        h = CIMultiDictProxy(\n            CIMultiDict({CONTENT_TYPE: \"multipart/related;boundary=\" + \"a\" * 80})\n        )\n        with Stream(b\"\") as stream:\n            resp = Response(h, stream)\n            with pytest.raises(ValueError):\n                aiohttp.MultipartReader.from_response(resp)  # type: ignore[arg-type]\n\n    def test_dispatch(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_TYPE: \"text/plain\"}))\n        with Stream(b\"--:\\r\\n\\r\\necho\\r\\n--:--\") as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/related;boundary=\":\"'},\n                stream,\n            )\n            res = reader._get_part_reader(h)\n        assert isinstance(res, reader.part_reader_cls)\n\n    def test_dispatch_bodypart(self) -> None:\n        h = CIMultiDictProxy(CIMultiDict({CONTENT_TYPE: \"text/plain\"}))\n        with Stream(b\"--:\\r\\n\\r\\necho\\r\\n--:--\") as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/related;boundary=\":\"'},\n                stream,\n            )\n            res = reader._get_part_reader(h)\n        assert isinstance(res, reader.part_reader_cls)\n\n    def test_dispatch_multipart(self) -> None:\n        h = CIMultiDictProxy(\n            CIMultiDict({CONTENT_TYPE: \"multipart/related;boundary=--:--\"})\n        )\n        with Stream(\n            b\"----:--\\r\\n\"\n            b\"\\r\\n\"\n            b\"test\\r\\n\"\n            b\"----:--\\r\\n\"\n            b\"\\r\\n\"\n            b\"passed\\r\\n\"\n            b\"----:----\\r\\n\"\n            b\"--:--\"\n        ) as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/related;boundary=\":\"'},\n                stream,\n            )\n            res = reader._get_part_reader(h)\n        assert isinstance(res, reader.__class__)\n\n    def test_dispatch_custom_multipart_reader(self) -> None:\n        class CustomReader(aiohttp.MultipartReader):\n            pass\n\n        h = CIMultiDictProxy(\n            CIMultiDict({CONTENT_TYPE: \"multipart/related;boundary=--:--\"})\n        )\n        with Stream(\n            b\"----:--\\r\\n\"\n            b\"\\r\\n\"\n            b\"test\\r\\n\"\n            b\"----:--\\r\\n\"\n            b\"\\r\\n\"\n            b\"passed\\r\\n\"\n            b\"----:----\\r\\n\"\n            b\"--:--\"\n        ) as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/related;boundary=\":\"'},\n                stream,\n            )\n            reader.multipart_reader_cls = CustomReader\n            res = reader._get_part_reader(h)\n        assert isinstance(res, CustomReader)\n\n    async def test_emit_next(self) -> None:\n        h = CIMultiDictProxy(\n            CIMultiDict({CONTENT_TYPE: 'multipart/related;boundary=\":\"'})\n        )\n        with Stream(b\"--:\\r\\n\\r\\necho\\r\\n--:--\") as stream:\n            reader = aiohttp.MultipartReader(h, stream)\n            res = await reader.next()\n        assert isinstance(res, reader.part_reader_cls)\n\n    async def test_invalid_boundary(self) -> None:\n        with Stream(b\"---:\\r\\n\\r\\necho\\r\\n---:--\") as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/related;boundary=\":\"'},\n                stream,\n            )\n            with pytest.raises(ValueError):\n                await reader.next()\n\n    async def test_read_boundary_across_chunks(self) -> None:\n        class SplitBoundaryStream(StreamReader):\n            def __init__(self) -> None:\n                self.content = [\n                    b\"--foobar\\r\\n\\r\\n\",\n                    b\"Hello,\\r\\n-\",\n                    b\"-fo\",\n                    b\"ob\",\n                    b\"ar\\r\\n\",\n                    b\"\\r\\nwor\",\n                    b\"ld!\",\n                    b\"\\r\\n--f\",\n                    b\"oobar--\",\n                ]\n\n            async def read(self, size: int | None = None) -> bytes:\n                chunk = self.content.pop(0)\n                assert size is not None and len(chunk) <= size\n                return chunk\n\n            def at_eof(self) -> bool:\n                return not self.content\n\n            async def readline(self, *, max_line_length: int | None = None) -> bytes:\n                line = b\"\"\n                while self.content and b\"\\n\" not in line:\n                    line += self.content.pop(0)\n                line, *extra = line.split(b\"\\n\", maxsplit=1)\n                if extra and extra[0]:\n                    self.content.insert(0, extra[0])\n                return line + b\"\\n\"\n\n            def unread_data(self, data: bytes) -> None:\n                if self.content:\n                    self.content[0] = data + self.content[0]\n                else:\n                    self.content.append(data)\n\n        stream = SplitBoundaryStream()\n        reader = aiohttp.MultipartReader(\n            {CONTENT_TYPE: 'multipart/related;boundary=\"foobar\"'}, stream\n        )\n        part = await anext(reader)\n        assert isinstance(part, BodyPartReader)\n        result = await part.read_chunk(10)\n        assert result == b\"Hello,\"\n        result = await part.read_chunk(10)\n        assert result == b\"\"\n        assert part.at_eof()\n\n        part = await anext(reader)\n        assert isinstance(part, BodyPartReader)\n        result = await part.read_chunk(10)\n        assert result == b\"world!\"\n        result = await part.read_chunk(10)\n        assert result == b\"\"\n        assert part.at_eof()\n\n        with pytest.raises(StopAsyncIteration):\n            await anext(reader)\n\n    async def test_release(self) -> None:\n        with Stream(\n            b\"--:\\r\\n\"\n            b\"Content-Type: multipart/related;boundary=--:--\\r\\n\"\n            b\"\\r\\n\"\n            b\"----:--\\r\\n\"\n            b\"\\r\\n\"\n            b\"test\\r\\n\"\n            b\"----:--\\r\\n\"\n            b\"\\r\\n\"\n            b\"passed\\r\\n\"\n            b\"----:----\\r\\n\"\n            b\"\\r\\n\"\n            b\"--:--\"\n        ) as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/mixed;boundary=\":\"'},\n                stream,\n            )\n            await reader.release()\n            assert reader.at_eof()\n\n    async def test_release_release(self) -> None:\n        with Stream(b\"--:\\r\\n\\r\\necho\\r\\n--:--\") as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/related;boundary=\":\"'},\n                stream,\n            )\n            await reader.release()\n            assert reader.at_eof()\n            await reader.release()\n            assert reader.at_eof()\n\n    async def test_release_next(self) -> None:\n        with Stream(b\"--:\\r\\n\\r\\necho\\r\\n--:--\") as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/related;boundary=\":\"'},\n                stream,\n            )\n            await reader.release()\n            assert reader.at_eof()\n            res = await reader.next()\n            assert res is None\n\n    async def test_second_next_releases_previous_object(self) -> None:\n        with Stream(b\"--:\\r\\n\\r\\ntest\\r\\n--:\\r\\n\\r\\npassed\\r\\n--:--\") as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/related;boundary=\":\"'},\n                stream,\n            )\n            first = await reader.next()\n            assert isinstance(first, aiohttp.BodyPartReader)\n            second = await reader.next()\n            assert second is not None\n            assert first.at_eof()\n            assert not second.at_eof()\n\n    async def test_release_without_read_the_last_object(self) -> None:\n        with Stream(b\"--:\\r\\n\\r\\ntest\\r\\n--:\\r\\n\\r\\npassed\\r\\n--:--\") as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/related;boundary=\":\"'},\n                stream,\n            )\n            first = await reader.next()\n            second = await reader.next()\n            third = await reader.next()\n\n            assert first is not None\n            assert second is not None\n            assert first.at_eof()\n            assert second.at_eof()\n            assert second.at_eof()\n            assert third is None\n\n    async def test_read_chunk_by_length_doesnt_break_reader(self) -> None:\n        with Stream(\n            b\"--:\\r\\n\"\n            b\"Content-Length: 4\\r\\n\\r\\n\"\n            b\"test\"\n            b\"\\r\\n--:\\r\\n\"\n            b\"Content-Length: 6\\r\\n\\r\\n\"\n            b\"passed\"\n            b\"\\r\\n--:--\"\n        ) as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/related;boundary=\":\"'},\n                stream,\n            )\n            body_parts = []\n            while True:\n                read_part = b\"\"\n                part = await reader.next()\n                if part is None:\n                    break\n                assert isinstance(part, BodyPartReader)\n                while not part.at_eof():\n                    read_part += await part.read_chunk(3)\n                body_parts.append(read_part)\n\n        assert body_parts == [b\"test\", b\"passed\"]\n\n    async def test_read_chunk_from_stream_doesnt_break_reader(self) -> None:\n        with Stream(\n            b\"--:\\r\\n\"\n            b\"\\r\\n\"\n            b\"chunk\"\n            b\"\\r\\n--:\\r\\n\"\n            b\"\\r\\n\"\n            b\"two_chunks\"\n            b\"\\r\\n--:--\"\n        ) as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/related;boundary=\":\"'},\n                stream,\n            )\n            body_parts = []\n            while True:\n                read_part = b\"\"\n                part = await reader.next()\n                if part is None:\n                    break\n                assert isinstance(part, BodyPartReader)\n                while not part.at_eof():\n                    chunk = await part.read_chunk(5)\n                    assert chunk\n                    read_part += chunk\n                body_parts.append(read_part)\n\n        assert body_parts == [b\"chunk\", b\"two_chunks\"]\n\n    async def test_reading_skips_prelude(self) -> None:\n        with Stream(\n            b\"Multi-part data is not supported.\\r\\n\"\n            b\"\\r\\n\"\n            b\"--:\\r\\n\"\n            b\"\\r\\n\"\n            b\"test\\r\\n\"\n            b\"--:\\r\\n\"\n            b\"\\r\\n\"\n            b\"passed\\r\\n\"\n            b\"--:--\"\n        ) as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/related;boundary=\":\"'},\n                stream,\n            )\n            first = await reader.next()\n            assert isinstance(first, aiohttp.BodyPartReader)\n            second = await reader.next()\n\n            assert isinstance(second, BodyPartReader)\n            assert first.at_eof()\n            assert not second.at_eof()\n\n    async def test_read_empty_body_part(self) -> None:\n        with Stream(b\"--:\\r\\n\\r\\n--:--\") as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/related;boundary=\":\"'},\n                stream,\n            )\n            body_parts = []\n            async for part in reader:\n                assert isinstance(part, BodyPartReader)\n                body_parts.append(await part.read())\n\n        assert body_parts == [b\"\"]\n\n    async def test_read_body_part_headers_only(self) -> None:\n        with Stream(b\"--:\\r\\nContent-Type: text/plain\\r\\n\\r\\n--:--\") as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/related;boundary=\":\"'},\n                stream,\n            )\n            body_parts = []\n            async for part in reader:\n                assert isinstance(part, BodyPartReader)\n                assert \"Content-Type\" in part.headers\n                body_parts.append(await part.read())\n\n        assert body_parts == [b\"\"]\n\n    async def test_read_form_default_encoding(self) -> None:\n        with Stream(\n            b\"--:\\r\\n\"\n            b'Content-Disposition: form-data; name=\"_charset_\"\\r\\n\\r\\n'\n            b\"ascii\"\n            b\"\\r\\n\"\n            b\"--:\\r\\n\"\n            b'Content-Disposition: form-data; name=\"field1\"\\r\\n\\r\\n'\n            b\"foo\"\n            b\"\\r\\n\"\n            b\"--:\\r\\n\"\n            b\"Content-Type: text/plain;charset=UTF-8\\r\\n\"\n            b'Content-Disposition: form-data; name=\"field2\"\\r\\n\\r\\n'\n            b\"foo\"\n            b\"\\r\\n\"\n            b\"--:\\r\\n\"\n            b'Content-Disposition: form-data; name=\"field3\"\\r\\n\\r\\n'\n            b\"foo\"\n            b\"\\r\\n\"\n        ) as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/form-data;boundary=\":\"'},\n                stream,\n            )\n            field1 = await reader.next()\n            assert isinstance(field1, BodyPartReader)\n            assert field1.name == \"field1\"\n            assert field1.get_charset(\"default\") == \"ascii\"\n            field2 = await reader.next()\n            assert isinstance(field2, BodyPartReader)\n            assert field2.name == \"field2\"\n            assert field2.get_charset(\"default\") == \"UTF-8\"\n            field3 = await reader.next()\n            assert isinstance(field3, BodyPartReader)\n            assert field3.name == \"field3\"\n            assert field3.get_charset(\"default\") == \"ascii\"\n\n    async def test_read_form_invalid_default_encoding(self) -> None:\n        with Stream(\n            b\"--:\\r\\n\"\n            b'Content-Disposition: form-data; name=\"_charset_\"\\r\\n\\r\\n'\n            b\"this-value-is-too-long-to-be-a-charset\"\n            b\"\\r\\n\"\n            b\"--:\\r\\n\"\n            b'Content-Disposition: form-data; name=\"field1\"\\r\\n\\r\\n'\n            b\"foo\"\n            b\"\\r\\n\"\n        ) as stream:\n            reader = aiohttp.MultipartReader(\n                {CONTENT_TYPE: 'multipart/form-data;boundary=\":\"'},\n                stream,\n            )\n            with pytest.raises(RuntimeError, match=\"Invalid default charset\"):\n                await reader.next()\n\n\nasync def test_writer(writer: aiohttp.MultipartWriter) -> None:\n    assert writer.size == 7\n    assert writer.boundary == \":\"\n\n\nasync def test_writer_serialize_io_chunk(\n    buf: bytearray, stream: AbstractStreamWriter, writer: aiohttp.MultipartWriter\n) -> None:\n    with io.BytesIO(b\"foobarbaz\") as file_handle:\n        writer.append(file_handle)\n        await writer.write(stream)\n    assert (\n        buf == b\"--:\\r\\nContent-Type: application/octet-stream\"\n        b\"\\r\\nContent-Length: 9\\r\\n\\r\\nfoobarbaz\\r\\n--:--\\r\\n\"\n    )\n\n\nasync def test_writer_serialize_json(\n    buf: bytearray, stream: AbstractStreamWriter, writer: aiohttp.MultipartWriter\n) -> None:\n    writer.append_json({\"привет\": \"мир\"})\n    await writer.write(stream)\n    assert (\n        b'{\"\\\\u043f\\\\u0440\\\\u0438\\\\u0432\\\\u0435\\\\u0442\":'\n        b' \"\\\\u043c\\\\u0438\\\\u0440\"}' in buf\n    )\n\n\nasync def test_writer_serialize_form(\n    buf: bytearray, stream: AbstractStreamWriter, writer: aiohttp.MultipartWriter\n) -> None:\n    data = [(\"foo\", \"bar\"), (\"foo\", \"baz\"), (\"boo\", \"zoo\")]\n    writer.append_form(data)\n    await writer.write(stream)\n\n    assert b\"foo=bar&foo=baz&boo=zoo\" in buf\n\n\nasync def test_writer_serialize_form_dict(\n    buf: bytearray, stream: AbstractStreamWriter, writer: aiohttp.MultipartWriter\n) -> None:\n    data = {\"hello\": \"мир\"}\n    writer.append_form(data)\n    await writer.write(stream)\n\n    assert b\"hello=%D0%BC%D0%B8%D1%80\" in buf\n\n\nasync def test_writer_write(\n    buf: bytearray, stream: AbstractStreamWriter, writer: aiohttp.MultipartWriter\n) -> None:\n    writer.append(\"foo-bar-baz\")\n    writer.append_json({\"test\": \"passed\"})\n    writer.append_form({\"test\": \"passed\"})\n    writer.append_form([(\"one\", \"1\"), (\"two\", \"2\")])\n\n    sub_multipart = aiohttp.MultipartWriter(boundary=\"::\")\n    sub_multipart.append(\"nested content\")\n    sub_multipart.headers[\"X-CUSTOM\"] = \"test\"\n    writer.append(sub_multipart)\n    await writer.write(stream)\n\n    assert (\n        b\"--:\\r\\n\"\n        b\"Content-Type: text/plain; charset=utf-8\\r\\n\"\n        b\"Content-Length: 11\\r\\n\\r\\n\"\n        b\"foo-bar-baz\"\n        b\"\\r\\n\"\n        b\"--:\\r\\n\"\n        b\"Content-Type: application/json\\r\\n\"\n        b\"Content-Length: 18\\r\\n\\r\\n\"\n        b'{\"test\": \"passed\"}'\n        b\"\\r\\n\"\n        b\"--:\\r\\n\"\n        b\"Content-Type: application/x-www-form-urlencoded\\r\\n\"\n        b\"Content-Length: 11\\r\\n\\r\\n\"\n        b\"test=passed\"\n        b\"\\r\\n\"\n        b\"--:\\r\\n\"\n        b\"Content-Type: application/x-www-form-urlencoded\\r\\n\"\n        b\"Content-Length: 11\\r\\n\\r\\n\"\n        b\"one=1&two=2\"\n        b\"\\r\\n\"\n        b\"--:\\r\\n\"\n        b'Content-Type: multipart/mixed; boundary=\"::\"\\r\\n'\n        b\"X-CUSTOM: test\\r\\nContent-Length: 93\\r\\n\\r\\n\"\n        b\"--::\\r\\n\"\n        b\"Content-Type: text/plain; charset=utf-8\\r\\n\"\n        b\"Content-Length: 14\\r\\n\\r\\n\"\n        b\"nested content\\r\\n\"\n        b\"--::--\\r\\n\"\n        b\"\\r\\n\"\n        b\"--:--\\r\\n\"\n    ) == bytes(buf)\n\n\nasync def test_writer_write_no_close_boundary(\n    buf: bytearray, stream: AbstractStreamWriter\n) -> None:\n    writer = aiohttp.MultipartWriter(boundary=\":\")\n    writer.append(\"foo-bar-baz\")\n    writer.append_json({\"test\": \"passed\"})\n    writer.append_form({\"test\": \"passed\"})\n    writer.append_form([(\"one\", \"1\"), (\"two\", \"2\")])\n    await writer.write(stream, close_boundary=False)\n\n    assert (\n        b\"--:\\r\\n\"\n        b\"Content-Type: text/plain; charset=utf-8\\r\\n\"\n        b\"Content-Length: 11\\r\\n\\r\\n\"\n        b\"foo-bar-baz\"\n        b\"\\r\\n\"\n        b\"--:\\r\\n\"\n        b\"Content-Type: application/json\\r\\n\"\n        b\"Content-Length: 18\\r\\n\\r\\n\"\n        b'{\"test\": \"passed\"}'\n        b\"\\r\\n\"\n        b\"--:\\r\\n\"\n        b\"Content-Type: application/x-www-form-urlencoded\\r\\n\"\n        b\"Content-Length: 11\\r\\n\\r\\n\"\n        b\"test=passed\"\n        b\"\\r\\n\"\n        b\"--:\\r\\n\"\n        b\"Content-Type: application/x-www-form-urlencoded\\r\\n\"\n        b\"Content-Length: 11\\r\\n\\r\\n\"\n        b\"one=1&two=2\"\n        b\"\\r\\n\"\n    ) == bytes(buf)\n\n\nasync def test_writer_write_no_parts(\n    buf: bytearray, stream: AbstractStreamWriter, writer: aiohttp.MultipartWriter\n) -> None:\n    await writer.write(stream)\n    assert b\"--:--\\r\\n\" == bytes(buf)\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_writer_serialize_with_content_encoding_gzip(\n    buf: bytearray,\n    stream: AbstractStreamWriter,\n    writer: aiohttp.MultipartWriter,\n) -> None:\n    writer.append(\"Time to Relax!\", {CONTENT_ENCODING: \"gzip\"})\n    await writer.write(stream)\n    headers, message = bytes(buf).split(b\"\\r\\n\\r\\n\", 1)\n\n    assert (\n        b\"--:\\r\\nContent-Type: text/plain; charset=utf-8\\r\\n\"\n        b\"Content-Encoding: gzip\" == headers\n    )\n\n    decompressor = ZLibBackend.decompressobj(wbits=16 + ZLibBackend.MAX_WBITS)\n    data = decompressor.decompress(message.split(b\"\\r\\n\")[0])\n    data += decompressor.flush()\n    assert b\"Time to Relax!\" == data\n\n\nasync def test_writer_serialize_with_content_encoding_deflate(\n    buf: bytearray, stream: AbstractStreamWriter, writer: aiohttp.MultipartWriter\n) -> None:\n    writer.append(\"Time to Relax!\", {CONTENT_ENCODING: \"deflate\"})\n    await writer.write(stream)\n    headers, message = bytes(buf).split(b\"\\r\\n\\r\\n\", 1)\n\n    assert (\n        b\"--:\\r\\nContent-Type: text/plain; charset=utf-8\\r\\n\"\n        b\"Content-Encoding: deflate\" == headers\n    )\n\n    thing = b\"\\x0b\\xc9\\xccMU(\\xc9W\\x08J\\xcdI\\xacP\\x04\\x00\\r\\n--:--\\r\\n\"\n    assert thing == message\n\n\nasync def test_writer_serialize_with_content_encoding_identity(\n    buf: bytearray, stream: AbstractStreamWriter, writer: aiohttp.MultipartWriter\n) -> None:\n    thing = b\"\\x0b\\xc9\\xccMU(\\xc9W\\x08J\\xcdI\\xacP\\x04\\x00\"\n    writer.append(thing, {CONTENT_ENCODING: \"identity\"})\n    await writer.write(stream)\n    headers, message = bytes(buf).split(b\"\\r\\n\\r\\n\", 1)\n\n    assert (\n        b\"--:\\r\\nContent-Type: application/octet-stream\\r\\n\"\n        b\"Content-Encoding: identity\\r\\n\"\n        b\"Content-Length: 16\" == headers\n    )\n\n    assert thing == message.split(b\"\\r\\n\")[0]\n\n\ndef test_writer_serialize_with_content_encoding_unknown(\n    buf: bytearray, stream: AbstractStreamWriter, writer: aiohttp.MultipartWriter\n) -> None:\n    with pytest.raises(RuntimeError):\n        writer.append(\"Time to Relax!\", {CONTENT_ENCODING: \"snappy\"})\n\n\nasync def test_writer_with_content_transfer_encoding_base64(\n    buf: bytearray, stream: AbstractStreamWriter, writer: aiohttp.MultipartWriter\n) -> None:\n    writer.append(\"Time to Relax!\", {CONTENT_TRANSFER_ENCODING: \"base64\"})\n    await writer.write(stream)\n    headers, message = bytes(buf).split(b\"\\r\\n\\r\\n\", 1)\n\n    assert (\n        b\"--:\\r\\nContent-Type: text/plain; charset=utf-8\\r\\n\"\n        b\"Content-Transfer-Encoding: base64\" == headers\n    )\n\n    assert b\"VGltZSB0byBSZWxheCE=\" == message.split(b\"\\r\\n\")[0]\n\n\nasync def test_writer_content_transfer_encoding_quote_printable(\n    buf: bytearray, stream: AbstractStreamWriter, writer: aiohttp.MultipartWriter\n) -> None:\n    writer.append(\"Привет, мир!\", {CONTENT_TRANSFER_ENCODING: \"quoted-printable\"})\n    await writer.write(stream)\n    headers, message = bytes(buf).split(b\"\\r\\n\\r\\n\", 1)\n\n    assert (\n        b\"--:\\r\\nContent-Type: text/plain; charset=utf-8\\r\\n\"\n        b\"Content-Transfer-Encoding: quoted-printable\" == headers\n    )\n\n    assert (\n        b\"=D0=9F=D1=80=D0=B8=D0=B2=D0=B5=D1=82,\"\n        b\" =D0=BC=D0=B8=D1=80!\" == message.split(b\"\\r\\n\")[0]\n    )\n\n\ndef test_writer_content_transfer_encoding_unknown(\n    buf: bytearray, stream: AbstractStreamWriter, writer: aiohttp.MultipartWriter\n) -> None:\n    with pytest.raises(RuntimeError):\n        writer.append(\"Time to Relax!\", {CONTENT_TRANSFER_ENCODING: \"unknown\"})\n\n\nclass TestMultipartWriter:\n    def test_default_subtype(self, writer: aiohttp.MultipartWriter) -> None:\n        mimetype = parse_mimetype(writer.headers.get(CONTENT_TYPE))\n\n        assert \"multipart\" == mimetype.type\n        assert \"mixed\" == mimetype.subtype\n\n    def test_unquoted_boundary(self) -> None:\n        writer = aiohttp.MultipartWriter(boundary=\"abc123\")\n        expected = {CONTENT_TYPE: \"multipart/mixed; boundary=abc123\"}\n        assert expected == writer.headers\n\n    def test_quoted_boundary(self) -> None:\n        writer = aiohttp.MultipartWriter(boundary=R\"\\\"\")\n        expected = {CONTENT_TYPE: R'multipart/mixed; boundary=\"\\\\\\\"\"'}\n        assert expected == writer.headers\n\n    def test_bad_boundary(self) -> None:\n        with pytest.raises(ValueError):\n            aiohttp.MultipartWriter(boundary=\"тест\")\n        with pytest.raises(ValueError):\n            aiohttp.MultipartWriter(boundary=\"test\\n\")\n        with pytest.raises(ValueError):\n            aiohttp.MultipartWriter(boundary=\"X\" * 71)\n\n    def test_default_headers(self, writer: aiohttp.MultipartWriter) -> None:\n        expected = {CONTENT_TYPE: 'multipart/mixed; boundary=\":\"'}\n        assert expected == writer.headers\n\n    def test_iter_parts(self, writer: aiohttp.MultipartWriter) -> None:\n        writer.append(\"foo\")\n        writer.append(\"bar\")\n        writer.append(\"baz\")\n        assert 3 == len(list(writer))\n\n    def test_append(self, writer: aiohttp.MultipartWriter) -> None:\n        assert 0 == len(writer)\n        writer.append(\"hello, world!\")\n        assert 1 == len(writer)\n        assert isinstance(writer._parts[0][0], payload.Payload)\n\n    def test_append_with_headers(self, writer: aiohttp.MultipartWriter) -> None:\n        writer.append(\"hello, world!\", {\"x-foo\": \"bar\"})\n        assert 1 == len(writer)\n        assert \"x-foo\" in writer._parts[0][0].headers\n        assert writer._parts[0][0].headers[\"x-foo\"] == \"bar\"\n\n    def test_append_json(self, writer: aiohttp.MultipartWriter) -> None:\n        writer.append_json({\"foo\": \"bar\"})\n        assert 1 == len(writer)\n        part = writer._parts[0][0]\n        assert part.headers[CONTENT_TYPE] == \"application/json\"\n\n    def test_append_part(self, writer: aiohttp.MultipartWriter) -> None:\n        part = payload.get_payload(\"test\", headers={CONTENT_TYPE: \"text/plain\"})\n        writer.append(part, {CONTENT_TYPE: \"test/passed\"})\n        assert 1 == len(writer)\n        part = writer._parts[0][0]\n        assert part.headers[CONTENT_TYPE] == \"test/passed\"\n\n    def test_append_json_overrides_content_type(\n        self, writer: aiohttp.MultipartWriter\n    ) -> None:\n        writer.append_json({\"foo\": \"bar\"}, {CONTENT_TYPE: \"test/passed\"})\n        assert 1 == len(writer)\n        part = writer._parts[0][0]\n        assert part.headers[CONTENT_TYPE] == \"test/passed\"\n\n    def test_append_form(self, writer: aiohttp.MultipartWriter) -> None:\n        writer.append_form({\"foo\": \"bar\"}, {CONTENT_TYPE: \"test/passed\"})\n        assert 1 == len(writer)\n        part = writer._parts[0][0]\n        assert part.headers[CONTENT_TYPE] == \"test/passed\"\n\n    def test_append_multipart(self, writer: aiohttp.MultipartWriter) -> None:\n        subwriter = aiohttp.MultipartWriter(boundary=\":\")\n        subwriter.append_json({\"foo\": \"bar\"})\n        writer.append(subwriter, {CONTENT_TYPE: \"test/passed\"})\n        assert 1 == len(writer)\n        part = writer._parts[0][0]\n        assert part.headers[CONTENT_TYPE] == \"test/passed\"\n\n    def test_set_content_disposition_after_append(self) -> None:\n        writer = aiohttp.MultipartWriter(\"form-data\")\n        part = writer.append(\"some-data\")\n        part.set_content_disposition(\"form-data\", name=\"method\")\n        assert 'name=\"method\"' in part.headers[CONTENT_DISPOSITION]\n\n    def test_automatic_content_disposition(self) -> None:\n        writer = aiohttp.MultipartWriter(\"form-data\")\n        writer.append_json(())\n        part = payload.StringPayload(\"foo\")\n        part.set_content_disposition(\"form-data\", name=\"second\")\n        writer.append_payload(part)\n        writer.append(\"foo\")\n\n        disps = tuple(p[0].headers[CONTENT_DISPOSITION] for p in writer._parts)\n        assert 'name=\"section-0\"' in disps[0]\n        assert 'name=\"second\"' in disps[1]\n        assert 'name=\"section-2\"' in disps[2]\n\n    def test_with(self) -> None:\n        with aiohttp.MultipartWriter(boundary=\":\") as writer:\n            writer.append(\"foo\")\n            writer.append(b\"bar\")\n            writer.append_json({\"baz\": True})\n        assert 3 == len(writer)\n\n    def test_append_int_not_allowed(self) -> None:\n        with pytest.raises(TypeError):\n            with aiohttp.MultipartWriter(boundary=\":\") as writer:\n                writer.append(1)\n\n    def test_append_float_not_allowed(self) -> None:\n        with pytest.raises(TypeError):\n            with aiohttp.MultipartWriter(boundary=\":\") as writer:\n                writer.append(1.1)\n\n    def test_append_none_not_allowed(self) -> None:\n        with pytest.raises(TypeError):\n            with aiohttp.MultipartWriter(boundary=\":\") as writer:\n                writer.append(None)\n\n    async def test_write_preserves_content_disposition(\n        self, buf: bytearray, stream: AbstractStreamWriter\n    ) -> None:\n        with aiohttp.MultipartWriter(boundary=\":\") as writer:\n            part = writer.append(b\"foo\", headers={CONTENT_TYPE: \"test/passed\"})\n            part.set_content_disposition(\"form-data\", filename=\"bug\")\n        await writer.write(stream)\n\n        headers, message = bytes(buf).split(b\"\\r\\n\\r\\n\", 1)\n\n        assert headers == (\n            b\"--:\\r\\n\"\n            b\"Content-Type: test/passed\\r\\n\"\n            b\"Content-Length: 3\\r\\n\"\n            b\"Content-Disposition:\"\n            b' form-data; filename=\"bug\"'\n        )\n        assert message == b\"foo\\r\\n--:--\\r\\n\"\n\n    async def test_preserve_content_disposition_header(\n        self, buf: bytearray, stream: AbstractStreamWriter\n    ) -> None:\n        # https://github.com/aio-libs/aiohttp/pull/3475#issuecomment-451072381\n        with pathlib.Path(__file__).open(\"rb\") as fobj:\n            with aiohttp.MultipartWriter(\"form-data\", boundary=\":\") as writer:\n                part = writer.append(\n                    fobj,\n                    headers={\n                        CONTENT_DISPOSITION: 'attachments; filename=\"bug.py\"',\n                        CONTENT_TYPE: \"text/python\",\n                    },\n                )\n            await writer.write(stream)\n\n        assert part.headers[CONTENT_TYPE] == \"text/python\"\n        assert part.headers[CONTENT_DISPOSITION] == ('attachments; filename=\"bug.py\"')\n\n        headers, _ = bytes(buf).split(b\"\\r\\n\\r\\n\", 1)\n\n        assert headers == (\n            b\"--:\\r\\n\"\n            b\"Content-Type: text/python\\r\\n\"\n            b'Content-Disposition: attachments; filename=\"bug.py\"'\n        )\n\n    async def test_set_content_disposition_override(\n        self, buf: bytearray, stream: AbstractStreamWriter\n    ) -> None:\n        # https://github.com/aio-libs/aiohttp/pull/3475#issuecomment-451072381\n        with pathlib.Path(__file__).open(\"rb\") as fobj:\n            with aiohttp.MultipartWriter(\"form-data\", boundary=\":\") as writer:\n                part = writer.append(\n                    fobj,\n                    headers={\n                        CONTENT_DISPOSITION: 'attachments; filename=\"bug.py\"',\n                        CONTENT_TYPE: \"text/python\",\n                    },\n                )\n            await writer.write(stream)\n\n        assert part.headers[CONTENT_TYPE] == \"text/python\"\n        assert part.headers[CONTENT_DISPOSITION] == ('attachments; filename=\"bug.py\"')\n\n        headers, _ = bytes(buf).split(b\"\\r\\n\\r\\n\", 1)\n\n        assert headers == (\n            b\"--:\\r\\n\"\n            b\"Content-Type: text/python\\r\\n\"\n            b'Content-Disposition: attachments; filename=\"bug.py\"'\n        )\n\n    async def test_reset_content_disposition_header(\n        self, buf: bytearray, stream: AbstractStreamWriter\n    ) -> None:\n        # https://github.com/aio-libs/aiohttp/pull/3475#issuecomment-451072381\n        with pathlib.Path(__file__).open(\"rb\") as fobj:\n            with aiohttp.MultipartWriter(\"form-data\", boundary=\":\") as writer:\n                part = writer.append(\n                    fobj,\n                    headers={CONTENT_TYPE: \"text/plain\"},\n                )\n\n            assert CONTENT_DISPOSITION in part.headers\n\n            part.set_content_disposition(\"attachments\", filename=\"bug.py\")\n\n            await writer.write(stream)\n\n        headers, _ = bytes(buf).split(b\"\\r\\n\\r\\n\", 1)\n\n        assert headers == (\n            b\"--:\\r\\n\"\n            b\"Content-Type: text/plain\\r\\n\"\n            b\"Content-Disposition:\"\n            b' attachments; filename=\"bug.py\"'\n        )\n\n\nasync def test_async_for_reader() -> None:\n    data: tuple[dict[str, str], int, bytes, bytes, bytes] = (\n        {\"test\": \"passed\"},\n        42,\n        b\"plain text\",\n        b\"aiohttp\\n\",\n        b\"no epilogue\",\n    )\n    with Stream(\n        b\"\\r\\n\".join(\n            [\n                b\"--:\",\n                b\"Content-Type: application/json\",\n                b\"\",\n                json.dumps(data[0]).encode(),\n                b\"--:\",\n                b\"Content-Type: application/json\",\n                b\"\",\n                json.dumps(data[1]).encode(),\n                b\"--:\",\n                b'Content-Type: multipart/related; boundary=\"::\"',\n                b\"\",\n                b\"--::\",\n                b\"Content-Type: text/plain\",\n                b\"\",\n                data[2],\n                b\"--::\",\n                b'Content-Disposition: attachment; filename=\"aiohttp\"',\n                b\"Content-Type: text/plain\",\n                b\"Content-Length: 28\",\n                b\"Content-Encoding: gzip\",\n                b\"\",\n                b\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x00\\x03K\\xcc\\xcc\\xcf())\"\n                b\"\\xe0\\x02\\x00\\xd6\\x90\\xe2O\\x08\\x00\\x00\\x00\",\n                b\"--::\",\n                b'Content-Type: multipart/related; boundary=\":::\"',\n                b\"\",\n                b\"--:::\",\n                b\"Content-Type: text/plain\",\n                b\"\",\n                data[4],\n                b\"--:::--\",\n                b\"--::--\",\n                b\"\",\n                b\"--:--\",\n                b\"\",\n            ]\n        )\n    ) as stream:\n        reader = aiohttp.MultipartReader(\n            headers={CONTENT_TYPE: 'multipart/mixed; boundary=\":\"'},\n            content=stream,\n        )\n        idata = iter(data)\n\n        async def check(reader: aiohttp.MultipartReader) -> None:\n            async for part in reader:\n                assert part is not None\n                if isinstance(part, aiohttp.BodyPartReader):\n                    if part.headers[CONTENT_TYPE] == \"application/json\":\n                        assert next(idata) == (await part.json())\n                    else:\n                        assert next(idata) == await part.read(decode=True)\n                else:\n                    await check(part)\n\n        await check(reader)\n\n\nasync def test_async_for_bodypart() -> None:\n    h = CIMultiDictProxy[str](CIMultiDict())\n    with Stream(b\"foobarbaz\\r\\n--:--\") as stream:\n        part = aiohttp.BodyPartReader(boundary=b\"--:\", headers=h, content=stream)\n        async for data in part:\n            assert data == b\"foobarbaz\"\n\n\nasync def test_multipart_writer_reusability(\n    buf: bytearray,\n    stream: mock.Mock,\n    buf2: bytearray,\n    stream2: mock.Mock,\n    writer: aiohttp.MultipartWriter,\n) -> None:\n    \"\"\"Test that MultipartWriter can be written multiple times.\"\"\"\n    # Add some parts\n    writer.append(\"text content\")\n    writer.append(b\"binary content\", {\"Content-Type\": \"application/octet-stream\"})\n    writer.append_json({\"key\": \"value\"})\n\n    # Test as_bytes multiple times\n    bytes1 = await writer.as_bytes()\n    bytes2 = await writer.as_bytes()\n    bytes3 = await writer.as_bytes()\n\n    # All as_bytes calls should return identical data\n    assert bytes1 == bytes2 == bytes3\n\n    # Verify content is there\n    assert b\"text content\" in bytes1\n    assert b\"binary content\" in bytes1\n    assert b'\"key\": \"value\"' in bytes1\n\n    # First write\n    buf.clear()\n    await writer.write(stream)\n    result1 = bytes(buf)\n\n    # Second write - should produce identical output\n    buf2.clear()\n    await writer.write(stream2)\n    result2 = bytes(buf2)\n\n    # Results should be identical\n    assert result1 == result2\n\n    # Third write to ensure continued reusability\n    buf.clear()\n    await writer.write(stream)\n    result3 = bytes(buf)\n\n    assert result1 == result3\n\n    # as_bytes should still work after writes\n    bytes4 = await writer.as_bytes()\n    assert bytes1 == bytes4\n\n\nasync def test_multipart_writer_reusability_with_io_payloads(\n    buf: bytearray,\n    stream: mock.Mock,\n    buf2: bytearray,\n    stream2: mock.Mock,\n    writer: aiohttp.MultipartWriter,\n) -> None:\n    \"\"\"Test that MultipartWriter with IO payloads can be reused.\"\"\"\n    # Create IO objects\n    bytes_io = io.BytesIO(b\"bytes io content\")\n    string_io = io.StringIO(\"string io content\")\n\n    # Add IO payloads\n    writer.append(bytes_io, {\"Content-Type\": \"application/octet-stream\"})\n    writer.append(string_io, {\"Content-Type\": \"text/plain\"})\n\n    # Test as_bytes multiple times\n    bytes1 = await writer.as_bytes()\n    bytes2 = await writer.as_bytes()\n\n    # All as_bytes calls should return identical data\n    assert bytes1 == bytes2\n    assert b\"bytes io content\" in bytes1\n    assert b\"string io content\" in bytes1\n\n    # First write\n    buf.clear()\n    await writer.write(stream)\n    result1 = bytes(buf)\n\n    assert b\"bytes io content\" in result1\n    assert b\"string io content\" in result1\n\n    # Reset IO objects for reuse\n    bytes_io.seek(0)\n    string_io.seek(0)\n\n    # Second write\n    buf2.clear()\n    await writer.write(stream2)\n    result2 = bytes(buf2)\n\n    # Should produce identical results\n    assert result1 == result2\n\n    # Test as_bytes after writes (IO objects should auto-reset)\n    bytes3 = await writer.as_bytes()\n    assert bytes1 == bytes3\n\n\nasync def test_body_part_reader_payload_as_bytes() -> None:\n    \"\"\"Test that BodyPartReaderPayload.as_bytes raises TypeError.\"\"\"\n    # Create a mock BodyPartReader\n    headers = CIMultiDictProxy(CIMultiDict({CONTENT_TYPE: \"text/plain\"}))\n    protocol = mock.Mock(_reading_paused=False)\n    stream = StreamReader(protocol, 2**16, loop=asyncio.get_event_loop())\n    body_part = BodyPartReader(BOUNDARY, headers, stream)\n\n    # Create the payload\n    payload = BodyPartReaderPayload(body_part)\n\n    # Test that as_bytes raises TypeError\n    with pytest.raises(TypeError, match=\"Unable to read body part as bytes\"):\n        await payload.as_bytes()\n\n    # Test that decode also raises TypeError\n    with pytest.raises(TypeError, match=\"Unable to decode\"):\n        payload.decode()\n\n\nasync def test_multipart_writer_close_with_exceptions() -> None:\n    \"\"\"Test that MultipartWriter.close() continues closing all parts even if one raises.\"\"\"\n    writer = aiohttp.MultipartWriter()\n\n    # Create mock payloads\n    # First part will raise during close\n    part1 = mock.Mock()\n    part1.autoclose = False\n    part1.consumed = False\n    part1.close = mock.AsyncMock(side_effect=RuntimeError(\"Part 1 close failed\"))\n\n    # Second part should still get closed\n    part2 = mock.Mock()\n    part2.autoclose = False\n    part2.consumed = False\n    part2.close = mock.AsyncMock()\n\n    # Third part with autoclose=True should not be closed\n    part3 = mock.Mock()\n    part3.autoclose = True\n    part3.consumed = False\n    part3.close = mock.AsyncMock()\n\n    # Fourth part already consumed should not be closed\n    part4 = mock.Mock()\n    part4.autoclose = False\n    part4.consumed = True\n    part4.close = mock.AsyncMock()\n\n    # Add parts to writer's internal list\n    writer._parts = [\n        (part1, \"\", \"\"),\n        (part2, \"\", \"\"),\n        (part3, \"\", \"\"),\n        (part4, \"\", \"\"),\n    ]\n\n    # Close the writer - should not raise despite part1 failing\n    await writer.close()\n\n    # Verify close was called on appropriate parts\n    part1.close.assert_called_once()\n    part2.close.assert_called_once()  # Should still be called despite part1 failing\n    part3.close.assert_not_called()  # autoclose=True\n    part4.close.assert_not_called()  # consumed=True\n\n    # Verify writer is marked as consumed\n    assert writer._consumed is True\n\n    # Calling close again should do nothing\n    await writer.close()\n    assert part1.close.call_count == 1\n    assert part2.close.call_count == 1\n"
  },
  {
    "path": "tests/test_multipart_helpers.py",
    "content": "import pytest\n\nimport aiohttp\nfrom aiohttp import content_disposition_filename, parse_content_disposition\n\n\nclass TestParseContentDisposition:\n    # http://greenbytes.de/tech/tc2231/\n\n    def test_parse_empty(self) -> None:\n        disptype, params = parse_content_disposition(None)\n        assert disptype is None\n        assert {} == params\n\n    def test_inlonly(self) -> None:\n        disptype, params = parse_content_disposition(\"inline\")\n        assert \"inline\" == disptype\n        assert {} == params\n\n    def test_inlonlyquoted(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition('\"inline\"')\n        assert disptype is None\n        assert {} == params\n\n    def test_semicolon(self) -> None:\n        disptype, params = parse_content_disposition(\n            'form-data; name=\"data\"; filename=\"file ; name.mp4\"'\n        )\n        assert disptype == \"form-data\"\n        assert params == {\"name\": \"data\", \"filename\": \"file ; name.mp4\"}\n\n    def test_inlwithasciifilename(self) -> None:\n        disptype, params = parse_content_disposition('inline; filename=\"foo.html\"')\n        assert \"inline\" == disptype\n        assert {\"filename\": \"foo.html\"} == params\n\n    def test_inlwithfnattach(self) -> None:\n        disptype, params = parse_content_disposition(\n            'inline; filename=\"Not an attachment!\"'\n        )\n        assert \"inline\" == disptype\n        assert {\"filename\": \"Not an attachment!\"} == params\n\n    def test_attonly(self) -> None:\n        disptype, params = parse_content_disposition(\"attachment\")\n        assert \"attachment\" == disptype\n        assert {} == params\n\n    def test_attonlyquoted(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition('\"attachment\"')\n        assert disptype is None\n        assert {} == params\n\n    def test_attonlyucase(self) -> None:\n        disptype, params = parse_content_disposition(\"ATTACHMENT\")\n        assert \"attachment\" == disptype\n        assert {} == params\n\n    def test_attwithasciifilename(self) -> None:\n        disptype, params = parse_content_disposition('attachment; filename=\"foo.html\"')\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo.html\"} == params\n\n    def test_inlwithasciifilenamepdf(self) -> None:\n        disptype, params = parse_content_disposition('attachment; filename=\"foo.pdf\"')\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo.pdf\"} == params\n\n    def test_attwithasciifilename25(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename=\"0000000000111111111122222\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"0000000000111111111122222\"} == params\n\n    def test_attwithasciifilename35(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename=\"00000000001111111111222222222233333\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"00000000001111111111222222222233333\"} == params\n\n    def test_attwithasciifnescapedchar(self) -> None:\n        disptype, params = parse_content_disposition(\n            r'attachment; filename=\"f\\oo.html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo.html\"} == params\n\n    def test_attwithasciifnescapedquote(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename=\"\"quoting\" tested.html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": '\"quoting\" tested.html'} == params\n\n    @pytest.mark.skip(\"need more smart parser which respects quoted text\")\n    def test_attwithquotedsemicolon(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename=\"Here\\'s a semicolon;.html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"Here's a semicolon;.html\"} == params\n\n    def test_attwithfilenameandextparam(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; foo=\"bar\"; filename=\"foo.html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo.html\", \"foo\": \"bar\"} == params\n\n    def test_attwithfilenameandextparamescaped(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; foo=\"\"\\\\\";filename=\"foo.html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo.html\", \"foo\": '\"\\\\'} == params\n\n    def test_attwithasciifilenameucase(self) -> None:\n        disptype, params = parse_content_disposition('attachment; FILENAME=\"foo.html\"')\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo.html\"} == params\n\n    def test_attwithasciifilenamenq(self) -> None:\n        disptype, params = parse_content_disposition(\"attachment; filename=foo.html\")\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo.html\"} == params\n\n    def test_attwithtokfncommanq(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename=foo,bar.html\"\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attwithasciifilenamenqs(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename=foo.html ;\"\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attemptyparam(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\"attachment; ;filename=foo\")\n        assert disptype is None\n        assert {} == params\n\n    def test_attwithasciifilenamenqws(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename=foo bar.html\"\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attwithfntokensq(self) -> None:\n        disptype, params = parse_content_disposition(\"attachment; filename='foo.html'\")\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"'foo.html'\"} == params\n\n    def test_attwithisofnplain(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename=\"foo-ä.html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo-ä.html\"} == params\n\n    def test_attwithutf8fnplain(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename=\"foo-Ã¤.html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo-Ã¤.html\"} == params\n\n    def test_attwithfnrawpctenca(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename=\"foo-%41.html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo-%41.html\"} == params\n\n    def test_attwithfnusingpct(self) -> None:\n        disptype, params = parse_content_disposition('attachment; filename=\"50%.html\"')\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"50%.html\"} == params\n\n    def test_attwithfnrawpctencaq(self) -> None:\n        disptype, params = parse_content_disposition(\n            r'attachment; filename=\"foo-%\\41.html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": r\"foo-%41.html\"} == params\n\n    def test_attwithnamepct(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename=\"foo-%41.html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo-%41.html\"} == params\n\n    def test_attwithfilenamepctandiso(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename=\"ä-%41.html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"ä-%41.html\"} == params\n\n    def test_attwithfnrawpctenclong(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename=\"foo-%c3%a4-%e2%82%ac.html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo-%c3%a4-%e2%82%ac.html\"} == params\n\n    def test_attwithasciifilenamews1(self) -> None:\n        disptype, params = parse_content_disposition('attachment; filename =\"foo.html\"')\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo.html\"} == params\n\n    def test_attwith2filenames(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                'attachment; filename=\"foo.html\"; filename=\"bar.html\"'\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attfnbrokentoken(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename=foo[1](2).html\"\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attfnbrokentokeniso(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename=foo-ä.html\"\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attfnbrokentokenutf(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename=foo-Ã¤.html\"\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attmissingdisposition(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\"filename=foo.html\")\n        assert disptype is None\n        assert {} == params\n\n    def test_attmissingdisposition2(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\"x=y; filename=foo.html\")\n        assert disptype is None\n        assert {} == params\n\n    def test_attmissingdisposition3(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                '\"foo; filename=bar;baz\"; filename=qux'\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attmissingdisposition4(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                \"filename=foo.html, filename=bar.html\"\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_emptydisposition(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\"; filename=foo.html\")\n        assert disptype is None\n        assert {} == params\n\n    def test_doublecolon(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                \": inline; attachment; filename=foo.html\"\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attandinline(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                \"inline; attachment; filename=foo.html\"\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attandinline2(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                \"attachment; inline; filename=foo.html\"\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attbrokenquotedfn(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                'attachment; filename=\"foo.html\".txt'\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attbrokenquotedfn2(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition('attachment; filename=\"bar')\n        assert disptype is None\n        assert {} == params\n\n    def test_attbrokenquotedfn3(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                'attachment; filename=foo\"bar;baz\"qux'\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attmultinstances(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename=foo.html, attachment; filename=bar.html\"\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attmissingdelim(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                \"attachment; foo=foo filename=bar\"\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attmissingdelim2(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename=bar foo=foo\"\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attmissingdelim3(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\"attachment filename=bar\")\n        assert disptype is None\n        assert {} == params\n\n    def test_attreversed(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                \"filename=foo.html; attachment\"\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attconfusedparam(self) -> None:\n        disptype, params = parse_content_disposition(\"attachment; xfilename=foo.html\")\n        assert \"attachment\" == disptype\n        assert {\"xfilename\": \"foo.html\"} == params\n\n    def test_attabspath(self) -> None:\n        disptype, params = parse_content_disposition('attachment; filename=\"/foo.html\"')\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo.html\"} == params\n\n    def test_attabspathwin(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename=\"\\\\foo.html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo.html\"} == params\n\n    def test_attcdate(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"creation-date\": \"Wed, 12 Feb 1997 16:29:51 -0500\"} == params\n\n    def test_attmdate(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"modification-date\": \"Wed, 12 Feb 1997 16:29:51 -0500\"} == params\n\n    def test_dispext(self) -> None:\n        disptype, params = parse_content_disposition(\"foobar\")\n        assert \"foobar\" == disptype\n        assert {} == params\n\n    def test_dispextbadfn(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; example=\"filename=example.txt\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"example\": \"filename=example.txt\"} == params\n\n    def test_attwithisofn2231iso(self) -> None:\n        disptype, params = parse_content_disposition(\n            \"attachment; filename*=iso-8859-1''foo-%E4.html\"\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename*\": \"foo-ä.html\"} == params\n\n    def test_attwithfn2231utf8(self) -> None:\n        disptype, params = parse_content_disposition(\n            \"attachment; filename*=UTF-8''foo-%c3%a4-%e2%82%ac.html\"\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename*\": \"foo-ä-€.html\"} == params\n\n    def test_attwithfn2231noc(self) -> None:\n        disptype, params = parse_content_disposition(\n            \"attachment; filename*=''foo-%c3%a4-%e2%82%ac.html\"\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename*\": \"foo-ä-€.html\"} == params\n\n    def test_attwithfn2231utf8comp(self) -> None:\n        disptype, params = parse_content_disposition(\n            \"attachment; filename*=UTF-8''foo-a%cc%88.html\"\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename*\": \"foo-ä.html\"} == params\n\n    @pytest.mark.skip(\"should raise decoding error: %82 is invalid for latin1\")\n    def test_attwithfn2231utf8_bad(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionParam):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename*=iso-8859-1''foo-%c3%a4-%e2%82%ac.html\"\n            )\n        assert \"attachment\" == disptype\n        assert {} == params\n\n    @pytest.mark.skip(\"should raise decoding error: %E4 is invalid for utf-8\")\n    def test_attwithfn2231iso_bad(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionParam):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename*=utf-8''foo-%E4.html\"\n            )\n        assert \"attachment\" == disptype\n        assert {} == params\n\n    def test_attwithfn2231ws1(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionParam):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename *=UTF-8''foo-%c3%a4.html\"\n            )\n        assert \"attachment\" == disptype\n        assert {} == params\n\n    def test_attwithfn2231ws2(self) -> None:\n        disptype, params = parse_content_disposition(\n            \"attachment; filename*= UTF-8''foo-%c3%a4.html\"\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename*\": \"foo-ä.html\"} == params\n\n    def test_attwithfn2231ws3(self) -> None:\n        disptype, params = parse_content_disposition(\n            \"attachment; filename* =UTF-8''foo-%c3%a4.html\"\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename*\": \"foo-ä.html\"} == params\n\n    def test_attwithfn2231quot(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionParam):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename*=\\\"UTF-8''foo-%c3%a4.html\\\"\"\n            )\n        assert \"attachment\" == disptype\n        assert {} == params\n\n    def test_attwithfn2231quot2(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionParam):\n            disptype, params = parse_content_disposition(\n                'attachment; filename*=\"foo%20bar.html\"'\n            )\n        assert \"attachment\" == disptype\n        assert {} == params\n\n    def test_attwithfn2231singleqmissing(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionParam):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename*=UTF-8'foo-%c3%a4.html\"\n            )\n        assert \"attachment\" == disptype\n        assert {} == params\n\n    @pytest.mark.skip(\"urllib.parse.unquote is tolerate to standalone % chars\")\n    def test_attwithfn2231nbadpct1(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionParam):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename*=UTF-8''foo%\"\n            )\n        assert \"attachment\" == disptype\n        assert {} == params\n\n    @pytest.mark.skip(\"urllib.parse.unquote is tolerate to standalone % chars\")\n    def test_attwithfn2231nbadpct2(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionParam):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename*=UTF-8''f%oo.html\"\n            )\n        assert \"attachment\" == disptype\n        assert {} == params\n\n    def test_attwithfn2231dpct(self) -> None:\n        disptype, params = parse_content_disposition(\n            \"attachment; filename*=UTF-8''A-%2541.html\"\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename*\": \"A-%41.html\"} == params\n\n    def test_attwithfn2231abspathdisguised(self) -> None:\n        disptype, params = parse_content_disposition(\n            \"attachment; filename*=UTF-8''%5cfoo.html\"\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename*\": \"\\\\foo.html\"} == params\n\n    def test_attfncont(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename*0=\"foo.\"; filename*1=\"html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename*0\": \"foo.\", \"filename*1\": \"html\"} == params\n\n    def test_attfncontqs(self) -> None:\n        disptype, params = parse_content_disposition(\n            r'attachment; filename*0=\"foo\"; filename*1=\"\\b\\a\\r.html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename*0\": \"foo\", \"filename*1\": \"bar.html\"} == params\n\n    def test_attfncontenc(self) -> None:\n        disptype, params = parse_content_disposition(\n            \"attachment; filename*0*=UTF-8\" + 'foo-%c3%a4; filename*1=\".html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename*0*\": \"UTF-8foo-%c3%a4\", \"filename*1\": \".html\"} == params\n\n    def test_attfncontlz(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename*0=\"foo\"; filename*01=\"bar\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename*0\": \"foo\", \"filename*01\": \"bar\"} == params\n\n    def test_attfncontnc(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename*0=\"foo\"; filename*2=\"bar\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename*0\": \"foo\", \"filename*2\": \"bar\"} == params\n\n    def test_attfnconts1(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename*0=\"foo.\"; filename*2=\"html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename*0\": \"foo.\", \"filename*2\": \"html\"} == params\n\n    def test_attfncontord(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename*1=\"bar\"; filename*0=\"foo\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename*0\": \"foo\", \"filename*1\": \"bar\"} == params\n\n    def test_attfnboth(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename=\"foo-ae.html\";' + \" filename*=UTF-8''foo-%c3%a4.html\"\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo-ae.html\", \"filename*\": \"foo-ä.html\"} == params\n\n    def test_attfnboth2(self) -> None:\n        disptype, params = parse_content_disposition(\n            \"attachment; filename*=UTF-8''foo-%c3%a4.html;\" + ' filename=\"foo-ae.html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"foo-ae.html\", \"filename*\": \"foo-ä.html\"} == params\n\n    def test_attfnboth3(self) -> None:\n        disptype, params = parse_content_disposition(\n            \"attachment; filename*0*=ISO-8859-15''euro-sign%3d%a4;\"\n            \" filename*=ISO-8859-1''currency-sign%3d%a4\"\n        )\n        assert \"attachment\" == disptype\n        assert {\n            \"filename*\": \"currency-sign=¤\",\n            \"filename*0*\": \"ISO-8859-15''euro-sign%3d%a4\",\n        } == params\n\n    def test_attnewandfn(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; foobar=x; filename=\"foo.html\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"foobar\": \"x\", \"filename\": \"foo.html\"} == params\n\n    def test_attrfc2047token(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionHeader):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename==?ISO-8859-1?Q?foo-=E4.html?=\"\n            )\n        assert disptype is None\n        assert {} == params\n\n    def test_attrfc2047quoted(self) -> None:\n        disptype, params = parse_content_disposition(\n            'attachment; filename=\"=?ISO-8859-1?Q?foo-=E4.html?=\"'\n        )\n        assert \"attachment\" == disptype\n        assert {\"filename\": \"=?ISO-8859-1?Q?foo-=E4.html?=\"} == params\n\n    def test_bad_continuous_param(self) -> None:\n        with pytest.warns(aiohttp.BadContentDispositionParam):\n            disptype, params = parse_content_disposition(\n                \"attachment; filename*0=foo bar\"\n            )\n        assert \"attachment\" == disptype\n        assert {} == params\n\n\nclass TestContentDispositionFilename:\n    # http://greenbytes.de/tech/tc2231/\n\n    def test_no_filename(self) -> None:\n        assert content_disposition_filename({}) is None\n        assert content_disposition_filename({\"foo\": \"bar\"}) is None\n\n    def test_filename(self) -> None:\n        params = {\"filename\": \"foo.html\"}\n        assert \"foo.html\" == content_disposition_filename(params)\n\n    def test_filename_ext(self) -> None:\n        params = {\"filename*\": \"файл.html\"}\n        assert \"файл.html\" == content_disposition_filename(params)\n\n    def test_attfncont(self) -> None:\n        params = {\"filename*0\": \"foo.\", \"filename*1\": \"html\"}\n        assert \"foo.html\" == content_disposition_filename(params)\n\n    def test_attfncontqs(self) -> None:\n        params = {\"filename*0\": \"foo\", \"filename*1\": \"bar.html\"}\n        assert \"foobar.html\" == content_disposition_filename(params)\n\n    def test_attfncontenc(self) -> None:\n        params = {\"filename*0*\": \"UTF-8''foo-%c3%a4\", \"filename*1\": \".html\"}\n        assert \"foo-ä.html\" == content_disposition_filename(params)\n\n    def test_attfncontlz(self) -> None:\n        params = {\"filename*0\": \"foo\", \"filename*01\": \"bar\"}\n        assert \"foo\" == content_disposition_filename(params)\n\n    def test_attfncontnc(self) -> None:\n        params = {\"filename*0\": \"foo\", \"filename*2\": \"bar\"}\n        assert \"foo\" == content_disposition_filename(params)\n\n    def test_attfnconts1(self) -> None:\n        params = {\"filename*1\": \"foo\", \"filename*2\": \"bar\"}\n        assert content_disposition_filename(params) is None\n\n    def test_attfnboth(self) -> None:\n        params = {\"filename\": \"foo-ae.html\", \"filename*\": \"foo-ä.html\"}\n        assert \"foo-ä.html\" == content_disposition_filename(params)\n\n    def test_attfnboth3(self) -> None:\n        params = {\n            \"filename*0*\": \"ISO-8859-15''euro-sign%3d%a4\",\n            \"filename*\": \"currency-sign=¤\",\n        }\n        assert \"currency-sign=¤\" == content_disposition_filename(params)\n\n    def test_attrfc2047quoted(self) -> None:\n        params = {\"filename\": \"=?ISO-8859-1?Q?foo-=E4.html?=\"}\n        assert \"=?ISO-8859-1?Q?foo-=E4.html?=\" == content_disposition_filename(params)\n"
  },
  {
    "path": "tests/test_payload.py",
    "content": "import array\nimport asyncio\nimport io\nimport json\nimport unittest.mock\nfrom collections.abc import AsyncIterator, Iterator\nfrom io import StringIO\nfrom pathlib import Path\nfrom typing import TextIO, Union\n\nimport pytest\nfrom multidict import CIMultiDict\n\nfrom aiohttp import payload\nfrom aiohttp.abc import AbstractStreamWriter\nfrom aiohttp.payload import READ_SIZE\n\n\nclass BufferWriter(AbstractStreamWriter):\n    \"\"\"Test writer that captures written bytes in a buffer.\"\"\"\n\n    def __init__(self) -> None:\n        self.buffer = bytearray()\n\n    async def write(\n        self, chunk: Union[bytes, bytearray, \"memoryview[int]\", \"memoryview[bytes]\"]\n    ) -> None:\n        self.buffer.extend(bytes(chunk))\n\n    async def write_eof(self, chunk: bytes = b\"\") -> None:\n        \"\"\"No-op for test writer.\"\"\"\n\n    async def drain(self) -> None:\n        \"\"\"No-op for test writer.\"\"\"\n\n    def enable_compression(\n        self, encoding: str = \"deflate\", strategy: int | None = None\n    ) -> None:\n        \"\"\"Compression not implemented for test writer.\"\"\"\n\n    def enable_chunking(self) -> None:\n        \"\"\"Chunking not implemented for test writer.\"\"\"\n\n    async def write_headers(self, status_line: str, headers: CIMultiDict[str]) -> None:\n        \"\"\"Headers not captured for payload tests.\"\"\"\n\n\n@pytest.fixture(autouse=True)\ndef cleanup(\n    cleanup_payload_pending_file_closes: None,\n) -> None:\n    \"\"\"Ensure all pending file close operations complete during test teardown.\"\"\"\n\n\n@pytest.fixture\ndef registry() -> Iterator[payload.PayloadRegistry]:\n    old = payload.PAYLOAD_REGISTRY\n    reg = payload.PAYLOAD_REGISTRY = payload.PayloadRegistry()\n    yield reg\n    payload.PAYLOAD_REGISTRY = old\n\n\nclass Payload(payload.Payload):\n    def decode(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> str:\n        assert False\n\n    async def write(self, writer: AbstractStreamWriter) -> None:\n        \"\"\"Dummy write.\"\"\"\n\n\ndef test_register_type(registry: payload.PayloadRegistry) -> None:\n    class TestProvider:\n        pass\n\n    payload.register_payload(Payload, TestProvider)\n    p = payload.get_payload(TestProvider())\n    assert isinstance(p, Payload)\n\n\ndef test_register_unsupported_order(registry: payload.PayloadRegistry) -> None:\n    class TestProvider:\n        pass\n\n    with pytest.raises(ValueError):\n        payload.register_payload(\n            Payload, TestProvider, order=object()  # type: ignore[arg-type]\n        )\n\n\ndef test_payload_ctor() -> None:\n    p = Payload(\"test\", encoding=\"utf-8\", filename=\"test.txt\")\n    assert p._value == \"test\"\n    assert p._encoding == \"utf-8\"\n    assert p.size is None\n    assert p.filename == \"test.txt\"\n    assert p.content_type == \"text/plain\"\n\n\ndef test_payload_content_type() -> None:\n    p = Payload(\"test\", headers={\"content-type\": \"application/json\"})\n    assert p.content_type == \"application/json\"\n\n\ndef test_bytes_payload_default_content_type() -> None:\n    p = payload.BytesPayload(b\"data\")\n    assert p.content_type == \"application/octet-stream\"\n\n\ndef test_bytes_payload_explicit_content_type() -> None:\n    p = payload.BytesPayload(b\"data\", content_type=\"application/custom\")\n    assert p.content_type == \"application/custom\"\n\n\ndef test_bytes_payload_bad_type() -> None:\n    with pytest.raises(TypeError):\n        payload.BytesPayload(object())  # type: ignore[arg-type]\n\n\ndef test_bytes_payload_memoryview_correct_size() -> None:\n    mv = memoryview(array.array(\"H\", [1, 2, 3]))\n    p = payload.BytesPayload(mv)\n    assert p.size == 6\n\n\ndef test_string_payload() -> None:\n    p = payload.StringPayload(\"test\")\n    assert p.encoding == \"utf-8\"\n    assert p.content_type == \"text/plain; charset=utf-8\"\n\n    p = payload.StringPayload(\"test\", encoding=\"koi8-r\")\n    assert p.encoding == \"koi8-r\"\n    assert p.content_type == \"text/plain; charset=koi8-r\"\n\n    p = payload.StringPayload(\"test\", content_type=\"text/plain; charset=koi8-r\")\n    assert p.encoding == \"koi8-r\"\n    assert p.content_type == \"text/plain; charset=koi8-r\"\n\n\ndef test_string_io_payload() -> None:\n    s = StringIO(\"ű\" * 5000)\n    p = payload.StringIOPayload(s)\n    assert p.encoding == \"utf-8\"\n    assert p.content_type == \"text/plain; charset=utf-8\"\n    assert p.size == 10000\n\n\ndef test_async_iterable_payload_default_content_type() -> None:\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"abc\"  # pragma: no cover\n\n    p = payload.AsyncIterablePayload(gen())\n    assert p.content_type == \"application/octet-stream\"\n\n\ndef test_async_iterable_payload_explicit_content_type() -> None:\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"abc\"  # pragma: no cover\n\n    p = payload.AsyncIterablePayload(gen(), content_type=\"application/custom\")\n    assert p.content_type == \"application/custom\"\n\n\ndef test_async_iterable_payload_not_async_iterable() -> None:\n    with pytest.raises(TypeError):\n        payload.AsyncIterablePayload(object())  # type: ignore[arg-type]\n\n\nclass MockStreamWriter(AbstractStreamWriter):\n    \"\"\"Mock stream writer for testing payload writes.\"\"\"\n\n    def __init__(self) -> None:\n        self.written: list[bytes] = []\n\n    async def write(\n        self, chunk: Union[bytes, bytearray, \"memoryview[int]\", \"memoryview[bytes]\"]\n    ) -> None:\n        \"\"\"Store the chunk in the written list.\"\"\"\n        self.written.append(bytes(chunk))\n\n    async def write_eof(self, chunk: bytes | None = None) -> None:\n        \"\"\"write_eof implementation - no-op for tests.\"\"\"\n\n    async def drain(self) -> None:\n        \"\"\"Drain implementation - no-op for tests.\"\"\"\n\n    def enable_compression(\n        self, encoding: str = \"deflate\", strategy: int | None = None\n    ) -> None:\n        \"\"\"Enable compression - no-op for tests.\"\"\"\n\n    def enable_chunking(self) -> None:\n        \"\"\"Enable chunking - no-op for tests.\"\"\"\n\n    async def write_headers(self, status_line: str, headers: CIMultiDict[str]) -> None:\n        \"\"\"Write headers - no-op for tests.\"\"\"\n\n    def get_written_bytes(self) -> bytes:\n        \"\"\"Return all written bytes as a single bytes object.\"\"\"\n        return b\"\".join(self.written)\n\n\nasync def test_bytes_payload_write_with_length_no_limit() -> None:\n    \"\"\"Test BytesPayload writing with no content length limit.\"\"\"\n    data = b\"0123456789\"\n    p = payload.BytesPayload(data)\n    writer = MockStreamWriter()\n\n    await p.write_with_length(writer, None)\n    assert writer.get_written_bytes() == data\n    assert len(writer.get_written_bytes()) == 10\n\n\nasync def test_bytes_payload_write_with_length_exact() -> None:\n    \"\"\"Test BytesPayload writing with exact content length.\"\"\"\n    data = b\"0123456789\"\n    p = payload.BytesPayload(data)\n    writer = MockStreamWriter()\n\n    await p.write_with_length(writer, 10)\n    assert writer.get_written_bytes() == data\n    assert len(writer.get_written_bytes()) == 10\n\n\nasync def test_bytes_payload_write_with_length_truncated() -> None:\n    \"\"\"Test BytesPayload writing with truncated content length.\"\"\"\n    data = b\"0123456789\"\n    p = payload.BytesPayload(data)\n    writer = MockStreamWriter()\n\n    await p.write_with_length(writer, 5)\n    assert writer.get_written_bytes() == b\"01234\"\n    assert len(writer.get_written_bytes()) == 5\n\n\nasync def test_iobase_payload_write_with_length_no_limit() -> None:\n    \"\"\"Test IOBasePayload writing with no content length limit.\"\"\"\n    data = b\"0123456789\"\n    p = payload.IOBasePayload(io.BytesIO(data))\n    writer = MockStreamWriter()\n\n    await p.write_with_length(writer, None)\n    assert writer.get_written_bytes() == data\n    assert len(writer.get_written_bytes()) == 10\n\n\nasync def test_iobase_payload_write_with_length_exact() -> None:\n    \"\"\"Test IOBasePayload writing with exact content length.\"\"\"\n    data = b\"0123456789\"\n    p = payload.IOBasePayload(io.BytesIO(data))\n    writer = MockStreamWriter()\n\n    await p.write_with_length(writer, 10)\n    assert writer.get_written_bytes() == data\n    assert len(writer.get_written_bytes()) == 10\n\n\nasync def test_iobase_payload_write_with_length_truncated() -> None:\n    \"\"\"Test IOBasePayload writing with truncated content length.\"\"\"\n    data = b\"0123456789\"\n    p = payload.IOBasePayload(io.BytesIO(data))\n    writer = MockStreamWriter()\n\n    await p.write_with_length(writer, 5)\n    assert writer.get_written_bytes() == b\"01234\"\n    assert len(writer.get_written_bytes()) == 5\n\n\nasync def test_bytesio_payload_write_with_length_no_limit() -> None:\n    \"\"\"Test BytesIOPayload writing with no content length limit.\"\"\"\n    data = b\"0123456789\"\n    p = payload.BytesIOPayload(io.BytesIO(data))\n    writer = MockStreamWriter()\n\n    await p.write_with_length(writer, None)\n    assert writer.get_written_bytes() == data\n    assert len(writer.get_written_bytes()) == 10\n\n\nasync def test_bytesio_payload_write_with_length_exact() -> None:\n    \"\"\"Test BytesIOPayload writing with exact content length.\"\"\"\n    data = b\"0123456789\"\n    p = payload.BytesIOPayload(io.BytesIO(data))\n    writer = MockStreamWriter()\n\n    await p.write_with_length(writer, 10)\n    assert writer.get_written_bytes() == data\n    assert len(writer.get_written_bytes()) == 10\n\n\nasync def test_bytesio_payload_write_with_length_truncated() -> None:\n    \"\"\"Test BytesIOPayload writing with truncated content length.\"\"\"\n    data = b\"0123456789\"\n    payload_bytesio = payload.BytesIOPayload(io.BytesIO(data))\n    writer = MockStreamWriter()\n\n    await payload_bytesio.write_with_length(writer, 5)\n    assert writer.get_written_bytes() == b\"01234\"\n    assert len(writer.get_written_bytes()) == 5\n\n\nasync def test_bytesio_payload_write_with_length_remaining_zero() -> None:\n    \"\"\"Test BytesIOPayload with content_length smaller than first read chunk.\"\"\"\n    data = b\"0123456789\" * 10  # 100 bytes\n    bio = io.BytesIO(data)\n    payload_bytesio = payload.BytesIOPayload(bio)\n    writer = MockStreamWriter()\n\n    # Mock the read method to return smaller chunks\n    original_read = bio.read\n    read_calls = 0\n\n    def mock_read(size: int | None = None) -> bytes:\n        nonlocal read_calls\n        read_calls += 1\n        if read_calls == 1:\n            # First call: return 3 bytes (less than content_length=5)\n            return original_read(3)\n        else:\n            # Subsequent calls return remaining data normally\n            return original_read(size)\n\n    with unittest.mock.patch.object(bio, \"read\", mock_read):\n        await payload_bytesio.write_with_length(writer, 5)\n\n    assert len(writer.get_written_bytes()) == 5\n    assert writer.get_written_bytes() == b\"01234\"\n\n\nasync def test_bytesio_payload_large_data_multiple_chunks() -> None:\n    \"\"\"Test BytesIOPayload with large data requiring multiple read chunks.\"\"\"\n    chunk_size = 2**16  # 64KB (READ_SIZE)\n    data = b\"x\" * (chunk_size + 1000)  # Slightly larger than READ_SIZE\n    payload_bytesio = payload.BytesIOPayload(io.BytesIO(data))\n    writer = MockStreamWriter()\n\n    await payload_bytesio.write_with_length(writer, None)\n    assert writer.get_written_bytes() == data\n    assert len(writer.get_written_bytes()) == chunk_size + 1000\n\n\nasync def test_bytesio_payload_remaining_bytes_exhausted() -> None:\n    \"\"\"Test BytesIOPayload when remaining_bytes becomes <= 0.\"\"\"\n    data = b\"0123456789abcdef\" * 1000  # 16000 bytes\n    payload_bytesio = payload.BytesIOPayload(io.BytesIO(data))\n    writer = MockStreamWriter()\n\n    await payload_bytesio.write_with_length(writer, 8000)  # Exactly half the data\n    written = writer.get_written_bytes()\n    assert len(written) == 8000\n    assert written == data[:8000]\n\n\nasync def test_iobase_payload_exact_chunk_size_limit() -> None:\n    \"\"\"Test IOBasePayload with content length matching exactly one read chunk.\"\"\"\n    chunk_size = 2**16  # 65536 bytes (READ_SIZE)\n    data = b\"x\" * chunk_size + b\"extra\"  # Slightly larger than one read chunk\n    p = payload.IOBasePayload(io.BytesIO(data))\n    writer = MockStreamWriter()\n\n    await p.write_with_length(writer, chunk_size)\n    written = writer.get_written_bytes()\n    assert len(written) == chunk_size\n    assert written == data[:chunk_size]\n\n\nasync def test_iobase_payload_reads_in_chunks() -> None:\n    \"\"\"Test IOBasePayload reads data in chunks of READ_SIZE, not all at once.\"\"\"\n    # Create a large file that's multiple times larger than READ_SIZE\n    large_data = b\"x\" * (READ_SIZE * 3 + 1000)  # ~192KB + 1000 bytes\n\n    # Mock the file-like object to track read calls\n    mock_file = unittest.mock.Mock(spec=io.BytesIO)\n    mock_file.tell.return_value = 0\n    mock_file.fileno.side_effect = AttributeError  # Make size return None\n\n    # Track the sizes of read() calls\n    read_sizes = []\n\n    def mock_read(size: int) -> bytes:\n        read_sizes.append(size)\n        # Return data based on how many times read was called\n        call_count = len(read_sizes)\n        if call_count == 1:\n            return large_data[:size]\n        elif call_count == 2:\n            return large_data[READ_SIZE : READ_SIZE + size]\n        elif call_count == 3:\n            return large_data[READ_SIZE * 2 : READ_SIZE * 2 + size]\n        else:\n            return large_data[READ_SIZE * 3 :]\n\n    mock_file.read.side_effect = mock_read\n\n    payload_obj = payload.IOBasePayload(mock_file)\n    writer = MockStreamWriter()\n\n    # Write with a large content_length\n    await payload_obj.write_with_length(writer, len(large_data))\n\n    # Verify that reads were limited to READ_SIZE\n    assert len(read_sizes) > 1  # Should have multiple reads\n    for read_size in read_sizes:\n        assert (\n            read_size <= READ_SIZE\n        ), f\"Read size {read_size} exceeds READ_SIZE {READ_SIZE}\"\n\n\nasync def test_iobase_payload_large_content_length() -> None:\n    \"\"\"Test IOBasePayload with very large content_length doesn't read all at once.\"\"\"\n    data = b\"x\" * (READ_SIZE + 1000)\n\n    # Create a custom file-like object that tracks read sizes\n    class TrackingBytesIO(io.BytesIO):\n        def __init__(self, data: bytes) -> None:\n            super().__init__(data)\n            self.read_sizes: list[int] = []\n\n        def read(self, size: int | None = -1) -> bytes:\n            self.read_sizes.append(size if size is not None else -1)\n            return super().read(size)\n\n    tracking_file = TrackingBytesIO(data)\n    payload_obj = payload.IOBasePayload(tracking_file)\n    writer = MockStreamWriter()\n\n    # Write with a very large content_length (simulating the bug scenario)\n    large_content_length = 10 * 1024 * 1024  # 10MB\n    await payload_obj.write_with_length(writer, large_content_length)\n\n    # Verify no single read exceeded READ_SIZE\n    for read_size in tracking_file.read_sizes:\n        assert (\n            read_size <= READ_SIZE\n        ), f\"Read size {read_size} exceeds READ_SIZE {READ_SIZE}\"\n\n    # Verify the correct amount of data was written\n    assert writer.get_written_bytes() == data\n\n\nasync def test_textio_payload_reads_in_chunks() -> None:\n    \"\"\"Test TextIOPayload reads data in chunks of READ_SIZE, not all at once.\"\"\"\n    # Create a large text file that's multiple times larger than READ_SIZE\n    large_text = \"x\" * (READ_SIZE * 3 + 1000)  # ~192KB + 1000 chars\n\n    # Mock the file-like object to track read calls\n    mock_file = unittest.mock.Mock(spec=io.StringIO)\n    mock_file.tell.return_value = 0\n    mock_file.fileno.side_effect = AttributeError  # Make size return None\n    mock_file.encoding = \"utf-8\"\n\n    # Track the sizes of read() calls\n    read_sizes = []\n\n    def mock_read(size: int) -> str:\n        read_sizes.append(size)\n        # Return data based on how many times read was called\n        call_count = len(read_sizes)\n        if call_count == 1:\n            return large_text[:size]\n        elif call_count == 2:\n            return large_text[READ_SIZE : READ_SIZE + size]\n        elif call_count == 3:\n            return large_text[READ_SIZE * 2 : READ_SIZE * 2 + size]\n        else:\n            return large_text[READ_SIZE * 3 :]\n\n    mock_file.read.side_effect = mock_read\n\n    payload_obj = payload.TextIOPayload(mock_file)\n    writer = MockStreamWriter()\n\n    # Write with a large content_length\n    await payload_obj.write_with_length(writer, len(large_text.encode(\"utf-8\")))\n\n    # Verify that reads were limited to READ_SIZE\n    assert len(read_sizes) > 1  # Should have multiple reads\n    for read_size in read_sizes:\n        assert (\n            read_size <= READ_SIZE\n        ), f\"Read size {read_size} exceeds READ_SIZE {READ_SIZE}\"\n\n\nasync def test_textio_payload_large_content_length() -> None:\n    \"\"\"Test TextIOPayload with very large content_length doesn't read all at once.\"\"\"\n    text_data = \"x\" * (READ_SIZE + 1000)\n\n    # Create a custom file-like object that tracks read sizes\n    class TrackingStringIO(io.StringIO):\n        def __init__(self, data: str) -> None:\n            super().__init__(data)\n            self.read_sizes: list[int] = []\n\n        def read(self, size: int | None = -1) -> str:\n            self.read_sizes.append(size if size is not None else -1)\n            return super().read(size)\n\n    tracking_file = TrackingStringIO(text_data)\n    payload_obj = payload.TextIOPayload(tracking_file)\n    writer = MockStreamWriter()\n\n    # Write with a very large content_length (simulating the bug scenario)\n    large_content_length = 10 * 1024 * 1024  # 10MB\n    await payload_obj.write_with_length(writer, large_content_length)\n\n    # Verify no single read exceeded READ_SIZE\n    for read_size in tracking_file.read_sizes:\n        assert (\n            read_size <= READ_SIZE\n        ), f\"Read size {read_size} exceeds READ_SIZE {READ_SIZE}\"\n\n    # Verify the correct amount of data was written\n    assert writer.get_written_bytes() == text_data.encode(\"utf-8\")\n\n\nasync def test_async_iterable_payload_write_with_length_no_limit() -> None:\n    \"\"\"Test AsyncIterablePayload writing with no content length limit.\"\"\"\n\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"0123\"\n        yield b\"4567\"\n        yield b\"89\"\n\n    p = payload.AsyncIterablePayload(gen())\n    writer = MockStreamWriter()\n\n    await p.write_with_length(writer, None)\n    assert writer.get_written_bytes() == b\"0123456789\"\n    assert len(writer.get_written_bytes()) == 10\n\n\nasync def test_async_iterable_payload_write_with_length_exact() -> None:\n    \"\"\"Test AsyncIterablePayload writing with exact content length.\"\"\"\n\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"0123\"\n        yield b\"4567\"\n        yield b\"89\"\n\n    p = payload.AsyncIterablePayload(gen())\n    writer = MockStreamWriter()\n\n    await p.write_with_length(writer, 10)\n    assert writer.get_written_bytes() == b\"0123456789\"\n    assert len(writer.get_written_bytes()) == 10\n\n\nasync def test_async_iterable_payload_write_with_length_truncated_mid_chunk() -> None:\n    \"\"\"Test AsyncIterablePayload writing with content length truncating mid-chunk.\"\"\"\n\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"0123\"\n        yield b\"4567\"\n        yield b\"89\"  # pragma: no cover\n\n    p = payload.AsyncIterablePayload(gen())\n    writer = MockStreamWriter()\n\n    await p.write_with_length(writer, 6)\n    assert writer.get_written_bytes() == b\"012345\"\n    assert len(writer.get_written_bytes()) == 6\n\n\nasync def test_async_iterable_payload_write_with_length_truncated_at_chunk() -> None:\n    \"\"\"Test AsyncIterablePayload writing with content length truncating at chunk boundary.\"\"\"\n\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"0123\"\n        yield b\"4567\"  # pragma: no cover\n        yield b\"89\"  # pragma: no cover\n\n    p = payload.AsyncIterablePayload(gen())\n    writer = MockStreamWriter()\n\n    await p.write_with_length(writer, 4)\n    assert writer.get_written_bytes() == b\"0123\"\n    assert len(writer.get_written_bytes()) == 4\n\n\nasync def test_bytes_payload_backwards_compatibility() -> None:\n    \"\"\"Test BytesPayload.write() backwards compatibility delegates to write_with_length().\"\"\"\n    p = payload.BytesPayload(b\"1234567890\")\n    writer = MockStreamWriter()\n\n    await p.write(writer)\n    assert writer.get_written_bytes() == b\"1234567890\"\n\n\nasync def test_textio_payload_with_encoding() -> None:\n    \"\"\"Test TextIOPayload reading with encoding and size constraints.\"\"\"\n    data = io.StringIO(\"hello world\")\n    p = payload.TextIOPayload(data, encoding=\"utf-8\")\n    writer = MockStreamWriter()\n\n    await p.write_with_length(writer, 8)\n    # Should write exactly 8 bytes: \"hello wo\"\n    assert writer.get_written_bytes() == b\"hello wo\"\n\n\nasync def test_textio_payload_as_bytes() -> None:\n    \"\"\"Test TextIOPayload.as_bytes method with different encodings.\"\"\"\n    # Test with UTF-8 encoding\n    data = io.StringIO(\"Hello 世界\")\n    p = payload.TextIOPayload(data, encoding=\"utf-8\")\n\n    # Test as_bytes() method\n    result = await p.as_bytes()\n    assert result == \"Hello 世界\".encode()\n\n    # Test that position is restored for multiple reads\n    result2 = await p.as_bytes()\n    assert result2 == \"Hello 世界\".encode()\n\n    # Test with different encoding parameter (should use instance encoding)\n    result3 = await p.as_bytes(encoding=\"latin-1\")\n    assert result3 == \"Hello 世界\".encode()  # Should still use utf-8\n\n    # Test with different encoding in payload\n    data2 = io.StringIO(\"Hello World\")\n    p2 = payload.TextIOPayload(data2, encoding=\"latin-1\")\n    result4 = await p2.as_bytes()\n    assert result4 == b\"Hello World\"  # latin-1 encoding\n\n    # Test with no explicit encoding (defaults to utf-8)\n    data3 = io.StringIO(\"Test データ\")\n    p3 = payload.TextIOPayload(data3)\n    result5 = await p3.as_bytes()\n    assert result5 == \"Test データ\".encode()\n\n    # Test with encoding errors parameter\n    data4 = io.StringIO(\"Test\")\n    p4 = payload.TextIOPayload(data4, encoding=\"ascii\")\n    result6 = await p4.as_bytes(errors=\"strict\")\n    assert result6 == b\"Test\"\n\n\nasync def test_bytesio_payload_backwards_compatibility() -> None:\n    \"\"\"Test BytesIOPayload.write() backwards compatibility delegates to write_with_length().\"\"\"\n    data = io.BytesIO(b\"test data\")\n    p = payload.BytesIOPayload(data)\n    writer = MockStreamWriter()\n\n    await p.write(writer)\n    assert writer.get_written_bytes() == b\"test data\"\n\n\nasync def test_async_iterable_payload_backwards_compatibility() -> None:\n    \"\"\"Test AsyncIterablePayload.write() backwards compatibility delegates to write_with_length().\"\"\"\n\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"chunk1\"\n        yield b\"chunk2\"  # pragma: no cover\n\n    p = payload.AsyncIterablePayload(gen())\n    writer = MockStreamWriter()\n\n    await p.write(writer)\n    assert writer.get_written_bytes() == b\"chunk1chunk2\"\n\n\nasync def test_async_iterable_payload_with_none_iterator() -> None:\n    \"\"\"Test AsyncIterablePayload with None iterator returns early without writing.\"\"\"\n\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"test\"  # pragma: no cover\n\n    p = payload.AsyncIterablePayload(gen())\n    # Manually set _iter to None to test the guard clause\n    p._iter = None\n    writer = MockStreamWriter()\n\n    # Should return early without writing anything\n    await p.write_with_length(writer, 10)\n    assert writer.get_written_bytes() == b\"\"\n\n\nasync def test_async_iterable_payload_caching() -> None:\n    \"\"\"Test AsyncIterablePayload caching behavior.\"\"\"\n\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"Hello\"\n        yield b\" \"\n        yield b\"World\"\n\n    p = payload.AsyncIterablePayload(gen())\n\n    # First call to as_bytes should consume iterator and cache\n    result1 = await p.as_bytes()\n    assert result1 == b\"Hello World\"\n    assert p._iter is None  # Iterator exhausted\n    assert p._cached_chunks == [b\"Hello\", b\" \", b\"World\"]  # Chunks cached\n    assert p._consumed is False  # Not marked as consumed to allow reuse\n\n    # Second call should use cache\n    result2 = await p.as_bytes()\n    assert result2 == b\"Hello World\"\n    assert p._cached_chunks == [b\"Hello\", b\" \", b\"World\"]  # Still cached\n\n    # decode should work with cached chunks\n    decoded = p.decode()\n    assert decoded == \"Hello World\"\n\n    # write_with_length should use cached chunks\n    writer = MockStreamWriter()\n    await p.write_with_length(writer, None)\n    assert writer.get_written_bytes() == b\"Hello World\"\n\n    # write_with_length with limit should respect it\n    writer2 = MockStreamWriter()\n    await p.write_with_length(writer2, 5)\n    assert writer2.get_written_bytes() == b\"Hello\"\n\n\nasync def test_async_iterable_payload_decode_without_cache() -> None:\n    \"\"\"Test AsyncIterablePayload decode raises error without cache.\"\"\"\n\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"test\"\n\n    p = payload.AsyncIterablePayload(gen())\n\n    # decode should raise without cache\n    with pytest.raises(TypeError) as excinfo:\n        p.decode()\n    assert \"Unable to decode - content not cached\" in str(excinfo.value)\n\n    # After as_bytes, decode should work\n    await p.as_bytes()\n    assert p.decode() == \"test\"\n\n\nasync def test_async_iterable_payload_write_then_cache() -> None:\n    \"\"\"Test AsyncIterablePayload behavior when written before caching.\"\"\"\n\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"Hello\"\n        yield b\"World\"\n\n    p = payload.AsyncIterablePayload(gen())\n\n    # First write without caching (streaming)\n    writer1 = MockStreamWriter()\n    await p.write_with_length(writer1, None)\n    assert writer1.get_written_bytes() == b\"HelloWorld\"\n    assert p._iter is None  # Iterator exhausted\n    assert p._cached_chunks is None  # No cache created\n    assert p._consumed is True  # Marked as consumed\n\n    # Subsequent operations should handle exhausted iterator\n    result = await p.as_bytes()\n    assert result == b\"\"  # Empty since iterator exhausted without cache\n\n    # Write should also be empty\n    writer2 = MockStreamWriter()\n    await p.write_with_length(writer2, None)\n    assert writer2.get_written_bytes() == b\"\"\n\n\nasync def test_bytes_payload_reusability() -> None:\n    \"\"\"Test that BytesPayload can be written and read multiple times.\"\"\"\n    data = b\"test payload data\"\n    p = payload.BytesPayload(data)\n\n    # First write_with_length\n    writer1 = MockStreamWriter()\n    await p.write_with_length(writer1, None)\n    assert writer1.get_written_bytes() == data\n\n    # Second write_with_length (simulating redirect)\n    writer2 = MockStreamWriter()\n    await p.write_with_length(writer2, None)\n    assert writer2.get_written_bytes() == data\n\n    # Write with partial length\n    writer3 = MockStreamWriter()\n    await p.write_with_length(writer3, 5)\n    assert writer3.get_written_bytes() == b\"test \"\n\n    # Test as_bytes multiple times\n    bytes1 = await p.as_bytes()\n    bytes2 = await p.as_bytes()\n    bytes3 = await p.as_bytes()\n    assert bytes1 == bytes2 == bytes3 == data\n\n\nasync def test_string_payload_reusability() -> None:\n    \"\"\"Test that StringPayload can be written and read multiple times.\"\"\"\n    text = \"test string data\"\n    expected_bytes = text.encode(\"utf-8\")\n    p = payload.StringPayload(text)\n\n    # First write_with_length\n    writer1 = MockStreamWriter()\n    await p.write_with_length(writer1, None)\n    assert writer1.get_written_bytes() == expected_bytes\n\n    # Second write_with_length (simulating redirect)\n    writer2 = MockStreamWriter()\n    await p.write_with_length(writer2, None)\n    assert writer2.get_written_bytes() == expected_bytes\n\n    # Write with partial length\n    writer3 = MockStreamWriter()\n    await p.write_with_length(writer3, 5)\n    assert writer3.get_written_bytes() == b\"test \"\n\n    # Test as_bytes multiple times\n    bytes1 = await p.as_bytes()\n    bytes2 = await p.as_bytes()\n    bytes3 = await p.as_bytes()\n    assert bytes1 == bytes2 == bytes3 == expected_bytes\n\n\nasync def test_bytes_io_payload_reusability() -> None:\n    \"\"\"Test that BytesIOPayload can be written and read multiple times.\"\"\"\n    data = b\"test bytesio payload\"\n    bytes_io = io.BytesIO(data)\n    p = payload.BytesIOPayload(bytes_io)\n\n    # First write_with_length\n    writer1 = MockStreamWriter()\n    await p.write_with_length(writer1, None)\n    assert writer1.get_written_bytes() == data\n\n    # Second write_with_length (simulating redirect)\n    writer2 = MockStreamWriter()\n    await p.write_with_length(writer2, None)\n    assert writer2.get_written_bytes() == data\n\n    # Write with partial length\n    writer3 = MockStreamWriter()\n    await p.write_with_length(writer3, 5)\n    assert writer3.get_written_bytes() == b\"test \"\n\n    # Test as_bytes multiple times\n    bytes1 = await p.as_bytes()\n    bytes2 = await p.as_bytes()\n    bytes3 = await p.as_bytes()\n    assert bytes1 == bytes2 == bytes3 == data\n\n\nasync def test_string_io_payload_reusability() -> None:\n    \"\"\"Test that StringIOPayload can be written and read multiple times.\"\"\"\n    text = \"test stringio payload\"\n    expected_bytes = text.encode(\"utf-8\")\n    string_io = io.StringIO(text)\n    p = payload.StringIOPayload(string_io)\n\n    # Note: StringIOPayload reads all content in __init__ and becomes a StringPayload\n    # So it should be fully reusable\n\n    # First write_with_length\n    writer1 = MockStreamWriter()\n    await p.write_with_length(writer1, None)\n    assert writer1.get_written_bytes() == expected_bytes\n\n    # Second write_with_length (simulating redirect)\n    writer2 = MockStreamWriter()\n    await p.write_with_length(writer2, None)\n    assert writer2.get_written_bytes() == expected_bytes\n\n    # Write with partial length\n    writer3 = MockStreamWriter()\n    await p.write_with_length(writer3, 5)\n    assert writer3.get_written_bytes() == b\"test \"\n\n    # Test as_bytes multiple times\n    bytes1 = await p.as_bytes()\n    bytes2 = await p.as_bytes()\n    bytes3 = await p.as_bytes()\n    assert bytes1 == bytes2 == bytes3 == expected_bytes\n\n\nasync def test_buffered_reader_payload_reusability() -> None:\n    \"\"\"Test that BufferedReaderPayload can be written and read multiple times.\"\"\"\n    data = b\"test buffered reader payload\"\n    buffer = io.BufferedReader(io.BytesIO(data))\n    p = payload.BufferedReaderPayload(buffer)\n\n    # First write_with_length\n    writer1 = MockStreamWriter()\n    await p.write_with_length(writer1, None)\n    assert writer1.get_written_bytes() == data\n\n    # Second write_with_length (simulating redirect)\n    writer2 = MockStreamWriter()\n    await p.write_with_length(writer2, None)\n    assert writer2.get_written_bytes() == data\n\n    # Write with partial length\n    writer3 = MockStreamWriter()\n    await p.write_with_length(writer3, 5)\n    assert writer3.get_written_bytes() == b\"test \"\n\n    # Test as_bytes multiple times\n    bytes1 = await p.as_bytes()\n    bytes2 = await p.as_bytes()\n    bytes3 = await p.as_bytes()\n    assert bytes1 == bytes2 == bytes3 == data\n\n\nasync def test_async_iterable_payload_reusability_with_cache() -> None:\n    \"\"\"Test that AsyncIterablePayload can be reused when cached via as_bytes.\"\"\"\n\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"async \"\n        yield b\"iterable \"\n        yield b\"payload\"\n\n    expected_data = b\"async iterable payload\"\n    p = payload.AsyncIterablePayload(gen())\n\n    # First call to as_bytes should cache the data\n    bytes1 = await p.as_bytes()\n    assert bytes1 == expected_data\n    assert p._cached_chunks is not None\n    assert p._iter is None  # Iterator exhausted\n\n    # Subsequent as_bytes calls should use cache\n    bytes2 = await p.as_bytes()\n    bytes3 = await p.as_bytes()\n    assert bytes1 == bytes2 == bytes3 == expected_data\n\n    # Now writes should also use the cached data\n    writer1 = MockStreamWriter()\n    await p.write_with_length(writer1, None)\n    assert writer1.get_written_bytes() == expected_data\n\n    # Second write should also work\n    writer2 = MockStreamWriter()\n    await p.write_with_length(writer2, None)\n    assert writer2.get_written_bytes() == expected_data\n\n    # Write with partial length\n    writer3 = MockStreamWriter()\n    await p.write_with_length(writer3, 5)\n    assert writer3.get_written_bytes() == b\"async\"\n\n\nasync def test_async_iterable_payload_no_reuse_without_cache() -> None:\n    \"\"\"Test that AsyncIterablePayload cannot be reused without caching.\"\"\"\n\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"test \"\n        yield b\"data\"\n\n    p = payload.AsyncIterablePayload(gen())\n\n    # First write exhausts the iterator\n    writer1 = MockStreamWriter()\n    await p.write_with_length(writer1, None)\n    assert writer1.get_written_bytes() == b\"test data\"\n    assert p._iter is None  # Iterator exhausted\n    assert p._consumed is True\n\n    # Second write should produce empty result\n    writer2 = MockStreamWriter()\n    await p.write_with_length(writer2, None)\n    assert writer2.get_written_bytes() == b\"\"\n\n\nasync def test_bytes_io_payload_close_does_not_close_io() -> None:\n    \"\"\"Test that BytesIOPayload close() does not close the underlying BytesIO.\"\"\"\n    bytes_io = io.BytesIO(b\"data\")\n    bytes_io_payload = payload.BytesIOPayload(bytes_io)\n\n    # Close the payload\n    await bytes_io_payload.close()\n\n    # BytesIO should NOT be closed\n    assert not bytes_io.closed\n\n    # Can still write after close\n    writer = MockStreamWriter()\n    await bytes_io_payload.write_with_length(writer, None)\n    assert writer.get_written_bytes() == b\"data\"\n\n\nasync def test_custom_payload_backwards_compat_as_bytes() -> None:\n    \"\"\"Test backwards compatibility for custom Payload that only implements decode().\"\"\"\n\n    class LegacyPayload(payload.Payload):\n        \"\"\"A custom payload that only implements decode() like old code might do.\"\"\"\n\n        def __init__(self, data: str) -> None:\n            super().__init__(data, headers=CIMultiDict())\n            self._data = data\n\n        def decode(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> str:\n            \"\"\"Custom decode implementation.\"\"\"\n            return self._data\n\n        async def write(self, writer: AbstractStreamWriter) -> None:\n            \"\"\"Write implementation which is a no-op for this test.\"\"\"\n\n    # Create instance with test data\n    p = LegacyPayload(\"Hello, World!\")\n\n    # Test that as_bytes() works even though it's not explicitly implemented\n    # The base class should call decode() and encode the result\n    result = await p.as_bytes()\n    assert result == b\"Hello, World!\"\n\n    # Test with different text\n    p2 = LegacyPayload(\"Test with special chars: café\")\n    result_utf8 = await p2.as_bytes(encoding=\"utf-8\")\n    assert result_utf8 == \"Test with special chars: café\".encode()\n\n    # Test that decode() still works as expected\n    assert p.decode() == \"Hello, World!\"\n    assert p2.decode() == \"Test with special chars: café\"\n\n\nasync def test_custom_payload_with_encoding_backwards_compat() -> None:\n    \"\"\"Test custom Payload with encoding set uses instance encoding for as_bytes().\"\"\"\n\n    class EncodedPayload(payload.Payload):\n        \"\"\"A custom payload with specific encoding.\"\"\"\n\n        def __init__(self, data: str, encoding: str) -> None:\n            super().__init__(data, headers=CIMultiDict(), encoding=encoding)\n            self._data = data\n\n        def decode(self, encoding: str = \"utf-8\", errors: str = \"strict\") -> str:\n            \"\"\"Custom decode implementation.\"\"\"\n            return self._data\n\n        async def write(self, writer: AbstractStreamWriter) -> None:\n            \"\"\"Write implementation is a no-op.\"\"\"\n\n    # Create instance with specific encoding\n    p = EncodedPayload(\"Test data\", encoding=\"latin-1\")\n\n    # as_bytes() should use the instance encoding (latin-1) not the default utf-8\n    result = await p.as_bytes()\n    assert result == b\"Test data\"  # ASCII chars are same in latin-1\n\n    # Test with non-ASCII that differs between encodings\n    p2 = EncodedPayload(\"café\", encoding=\"latin-1\")\n    result_latin1 = await p2.as_bytes()\n    assert result_latin1 == \"café\".encode(\"latin-1\")\n    assert result_latin1 != \"café\".encode()  # Should be different bytes\n\n\nasync def test_iobase_payload_close_idempotent() -> None:\n    \"\"\"Test that IOBasePayload.close() is idempotent and covers the _consumed check.\"\"\"\n    file_like = io.BytesIO(b\"test data\")\n    p = payload.IOBasePayload(file_like)\n\n    # First close should set _consumed to True\n    await p.close()\n    assert p._consumed is True\n\n    # Second close should be a no-op due to _consumed check (line 621)\n    await p.close()\n    assert p._consumed is True\n\n\ndef test_iobase_payload_decode() -> None:\n    \"\"\"Test IOBasePayload.decode() returns correct string.\"\"\"\n    # Test with UTF-8 encoded text\n    text = \"Hello, 世界! 🌍\"\n    file_like = io.BytesIO(text.encode(\"utf-8\"))\n    p = payload.IOBasePayload(file_like)\n\n    # decode() should return the original string\n    assert p.decode() == text\n\n    # Test with different encoding\n    latin1_text = \"café\"\n    file_like2 = io.BytesIO(latin1_text.encode(\"latin-1\"))\n    p2 = payload.IOBasePayload(file_like2)\n    assert p2.decode(\"latin-1\") == latin1_text\n\n    # Test that file position is restored\n    file_like3 = io.BytesIO(b\"test data\")\n    file_like3.read(4)  # Move position forward\n    p3 = payload.IOBasePayload(file_like3)\n    # decode() should read from the stored start position (4)\n    assert p3.decode() == \" data\"\n\n\ndef test_bytes_payload_size() -> None:\n    \"\"\"Test BytesPayload.size property returns correct byte length.\"\"\"\n    # Test with bytes\n    bp = payload.BytesPayload(b\"Hello World\")\n    assert bp.size == 11\n\n    # Test with empty bytes\n    bp_empty = payload.BytesPayload(b\"\")\n    assert bp_empty.size == 0\n\n    # Test with bytearray\n    ba = bytearray(b\"Hello World\")\n    bp_array = payload.BytesPayload(ba)\n    assert bp_array.size == 11\n\n\ndef test_string_payload_size() -> None:\n    \"\"\"Test StringPayload.size property with different encodings.\"\"\"\n    # Test ASCII string with default UTF-8 encoding\n    sp = payload.StringPayload(\"Hello World\")\n    assert sp.size == 11\n\n    # Test Unicode string with default UTF-8 encoding\n    unicode_str = \"Hello 世界\"\n    sp_unicode = payload.StringPayload(unicode_str)\n    assert sp_unicode.size == len(unicode_str.encode(\"utf-8\"))\n\n    # Test with UTF-16 encoding\n    sp_utf16 = payload.StringPayload(\"Hello World\", encoding=\"utf-16\")\n    assert sp_utf16.size == len(\"Hello World\".encode(\"utf-16\"))\n\n    # Test with latin-1 encoding\n    sp_latin1 = payload.StringPayload(\"café\", encoding=\"latin-1\")\n    assert sp_latin1.size == len(\"café\".encode(\"latin-1\"))\n\n\ndef test_string_io_payload_size() -> None:\n    \"\"\"Test StringIOPayload.size property.\"\"\"\n    # Test normal string\n    sio = StringIO(\"Hello World\")\n    siop = payload.StringIOPayload(sio)\n    assert siop.size == 11\n\n    # Test Unicode string\n    sio_unicode = StringIO(\"Hello 世界\")\n    siop_unicode = payload.StringIOPayload(sio_unicode)\n    assert siop_unicode.size == len(\"Hello 世界\".encode())\n\n    # Test with custom encoding\n    sio_custom = StringIO(\"Hello\")\n    siop_custom = payload.StringIOPayload(sio_custom, encoding=\"utf-16\")\n    assert siop_custom.size == len(\"Hello\".encode(\"utf-16\"))\n\n    # Test with emoji to ensure correct byte count\n    sio_emoji = StringIO(\"Hello 👋🌍\")\n    siop_emoji = payload.StringIOPayload(sio_emoji)\n    assert siop_emoji.size == len(\"Hello 👋🌍\".encode())\n    # Verify it's not the string length\n    assert siop_emoji.size != len(\"Hello 👋🌍\")\n\n\ndef test_all_string_payloads_size_is_bytes() -> None:\n    \"\"\"Test that all string-like payload classes report size in bytes, not string length.\"\"\"\n    # Test string with multibyte characters\n    test_str = \"Hello 👋 世界 🌍\"  # Contains emoji and Chinese characters\n\n    # StringPayload\n    sp = payload.StringPayload(test_str)\n    assert sp.size == len(test_str.encode(\"utf-8\"))\n    assert sp.size != len(test_str)  # Ensure it's not string length\n\n    # StringIOPayload\n    sio = StringIO(test_str)\n    siop = payload.StringIOPayload(sio)\n    assert siop.size == len(test_str.encode(\"utf-8\"))\n    assert siop.size != len(test_str)\n\n    # Test with different encoding\n    sp_utf16 = payload.StringPayload(test_str, encoding=\"utf-16\")\n    assert sp_utf16.size == len(test_str.encode(\"utf-16\"))\n    assert sp_utf16.size != sp.size  # Different encoding = different size\n\n    # JsonPayload (which extends BytesPayload)\n    json_data = {\"message\": test_str}\n    jp = payload.JsonPayload(json_data)\n    # JSON escapes Unicode, so we need to check the actual encoded size\n    json_str = json.dumps(json_data)\n    assert jp.size == len(json_str.encode(\"utf-8\"))\n\n    # Test JsonPayload with ensure_ascii=False to get actual UTF-8 encoding\n    jp_utf8 = payload.JsonPayload(\n        json_data, dumps=lambda x: json.dumps(x, ensure_ascii=False)\n    )\n    json_str_utf8 = json.dumps(json_data, ensure_ascii=False)\n    assert jp_utf8.size == len(json_str_utf8.encode(\"utf-8\"))\n    assert jp_utf8.size != len(\n        json_str_utf8\n    )  # Now it's different due to multibyte chars\n\n\ndef test_bytes_io_payload_size() -> None:\n    \"\"\"Test BytesIOPayload.size property.\"\"\"\n    # Test normal bytes\n    bio = io.BytesIO(b\"Hello World\")\n    biop = payload.BytesIOPayload(bio)\n    assert biop.size == 11\n\n    # Test empty BytesIO\n    bio_empty = io.BytesIO(b\"\")\n    biop_empty = payload.BytesIOPayload(bio_empty)\n    assert biop_empty.size == 0\n\n    # Test with position not at start\n    bio_pos = io.BytesIO(b\"Hello World\")\n    bio_pos.seek(5)\n    biop_pos = payload.BytesIOPayload(bio_pos)\n    assert biop_pos.size == 6  # Size should be from position to end\n\n\ndef test_json_payload_size() -> None:\n    \"\"\"Test JsonPayload.size property.\"\"\"\n    # Test simple dict\n    data = {\"hello\": \"world\"}\n    jp = payload.JsonPayload(data)\n    expected_json = json.dumps(data)  # Use actual json.dumps output\n    assert jp.size == len(expected_json.encode(\"utf-8\"))\n\n    # Test with Unicode\n    data_unicode = {\"message\": \"Hello 世界\"}\n    jp_unicode = payload.JsonPayload(data_unicode)\n    expected_unicode = json.dumps(data_unicode)\n    assert jp_unicode.size == len(expected_unicode.encode(\"utf-8\"))\n\n    # Test with custom encoding\n    data_custom = {\"test\": \"data\"}\n    jp_custom = payload.JsonPayload(data_custom, encoding=\"utf-16\")\n    expected_custom = json.dumps(data_custom)\n    assert jp_custom.size == len(expected_custom.encode(\"utf-16\"))\n\n\ndef test_json_bytes_payload() -> None:\n    \"\"\"Test JsonBytesPayload with a bytes-returning encoder.\"\"\"\n    data = {\"hello\": \"world\"}\n\n    # Test with standard library encoder\n    jp = payload.JsonBytesPayload(data, dumps=lambda x: json.dumps(x).encode(\"utf-8\"))\n    expected = json.dumps(data).encode(\"utf-8\")\n    assert jp.size == len(expected)\n\n    # Test with custom bytes-returning encoder (compact separators)\n    jp_custom = payload.JsonBytesPayload(\n        data, dumps=lambda x: json.dumps(x, separators=(\",\", \":\")).encode(\"utf-8\")\n    )\n    expected_custom = json.dumps(data, separators=(\",\", \":\")).encode(\"utf-8\")\n    assert jp_custom.size == len(expected_custom)\n\n\ndef test_json_bytes_payload_content_type() -> None:\n    \"\"\"Test JsonBytesPayload content_type.\"\"\"\n    data = {\"test\": \"data\"}\n\n    # Default content type\n    jp = payload.JsonBytesPayload(data, dumps=lambda x: json.dumps(x).encode(\"utf-8\"))\n    assert jp.content_type == \"application/json\"\n\n    # Custom content type\n    jp_custom = payload.JsonBytesPayload(\n        data,\n        dumps=lambda x: json.dumps(x).encode(\"utf-8\"),\n        content_type=\"application/vnd.api+json\",\n    )\n    assert jp_custom.content_type == \"application/vnd.api+json\"\n\n\nasync def test_text_io_payload_size_matches_file_encoding(tmp_path: Path) -> None:\n    \"\"\"Test TextIOPayload.size when file encoding matches payload encoding.\"\"\"\n    # Create UTF-8 file\n    utf8_file = tmp_path / \"test_utf8.txt\"\n    content = \"Hello 世界\"\n\n    # Write file in executor\n    loop = asyncio.get_running_loop()\n    await loop.run_in_executor(None, utf8_file.write_text, content, \"utf-8\")\n\n    # Open file in executor\n    def open_file() -> TextIO:\n        return open(utf8_file, encoding=\"utf-8\")\n\n    f = await loop.run_in_executor(None, open_file)\n    try:\n        tiop = payload.TextIOPayload(f)\n        # Size should match the actual UTF-8 encoded size\n        assert tiop.size == len(content.encode(\"utf-8\"))\n    finally:\n        await loop.run_in_executor(None, f.close)\n\n\nasync def test_text_io_payload_size_utf16(tmp_path: Path) -> None:\n    \"\"\"Test TextIOPayload.size reports correct size with utf-16.\"\"\"\n    # Create UTF-16 file\n    utf16_file = tmp_path / \"test_utf16.txt\"\n    content = \"Hello World\"\n\n    loop = asyncio.get_running_loop()\n    # Write file in executor\n    await loop.run_in_executor(None, utf16_file.write_text, content, \"utf-16\")\n\n    # Get file size in executor\n    utf16_file_size = await loop.run_in_executor(\n        None, lambda: utf16_file.stat().st_size\n    )\n\n    # Open file in executor\n    def open_file() -> TextIO:\n        return open(utf16_file, encoding=\"utf-16\")\n\n    f = await loop.run_in_executor(None, open_file)\n    try:\n        tiop = payload.TextIOPayload(f, encoding=\"utf-16\")\n        # Payload reports file size on disk (UTF-16)\n        assert tiop.size == utf16_file_size\n\n        # Write to a buffer to see what actually gets sent\n        writer = BufferWriter()\n        await tiop.write(writer)\n\n        # Check that the actual written bytes match file size\n        assert len(writer.buffer) == utf16_file_size\n    finally:\n        await loop.run_in_executor(None, f.close)\n\n\nasync def test_iobase_payload_size_after_reading(tmp_path: Path) -> None:\n    \"\"\"Test that IOBasePayload.size returns correct size after file has been read.\n\n    This verifies that size calculation properly accounts for the initial\n    file position, which is critical for 307/308 redirects where the same\n    payload instance is reused.\n    \"\"\"\n    # Create a test file with known content\n    test_file = tmp_path / \"test.txt\"\n    content = b\"Hello, World! This is test content.\"\n    await asyncio.to_thread(test_file.write_bytes, content)\n    expected_size = len(content)\n\n    # Open the file and create payload\n    f = await asyncio.to_thread(open, test_file, \"rb\")\n    try:\n        p = payload.BufferedReaderPayload(f)\n\n        # First size check - should return full file size\n        assert p.size == expected_size\n\n        # Read the file (simulating first request)\n        writer = BufferWriter()\n        await p.write(writer)\n        assert len(writer.buffer) == expected_size\n\n        # Second size check - should still return full file size\n        assert p.size == expected_size\n\n        # Attempting to write again should write the full content\n        writer2 = BufferWriter()\n        await p.write(writer2)\n        assert len(writer2.buffer) == expected_size\n    finally:\n        await asyncio.to_thread(f.close)\n\n\nasync def test_iobase_payload_size_unseekable() -> None:\n    \"\"\"Test that IOBasePayload.size returns None for unseekable files.\"\"\"\n\n    class UnseekableFile:\n        \"\"\"Mock file object that doesn't support seeking.\"\"\"\n\n        def __init__(self, content: bytes) -> None:\n            self.content = content\n            self.pos = 0\n\n        def read(self, size: int) -> bytes:\n            result = self.content[self.pos : self.pos + size]\n            self.pos += len(result)\n            return result\n\n        def tell(self) -> int:\n            raise OSError(\"Unseekable file\")\n\n    content = b\"Unseekable content\"\n    f = UnseekableFile(content)\n    p = payload.IOBasePayload(f)  # type: ignore[arg-type]\n\n    # Size should return None for unseekable files\n    assert p.size is None\n\n    # Payload should not be consumed before writing\n    assert p.consumed is False\n\n    # Writing should still work\n    writer = BufferWriter()\n    await p.write(writer)\n    assert writer.buffer == content\n\n    # For unseekable files that can't tell() or seek(),\n    # they are marked as consumed after the first write\n    assert p.consumed is True\n\n\nasync def test_empty_bytes_payload_is_reusable() -> None:\n    \"\"\"Test that empty BytesPayload can be safely reused across requests.\"\"\"\n    empty_payload = payload.PAYLOAD_REGISTRY.get(b\"\", disposition=None)\n\n    assert isinstance(empty_payload, payload.BytesPayload)\n    assert empty_payload.size == 0\n    assert empty_payload.consumed is False\n    assert empty_payload.autoclose is True\n\n    initial_headers = dict(empty_payload.headers)\n\n    for i in range(3):\n        writer = BufferWriter()\n        await empty_payload.write_with_length(writer, None)\n\n        assert writer.buffer == b\"\"\n        assert empty_payload.consumed is False, f\"consumed flag changed on write {i+1}\"\n        assert (\n            dict(empty_payload.headers) == initial_headers\n        ), f\"headers mutated on write {i+1}\"\n        assert empty_payload.size == 0, f\"size changed on write {i+1}\"\n\n    assert empty_payload.headers == CIMultiDict(initial_headers)\n"
  },
  {
    "path": "tests/test_proxy.py",
    "content": "import asyncio\nimport socket\nimport ssl\nimport sys\nfrom typing import Callable\nfrom unittest import mock\n\nimport pytest\nfrom multidict import CIMultiDict\nfrom yarl import URL\n\nimport aiohttp\nfrom aiohttp.client_reqrep import (\n    ClientRequest,\n    ClientRequestArgs,\n    ClientRequestBase,\n    ClientResponse,\n    Fingerprint,\n)\nfrom aiohttp.connector import _SSL_CONTEXT_VERIFIED\nfrom aiohttp.helpers import TimerNoop\n\nif sys.version_info >= (3, 11):\n    from typing import Unpack\n\n    _RequestMaker = Callable[[str, URL, Unpack[ClientRequestArgs]], ClientRequest]\nelse:\n    from typing import Any\n\n    _RequestMaker = Any\n\n\n@mock.patch(\"aiohttp.connector.ClientRequestBase\")\n@mock.patch(\n    \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n    autospec=True,\n    spec_set=True,\n)\nasync def test_connect(  # type: ignore[misc]\n    start_connection: mock.Mock,\n    ClientRequestMock: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    event_loop = asyncio.get_running_loop()\n    req = make_client_request(\n        \"GET\",\n        URL(\"http://www.python.org\"),\n        proxy=URL(\"http://proxy.example.com\"),\n        loop=event_loop,\n        ssl=True,\n        headers=CIMultiDict({}),\n    )\n    assert str(req.proxy) == \"http://proxy.example.com\"\n\n    connector = aiohttp.TCPConnector()\n    r = {\n        \"hostname\": \"hostname\",\n        \"host\": \"127.0.0.1\",\n        \"port\": 80,\n        \"family\": socket.AF_INET,\n        \"proto\": 0,\n        \"flags\": 0,\n    }\n    with mock.patch.object(connector, \"_resolve_host\", autospec=True, return_value=[r]):\n        proto = mock.Mock(\n            **{\n                \"transport.get_extra_info.return_value\": False,\n            }\n        )\n        with mock.patch.object(\n            event_loop,\n            \"create_connection\",\n            autospec=True,\n            return_value=(proto.transport, proto),\n        ):\n            conn = await connector.connect(req, [], aiohttp.ClientTimeout())\n            assert req.url == URL(\"http://www.python.org\")\n            assert conn._protocol is proto\n            assert conn.transport is proto.transport\n\n            ClientRequestMock.assert_called_with(\n                \"GET\",\n                URL(\"http://proxy.example.com\"),\n                auth=None,\n                headers={\"Host\": \"www.python.org\"},\n                loop=event_loop,\n                ssl=True,\n            )\n\n            conn.close()\n    await connector.close()\n\n\n@mock.patch(\"aiohttp.connector.ClientRequestBase\")\n@mock.patch(\n    \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n    autospec=True,\n    spec_set=True,\n)\nasync def test_proxy_headers(  # type: ignore[misc]\n    start_connection: mock.Mock,\n    ClientRequestMock: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    event_loop = asyncio.get_running_loop()\n    req = make_client_request(\n        \"GET\",\n        URL(\"http://www.python.org\"),\n        proxy=URL(\"http://proxy.example.com\"),\n        proxy_headers=CIMultiDict({\"Foo\": \"Bar\"}),\n        loop=event_loop,\n        ssl=True,\n        headers=CIMultiDict({}),\n    )\n    assert str(req.proxy) == \"http://proxy.example.com\"\n\n    connector = aiohttp.TCPConnector()\n    r = {\n        \"hostname\": \"hostname\",\n        \"host\": \"127.0.0.1\",\n        \"port\": 80,\n        \"family\": socket.AF_INET,\n        \"proto\": 0,\n        \"flags\": 0,\n    }\n    with mock.patch.object(connector, \"_resolve_host\", autospec=True, return_value=[r]):\n        proto = mock.Mock(\n            **{\n                \"transport.get_extra_info.return_value\": False,\n            }\n        )\n        with mock.patch.object(\n            event_loop,\n            \"create_connection\",\n            autospec=True,\n            return_value=(proto.transport, proto),\n        ):\n            conn = await connector.connect(req, [], aiohttp.ClientTimeout())\n            assert req.url == URL(\"http://www.python.org\")\n            assert conn._protocol is proto\n            assert conn.transport is proto.transport\n\n            ClientRequestMock.assert_called_with(\n                \"GET\",\n                URL(\"http://proxy.example.com\"),\n                auth=None,\n                headers={\"Host\": \"www.python.org\", \"Foo\": \"Bar\"},\n                loop=event_loop,\n                ssl=True,\n            )\n\n            conn.close()\n    await connector.close()\n\n\n@mock.patch(\n    \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n    autospec=True,\n    spec_set=True,\n)\nasync def test_proxy_auth(  # type: ignore[misc]\n    start_connection: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    msg = r\"proxy_auth must be None or BasicAuth\\(\\) tuple\"\n    with pytest.raises(ValueError, match=msg):\n        make_client_request(\n            \"GET\",\n            URL(\"http://python.org\"),\n            proxy=URL(\"http://proxy.example.com\"),\n            proxy_auth=(\"user\", \"pass\"),  # type: ignore[arg-type]\n            loop=mock.Mock(),\n        )\n\n\n@mock.patch(\n    \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n    autospec=True,\n    spec_set=True,\n)\nasync def test_proxy_dns_error(  # type: ignore[misc]\n    start_connection: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    connector = aiohttp.TCPConnector()\n    with mock.patch.object(\n        connector,\n        \"_resolve_host\",\n        autospec=True,\n        side_effect=OSError(\"dont take it serious\"),\n    ):\n        req = make_client_request(\n            \"GET\",\n            URL(\"http://www.python.org\"),\n            proxy=URL(\"http://proxy.example.com\"),\n            loop=asyncio.get_running_loop(),\n        )\n        expected_headers = dict(req.headers)\n        with pytest.raises(aiohttp.ClientConnectorError):\n            await connector.connect(req, [], aiohttp.ClientTimeout())\n        assert req.url.path == \"/\"\n        assert dict(req.headers) == expected_headers\n    await connector.close()\n\n\n@mock.patch(\n    \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n    autospec=True,\n    spec_set=True,\n    return_value=mock.create_autospec(socket.socket, spec_set=True, instance=True),\n)\nasync def test_proxy_connection_error(  # type: ignore[misc]\n    start_connection: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    connector = aiohttp.TCPConnector()\n    r = {\n        \"hostname\": \"www.python.org\",\n        \"host\": \"127.0.0.1\",\n        \"port\": 80,\n        \"family\": socket.AF_INET,\n        \"proto\": 0,\n        \"flags\": socket.AI_NUMERICHOST,\n    }\n    with mock.patch.object(connector, \"_resolve_host\", autospec=True, return_value=[r]):\n        with mock.patch.object(\n            connector._loop,\n            \"create_connection\",\n            autospec=True,\n            side_effect=OSError(\"dont take it serious\"),\n        ):\n            req = make_client_request(\n                \"GET\",\n                URL(\"http://www.python.org\"),\n                proxy=URL(\"http://proxy.example.com\"),\n            )\n            with pytest.raises(aiohttp.ClientProxyConnectionError):\n                await connector.connect(req, [], aiohttp.ClientTimeout())\n    await connector.close()\n\n\n@mock.patch(\"aiohttp.connector.ClientRequestBase\")\n@mock.patch(\n    \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n    autospec=True,\n    spec_set=True,\n)\nasync def test_proxy_server_hostname_default(  # type: ignore[misc]\n    start_connection: mock.Mock,\n    ClientRequestMock: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    event_loop = asyncio.get_running_loop()\n    proxy_req = ClientRequestBase(\n        \"GET\",\n        URL(\"http://proxy.example.com\"),\n        auth=None,\n        loop=event_loop,\n        ssl=True,\n        headers=CIMultiDict({}),\n    )\n    ClientRequestMock.return_value = proxy_req\n\n    url = URL(\"http://proxy.example.com\")\n    proxy_resp = ClientResponse(\n        \"get\",\n        url,\n        writer=None,\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=event_loop,\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    with mock.patch.object(proxy_req, \"_send\", autospec=True, return_value=proxy_resp):\n        with mock.patch.object(proxy_resp, \"start\", autospec=True) as m:\n            m.return_value.status = 200\n\n            connector = aiohttp.TCPConnector()\n            r = {\n                \"hostname\": \"hostname\",\n                \"host\": \"127.0.0.1\",\n                \"port\": 80,\n                \"family\": socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": 0,\n            }\n            with mock.patch.object(\n                connector, \"_resolve_host\", autospec=True, return_value=[r]\n            ):\n                tr, proto = mock.Mock(), mock.Mock()\n                with mock.patch.object(\n                    event_loop,\n                    \"create_connection\",\n                    autospec=True,\n                    return_value=(tr, proto),\n                ):\n                    with mock.patch.object(\n                        event_loop,\n                        \"start_tls\",\n                        autospec=True,\n                        return_value=mock.Mock(),\n                    ) as tls_m:\n                        req = make_client_request(\n                            \"GET\",\n                            URL(\"https://www.python.org\"),\n                            proxy=URL(\"http://proxy.example.com\"),\n                            loop=event_loop,\n                        )\n                        await connector._create_connection(\n                            req, [], aiohttp.ClientTimeout()\n                        )\n\n                        assert (\n                            tls_m.call_args.kwargs[\"server_hostname\"]\n                            == \"www.python.org\"\n                        )\n\n                        proxy_resp.close()\n                        await req._close()\n            await connector.close()\n\n\n@mock.patch(\"aiohttp.connector.ClientRequestBase\")\n@mock.patch(\n    \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n    autospec=True,\n    spec_set=True,\n)\nasync def test_proxy_server_hostname_override(  # type: ignore[misc]\n    start_connection: mock.Mock,\n    ClientRequestMock: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    event_loop = asyncio.get_running_loop()\n    proxy_req = ClientRequestBase(\n        \"GET\",\n        URL(\"http://proxy.example.com\"),\n        auth=None,\n        loop=event_loop,\n        ssl=True,\n        headers=CIMultiDict({}),\n    )\n    ClientRequestMock.return_value = proxy_req\n\n    url = URL(\"http://proxy.example.com\")\n    proxy_resp = ClientResponse(\n        \"get\",\n        url,\n        writer=None,\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=event_loop,\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    with mock.patch.object(proxy_req, \"_send\", autospec=True, return_value=proxy_resp):\n        with mock.patch.object(proxy_resp, \"start\", autospec=True) as m:\n            m.return_value.status = 200\n\n            connector = aiohttp.TCPConnector()\n            r = {\n                \"hostname\": \"hostname\",\n                \"host\": \"127.0.0.1\",\n                \"port\": 80,\n                \"family\": socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": 0,\n            }\n            with mock.patch.object(\n                connector, \"_resolve_host\", autospec=True, return_value=[r]\n            ):\n                tr, proto = mock.Mock(), mock.Mock()\n                with mock.patch.object(\n                    event_loop,\n                    \"create_connection\",\n                    autospec=True,\n                    return_value=(tr, proto),\n                ):\n                    with mock.patch.object(\n                        event_loop,\n                        \"start_tls\",\n                        autospec=True,\n                        return_value=mock.Mock(),\n                    ) as tls_m:\n                        req = make_client_request(\n                            \"GET\",\n                            URL(\"https://www.python.org\"),\n                            proxy=URL(\"http://proxy.example.com\"),\n                            server_hostname=\"server-hostname.example.com\",\n                        )\n                        await connector._create_connection(\n                            req, [], aiohttp.ClientTimeout()\n                        )\n\n                        assert (\n                            tls_m.call_args.kwargs[\"server_hostname\"]\n                            == \"server-hostname.example.com\"\n                        )\n\n                        proxy_resp.close()\n                        await req._close()\n            await connector.close()\n\n\n@mock.patch(\"aiohttp.connector.ClientRequestBase\")\n@mock.patch(\n    \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n    autospec=True,\n    spec_set=True,\n)\n@pytest.mark.usefixtures(\"enable_cleanup_closed\")\n@pytest.mark.parametrize(\"cleanup\", (True, False))\nasync def test_https_connect_fingerprint_mismatch(  # type: ignore[misc]\n    start_connection: mock.Mock,\n    ClientRequestMock: mock.Mock,\n    cleanup: bool,\n    make_client_request: _RequestMaker,\n) -> None:\n    event_loop = asyncio.get_running_loop()\n    proxy_req = ClientRequestBase(\n        \"GET\",\n        URL(\"http://proxy.example.com\"),\n        auth=None,\n        loop=event_loop,\n        ssl=True,\n        headers=CIMultiDict({}),\n    )\n    ClientRequestMock.return_value = proxy_req\n\n    class TransportMock(asyncio.Transport):\n        def close(self) -> None:\n            pass\n\n    url = URL(\"http://proxy.example.com\")\n    proxy_resp = ClientResponse(\n        \"get\",\n        url,\n        writer=mock.Mock(),\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=event_loop,\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    fingerprint_mock = mock.Mock(spec=Fingerprint, auto_spec=True)\n    fingerprint_mock.check.side_effect = aiohttp.ServerFingerprintMismatch(\n        b\"exp\", b\"got\", \"example.com\", 8080\n    )\n    with (\n        mock.patch.object(\n            proxy_req,\n            \"_send\",\n            autospec=True,\n            spec_set=True,\n            return_value=proxy_resp,\n        ),\n        mock.patch.object(\n            proxy_resp,\n            \"start\",\n            autospec=True,\n            spec_set=True,\n            return_value=mock.Mock(status=200),\n        ),\n    ):\n        connector = aiohttp.TCPConnector(enable_cleanup_closed=cleanup)\n        host = [\n            {\n                \"hostname\": \"hostname\",\n                \"host\": \"127.0.0.1\",\n                \"port\": 80,\n                \"family\": socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": 0,\n            }\n        ]\n        with (\n            mock.patch.object(\n                connector,\n                \"_resolve_host\",\n                autospec=True,\n                spec_set=True,\n                return_value=host,\n            ),\n            mock.patch.object(\n                connector,\n                \"_get_fingerprint\",\n                autospec=True,\n                spec_set=True,\n                return_value=fingerprint_mock,\n            ),\n            mock.patch.object(  # Called on connection to http://proxy.example.com\n                event_loop,\n                \"create_connection\",\n                autospec=True,\n                spec_set=True,\n                return_value=(mock.Mock(), mock.Mock()),\n            ),\n            mock.patch.object(  # Called on connection to https://www.python.org\n                event_loop,\n                \"start_tls\",\n                autospec=True,\n                spec_set=True,\n                return_value=TransportMock(),\n            ),\n        ):\n            req = make_client_request(\n                \"GET\",\n                URL(\"https://www.python.org\"),\n                proxy=URL(\"http://proxy.example.com\"),\n                loop=event_loop,\n            )\n            with pytest.raises(aiohttp.ServerFingerprintMismatch):\n                await connector._create_connection(req, [], aiohttp.ClientTimeout())\n\n\n@mock.patch(\"aiohttp.connector.ClientRequestBase\")\n@mock.patch(\n    \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n    autospec=True,\n    spec_set=True,\n)\nasync def test_https_connect(  # type: ignore[misc]\n    start_connection: mock.Mock,\n    ClientRequestMock: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    event_loop = asyncio.get_running_loop()\n    proxy_req = ClientRequestBase(\n        \"GET\",\n        URL(\"http://proxy.example.com\"),\n        auth=None,\n        loop=event_loop,\n        ssl=True,\n        headers=CIMultiDict({}),\n    )\n    ClientRequestMock.return_value = proxy_req\n\n    url = URL(\"http://proxy.example.com\")\n    proxy_resp = ClientResponse(\n        \"get\",\n        url,\n        writer=None,\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=event_loop,\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    with mock.patch.object(proxy_req, \"_send\", autospec=True, return_value=proxy_resp):\n        with mock.patch.object(proxy_resp, \"start\", autospec=True) as m:\n            m.return_value.status = 200\n\n            connector = aiohttp.TCPConnector()\n            r = {\n                \"hostname\": \"hostname\",\n                \"host\": \"127.0.0.1\",\n                \"port\": 80,\n                \"family\": socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": 0,\n            }\n            with mock.patch.object(\n                connector, \"_resolve_host\", autospec=True, return_value=[r]\n            ):\n                tr, proto = mock.Mock(), mock.Mock()\n                with mock.patch.object(\n                    event_loop,\n                    \"create_connection\",\n                    autospec=True,\n                    return_value=(tr, proto),\n                ):\n                    with mock.patch.object(\n                        event_loop,\n                        \"start_tls\",\n                        autospec=True,\n                        return_value=mock.Mock(),\n                    ):\n                        req = make_client_request(\n                            \"GET\",\n                            URL(\"https://www.python.org\"),\n                            proxy=URL(\"http://proxy.example.com\"),\n                            loop=event_loop,\n                        )\n                        await connector._create_connection(\n                            req, [], aiohttp.ClientTimeout()\n                        )\n\n                        assert req.url.path == \"/\"\n                        assert proxy_req.method == \"CONNECT\"\n                        assert proxy_req.url == URL(\"https://www.python.org\")\n\n                        proxy_resp.close()\n                        await req._close()\n            await connector.close()\n\n\n@mock.patch(\"aiohttp.connector.ClientRequestBase\")\n@mock.patch(\n    \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n    autospec=True,\n    spec_set=True,\n)\nasync def test_https_connect_certificate_error(  # type: ignore[misc]\n    start_connection: mock.Mock,\n    ClientRequestMock: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    event_loop = asyncio.get_running_loop()\n    proxy_req = ClientRequestBase(\n        \"GET\",\n        URL(\"http://proxy.example.com\"),\n        auth=None,\n        loop=event_loop,\n        ssl=True,\n        headers=CIMultiDict({}),\n    )\n    ClientRequestMock.return_value = proxy_req\n\n    url = URL(\"http://proxy.example.com\")\n    proxy_resp = ClientResponse(\n        \"get\",\n        url,\n        writer=None,\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=event_loop,\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    with mock.patch.object(proxy_req, \"_send\", autospec=True, return_value=proxy_resp):\n        with mock.patch.object(proxy_resp, \"start\", autospec=True) as m:\n            m.return_value.status = 200\n\n            connector = aiohttp.TCPConnector()\n            r = {\n                \"hostname\": \"hostname\",\n                \"host\": \"127.0.0.1\",\n                \"port\": 80,\n                \"family\": socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": 0,\n            }\n            with mock.patch.object(\n                connector, \"_resolve_host\", autospec=True, return_value=[r]\n            ):\n                tr, proto = mock.Mock(), mock.Mock()\n                # Called on connection to http://proxy.example.com\n                with mock.patch.object(\n                    event_loop,\n                    \"create_connection\",\n                    autospec=True,\n                    return_value=(tr, proto),\n                ):\n                    # Called on connection to https://www.python.org\n                    with mock.patch.object(\n                        event_loop,\n                        \"start_tls\",\n                        autospec=True,\n                        side_effect=ssl.CertificateError,\n                    ):\n                        req = make_client_request(\n                            \"GET\",\n                            URL(\"https://www.python.org\"),\n                            proxy=URL(\"http://proxy.example.com\"),\n                            loop=event_loop,\n                        )\n                        with pytest.raises(aiohttp.ClientConnectorCertificateError):\n                            await connector._create_connection(\n                                req, [], aiohttp.ClientTimeout()\n                            )\n            await connector.close()\n\n\n@mock.patch(\"aiohttp.connector.ClientRequestBase\")\n@mock.patch(\n    \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n    autospec=True,\n    spec_set=True,\n)\nasync def test_https_connect_ssl_error(  # type: ignore[misc]\n    start_connection: mock.Mock,\n    ClientRequestMock: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    event_loop = asyncio.get_running_loop()\n    proxy_req = ClientRequestBase(\n        \"GET\",\n        URL(\"http://proxy.example.com\"),\n        auth=None,\n        loop=event_loop,\n        ssl=True,\n        headers=CIMultiDict({}),\n    )\n    ClientRequestMock.return_value = proxy_req\n\n    url = URL(\"http://proxy.example.com\")\n    proxy_resp = ClientResponse(\n        \"get\",\n        url,\n        writer=None,\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=event_loop,\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    with mock.patch.object(proxy_req, \"_send\", autospec=True, return_value=proxy_resp):\n        with mock.patch.object(proxy_resp, \"start\", autospec=True) as m:\n            m.return_value.status = 200\n\n            connector = aiohttp.TCPConnector()\n            r = {\n                \"hostname\": \"hostname\",\n                \"host\": \"127.0.0.1\",\n                \"port\": 80,\n                \"family\": socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": 0,\n            }\n            with mock.patch.object(\n                connector, \"_resolve_host\", autospec=True, return_value=[r]\n            ):\n                tr, proto = mock.Mock(), mock.Mock()\n                # Called on connection to http://proxy.example.com\n                with mock.patch.object(\n                    event_loop,\n                    \"create_connection\",\n                    autospec=True,\n                    return_value=(tr, proto),\n                ):\n                    # Called on connection to https://www.python.org\n                    with mock.patch.object(\n                        event_loop,\n                        \"start_tls\",\n                        autospec=True,\n                        side_effect=ssl.SSLError,\n                    ):\n                        req = make_client_request(\n                            \"GET\",\n                            URL(\"https://www.python.org\"),\n                            proxy=URL(\"http://proxy.example.com\"),\n                            loop=event_loop,\n                        )\n                        with pytest.raises(aiohttp.ClientConnectorSSLError):\n                            await connector._create_connection(\n                                req, [], aiohttp.ClientTimeout()\n                            )\n            await connector.close()\n\n\n@mock.patch(\"aiohttp.connector.ClientRequestBase\")\n@mock.patch(\n    \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n    autospec=True,\n    spec_set=True,\n)\nasync def test_https_connect_http_proxy_error(  # type: ignore[misc]\n    start_connection: mock.Mock,\n    ClientRequestMock: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    event_loop = asyncio.get_running_loop()\n    proxy_req = ClientRequestBase(\n        \"GET\",\n        URL(\"http://proxy.example.com\"),\n        auth=None,\n        loop=event_loop,\n        ssl=True,\n        headers=CIMultiDict({}),\n    )\n    ClientRequestMock.return_value = proxy_req\n\n    url = URL(\"http://proxy.example.com\")\n    proxy_resp = ClientResponse(\n        \"get\",\n        url,\n        writer=None,\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=event_loop,\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    with mock.patch.object(proxy_req, \"_send\", autospec=True, return_value=proxy_resp):\n        with mock.patch.object(proxy_resp, \"start\", autospec=True) as m:\n            m.return_value.status = 400\n            m.return_value.reason = \"bad request\"\n\n            connector = aiohttp.TCPConnector()\n            r = {\n                \"hostname\": \"hostname\",\n                \"host\": \"127.0.0.1\",\n                \"port\": 80,\n                \"family\": socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": 0,\n            }\n            with mock.patch.object(\n                connector, \"_resolve_host\", autospec=True, return_value=[r]\n            ):\n                tr, proto = mock.Mock(), mock.Mock()\n                tr.get_extra_info.return_value = None\n                # Called on connection to http://proxy.example.com\n                with mock.patch.object(\n                    event_loop,\n                    \"create_connection\",\n                    autospec=True,\n                    return_value=(tr, proto),\n                ):\n                    req = make_client_request(\n                        \"GET\",\n                        URL(\"https://www.python.org\"),\n                        proxy=URL(\"http://proxy.example.com\"),\n                        loop=event_loop,\n                    )\n                    with pytest.raises(\n                        aiohttp.ClientHttpProxyError, match=\"400, message='bad request'\"\n                    ):\n                        await connector._create_connection(\n                            req, [], aiohttp.ClientTimeout()\n                        )\n\n                    proxy_resp.close()\n                    await req._close()\n            await connector.close()\n\n\n@mock.patch(\"aiohttp.connector.ClientRequestBase\")\n@mock.patch(\n    \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n    autospec=True,\n    spec_set=True,\n)\nasync def test_https_connect_resp_start_error(  # type: ignore[misc]\n    start_connection: mock.Mock,\n    ClientRequestMock: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    event_loop = asyncio.get_running_loop()\n    proxy_req = ClientRequestBase(\n        \"GET\",\n        URL(\"http://proxy.example.com\"),\n        auth=None,\n        loop=event_loop,\n        ssl=True,\n        headers=CIMultiDict({}),\n    )\n    ClientRequestMock.return_value = proxy_req\n\n    url = URL(\"http://proxy.example.com\")\n    proxy_resp = ClientResponse(\n        \"get\",\n        url,\n        writer=None,\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=event_loop,\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    with mock.patch.object(proxy_req, \"_send\", autospec=True, return_value=proxy_resp):\n        with mock.patch.object(\n            proxy_resp, \"start\", autospec=True, side_effect=OSError(\"error message\")\n        ):\n            connector = aiohttp.TCPConnector()\n            r = {\n                \"hostname\": \"hostname\",\n                \"host\": \"127.0.0.1\",\n                \"port\": 80,\n                \"family\": socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": 0,\n            }\n            with mock.patch.object(\n                connector, \"_resolve_host\", autospec=True, return_value=[r]\n            ):\n                tr, proto = mock.Mock(), mock.Mock()\n                tr.get_extra_info.return_value = None\n                # Called on connection to http://proxy.example.com\n                with mock.patch.object(\n                    event_loop,\n                    \"create_connection\",\n                    autospec=True,\n                    return_value=(tr, proto),\n                ):\n                    req = make_client_request(\n                        \"GET\",\n                        URL(\"https://www.python.org\"),\n                        proxy=URL(\"http://proxy.example.com\"),\n                        loop=event_loop,\n                    )\n                    with pytest.raises(OSError, match=\"error message\"):\n                        await connector._create_connection(\n                            req, [], aiohttp.ClientTimeout()\n                        )\n            await connector.close()\n\n\n@mock.patch(\"aiohttp.connector.ClientRequest\")\n@mock.patch(\n    \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n    autospec=True,\n    spec_set=True,\n)\nasync def test_request_port(  # type: ignore[misc]\n    start_connection: mock.Mock,\n    ClientRequestMock: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    event_loop = asyncio.get_running_loop()\n    proxy_req = make_client_request(\n        \"GET\", URL(\"http://proxy.example.com\"), loop=event_loop\n    )\n    ClientRequestMock.return_value = proxy_req\n\n    connector = aiohttp.TCPConnector()\n    r = {\n        \"hostname\": \"hostname\",\n        \"host\": \"127.0.0.1\",\n        \"port\": 80,\n        \"family\": socket.AF_INET,\n        \"proto\": 0,\n        \"flags\": 0,\n    }\n    with mock.patch.object(connector, \"_resolve_host\", autospec=True, return_value=[r]):\n        tr, proto = mock.Mock(), mock.Mock()\n        tr.get_extra_info.return_value = None\n        # Called on connection to http://proxy.example.com\n        with mock.patch.object(\n            event_loop, \"create_connection\", autospec=True, return_value=(tr, proto)\n        ):\n            req = make_client_request(\n                \"GET\",\n                URL(\"http://localhost:1234/path\"),\n                proxy=URL(\"http://proxy.example.com\"),\n                loop=event_loop,\n            )\n            await connector._create_connection(req, [], aiohttp.ClientTimeout())\n            assert req.url == URL(\"http://localhost:1234/path\")\n    await connector.close()\n\n\nasync def test_proxy_auth_property(\n    event_loop: asyncio.AbstractEventLoop, make_client_request: _RequestMaker\n) -> None:\n    req = make_client_request(\n        \"GET\",\n        URL(\"http://localhost:1234/path\"),\n        proxy=URL(\"http://proxy.example.com\"),\n        proxy_auth=aiohttp.helpers.BasicAuth(\"user\", \"pass\"),\n        loop=event_loop,\n    )\n    assert (\"user\", \"pass\", \"latin1\") == req.proxy_auth\n\n\nasync def test_proxy_auth_property_default(\n    event_loop: asyncio.AbstractEventLoop,\n    make_client_request: _RequestMaker,\n) -> None:\n    req = make_client_request(\n        \"GET\",\n        URL(\"http://localhost:1234/path\"),\n        proxy=URL(\"http://proxy.example.com\"),\n        loop=event_loop,\n    )\n    assert req.proxy_auth is None\n\n\n@mock.patch(\"aiohttp.connector.ClientRequestBase\")\n@mock.patch(\n    \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n    autospec=True,\n    spec_set=True,\n)\nasync def test_https_connect_pass_ssl_context(  # type: ignore[misc]\n    start_connection: mock.Mock,\n    ClientRequestMock: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    event_loop = asyncio.get_running_loop()\n    proxy_req = ClientRequestBase(\n        \"GET\",\n        URL(\"http://proxy.example.com\"),\n        auth=None,\n        loop=event_loop,\n        ssl=True,\n        headers=CIMultiDict({}),\n    )\n    ClientRequestMock.return_value = proxy_req\n\n    url = URL(\"http://proxy.example.com\")\n    proxy_resp = ClientResponse(\n        \"get\",\n        url,\n        writer=None,\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=event_loop,\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    with mock.patch.object(proxy_req, \"_send\", autospec=True, return_value=proxy_resp):\n        with mock.patch.object(proxy_resp, \"start\", autospec=True) as m:\n            m.return_value.status = 200\n\n            connector = aiohttp.TCPConnector()\n            r = {\n                \"hostname\": \"hostname\",\n                \"host\": \"127.0.0.1\",\n                \"port\": 80,\n                \"family\": socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": 0,\n            }\n            with mock.patch.object(\n                connector, \"_resolve_host\", autospec=True, return_value=[r]\n            ):\n                tr, proto = mock.Mock(), mock.Mock()\n                with mock.patch.object(\n                    event_loop,\n                    \"create_connection\",\n                    autospec=True,\n                    return_value=(tr, proto),\n                ):\n                    with mock.patch.object(\n                        event_loop,\n                        \"start_tls\",\n                        autospec=True,\n                        return_value=mock.Mock(),\n                    ) as tls_m:\n                        req = make_client_request(\n                            \"GET\",\n                            URL(\"https://www.python.org\"),\n                            proxy=URL(\"http://proxy.example.com\"),\n                            loop=event_loop,\n                        )\n                        await connector._create_connection(\n                            req, [], aiohttp.ClientTimeout()\n                        )\n\n                        # ssl_shutdown_timeout=0 is not passed to start_tls\n                        tls_m.assert_called_with(\n                            mock.ANY,\n                            mock.ANY,\n                            _SSL_CONTEXT_VERIFIED,\n                            server_hostname=\"www.python.org\",\n                            ssl_handshake_timeout=mock.ANY,\n                        )\n\n                        assert req.url.path == \"/\"\n                        assert proxy_req.method == \"CONNECT\"\n                        assert proxy_req.url == URL(\"https://www.python.org\")\n\n                        proxy_resp.close()\n                        await req._close()\n            await connector.close()\n\n\n@mock.patch(\"aiohttp.connector.ClientRequestBase\")\n@mock.patch(\n    \"aiohttp.connector.aiohappyeyeballs.start_connection\",\n    autospec=True,\n    spec_set=True,\n)\nasync def test_https_auth(  # type: ignore[misc]\n    start_connection: mock.Mock,\n    ClientRequestMock: mock.Mock,\n    make_client_request: _RequestMaker,\n) -> None:\n    event_loop = asyncio.get_running_loop()\n    proxy_req = ClientRequestBase(\n        \"GET\",\n        URL(\"http://proxy.example.com\"),\n        auth=aiohttp.helpers.BasicAuth(\"user\", \"pass\"),\n        loop=event_loop,\n        ssl=True,\n        headers=CIMultiDict({}),\n    )\n    ClientRequestMock.return_value = proxy_req\n\n    url = URL(\"http://proxy.example.com\")\n    proxy_resp = ClientResponse(\n        \"get\",\n        url,\n        writer=None,\n        continue100=None,\n        timer=TimerNoop(),\n        traces=[],\n        loop=event_loop,\n        session=mock.Mock(),\n        request_headers=CIMultiDict[str](),\n        original_url=url,\n    )\n    with mock.patch.object(proxy_req, \"_send\", autospec=True, return_value=proxy_resp):\n        with mock.patch.object(proxy_resp, \"start\", autospec=True) as m:\n            m.return_value.status = 200\n\n            connector = aiohttp.TCPConnector()\n            r = {\n                \"hostname\": \"hostname\",\n                \"host\": \"127.0.0.1\",\n                \"port\": 80,\n                \"family\": socket.AF_INET,\n                \"proto\": 0,\n                \"flags\": 0,\n            }\n            with mock.patch.object(\n                connector, \"_resolve_host\", autospec=True, return_value=[r]\n            ) as host_m:\n                tr, proto = mock.Mock(), mock.Mock()\n                with mock.patch.object(\n                    event_loop,\n                    \"create_connection\",\n                    autospec=True,\n                    return_value=(tr, proto),\n                ):\n                    with mock.patch.object(\n                        event_loop,\n                        \"start_tls\",\n                        autospec=True,\n                        return_value=mock.Mock(),\n                    ):\n                        assert \"AUTHORIZATION\" in proxy_req.headers\n                        assert \"PROXY-AUTHORIZATION\" not in proxy_req.headers\n\n                        req = make_client_request(\n                            \"GET\",\n                            URL(\"https://www.python.org\"),\n                            proxy=URL(\"http://proxy.example.com\"),\n                            loop=event_loop,\n                        )\n                        assert \"AUTHORIZATION\" not in req.headers\n                        assert \"PROXY-AUTHORIZATION\" not in req.headers\n                        await connector._create_connection(\n                            req, [], aiohttp.ClientTimeout()\n                        )\n\n                        assert req.url.path == \"/\"\n                        assert \"AUTHORIZATION\" not in req.headers\n                        assert \"PROXY-AUTHORIZATION\" not in req.headers\n                        assert \"AUTHORIZATION\" not in proxy_req.headers\n                        assert \"PROXY-AUTHORIZATION\" in proxy_req.headers\n\n                        host_m.assert_called_with(\n                            \"proxy.example.com\", 80, traces=mock.ANY\n                        )\n\n                        proxy_resp.close()\n                        await req._close()\n            await connector.close()\n"
  },
  {
    "path": "tests/test_proxy_functional.py",
    "content": "import asyncio\nimport os\nimport pathlib\nimport platform\nimport ssl\nimport sys\nfrom collections.abc import Awaitable, Callable, Iterator\nfrom contextlib import suppress\nfrom re import match as match_regex\nfrom typing import TYPE_CHECKING, TypedDict\nfrom unittest import mock\nfrom uuid import uuid4\n\nimport proxy\nimport pytest\nfrom pytest_mock import MockerFixture\nfrom yarl import URL\n\nimport aiohttp\nfrom aiohttp import ClientResponse, web\nfrom aiohttp.client import _RequestOptions\nfrom aiohttp.client_exceptions import ClientConnectionError\nfrom aiohttp.pytest_plugin import AiohttpRawServer, AiohttpServer\nfrom aiohttp.test_utils import TestServer\n\nASYNCIO_SUPPORTS_TLS_IN_TLS = sys.version_info >= (3, 11)\n\n\nclass _ResponseArgs(TypedDict):\n    status: int\n    headers: dict[str, str] | None\n    body: bytes | None\n\n\nif sys.version_info >= (3, 11) and TYPE_CHECKING:\n    from typing import Unpack\n\n    async def get_request(\n        method: str = \"GET\",\n        *,\n        url: str | URL,\n        trust_env: bool = False,\n        **kwargs: Unpack[_RequestOptions],\n    ) -> ClientResponse: ...\n\nelse:\n    from typing import Any\n\n    async def get_request(\n        method: str = \"GET\",\n        *,\n        url: str | URL,\n        trust_env: bool = False,\n        **kwargs: Any,\n    ) -> ClientResponse:\n        connector = aiohttp.TCPConnector(ssl=False)\n        async with aiohttp.ClientSession(\n            connector=connector, trust_env=trust_env\n        ) as client:\n            async with client.request(method, url, **kwargs) as resp:\n                return resp\n\n\n@pytest.fixture\ndef secure_proxy_url(tls_certificate_pem_path: str) -> Iterator[URL]:\n    \"\"\"Return the URL of an instance of a running secure proxy.\n\n    This fixture also spawns that instance and tears it down after the test.\n    \"\"\"\n    proxypy_args = [\n        # --threadless does not work on windows, see\n        # https://github.com/abhinavsingh/proxy.py/issues/492\n        \"--threaded\" if os.name == \"nt\" else \"--threadless\",\n        \"--num-workers\",\n        \"1\",  # the tests only send one query anyway\n        \"--hostname\",\n        \"127.0.0.1\",  # network interface to listen to\n        \"--port\",\n        \"0\",  # ephemeral port, so that kernel allocates a free one\n        \"--cert-file\",\n        tls_certificate_pem_path,  # contains both key and cert\n        \"--key-file\",\n        tls_certificate_pem_path,  # contains both key and cert\n    ]\n\n    with proxy.Proxy(input_args=proxypy_args) as proxy_instance:\n        yield URL.build(\n            scheme=\"https\",\n            host=str(proxy_instance.flags.hostname),\n            port=proxy_instance.flags.port,\n        )\n\n\n@pytest.fixture\ndef web_server_endpoint_payload() -> str:\n    return str(uuid4())\n\n\n@pytest.fixture(params=(\"http\", \"https\"))\ndef web_server_endpoint_type(request: pytest.FixtureRequest) -> str:\n    return request.param  # type: ignore[no-any-return]\n\n\n@pytest.fixture\nasync def web_server_endpoint_url(\n    aiohttp_server: AiohttpServer,\n    ssl_ctx: ssl.SSLContext,\n    web_server_endpoint_payload: str,\n    web_server_endpoint_type: str,\n) -> URL:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=web_server_endpoint_payload)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    if web_server_endpoint_type == \"https\":\n        server = await aiohttp_server(app, ssl=ssl_ctx)\n    else:\n        server = await aiohttp_server(app)\n\n    return URL.build(\n        scheme=web_server_endpoint_type,\n        host=server.host,\n        port=server.port,\n    )\n\n\n@pytest.mark.skipif(\n    not ASYNCIO_SUPPORTS_TLS_IN_TLS,\n    reason=\"asyncio on this python does not support TLS in TLS\",\n)\n@pytest.mark.parametrize(\"web_server_endpoint_type\", (\"http\", \"https\"))\n@pytest.mark.filterwarnings(r\"ignore:.*ssl.OP_NO_SSL*\")\n# Filter out the warning from\n# https://github.com/abhinavsingh/proxy.py/blob/30574fd0414005dfa8792a6e797023e862bdcf43/proxy/common/utils.py#L226\n# otherwise this test will fail because the proxy will die with an error.\n@pytest.mark.usefixtures(\"loop\")\nasync def test_secure_https_proxy_absolute_path(\n    client_ssl_ctx: ssl.SSLContext,\n    secure_proxy_url: URL,\n    web_server_endpoint_url: URL,\n    web_server_endpoint_payload: str,\n) -> None:\n    \"\"\"Ensure HTTP(S) sites are accessible through a secure proxy.\"\"\"\n    conn = aiohttp.TCPConnector()\n    sess = aiohttp.ClientSession(connector=conn)\n\n    async with sess.get(\n        web_server_endpoint_url,\n        proxy=secure_proxy_url,\n        ssl=client_ssl_ctx,  # used for both proxy and endpoint connections\n    ) as response:\n        assert response.status == 200\n        assert await response.text() == web_server_endpoint_payload\n\n    await sess.close()\n    await conn.close()\n    await asyncio.sleep(0.1)\n\n\n@pytest.mark.parametrize(\"web_server_endpoint_type\", (\"https\",))\n@pytest.mark.usefixtures(\"loop\")\n@pytest.mark.skipif(\n    ASYNCIO_SUPPORTS_TLS_IN_TLS, reason=\"asyncio on this python supports TLS in TLS\"\n)\n@pytest.mark.filterwarnings(r\"ignore:.*ssl.OP_NO_SSL*\")\n# Filter out the warning from\n# https://github.com/abhinavsingh/proxy.py/blob/30574fd0414005dfa8792a6e797023e862bdcf43/proxy/common/utils.py#L226\n# otherwise this test will fail because the proxy will die with an error.\nasync def test_https_proxy_unsupported_tls_in_tls(\n    client_ssl_ctx: ssl.SSLContext,\n    secure_proxy_url: URL,\n    web_server_endpoint_type: str,\n) -> None:\n    \"\"\"Ensure connecting to TLS endpoints w/ HTTPS proxy needs patching.\n\n    This also checks that a helpful warning on how to patch the env\n    is displayed.\n    \"\"\"\n    url = URL.build(scheme=web_server_endpoint_type, host=\"python.org\")\n\n    assert url.host is not None\n    escaped_host_port = \":\".join((url.host.replace(\".\", r\"\\.\"), str(url.port)))\n    escaped_proxy_url = str(secure_proxy_url).replace(\".\", r\"\\.\")\n\n    conn = aiohttp.TCPConnector()\n    sess = aiohttp.ClientSession(connector=conn)\n\n    expected_warning_text = (\n        r\"^\"\n        r\"An HTTPS request is being sent through an HTTPS proxy\\. \"\n        \"This support for TLS in TLS is known to be disabled \"\n        r\"in the stdlib asyncio\\. This is why you'll probably see \"\n        r\"an error in the log below\\.\\n\\n\"\n        r\"It is possible to enable it via monkeypatching\\. \"\n        r\"For more details, see:\\n\"\n        r\"\\* https://bugs\\.python\\.org/issue37179\\n\"\n        r\"\\* https://github\\.com/python/cpython/pull/28073\\n\\n\"\n        r\"You can temporarily patch this as follows:\\n\"\n        r\"\\* https://docs\\.aiohttp\\.org/en/stable/client_advanced\\.html#proxy-support\\n\"\n        r\"\\* https://github\\.com/aio-libs/aiohttp/discussions/6044\\n$\"\n    )\n    type_err = (\n        r\"transport <asyncio\\.sslproto\\._SSLProtocolTransport object at \"\n        r\"0x[\\d\\w]+> is not supported by start_tls\\(\\)\"\n    )\n    expected_exception_reason = (\n        r\"^\"\n        \"Cannot initialize a TLS-in-TLS connection to host \"\n        f\"{escaped_host_port!s} through an underlying connection \"\n        f\"to an HTTPS proxy {escaped_proxy_url!s} ssl:{client_ssl_ctx!s} \"\n        f\"[{type_err!s}]\"\n        r\"$\"\n    )\n\n    with (\n        pytest.warns(\n            RuntimeWarning,\n            match=expected_warning_text,\n        ),\n        pytest.raises(\n            ClientConnectionError,\n            match=expected_exception_reason,\n        ) as conn_err,\n    ):\n        async with sess.get(url, proxy=secure_proxy_url, ssl=client_ssl_ctx):\n            pass\n\n    assert isinstance(conn_err.value.__cause__, TypeError)\n    assert match_regex(f\"^{type_err!s}$\", str(conn_err.value.__cause__))\n\n    await sess.close()\n    await conn.close()\n\n    await asyncio.sleep(0.1)\n\n\n@pytest.mark.skipif(\n    platform.system() == \"Windows\" or sys.implementation.name != \"cpython\",\n    reason=\"uvloop is not supported on Windows and non-CPython implementations\",\n)\n@pytest.mark.filterwarnings(r\"ignore:.*ssl.OP_NO_SSL*\")\n# Filter out the warning from\n# https://github.com/abhinavsingh/proxy.py/blob/30574fd0414005dfa8792a6e797023e862bdcf43/proxy/common/utils.py#L226\n# otherwise this test will fail because the proxy will die with an error.\nasync def test_uvloop_secure_https_proxy(\n    client_ssl_ctx: ssl.SSLContext,\n    ssl_ctx: ssl.SSLContext,\n    secure_proxy_url: URL,\n    uvloop_loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Ensure HTTPS sites are accessible through a secure proxy without warning when using uvloop.\"\"\"\n    payload = str(uuid4())\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=payload)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = TestServer(app, host=\"127.0.0.1\")\n    await server.start_server(ssl=ssl_ctx)\n\n    url = URL.build(scheme=\"https\", host=server.host, port=server.port)\n    conn = aiohttp.TCPConnector(force_close=True)\n    sess = aiohttp.ClientSession(connector=conn)\n    try:\n        async with sess.get(\n            url, proxy=secure_proxy_url, ssl=client_ssl_ctx\n        ) as response:\n            assert response.status == 200\n            assert await response.text() == payload\n    finally:\n        await sess.close()\n        await conn.close()\n        await server.close()\n        await asyncio.sleep(0)\n        await asyncio.sleep(0.1)\n\n\n@pytest.fixture\ndef proxy_test_server(\n    aiohttp_raw_server: AiohttpRawServer,\n    loop: asyncio.AbstractEventLoop,\n    monkeypatch: pytest.MonkeyPatch,\n) -> Callable[[], Awaitable[mock.Mock]]:\n    # Handle all proxy requests and imitate remote server response.\n\n    _patch_ssl_transport(monkeypatch)\n\n    default_response = _ResponseArgs(status=200, headers=None, body=None)\n\n    proxy_mock = mock.Mock()\n\n    async def proxy_handler(request: web.Request) -> web.Response:\n        proxy_mock.request = request\n        proxy_mock.requests_list.append(request)\n\n        response = default_response.copy()\n        if isinstance(proxy_mock.return_value, dict):\n            response.update(proxy_mock.return_value)  # type: ignore[typeddict-item]\n\n        headers = response[\"headers\"]\n        if not headers:\n            headers = {}\n\n        if request.method == \"CONNECT\":\n            response[\"body\"] = None\n\n        response[\"headers\"] = headers\n\n        resp = web.Response(**response)\n        await resp.prepare(request)\n        await resp.write_eof()\n        return resp\n\n    async def proxy_server() -> mock.Mock:\n        proxy_mock.request = None\n        proxy_mock.auth = None\n        proxy_mock.requests_list = []\n\n        server = await aiohttp_raw_server(proxy_handler)  # type: ignore[arg-type]\n\n        proxy_mock.server = server\n        proxy_mock.url = server.make_url(\"/\")\n\n        return proxy_mock\n\n    return proxy_server\n\n\nasync def test_proxy_http_absolute_path(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n) -> None:\n    url = \"http://aiohttp.io/path?query=yes\"\n    proxy = await proxy_test_server()\n\n    await get_request(url=url, proxy=proxy.url)\n\n    assert len(proxy.requests_list) == 1\n    assert proxy.request.method == \"GET\"\n    assert proxy.request.host == \"aiohttp.io\"\n    assert proxy.request.path_qs == \"/path?query=yes\"\n\n\nasync def test_proxy_http_raw_path(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n) -> None:\n    url = \"http://aiohttp.io:2561/space sheep?q=can:fly\"\n    raw_url = \"/space%20sheep?q=can:fly\"\n    proxy = await proxy_test_server()\n\n    await get_request(url=url, proxy=proxy.url)\n\n    assert proxy.request.host == \"aiohttp.io\"\n    assert proxy.request.path_qs == raw_url\n\n\nasync def test_proxy_http_idna_support(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n) -> None:\n    url = \"http://éé.com/\"\n    proxy = await proxy_test_server()\n\n    await get_request(url=url, proxy=proxy.url)\n\n    assert proxy.request.host == \"éé.com\"\n    assert proxy.request.path_qs == \"/\"\n\n\nasync def test_proxy_http_connection_error() -> None:\n    url = \"http://aiohttp.io/path\"\n    proxy_url = \"http://localhost:2242/\"\n\n    with pytest.raises(aiohttp.ClientConnectorError):\n        await get_request(url=url, proxy=proxy_url)\n\n\nasync def test_proxy_http_bad_response(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n) -> None:\n    url = \"http://aiohttp.io/path\"\n    proxy = await proxy_test_server()\n    proxy.return_value = dict(status=502, headers={\"Proxy-Agent\": \"TestProxy\"})\n\n    resp = await get_request(url=url, proxy=proxy.url)\n\n    assert resp.status == 502\n    assert resp.headers[\"Proxy-Agent\"] == \"TestProxy\"\n\n\nasync def test_proxy_http_auth(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n) -> None:\n    url = \"http://aiohttp.io/path\"\n    proxy = await proxy_test_server()\n\n    await get_request(url=url, proxy=proxy.url)\n\n    assert \"Authorization\" not in proxy.request.headers\n    assert \"Proxy-Authorization\" not in proxy.request.headers\n\n    auth = aiohttp.BasicAuth(\"user\", \"pass\")\n    await get_request(url=url, auth=auth, proxy=proxy.url)\n\n    assert \"Authorization\" in proxy.request.headers\n    assert \"Proxy-Authorization\" not in proxy.request.headers\n\n    await get_request(url=url, proxy_auth=auth, proxy=proxy.url)\n\n    assert \"Authorization\" not in proxy.request.headers\n    assert \"Proxy-Authorization\" in proxy.request.headers\n\n    await get_request(url=url, auth=auth, proxy_auth=auth, proxy=proxy.url)\n\n    assert \"Authorization\" in proxy.request.headers\n    assert \"Proxy-Authorization\" in proxy.request.headers\n\n\nasync def test_proxy_http_auth_utf8(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n) -> None:\n    url = \"http://aiohttp.io/path\"\n    auth = aiohttp.BasicAuth(\"юзер\", \"пасс\", \"utf-8\")\n    proxy = await proxy_test_server()\n\n    await get_request(url=url, auth=auth, proxy=proxy.url)\n\n    assert \"Authorization\" in proxy.request.headers\n    assert \"Proxy-Authorization\" not in proxy.request.headers\n\n\nasync def test_proxy_http_auth_from_url(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n) -> None:\n    url = \"http://aiohttp.io/path\"\n    proxy = await proxy_test_server()\n\n    auth_url = URL(url).with_user(\"user\").with_password(\"pass\")\n    await get_request(url=auth_url, proxy=proxy.url)\n\n    assert \"Authorization\" in proxy.request.headers\n    assert \"Proxy-Authorization\" not in proxy.request.headers\n\n    proxy_url = URL(proxy.url).with_user(\"user\").with_password(\"pass\")\n    await get_request(url=url, proxy=proxy_url)\n\n    assert \"Authorization\" not in proxy.request.headers\n    assert \"Proxy-Authorization\" in proxy.request.headers\n\n\nasync def test_proxy_http_acquired_cleanup(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    url = \"http://aiohttp.io/path\"\n\n    conn = aiohttp.TCPConnector()\n    sess = aiohttp.ClientSession(connector=conn)\n    proxy = await proxy_test_server()\n\n    assert 0 == len(conn._acquired)\n\n    async with sess.get(url, proxy=proxy.url) as resp:\n        pass\n    assert resp.closed\n\n    assert 0 == len(conn._acquired)\n\n    await sess.close()\n    await conn.close()\n\n\n@pytest.mark.skip(\"we need to reconsider how we test this\")\nasync def test_proxy_http_acquired_cleanup_force(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    url = \"http://aiohttp.io/path\"\n\n    conn = aiohttp.TCPConnector(force_close=True)\n    sess = aiohttp.ClientSession(connector=conn)\n    proxy = await proxy_test_server()\n\n    assert 0 == len(conn._acquired)\n\n    async def request() -> None:\n        async with sess.get(url, proxy=proxy.url):\n            assert 1 == len(conn._acquired)\n\n    await request()\n\n    assert 0 == len(conn._acquired)\n\n    await sess.close()\n    await conn.close()\n\n\n@pytest.mark.skip(\"we need to reconsider how we test this\")\nasync def test_proxy_http_multi_conn_limit(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    url = \"http://aiohttp.io/path\"\n    limit, multi_conn_num = 1, 5\n\n    conn = aiohttp.TCPConnector(limit=limit)\n    sess = aiohttp.ClientSession(connector=conn)\n    proxy = await proxy_test_server()\n\n    current_pid = None\n\n    async def request(pid: int) -> ClientResponse:\n        # process requests only one by one\n        nonlocal current_pid\n\n        async with sess.get(url, proxy=proxy.url) as resp:\n            current_pid = pid\n            await asyncio.sleep(0.2)\n            assert current_pid == pid\n\n        return resp\n\n    requests = [request(pid) for pid in range(multi_conn_num)]\n    responses = await asyncio.gather(*requests)\n\n    assert len(responses) == multi_conn_num\n    assert {resp.status for resp in responses} == {200}\n\n    await sess.close()\n    await conn.close()\n\n\n@pytest.mark.xfail\nasync def test_proxy_https_connect(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n) -> None:\n    proxy = await proxy_test_server()\n    url = \"https://www.google.com.ua/search?q=aiohttp proxy\"\n\n    await get_request(url=url, proxy=proxy.url)\n\n    connect = proxy.requests_list[0]\n    assert connect.method == \"CONNECT\"\n    assert connect.path == \"www.google.com.ua:443\"\n    assert connect.host == \"www.google.com.ua\"\n\n    assert proxy.request.host == \"www.google.com.ua\"\n    assert proxy.request.path_qs == \"/search?q=aiohttp+proxy\"\n\n\n@pytest.mark.xfail\nasync def test_proxy_https_connect_with_port(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n) -> None:\n    proxy = await proxy_test_server()\n    url = \"https://secure.aiohttp.io:2242/path\"\n\n    await get_request(url=url, proxy=proxy.url)\n\n    connect = proxy.requests_list[0]\n    assert connect.method == \"CONNECT\"\n    assert connect.path == \"secure.aiohttp.io:2242\"\n    assert connect.host == \"secure.aiohttp.io:2242\"\n\n    assert proxy.request.host == \"secure.aiohttp.io:2242\"\n    assert proxy.request.path_qs == \"/path\"\n\n\n@pytest.mark.xfail\nasync def test_proxy_https_send_body(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    sess = aiohttp.ClientSession()\n    try:\n        proxy = await proxy_test_server()\n        proxy.return_value = {\"status\": 200, \"body\": b\"1\" * (2**20)}\n        url = \"https://www.google.com.ua/search?q=aiohttp proxy\"\n\n        async with sess.get(url, proxy=proxy.url) as resp:\n            body = await resp.read()\n\n        assert body == b\"1\" * (2**20)\n    finally:\n        await sess.close()\n\n\n@pytest.mark.xfail\nasync def test_proxy_https_idna_support(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n) -> None:\n    url = \"https://éé.com/\"\n    proxy = await proxy_test_server()\n\n    await get_request(url=url, proxy=proxy.url)\n\n    connect = proxy.requests_list[0]\n    assert connect.method == \"CONNECT\"\n    assert connect.path == \"xn--9caa.com:443\"\n    assert connect.host == \"xn--9caa.com\"\n\n\nasync def test_proxy_https_connection_error() -> None:\n    url = \"https://secure.aiohttp.io/path\"\n    proxy_url = \"http://localhost:2242/\"\n\n    with pytest.raises(aiohttp.ClientConnectorError):\n        await get_request(url=url, proxy=proxy_url)\n\n\nasync def test_proxy_https_bad_response(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n) -> None:\n    url = \"https://secure.aiohttp.io/path\"\n    proxy = await proxy_test_server()\n    proxy.return_value = dict(status=502, headers={\"Proxy-Agent\": \"TestProxy\"})\n\n    with pytest.raises(aiohttp.ClientHttpProxyError):\n        await get_request(url=url, proxy=proxy.url)\n\n    assert len(proxy.requests_list) == 1\n    assert proxy.request.method == \"CONNECT\"\n    # The following check fails on MacOS\n    # assert proxy.request.path == 'secure.aiohttp.io:443'\n\n\n@pytest.mark.xfail\nasync def test_proxy_https_auth(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n) -> None:\n    url = \"https://secure.aiohttp.io/path\"\n    auth = aiohttp.BasicAuth(\"user\", \"pass\")\n\n    proxy = await proxy_test_server()\n    await get_request(url=url, proxy=proxy.url)\n\n    connect = proxy.requests_list[0]\n    assert \"Authorization\" not in connect.headers\n    assert \"Proxy-Authorization\" not in connect.headers\n    assert \"Authorization\" not in proxy.request.headers\n    assert \"Proxy-Authorization\" not in proxy.request.headers\n\n    proxy = await proxy_test_server()\n    await get_request(url=url, auth=auth, proxy=proxy.url)\n\n    connect = proxy.requests_list[0]\n    assert \"Authorization\" not in connect.headers\n    assert \"Proxy-Authorization\" not in connect.headers\n    assert \"Authorization\" in proxy.request.headers\n    assert \"Proxy-Authorization\" not in proxy.request.headers\n\n    proxy = await proxy_test_server()\n    await get_request(url=url, proxy_auth=auth, proxy=proxy.url)\n\n    connect = proxy.requests_list[0]\n    assert \"Authorization\" not in connect.headers\n    assert \"Proxy-Authorization\" in connect.headers\n    assert \"Authorization\" not in proxy.request.headers\n    assert \"Proxy-Authorization\" not in proxy.request.headers\n\n    proxy = await proxy_test_server()\n    await get_request(url=url, auth=auth, proxy_auth=auth, proxy=proxy.url)\n\n    connect = proxy.requests_list[0]\n    assert \"Authorization\" not in connect.headers\n    assert \"Proxy-Authorization\" in connect.headers\n    assert \"Authorization\" in proxy.request.headers\n    assert \"Proxy-Authorization\" not in proxy.request.headers\n\n\n@pytest.mark.xfail\nasync def test_proxy_https_acquired_cleanup(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    url = \"https://secure.aiohttp.io/path\"\n\n    conn = aiohttp.TCPConnector()\n    sess = aiohttp.ClientSession(connector=conn)\n    try:\n        proxy = await proxy_test_server()\n\n        assert 0 == len(conn._acquired)\n\n        async def request() -> None:\n            async with sess.get(url, proxy=proxy.url):\n                assert 1 == len(conn._acquired)\n\n        await request()\n\n        assert 0 == len(conn._acquired)\n    finally:\n        await sess.close()\n        await conn.close()\n\n\n@pytest.mark.xfail\nasync def test_proxy_https_acquired_cleanup_force(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    url = \"https://secure.aiohttp.io/path\"\n\n    conn = aiohttp.TCPConnector(force_close=True)\n    sess = aiohttp.ClientSession(connector=conn)\n    try:\n        proxy = await proxy_test_server()\n\n        assert 0 == len(conn._acquired)\n\n        async def request() -> None:\n            async with sess.get(url, proxy=proxy.url):\n                assert 1 == len(conn._acquired)\n\n        await request()\n\n        assert 0 == len(conn._acquired)\n    finally:\n        await sess.close()\n        await conn.close()\n\n\n@pytest.mark.xfail\nasync def test_proxy_https_multi_conn_limit(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    url = \"https://secure.aiohttp.io/path\"\n    limit, multi_conn_num = 1, 5\n\n    conn = aiohttp.TCPConnector(limit=limit)\n    sess = aiohttp.ClientSession(connector=conn)\n    proxy = await proxy_test_server()\n\n    try:\n        current_pid = None\n\n        async def request(pid: int) -> ClientResponse:\n            # process requests only one by one\n            nonlocal current_pid\n\n            async with sess.get(url, proxy=proxy.url) as resp:\n                current_pid = pid\n                await asyncio.sleep(0.2)\n                assert current_pid == pid\n\n            return resp\n\n        requests = [request(pid) for pid in range(multi_conn_num)]\n        responses = await asyncio.gather(*requests, return_exceptions=True)\n\n        # Filter out exceptions to count actual responses\n        actual_responses = [r for r in responses if isinstance(r, ClientResponse)]\n        assert len(actual_responses) == multi_conn_num\n        assert {resp.status for resp in actual_responses} == {200}\n    finally:\n        await sess.close()\n        await conn.close()\n\n\ndef _patch_ssl_transport(monkeypatch: pytest.MonkeyPatch) -> None:\n    # Make ssl transport substitution to prevent ssl handshake.\n    def _make_ssl_transport_dummy(\n        self: asyncio.selector_events.BaseSelectorEventLoop,\n        rawsock: object,\n        protocol: object,\n        sslcontext: object,\n        waiter: object = None,\n        **kwargs: object,\n    ) -> object:\n        return self._make_socket_transport(  # type: ignore[attr-defined]\n            rawsock,\n            protocol,\n            waiter,\n            extra=kwargs.get(\"extra\"),\n            server=kwargs.get(\"server\"),\n        )\n\n    monkeypatch.setattr(\n        \"asyncio.selector_events.BaseSelectorEventLoop._make_ssl_transport\",\n        _make_ssl_transport_dummy,\n    )\n\n\noriginal_is_file = pathlib.Path.is_file\n\n\ndef mock_is_file(self: pathlib.Path) -> bool:\n    # make real netrc file invisible in home dir\n    if self.name in [\"_netrc\", \".netrc\"] and self.parent == self.home():\n        return False\n    else:\n        return original_is_file(self)\n\n\nasync def test_proxy_from_env_http(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]], mocker: MockerFixture\n) -> None:\n    url = \"http://aiohttp.io/path\"\n    proxy = await proxy_test_server()\n    mocker.patch.dict(os.environ, {\"http_proxy\": str(proxy.url)})\n    mocker.patch(\"pathlib.Path.is_file\", mock_is_file)\n\n    await get_request(url=url, trust_env=True)\n\n    assert len(proxy.requests_list) == 1\n    assert proxy.request.method == \"GET\"\n    assert proxy.request.host == \"aiohttp.io\"\n    assert proxy.request.path_qs == \"/path\"\n    assert \"Proxy-Authorization\" not in proxy.request.headers\n\n\nasync def test_proxy_from_env_http_with_auth(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]], mocker: MockerFixture\n) -> None:\n    url = \"http://aiohttp.io/path\"\n    proxy = await proxy_test_server()\n    auth = aiohttp.BasicAuth(\"user\", \"pass\")\n    mocker.patch.dict(\n        os.environ,\n        {\n            \"http_proxy\": str(\n                proxy.url.with_user(auth.login).with_password(auth.password)\n            )\n        },\n    )\n\n    await get_request(url=url, trust_env=True)\n\n    assert len(proxy.requests_list) == 1\n    assert proxy.request.method == \"GET\"\n    assert proxy.request.host == \"aiohttp.io\"\n    assert proxy.request.path_qs == \"/path\"\n    assert proxy.request.headers[\"Proxy-Authorization\"] == auth.encode()\n\n\nasync def test_proxy_from_env_http_with_auth_from_netrc(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n    tmp_path: pathlib.Path,\n    mocker: MockerFixture,\n) -> None:\n    url = \"http://aiohttp.io/path\"\n    proxy = await proxy_test_server()\n    auth = aiohttp.BasicAuth(\"user\", \"pass\")\n    netrc_file = tmp_path / \"test_netrc\"\n    netrc_file_data = f\"machine 127.0.0.1 login {auth.login} password {auth.password}\"\n    with netrc_file.open(\"w\") as f:\n        f.write(netrc_file_data)\n    mocker.patch.dict(\n        os.environ, {\"http_proxy\": str(proxy.url), \"NETRC\": str(netrc_file)}\n    )\n\n    await get_request(url=url, trust_env=True)\n\n    assert len(proxy.requests_list) == 1\n    assert proxy.request.method == \"GET\"\n    assert proxy.request.host == \"aiohttp.io\"\n    assert proxy.request.path_qs == \"/path\"\n    assert proxy.request.headers[\"Proxy-Authorization\"] == auth.encode()\n\n\nasync def test_proxy_from_env_http_without_auth_from_netrc(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n    tmp_path: pathlib.Path,\n    mocker: MockerFixture,\n) -> None:\n    url = \"http://aiohttp.io/path\"\n    proxy = await proxy_test_server()\n    auth = aiohttp.BasicAuth(\"user\", \"pass\")\n    netrc_file = tmp_path / \"test_netrc\"\n    netrc_file_data = f\"machine 127.0.0.2 login {auth.login} password {auth.password}\"\n    with netrc_file.open(\"w\") as f:\n        f.write(netrc_file_data)\n    mocker.patch.dict(\n        os.environ, {\"http_proxy\": str(proxy.url), \"NETRC\": str(netrc_file)}\n    )\n\n    await get_request(url=url, trust_env=True)\n\n    assert len(proxy.requests_list) == 1\n    assert proxy.request.method == \"GET\"\n    assert proxy.request.host == \"aiohttp.io\"\n    assert proxy.request.path_qs == \"/path\"\n    assert \"Proxy-Authorization\" not in proxy.request.headers\n\n\nasync def test_proxy_from_env_http_without_auth_from_wrong_netrc(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]],\n    tmp_path: pathlib.Path,\n    mocker: MockerFixture,\n) -> None:\n    url = \"http://aiohttp.io/path\"\n    proxy = await proxy_test_server()\n    auth = aiohttp.BasicAuth(\"user\", \"pass\")\n    netrc_file = tmp_path / \"test_netrc\"\n    invalid_data = f\"machine 127.0.0.1 {auth.login} pass {auth.password}\"\n    with netrc_file.open(\"w\") as f:\n        f.write(invalid_data)\n\n    mocker.patch.dict(\n        os.environ, {\"http_proxy\": str(proxy.url), \"NETRC\": str(netrc_file)}\n    )\n\n    await get_request(url=url, trust_env=True)\n\n    assert len(proxy.requests_list) == 1\n    assert proxy.request.method == \"GET\"\n    assert proxy.request.host == \"aiohttp.io\"\n    assert proxy.request.path_qs == \"/path\"\n    assert \"Proxy-Authorization\" not in proxy.request.headers\n\n\n@pytest.mark.xfail\nasync def test_proxy_from_env_https(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]], mocker: MockerFixture\n) -> None:\n    url = \"https://aiohttp.io/path\"\n    proxy = await proxy_test_server()\n    mocker.patch.dict(os.environ, {\"https_proxy\": str(proxy.url)})\n    mocker.patch(\"pathlib.Path.is_file\", mock_is_file)\n\n    await get_request(url=url, trust_env=True)\n\n    assert len(proxy.requests_list) == 2\n    assert proxy.request.method == \"GET\"\n    assert proxy.request.host == \"aiohttp.io\"\n    assert proxy.request.path_qs == \"/path\"\n    assert \"Proxy-Authorization\" not in proxy.request.headers\n\n\n@pytest.mark.xfail\nasync def test_proxy_from_env_https_with_auth(\n    proxy_test_server: Callable[[], Awaitable[mock.Mock]], mocker: MockerFixture\n) -> None:\n    url = \"https://aiohttp.io/path\"\n    proxy = await proxy_test_server()\n    auth = aiohttp.BasicAuth(\"user\", \"pass\")\n    mocker.patch.dict(\n        os.environ,\n        {\n            \"https_proxy\": str(\n                proxy.url.with_user(auth.login).with_password(auth.password)\n            )\n        },\n    )\n\n    await get_request(url=url, trust_env=True)\n\n    assert len(proxy.requests_list) == 2\n\n    assert proxy.request.method == \"GET\"\n    assert proxy.request.host == \"aiohttp.io\"\n    assert proxy.request.path_qs == \"/path\"\n    assert \"Proxy-Authorization\" not in proxy.request.headers\n\n    r2 = proxy.requests_list[0]\n    assert r2.method == \"CONNECT\"\n    assert r2.host == \"aiohttp.io\"\n    assert r2.path_qs == \"/path\"\n    assert r2.headers[\"Proxy-Authorization\"] == auth.encode()\n\n\nasync def test_proxy_auth() -> None:\n    async with aiohttp.ClientSession() as session:\n        with pytest.raises(\n            ValueError, match=r\"proxy_auth must be None or BasicAuth\\(\\) tuple\"\n        ):\n            async with session.get(\n                \"http://python.org\",\n                proxy=\"http://proxy.example.com\",\n                proxy_auth=(\"user\", \"pass\"),  # type: ignore[arg-type]\n            ):\n                pass\n\n\nasync def test_https_proxy_connect_tunnel_session_close_no_hang(\n    aiohttp_server: AiohttpServer,\n) -> None:\n    \"\"\"Test that CONNECT tunnel connections are not pooled.\"\"\"\n    # Regression test for issue #11273.\n\n    # Create a minimal proxy server\n    # The CONNECT method is handled at the protocol level, not by the handler\n    proxy_app = web.Application()\n    proxy_server = await aiohttp_server(proxy_app)\n    proxy_url = f\"http://{proxy_server.host}:{proxy_server.port}\"\n\n    # Create session and make HTTPS request through proxy\n    session = aiohttp.ClientSession()\n\n    try:\n        # This will fail during TLS upgrade because proxy doesn't establish tunnel\n        with suppress(aiohttp.ClientError):\n            async with session.get(\"https://example.com/test\", proxy=proxy_url) as resp:\n                await resp.read()\n\n        # The critical test: Check if any connections were pooled with proxy=None\n        # This is the root cause of the hang - CONNECT tunnel connections\n        # should NOT be pooled\n        connector = session.connector\n        assert connector is not None\n\n        # Count connections with proxy=None in the pool\n        proxy_none_keys = [key for key in connector._conns if key.proxy is None]\n        proxy_none_count = len(proxy_none_keys)\n\n        # Before the fix, there would be a connection with proxy=None\n        # After the fix, CONNECT tunnel connections are not pooled\n        assert proxy_none_count == 0, (\n            f\"Found {proxy_none_count} connections with proxy=None in pool. \"\n            f\"CONNECT tunnel connections should not be pooled - this is bug #11273\"\n        )\n\n    finally:\n        # Clean close\n        await session.close()\n"
  },
  {
    "path": "tests/test_pytest_plugin.py",
    "content": "import os\nimport platform\nimport warnings\n\nimport pytest\n\nfrom aiohttp import pytest_plugin\n\npytest_plugins: str = \"pytester\"\n\nCONFTEST: str = \"\"\"\npytest_plugins = 'aiohttp.pytest_plugin'\n\"\"\"\n\n\nIS_PYPY = platform.python_implementation() == \"PyPy\"\n\n\ndef test_aiohttp_plugin(testdir: pytest.Testdir) -> None:\n    testdir.makepyfile(\"\"\"\\\nimport pytest\nfrom unittest import mock\n\nfrom aiohttp import web\n\nvalue = web.AppKey('value', str)\n\n\nasync def hello(request):\n    return web.Response(body=b'Hello, world')\n\n\nasync def create_app():\n    app = web.Application()\n    app.router.add_route('GET', '/', hello)\n    return app\n\n\nasync def test_hello(aiohttp_client) -> None:\n    client = await aiohttp_client(await create_app())\n    resp = await client.get('/')\n    assert resp.status == 200\n    text = await resp.text()\n    assert 'Hello, world' in text\n\n\nasync def test_hello_from_app(aiohttp_client) -> None:\n    app = web.Application()\n    app.router.add_get('/', hello)\n    client = await aiohttp_client(app)\n    resp = await client.get('/')\n    assert resp.status == 200\n    text = await resp.text()\n    assert 'Hello, world' in text\n\n\nasync def test_hello_with_loop(aiohttp_client) -> None:\n    client = await aiohttp_client(await create_app())\n    resp = await client.get('/')\n    assert resp.status == 200\n    text = await resp.text()\n    assert 'Hello, world' in text\n\n\nasync def test_noop() -> None:\n    pass\n\n\nasync def previous(request):\n    if request.method == 'POST':\n        with pytest.deprecated_call():  # FIXME: this isn't actually called\n            request.app[value] = (await request.post())['value']\n        return web.Response(body=b'thanks for the data')\n    else:\n        v = request.app.get(value, 'unknown')\n        return web.Response(body='value: {}'.format(v).encode())\n\n\ndef create_stateful_app():\n    app = web.Application()\n    app.router.add_route('*', '/', previous)\n    return app\n\n\n@pytest.fixture\ndef cli(loop, aiohttp_client):\n    return loop.run_until_complete(aiohttp_client(create_stateful_app()))\n\n\ndef test_noncoro() -> None:\n    assert True\n\n\nasync def test_failed_to_create_client(aiohttp_client) -> None:\n\n    def make_app():\n        raise RuntimeError()\n\n    with pytest.raises(RuntimeError):\n        await aiohttp_client(make_app())\n\n\nasync def test_custom_port_aiohttp_client(aiohttp_client, aiohttp_unused_port):\n    port = aiohttp_unused_port()\n    client = await aiohttp_client(await create_app(),\n                                  server_kwargs={'port': port})\n    assert client.port == port\n    resp = await client.get('/')\n    assert resp.status == 200\n    text = await resp.text()\n    assert 'Hello, world' in text\n\n\nasync def test_custom_port_test_server(aiohttp_server, aiohttp_unused_port):\n    app = await create_app()\n    port = aiohttp_unused_port()\n    server = await aiohttp_server(app, port=port)\n    assert server.port == port\n\"\"\")\n    testdir.makeconftest(CONFTEST)\n    result = testdir.runpytest(\"-p\", \"no:sugar\", \"--aiohttp-loop=pyloop\")\n    result.assert_outcomes(passed=8)\n\n\ndef test_warning_checks(testdir: pytest.Testdir) -> None:\n    testdir.makepyfile(\"\"\"\\\n\nasync def foobar():\n    return 123\n\nasync def test_good() -> None:\n    v = await foobar()\n    assert v == 123\n\nasync def test_bad() -> None:\n    foobar()\n\"\"\")\n    testdir.makeconftest(CONFTEST)\n    result = testdir.runpytest(\n        \"-p\", \"no:sugar\", \"-s\", \"-W\", \"default\", \"--aiohttp-loop=pyloop\"\n    )\n    expected_outcomes = (\n        {\"failed\": 0, \"passed\": 2}\n        if IS_PYPY and bool(os.environ.get(\"PYTHONASYNCIODEBUG\"))\n        else {\"failed\": 1, \"passed\": 1}\n    )\n    # Under PyPy \"coroutine 'foobar' was never awaited\" does not happen.\n    result.assert_outcomes(**expected_outcomes)\n\n\ndef test_aiohttp_plugin_async_fixture(\n    testdir: pytest.Testdir, capsys: pytest.CaptureFixture[str]\n) -> None:\n    testdir.makepyfile(\"\"\"\\\nimport pytest\n\nfrom aiohttp import web\n\n\nasync def hello(request):\n    return web.Response(body=b'Hello, world')\n\n\ndef create_app():\n    app = web.Application()\n    app.router.add_route('GET', '/', hello)\n    return app\n\n\n@pytest.fixture\nasync def cli(aiohttp_client, loop):\n    client = await aiohttp_client(create_app())\n    return client\n\n\n@pytest.fixture\nasync def foo():\n    return 42\n\n\n@pytest.fixture\nasync def bar(request):\n    # request should be accessible in async fixtures if needed\n    return request.function\n\n\nasync def test_hello(cli, loop) -> None:\n    resp = await cli.get('/')\n    assert resp.status == 200\n\n\ndef test_foo(loop, foo) -> None:\n    assert foo == 42\n\n\ndef test_foo_without_loop(foo) -> None:\n    # will raise an error because there is no loop\n    pass\n\n\ndef test_bar(loop, bar) -> None:\n    assert bar is test_bar\n\"\"\")\n    testdir.makeconftest(CONFTEST)\n    result = testdir.runpytest(\"-p\", \"no:sugar\", \"--aiohttp-loop=pyloop\")\n    result.assert_outcomes(passed=3, errors=1)\n    result.stdout.fnmatch_lines(\n        \"*Asynchronous fixtures must depend on the 'loop' fixture \"\n        \"or be used in tests depending from it.\"\n    )\n\n\ndef test_aiohttp_plugin_async_gen_fixture(testdir: pytest.Testdir) -> None:\n    testdir.makepyfile(\"\"\"\\\nimport pytest\nfrom unittest import mock\n\nfrom aiohttp import web\n\n\ncanary = mock.Mock()\n\n\nasync def hello(request):\n    return web.Response(body=b'Hello, world')\n\n\ndef create_app():\n    app = web.Application()\n    app.router.add_route('GET', '/', hello)\n    return app\n\n\n@pytest.fixture\nasync def cli(aiohttp_client, loop):\n    yield await aiohttp_client(create_app())\n    canary()\n\n\nasync def test_hello(cli) -> None:\n    resp = await cli.get('/')\n    assert resp.status == 200\n\n\ndef test_finalized() -> None:\n    assert canary.called is True\n\"\"\")\n    testdir.makeconftest(CONFTEST)\n    result = testdir.runpytest(\"-p\", \"no:sugar\", \"--aiohttp-loop=pyloop\")\n    result.assert_outcomes(passed=2)\n\n\ndef test_warnings_propagated(recwarn: pytest.WarningsRecorder) -> None:\n    with pytest_plugin._runtime_warning_context():\n        warnings.warn(\"test warning is propagated\")\n    assert len(recwarn) == 1\n    message = recwarn[0].message\n    assert isinstance(message, UserWarning)\n    assert message.args == (\"test warning is propagated\",)\n\n\ndef test_aiohttp_client_cls_fixture_custom_client_used(testdir: pytest.Testdir) -> None:\n    testdir.makepyfile(\"\"\"\nimport pytest\nfrom aiohttp.web import Application\nfrom aiohttp.test_utils import TestClient\n\n\nclass CustomClient(TestClient):\n    pass\n\n\n@pytest.fixture\ndef aiohttp_client_cls():\n    return CustomClient\n\n\nasync def test_hello(aiohttp_client) -> None:\n    client = await aiohttp_client(Application())\n    assert isinstance(client, CustomClient)\n\n\"\"\")\n    testdir.makeconftest(CONFTEST)\n    result = testdir.runpytest()\n    result.assert_outcomes(passed=1)\n\n\ndef test_aiohttp_client_cls_fixture_factory(testdir: pytest.Testdir) -> None:\n    testdir.makeconftest(CONFTEST + \"\"\"\n\ndef pytest_configure(config):\n    config.addinivalue_line(\"markers\", \"rest: RESTful API tests\")\n    config.addinivalue_line(\"markers\", \"graphql: GraphQL API tests\")\n\n\"\"\")\n    testdir.makepyfile(\"\"\"\nimport pytest\nfrom aiohttp.web import Application\nfrom aiohttp.test_utils import TestClient\n\n\nclass RESTfulClient(TestClient):\n    pass\n\n\nclass GraphQLClient(TestClient):\n    pass\n\n\n@pytest.fixture\ndef aiohttp_client_cls(request):\n    if request.node.get_closest_marker('rest') is not None:\n        return RESTfulClient\n    elif request.node.get_closest_marker('graphql') is not None:\n        return GraphQLClient\n    return TestClient\n\n\n@pytest.mark.rest\nasync def test_rest(aiohttp_client) -> None:\n    client = await aiohttp_client(Application())\n    assert isinstance(client, RESTfulClient)\n\n\n@pytest.mark.graphql\nasync def test_graphql(aiohttp_client) -> None:\n    client = await aiohttp_client(Application())\n    assert isinstance(client, GraphQLClient)\n\n\"\"\")\n    result = testdir.runpytest()\n    result.assert_outcomes(passed=2)\n"
  },
  {
    "path": "tests/test_resolver.py",
    "content": "import asyncio\nimport gc\nimport ipaddress\nimport socket\nfrom collections.abc import Awaitable, Callable, Collection, Generator\nfrom ipaddress import ip_address\nfrom typing import Any, NamedTuple\nfrom unittest.mock import Mock, create_autospec, patch\n\nimport pytest\n\nfrom aiohttp.resolver import (\n    _NAME_SOCKET_FLAGS,\n    AsyncResolver,\n    DefaultResolver,\n    ThreadedResolver,\n    _DNSResolverManager,\n)\n\ntry:\n    import aiodns\n\n    getaddrinfo = hasattr(aiodns.DNSResolver, \"getaddrinfo\")\nexcept ImportError:  # pragma: no cover\n    aiodns = None  # type: ignore[assignment]\n    getaddrinfo = False\n\n_AddrInfo4 = list[\n    tuple[socket.AddressFamily, None, socket.SocketKind, None, tuple[str, int]]\n]\n_AddrInfo6 = list[\n    tuple[\n        socket.AddressFamily, None, socket.SocketKind, None, tuple[str, int, int, int]\n    ]\n]\n_UnknownAddrInfo = list[\n    tuple[socket.AddressFamily, socket.SocketKind, int, str, tuple[int, bytes]]\n]\n\n\n@pytest.fixture()\ndef check_no_lingering_resolvers() -> Generator[None, None, None]:\n    \"\"\"Verify no resolvers remain after the test.\n\n    This fixture should be used in any test that creates instances of\n    AsyncResolver or directly uses _DNSResolverManager.\n    \"\"\"\n    manager = _DNSResolverManager()\n    before = len(manager._loop_data)\n    yield\n    after = len(manager._loop_data)\n    if after > before:  # pragma: no branch\n        # Force garbage collection to ensure weak references are updated\n        gc.collect()  # pragma: no cover\n        after = len(manager._loop_data)  # pragma: no cover\n        if after > before:  # pragma: no cover\n            pytest.fail(  # pragma: no cover\n                f\"Lingering resolvers found: {(after - before)} \"\n                \"new AsyncResolver instances were not properly closed.\"\n            )\n\n\n@pytest.fixture()\ndef dns_resolver_manager() -> Generator[_DNSResolverManager, None, None]:\n    \"\"\"Create a fresh _DNSResolverManager instance for testing.\n\n    Saves and restores the singleton state to avoid affecting other tests.\n    \"\"\"\n    # Save the original instance\n    original_instance = _DNSResolverManager._instance\n\n    # Reset the singleton\n    _DNSResolverManager._instance = None\n\n    # Create and yield a fresh instance\n    try:\n        yield _DNSResolverManager()\n    finally:\n        # Clean up and restore the original instance\n        _DNSResolverManager._instance = original_instance\n\n\nclass FakeAIODNSAddrInfoNode(NamedTuple):\n\n    family: int\n    addr: tuple[bytes, int] | tuple[bytes, int, int, int]\n\n\nclass FakeAIODNSAddrInfoIPv4Result:\n    def __init__(self, hosts: Collection[str]) -> None:\n        self.nodes = [\n            FakeAIODNSAddrInfoNode(socket.AF_INET, (h.encode(), 0)) for h in hosts\n        ]\n\n\nclass FakeAIODNSAddrInfoIPv6Result:\n    def __init__(self, hosts: Collection[str]) -> None:\n        self.nodes = [\n            FakeAIODNSAddrInfoNode(\n                socket.AF_INET6,\n                (h.encode(), 0, 0, 3 if ip_address(h).is_link_local else 0),\n            )\n            for h in hosts\n        ]\n\n\nclass FakeAIODNSNameInfoIPv6Result:\n    def __init__(self, host: str) -> None:\n        self.node = host\n        self.service = None\n\n\nasync def fake_aiodns_getaddrinfo_ipv4_result(\n    hosts: Collection[str],\n) -> FakeAIODNSAddrInfoIPv4Result:\n    return FakeAIODNSAddrInfoIPv4Result(hosts=hosts)\n\n\nasync def fake_aiodns_getaddrinfo_ipv6_result(\n    hosts: Collection[str],\n) -> FakeAIODNSAddrInfoIPv6Result:\n    return FakeAIODNSAddrInfoIPv6Result(hosts=hosts)\n\n\nasync def fake_aiodns_getnameinfo_ipv6_result(\n    host: str,\n) -> FakeAIODNSNameInfoIPv6Result:\n    return FakeAIODNSNameInfoIPv6Result(host)\n\n\ndef fake_addrinfo(hosts: Collection[str]) -> Callable[..., Awaitable[_AddrInfo4]]:\n    async def fake(*args: Any, **kwargs: Any) -> _AddrInfo4:\n        if not hosts:\n            raise socket.gaierror\n\n        return [(socket.AF_INET, None, socket.SOCK_STREAM, None, (h, 0)) for h in hosts]\n\n    return fake\n\n\ndef fake_ipv6_addrinfo(hosts: Collection[str]) -> Callable[..., Awaitable[_AddrInfo6]]:\n    async def fake(*args: Any, **kwargs: Any) -> _AddrInfo6:\n        if not hosts:\n            raise socket.gaierror\n\n        return [\n            (\n                socket.AF_INET6,\n                None,\n                socket.SOCK_STREAM,\n                None,\n                (h, 0, 0, 3 if ip_address(h).is_link_local else 0),\n            )\n            for h in hosts\n        ]\n\n    return fake\n\n\ndef fake_ipv6_nameinfo(host: str) -> Callable[..., Awaitable[tuple[str, int]]]:\n    async def fake(*args: Any, **kwargs: Any) -> tuple[str, int]:\n        return host, 0\n\n    return fake\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\n@pytest.mark.usefixtures(\"check_no_lingering_resolvers\")\nasync def test_async_resolver_positive_ipv4_lookup(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    with patch(\"aiodns.DNSResolver\") as mock:\n        mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv4_result(\n            [\"127.0.0.1\"]\n        )\n        resolver = AsyncResolver()\n        real = await resolver.resolve(\"www.python.org\")\n        ipaddress.ip_address(real[0][\"host\"])\n        mock().getaddrinfo.assert_called_with(\n            \"www.python.org\",\n            family=socket.AF_INET,\n            flags=socket.AI_ADDRCONFIG,\n            port=0,\n            type=socket.SOCK_STREAM,\n        )\n        await resolver.close()\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\n@pytest.mark.usefixtures(\"check_no_lingering_resolvers\")\nasync def test_async_resolver_positive_link_local_ipv6_lookup(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    with patch(\"aiodns.DNSResolver\") as mock:\n        mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv6_result(\n            [\"fe80::1\"]\n        )\n        mock().getnameinfo.return_value = fake_aiodns_getnameinfo_ipv6_result(\n            \"fe80::1%eth0\"\n        )\n        resolver = AsyncResolver()\n        real = await resolver.resolve(\"www.python.org\")\n        ipaddress.ip_address(real[0][\"host\"])\n        mock().getaddrinfo.assert_called_with(\n            \"www.python.org\",\n            family=socket.AF_INET,\n            flags=socket.AI_ADDRCONFIG,\n            port=0,\n            type=socket.SOCK_STREAM,\n        )\n        mock().getnameinfo.assert_called_with((\"fe80::1\", 0, 0, 3), _NAME_SOCKET_FLAGS)\n        await resolver.close()\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\n@pytest.mark.usefixtures(\"check_no_lingering_resolvers\")\nasync def test_async_resolver_multiple_replies(loop: asyncio.AbstractEventLoop) -> None:\n    with patch(\"aiodns.DNSResolver\") as mock:\n        ips = [\"127.0.0.1\", \"127.0.0.2\", \"127.0.0.3\", \"127.0.0.4\"]\n        mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv4_result(ips)\n        resolver = AsyncResolver()\n        real = await resolver.resolve(\"www.google.com\")\n        ipaddrs = [ipaddress.ip_address(x[\"host\"]) for x in real]\n        assert len(ipaddrs) > 3, \"Expecting multiple addresses\"\n        await resolver.close()\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\n@pytest.mark.usefixtures(\"check_no_lingering_resolvers\")\nasync def test_async_resolver_negative_lookup(loop: asyncio.AbstractEventLoop) -> None:\n    with patch(\"aiodns.DNSResolver\") as mock:\n        mock().getaddrinfo.side_effect = aiodns.error.DNSError()\n        resolver = AsyncResolver()\n        with pytest.raises(OSError):\n            await resolver.resolve(\"doesnotexist.bla\")\n        await resolver.close()\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\n@pytest.mark.usefixtures(\"check_no_lingering_resolvers\")\nasync def test_async_resolver_no_hosts_in_getaddrinfo(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    with patch(\"aiodns.DNSResolver\") as mock:\n        mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv4_result([])\n        resolver = AsyncResolver()\n        with pytest.raises(OSError):\n            await resolver.resolve(\"doesnotexist.bla\")\n        await resolver.close()\n\n\nasync def test_threaded_resolver_positive_lookup() -> None:\n    loop = Mock()\n    loop.getaddrinfo = fake_addrinfo([\"127.0.0.1\"])\n    resolver = ThreadedResolver()\n    resolver._loop = loop\n    real = await resolver.resolve(\"www.python.org\")\n    assert real[0][\"hostname\"] == \"www.python.org\"\n    ipaddress.ip_address(real[0][\"host\"])\n\n\nasync def test_threaded_resolver_positive_ipv6_link_local_lookup() -> None:\n    loop = Mock()\n    loop.getaddrinfo = fake_ipv6_addrinfo([\"fe80::1\"])\n    loop.getnameinfo = fake_ipv6_nameinfo(\"fe80::1%eth0\")\n\n    # Mock the fake function that was returned by helper functions\n    loop.getaddrinfo = create_autospec(loop.getaddrinfo)\n    loop.getnameinfo = create_autospec(loop.getnameinfo)\n\n    # Set the correct return values for mock functions\n    loop.getaddrinfo.return_value = await fake_ipv6_addrinfo([\"fe80::1\"])()\n    loop.getnameinfo.return_value = await fake_ipv6_nameinfo(\"fe80::1%eth0\")()\n\n    resolver = ThreadedResolver()\n    resolver._loop = loop\n    real = await resolver.resolve(\"www.python.org\")\n    assert real[0][\"hostname\"] == \"www.python.org\"\n    ipaddress.ip_address(real[0][\"host\"])\n\n    loop.getaddrinfo.assert_called_with(\n        \"www.python.org\",\n        0,\n        type=socket.SOCK_STREAM,\n        family=socket.AF_INET,\n        flags=socket.AI_ADDRCONFIG,\n    )\n\n    loop.getnameinfo.assert_called_with((\"fe80::1\", 0, 0, 3), _NAME_SOCKET_FLAGS)\n\n\nasync def test_threaded_resolver_multiple_replies() -> None:\n    loop = Mock()\n    ips = [\"127.0.0.1\", \"127.0.0.2\", \"127.0.0.3\", \"127.0.0.4\"]\n    loop.getaddrinfo = fake_addrinfo(ips)\n    resolver = ThreadedResolver()\n    resolver._loop = loop\n    real = await resolver.resolve(\"www.google.com\")\n    ipaddrs = [ipaddress.ip_address(x[\"host\"]) for x in real]\n    assert len(ipaddrs) > 3, \"Expecting multiple addresses\"\n\n\nasync def test_threaded_negative_lookup() -> None:\n    loop = Mock()\n    ips: list[str] = []\n    loop.getaddrinfo = fake_addrinfo(ips)\n    resolver = ThreadedResolver()\n    resolver._loop = loop\n    with pytest.raises(socket.gaierror):\n        await resolver.resolve(\"doesnotexist.bla\")\n\n\nasync def test_threaded_negative_ipv6_lookup() -> None:\n    loop = Mock()\n    ips: list[str] = []\n    loop.getaddrinfo = fake_ipv6_addrinfo(ips)\n    resolver = ThreadedResolver()\n    resolver._loop = loop\n    with pytest.raises(socket.gaierror):\n        await resolver.resolve(\"doesnotexist.bla\")\n\n\nasync def test_threaded_negative_lookup_with_unknown_result() -> None:\n    loop = Mock()\n\n    # If compile CPython with `--disable-ipv6` option,\n    # we will get an (int, bytes) tuple, instead of a Exception.\n    async def unknown_addrinfo(*args: Any, **kwargs: Any) -> _UnknownAddrInfo:\n        return [\n            (\n                socket.AF_INET6,\n                socket.SOCK_STREAM,\n                6,\n                \"\",\n                (10, b\"\\x01\\xbb\\x00\\x00\\x00\\x00*\\x04NB\\x00\\x1a\\x00\\x00\"),\n            )\n        ]\n\n    loop.getaddrinfo = unknown_addrinfo\n    resolver = ThreadedResolver()\n    resolver._loop = loop\n    with patch(\"socket.has_ipv6\", False):\n        res = await resolver.resolve(\"www.python.org\")\n    assert len(res) == 0\n\n\nasync def test_close_for_threaded_resolver(loop: asyncio.AbstractEventLoop) -> None:\n    resolver = ThreadedResolver()\n    await resolver.close()\n\n\n@pytest.mark.skipif(aiodns is None, reason=\"aiodns required\")\n@pytest.mark.usefixtures(\"check_no_lingering_resolvers\")\nasync def test_close_for_async_resolver(loop: asyncio.AbstractEventLoop) -> None:\n    resolver = AsyncResolver()\n    await resolver.close()\n\n\nasync def test_default_loop_for_threaded_resolver(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    asyncio.set_event_loop(loop)\n    resolver = ThreadedResolver()\n    assert resolver._loop is loop\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\n@pytest.mark.usefixtures(\"check_no_lingering_resolvers\")\nasync def test_async_resolver_ipv6_positive_lookup(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    with patch(\"aiodns.DNSResolver\") as mock:\n        mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv6_result([\"::1\"])\n        resolver = AsyncResolver()\n        real = await resolver.resolve(\"www.python.org\")\n        ipaddress.ip_address(real[0][\"host\"])\n        mock().getaddrinfo.assert_called_with(\n            \"www.python.org\",\n            family=socket.AF_INET,\n            flags=socket.AI_ADDRCONFIG,\n            port=0,\n            type=socket.SOCK_STREAM,\n        )\n        await resolver.close()\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\n@pytest.mark.usefixtures(\"check_no_lingering_resolvers\")\nasync def test_async_resolver_error_messages_passed(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Ensure error messages are passed through from aiodns.\"\"\"\n    with patch(\"aiodns.DNSResolver\", autospec=True, spec_set=True) as mock:\n        mock().getaddrinfo.side_effect = aiodns.error.DNSError(1, \"Test error message\")\n        resolver = AsyncResolver()\n        with pytest.raises(OSError, match=\"Test error message\") as excinfo:\n            await resolver.resolve(\"x.org\")\n\n        assert excinfo.value.strerror == \"Test error message\"\n        await resolver.close()\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\n@pytest.mark.usefixtures(\"check_no_lingering_resolvers\")\nasync def test_async_resolver_error_messages_passed_no_hosts(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    \"\"\"Ensure error messages are passed through from aiodns.\"\"\"\n    with patch(\"aiodns.DNSResolver\", autospec=True, spec_set=True) as mock:\n        mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv6_result([])\n        resolver = AsyncResolver()\n        with pytest.raises(OSError, match=\"DNS lookup failed\") as excinfo:\n            await resolver.resolve(\"x.org\")\n\n        assert excinfo.value.strerror == \"DNS lookup failed\"\n        await resolver.close()\n\n\n@pytest.mark.usefixtures(\"check_no_lingering_resolvers\")\nasync def test_async_resolver_aiodns_not_present(\n    loop: asyncio.AbstractEventLoop, monkeypatch: pytest.MonkeyPatch\n) -> None:\n    monkeypatch.setattr(\"aiohttp.resolver.aiodns\", None)\n    with pytest.raises(RuntimeError):\n        AsyncResolver()\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\n@pytest.mark.usefixtures(\"check_no_lingering_resolvers\")\ndef test_aio_dns_is_default() -> None:\n    assert DefaultResolver is AsyncResolver\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\nasync def test_dns_resolver_manager_sharing(\n    dns_resolver_manager: _DNSResolverManager,\n) -> None:\n    \"\"\"Test that the DNSResolverManager shares a resolver among AsyncResolver instances.\"\"\"\n    # Create two default AsyncResolver instances\n    resolver1 = AsyncResolver()\n    resolver2 = AsyncResolver()\n\n    # Check that they share the same underlying resolver\n    assert resolver1._resolver is resolver2._resolver\n\n    # Create an AsyncResolver with custom args\n    resolver3 = AsyncResolver(nameservers=[\"8.8.8.8\"])\n\n    # Check that it has its own resolver\n    assert resolver1._resolver is not resolver3._resolver\n\n    # Cleanup\n    await resolver1.close()\n    await resolver2.close()\n    await resolver3.close()\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\nasync def test_dns_resolver_manager_singleton(\n    dns_resolver_manager: _DNSResolverManager,\n) -> None:\n    \"\"\"Test that DNSResolverManager is a singleton.\"\"\"\n    # Create a second manager and check it's the same instance\n    manager1 = dns_resolver_manager\n    manager2 = _DNSResolverManager()\n\n    assert manager1 is manager2\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\nasync def test_dns_resolver_manager_resolver_lifecycle(\n    dns_resolver_manager: _DNSResolverManager,\n) -> None:\n    \"\"\"Test that DNSResolverManager creates and destroys resolver correctly.\"\"\"\n    manager = dns_resolver_manager\n\n    # Initially there should be no resolvers\n    assert not manager._loop_data\n\n    # Create a mock AsyncResolver for testing\n    mock_client = Mock(spec=AsyncResolver)\n    mock_client._loop = asyncio.get_running_loop()\n\n    # Getting resolver should create one\n    mock_loop = mock_client._loop\n    resolver = manager.get_resolver(mock_client, mock_loop)\n    assert resolver is not None\n    assert manager._loop_data[mock_loop][0] is resolver\n\n    # Getting it again should return the same instance\n    assert manager.get_resolver(mock_client, mock_loop) is resolver\n\n    # Clean up\n    manager.release_resolver(mock_client, mock_loop)\n    assert not manager._loop_data\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\nasync def test_dns_resolver_manager_client_registration(\n    dns_resolver_manager: _DNSResolverManager,\n) -> None:\n    \"\"\"Test client registration and resolver release logic.\"\"\"\n    with patch(\"aiodns.DNSResolver\") as mock:\n        # Create resolver instances\n        resolver1 = AsyncResolver()\n        resolver2 = AsyncResolver()\n\n        # Both should use the same resolver from the manager\n        assert resolver1._resolver is resolver2._resolver\n\n        # The manager should be tracking both clients\n        assert resolver1._manager is resolver2._manager\n        manager = resolver1._manager\n        assert manager is not None\n        loop = asyncio.get_running_loop()\n        _, client_set = manager._loop_data[loop]\n        assert len(client_set) == 2\n\n        # Close one resolver\n        await resolver1.close()\n        _, client_set = manager._loop_data[loop]\n        assert len(client_set) == 1\n\n        # Resolver should still exist\n        assert manager._loop_data  # Not empty\n\n        # Close the second resolver\n        await resolver2.close()\n        assert not manager._loop_data  # Should be empty after closing all clients\n\n        # Now all resolvers should be canceled and removed\n        assert not manager._loop_data  # Should be empty\n        mock().cancel.assert_called_once()\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\nasync def test_dns_resolver_manager_multiple_event_loops(\n    dns_resolver_manager: _DNSResolverManager,\n) -> None:\n    \"\"\"Test that DNSResolverManager correctly manages resolvers across different event loops.\"\"\"\n    # Create separate resolvers for each loop\n    resolver1 = Mock(name=\"resolver1\")\n    resolver2 = Mock(name=\"resolver2\")\n\n    # Create a patch that returns different resolvers based on the loop argument\n    mock_resolver = Mock()\n    mock_resolver.side_effect = lambda loop=None, **kwargs: (\n        resolver1 if loop is asyncio.get_running_loop() else resolver2\n    )\n\n    with patch(\"aiodns.DNSResolver\", mock_resolver):\n        manager = dns_resolver_manager\n\n        # Create two mock clients on different loops\n        mock_client1 = Mock(spec=AsyncResolver)\n        mock_client1._loop = asyncio.get_running_loop()\n\n        # Create a second event loop\n        loop2 = Mock(spec=asyncio.AbstractEventLoop)\n        mock_client2 = Mock(spec=AsyncResolver)\n        mock_client2._loop = loop2\n\n        # Get resolvers for both clients\n        loop1 = mock_client1._loop\n        loop2 = mock_client2._loop\n\n        # Get the resolvers through the manager\n        manager_resolver1 = manager.get_resolver(mock_client1, loop1)\n        manager_resolver2 = manager.get_resolver(mock_client2, loop2)\n\n        # Should be different resolvers for different loops\n        assert manager_resolver1 is resolver1\n        assert manager_resolver2 is resolver2\n        assert manager._loop_data[loop1][0] is resolver1\n        assert manager._loop_data[loop2][0] is resolver2\n\n        # Release the first resolver\n        manager.release_resolver(mock_client1, loop1)\n\n        # First loop's resolver should be gone, but second should remain\n        assert loop1 not in manager._loop_data\n        assert loop2 in manager._loop_data\n\n        # Release the second resolver\n        manager.release_resolver(mock_client2, loop2)\n\n        # Both resolvers should be gone\n        assert not manager._loop_data\n\n        # Verify resolver cleanup\n        resolver1.cancel.assert_called_once()\n        resolver2.cancel.assert_called_once()\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\nasync def test_dns_resolver_manager_weakref_garbage_collection() -> None:\n    \"\"\"Test that release_resolver handles None resolver due to weakref garbage collection.\"\"\"\n    manager = _DNSResolverManager()\n\n    # Create a mock resolver that will be None when accessed\n    mock_resolver = Mock()\n    mock_resolver.cancel = Mock()\n\n    with patch(\"aiodns.DNSResolver\", return_value=mock_resolver):\n        # Create an AsyncResolver to get a resolver from the manager\n        resolver = AsyncResolver()\n        loop = asyncio.get_running_loop()\n\n        # Manually corrupt the data to simulate garbage collection\n        # by setting the resolver to None\n        manager._loop_data[loop] = (None, manager._loop_data[loop][1])  # type: ignore[assignment]\n\n        # This should not raise an AttributeError: 'NoneType' object has no attribute 'cancel'\n        await resolver.close()\n\n        # Verify no exception was raised and the loop data was cleaned up properly\n        # Since we set resolver to None and there was one client, the entry should be removed\n        assert loop not in manager._loop_data\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\nasync def test_dns_resolver_manager_missing_loop_data() -> None:\n    \"\"\"Test that release_resolver handles missing loop data gracefully.\"\"\"\n    manager = _DNSResolverManager()\n\n    with patch(\"aiodns.DNSResolver\"):\n        # Create an AsyncResolver\n        resolver = AsyncResolver()\n        loop = asyncio.get_running_loop()\n\n        # Manually remove the loop data to simulate race condition\n        manager._loop_data.clear()\n\n        # This should not raise a KeyError\n        await resolver.close()\n\n        # Verify no exception was raised\n        assert loop not in manager._loop_data\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\n@pytest.mark.usefixtures(\"check_no_lingering_resolvers\")\nasync def test_async_resolver_close_multiple_times() -> None:\n    \"\"\"Test that AsyncResolver.close() can be called multiple times without error.\"\"\"\n    with patch(\"aiodns.DNSResolver\") as mock_dns_resolver:\n        mock_resolver = Mock()\n        mock_resolver.cancel = Mock()\n        mock_dns_resolver.return_value = mock_resolver\n\n        # Create a resolver with custom args (dedicated resolver)\n        resolver = AsyncResolver(nameservers=[\"8.8.8.8\"])\n\n        # Close it once\n        await resolver.close()\n        mock_resolver.cancel.assert_called_once()\n\n        # Close it again - should not raise AttributeError\n        await resolver.close()\n        # cancel should still only be called once\n        mock_resolver.cancel.assert_called_once()\n\n\n@pytest.mark.skipif(not getaddrinfo, reason=\"aiodns >=3.2.0 required\")\n@pytest.mark.usefixtures(\"check_no_lingering_resolvers\")\nasync def test_async_resolver_close_with_none_resolver() -> None:\n    \"\"\"Test that AsyncResolver.close() handles None resolver gracefully.\"\"\"\n    with patch(\"aiodns.DNSResolver\"):\n        # Create a resolver with custom args (dedicated resolver)\n        resolver = AsyncResolver(nameservers=[\"8.8.8.8\"])\n\n        # Manually set resolver to None to simulate edge case\n        resolver._resolver = None  # type: ignore[assignment]\n\n        # This should not raise AttributeError\n        await resolver.close()\n"
  },
  {
    "path": "tests/test_route_def.py",
    "content": "import pathlib\nfrom typing import NoReturn\n\nimport pytest\nfrom yarl import URL\n\nfrom aiohttp import web\nfrom aiohttp.web_urldispatcher import UrlDispatcher\n\n\n@pytest.fixture\ndef router() -> UrlDispatcher:\n    return UrlDispatcher()\n\n\ndef test_get(router: UrlDispatcher) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    router.add_routes([web.get(\"/\", handler)])\n    assert len(router.routes()) == 2  # GET and HEAD\n\n    route = list(router.routes())[1]\n    assert route.handler is handler\n    assert route.method == \"GET\"\n    assert str(route.url_for()) == \"/\"\n\n    route2 = list(router.routes())[0]\n    assert route2.handler is handler\n    assert route2.method == \"HEAD\"\n\n\ndef test_head(router: UrlDispatcher) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    router.add_routes([web.head(\"/\", handler)])\n    assert len(router.routes()) == 1\n\n    route = list(router.routes())[0]\n    assert route.handler is handler\n    assert route.method == \"HEAD\"\n    assert str(route.url_for()) == \"/\"\n\n\ndef test_options(router: UrlDispatcher) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    router.add_routes([web.options(\"/\", handler)])\n    assert len(router.routes()) == 1\n\n    route = list(router.routes())[0]\n    assert route.handler is handler\n    assert route.method == \"OPTIONS\"\n    assert str(route.url_for()) == \"/\"\n\n\ndef test_post(router: UrlDispatcher) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    router.add_routes([web.post(\"/\", handler)])\n\n    route = list(router.routes())[0]\n    assert route.handler is handler\n    assert route.method == \"POST\"\n    assert str(route.url_for()) == \"/\"\n\n\ndef test_put(router: UrlDispatcher) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    router.add_routes([web.put(\"/\", handler)])\n    assert len(router.routes()) == 1\n\n    route = list(router.routes())[0]\n    assert route.handler is handler\n    assert route.method == \"PUT\"\n    assert str(route.url_for()) == \"/\"\n\n\ndef test_patch(router: UrlDispatcher) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    router.add_routes([web.patch(\"/\", handler)])\n    assert len(router.routes()) == 1\n\n    route = list(router.routes())[0]\n    assert route.handler is handler\n    assert route.method == \"PATCH\"\n    assert str(route.url_for()) == \"/\"\n\n\ndef test_delete(router: UrlDispatcher) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    router.add_routes([web.delete(\"/\", handler)])\n    assert len(router.routes()) == 1\n\n    route = list(router.routes())[0]\n    assert route.handler is handler\n    assert route.method == \"DELETE\"\n    assert str(route.url_for()) == \"/\"\n\n\ndef test_route(router: UrlDispatcher) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    router.add_routes([web.route(\"OTHER\", \"/\", handler)])\n    assert len(router.routes()) == 1\n\n    route = list(router.routes())[0]\n    assert route.handler is handler\n    assert route.method == \"OTHER\"\n    assert str(route.url_for()) == \"/\"\n\n\ndef test_static(router: UrlDispatcher) -> None:\n    folder = pathlib.Path(__file__).parent\n    router.add_routes([web.static(\"/prefix\", folder)])\n    assert len(router.resources()) == 1  # 2 routes: for HEAD and GET\n\n    resource = list(router.resources())[0]\n    info = resource.get_info()\n    assert info[\"prefix\"] == \"/prefix\"\n    assert info[\"directory\"] == folder\n    url = resource.url_for(filename=\"aiohttp.png\")\n    assert url == URL(\"/prefix/aiohttp.png\")\n\n\ndef test_head_deco(router: UrlDispatcher) -> None:\n    routes = web.RouteTableDef()\n\n    @routes.head(\"/path\")\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    router.add_routes(routes)\n\n    assert len(router.routes()) == 1\n\n    route = list(router.routes())[0]\n    assert route.method == \"HEAD\"\n    assert str(route.url_for()) == \"/path\"\n\n\ndef test_get_deco(router: UrlDispatcher) -> None:\n    routes = web.RouteTableDef()\n\n    @routes.get(\"/path\")\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    router.add_routes(routes)\n\n    assert len(router.routes()) == 2\n\n    route1 = list(router.routes())[0]\n    assert route1.method == \"HEAD\"\n    assert str(route1.url_for()) == \"/path\"\n\n    route2 = list(router.routes())[1]\n    assert route2.method == \"GET\"\n    assert str(route2.url_for()) == \"/path\"\n\n\ndef test_post_deco(router: UrlDispatcher) -> None:\n    routes = web.RouteTableDef()\n\n    @routes.post(\"/path\")\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    router.add_routes(routes)\n\n    assert len(router.routes()) == 1\n\n    route = list(router.routes())[0]\n    assert route.method == \"POST\"\n    assert str(route.url_for()) == \"/path\"\n\n\ndef test_put_deco(router: UrlDispatcher) -> None:\n    routes = web.RouteTableDef()\n\n    @routes.put(\"/path\")\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    router.add_routes(routes)\n\n    assert len(router.routes()) == 1\n\n    route = list(router.routes())[0]\n    assert route.method == \"PUT\"\n    assert str(route.url_for()) == \"/path\"\n\n\ndef test_patch_deco(router: UrlDispatcher) -> None:\n    routes = web.RouteTableDef()\n\n    @routes.patch(\"/path\")\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    router.add_routes(routes)\n\n    assert len(router.routes()) == 1\n\n    route = list(router.routes())[0]\n    assert route.method == \"PATCH\"\n    assert str(route.url_for()) == \"/path\"\n\n\ndef test_delete_deco(router: UrlDispatcher) -> None:\n    routes = web.RouteTableDef()\n\n    @routes.delete(\"/path\")\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    router.add_routes(routes)\n\n    assert len(router.routes()) == 1\n\n    route = list(router.routes())[0]\n    assert route.method == \"DELETE\"\n    assert str(route.url_for()) == \"/path\"\n\n\ndef test_options_deco(router: UrlDispatcher) -> None:\n    routes = web.RouteTableDef()\n\n    @routes.options(\"/path\")\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    router.add_routes(routes)\n\n    assert len(router.routes()) == 1\n\n    route = list(router.routes())[0]\n    assert route.method == \"OPTIONS\"\n    assert str(route.url_for()) == \"/path\"\n\n\ndef test_route_deco(router: UrlDispatcher) -> None:\n    routes = web.RouteTableDef()\n\n    @routes.route(\"OTHER\", \"/path\")\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    router.add_routes(routes)\n\n    assert len(router.routes()) == 1\n\n    route = list(router.routes())[0]\n    assert route.method == \"OTHER\"\n    assert str(route.url_for()) == \"/path\"\n\n\ndef test_routedef_sequence_protocol() -> None:\n    routes = web.RouteTableDef()\n\n    @routes.delete(\"/path\")\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    assert len(routes) == 1\n\n    info = routes[0]\n    assert isinstance(info, web.RouteDef)\n    assert info in routes\n    assert list(routes)[0] is info\n\n\ndef test_repr_route_def() -> None:\n    routes = web.RouteTableDef()\n\n    @routes.get(\"/path\")\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    rd = routes[0]\n    assert repr(rd) == \"<RouteDef GET /path -> 'handler'>\"\n\n\ndef test_repr_route_def_with_extra_info() -> None:\n    routes = web.RouteTableDef()\n\n    @routes.get(\"/path\", extra=\"info\")\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    rd = routes[0]\n    assert repr(rd) == \"<RouteDef GET /path -> 'handler', extra='info'>\"\n\n\ndef test_repr_static_def() -> None:\n    routes = web.RouteTableDef()\n\n    routes.static(\"/prefix\", \"/path\", name=\"name\")\n\n    rd = routes[0]\n    assert repr(rd) == \"<StaticDef /prefix -> /path, name='name'>\"\n\n\ndef test_repr_route_table_def() -> None:\n    routes = web.RouteTableDef()\n\n    @routes.get(\"/path\")\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    assert repr(routes) == \"<RouteTableDef count=1>\"\n"
  },
  {
    "path": "tests/test_run_app.py",
    "content": "import asyncio\nimport contextlib\nimport logging\nimport os\nimport platform\nimport signal\nimport socket\nimport ssl\nimport subprocess\nimport sys\nimport time\nfrom collections.abc import AsyncIterator, Awaitable, Callable, Coroutine, Iterator\nfrom typing import Any, NoReturn\nfrom unittest import mock\nfrom uuid import uuid4\n\nimport pytest\nfrom pytest_mock import MockerFixture\n\nfrom aiohttp import ClientConnectorError, ClientSession, ClientTimeout, WSCloseCode, web\nfrom aiohttp.log import access_logger\nfrom aiohttp.web_protocol import RequestHandler\nfrom aiohttp.web_runner import BaseRunner\n\n_has_unix_domain_socks = hasattr(socket, \"AF_UNIX\")\nif _has_unix_domain_socks:\n    with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as _abstract_path_sock:\n        try:\n            _abstract_path_sock.bind(b\"\\x00\" + uuid4().hex.encode(\"ascii\"))\n        except FileNotFoundError:\n            _abstract_path_failed = True\n        else:\n            _abstract_path_failed = False\n        finally:\n            del _abstract_path_sock\nelse:\n    _abstract_path_failed = True\n\nskip_if_no_abstract_paths = pytest.mark.skipif(\n    _abstract_path_failed, reason=\"Linux-style abstract paths are not supported.\"\n)\nskip_if_no_unix_socks = pytest.mark.skipif(\n    not _has_unix_domain_socks, reason=\"Unix domain sockets are not supported\"\n)\ndel _has_unix_domain_socks, _abstract_path_failed\n\nHAS_IPV6: bool = socket.has_ipv6\nif HAS_IPV6:  # pragma: no branch\n    # The socket.has_ipv6 flag may be True if Python was built with IPv6\n    # support, but the target system still may not have it.\n    # So let's ensure that we really have IPv6 support.\n    try:\n        with socket.socket(socket.AF_INET6, socket.SOCK_STREAM):\n            pass\n    except OSError:  # pragma: no cover\n        HAS_IPV6 = False\n\n\ndef skip_if_on_windows() -> None:\n    if platform.system() == \"Windows\":\n        pytest.skip(\"the test is not valid for Windows\")\n\n\n@pytest.fixture\ndef patched_loop(\n    loop: asyncio.AbstractEventLoop,\n) -> Iterator[asyncio.AbstractEventLoop]:\n    server = mock.create_autospec(asyncio.Server, spec_set=True, instance=True)\n    server.wait_closed.return_value = None\n    server.sockets = []\n    unix_server = mock.create_autospec(asyncio.Server, spec_set=True, instance=True)\n    unix_server.wait_closed.return_value = None\n    unix_server.sockets = []\n    with mock.patch.object(\n        loop, \"create_server\", autospec=True, spec_set=True, return_value=server\n    ):\n        with mock.patch.object(\n            loop,\n            \"create_unix_server\",\n            autospec=True,\n            spec_set=True,\n            return_value=unix_server,\n        ):\n            asyncio.set_event_loop(loop)\n            yield loop\n\n\ndef stopper(loop: asyncio.AbstractEventLoop) -> Callable[[], None]:\n    def raiser() -> NoReturn:\n        raise KeyboardInterrupt\n\n    def f(*args: object) -> None:\n        loop.call_soon(raiser)\n\n    return f\n\n\ndef test_run_app_http(patched_loop: asyncio.AbstractEventLoop) -> None:\n    app = web.Application()\n    startup_handler = mock.AsyncMock()\n    app.on_startup.append(startup_handler)\n    cleanup_handler = mock.AsyncMock()\n    app.on_cleanup.append(cleanup_handler)\n\n    web.run_app(app, print=stopper(patched_loop), loop=patched_loop)\n\n    patched_loop.create_server.assert_called_with(  # type: ignore[attr-defined]\n        mock.ANY, None, 8080, ssl=None, backlog=128, reuse_address=None, reuse_port=None\n    )\n    startup_handler.assert_called_once_with(app)\n    cleanup_handler.assert_called_once_with(app)\n\n\ndef test_run_app_close_loop(patched_loop: asyncio.AbstractEventLoop) -> None:\n    app = web.Application()\n    web.run_app(app, print=stopper(patched_loop), loop=patched_loop)\n\n    patched_loop.create_server.assert_called_with(  # type: ignore[attr-defined]\n        mock.ANY, None, 8080, ssl=None, backlog=128, reuse_address=None, reuse_port=None\n    )\n    assert patched_loop.is_closed()\n\n\nmock_unix_server_single = [\n    mock.call(mock.ANY, \"/tmp/testsock1.sock\", ssl=None, backlog=128),\n]\nmock_unix_server_multi = [\n    mock.call(mock.ANY, \"/tmp/testsock1.sock\", ssl=None, backlog=128),\n    mock.call(mock.ANY, \"/tmp/testsock2.sock\", ssl=None, backlog=128),\n]\nmock_server_single = [\n    mock.call(\n        mock.ANY,\n        \"127.0.0.1\",\n        8080,\n        ssl=None,\n        backlog=128,\n        reuse_address=None,\n        reuse_port=None,\n    ),\n]\nmock_server_multi = [\n    mock.call(\n        mock.ANY,\n        \"127.0.0.1\",\n        8080,\n        ssl=None,\n        backlog=128,\n        reuse_address=None,\n        reuse_port=None,\n    ),\n    mock.call(\n        mock.ANY,\n        \"192.168.1.1\",\n        8080,\n        ssl=None,\n        backlog=128,\n        reuse_address=None,\n        reuse_port=None,\n    ),\n]\nmock_server_default_8989 = [\n    mock.call(\n        mock.ANY, None, 8989, ssl=None, backlog=128, reuse_address=None, reuse_port=None\n    )\n]\nmock_socket = mock.Mock(getsockname=lambda: (\"mock-socket\", 123))\nmixed_bindings_tests: tuple[\n    tuple[str, dict[str, Any], list[mock._Call], list[mock._Call]], ...\n] = (\n    (\n        \"Nothing Specified\",\n        {},\n        [\n            mock.call(\n                mock.ANY,\n                None,\n                8080,\n                ssl=None,\n                backlog=128,\n                reuse_address=None,\n                reuse_port=None,\n            )\n        ],\n        [],\n    ),\n    (\"Port Only\", {\"port\": 8989}, mock_server_default_8989, []),\n    (\"Multiple Hosts\", {\"host\": (\"127.0.0.1\", \"192.168.1.1\")}, mock_server_multi, []),\n    (\n        \"Multiple Paths\",\n        {\"path\": (\"/tmp/testsock1.sock\", \"/tmp/testsock2.sock\")},\n        [],\n        mock_unix_server_multi,\n    ),\n    (\n        \"Multiple Paths, Port\",\n        {\"path\": (\"/tmp/testsock1.sock\", \"/tmp/testsock2.sock\"), \"port\": 8989},\n        mock_server_default_8989,\n        mock_unix_server_multi,\n    ),\n    (\n        \"Multiple Paths, Single Host\",\n        {\"path\": (\"/tmp/testsock1.sock\", \"/tmp/testsock2.sock\"), \"host\": \"127.0.0.1\"},\n        mock_server_single,\n        mock_unix_server_multi,\n    ),\n    (\n        \"Single Path, Single Host\",\n        {\"path\": \"/tmp/testsock1.sock\", \"host\": \"127.0.0.1\"},\n        mock_server_single,\n        mock_unix_server_single,\n    ),\n    (\n        \"Single Path, Multiple Hosts\",\n        {\"path\": \"/tmp/testsock1.sock\", \"host\": (\"127.0.0.1\", \"192.168.1.1\")},\n        mock_server_multi,\n        mock_unix_server_single,\n    ),\n    (\n        \"Single Path, Port\",\n        {\"path\": \"/tmp/testsock1.sock\", \"port\": 8989},\n        mock_server_default_8989,\n        mock_unix_server_single,\n    ),\n    (\n        \"Multiple Paths, Multiple Hosts, Port\",\n        {\n            \"path\": (\"/tmp/testsock1.sock\", \"/tmp/testsock2.sock\"),\n            \"host\": (\"127.0.0.1\", \"192.168.1.1\"),\n            \"port\": 8000,\n        },\n        [\n            mock.call(\n                mock.ANY,\n                \"127.0.0.1\",\n                8000,\n                ssl=None,\n                backlog=128,\n                reuse_address=None,\n                reuse_port=None,\n            ),\n            mock.call(\n                mock.ANY,\n                \"192.168.1.1\",\n                8000,\n                ssl=None,\n                backlog=128,\n                reuse_address=None,\n                reuse_port=None,\n            ),\n        ],\n        mock_unix_server_multi,\n    ),\n    (\n        \"Only socket\",\n        {\"sock\": [mock_socket]},\n        [mock.call(mock.ANY, ssl=None, sock=mock_socket, backlog=128)],\n        [],\n    ),\n    (\n        \"Socket, port\",\n        {\"sock\": [mock_socket], \"port\": 8765},\n        [\n            mock.call(\n                mock.ANY,\n                None,\n                8765,\n                ssl=None,\n                backlog=128,\n                reuse_address=None,\n                reuse_port=None,\n            ),\n            mock.call(mock.ANY, sock=mock_socket, ssl=None, backlog=128),\n        ],\n        [],\n    ),\n    (\n        \"Socket, Host, No port\",\n        {\"sock\": [mock_socket], \"host\": \"localhost\"},\n        [\n            mock.call(\n                mock.ANY,\n                \"localhost\",\n                8080,\n                ssl=None,\n                backlog=128,\n                reuse_address=None,\n                reuse_port=None,\n            ),\n            mock.call(mock.ANY, sock=mock_socket, ssl=None, backlog=128),\n        ],\n        [],\n    ),\n    (\n        \"reuse_port\",\n        {\"reuse_port\": True},\n        [\n            mock.call(\n                mock.ANY,\n                None,\n                8080,\n                ssl=None,\n                backlog=128,\n                reuse_address=None,\n                reuse_port=True,\n            )\n        ],\n        [],\n    ),\n    (\n        \"reuse_address\",\n        {\"reuse_address\": False},\n        [\n            mock.call(\n                mock.ANY,\n                None,\n                8080,\n                ssl=None,\n                backlog=128,\n                reuse_address=False,\n                reuse_port=None,\n            )\n        ],\n        [],\n    ),\n    (\n        \"reuse_port, reuse_address\",\n        {\"reuse_address\": True, \"reuse_port\": True},\n        [\n            mock.call(\n                mock.ANY,\n                None,\n                8080,\n                ssl=None,\n                backlog=128,\n                reuse_address=True,\n                reuse_port=True,\n            )\n        ],\n        [],\n    ),\n    (\n        \"Port, reuse_port\",\n        {\"port\": 8989, \"reuse_port\": True},\n        [\n            mock.call(\n                mock.ANY,\n                None,\n                8989,\n                ssl=None,\n                backlog=128,\n                reuse_address=None,\n                reuse_port=True,\n            )\n        ],\n        [],\n    ),\n    (\n        \"Multiple Hosts, reuse_port\",\n        {\"host\": (\"127.0.0.1\", \"192.168.1.1\"), \"reuse_port\": True},\n        [\n            mock.call(\n                mock.ANY,\n                \"127.0.0.1\",\n                8080,\n                ssl=None,\n                backlog=128,\n                reuse_address=None,\n                reuse_port=True,\n            ),\n            mock.call(\n                mock.ANY,\n                \"192.168.1.1\",\n                8080,\n                ssl=None,\n                backlog=128,\n                reuse_address=None,\n                reuse_port=True,\n            ),\n        ],\n        [],\n    ),\n    (\n        \"Multiple Paths, Port, reuse_address\",\n        {\n            \"path\": (\"/tmp/testsock1.sock\", \"/tmp/testsock2.sock\"),\n            \"port\": 8989,\n            \"reuse_address\": False,\n        },\n        [\n            mock.call(\n                mock.ANY,\n                None,\n                8989,\n                ssl=None,\n                backlog=128,\n                reuse_address=False,\n                reuse_port=None,\n            )\n        ],\n        mock_unix_server_multi,\n    ),\n    (\n        \"Multiple Paths, Single Host, reuse_address, reuse_port\",\n        {\n            \"path\": (\"/tmp/testsock1.sock\", \"/tmp/testsock2.sock\"),\n            \"host\": \"127.0.0.1\",\n            \"reuse_address\": True,\n            \"reuse_port\": True,\n        },\n        [\n            mock.call(\n                mock.ANY,\n                \"127.0.0.1\",\n                8080,\n                ssl=None,\n                backlog=128,\n                reuse_address=True,\n                reuse_port=True,\n            ),\n        ],\n        mock_unix_server_multi,\n    ),\n)\nmixed_bindings_test_ids = [test[0] for test in mixed_bindings_tests]\nmixed_bindings_test_params = [test[1:] for test in mixed_bindings_tests]\n\n\n@pytest.mark.parametrize(\n    \"run_app_kwargs, expected_server_calls, expected_unix_server_calls\",\n    mixed_bindings_test_params,\n    ids=mixed_bindings_test_ids,\n)\ndef test_run_app_mixed_bindings(  # type: ignore[misc]\n    run_app_kwargs: dict[str, Any],\n    expected_server_calls: list[mock._Call],\n    expected_unix_server_calls: list[mock._Call],\n    patched_loop: asyncio.AbstractEventLoop,\n) -> None:\n    app = web.Application()\n    web.run_app(app, print=stopper(patched_loop), **run_app_kwargs, loop=patched_loop)\n\n    assert patched_loop.create_unix_server.mock_calls == expected_unix_server_calls  # type: ignore[attr-defined]\n    assert patched_loop.create_server.mock_calls == expected_server_calls  # type: ignore[attr-defined]\n\n\ndef test_run_app_https(patched_loop: asyncio.AbstractEventLoop) -> None:\n    app = web.Application()\n\n    ssl_context = ssl.create_default_context()\n    web.run_app(\n        app, ssl_context=ssl_context, print=stopper(patched_loop), loop=patched_loop\n    )\n\n    patched_loop.create_server.assert_called_with(  # type: ignore[attr-defined]\n        mock.ANY,\n        None,\n        8443,\n        ssl=ssl_context,\n        backlog=128,\n        reuse_address=None,\n        reuse_port=None,\n    )\n\n\ndef test_run_app_nondefault_host_port(\n    patched_loop: asyncio.AbstractEventLoop, unused_port_socket: socket.socket\n) -> None:\n    port = unused_port_socket.getsockname()[1]\n    host = \"127.0.0.1\"\n\n    app = web.Application()\n    web.run_app(\n        app, host=host, port=port, print=stopper(patched_loop), loop=patched_loop\n    )\n\n    patched_loop.create_server.assert_called_with(  # type: ignore[attr-defined]\n        mock.ANY, host, port, ssl=None, backlog=128, reuse_address=None, reuse_port=None\n    )\n\n\ndef test_run_app_with_sock(\n    patched_loop: asyncio.AbstractEventLoop, unused_port_socket: socket.socket\n) -> None:\n    sock = unused_port_socket\n    app = web.Application()\n    web.run_app(\n        app,\n        sock=sock,\n        print=stopper(patched_loop),\n        loop=patched_loop,\n    )\n\n    patched_loop.create_server.assert_called_with(  # type: ignore[attr-defined]\n        mock.ANY, sock=sock, ssl=None, backlog=128\n    )\n\n\ndef test_run_app_multiple_hosts(patched_loop: asyncio.AbstractEventLoop) -> None:\n    hosts = (\"127.0.0.1\", \"127.0.0.2\")\n\n    app = web.Application()\n    web.run_app(app, host=hosts, print=stopper(patched_loop), loop=patched_loop)\n\n    calls = map(\n        lambda h: mock.call(\n            mock.ANY,\n            h,\n            8080,\n            ssl=None,\n            backlog=128,\n            reuse_address=None,\n            reuse_port=None,\n        ),\n        hosts,\n    )\n    patched_loop.create_server.assert_has_calls(calls)  # type: ignore[attr-defined]\n\n\ndef test_run_app_custom_backlog(patched_loop: asyncio.AbstractEventLoop) -> None:\n    app = web.Application()\n    web.run_app(app, backlog=10, print=stopper(patched_loop), loop=patched_loop)\n\n    patched_loop.create_server.assert_called_with(  # type: ignore[attr-defined]\n        mock.ANY, None, 8080, ssl=None, backlog=10, reuse_address=None, reuse_port=None\n    )\n\n\ndef test_run_app_custom_backlog_unix(patched_loop: asyncio.AbstractEventLoop) -> None:\n    app = web.Application()\n    web.run_app(\n        app,\n        path=\"/tmp/tmpsock.sock\",\n        backlog=10,\n        print=stopper(patched_loop),\n        loop=patched_loop,\n    )\n\n    patched_loop.create_unix_server.assert_called_with(  # type: ignore[attr-defined]\n        mock.ANY, \"/tmp/tmpsock.sock\", ssl=None, backlog=10\n    )\n\n\n@skip_if_no_unix_socks\ndef test_run_app_http_unix_socket(\n    patched_loop: asyncio.AbstractEventLoop, unix_sockname: str\n) -> None:\n    app = web.Application()\n\n    printer = mock.Mock(wraps=stopper(patched_loop))\n    web.run_app(app, path=unix_sockname, print=printer, loop=patched_loop)\n\n    patched_loop.create_unix_server.assert_called_with(  # type: ignore[attr-defined]\n        mock.ANY, unix_sockname, ssl=None, backlog=128\n    )\n    assert f\"http://unix:{unix_sockname}:\" in printer.call_args[0][0]\n\n\n@skip_if_no_unix_socks\ndef test_run_app_https_unix_socket(\n    patched_loop: asyncio.AbstractEventLoop, unix_sockname: str\n) -> None:\n    app = web.Application()\n\n    ssl_context = ssl.create_default_context()\n    printer = mock.Mock(wraps=stopper(patched_loop))\n    web.run_app(\n        app,\n        path=unix_sockname,\n        ssl_context=ssl_context,\n        print=printer,\n        loop=patched_loop,\n    )\n\n    patched_loop.create_unix_server.assert_called_with(  # type: ignore[attr-defined]\n        mock.ANY, unix_sockname, ssl=ssl_context, backlog=128\n    )\n    assert f\"https://unix:{unix_sockname}:\" in printer.call_args[0][0]\n\n\n@pytest.mark.skipif(not hasattr(socket, \"AF_UNIX\"), reason=\"requires UNIX sockets\")\n@skip_if_no_abstract_paths\ndef test_run_app_abstract_linux_socket(patched_loop: asyncio.AbstractEventLoop) -> None:\n    sock_path = b\"\\x00\" + uuid4().hex.encode(\"ascii\")\n    app = web.Application()\n    web.run_app(\n        app,\n        path=sock_path.decode(\"ascii\", \"ignore\"),\n        print=stopper(patched_loop),\n        loop=patched_loop,\n    )\n\n    patched_loop.create_unix_server.assert_called_with(  # type: ignore[attr-defined]\n        mock.ANY, sock_path.decode(\"ascii\"), ssl=None, backlog=128\n    )\n\n\ndef test_run_app_preexisting_inet_socket(\n    patched_loop: asyncio.AbstractEventLoop, mocker: MockerFixture\n) -> None:\n    app = web.Application()\n\n    sock = socket.socket()\n    with contextlib.closing(sock):\n        sock.bind((\"127.0.0.1\", 0))\n        _, port = sock.getsockname()\n\n        printer = mock.Mock(wraps=stopper(patched_loop))\n        web.run_app(app, sock=sock, print=printer, loop=patched_loop)\n\n        patched_loop.create_server.assert_called_with(  # type: ignore[attr-defined]\n            mock.ANY, sock=sock, backlog=128, ssl=None\n        )\n        assert f\"http://127.0.0.1:{port}\" in printer.call_args[0][0]\n\n\n@pytest.mark.skipif(not HAS_IPV6, reason=\"IPv6 is not available\")\ndef test_run_app_preexisting_inet6_socket(\n    patched_loop: asyncio.AbstractEventLoop,\n) -> None:\n    app = web.Application()\n\n    sock = socket.socket(socket.AF_INET6)\n    with contextlib.closing(sock):\n        sock.bind((\"::1\", 0))\n        port = sock.getsockname()[1]\n\n        printer = mock.Mock(wraps=stopper(patched_loop))\n        web.run_app(app, sock=sock, print=printer, loop=patched_loop)\n\n        patched_loop.create_server.assert_called_with(  # type: ignore[attr-defined]\n            mock.ANY, sock=sock, backlog=128, ssl=None\n        )\n        assert f\"http://[::1]:{port}\" in printer.call_args[0][0]\n\n\n@skip_if_no_unix_socks\ndef test_run_app_preexisting_unix_socket(\n    patched_loop: asyncio.AbstractEventLoop, unix_sockname: str, mocker: MockerFixture\n) -> None:\n    app = web.Application()\n\n    sock = socket.socket(socket.AF_UNIX)\n    with contextlib.closing(sock):\n        sock.bind(unix_sockname)\n        os.unlink(unix_sockname)\n\n        printer = mock.Mock(wraps=stopper(patched_loop))\n        web.run_app(app, sock=sock, print=printer, loop=patched_loop)\n\n        patched_loop.create_server.assert_called_with(  # type: ignore[attr-defined]\n            mock.ANY, sock=sock, backlog=128, ssl=None\n        )\n        assert f\"http://unix:{unix_sockname}:\" in printer.call_args[0][0]\n\n\ndef test_run_app_multiple_preexisting_sockets(\n    patched_loop: asyncio.AbstractEventLoop,\n) -> None:\n    app = web.Application()\n\n    sock1 = socket.socket()\n    sock2 = socket.socket()\n    with contextlib.closing(sock1), contextlib.closing(sock2):\n        sock1.bind((\"localhost\", 0))\n        _, port1 = sock1.getsockname()\n        sock2.bind((\"localhost\", 0))\n        _, port2 = sock2.getsockname()\n\n        printer = mock.Mock(wraps=stopper(patched_loop))\n        web.run_app(app, sock=(sock1, sock2), print=printer, loop=patched_loop)\n\n        patched_loop.create_server.assert_has_calls(  # type: ignore[attr-defined]\n            [\n                mock.call(mock.ANY, sock=sock1, backlog=128, ssl=None),\n                mock.call(mock.ANY, sock=sock2, backlog=128, ssl=None),\n            ]\n        )\n        assert f\"http://127.0.0.1:{port1}\" in printer.call_args[0][0]\n        assert f\"http://127.0.0.1:{port2}\" in printer.call_args[0][0]\n\n\n_script_test_signal = \"\"\"\nfrom aiohttp import web\n\napp = web.Application()\nweb.run_app(app, host=())\n\"\"\"\n\n\ndef test_sigint() -> None:\n    skip_if_on_windows()\n\n    with subprocess.Popen(\n        (sys.executable, \"-u\", \"-c\", _script_test_signal),\n        stdout=subprocess.PIPE,\n    ) as proc:\n        assert proc.stdout.readline().startswith(b\"======== Running on\")  # type: ignore[union-attr]\n        proc.send_signal(signal.SIGINT)\n        assert proc.wait() == 0\n\n\ndef test_sigterm() -> None:\n    skip_if_on_windows()\n\n    with subprocess.Popen(\n        (sys.executable, \"-u\", \"-c\", _script_test_signal),\n        stdout=subprocess.PIPE,\n    ) as proc:\n        assert proc.stdout.readline().startswith(b\"======== Running on\")  # type: ignore[union-attr]\n        proc.terminate()\n        assert proc.wait() == 0\n\n\ndef test_startup_cleanup_signals_even_on_failure(\n    patched_loop: asyncio.AbstractEventLoop,\n) -> None:\n    patched_loop.create_server.side_effect = RuntimeError()  # type: ignore[attr-defined]\n\n    app = web.Application()\n    startup_handler = mock.AsyncMock()\n    app.on_startup.append(startup_handler)\n    cleanup_handler = mock.AsyncMock()\n    app.on_cleanup.append(cleanup_handler)\n\n    with pytest.raises(RuntimeError):\n        web.run_app(app, print=stopper(patched_loop), loop=patched_loop)\n\n    startup_handler.assert_called_once_with(app)\n    cleanup_handler.assert_called_once_with(app)\n\n\ndef test_run_app_coro(patched_loop: asyncio.AbstractEventLoop) -> None:\n    startup_handler = cleanup_handler = None\n\n    async def make_app() -> web.Application:\n        nonlocal startup_handler, cleanup_handler\n        app = web.Application()\n        startup_handler = mock.AsyncMock()\n        app.on_startup.append(startup_handler)\n        cleanup_handler = mock.AsyncMock()\n        app.on_cleanup.append(cleanup_handler)\n        return app\n\n    web.run_app(make_app(), print=stopper(patched_loop), loop=patched_loop)\n\n    patched_loop.create_server.assert_called_with(  # type: ignore[attr-defined]\n        mock.ANY, None, 8080, ssl=None, backlog=128, reuse_address=None, reuse_port=None\n    )\n    assert startup_handler is not None\n    assert cleanup_handler is not None\n    startup_handler.assert_called_once_with(mock.ANY)\n    cleanup_handler.assert_called_once_with(mock.ANY)\n\n\ndef test_run_app_default_logger(\n    monkeypatch: pytest.MonkeyPatch, patched_loop: asyncio.AbstractEventLoop\n) -> None:\n    logger = access_logger\n    attrs = {\n        \"hasHandlers.return_value\": False,\n        \"level\": logging.NOTSET,\n        \"name\": \"aiohttp.access\",\n    }\n    mock_logger = mock.create_autospec(logger, name=\"mock_access_logger\")\n    mock_logger.configure_mock(**attrs)\n\n    app = web.Application()\n    web.run_app(\n        app,\n        debug=True,\n        print=stopper(patched_loop),\n        access_log=mock_logger,\n        loop=patched_loop,\n    )\n    mock_logger.setLevel.assert_any_call(logging.DEBUG)\n    mock_logger.hasHandlers.assert_called_with()\n    assert isinstance(mock_logger.addHandler.call_args[0][0], logging.StreamHandler)\n\n\ndef test_run_app_default_logger_setup_requires_debug(\n    patched_loop: asyncio.AbstractEventLoop,\n) -> None:\n    logger = access_logger\n    attrs = {\n        \"hasHandlers.return_value\": False,\n        \"level\": logging.NOTSET,\n        \"name\": \"aiohttp.access\",\n    }\n    mock_logger = mock.create_autospec(logger, name=\"mock_access_logger\")\n    mock_logger.configure_mock(**attrs)\n\n    app = web.Application()\n    web.run_app(\n        app,\n        debug=False,\n        print=stopper(patched_loop),\n        access_log=mock_logger,\n        loop=patched_loop,\n    )\n    mock_logger.setLevel.assert_not_called()\n    mock_logger.hasHandlers.assert_not_called()\n    mock_logger.addHandler.assert_not_called()\n\n\ndef test_run_app_default_logger_setup_requires_default_logger(\n    patched_loop: asyncio.AbstractEventLoop,\n) -> None:\n    logger = access_logger\n    attrs = {\n        \"hasHandlers.return_value\": False,\n        \"level\": logging.NOTSET,\n        \"name\": None,\n    }\n    mock_logger = mock.create_autospec(logger, name=\"mock_access_logger\")\n    mock_logger.configure_mock(**attrs)\n\n    app = web.Application()\n    web.run_app(\n        app,\n        debug=True,\n        print=stopper(patched_loop),\n        access_log=mock_logger,\n        loop=patched_loop,\n    )\n    mock_logger.setLevel.assert_not_called()\n    mock_logger.hasHandlers.assert_not_called()\n    mock_logger.addHandler.assert_not_called()\n\n\ndef test_run_app_default_logger_setup_only_if_unconfigured(\n    patched_loop: asyncio.AbstractEventLoop,\n) -> None:\n    logger = access_logger\n    attrs = {\n        \"hasHandlers.return_value\": True,\n        \"level\": None,\n        \"name\": \"aiohttp.access\",\n    }\n    mock_logger = mock.create_autospec(logger, name=\"mock_access_logger\")\n    mock_logger.configure_mock(**attrs)\n\n    app = web.Application()\n    web.run_app(\n        app,\n        debug=True,\n        print=stopper(patched_loop),\n        access_log=mock_logger,\n        loop=patched_loop,\n    )\n    mock_logger.setLevel.assert_not_called()\n    mock_logger.hasHandlers.assert_called_with()\n    mock_logger.addHandler.assert_not_called()\n\n\ndef test_run_app_cancels_all_pending_tasks(\n    patched_loop: asyncio.AbstractEventLoop,\n) -> None:\n    app = web.Application()\n    task = None\n\n    async def on_startup(app: web.Application) -> None:\n        nonlocal task\n        loop = asyncio.get_event_loop()\n        task = loop.create_task(asyncio.sleep(1000))\n\n    app.on_startup.append(on_startup)\n\n    web.run_app(app, print=stopper(patched_loop), loop=patched_loop)\n    assert task is not None\n    assert task.cancelled()\n\n\ndef test_run_app_cancels_done_tasks(patched_loop: asyncio.AbstractEventLoop) -> None:\n    app = web.Application()\n    task = None\n\n    async def coro() -> int:\n        return 123\n\n    async def on_startup(app: web.Application) -> None:\n        nonlocal task\n        loop = asyncio.get_event_loop()\n        task = loop.create_task(coro())\n\n    app.on_startup.append(on_startup)\n\n    web.run_app(app, print=stopper(patched_loop), loop=patched_loop)\n    assert task is not None\n    assert task.done()\n\n\ndef test_run_app_cancels_failed_tasks(patched_loop: asyncio.AbstractEventLoop) -> None:\n    app = web.Application()\n    task = None\n\n    exc = RuntimeError(\"FAIL\")\n\n    async def fail() -> None:\n        try:\n            await asyncio.sleep(1000)\n        except asyncio.CancelledError:\n            raise exc\n\n    async def on_startup(app: web.Application) -> None:\n        nonlocal task\n        loop = asyncio.get_event_loop()\n        task = loop.create_task(fail())\n        await asyncio.sleep(0.01)\n\n    app.on_startup.append(on_startup)\n\n    exc_handler = mock.Mock()\n    patched_loop.set_exception_handler(exc_handler)\n    web.run_app(app, print=stopper(patched_loop), loop=patched_loop)\n    assert task is not None\n    assert task.done()\n\n    msg = {\n        \"message\": \"unhandled exception during asyncio.run() shutdown\",\n        \"exception\": exc,\n        \"task\": task,\n    }\n    exc_handler.assert_called_with(patched_loop, msg)\n\n\n@pytest.mark.parametrize(\n    \"param\",\n    (\n        \"keepalive_timeout\",\n        \"max_line_size\",\n        \"max_headers\",\n        \"max_field_size\",\n        \"lingering_time\",\n        \"read_bufsize\",\n        \"auto_decompress\",\n    ),\n)\ndef test_run_app_pass_apprunner_kwargs(\n    param: str,\n    patched_loop: asyncio.AbstractEventLoop,\n    monkeypatch: pytest.MonkeyPatch,\n) -> None:\n    m = mock.Mock()\n    base_runner_init_orig = BaseRunner.__init__\n\n    def base_runner_init_spy(\n        self: BaseRunner[web.Request], *args: Any, **kwargs: Any\n    ) -> None:\n        assert kwargs[param] is m\n        base_runner_init_orig(self, *args, **kwargs)\n\n    app = web.Application()\n    monkeypatch.setattr(BaseRunner, \"__init__\", base_runner_init_spy)\n    web.run_app(app, print=stopper(patched_loop), loop=patched_loop, **{param: m})\n\n\ndef test_run_app_context_vars(patched_loop: asyncio.AbstractEventLoop) -> None:\n    from contextvars import ContextVar\n\n    count = 0\n    VAR = ContextVar(\"VAR\", default=\"default\")\n\n    async def on_startup(app: web.Application) -> None:\n        nonlocal count\n        assert \"init\" == VAR.get()\n        VAR.set(\"on_startup\")\n        count += 1\n\n    async def on_cleanup(app: web.Application) -> None:\n        nonlocal count\n        assert \"on_startup\" == VAR.get()\n        count += 1\n\n    async def init() -> web.Application:\n        nonlocal count\n        assert \"default\" == VAR.get()\n        VAR.set(\"init\")\n        app = web.Application()\n\n        app.on_startup.append(on_startup)\n        app.on_cleanup.append(on_cleanup)\n        count += 1\n        return app\n\n    web.run_app(init(), print=stopper(patched_loop), loop=patched_loop)\n    assert count == 3\n\n\ndef test_run_app_raises_exception(patched_loop: asyncio.AbstractEventLoop) -> None:\n    async def context(app: web.Application) -> AsyncIterator[None]:\n        raise RuntimeError(\"foo\")\n        yield  # type: ignore[unreachable]  # pragma: no cover\n\n    app = web.Application()\n    app.cleanup_ctx.append(context)\n\n    with mock.patch.object(\n        patched_loop, \"call_exception_handler\", autospec=True, spec_set=True\n    ) as m:\n        with pytest.raises(RuntimeError, match=\"foo\"):\n            web.run_app(app, loop=patched_loop)\n\n    assert not m.called\n\n\nclass TestShutdown:\n    def raiser(self) -> NoReturn:\n        raise KeyboardInterrupt\n\n    async def stop(self, request: web.Request) -> web.Response:\n        asyncio.get_running_loop().call_soon(self.raiser)\n        return web.Response()\n\n    def run_app(\n        self,\n        sock: socket.socket,\n        timeout: int,\n        task: Callable[[], Coroutine[None, None, None]],\n        extra_test: Callable[[ClientSession], Awaitable[None]] | None = None,\n    ) -> tuple[\"asyncio.Task[None]\", int]:\n        num_connections = -1\n        t = test_task = None\n        port = sock.getsockname()[1]\n\n        class DictRecordClear(dict[RequestHandler[web.Request], asyncio.Transport]):\n            def clear(self) -> None:\n                nonlocal num_connections\n                # During Server.shutdown() we want to know how many connections still\n                # remained before it got cleared. If the handler completed successfully\n                # the connection should've been removed already. If not, this may\n                # indicate a memory leak.\n                num_connections = len(self)\n                super().clear()\n\n        class ServerWithRecordClear(web.Server[web.Request]):\n            def __init__(self, *args: Any, **kwargs: Any):\n                super().__init__(*args, **kwargs)\n                self._connections = DictRecordClear()\n\n        async def test() -> None:\n            await asyncio.sleep(0.5)\n            async with ClientSession() as sess:\n                for _ in range(5):  # Retry for flaky tests  # pragma: no cover\n                    try:\n                        with pytest.raises(asyncio.TimeoutError):\n                            async with sess.get(\n                                f\"http://127.0.0.1:{port}/\",\n                                timeout=ClientTimeout(total=0.1),\n                            ):\n                                pass\n                    except ClientConnectorError:\n                        await asyncio.sleep(0.5)\n                    else:\n                        break\n                async with sess.get(f\"http://127.0.0.1:{port}/stop\"):\n                    pass\n\n                if extra_test:\n                    await extra_test(sess)\n\n        async def run_test(app: web.Application) -> AsyncIterator[None]:\n            nonlocal test_task\n            test_task = asyncio.create_task(test())\n            yield\n            await test_task\n\n        async def handler(request: web.Request) -> web.Response:\n            nonlocal t\n            t = asyncio.create_task(task())\n            await t\n            return web.Response(text=\"FOO\")\n\n        app = web.Application()\n        app.cleanup_ctx.append(run_test)\n        app.router.add_get(\"/\", handler)\n        app.router.add_get(\"/stop\", self.stop)\n\n        with mock.patch(\"aiohttp.web_runner.Server\", ServerWithRecordClear):\n            web.run_app(app, sock=sock, shutdown_timeout=timeout)\n        assert test_task is not None\n        assert test_task.exception() is None\n        assert t is not None\n        return t, num_connections\n\n    def test_shutdown_wait_for_handler(self, unused_port_socket: socket.socket) -> None:\n        sock = unused_port_socket\n        finished = False\n\n        async def task() -> None:\n            nonlocal finished\n            await asyncio.sleep(2)\n            finished = True\n\n        t, connection_count = self.run_app(sock, 3, task)\n\n        assert finished is True\n        assert t.done()\n        assert not t.cancelled()\n        assert connection_count == 0\n\n    def test_shutdown_timeout_handler(self, unused_port_socket: socket.socket) -> None:\n        sock = unused_port_socket\n        finished = False\n\n        async def task() -> None:\n            nonlocal finished\n            await asyncio.sleep(2)\n            finished = True  # pragma: no cover\n\n        t, connection_count = self.run_app(sock, 1, task)\n\n        assert finished is False\n        assert t.done()\n        assert t.cancelled()\n        assert connection_count == 1\n\n    def test_shutdown_timeout_not_reached(\n        self, unused_port_socket: socket.socket\n    ) -> None:\n        sock = unused_port_socket\n        finished = False\n\n        async def task() -> None:\n            nonlocal finished\n            await asyncio.sleep(1)\n            finished = True\n\n        start_time = time.time()\n\n        t, connection_count = self.run_app(sock, 15, task)\n\n        assert finished is True\n        assert t.done()\n        assert connection_count == 0\n        # Verify run_app has not waited for timeout.\n        assert time.time() - start_time < 10\n\n    def test_shutdown_new_conn_rejected(\n        self, unused_port_socket: socket.socket\n    ) -> None:\n        sock = unused_port_socket\n        port = sock.getsockname()[1]\n        finished = False\n\n        async def task() -> None:\n            nonlocal finished\n            await asyncio.sleep(9)\n            finished = True\n\n        async def test(sess: ClientSession) -> None:\n            # Ensure we are in the middle of shutdown (waiting for task()).\n            await asyncio.sleep(1)\n            with pytest.raises(ClientConnectorError):\n                # Use a new session to try and open a new connection.\n                async with ClientSession() as sess:\n                    async with sess.get(f\"http://127.0.0.1:{port}/\"):\n                        assert False  # Should fail before here\n            assert finished is False\n\n        t, connection_count = self.run_app(sock, 10, task, test)\n\n        assert finished is True\n        assert t.done()\n        assert connection_count == 0\n\n    def test_shutdown_pending_handler_responds(\n        self, unused_port_socket: socket.socket\n    ) -> None:\n        sock = unused_port_socket\n        port = sock.getsockname()[1]\n        finished = False\n        t = None\n\n        async def test() -> None:\n            async def test_resp(sess: ClientSession) -> None:\n                async with sess.get(f\"http://127.0.0.1:{port}/\") as resp:\n                    assert await resp.text() == \"FOO\"\n\n            await asyncio.sleep(1)\n            async with ClientSession() as sess:\n                t = asyncio.create_task(test_resp(sess))\n                await asyncio.sleep(1)\n                # Handler is in-progress while we trigger server shutdown.\n                async with sess.get(f\"http://127.0.0.1:{port}/stop\"):\n                    pass\n\n                assert finished is False\n                # Handler should still complete and produce a response.\n                await t\n\n        async def run_test(app: web.Application) -> AsyncIterator[None]:\n            nonlocal t\n            t = asyncio.create_task(test())\n            yield\n            await t\n\n        async def handler(request: web.Request) -> web.Response:\n            nonlocal finished\n            await asyncio.sleep(3)\n            finished = True\n            return web.Response(text=\"FOO\")\n\n        app = web.Application()\n        app.cleanup_ctx.append(run_test)\n        app.router.add_get(\"/\", handler)\n        app.router.add_get(\"/stop\", self.stop)\n\n        web.run_app(app, sock=sock, shutdown_timeout=5)\n        assert t is not None\n        assert t.exception() is None\n        assert finished is True\n\n    def test_shutdown_close_idle_keepalive(\n        self, unused_port_socket: socket.socket\n    ) -> None:\n        sock = unused_port_socket\n        port = sock.getsockname()[1]\n        t = None\n\n        async def test() -> None:\n            await asyncio.sleep(1)\n            async with ClientSession() as sess:\n                async with sess.get(f\"http://127.0.0.1:{port}/stop\"):\n                    pass\n\n                # Hold on to keep-alive connection.\n                await asyncio.sleep(5)\n\n        async def run_test(app: web.Application) -> AsyncIterator[None]:\n            nonlocal t\n            t = asyncio.create_task(test())\n            yield\n            t.cancel()\n            with contextlib.suppress(asyncio.CancelledError):\n                await t\n\n        app = web.Application()\n        app.cleanup_ctx.append(run_test)\n        app.router.add_get(\"/stop\", self.stop)\n\n        web.run_app(app, sock=sock, shutdown_timeout=10)\n        # If connection closed, then test() will be cancelled in cleanup_ctx.\n        # If not, then shutdown_timeout will allow it to sleep until complete.\n        assert t is not None\n        assert t.cancelled()\n\n    def test_shutdown_close_websockets(self, unused_port_socket: socket.socket) -> None:\n        sock = unused_port_socket\n        port = sock.getsockname()[1]\n        WS = web.AppKey(\"ws\", set[web.WebSocketResponse])\n        client_finished = server_finished = False\n        t = None\n\n        async def ws_handler(request: web.Request) -> web.WebSocketResponse:\n            ws = web.WebSocketResponse()\n            await ws.prepare(request)\n            request.app[WS].add(ws)\n            async for msg in ws:\n                assert False  # No messages actually sent\n            nonlocal server_finished\n            server_finished = True\n            return ws\n\n        async def close_websockets(app: web.Application) -> None:\n            for ws in app[WS]:\n                await ws.close(code=WSCloseCode.GOING_AWAY)\n\n        async def test() -> None:\n            await asyncio.sleep(1)\n            async with ClientSession() as sess:\n                async with sess.ws_connect(f\"http://127.0.0.1:{port}/ws\") as ws:\n                    async with sess.get(f\"http://127.0.0.1:{port}/stop\"):\n                        pass\n\n                    async for msg in ws:\n                        assert False  # No messages actually sent\n                    nonlocal client_finished\n                    client_finished = True\n\n        async def run_test(app: web.Application) -> AsyncIterator[None]:\n            nonlocal t\n            t = asyncio.create_task(test())\n            yield\n            await asyncio.sleep(0)  # In case test() hasn't resumed yet.\n            t.cancel()\n            with contextlib.suppress(asyncio.CancelledError):\n                await t\n\n        app = web.Application()\n        app[WS] = set()\n        app.on_shutdown.append(close_websockets)\n        app.cleanup_ctx.append(run_test)\n        app.router.add_get(\"/ws\", ws_handler)\n        app.router.add_get(\"/stop\", self.stop)\n\n        start = time.time()\n        web.run_app(app, sock=sock, shutdown_timeout=10)\n        assert time.time() - start < 5\n        assert client_finished\n        assert server_finished\n\n    def test_shutdown_handler_cancellation_suppressed(\n        self, unused_port_socket: socket.socket\n    ) -> None:\n        sock = unused_port_socket\n        port = sock.getsockname()[1]\n        actions = []\n        t = None\n\n        async def test() -> None:\n            async def test_resp(sess: ClientSession) -> None:\n                t = ClientTimeout(total=0.4)\n                with pytest.raises(asyncio.TimeoutError):\n                    async with sess.get(f\"http://127.0.0.1:{port}/\", timeout=t):\n                        assert False  # Should timeout before this\n                actions.append(\"CANCELLED\")\n\n            async with ClientSession() as sess:\n                t = asyncio.create_task(test_resp(sess))\n                await asyncio.sleep(0.5)\n                # Handler is in-progress while we trigger server shutdown.\n                actions.append(\"PRESTOP\")\n                async with sess.get(f\"http://127.0.0.1:{port}/stop\"):\n                    pass\n\n                actions.append(\"STOPPING\")\n                # Handler should still complete and produce a response.\n                await t\n\n        async def run_test(app: web.Application) -> AsyncIterator[None]:\n            nonlocal t\n            t = asyncio.create_task(test())\n            yield\n            await t\n\n        async def handler(request: web.Request) -> web.Response:\n            try:\n                await asyncio.sleep(5)\n            except asyncio.CancelledError:\n                actions.append(\"SUPPRESSED\")\n                await asyncio.sleep(2)\n                actions.append(\"DONE\")\n            return web.Response(text=\"FOO\")\n\n        app = web.Application()\n        app.cleanup_ctx.append(run_test)\n        app.router.add_get(\"/\", handler)\n        app.router.add_get(\"/stop\", self.stop)\n\n        web.run_app(app, sock=sock, shutdown_timeout=2, handler_cancellation=True)\n        assert t is not None\n        assert t.exception() is None\n        assert actions == [\"CANCELLED\", \"SUPPRESSED\", \"PRESTOP\", \"STOPPING\", \"DONE\"]\n"
  },
  {
    "path": "tests/test_streams.py",
    "content": "# Tests for streams.py\n\nimport abc\nimport asyncio\nimport gc\nimport types\nfrom collections import defaultdict\nfrom collections.abc import Iterator, Sequence\nfrom itertools import groupby\nfrom typing import TypeVar\nfrom unittest import mock\n\nimport pytest\n\nfrom aiohttp import streams\nfrom aiohttp.base_protocol import BaseProtocol\nfrom aiohttp.http_exceptions import LineTooLong\n\nDATA: bytes = b\"line1\\nline2\\nline3\\n\"\n\n_T = TypeVar(\"_T\")\n\n\ndef chunkify(seq: Sequence[_T], n: int) -> Iterator[Sequence[_T]]:\n    for i in range(0, len(seq), n):\n        yield seq[i : i + n]\n\n\nasync def create_stream() -> streams.StreamReader:\n    loop = asyncio.get_event_loop()\n    protocol = mock.Mock(_reading_paused=False)\n    stream = streams.StreamReader(protocol, 2**16, loop=loop)\n    stream.feed_data(DATA)\n    stream.feed_eof()\n    return stream\n\n\n@pytest.fixture\ndef protocol() -> mock.Mock:\n    return mock.Mock(_reading_paused=False)\n\n\nMEMLEAK_SKIP_TYPES = (\n    *(getattr(types, name) for name in dir(types) if name.endswith(\"Type\")),\n    mock.Mock,\n    abc.ABCMeta,\n)\n\n\ndef get_memory_usage(obj: object) -> int:\n    objs = [obj]\n    # Memory leak may be caused by leaked links to same objects.\n    # Without link counting, [1,2,3] is indistinguishable from [1,2,3,3,3,3,3,3]\n    known: defaultdict[int, int] = defaultdict(int)\n    known[id(obj)] += 1\n\n    while objs:\n        refs = gc.get_referents(*objs)\n        objs = []\n        for obj in refs:\n            if isinstance(obj, MEMLEAK_SKIP_TYPES):\n                continue\n            i = id(obj)\n            known[i] += 1\n            if known[i] == 1:\n                objs.append(obj)\n\n        # Make list of unhashable objects uniq\n        objs.sort(key=id)\n        objs = [next(g) for (i, g) in groupby(objs, id)]\n\n    return sum(known.values())\n\n\nclass TestStreamReader:\n    DATA: bytes = b\"line1\\nline2\\nline3\\n\"\n\n    def _make_one(self, limit: int = 2**16) -> streams.StreamReader:\n        loop = asyncio.get_event_loop()\n        return streams.StreamReader(mock.Mock(_reading_paused=False), limit, loop=loop)\n\n    async def test_create_waiter(self) -> None:\n        loop = asyncio.get_event_loop()\n        stream = self._make_one()\n        stream._waiter = loop.create_future  # type: ignore[assignment]\n        with pytest.raises(RuntimeError):\n            await stream._wait(\"test\")\n\n    async def test_at_eof(self) -> None:\n        stream = self._make_one()\n        assert not stream.at_eof()\n\n        stream.feed_data(b\"some data\\n\")\n        assert not stream.at_eof()\n\n        await stream.readline()\n        assert not stream.at_eof()\n\n        stream.feed_data(b\"some data\\n\")\n        stream.feed_eof()\n        await stream.readline()\n        assert stream.at_eof()\n\n    async def test_wait_eof(self) -> None:\n        loop = asyncio.get_event_loop()\n        stream = self._make_one()\n        wait_task = loop.create_task(stream.wait_eof())\n\n        async def cb() -> None:\n            await asyncio.sleep(0.1)\n            stream.feed_eof()\n\n        t = loop.create_task(cb())\n        await wait_task\n        assert stream.is_eof()\n        assert stream._eof_waiter is None\n        await t\n\n    async def test_wait_eof_eof(self) -> None:\n        loop = asyncio.get_event_loop()\n        stream = self._make_one()\n        stream.feed_eof()\n\n        wait_task = loop.create_task(stream.wait_eof())\n        await wait_task\n        assert stream.is_eof()\n\n    async def test_feed_empty_data(self) -> None:\n        stream = self._make_one()\n        stream.feed_data(b\"\")\n        stream.feed_eof()\n\n        data = await stream.read()\n        assert b\"\" == data\n\n    async def test_feed_nonempty_data(self) -> None:\n        stream = self._make_one()\n        stream.feed_data(self.DATA)\n        stream.feed_eof()\n\n        data = await stream.read()\n        assert self.DATA == data\n\n    async def test_read_zero(self) -> None:\n        # Read zero bytes.\n        stream = self._make_one()\n        stream.feed_data(self.DATA)\n\n        data = await stream.read(0)\n        assert b\"\" == data\n\n        stream.feed_eof()\n        data = await stream.read()\n        assert self.DATA == data\n\n    async def test_read(self) -> None:\n        loop = asyncio.get_event_loop()\n        # Read bytes.\n        stream = self._make_one()\n        read_task = loop.create_task(stream.read(30))\n\n        def cb() -> None:\n            stream.feed_data(self.DATA)\n\n        loop.call_soon(cb)\n\n        data = await read_task\n        assert self.DATA == data\n\n        stream.feed_eof()\n        data = await stream.read()\n        assert b\"\" == data\n\n    async def test_read_line_breaks(self) -> None:\n        # Read bytes without line breaks.\n        stream = self._make_one()\n        stream.feed_data(b\"line1\")\n        stream.feed_data(b\"line2\")\n\n        data = await stream.read(5)\n        assert b\"line1\" == data\n\n        data = await stream.read(5)\n        assert b\"line2\" == data\n\n    async def test_read_all(self) -> None:\n        # Read all available buffered bytes\n        stream = self._make_one()\n        stream.feed_data(b\"line1\")\n        stream.feed_data(b\"line2\")\n        stream.feed_eof()\n\n        data = await stream.read()\n        assert b\"line1line2\" == data\n\n    async def test_read_up_to(self) -> None:\n        # Read available buffered bytes up to requested amount\n        stream = self._make_one()\n        stream.feed_data(b\"line1\")\n        stream.feed_data(b\"line2\")\n\n        data = await stream.read(8)\n        assert b\"line1lin\" == data\n\n        data = await stream.read(8)\n        assert b\"e2\" == data\n\n    async def test_read_eof(self) -> None:\n        loop = asyncio.get_event_loop()\n        # Read bytes, stop at eof.\n        stream = self._make_one()\n        read_task = loop.create_task(stream.read(1024))\n\n        def cb() -> None:\n            stream.feed_eof()\n\n        loop.call_soon(cb)\n\n        data = await read_task\n        assert b\"\" == data\n\n        data = await stream.read()\n        assert data == b\"\"\n\n    async def test_read_eof_unread_data_no_warning(self) -> None:\n        # Read bytes.\n        stream = self._make_one()\n        stream.feed_eof()\n\n        with mock.patch(\"aiohttp.streams.internal_logger\") as internal_logger:\n            await stream.read()\n            await stream.read()\n            await stream.read()\n            await stream.read()\n            await stream.read()\n        with pytest.deprecated_call(\n            match=r\"^unread_data\\(\\) is deprecated and will be \"\n            r\"removed in future releases \\(#3260\\)$\",\n        ):\n            stream.unread_data(b\"data\")\n        await stream.read()\n        await stream.read()\n        assert not internal_logger.warning.called\n\n    async def test_read_until_eof(self) -> None:\n        loop = asyncio.get_event_loop()\n        # Read all bytes until eof.\n        stream = self._make_one()\n        read_task = loop.create_task(stream.read(-1))\n\n        def cb() -> None:\n            stream.feed_data(b\"chunk1\\n\")\n            stream.feed_data(b\"chunk2\")\n            stream.feed_eof()\n\n        loop.call_soon(cb)\n\n        data = await read_task\n        assert b\"chunk1\\nchunk2\" == data\n\n        data = await stream.read()\n        assert b\"\" == data\n\n    async def test_read_exception(self) -> None:\n        stream = self._make_one()\n        stream.feed_data(b\"line\\n\")\n\n        data = await stream.read(2)\n        assert b\"li\" == data\n\n        stream.set_exception(ValueError())\n        with pytest.raises(ValueError):\n            await stream.read(2)\n\n    async def test_readline(self) -> None:\n        loop = asyncio.get_event_loop()\n        # Read one line. 'readline' will need to wait for the data\n        # to come from 'cb'\n        stream = self._make_one()\n        stream.feed_data(b\"chunk1 \")\n        read_task = loop.create_task(stream.readline())\n\n        def cb() -> None:\n            stream.feed_data(b\"chunk2 \")\n            stream.feed_data(b\"chunk3 \")\n            stream.feed_data(b\"\\n chunk4\")\n\n        loop.call_soon(cb)\n\n        line = await read_task\n        assert b\"chunk1 chunk2 chunk3 \\n\" == line\n\n        stream.feed_eof()\n        data = await stream.read()\n        assert b\" chunk4\" == data\n\n    async def test_readline_limit_with_existing_data(self) -> None:\n        # Read one line. The data is in StreamReader's buffer\n        # before the event loop is run.\n\n        stream = self._make_one(limit=2)\n        stream.feed_data(b\"li\")\n        stream.feed_data(b\"ne1\\nline2\\n\")\n\n        with pytest.raises(LineTooLong):\n            await stream.readline()\n        # The buffer should contain the remaining data after exception\n        stream.feed_eof()\n        data = await stream.read()\n        assert b\"line2\\n\" == data\n\n    async def test_readline_limit(self) -> None:\n        loop = asyncio.get_event_loop()\n        # Read one line. StreamReaders are fed with data after\n        # their 'readline' methods are called.\n        stream = self._make_one(limit=4)\n\n        def cb() -> None:\n            stream.feed_data(b\"chunk1\")\n            stream.feed_data(b\"chunk2\\n\")\n            stream.feed_data(b\"chunk3\\n\")\n            stream.feed_eof()\n\n        loop.call_soon(cb)\n\n        with pytest.raises(LineTooLong):\n            await stream.readline()\n        data = await stream.read()\n        assert b\"chunk3\\n\" == data\n\n    async def test_readline_nolimit_nowait(self) -> None:\n        # All needed data for the first 'readline' call will be\n        # in the buffer.\n        stream = self._make_one()\n        stream.feed_data(self.DATA[:6])\n        stream.feed_data(self.DATA[6:])\n\n        line = await stream.readline()\n        assert b\"line1\\n\" == line\n\n        stream.feed_eof()\n        data = await stream.read()\n        assert b\"line2\\nline3\\n\" == data\n\n    async def test_readline_eof(self) -> None:\n        stream = self._make_one()\n        stream.feed_data(b\"some data\")\n        stream.feed_eof()\n\n        line = await stream.readline()\n        assert b\"some data\" == line\n\n    async def test_readline_empty_eof(self) -> None:\n        stream = self._make_one()\n        stream.feed_eof()\n\n        line = await stream.readline()\n        assert b\"\" == line\n\n    async def test_readline_read_byte_count(self) -> None:\n        stream = self._make_one()\n        stream.feed_data(self.DATA)\n\n        await stream.readline()\n\n        data = await stream.read(7)\n        assert b\"line2\\nl\" == data\n\n        stream.feed_eof()\n        data = await stream.read()\n        assert b\"ine3\\n\" == data\n\n    async def test_readline_exception(self) -> None:\n        stream = self._make_one()\n        stream.feed_data(b\"line\\n\")\n\n        data = await stream.readline()\n        assert b\"line\\n\" == data\n\n        stream.set_exception(ValueError())\n        with pytest.raises(ValueError):\n            await stream.readline()\n\n    @pytest.mark.parametrize(\"separator\", [b\"*\", b\"**\"])\n    async def test_readuntil(self, separator: bytes) -> None:\n        loop = asyncio.get_event_loop()\n        # Read one chunk. 'readuntil' will need to wait for the data\n        # to come from 'cb'\n        stream = self._make_one()\n        stream.feed_data(b\"chunk1 \")\n        read_task = loop.create_task(stream.readuntil(separator))\n\n        def cb() -> None:\n            stream.feed_data(b\"chunk2 \")\n            stream.feed_data(b\"chunk3 \")\n            stream.feed_data(separator + b\" chunk4\")\n\n        loop.call_soon(cb)\n\n        line = await read_task\n        assert b\"chunk1 chunk2 chunk3 \" + separator == line\n\n        stream.feed_eof()\n        data = await stream.read()\n        assert b\" chunk4\" == data\n\n    @pytest.mark.parametrize(\"separator\", [b\"&\", b\"&&\"])\n    async def test_readuntil_limit_with_existing_data(self, separator: bytes) -> None:\n        # Read one chunk. The data is in StreamReader's buffer\n        # before the event loop is run.\n\n        stream = self._make_one(limit=2)\n        stream.feed_data(b\"li\")\n        stream.feed_data(b\"ne1\" + separator + b\"line2\" + separator)\n\n        with pytest.raises(LineTooLong):\n            await stream.readuntil(separator)\n        # The buffer should contain the remaining data after exception\n        stream.feed_eof()\n        data = await stream.read()\n        assert b\"line2\" + separator == data\n\n    @pytest.mark.parametrize(\"separator\", [b\"$\", b\"$$\"])\n    async def test_readuntil_limit(self, separator: bytes) -> None:\n        loop = asyncio.get_event_loop()\n        # Read one chunk. StreamReaders are fed with data after\n        # their 'readuntil' methods are called.\n        stream = self._make_one(limit=4)\n\n        def cb() -> None:\n            stream.feed_data(b\"chunk1\")\n            stream.feed_data(b\"chunk2\" + separator)\n            stream.feed_data(b\"chunk3#\")\n            stream.feed_eof()\n\n        loop.call_soon(cb)\n\n        with pytest.raises(LineTooLong):\n            await stream.readuntil(separator)\n        data = await stream.read()\n        assert b\"chunk3#\" == data\n\n    @pytest.mark.parametrize(\"separator\", [b\"!\", b\"!!\"])\n    async def test_readuntil_nolimit_nowait(self, separator: bytes) -> None:\n        # All needed data for the first 'readuntil' call will be\n        # in the buffer.\n        seplen = len(separator)\n        stream = self._make_one()\n        data = b\"line1\" + separator + b\"line2\" + separator + b\"line3\" + separator\n        stream.feed_data(data[: 5 + seplen])\n        stream.feed_data(data[5 + seplen :])\n\n        line = await stream.readuntil(separator)\n        assert b\"line1\" + separator == line\n\n        stream.feed_eof()\n        data = await stream.read()\n        assert b\"line2\" + separator + b\"line3\" + separator == data\n\n    @pytest.mark.parametrize(\"separator\", [b\"@\", b\"@@\"])\n    async def test_readuntil_eof(self, separator: bytes) -> None:\n        stream = self._make_one()\n        stream.feed_data(b\"some data\")\n        stream.feed_eof()\n\n        line = await stream.readuntil(separator)\n        assert b\"some data\" == line\n\n    @pytest.mark.parametrize(\"separator\", [b\"@\", b\"@@\"])\n    async def test_readuntil_empty_eof(self, separator: bytes) -> None:\n        stream = self._make_one()\n        stream.feed_eof()\n\n        line = await stream.readuntil(separator)\n        assert b\"\" == line\n\n    @pytest.mark.parametrize(\"separator\", [b\"!\", b\"!!\"])\n    async def test_readuntil_read_byte_count(self, separator: bytes) -> None:\n        seplen = len(separator)\n        stream = self._make_one()\n        stream.feed_data(\n            b\"line1\" + separator + b\"line2\" + separator + b\"line3\" + separator\n        )\n\n        await stream.readuntil(separator)\n\n        data = await stream.read(6 + seplen)\n        assert b\"line2\" + separator + b\"l\" == data\n\n        stream.feed_eof()\n        data = await stream.read()\n        assert b\"ine3\" + separator == data\n\n    @pytest.mark.parametrize(\"separator\", [b\"#\", b\"##\"])\n    async def test_readuntil_exception(self, separator: bytes) -> None:\n        stream = self._make_one()\n        stream.feed_data(b\"line\" + separator)\n\n        data = await stream.readuntil(separator)\n        assert b\"line\" + separator == data\n\n        stream.set_exception(ValueError(\"Another exception\"))\n        with pytest.raises(ValueError, match=\"Another exception\"):\n            await stream.readuntil(separator)\n\n    async def test_readexactly_zero_or_less(self) -> None:\n        # Read exact number of bytes (zero or less).\n        stream = self._make_one()\n        stream.feed_data(self.DATA)\n\n        data = await stream.readexactly(0)\n        assert b\"\" == data\n        stream.feed_eof()\n        data = await stream.read()\n        assert self.DATA == data\n\n        stream = self._make_one()\n        stream.feed_data(self.DATA)\n\n        data = await stream.readexactly(-1)\n        assert b\"\" == data\n        stream.feed_eof()\n        data = await stream.read()\n        assert self.DATA == data\n\n    async def test_readexactly(self) -> None:\n        loop = asyncio.get_event_loop()\n        # Read exact number of bytes.\n        stream = self._make_one()\n\n        n = 2 * len(self.DATA)\n        read_task = loop.create_task(stream.readexactly(n))\n\n        def cb() -> None:\n            stream.feed_data(self.DATA)\n            stream.feed_data(self.DATA)\n            stream.feed_data(self.DATA)\n\n        loop.call_soon(cb)\n\n        data = await read_task\n        assert self.DATA + self.DATA == data\n\n        stream.feed_eof()\n        data = await stream.read()\n        assert self.DATA == data\n\n    async def test_readexactly_eof(self) -> None:\n        loop = asyncio.get_event_loop()\n        # Read exact number of bytes (eof).\n        stream = self._make_one()\n        n = 2 * len(self.DATA)\n        read_task = loop.create_task(stream.readexactly(n))\n\n        def cb() -> None:\n            stream.feed_data(self.DATA)\n            stream.feed_eof()\n\n        loop.call_soon(cb)\n\n        with pytest.raises(asyncio.IncompleteReadError) as cm:\n            await read_task\n        assert cm.value.partial == self.DATA\n        assert cm.value.expected == n\n        assert str(cm.value) == \"18 bytes read on a total of 36 expected bytes\"\n        data = await stream.read()\n        assert b\"\" == data\n\n    async def test_readexactly_exception(self) -> None:\n        stream = self._make_one()\n        stream.feed_data(b\"line\\n\")\n\n        data = await stream.readexactly(2)\n        assert b\"li\" == data\n\n        stream.set_exception(ValueError())\n        with pytest.raises(ValueError):\n            await stream.readexactly(2)\n\n    async def test_unread_data(self) -> None:\n        stream = self._make_one()\n        stream.feed_data(b\"line1\")\n        stream.feed_data(b\"line2\")\n        stream.feed_data(b\"onemoreline\")\n\n        data = await stream.read(5)\n        assert b\"line1\" == data\n\n        with pytest.deprecated_call(\n            match=r\"^unread_data\\(\\) is deprecated and will be \"\n            r\"removed in future releases \\(#3260\\)$\",\n        ):\n            stream.unread_data(data)\n\n        data = await stream.read(5)\n        assert b\"line1\" == data\n\n        data = await stream.read(4)\n        assert b\"line\" == data\n\n        with pytest.deprecated_call(\n            match=r\"^unread_data\\(\\) is deprecated and will be \"\n            r\"removed in future releases \\(#3260\\)$\",\n        ):\n            stream.unread_data(b\"line1line\")\n\n        data = b\"\"\n        while len(data) < 10:\n            data += await stream.read(10)\n        assert b\"line1line2\" == data\n\n        data = await stream.read(7)\n        assert b\"onemore\" == data\n\n        with pytest.deprecated_call(\n            match=r\"^unread_data\\(\\) is deprecated and will be \"\n            r\"removed in future releases \\(#3260\\)$\",\n        ):\n            stream.unread_data(data)\n\n        data = b\"\"\n        while len(data) < 11:\n            data += await stream.read(11)\n        assert b\"onemoreline\" == data\n\n        with pytest.deprecated_call(\n            match=r\"^unread_data\\(\\) is deprecated and will be \"\n            r\"removed in future releases \\(#3260\\)$\",\n        ):\n            stream.unread_data(b\"line\")\n        data = await stream.read(4)\n        assert b\"line\" == data\n\n        stream.feed_eof()\n        with pytest.deprecated_call(\n            match=r\"^unread_data\\(\\) is deprecated and will be \"\n            r\"removed in future releases \\(#3260\\)$\",\n        ):\n            stream.unread_data(b\"at_eof\")\n        data = await stream.read(6)\n        assert b\"at_eof\" == data\n\n    async def test_exception(self) -> None:\n        stream = self._make_one()\n        assert stream.exception() is None\n\n        exc = ValueError()\n        stream.set_exception(exc)\n        assert stream.exception() is exc\n\n    async def test_exception_waiter(self) -> None:\n        loop = asyncio.get_event_loop()\n        stream = self._make_one()\n\n        async def set_err() -> None:\n            stream.set_exception(ValueError())\n\n        t1 = loop.create_task(stream.readline())\n        t2 = loop.create_task(set_err())\n\n        await asyncio.wait((t1, t2))\n        with pytest.raises(ValueError):\n            t1.result()\n\n    async def test_exception_cancel(self) -> None:\n        loop = asyncio.get_event_loop()\n        stream = self._make_one()\n\n        async def read_a_line() -> None:\n            await stream.readline()\n\n        t = loop.create_task(read_a_line())\n        await asyncio.sleep(0)\n        t.cancel()\n        await asyncio.sleep(0)\n        # The following line fails if set_exception() isn't careful.\n        stream.set_exception(RuntimeError(\"message\"))\n        await asyncio.sleep(0)\n        assert stream._waiter is None\n\n    async def test_readany_eof(self) -> None:\n        loop = asyncio.get_event_loop()\n        stream = self._make_one()\n        read_task = loop.create_task(stream.readany())\n        loop.call_soon(stream.feed_data, b\"chunk1\\n\")\n\n        data = await read_task\n        assert b\"chunk1\\n\" == data\n        stream.feed_eof()\n        data = await stream.read()\n        assert b\"\" == data\n\n    async def test_readany_empty_eof(self) -> None:\n        loop = asyncio.get_event_loop()\n        stream = self._make_one()\n        stream.feed_eof()\n        read_task = loop.create_task(stream.readany())\n\n        data = await read_task\n\n        assert b\"\" == data\n\n    async def test_readany_exception(self) -> None:\n        stream = self._make_one()\n        stream.feed_data(b\"line\\n\")\n\n        data = await stream.readany()\n        assert b\"line\\n\" == data\n\n        stream.set_exception(ValueError())\n        with pytest.raises(ValueError):\n            await stream.readany()\n\n    async def test_read_nowait(self) -> None:\n        stream = self._make_one()\n        stream.feed_data(b\"line1\\nline2\\n\")\n\n        assert stream.read_nowait() == b\"line1\\nline2\\n\"\n        assert stream.read_nowait() == b\"\"\n        stream.feed_eof()\n        data = await stream.read()\n        assert b\"\" == data\n\n    async def test_read_nowait_n(self) -> None:\n        stream = self._make_one()\n        stream.feed_data(b\"line1\\nline2\\n\")\n\n        assert stream.read_nowait(4) == b\"line\"\n        assert stream.read_nowait() == b\"1\\nline2\\n\"\n        assert stream.read_nowait() == b\"\"\n        stream.feed_eof()\n        data = await stream.read()\n        assert b\"\" == data\n\n    async def test_read_nowait_exception(self) -> None:\n        stream = self._make_one()\n        stream.feed_data(b\"line\\n\")\n        stream.set_exception(ValueError())\n\n        with pytest.raises(ValueError):\n            stream.read_nowait()\n\n    async def test_read_nowait_waiter(self) -> None:\n        loop = asyncio.get_event_loop()\n        stream = self._make_one()\n        stream.feed_data(b\"line\\n\")\n        stream._waiter = loop.create_future()\n\n        with pytest.raises(RuntimeError):\n            stream.read_nowait()\n\n    async def test_readchunk(self) -> None:\n        loop = asyncio.get_event_loop()\n        stream = self._make_one()\n\n        def cb() -> None:\n            stream.feed_data(b\"chunk1\")\n            stream.feed_data(b\"chunk2\")\n            stream.feed_eof()\n\n        loop.call_soon(cb)\n\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"chunk1\" == data\n        assert not end_of_chunk\n\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"chunk2\" == data\n        assert not end_of_chunk\n\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"\" == data\n        assert not end_of_chunk\n\n    async def test_readchunk_wait_eof(self) -> None:\n        loop = asyncio.get_event_loop()\n        stream = self._make_one()\n\n        async def cb() -> None:\n            await asyncio.sleep(0.1)\n            stream.feed_eof()\n\n        t = loop.create_task(cb())\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"\" == data\n        assert not end_of_chunk\n        assert stream.is_eof()\n        await t\n\n    async def test_begin_and_end_chunk_receiving(self) -> None:\n        stream = self._make_one()\n\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"part1\")\n        stream.feed_data(b\"part2\")\n        stream.end_http_chunk_receiving()\n\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"part1part2\" == data\n        assert end_of_chunk\n\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"part3\")\n\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"part3\" == data\n        assert not end_of_chunk\n\n        stream.end_http_chunk_receiving()\n\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"\" == data\n        assert end_of_chunk\n\n        stream.feed_eof()\n\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"\" == data\n        assert not end_of_chunk\n\n    async def test_readany_chunk_end_race(self) -> None:\n        stream = self._make_one()\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"part1\")\n\n        data = await stream.readany()\n        assert data == b\"part1\"\n\n        loop = asyncio.get_event_loop()\n        task = loop.create_task(stream.readany())\n\n        # Give a chance for task to create waiter and start waiting for it.\n        await asyncio.sleep(0.1)\n        assert stream._waiter is not None\n        assert not task.done()  # Just for sure.\n\n        # This will trigger waiter, but without feeding any data.\n        # The stream should re-create waiter again.\n        stream.end_http_chunk_receiving()\n\n        # Give a chance for task to resolve.\n        # If everything is OK, previous action SHOULD NOT resolve the task.\n        await asyncio.sleep(0.1)\n        assert not task.done()  # The actual test.\n\n        stream.begin_http_chunk_receiving()\n        # This SHOULD unblock the task actually.\n        stream.feed_data(b\"part2\")\n        stream.end_http_chunk_receiving()\n\n        data = await task\n        assert data == b\"part2\"\n\n    async def test_end_chunk_receiving_without_begin(self) -> None:\n        stream = self._make_one()\n        with pytest.raises(RuntimeError):\n            stream.end_http_chunk_receiving()\n\n    async def test_readchunk_with_unread(self) -> None:\n        # Test that stream.unread does not break controlled chunk receiving.\n        stream = self._make_one()\n\n        # Send 2 chunks\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"part1\")\n        stream.end_http_chunk_receiving()\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"part2\")\n        stream.end_http_chunk_receiving()\n\n        # Read only one chunk\n        data, end_of_chunk = await stream.readchunk()\n\n        # Try to unread a part of the first chunk\n        with pytest.deprecated_call(\n            match=r\"^unread_data\\(\\) is deprecated and will be \"\n            r\"removed in future releases \\(#3260\\)$\",\n        ):\n            stream.unread_data(b\"rt1\")\n\n        # The end_of_chunk signal was already received for the first chunk,\n        # so we receive up to the second one\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"rt1part2\" == data\n        assert end_of_chunk\n\n        # Unread a part of the second chunk\n        with pytest.deprecated_call(\n            match=r\"^unread_data\\(\\) is deprecated and will be \"\n            r\"removed in future releases \\(#3260\\)$\",\n        ):\n            stream.unread_data(b\"rt2\")\n\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"rt2\" == data\n        # end_of_chunk was already received for this chunk\n        assert not end_of_chunk\n\n        stream.feed_eof()\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"\" == data\n        assert not end_of_chunk\n\n    async def test_readchunk_with_other_read_calls(self) -> None:\n        # Test that stream.readchunk works when other read calls are made on\n        # the stream.\n        stream = self._make_one()\n\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"part1\")\n        stream.end_http_chunk_receiving()\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"part2\")\n        stream.end_http_chunk_receiving()\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"part3\")\n        stream.end_http_chunk_receiving()\n\n        data = await stream.read(7)\n        assert b\"part1pa\" == data\n\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"rt2\" == data\n        assert end_of_chunk\n\n        # Corner case between read/readchunk\n        data = await stream.read(5)\n        assert b\"part3\" == data\n\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"\" == data\n        assert end_of_chunk\n\n        stream.feed_eof()\n\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"\" == data\n        assert not end_of_chunk\n\n    async def test_chunksplits_memory_leak(self) -> None:\n        # Test for memory leak on chunksplits\n        stream = self._make_one()\n\n        N = 500\n\n        # Warm-up variables\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"Y\" * N)\n        stream.end_http_chunk_receiving()\n        await stream.read(N)\n\n        N = 300\n\n        before = get_memory_usage(stream)\n        for _ in range(N):\n            stream.begin_http_chunk_receiving()\n            stream.feed_data(b\"X\")\n            stream.end_http_chunk_receiving()\n        await stream.read(N)\n        after = get_memory_usage(stream)\n\n        assert abs(after - before) == 0\n\n    async def test_read_empty_chunks(self) -> None:\n        # Test that feeding empty chunks does not break stream\n        stream = self._make_one()\n\n        # Simulate empty first chunk. This is significant special case\n        stream.begin_http_chunk_receiving()\n        stream.end_http_chunk_receiving()\n\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"ungzipped\")\n        stream.end_http_chunk_receiving()\n\n        # Possible when compression is enabled.\n        stream.begin_http_chunk_receiving()\n        stream.end_http_chunk_receiving()\n\n        # is also possible\n        stream.begin_http_chunk_receiving()\n        stream.end_http_chunk_receiving()\n\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\" data\")\n        stream.end_http_chunk_receiving()\n\n        stream.feed_eof()\n\n        data = await stream.read()\n        assert data == b\"ungzipped data\"\n\n    async def test_readchunk_separate_http_chunk_tail(self) -> None:\n        # Test that stream.readchunk returns (b'', True) when end of\n        # http chunk received after body\n        loop = asyncio.get_event_loop()\n        stream = self._make_one()\n\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"part1\")\n\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"part1\" == data\n        assert not end_of_chunk\n\n        async def cb() -> None:\n            await asyncio.sleep(0.1)\n            stream.end_http_chunk_receiving()\n\n        t = loop.create_task(cb())\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"\" == data\n        assert end_of_chunk\n\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"part2\")\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"part2\" == data\n        assert not end_of_chunk\n\n        stream.end_http_chunk_receiving()\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"part3\")\n        stream.end_http_chunk_receiving()\n\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"\" == data\n        assert end_of_chunk\n\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"part3\" == data\n        assert end_of_chunk\n\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"part4\")\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"part4\" == data\n        assert not end_of_chunk\n\n        await t\n\n        async def cb2() -> None:\n            await asyncio.sleep(0.1)\n            stream.end_http_chunk_receiving()\n            stream.feed_eof()\n\n        t = loop.create_task(cb2())\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"\" == data\n        assert end_of_chunk\n\n        data, end_of_chunk = await stream.readchunk()\n        assert b\"\" == data\n        assert not end_of_chunk\n        await t\n\n    async def test___repr__(self) -> None:\n        stream = self._make_one()\n        assert \"<StreamReader>\" == repr(stream)\n\n    async def test___repr__nondefault_limit(self) -> None:\n        stream = self._make_one(limit=123)\n        assert \"<StreamReader low=123 high=246>\" == repr(stream)\n\n    async def test___repr__eof(self) -> None:\n        stream = self._make_one()\n        stream.feed_eof()\n        assert \"<StreamReader eof>\" == repr(stream)\n\n    async def test___repr__data(self) -> None:\n        stream = self._make_one()\n        stream.feed_data(b\"data\")\n        assert \"<StreamReader 4 bytes>\" == repr(stream)\n\n    async def test___repr__exception(self) -> None:\n        stream = self._make_one()\n        exc = RuntimeError()\n        stream.set_exception(exc)\n        assert \"<StreamReader e=RuntimeError()>\" == repr(stream)\n\n    async def test___repr__waiter(self) -> None:\n        loop = asyncio.get_event_loop()\n        stream = self._make_one()\n        stream._waiter = loop.create_future()\n        assert repr(stream).startswith(\"<StreamReader w=<Future pending\")\n        stream._waiter.set_result(None)\n        await stream._waiter\n        stream._waiter = None\n        assert \"<StreamReader>\" == repr(stream)\n\n    async def test_unread_empty(self) -> None:\n        stream = self._make_one()\n        stream.feed_data(b\"line1\")\n        stream.feed_eof()\n        with pytest.deprecated_call(\n            match=r\"^unread_data\\(\\) is deprecated and will be \"\n            r\"removed in future releases \\(#3260\\)$\",\n        ):\n            stream.unread_data(b\"\")\n\n        data = await stream.read(5)\n        assert b\"line1\" == data\n        assert stream.at_eof()\n\n\nasync def test_empty_stream_reader() -> None:\n    s = streams.EmptyStreamReader()\n    assert str(s) is not None\n    assert repr(s) == \"<EmptyStreamReader>\"\n    assert s.set_exception(ValueError()) is None  # type: ignore[func-returns-value]\n    assert s.exception() is None\n    assert s.feed_eof() is None  # type: ignore[func-returns-value]\n    assert s.feed_data(b\"data\") is None  # type: ignore[func-returns-value]\n    assert s.at_eof()\n    await s.wait_eof()\n    assert await s.read() == b\"\"\n    assert await s.readline() == b\"\"\n    assert await s.readany() == b\"\"\n    assert await s.readchunk() == (b\"\", False)\n    assert await s.readchunk() == (b\"\", True)\n    with pytest.raises(asyncio.IncompleteReadError):\n        await s.readexactly(10)\n    assert s.read_nowait() == b\"\"\n    assert s.total_bytes == 0\n\n\nasync def test_empty_stream_reader_iter_chunks() -> None:\n    s = streams.EmptyStreamReader()\n\n    # check that iter_chunks() does not cause infinite loop\n    iter_chunks = s.iter_chunks()\n    with pytest.raises(StopAsyncIteration):\n        await iter_chunks.__anext__()\n\n\n@pytest.fixture\nasync def buffer(loop: asyncio.AbstractEventLoop) -> streams.DataQueue[bytes]:\n    return streams.DataQueue(loop)\n\n\nclass TestDataQueue:\n    def test_is_eof(self, buffer: streams.DataQueue[bytes]) -> None:\n        assert not buffer.is_eof()\n        buffer.feed_eof()\n        assert buffer.is_eof()\n\n    def test_at_eof(self, buffer: streams.DataQueue[bytes]) -> None:\n        assert not buffer.at_eof()\n        buffer.feed_eof()\n        assert buffer.at_eof()\n        buffer._buffer.append(b\"foo\")\n        assert not buffer.at_eof()\n\n    def test_feed_data(self, buffer: streams.DataQueue[bytes]) -> None:\n        item = b\" \"\n        buffer.feed_data(item)\n        assert [item] == list(buffer._buffer)\n\n    def test_feed_eof(self, buffer: streams.DataQueue[bytes]) -> None:\n        buffer.feed_eof()\n        assert buffer._eof\n\n    async def test_read(self, buffer: streams.DataQueue[bytes]) -> None:\n        loop = asyncio.get_event_loop()\n        item = b\"\"\n\n        def cb() -> None:\n            buffer.feed_data(item)\n\n        loop.call_soon(cb)\n\n        data = await buffer.read()\n        assert item is data\n\n    async def test_read_eof(self, buffer: streams.DataQueue[bytes]) -> None:\n        loop = asyncio.get_event_loop()\n\n        def cb() -> None:\n            buffer.feed_eof()\n\n        loop.call_soon(cb)\n\n        with pytest.raises(streams.EofStream):\n            await buffer.read()\n\n    async def test_read_cancelled(self, buffer: streams.DataQueue[bytes]) -> None:\n        loop = asyncio.get_event_loop()\n        read_task = loop.create_task(buffer.read())\n        await asyncio.sleep(0)\n        waiter = buffer._waiter\n        assert asyncio.isfuture(waiter)\n\n        read_task.cancel()\n        with pytest.raises(asyncio.CancelledError):\n            await read_task\n        assert waiter.cancelled()\n        assert buffer._waiter is None\n\n        buffer.feed_data(b\"test\")\n        assert buffer._waiter is None\n\n    async def test_read_until_eof(self, buffer: streams.DataQueue[bytes]) -> None:\n        item = b\"\"\n        buffer.feed_data(item)\n        buffer.feed_eof()\n\n        data = await buffer.read()\n        assert data is item\n\n        with pytest.raises(streams.EofStream):\n            await buffer.read()\n\n    async def test_read_exc(self, buffer: streams.DataQueue[bytes]) -> None:\n        item = b\"\"\n        buffer.feed_data(item)\n        buffer.set_exception(ValueError)\n\n        data = await buffer.read()\n        assert item is data\n\n        with pytest.raises(ValueError):\n            await buffer.read()\n\n    async def test_read_exception(self, buffer: streams.DataQueue[bytes]) -> None:\n        buffer.set_exception(ValueError())\n\n        with pytest.raises(ValueError):\n            await buffer.read()\n\n    async def test_read_exception_with_data(\n        self, buffer: streams.DataQueue[bytes]\n    ) -> None:\n        val = b\"\"\n        buffer.feed_data(val)\n        buffer.set_exception(ValueError())\n\n        assert val is (await buffer.read())\n        with pytest.raises(ValueError):\n            await buffer.read()\n\n    async def test_read_exception_on_wait(\n        self, buffer: streams.DataQueue[bytes]\n    ) -> None:\n        loop = asyncio.get_event_loop()\n        read_task = loop.create_task(buffer.read())\n        await asyncio.sleep(0)\n        assert asyncio.isfuture(buffer._waiter)\n\n        buffer.feed_eof()\n        buffer.set_exception(ValueError())\n\n        with pytest.raises(ValueError):\n            await read_task\n\n    def test_exception(self, buffer: streams.DataQueue[bytes]) -> None:\n        assert buffer.exception() is None\n\n        exc = ValueError()\n        buffer.set_exception(exc)\n        assert buffer.exception() is exc\n\n    async def test_exception_waiter(self, buffer: streams.DataQueue[bytes]) -> None:\n        loop = asyncio.get_event_loop()\n\n        async def set_err() -> None:\n            buffer.set_exception(ValueError())\n\n        t1 = loop.create_task(buffer.read())\n        t2 = loop.create_task(set_err())\n\n        await asyncio.wait([t1, t2])\n\n        with pytest.raises(ValueError):\n            t1.result()\n\n\nasync def test_feed_data_waiters(protocol: BaseProtocol) -> None:\n    loop = asyncio.get_event_loop()\n    reader = streams.StreamReader(protocol, 2**16, loop=loop)\n    waiter = reader._waiter = loop.create_future()\n    eof_waiter = reader._eof_waiter = loop.create_future()\n\n    reader.feed_data(b\"1\")\n    assert list(reader._buffer) == [b\"1\"]\n    assert reader._size == 1\n    assert reader.total_bytes == 1\n\n    assert waiter.done()\n    assert not eof_waiter.done()\n    assert reader._waiter is None\n    assert reader._eof_waiter is eof_waiter  # type: ignore[unreachable]\n\n\nasync def test_feed_data_completed_waiters(protocol: BaseProtocol) -> None:\n    loop = asyncio.get_event_loop()\n    reader = streams.StreamReader(protocol, 2**16, loop=loop)\n    waiter = reader._waiter = loop.create_future()\n\n    waiter.set_result(1)\n    reader.feed_data(b\"1\")\n\n    assert reader._waiter is None\n\n\nasync def test_feed_eof_waiters(protocol: BaseProtocol) -> None:\n    loop = asyncio.get_event_loop()\n    reader = streams.StreamReader(protocol, 2**16, loop=loop)\n    waiter = reader._waiter = loop.create_future()\n    eof_waiter = reader._eof_waiter = loop.create_future()\n\n    reader.feed_eof()\n    assert reader._eof\n\n    assert waiter.done()\n    assert eof_waiter.done()\n    assert reader._waiter is None\n    assert reader._eof_waiter is None  # type: ignore[unreachable]\n\n\nasync def test_feed_eof_cancelled(protocol: BaseProtocol) -> None:\n    loop = asyncio.get_event_loop()\n    reader = streams.StreamReader(protocol, 2**16, loop=loop)\n    waiter = reader._waiter = loop.create_future()\n    eof_waiter = reader._eof_waiter = loop.create_future()\n\n    waiter.set_result(1)\n    eof_waiter.set_result(1)\n\n    reader.feed_eof()\n\n    assert waiter.done()\n    assert eof_waiter.done()\n    assert reader._waiter is None\n    assert reader._eof_waiter is None  # type: ignore[unreachable]\n\n\nasync def test_on_eof(protocol: BaseProtocol) -> None:\n    loop = asyncio.get_event_loop()\n    reader = streams.StreamReader(protocol, 2**16, loop=loop)\n\n    on_eof = mock.Mock()\n    reader.on_eof(on_eof)\n\n    assert not on_eof.called\n    reader.feed_eof()\n    assert on_eof.called\n\n\nasync def test_on_eof_empty_reader() -> None:\n    reader = streams.EmptyStreamReader()\n\n    on_eof = mock.Mock()\n    reader.on_eof(on_eof)\n\n    assert on_eof.called\n\n\nasync def test_on_eof_exc_in_callback(protocol: BaseProtocol) -> None:\n    loop = asyncio.get_event_loop()\n    reader = streams.StreamReader(protocol, 2**16, loop=loop)\n\n    on_eof = mock.Mock()\n    on_eof.side_effect = ValueError\n\n    reader.on_eof(on_eof)\n    assert not on_eof.called\n    reader.feed_eof()\n    assert on_eof.called\n    assert not reader._eof_callbacks  # type: ignore[unreachable]\n\n\nasync def test_on_eof_exc_in_callback_empty_stream_reader() -> None:\n    reader = streams.EmptyStreamReader()\n\n    on_eof = mock.Mock()\n    on_eof.side_effect = ValueError\n\n    reader.on_eof(on_eof)\n    assert on_eof.called\n\n\nasync def test_on_eof_eof_is_set(protocol: BaseProtocol) -> None:\n    loop = asyncio.get_event_loop()\n    reader = streams.StreamReader(protocol, 2**16, loop=loop)\n    reader.feed_eof()\n\n    on_eof = mock.Mock()\n    reader.on_eof(on_eof)\n    assert on_eof.called\n    assert not reader._eof_callbacks\n\n\nasync def test_on_eof_eof_is_set_exception(protocol: BaseProtocol) -> None:\n    loop = asyncio.get_event_loop()\n    reader = streams.StreamReader(protocol, 2**16, loop=loop)\n    reader.feed_eof()\n\n    on_eof = mock.Mock()\n    on_eof.side_effect = ValueError\n\n    reader.on_eof(on_eof)\n    assert on_eof.called\n    assert not reader._eof_callbacks\n\n\nasync def test_set_exception(protocol: BaseProtocol) -> None:\n    loop = asyncio.get_event_loop()\n    reader = streams.StreamReader(protocol, 2**16, loop=loop)\n    waiter = reader._waiter = loop.create_future()\n    eof_waiter = reader._eof_waiter = loop.create_future()\n\n    exc = ValueError()\n    reader.set_exception(exc)\n\n    assert waiter.exception() is exc\n    assert eof_waiter.exception() is exc\n    assert reader._waiter is None\n    assert reader._eof_waiter is None  # type: ignore[unreachable]\n\n\nasync def test_set_exception_cancelled(protocol: BaseProtocol) -> None:\n    loop = asyncio.get_event_loop()\n    reader = streams.StreamReader(protocol, 2**16, loop=loop)\n    waiter = reader._waiter = loop.create_future()\n    eof_waiter = reader._eof_waiter = loop.create_future()\n\n    waiter.set_result(1)\n    eof_waiter.set_result(1)\n\n    exc = ValueError()\n    reader.set_exception(exc)\n\n    assert waiter.exception() is None\n    assert eof_waiter.exception() is None\n    assert reader._waiter is None\n    assert reader._eof_waiter is None  # type: ignore[unreachable]\n\n\nasync def test_set_exception_eof_callbacks(protocol: BaseProtocol) -> None:\n    loop = asyncio.get_event_loop()\n    reader = streams.StreamReader(protocol, 2**16, loop=loop)\n\n    on_eof = mock.Mock()\n    reader.on_eof(on_eof)\n\n    reader.set_exception(ValueError())\n    assert not on_eof.called\n    assert not reader._eof_callbacks\n\n\nasync def test_stream_reader_lines() -> None:\n    line_iter = iter(DATA.splitlines(keepends=True))\n    async for line in await create_stream():\n        assert line == next(line_iter, None)\n    pytest.raises(StopIteration, next, line_iter)\n\n\nasync def test_stream_reader_chunks_complete() -> None:\n    # Tests if chunked iteration works if the chunking works out\n    # (i.e. the data is divisible by the chunk size)\n    chunk_iter = chunkify(DATA, 9)\n    async for data in (await create_stream()).iter_chunked(9):\n        assert data == next(chunk_iter, None)\n    pytest.raises(StopIteration, next, chunk_iter)\n\n\nasync def test_stream_reader_chunks_incomplete() -> None:\n    # Tests if chunked iteration works if the last chunk is incomplete\n    chunk_iter = chunkify(DATA, 8)\n    async for data in (await create_stream()).iter_chunked(8):\n        assert data == next(chunk_iter, None)\n    pytest.raises(StopIteration, next, chunk_iter)\n\n\nasync def test_data_queue_empty() -> None:\n    # Tests that async looping yields nothing if nothing is there\n    loop = asyncio.get_event_loop()\n    buffer: streams.DataQueue[bytes] = streams.DataQueue(loop)\n    buffer.feed_eof()\n\n    async for _ in buffer:\n        assert False\n\n\nasync def test_data_queue_items() -> None:\n    # Tests that async looping yields objects identically\n    loop = asyncio.get_event_loop()\n    buffer = streams.DataQueue[str](loop)\n\n    items = [\"a\", \"b\"]\n    buffer.feed_data(items[0])\n    buffer.feed_data(items[1])\n    buffer.feed_eof()\n\n    item_iter = iter(items)\n    async for item in buffer:\n        assert item is next(item_iter, None)\n    pytest.raises(StopIteration, next, item_iter)\n\n\nasync def test_stream_reader_iter_any() -> None:\n    it = iter([b\"line1\\nline2\\nline3\\n\"])\n    async for raw in (await create_stream()).iter_any():\n        assert raw == next(it)\n    pytest.raises(StopIteration, next, it)\n\n\nasync def test_stream_reader_iter() -> None:\n    it = iter([b\"line1\\n\", b\"line2\\n\", b\"line3\\n\"])\n    async for raw in await create_stream():\n        assert raw == next(it)\n    pytest.raises(StopIteration, next, it)\n\n\nasync def test_stream_reader_iter_chunks_no_chunked_encoding() -> None:\n    it = iter([b\"line1\\nline2\\nline3\\n\"])\n    async for data, end_of_chunk in (await create_stream()).iter_chunks():\n        assert (data, end_of_chunk) == (next(it), False)\n    pytest.raises(StopIteration, next, it)\n\n\nasync def test_stream_reader_iter_chunks_chunked_encoding(\n    protocol: BaseProtocol,\n) -> None:\n    loop = asyncio.get_event_loop()\n    stream = streams.StreamReader(protocol, 2**16, loop=loop)\n    for line in DATA.splitlines(keepends=True):\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(line)\n        stream.end_http_chunk_receiving()\n    stream.feed_eof()\n\n    it = iter([b\"line1\\n\", b\"line2\\n\", b\"line3\\n\"])\n    async for data, end_of_chunk in stream.iter_chunks():\n        assert (data, end_of_chunk) == (next(it), True)\n    pytest.raises(StopIteration, next, it)\n\n\ndef test_isinstance_check() -> None:\n    assert isinstance(streams.EMPTY_PAYLOAD, streams.StreamReader)\n\n\nasync def test_stream_reader_pause_on_high_water_chunks(\n    protocol: mock.Mock,\n) -> None:\n    \"\"\"Test that reading is paused when chunk count exceeds high water mark.\"\"\"\n    loop = asyncio.get_event_loop()\n    # Use small limit so high_water_chunks is small: limit // 4 = 10\n    stream = streams.StreamReader(protocol, limit=40, loop=loop)\n\n    assert stream._high_water_chunks == 10\n    assert stream._low_water_chunks == 5\n\n    # Feed chunks until we exceed high_water_chunks\n    for i in range(12):\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"x\")  # 1 byte per chunk\n        stream.end_http_chunk_receiving()\n\n    # pause_reading should have been called when chunk count exceeded 10\n    protocol.pause_reading.assert_called()\n\n\nasync def test_stream_reader_resume_on_low_water_chunks(\n    protocol: mock.Mock,\n) -> None:\n    \"\"\"Test that reading resumes when chunk count drops below low water mark.\"\"\"\n    loop = asyncio.get_event_loop()\n    # Use small limit so high_water_chunks is small: limit // 4 = 10\n    stream = streams.StreamReader(protocol, limit=40, loop=loop)\n\n    assert stream._high_water_chunks == 10\n    assert stream._low_water_chunks == 5\n\n    # Feed chunks until we exceed high_water_chunks\n    for i in range(12):\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"x\")  # 1 byte per chunk\n        stream.end_http_chunk_receiving()\n\n    # Simulate that reading was paused\n    protocol._reading_paused = True\n    protocol.pause_reading.reset_mock()\n\n    # Read data to reduce both size and chunk count\n    # Reading will consume chunks and reduce _http_chunk_splits\n    data = await stream.read(10)\n    assert data == b\"xxxxxxxxxx\"\n\n    # resume_reading should have been called when both size and chunk count\n    # dropped below their respective low water marks\n    protocol.resume_reading.assert_called()\n\n\nasync def test_stream_reader_no_resume_when_chunks_still_high(\n    protocol: mock.Mock,\n) -> None:\n    \"\"\"Test that reading doesn't resume if chunk count is still above low water.\"\"\"\n    loop = asyncio.get_event_loop()\n    # Use small limit so high_water_chunks is small: limit // 4 = 10\n    stream = streams.StreamReader(protocol, limit=40, loop=loop)\n\n    # Feed many chunks\n    for i in range(12):\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(b\"x\")\n        stream.end_http_chunk_receiving()\n\n    # Simulate that reading was paused\n    protocol._reading_paused = True\n\n    # Read only a few bytes - chunk count will still be high\n    data = await stream.read(2)\n    assert data == b\"xx\"\n\n    # resume_reading should NOT be called because chunk count is still >= low_water_chunks\n    protocol.resume_reading.assert_not_called()\n\n\nasync def test_stream_reader_read_non_chunked_response(\n    protocol: mock.Mock,\n) -> None:\n    \"\"\"Test that non-chunked responses work correctly (no chunk tracking).\"\"\"\n    loop = asyncio.get_event_loop()\n    stream = streams.StreamReader(protocol, limit=40, loop=loop)\n\n    # Non-chunked: just feed data without begin/end_http_chunk_receiving\n    stream.feed_data(b\"Hello World\")\n\n    # _http_chunk_splits should be None for non-chunked responses\n    assert stream._http_chunk_splits is None\n\n    # Reading should work without issues\n    data = await stream.read(5)\n    assert data == b\"Hello\"\n\n    data = await stream.read(6)\n    assert data == b\" World\"\n\n\nasync def test_stream_reader_resume_non_chunked_when_paused(\n    protocol: mock.Mock,\n) -> None:\n    \"\"\"Test that resume works for non-chunked responses when paused due to size.\"\"\"\n    loop = asyncio.get_event_loop()\n    # Small limit so we can trigger pause via size\n    stream = streams.StreamReader(protocol, limit=10, loop=loop)\n\n    # Feed data that exceeds high_water (limit * 2 = 20)\n    stream.feed_data(b\"x\" * 25)\n\n    # Simulate that reading was paused due to size\n    protocol._reading_paused = True\n    protocol.pause_reading.assert_called()\n\n    # Read enough to drop below low_water (limit = 10)\n    data = await stream.read(20)\n    assert data == b\"x\" * 20\n\n    # resume_reading should be called (size is now 5 < low_water 10)\n    protocol.resume_reading.assert_called()\n\n\n@pytest.mark.parametrize(\"limit\", [1, 2, 4])\nasync def test_stream_reader_small_limit_resumes_reading(\n    protocol: mock.Mock,\n    limit: int,\n) -> None:\n    \"\"\"Test that small limits still allow resume_reading to be called.\n\n    Even with very small limits, high_water_chunks should be at least 3\n    and low_water_chunks should be at least 2, with high > low to ensure\n    proper flow control.\n    \"\"\"\n    loop = asyncio.get_event_loop()\n    stream = streams.StreamReader(protocol, limit=limit, loop=loop)\n\n    # Verify minimum thresholds are enforced and high > low\n    assert stream._high_water_chunks >= 3\n    assert stream._low_water_chunks >= 2\n    assert stream._high_water_chunks > stream._low_water_chunks\n\n    # Set up pause/resume side effects\n    def pause_reading() -> None:\n        protocol._reading_paused = True\n\n    protocol.pause_reading.side_effect = pause_reading\n\n    def resume_reading() -> None:\n        protocol._reading_paused = False\n\n    protocol.resume_reading.side_effect = resume_reading\n\n    # Feed 4 chunks (triggers pause at > high_water_chunks which is >= 3)\n    for char in b\"abcd\":\n        stream.begin_http_chunk_receiving()\n        stream.feed_data(bytes([char]))\n        stream.end_http_chunk_receiving()\n\n    # Reading should now be paused\n    assert protocol._reading_paused is True\n    assert protocol.pause_reading.called\n\n    # Read all data - should resume (chunk count drops below low_water_chunks)\n    data = stream.read_nowait()\n    assert data == b\"abcd\"\n    assert stream._size == 0\n\n    protocol.resume_reading.assert_called()\n    assert protocol._reading_paused is False\n"
  },
  {
    "path": "tests/test_tcp_helpers.py",
    "content": "import socket\nfrom unittest import mock\n\nimport pytest\n\nfrom aiohttp.tcp_helpers import tcp_nodelay\n\nhas_ipv6: bool = socket.has_ipv6\nif has_ipv6:\n    # The socket.has_ipv6 flag may be True if Python was built with IPv6\n    # support, but the target system still may not have it.\n    # So let's ensure that we really have IPv6 support.\n    try:\n        with socket.socket(socket.AF_INET6, socket.SOCK_STREAM):\n            pass\n    except OSError:  # pragma: no cover\n        has_ipv6 = False\n\n\n# nodelay\n\n\ndef test_tcp_nodelay_exception() -> None:\n    transport = mock.Mock()\n    s = mock.Mock()\n    s.setsockopt = mock.Mock()\n    s.family = socket.AF_INET\n    s.setsockopt.side_effect = OSError\n    transport.get_extra_info.return_value = s\n    tcp_nodelay(transport, True)\n    s.setsockopt.assert_called_with(socket.IPPROTO_TCP, socket.TCP_NODELAY, True)\n\n\ndef test_tcp_nodelay_enable() -> None:\n    transport = mock.Mock()\n    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:\n        transport.get_extra_info.return_value = s\n        tcp_nodelay(transport, True)\n        assert s.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)\n\n\ndef test_tcp_nodelay_enable_and_disable() -> None:\n    transport = mock.Mock()\n    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:\n        transport.get_extra_info.return_value = s\n        tcp_nodelay(transport, True)\n        assert s.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)\n        tcp_nodelay(transport, False)\n        assert not s.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)\n\n\n@pytest.mark.skipif(not has_ipv6, reason=\"IPv6 is not available\")\ndef test_tcp_nodelay_enable_ipv6() -> None:\n    transport = mock.Mock()\n    with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:\n        transport.get_extra_info.return_value = s\n        tcp_nodelay(transport, True)\n        assert s.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)\n\n\n@pytest.mark.skipif(not hasattr(socket, \"AF_UNIX\"), reason=\"requires unix sockets\")\ndef test_tcp_nodelay_enable_unix() -> None:\n    # do not set nodelay for unix socket\n    transport = mock.Mock()\n    s = mock.Mock(family=socket.AF_UNIX, type=socket.SOCK_STREAM)\n    transport.get_extra_info.return_value = s\n    tcp_nodelay(transport, True)\n    assert not s.setsockopt.called\n\n\ndef test_tcp_nodelay_enable_no_socket() -> None:\n    transport = mock.Mock()\n    transport.get_extra_info.return_value = None\n    tcp_nodelay(transport, True)\n"
  },
  {
    "path": "tests/test_test_utils.py",
    "content": "import asyncio\nimport gzip\nimport socket\nimport sys\nfrom collections.abc import Iterator, Mapping\nfrom typing import NoReturn\nfrom unittest import mock\n\nimport pytest\nfrom multidict import CIMultiDict, CIMultiDictProxy\nfrom yarl import URL\n\nimport aiohttp\nfrom aiohttp import web\nfrom aiohttp.pytest_plugin import AiohttpClient\nfrom aiohttp.test_utils import (\n    AioHTTPTestCase,\n    RawTestServer,\n    TestClient,\n    TestServer,\n    get_port_socket,\n    loop_context,\n    make_mocked_request,\n)\n\nif sys.version_info >= (3, 11):\n    from typing import assert_type\n\n_TestClient = TestClient[web.Request, web.Application]\n\n_hello_world_str = \"Hello, world\"\n_hello_world_bytes = _hello_world_str.encode(\"utf-8\")\n_hello_world_gz = gzip.compress(_hello_world_bytes)\n\n\ndef _create_example_app() -> web.Application:\n    async def hello(request: web.Request) -> web.Response:\n        return web.Response(body=_hello_world_bytes)\n\n    async def websocket_handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        msg = await ws.receive()\n        assert msg.type == aiohttp.WSMsgType.TEXT\n        await ws.send_str(msg.data + \"/answer\")\n\n        return ws\n\n    async def cookie_handler(request: web.Request) -> web.Response:\n        resp = web.Response(body=_hello_world_bytes)\n        resp.set_cookie(\"cookie\", \"val\")\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"*\", \"/\", hello)\n    app.router.add_route(\"*\", \"/websocket\", websocket_handler)\n    app.router.add_route(\"*\", \"/cookie\", cookie_handler)\n    return app\n\n\n# these exist to test the pytest scenario\n@pytest.fixture\ndef loop() -> Iterator[asyncio.AbstractEventLoop]:\n    with loop_context() as loop:\n        yield loop\n\n\n@pytest.fixture\ndef app() -> web.Application:\n    return _create_example_app()\n\n\n@pytest.fixture\ndef test_client(\n    loop: asyncio.AbstractEventLoop, app: web.Application\n) -> Iterator[_TestClient]:\n    async def make_client() -> TestClient[web.Request, web.Application]:\n        return TestClient(TestServer(app))\n\n    client = loop.run_until_complete(make_client())\n\n    loop.run_until_complete(client.start_server())\n    yield client\n    loop.run_until_complete(client.close())\n\n\nasync def test_aiohttp_client_close_is_idempotent() -> None:\n    # a test client, called multiple times, should\n    # not attempt to close the server again.\n    app = _create_example_app()\n    client = TestClient(TestServer(app))\n    await client.close()\n    await client.close()\n\n\nclass TestAioHTTPTestCase(AioHTTPTestCase):\n    async def get_application(self) -> web.Application:\n        return _create_example_app()\n\n    async def test_example_with_loop(self) -> None:\n        request = await self.client.request(\"GET\", \"/\")\n        assert request.status == 200\n        text = await request.text()\n        assert _hello_world_str == text\n\n    async def test_example_without_explicit_loop(self) -> None:\n        request = await self.client.request(\"GET\", \"/\")\n        assert request.status == 200\n        text = await request.text()\n        assert _hello_world_str == text\n\n    async def test_inner_example(self) -> None:\n        async def test_get_route() -> None:\n            resp = await self.client.request(\"GET\", \"/\")\n            assert resp.status == 200\n            text = await resp.text()\n            assert _hello_world_str == text\n\n        await test_get_route()\n\n\ndef test_get_route(loop: asyncio.AbstractEventLoop, test_client: _TestClient) -> None:\n    async def test_get_route() -> None:\n        resp = await test_client.request(\"GET\", \"/\")\n        assert resp.status == 200\n        text = await resp.text()\n        assert _hello_world_str == text\n\n    loop.run_until_complete(test_get_route())\n\n\nasync def test_client_websocket(\n    loop: asyncio.AbstractEventLoop, test_client: _TestClient\n) -> None:\n    resp = await test_client.ws_connect(\"/websocket\")\n    await resp.send_str(\"foo\")\n    msg = await resp.receive()\n    assert msg.type == aiohttp.WSMsgType.TEXT\n    assert \"foo\" in msg.data\n    await resp.send_str(\"close\")\n    msg = await resp.receive()\n    assert msg.type == aiohttp.WSMsgType.CLOSE\n\n\nasync def test_client_cookie(\n    loop: asyncio.AbstractEventLoop, test_client: _TestClient\n) -> None:\n    assert not test_client.session.cookie_jar\n    await test_client.get(\"/cookie\")\n    cookies = list(test_client.session.cookie_jar)\n    assert cookies[0].key == \"cookie\"\n    assert cookies[0].value == \"val\"\n\n\n@pytest.mark.parametrize(\n    \"method\", [\"get\", \"post\", \"options\", \"post\", \"put\", \"patch\", \"delete\"]\n)\nasync def test_test_client_methods(\n    method: str, loop: asyncio.AbstractEventLoop, test_client: _TestClient\n) -> None:\n    resp = await getattr(test_client, method)(\"/\")\n    assert resp.status == 200\n    text = await resp.text()\n    assert _hello_world_str == text\n\n\nasync def test_test_client_head(\n    loop: asyncio.AbstractEventLoop, test_client: _TestClient\n) -> None:\n    resp = await test_client.head(\"/\")\n    assert resp.status == 200\n\n\n@pytest.mark.parametrize(\"headers\", [{\"token\": \"x\"}, CIMultiDict({\"token\": \"x\"}), {}])\ndef test_make_mocked_request(headers: Mapping[str, str]) -> None:\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    assert req.method == \"GET\"\n    assert req.path == \"/\"\n    assert isinstance(req, web.Request)\n    assert isinstance(req.headers, CIMultiDictProxy)\n\n\ndef test_make_mocked_request_sslcontext() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    assert req.transport is not None\n    assert req.transport.get_extra_info(\"sslcontext\") is None\n\n\ndef test_make_mocked_request_unknown_extra_info() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    assert req.transport is not None\n    assert req.transport.get_extra_info(\"unknown_extra_info\") is None\n\n\ndef test_make_mocked_request_app() -> None:\n    app = mock.Mock()\n    req = make_mocked_request(\"GET\", \"/\", app=app)\n    assert req.app is app\n\n\ndef test_make_mocked_request_app_can_store_values() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    req.app[\"a_field\"] = \"a_value\"\n    assert req.app[\"a_field\"] == \"a_value\"\n\n\ndef test_make_mocked_request_app_access_non_existing() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    with pytest.raises(AttributeError):\n        req.app.foo  # type: ignore[attr-defined]\n\n\ndef test_make_mocked_request_match_info() -> None:\n    req = make_mocked_request(\"GET\", \"/\", match_info={\"a\": \"1\", \"b\": \"2\"})\n    assert req.match_info == {\"a\": \"1\", \"b\": \"2\"}\n\n\ndef test_make_mocked_request_content() -> None:\n    payload = mock.Mock()\n    req = make_mocked_request(\"GET\", \"/\", payload=payload)\n    assert req.content is payload\n\n\nasync def test_make_mocked_request_empty_payload() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    assert await req.read() == b\"\"\n\n\ndef test_make_mocked_request_transport() -> None:\n    transport = mock.Mock()\n    req = make_mocked_request(\"GET\", \"/\", transport=transport)\n    assert req.transport is transport\n\n\nasync def test_test_client_props() -> None:\n    app = _create_example_app()\n    server = TestServer(app, scheme=\"http\", host=\"127.0.0.1\")\n    client = TestClient(server)\n    assert client.scheme == \"http\"\n    assert client.host == \"127.0.0.1\"\n    assert client.port == 0\n    async with client:\n        assert isinstance(client.port, int)\n        assert client.server is not None\n        if sys.version_info >= (3, 11):\n            assert_type(client.app, web.Application)\n        assert client.app is not None\n    assert client.port == 0\n\n\nasync def test_test_client_raw_server_props() -> None:\n    async def hello(request: web.BaseRequest) -> NoReturn:\n        assert False\n\n    server = RawTestServer(hello, scheme=\"http\", host=\"127.0.0.1\")\n    client = TestClient(server)\n    assert client.scheme == \"http\"\n    assert client.host == \"127.0.0.1\"\n    assert client.port == 0\n    async with client:\n        assert isinstance(client.port, int)\n        assert client.server is not None\n        if sys.version_info >= (3, 11):\n            assert_type(client.app, None)\n        assert client.app is None\n    assert client.port == 0\n\n\nasync def test_test_server_context_manager(loop: asyncio.AbstractEventLoop) -> None:\n    app = _create_example_app()\n    async with TestServer(app) as server:\n        client = aiohttp.ClientSession()\n        resp = await client.head(server.make_url(\"/\"))\n        assert resp.status == 200\n        resp.close()\n        await client.close()\n\n\ndef test_client_unsupported_arg() -> None:\n    with pytest.raises(TypeError) as e:\n        TestClient(\"string\")  # type: ignore[call-overload]\n\n    assert (\n        str(e.value) == \"server must be TestServer instance, found type: <class 'str'>\"\n    )\n\n\nasync def test_server_make_url_yarl_compatibility(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    app = _create_example_app()\n    async with TestServer(app) as server:\n        make_url = server.make_url\n        assert make_url(URL(\"/foo\")) == make_url(\"/foo\")\n        with pytest.raises(AssertionError):\n            make_url(\"http://foo.com\")\n        with pytest.raises(AssertionError):\n            make_url(URL(\"http://foo.com\"))\n\n\n@pytest.mark.xfail(reason=\"https://github.com/pytest-dev/pytest/issues/13546\")\ndef test_testcase_no_app(\n    testdir: pytest.Testdir, loop: asyncio.AbstractEventLoop\n) -> None:\n    testdir.makepyfile(\"\"\"\n        from aiohttp.test_utils import AioHTTPTestCase\n\n\n        class InvalidTestCase(AioHTTPTestCase):\n            def test_noop(self) -> None:\n                pass\n        \"\"\")\n    result = testdir.runpytest()\n    result.stdout.fnmatch_lines([\"*TypeError*\"])\n\n\nasync def test_disable_retry_persistent_connection(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    num_requests = 0\n\n    async def handler(request: web.Request) -> web.Response:\n        nonlocal num_requests\n\n        num_requests += 1\n        request.protocol.force_close()\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n    with pytest.raises(aiohttp.ServerDisconnectedError):\n        await client.get(\"/\")\n\n    assert num_requests == 1\n\n\nasync def test_server_context_manager(\n    app: web.Application, loop: asyncio.AbstractEventLoop\n) -> None:\n    async with TestServer(app) as server:\n        async with aiohttp.ClientSession() as client:\n            async with client.head(server.make_url(\"/\")) as resp:\n                assert resp.status == 200\n\n\n@pytest.mark.parametrize(\n    \"method\", [\"head\", \"get\", \"post\", \"options\", \"post\", \"put\", \"patch\", \"delete\"]\n)\nasync def test_client_context_manager_response(\n    method: str, app: web.Application, loop: asyncio.AbstractEventLoop\n) -> None:\n    async with TestClient(TestServer(app)) as client:\n        async with getattr(client, method)(\"/\") as resp:\n            assert resp.status == 200\n            if method != \"head\":\n                text = await resp.text()\n                assert \"Hello, world\" in text\n\n\nasync def test_custom_port(\n    loop: asyncio.AbstractEventLoop,\n    app: web.Application,\n    unused_port_socket: socket.socket,\n) -> None:\n    sock = unused_port_socket\n    port = sock.getsockname()[1]\n    client = TestClient(\n        TestServer(app, port=port, socket_factory=lambda *args, **kwargs: sock)\n    )\n    await client.start_server()\n\n    assert client.server.port == port\n\n    resp = await client.get(\"/\")\n    assert resp.status == 200\n    text = await resp.text()\n    assert _hello_world_str == text\n\n    await client.close()\n\n\n@pytest.mark.parametrize(\n    (\"hostname\", \"expected_host\"),\n    [(\"127.0.0.1\", \"127.0.0.1\"), (\"localhost\", \"127.0.0.1\"), (\"::1\", \"::1\")],\n)\nasync def test_test_server_hostnames(\n    hostname: str, expected_host: str, loop: asyncio.AbstractEventLoop\n) -> None:\n    app = _create_example_app()\n    server = TestServer(app, host=hostname, loop=loop)\n    async with server:\n        pass\n    assert server.host == expected_host\n\n\n@pytest.mark.parametrize(\"test_server_cls\", [TestServer, RawTestServer])\nasync def test_base_test_server_socket_factory(\n    test_server_cls: type, app: web.Application, loop: asyncio.AbstractEventLoop\n) -> None:\n    factory_called = False\n\n    def factory(host: str, port: int, family: socket.AddressFamily) -> socket.socket:\n        nonlocal factory_called\n        factory_called = True\n        return get_port_socket(host, port, family)\n\n    server = test_server_cls(app, loop=loop, socket_factory=factory)\n    async with server:\n        pass\n\n    assert factory_called\n"
  },
  {
    "path": "tests/test_tracing.py",
    "content": "import sys\nfrom types import SimpleNamespace\nfrom typing import Any\nfrom unittest import mock\nfrom unittest.mock import Mock\n\nimport pytest\nfrom aiosignal import Signal\n\nfrom aiohttp import ClientSession\nfrom aiohttp.tracing import (\n    Trace,\n    TraceConfig,\n    TraceConnectionCreateEndParams,\n    TraceConnectionCreateStartParams,\n    TraceConnectionQueuedEndParams,\n    TraceConnectionQueuedStartParams,\n    TraceConnectionReuseconnParams,\n    TraceDnsCacheHitParams,\n    TraceDnsCacheMissParams,\n    TraceDnsResolveHostEndParams,\n    TraceDnsResolveHostStartParams,\n    TraceRequestChunkSentParams,\n    TraceRequestEndParams,\n    TraceRequestExceptionParams,\n    TraceRequestRedirectParams,\n    TraceRequestStartParams,\n    TraceResponseChunkReceivedParams,\n)\n\nif sys.version_info >= (3, 11):\n    from typing import assert_type\n\n\nclass TestTraceConfig:\n    def test_trace_config_ctx_default(self) -> None:\n        trace_config = TraceConfig()\n        assert isinstance(trace_config.trace_config_ctx(), SimpleNamespace)\n        if sys.version_info >= (3, 11):\n            assert_type(\n                trace_config.on_request_chunk_sent,\n                Signal[ClientSession, SimpleNamespace, TraceRequestChunkSentParams],\n            )\n\n    def test_trace_config_ctx_factory(self) -> None:\n        trace_config = TraceConfig(trace_config_ctx_factory=dict)\n        assert isinstance(trace_config.trace_config_ctx(), dict)\n        if sys.version_info >= (3, 11):\n            assert_type(\n                trace_config.on_request_start,\n                Signal[ClientSession, dict[str, Any], TraceRequestStartParams],\n            )\n\n    def test_trace_config_ctx_request_ctx(self) -> None:\n        trace_request_ctx = Mock()\n        trace_config = TraceConfig()\n        trace_config_ctx = trace_config.trace_config_ctx(\n            trace_request_ctx=trace_request_ctx\n        )\n        assert trace_config_ctx.trace_request_ctx is trace_request_ctx\n\n    def test_trace_config_ctx_custom_class(self) -> None:\n        \"\"\"Custom class instances should be accepted as trace_request_ctx (#10753).\"\"\"\n\n        class MyContext:\n            def __init__(self, request_id: int) -> None:\n                self.request_id = request_id\n\n        ctx = MyContext(request_id=42)\n        trace_config = TraceConfig()\n        trace_config_ctx = trace_config.trace_config_ctx(trace_request_ctx=ctx)\n        assert trace_config_ctx.trace_request_ctx is ctx\n        assert trace_config_ctx.trace_request_ctx.request_id == 42\n\n    def test_freeze(self) -> None:\n        trace_config = TraceConfig()\n        trace_config.freeze()\n\n        assert trace_config.on_request_start.frozen\n        assert trace_config.on_request_chunk_sent.frozen\n        assert trace_config.on_response_chunk_received.frozen\n        assert trace_config.on_request_end.frozen\n        assert trace_config.on_request_exception.frozen\n        assert trace_config.on_request_redirect.frozen\n        assert trace_config.on_connection_queued_start.frozen\n        assert trace_config.on_connection_queued_end.frozen\n        assert trace_config.on_connection_create_start.frozen\n        assert trace_config.on_connection_create_end.frozen\n        assert trace_config.on_connection_reuseconn.frozen\n        assert trace_config.on_dns_resolvehost_start.frozen\n        assert trace_config.on_dns_resolvehost_end.frozen\n        assert trace_config.on_dns_cache_hit.frozen\n        assert trace_config.on_dns_cache_miss.frozen\n        assert trace_config.on_request_headers_sent.frozen\n\n\nclass TestTrace:\n    @pytest.mark.parametrize(\n        \"signal,params,param_obj\",\n        [\n            (\"request_start\", (Mock(), Mock(), Mock()), TraceRequestStartParams),\n            (\n                \"request_chunk_sent\",\n                (Mock(), Mock(), Mock()),\n                TraceRequestChunkSentParams,\n            ),\n            (\n                \"response_chunk_received\",\n                (Mock(), Mock(), Mock()),\n                TraceResponseChunkReceivedParams,\n            ),\n            (\"request_end\", (Mock(), Mock(), Mock(), Mock()), TraceRequestEndParams),\n            (\n                \"request_exception\",\n                (Mock(), Mock(), Mock(), Mock()),\n                TraceRequestExceptionParams,\n            ),\n            (\n                \"request_redirect\",\n                (Mock(), Mock(), Mock(), Mock()),\n                TraceRequestRedirectParams,\n            ),\n            (\"connection_queued_start\", (), TraceConnectionQueuedStartParams),\n            (\"connection_queued_end\", (), TraceConnectionQueuedEndParams),\n            (\"connection_create_start\", (), TraceConnectionCreateStartParams),\n            (\"connection_create_end\", (), TraceConnectionCreateEndParams),\n            (\"connection_reuseconn\", (), TraceConnectionReuseconnParams),\n            (\"dns_resolvehost_start\", (Mock(),), TraceDnsResolveHostStartParams),\n            (\"dns_resolvehost_end\", (Mock(),), TraceDnsResolveHostEndParams),\n            (\"dns_cache_hit\", (Mock(),), TraceDnsCacheHitParams),\n            (\"dns_cache_miss\", (Mock(),), TraceDnsCacheMissParams),\n        ],\n    )\n    async def test_send(  # type: ignore[misc]\n        self, signal: str, params: tuple[Mock, ...], param_obj: Any\n    ) -> None:\n        session = Mock()\n        trace_request_ctx = Mock()\n        callback = mock.AsyncMock()\n\n        trace_config = TraceConfig()\n        getattr(trace_config, \"on_%s\" % signal).append(callback)\n        trace_config.freeze()\n        trace = Trace(\n            session,\n            trace_config,\n            trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx),\n        )\n        await getattr(trace, \"send_%s\" % signal)(*params)\n\n        callback.assert_called_once_with(\n            session,\n            SimpleNamespace(trace_request_ctx=trace_request_ctx),\n            param_obj(*params),\n        )\n"
  },
  {
    "path": "tests/test_urldispatch.py",
    "content": "import asyncio\nimport pathlib\nimport platform\nimport re\nfrom collections.abc import (\n    Awaitable,\n    Callable,\n    Container,\n    Iterable,\n    Mapping,\n    MutableMapping,\n    Sized,\n)\nfrom functools import partial\nfrom typing import NoReturn\nfrom urllib.parse import quote, unquote\n\nimport pytest\nfrom yarl import URL\n\nimport aiohttp\nfrom aiohttp import hdrs, web\nfrom aiohttp.test_utils import make_mocked_request\nfrom aiohttp.web_urldispatcher import (\n    PATH_SEP,\n    Domain,\n    MaskDomain,\n    SystemRoute,\n    _default_expect_handler,\n)\n\n\ndef make_handler() -> Callable[[web.Request], Awaitable[NoReturn]]:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    return handler\n\n\ndef make_partial_handler() -> Callable[[web.Request], Awaitable[NoReturn]]:\n    async def handler(a: int, request: web.Request) -> NoReturn:\n        assert False\n\n    return partial(handler, 5)\n\n\n@pytest.fixture\ndef app() -> web.Application:\n    return web.Application()\n\n\n@pytest.fixture\ndef router(app: web.Application) -> web.UrlDispatcher:\n    return app.router\n\n\n@pytest.fixture\ndef fill_routes(router: web.UrlDispatcher) -> Callable[[], list[web.AbstractRoute]]:\n    def go() -> list[web.AbstractRoute]:\n        route1 = router.add_route(\"GET\", \"/plain\", make_handler())\n        route2 = router.add_route(\"GET\", \"/variable/{name}\", make_handler())\n        resource = router.add_static(\"/static\", pathlib.Path(aiohttp.__file__).parent)\n        return [route1, route2] + list(resource)\n\n    return go\n\n\ndef test_register_uncommon_http_methods(router: web.UrlDispatcher) -> None:\n    uncommon_http_methods = {\n        \"PROPFIND\",\n        \"PROPPATCH\",\n        \"COPY\",\n        \"LOCK\",\n        \"UNLOCK\",\n        \"MOVE\",\n        \"SUBSCRIBE\",\n        \"UNSUBSCRIBE\",\n        \"NOTIFY\",\n    }\n\n    for method in uncommon_http_methods:\n        router.add_route(method, \"/handler/to/path\", make_handler())\n\n\nasync def test_add_partial_handler(router: web.UrlDispatcher) -> None:\n    handler = make_partial_handler()\n    router.add_get(\"/handler/to/path\", handler)\n\n\nasync def test_add_sync_handler(router: web.UrlDispatcher) -> None:\n    def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    with pytest.raises(TypeError):\n        router.add_get(\"/handler/to/path\", handler)\n\n\nasync def test_add_route_root(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/\", handler)\n    req = make_mocked_request(\"GET\", \"/\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert 0 == len(info)\n    assert handler is info.handler\n    assert info.route.name is None\n\n\nasync def test_add_route_simple(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/handler/to/path\", handler)\n    req = make_mocked_request(\"GET\", \"/handler/to/path\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert 0 == len(info)\n    assert handler is info.handler\n    assert info.route.name is None\n\n\nasync def test_add_with_matchdict(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/handler/{to}\", handler)\n    req = make_mocked_request(\"GET\", \"/handler/tail\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert {\"to\": \"tail\"} == info\n    assert handler is info.handler\n    assert info.route.name is None\n\n\nasync def test_add_with_matchdict_with_colon(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/handler/{to}\", handler)\n    req = make_mocked_request(\"GET\", \"/handler/1:2:3\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert {\"to\": \"1:2:3\"} == info\n    assert handler is info.handler\n    assert info.route.name is None\n\n\nasync def test_add_route_with_add_get_shortcut(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_get(\"/handler/to/path\", handler)\n    req = make_mocked_request(\"GET\", \"/handler/to/path\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert 0 == len(info)\n    assert handler is info.handler\n    assert info.route.name is None\n\n\nasync def test_add_route_with_add_post_shortcut(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_post(\"/handler/to/path\", handler)\n    req = make_mocked_request(\"POST\", \"/handler/to/path\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert 0 == len(info)\n    assert handler is info.handler\n    assert info.route.name is None\n\n\nasync def test_add_route_with_add_put_shortcut(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_put(\"/handler/to/path\", handler)\n    req = make_mocked_request(\"PUT\", \"/handler/to/path\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert 0 == len(info)\n    assert handler is info.handler\n    assert info.route.name is None\n\n\nasync def test_add_route_with_add_patch_shortcut(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_patch(\"/handler/to/path\", handler)\n    req = make_mocked_request(\"PATCH\", \"/handler/to/path\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert 0 == len(info)\n    assert handler is info.handler\n    assert info.route.name is None\n\n\nasync def test_add_route_with_add_delete_shortcut(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_delete(\"/handler/to/path\", handler)\n    req = make_mocked_request(\"DELETE\", \"/handler/to/path\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert 0 == len(info)\n    assert handler is info.handler\n    assert info.route.name is None\n\n\nasync def test_add_route_with_add_head_shortcut(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_head(\"/handler/to/path\", handler)\n    req = make_mocked_request(\"HEAD\", \"/handler/to/path\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert 0 == len(info)\n    assert handler is info.handler\n    assert info.route.name is None\n\n\nasync def test_add_with_name(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/handler/to/path\", handler, name=\"name\")\n    req = make_mocked_request(\"GET\", \"/handler/to/path\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert \"name\" == info.route.name\n\n\nasync def test_add_with_tailing_slash(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/handler/to/path/\", handler)\n    req = make_mocked_request(\"GET\", \"/handler/to/path/\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert {} == info\n    assert handler is info.handler\n\n\ndef test_add_invalid_path(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    with pytest.raises(ValueError):\n        router.add_route(\"GET\", \"/{/\", handler)\n\n\ndef test_add_url_invalid1(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    with pytest.raises(ValueError):\n        router.add_route(\"post\", \"/post/{id\", handler)\n\n\ndef test_add_url_invalid2(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    with pytest.raises(ValueError):\n        router.add_route(\"post\", \"/post/{id{}}\", handler)\n\n\ndef test_add_url_invalid3(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    with pytest.raises(ValueError):\n        router.add_route(\"post\", \"/post/{id{}\", handler)\n\n\ndef test_add_url_invalid4(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    with pytest.raises(ValueError):\n        router.add_route(\"post\", '/post/{id\"}', handler)\n\n\nasync def test_add_url_escaping(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/+$\", handler)\n\n    req = make_mocked_request(\"GET\", \"/+$\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert handler is info.handler\n\n\nasync def test_any_method(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    route = router.add_route(hdrs.METH_ANY, \"/\", handler)\n\n    req = make_mocked_request(\"GET\", \"/\")\n    info1 = await router.resolve(req)\n    assert info1 is not None\n    assert route is info1.route\n\n    req = make_mocked_request(\"POST\", \"/\")\n    info2 = await router.resolve(req)\n    assert info2 is not None\n\n    assert info1.route is info2.route\n\n\nasync def test_any_method_appears_in_routes(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    route = router.add_route(hdrs.METH_ANY, \"/\", handler)\n    assert route in router.routes()\n\n\nasync def test_match_second_result_in_table(router: web.UrlDispatcher) -> None:\n    handler1 = make_handler()\n    handler2 = make_handler()\n    router.add_route(\"GET\", \"/h1\", handler1)\n    router.add_route(\"POST\", \"/h2\", handler2)\n    req = make_mocked_request(\"POST\", \"/h2\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert {} == info\n    assert handler2 is info.handler\n\n\nasync def test_raise_method_not_allowed(router: web.UrlDispatcher) -> None:\n    handler1 = make_handler()\n    handler2 = make_handler()\n    router.add_route(\"GET\", \"/\", handler1)\n    router.add_route(\"POST\", \"/\", handler2)\n    req = make_mocked_request(\"PUT\", \"/\")\n\n    match_info = await router.resolve(req)\n    assert isinstance(match_info.route, SystemRoute)\n    assert {} == match_info\n\n    with pytest.raises(web.HTTPMethodNotAllowed) as ctx:\n        await match_info.handler(req)\n\n    exc = ctx.value\n    assert \"PUT\" == exc.method\n    assert 405 == exc.status\n    assert {\"POST\", \"GET\"} == exc.allowed_methods\n\n\nasync def test_raise_method_not_found(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/a\", handler)\n    req = make_mocked_request(\"GET\", \"/b\")\n\n    match_info = await router.resolve(req)\n    assert isinstance(match_info.route, SystemRoute)\n    assert {} == match_info\n\n    with pytest.raises(web.HTTPNotFound) as ctx:\n        await match_info.handler(req)\n\n    exc = ctx.value\n    assert 404 == exc.status\n\n\ndef test_double_add_url_with_the_same_name(router: web.UrlDispatcher) -> None:\n    handler1 = make_handler()\n    handler2 = make_handler()\n    router.add_route(\"GET\", \"/get\", handler1, name=\"name\")\n\n    with pytest.raises(ValueError) as ctx:\n        router.add_route(\"GET\", \"/get_other\", handler2, name=\"name\")\n    assert str(ctx.value).startswith(\"Duplicate 'name', already handled by\")\n\n\ndef test_route_plain(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    route = router.add_route(\"GET\", \"/get\", handler, name=\"name\")\n    route2 = next(iter(router[\"name\"]))\n    url = route2.url_for()\n    assert \"/get\" == str(url)\n    assert route is route2\n\n\ndef test_route_unknown_route_name(router: web.UrlDispatcher) -> None:\n    with pytest.raises(KeyError):\n        router[\"unknown\"]\n\n\ndef test_route_dynamic(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    route = router.add_route(\"GET\", \"/get/{name}\", handler, name=\"name\")\n\n    route2 = next(iter(router[\"name\"]))\n    url = route2.url_for(name=\"John\")\n    assert \"/get/John\" == str(url)\n    assert route is route2\n\n\ndef test_add_static_path_checks(\n    router: web.UrlDispatcher, tmp_path: pathlib.Path\n) -> None:\n    \"\"\"Test that static paths must exist and be directories.\"\"\"\n    with pytest.raises(ValueError, match=\"does not exist\"):\n        router.add_static(\"/\", tmp_path / \"does-not-exist\")\n    with pytest.raises(ValueError, match=\"is not a directory\"):\n        router.add_static(\"/\", __file__)\n\n\ndef test_add_static_path_resolution(router: web.UrlDispatcher) -> None:\n    \"\"\"Test that static paths are expanded and absolute.\"\"\"\n    res = router.add_static(\"/\", \"~/..\")\n    directory = str(res.get_info()[\"directory\"])\n    assert directory == str(pathlib.Path.home().resolve(strict=True).parent)\n\n\ndef test_add_static(router: web.UrlDispatcher) -> None:\n    resource = router.add_static(\n        \"/st\", pathlib.Path(aiohttp.__file__).parent, name=\"static\"\n    )\n    assert router[\"static\"] is resource\n    url = resource.url_for(filename=\"/dir/a.txt\")\n    assert \"/st/dir/a.txt\" == str(url)\n    assert len(resource) == 2\n\n\ndef test_add_static_append_version(router: web.UrlDispatcher) -> None:\n    resource = router.add_static(\"/st\", pathlib.Path(__file__).parent, name=\"static\")\n    url = resource.url_for(filename=\"/data.unknown_mime_type\", append_version=True)\n    expect_url = (\n        \"/st/data.unknown_mime_type?v=aUsn8CHEhhszc81d28QmlcBW0KQpfS2F4trgQKhOYd8%3D\"\n    )\n    assert expect_url == str(url)\n\n\ndef test_add_static_append_version_set_from_constructor(\n    router: web.UrlDispatcher,\n) -> None:\n    resource = router.add_static(\n        \"/st\", pathlib.Path(__file__).parent, append_version=True, name=\"static\"\n    )\n    url = resource.url_for(filename=\"/data.unknown_mime_type\")\n    expect_url = (\n        \"/st/data.unknown_mime_type?v=aUsn8CHEhhszc81d28QmlcBW0KQpfS2F4trgQKhOYd8%3D\"\n    )\n    assert expect_url == str(url)\n\n\ndef test_add_static_append_version_override_constructor(\n    router: web.UrlDispatcher,\n) -> None:\n    resource = router.add_static(\n        \"/st\", pathlib.Path(__file__).parent, append_version=True, name=\"static\"\n    )\n    url = resource.url_for(filename=\"/data.unknown_mime_type\", append_version=False)\n    expect_url = \"/st/data.unknown_mime_type\"\n    assert expect_url == str(url)\n\n\ndef test_add_static_append_version_filename_without_slash(\n    router: web.UrlDispatcher,\n) -> None:\n    resource = router.add_static(\"/st\", pathlib.Path(__file__).parent, name=\"static\")\n    url = resource.url_for(filename=\"data.unknown_mime_type\", append_version=True)\n    expect_url = (\n        \"/st/data.unknown_mime_type?v=aUsn8CHEhhszc81d28QmlcBW0KQpfS2F4trgQKhOYd8%3D\"\n    )\n    assert expect_url == str(url)\n\n\ndef test_add_static_append_version_non_exists_file(router: web.UrlDispatcher) -> None:\n    resource = router.add_static(\"/st\", pathlib.Path(__file__).parent, name=\"static\")\n    url = resource.url_for(filename=\"/non_exists_file\", append_version=True)\n    assert \"/st/non_exists_file\" == str(url)\n\n\ndef test_add_static_append_version_non_exists_file_without_slash(\n    router: web.UrlDispatcher,\n) -> None:\n    resource = router.add_static(\"/st\", pathlib.Path(__file__).parent, name=\"static\")\n    url = resource.url_for(filename=\"non_exists_file\", append_version=True)\n    assert \"/st/non_exists_file\" == str(url)\n\n\ndef test_add_static_append_version_follow_symlink(\n    router: web.UrlDispatcher, tmp_path: pathlib.Path\n) -> None:\n    # Tests the access to a symlink, in static folder with apeend_version\n    symlink_path = tmp_path / \"append_version_symlink\"\n    symlink_target_path = pathlib.Path(__file__).parent\n    pathlib.Path(str(symlink_path)).symlink_to(str(symlink_target_path), True)\n\n    # Register global static route:\n    resource = router.add_static(\n        \"/st\", str(tmp_path), follow_symlinks=True, append_version=True\n    )\n\n    url = resource.url_for(filename=\"/append_version_symlink/data.unknown_mime_type\")\n\n    expect_url = (\n        \"/st/append_version_symlink/data.unknown_mime_type?\"\n        \"v=aUsn8CHEhhszc81d28QmlcBW0KQpfS2F4trgQKhOYd8%3D\"\n    )\n    assert expect_url == str(url)\n\n\ndef test_add_static_append_version_not_follow_symlink(\n    router: web.UrlDispatcher, tmp_path: pathlib.Path\n) -> None:\n    # Tests the access to a symlink, in static folder with apeend_version\n\n    symlink_path = tmp_path / \"append_version_symlink\"\n    symlink_target_path = pathlib.Path(__file__).parent\n\n    pathlib.Path(str(symlink_path)).symlink_to(str(symlink_target_path), True)\n\n    # Register global static route:\n    resource = router.add_static(\n        \"/st\", str(tmp_path), follow_symlinks=False, append_version=True\n    )\n\n    filename = \"/append_version_symlink/data.unknown_mime_type\"\n    url = resource.url_for(filename=filename)\n    assert \"/st/append_version_symlink/data.unknown_mime_type\" == str(url)\n\n\ndef test_add_static_quoting(router: web.UrlDispatcher) -> None:\n    resource = router.add_static(\n        \"/пре %2Fфикс\", pathlib.Path(aiohttp.__file__).parent, name=\"static\"\n    )\n    assert router[\"static\"] is resource\n    url = resource.url_for(filename=\"/1 2/файл%2F.txt\")\n    assert url.path == \"/пре /фикс/1 2/файл%2F.txt\"\n    assert str(url) == (\n        \"/%D0%BF%D1%80%D0%B5%20%2F%D1%84%D0%B8%D0%BA%D1%81\"\n        \"/1%202/%D1%84%D0%B0%D0%B9%D0%BB%252F.txt\"\n    )\n    assert len(resource) == 2\n\n\ndef test_plain_not_match(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/get/path\", handler, name=\"name\")\n    route = router[\"name\"]\n    assert isinstance(route, web.Resource)\n    assert route._match(\"/another/path\") is None\n\n\ndef test_dynamic_not_match(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/get/{name}\", handler, name=\"name\")\n    route = router[\"name\"]\n    assert isinstance(route, web.Resource)\n    assert route._match(\"/another/path\") is None\n\n\nasync def test_static_not_match(router: web.UrlDispatcher) -> None:\n    router.add_static(\"/pre\", pathlib.Path(aiohttp.__file__).parent, name=\"name\")\n    resource = router[\"name\"]\n    ret = await resource.resolve(make_mocked_request(\"GET\", \"/another/path\"))\n    assert (None, set()) == ret\n\n\nasync def test_add_static_access_resources(router: web.UrlDispatcher) -> None:\n    \"\"\"Test accessing resource._routes externally.\n\n    aiohttp-cors accesses the resource._routes, this test ensures that this\n    continues to work.\n    \"\"\"\n    # https://github.com/aio-libs/aiohttp-cors/blob/38c6c17bffc805e46baccd7be1b4fd8c69d95dc3/aiohttp_cors/urldispatcher_router_adapter.py#L187\n    resource = router.add_static(\n        \"/st\", pathlib.Path(aiohttp.__file__).parent, name=\"static\"\n    )\n    resource._routes[hdrs.METH_OPTIONS] = resource._routes[hdrs.METH_GET]\n    resource._allowed_methods.add(hdrs.METH_OPTIONS)\n    mapping, allowed_methods = await resource.resolve(\n        make_mocked_request(\"OPTIONS\", \"/st/path\")\n    )\n    assert mapping is not None\n    assert allowed_methods == {hdrs.METH_GET, hdrs.METH_OPTIONS, hdrs.METH_HEAD}\n\n\nasync def test_add_static_set_options_route(router: web.UrlDispatcher) -> None:\n    \"\"\"Ensure set_options_route works as expected.\"\"\"\n    resource = router.add_static(\n        \"/st\", pathlib.Path(aiohttp.__file__).parent, name=\"static\"\n    )\n\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    resource.set_options_route(handler)\n    mapping, allowed_methods = await resource.resolve(\n        make_mocked_request(\"OPTIONS\", \"/st/path\")\n    )\n    assert mapping is not None\n    assert allowed_methods == {hdrs.METH_GET, hdrs.METH_OPTIONS, hdrs.METH_HEAD}\n\n\ndef test_dynamic_with_trailing_slash(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/get/{name}/\", handler, name=\"name\")\n    route = router[\"name\"]\n    assert isinstance(route, web.Resource)\n    assert {\"name\": \"John\"} == route._match(\"/get/John/\")\n\n\ndef test_len(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/get1\", handler, name=\"name1\")\n    router.add_route(\"GET\", \"/get2\", handler, name=\"name2\")\n    assert 2 == len(router)\n\n\ndef test_iter(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/get1\", handler, name=\"name1\")\n    router.add_route(\"GET\", \"/get2\", handler, name=\"name2\")\n    assert {\"name1\", \"name2\"} == set(iter(router))\n\n\ndef test_contains(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/get1\", handler, name=\"name1\")\n    router.add_route(\"GET\", \"/get2\", handler, name=\"name2\")\n    assert \"name1\" in router\n    assert \"name3\" not in router\n\n\ndef test_static_repr(router: web.UrlDispatcher) -> None:\n    router.add_static(\"/get\", pathlib.Path(aiohttp.__file__).parent, name=\"name\")\n    assert repr(router[\"name\"]).startswith(\"<StaticResource 'name' /get\")\n\n\ndef test_static_adds_slash(router: web.UrlDispatcher) -> None:\n    route = router.add_static(\"/prefix\", pathlib.Path(aiohttp.__file__).parent)\n    assert \"/prefix\" == route._prefix\n\n\ndef test_static_remove_trailing_slash(router: web.UrlDispatcher) -> None:\n    route = router.add_static(\"/prefix/\", pathlib.Path(aiohttp.__file__).parent)\n    assert \"/prefix\" == route._prefix\n\n\n@pytest.mark.parametrize(\n    \"pattern,url,expected\",\n    (\n        (r\"{to:\\d+}\", r\"1234\", {\"to\": \"1234\"}),\n        (\"{name}.html\", \"test.html\", {\"name\": \"test\"}),\n        (r\"{fn:\\w+ \\d+}\", \"abc 123\", {\"fn\": \"abc 123\"}),\n        (r\"{fn:\\w+\\s\\d+}\", \"abc 123\", {\"fn\": \"abc 123\"}),\n    ),\n)\nasync def test_add_route_with_re(\n    router: web.UrlDispatcher, pattern: str, url: str, expected: dict[str, str]\n) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", f\"/handler/{pattern}\", handler)\n    req = make_mocked_request(\"GET\", f\"/handler/{url}\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert info == expected\n\n\nasync def test_add_route_with_re_and_slashes(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", r\"/handler/{to:[^/]+/?}\", handler)\n    req = make_mocked_request(\"GET\", \"/handler/1234/\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert {\"to\": \"1234/\"} == info\n\n    router.add_route(\"GET\", r\"/handler/{to:.+}\", handler)\n    req = make_mocked_request(\"GET\", \"/handler/1234/5/6/7\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert {\"to\": \"1234/5/6/7\"} == info\n\n\nasync def test_add_route_with_re_not_match(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", r\"/handler/{to:\\d+}\", handler)\n\n    req = make_mocked_request(\"GET\", \"/handler/tail\")\n    match_info = await router.resolve(req)\n    assert isinstance(match_info.route, SystemRoute)\n    assert {} == match_info\n    with pytest.raises(web.HTTPNotFound):\n        await match_info.handler(req)\n\n\nasync def test_add_route_with_re_including_slashes(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", r\"/handler/{to:.+}/tail\", handler)\n    req = make_mocked_request(\"GET\", \"/handler/re/with/slashes/tail\")\n    info = await router.resolve(req)\n    assert info is not None\n    assert {\"to\": \"re/with/slashes\"} == info\n\n\ndef test_add_route_with_invalid_re(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    with pytest.raises(ValueError) as ctx:\n        router.add_route(\"GET\", r\"/handler/{to:+++}\", handler)\n    s = str(ctx.value)\n    assert s.startswith(\n        \"Bad pattern '\"\n        + PATH_SEP\n        + \"handler\"\n        + PATH_SEP\n        + \"(?P<to>+++)': nothing to repeat\"\n    )\n    assert ctx.value.__cause__ is None\n\n\ndef test_route_dynamic_with_regex_spec(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    route = router.add_route(\"GET\", r\"/get/{num:^\\d+}\", handler, name=\"name\")\n\n    url = route.url_for(num=\"123\")\n    assert \"/get/123\" == str(url)\n\n\ndef test_route_dynamic_with_regex_spec_and_trailing_slash(\n    router: web.UrlDispatcher,\n) -> None:\n    handler = make_handler()\n    route = router.add_route(\"GET\", r\"/get/{num:^\\d+}/\", handler, name=\"name\")\n\n    url = route.url_for(num=\"123\")\n    assert \"/get/123/\" == str(url)\n\n\ndef test_route_dynamic_with_regex(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    route = router.add_route(\"GET\", r\"/{one}/{two:.+}\", handler)\n\n    url = route.url_for(one=\"1\", two=\"2\")\n    assert \"/1/2\" == str(url)\n\n\ndef test_route_dynamic_quoting(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    route = router.add_route(\"GET\", r\"/пре %2Fфикс/{arg}\", handler)\n\n    url = route.url_for(arg=\"1 2/текст%2F\")\n    assert url.path == \"/пре /фикс/1 2/текст%2F\"\n    assert str(url) == (\n        \"/%D0%BF%D1%80%D0%B5%20%2F%D1%84%D0%B8%D0%BA%D1%81\"\n        \"/1%202/%D1%82%D0%B5%D0%BA%D1%81%D1%82%252F\"\n    )\n\n\nasync def test_regular_match_info(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/get/{name}\", handler)\n\n    req = make_mocked_request(\"GET\", \"/get/john\")\n    match_info = await router.resolve(req)\n    assert {\"name\": \"john\"} == match_info\n    assert repr(match_info).startswith(\"<MatchInfo {'name': 'john'}:\")\n    assert \"<Dynamic\" in repr(match_info)\n\n\nasync def test_match_info_with_plus(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/get/{version}\", handler)\n\n    req = make_mocked_request(\"GET\", \"/get/1.0+test\")\n    match_info = await router.resolve(req)\n    assert {\"version\": \"1.0+test\"} == match_info\n\n\nasync def test_not_found_repr(router: web.UrlDispatcher) -> None:\n    req = make_mocked_request(\"POST\", \"/path/to\")\n    match_info = await router.resolve(req)\n    assert \"<MatchInfoError 404: Not Found>\" == repr(match_info)\n\n\nasync def test_not_allowed_repr(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/path/to\", handler)\n\n    handler2 = make_handler()\n    router.add_route(\"POST\", \"/path/to\", handler2)\n\n    req = make_mocked_request(\"PUT\", \"/path/to\")\n    match_info = await router.resolve(req)\n    assert \"<MatchInfoError 405: Method Not Allowed>\" == repr(match_info)\n\n\ndef test_default_expect_handler(router: web.UrlDispatcher) -> None:\n    route = router.add_route(\"GET\", \"/\", make_handler())\n    assert route._expect_handler is _default_expect_handler\n\n\ndef test_custom_expect_handler_plain(router: web.UrlDispatcher) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    route = router.add_route(\"GET\", \"/\", make_handler(), expect_handler=handler)\n    assert route._expect_handler is handler\n    assert isinstance(route, web.ResourceRoute)\n\n\ndef test_custom_expect_handler_dynamic(router: web.UrlDispatcher) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    route = router.add_route(\n        \"GET\", \"/get/{name}\", make_handler(), expect_handler=handler\n    )\n    assert route._expect_handler is handler\n    assert isinstance(route, web.ResourceRoute)\n\n\ndef test_expect_handler_non_coroutine(router: web.UrlDispatcher) -> None:\n    def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    with pytest.raises(AssertionError):\n        router.add_route(\"GET\", \"/\", make_handler(), expect_handler=handler)\n\n\nasync def test_dynamic_match_non_ascii(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/{var}\", handler)\n    req = make_mocked_request(\n        \"GET\", \"/%D1%80%D1%83%D1%81%20%D1%82%D0%B5%D0%BA%D1%81%D1%82\"\n    )\n    match_info = await router.resolve(req)\n    assert {\"var\": \"рус текст\"} == match_info\n\n\nasync def test_dynamic_match_with_static_part(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/{name}.html\", handler)\n    req = make_mocked_request(\"GET\", \"/file.html\")\n    match_info = await router.resolve(req)\n    assert {\"name\": \"file\"} == match_info\n\n\nasync def test_dynamic_match_two_part2(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/{name}.{ext}\", handler)\n    req = make_mocked_request(\"GET\", \"/file.html\")\n    match_info = await router.resolve(req)\n    assert {\"name\": \"file\", \"ext\": \"html\"} == match_info\n\n\nasync def test_dynamic_match_unquoted_path(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/{path}/{subpath}\", handler)\n    resource_id = \"my%2Fpath%7Cwith%21some%25strange%24characters\"\n    req = make_mocked_request(\"GET\", f\"/path/{resource_id}\")\n    match_info = await router.resolve(req)\n    assert match_info == {\"path\": \"path\", \"subpath\": unquote(resource_id)}\n\n\nasync def test_dynamic_match_double_quoted_path(router: web.UrlDispatcher) -> None:\n    \"\"\"Verify that double-quoted path is unquoted only once.\"\"\"\n    handler = make_handler()\n    router.add_route(\"GET\", \"/{path}/{subpath}\", handler)\n    resource_id = quote(\"my/path|with!some%strange$characters\", safe=\"\")\n    double_quoted_resource_id = quote(resource_id, safe=\"\")\n    req = make_mocked_request(\"GET\", f\"/path/{double_quoted_resource_id}\")\n    match_info = await router.resolve(req)\n    assert match_info == {\"path\": \"path\", \"subpath\": resource_id}\n\n\ndef test_add_route_not_started_with_slash(router: web.UrlDispatcher) -> None:\n    with pytest.raises(ValueError):\n        handler = make_handler()\n        router.add_route(\"GET\", \"invalid_path\", handler)\n\n\ndef test_add_route_invalid_method(router: web.UrlDispatcher) -> None:\n    sample_bad_methods = {\n        \"BAD METHOD\",\n        \"B@D_METHOD\",\n        \"[BAD_METHOD]\",\n        \"{BAD_METHOD}\",\n        \"(BAD_METHOD)\",\n        \"B?D_METHOD\",\n    }\n\n    for bad_method in sample_bad_methods:\n        with pytest.raises(ValueError):\n            handler = make_handler()\n            router.add_route(bad_method, \"/path\", handler)\n\n\ndef test_routes_view_len(\n    router: web.UrlDispatcher, fill_routes: Callable[[], list[web.AbstractRoute]]\n) -> None:\n    fill_routes()\n    assert 4 == len(router.routes())\n\n\ndef test_routes_view_iter(\n    router: web.UrlDispatcher, fill_routes: Callable[[], list[web.AbstractRoute]]\n) -> None:\n    routes = fill_routes()\n    assert list(routes) == list(router.routes())\n\n\ndef test_routes_view_contains(\n    router: web.UrlDispatcher, fill_routes: Callable[[], list[web.AbstractRoute]]\n) -> None:\n    routes = fill_routes()\n    for route in routes:\n        assert route in router.routes()\n\n\ndef test_routes_abc(router: web.UrlDispatcher) -> None:\n    assert isinstance(router.routes(), Sized)\n    assert isinstance(router.routes(), Iterable)\n    assert isinstance(router.routes(), Container)\n\n\ndef test_named_resources_abc(router: web.UrlDispatcher) -> None:\n    assert isinstance(router.named_resources(), Mapping)\n    assert not isinstance(router.named_resources(), MutableMapping)\n\n\ndef test_named_resources(router: web.UrlDispatcher) -> None:\n    route1 = router.add_route(\"GET\", \"/plain\", make_handler(), name=\"route1\")\n    route2 = router.add_route(\"GET\", \"/variable/{name}\", make_handler(), name=\"route2\")\n    route3 = router.add_static(\n        \"/static\", pathlib.Path(aiohttp.__file__).parent, name=\"route3\"\n    )\n    names = {route1.name, route2.name, route3.name}\n\n    assert 3 == len(router.named_resources())\n\n    for name in names:\n        assert name is not None\n        assert name in router.named_resources()\n        assert isinstance(router.named_resources()[name], web.AbstractResource)\n\n\ndef test_resource_iter(router: web.UrlDispatcher) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    resource = router.add_resource(\"/path\")\n    r1 = resource.add_route(\"GET\", handler)\n    r2 = resource.add_route(\"POST\", handler)\n    assert 2 == len(resource)\n    assert [r1, r2] == list(resource)\n\n\ndef test_view_route(router: web.UrlDispatcher) -> None:\n    resource = router.add_resource(\"/path\")\n\n    route = resource.add_route(\"*\", web.View)\n    assert web.View is route.handler\n\n\ndef test_resource_route_match(router: web.UrlDispatcher) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    resource = router.add_resource(\"/path\")\n    route = resource.add_route(\"GET\", handler)\n    assert isinstance(route.resource, web.Resource)\n    assert {} == route.resource._match(\"/path\")\n\n\ndef test_error_on_double_route_adding(router: web.UrlDispatcher) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    resource = router.add_resource(\"/path\")\n\n    resource.add_route(\"GET\", handler)\n    with pytest.raises(RuntimeError):\n        resource.add_route(\"GET\", handler)\n\n\ndef test_error_on_adding_route_after_wildcard(router: web.UrlDispatcher) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    resource = router.add_resource(\"/path\")\n\n    resource.add_route(\"*\", handler)\n    with pytest.raises(RuntimeError):\n        resource.add_route(\"GET\", handler)\n\n\nasync def test_http_exception_is_none_when_resolved(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/\", handler)\n    req = make_mocked_request(\"GET\", \"/\")\n    info = await router.resolve(req)\n    assert info.http_exception is None\n\n\nasync def test_http_exception_is_not_none_when_not_resolved(\n    router: web.UrlDispatcher,\n) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/\", handler)\n    req = make_mocked_request(\"GET\", \"/abc\")\n    info = await router.resolve(req)\n    assert info.http_exception is not None\n    assert info.http_exception.status == 404\n\n\nasync def test_match_info_get_info_plain(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/\", handler)\n    req = make_mocked_request(\"GET\", \"/\")\n    info = await router.resolve(req)\n    assert info.get_info() == {\"path\": \"/\"}\n\n\nasync def test_match_info_get_info_dynamic(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/{a}\", handler)\n    req = make_mocked_request(\"GET\", \"/value\")\n    info = await router.resolve(req)\n    assert info.get_info() == {\n        \"pattern\": re.compile(PATH_SEP + \"(?P<a>[^{}/]+)\"),\n        \"formatter\": \"/{a}\",\n    }\n\n\nasync def test_match_info_get_info_dynamic2(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/{a}/{b}\", handler)\n    req = make_mocked_request(\"GET\", \"/path/to\")\n    info = await router.resolve(req)\n    assert info.get_info() == {\n        \"pattern\": re.compile(\n            PATH_SEP + \"(?P<a>[^{}/]+)\" + PATH_SEP + \"(?P<b>[^{}/]+)\"\n        ),\n        \"formatter\": \"/{a}/{b}\",\n    }\n\n\ndef test_static_resource_get_info(router: web.UrlDispatcher) -> None:\n    directory = pathlib.Path(aiohttp.__file__).parent.resolve()\n    resource = router.add_static(\"/st\", directory)\n    info = resource.get_info()\n    assert len(info) == 3\n    assert info[\"directory\"] == directory\n    assert info[\"prefix\"] == \"/st\"\n    assert all([type(r) is web.ResourceRoute for r in info[\"routes\"].values()])\n\n\nasync def test_system_route_get_info(router: web.UrlDispatcher) -> None:\n    handler = make_handler()\n    router.add_route(\"GET\", \"/\", handler)\n    req = make_mocked_request(\"GET\", \"/abc\")\n    info = await router.resolve(req)\n    assert info.get_info()[\"http_exception\"].status == 404\n\n\ndef test_resources_view_len(router: web.UrlDispatcher) -> None:\n    router.add_resource(\"/plain\")\n    router.add_resource(\"/variable/{name}\")\n    assert 2 == len(router.resources())\n\n\ndef test_resources_view_iter(router: web.UrlDispatcher) -> None:\n    resource1 = router.add_resource(\"/plain\")\n    resource2 = router.add_resource(\"/variable/{name}\")\n    resources = [resource1, resource2]\n    assert list(resources) == list(router.resources())\n\n\ndef test_resources_view_contains(router: web.UrlDispatcher) -> None:\n    resource1 = router.add_resource(\"/plain\")\n    resource2 = router.add_resource(\"/variable/{name}\")\n    resources = [resource1, resource2]\n    for resource in resources:\n        assert resource in router.resources()\n\n\ndef test_resources_abc(router: web.UrlDispatcher) -> None:\n    assert isinstance(router.resources(), Sized)\n    assert isinstance(router.resources(), Iterable)\n    assert isinstance(router.resources(), Container)\n\n\ndef test_static_route_user_home(router: web.UrlDispatcher) -> None:\n    here = pathlib.Path(aiohttp.__file__).parent\n    try:\n        static_dir = pathlib.Path(\"~\") / here.relative_to(pathlib.Path.home())\n    except ValueError:\n        pytest.skip(\"aiohttp folder is not placed in user's HOME\")\n    route = router.add_static(\"/st\", str(static_dir))\n    assert here == route.get_info()[\"directory\"]\n\n\ndef test_static_route_points_to_file(router: web.UrlDispatcher) -> None:\n    here = pathlib.Path(aiohttp.__file__).parent / \"__init__.py\"\n    with pytest.raises(ValueError):\n        router.add_static(\"/st\", here)\n\n\nasync def test_404_for_static_resource(router: web.UrlDispatcher) -> None:\n    resource = router.add_static(\"/st\", pathlib.Path(aiohttp.__file__).parent)\n    ret = await resource.resolve(make_mocked_request(\"GET\", \"/unknown/path\"))\n    assert (None, set()) == ret\n\n\nasync def test_405_for_resource_adapter(router: web.UrlDispatcher) -> None:\n    resource = router.add_static(\"/st\", pathlib.Path(aiohttp.__file__).parent)\n    ret = await resource.resolve(make_mocked_request(\"POST\", \"/st/abc.py\"))\n    assert (None, {\"HEAD\", \"GET\"}) == ret\n\n\n@pytest.mark.skipif(platform.system() == \"Windows\", reason=\"Different path formats\")\nasync def test_static_resource_outside_traversal(router: web.UrlDispatcher) -> None:\n    \"\"\"Test relative path traversing outside root does not resolve.\"\"\"\n    static_file = pathlib.Path(aiohttp.__file__)\n    request_path = \"/st\" + \"/..\" * (len(static_file.parts) - 2) + str(static_file)\n    assert pathlib.Path(request_path).resolve() == static_file\n\n    resource = router.add_static(\"/st\", static_file.parent)\n    ret = await resource.resolve(make_mocked_request(\"GET\", request_path))\n    # Should not resolve, otherwise filesystem information may be leaked.\n    assert (None, set()) == ret\n\n\nasync def test_check_allowed_method_for_found_resource(\n    router: web.UrlDispatcher,\n) -> None:\n    handler = make_handler()\n    resource = router.add_resource(\"/\")\n    resource.add_route(\"GET\", handler)\n    ret = await resource.resolve(make_mocked_request(\"GET\", \"/\"))\n    assert ret[0] is not None\n    assert {\"GET\"} == ret[1]\n\n\ndef test_url_for_in_static_resource(router: web.UrlDispatcher) -> None:\n    resource = router.add_static(\"/static\", pathlib.Path(aiohttp.__file__).parent)\n    assert URL(\"/static/file.txt\") == resource.url_for(filename=\"file.txt\")\n\n\ndef test_url_for_in_static_resource_pathlib(router: web.UrlDispatcher) -> None:\n    resource = router.add_static(\"/static\", pathlib.Path(aiohttp.__file__).parent)\n    assert URL(\"/static/file.txt\") == resource.url_for(\n        filename=pathlib.Path(\"file.txt\")\n    )\n\n\ndef test_url_for_in_resource_route(router: web.UrlDispatcher) -> None:\n    route = router.add_route(\"GET\", \"/get/{name}\", make_handler(), name=\"name\")\n    assert URL(\"/get/John\") == route.url_for(name=\"John\")\n\n\ndef test_subapp_get_info(app: web.Application) -> None:\n    subapp = web.Application()\n    resource = subapp.add_subapp(\"/pre\", subapp)\n    assert resource.get_info() == {\"prefix\": \"/pre\", \"app\": subapp}\n\n\n@pytest.mark.parametrize(\n    \"domain,error\",\n    [\n        (None, TypeError),\n        (\"\", ValueError),\n        (\"http://dom\", ValueError),\n        (\"*.example.com\", ValueError),\n        (\"example$com\", ValueError),\n    ],\n)\ndef test_domain_validation_error(domain: str | None, error: type[Exception]) -> None:\n    with pytest.raises(error):\n        Domain(domain)  # type: ignore[arg-type]\n\n\ndef test_domain_valid() -> None:\n    assert Domain(\"example.com:81\").canonical == \"example.com:81\"\n    assert MaskDomain(\"*.example.com\").canonical == r\".*\\.example\\.com\"\n    assert Domain(\"пуни.код\").canonical == \"xn--h1ajfq.xn--d1alm\"\n\n\n@pytest.mark.parametrize(\n    \"a,b,result\",\n    [\n        (\"example.com\", \"example.com\", True),\n        (\"example.com:81\", \"example.com:81\", True),\n        (\"example.com:81\", \"example.com\", False),\n        (\"пуникод\", \"xn--d1ahgkhc2a\", True),\n        (\"*.example.com\", \"jpg.example.com\", True),\n        (\"*.example.com\", \"a.example.com\", True),\n        (\"*.example.com\", \"example.com\", False),\n    ],\n)\ndef test_match_domain(a: str, b: str, result: bool) -> None:\n    if \"*\" in a:\n        rule: Domain = MaskDomain(a)\n    else:\n        rule = Domain(a)\n    assert rule.match_domain(b) is result\n\n\ndef test_add_subapp_errors(app: web.Application) -> None:\n    with pytest.raises(TypeError):\n        app.add_subapp(1, web.Application())  # type: ignore[arg-type]\n\n\ndef test_subapp_rule_resource(app: web.Application) -> None:\n    subapp = web.Application()\n    subapp.router.add_get(\"/\", make_handler())\n    rule = Domain(\"example.com\")\n    assert rule.get_info() == {\"domain\": \"example.com\"}\n    resource = app.add_domain(\"example.com\", subapp)\n    assert resource.canonical == \"example.com\"\n    assert resource.get_info() == {\"rule\": resource._rule, \"app\": subapp}\n    resource.add_prefix(\"/a\")\n    resource.raw_match(\"/b\")\n    assert len(resource)\n    assert list(resource)\n    assert repr(resource).startswith(\"<MatchedSubAppResource\")\n    with pytest.raises(RuntimeError):\n        resource.url_for()\n\n\nasync def test_add_domain_not_str(\n    app: web.Application, loop: asyncio.AbstractEventLoop\n) -> None:\n    app = web.Application()\n    with pytest.raises(TypeError):\n        app.add_domain(1, app)  # type: ignore[arg-type]\n\n\nasync def test_add_domain(\n    app: web.Application, loop: asyncio.AbstractEventLoop\n) -> None:\n    subapp1 = web.Application()\n    h1 = make_handler()\n    subapp1.router.add_get(\"/\", h1)\n    app.add_domain(\"example.com\", subapp1)\n\n    subapp2 = web.Application()\n    h2 = make_handler()\n    subapp2.router.add_get(\"/\", h2)\n    app.add_domain(\"*.example.com\", subapp2)\n\n    subapp3 = web.Application()\n    h3 = make_handler()\n    subapp3.router.add_get(\"/\", h3)\n    app.add_domain(\"*\", subapp3)\n\n    request = make_mocked_request(\"GET\", \"/\", {\"host\": \"example.com\"})\n    match_info = await app.router.resolve(request)\n    assert match_info.route.handler is h1\n\n    request = make_mocked_request(\"GET\", \"/\", {\"host\": \"a.example.com\"})\n    match_info = await app.router.resolve(request)\n    assert match_info.route.handler is h2\n\n    request = make_mocked_request(\"GET\", \"/\", {\"host\": \"example2.com\"})\n    match_info = await app.router.resolve(request)\n    assert match_info.route.handler is h3\n\n    request = make_mocked_request(\"POST\", \"/\", {\"host\": \"example.com\"})\n    match_info = await app.router.resolve(request)\n    assert isinstance(match_info.http_exception, web.HTTPMethodNotAllowed)\n\n\ndef test_subapp_url_for(app: web.Application) -> None:\n    subapp = web.Application()\n    resource = app.add_subapp(\"/pre\", subapp)\n    with pytest.raises(RuntimeError):\n        resource.url_for()\n\n\ndef test_subapp_repr(app: web.Application) -> None:\n    subapp = web.Application()\n    resource = app.add_subapp(\"/pre\", subapp)\n    assert repr(resource).startswith(\"<PrefixedSubAppResource /pre -> <Application\")\n\n\ndef test_subapp_len(app: web.Application) -> None:\n    subapp = web.Application()\n    subapp.router.add_get(\"/\", make_handler(), allow_head=False)\n    subapp.router.add_post(\"/\", make_handler())\n    resource = app.add_subapp(\"/pre\", subapp)\n    assert len(resource) == 2\n\n\ndef test_subapp_iter(app: web.Application) -> None:\n    subapp = web.Application()\n    r1 = subapp.router.add_get(\"/\", make_handler(), allow_head=False)\n    r2 = subapp.router.add_post(\"/\", make_handler())\n    resource = app.add_subapp(\"/pre\", subapp)\n    assert list(resource) == [r1, r2]\n\n\n@pytest.mark.parametrize(\n    \"route_name\",\n    (\n        \"invalid name\",\n        \"class\",\n    ),\n)\ndef test_invalid_route_name(router: web.UrlDispatcher, route_name: str) -> None:\n    with pytest.raises(ValueError):\n        router.add_get(\"/\", make_handler(), name=route_name)\n\n\ndef test_frozen_router(router: web.UrlDispatcher) -> None:\n    router.freeze()\n    with pytest.raises(RuntimeError):\n        router.add_get(\"/\", make_handler())\n\n\ndef test_frozen_router_subapp(app: web.Application) -> None:\n    subapp = web.Application()\n    subapp.freeze()\n    with pytest.raises(RuntimeError):\n        app.add_subapp(\"/pre\", subapp)\n\n\ndef test_frozen_app_on_subapp(app: web.Application) -> None:\n    app.freeze()\n    subapp = web.Application()\n    with pytest.raises(RuntimeError):\n        app.add_subapp(\"/pre\", subapp)\n\n\ndef test_set_options_route(router: web.UrlDispatcher) -> None:\n    resource = router.add_static(\"/static\", pathlib.Path(aiohttp.__file__).parent)\n    assert all(r.method != \"OPTIONS\" for r in resource)\n    resource.set_options_route(make_handler())\n    assert any(r.method == \"OPTIONS\" for r in resource)\n\n    with pytest.raises(RuntimeError):\n        resource.set_options_route(make_handler())\n\n\ndef test_dynamic_url_with_name_started_from_underscore(\n    router: web.UrlDispatcher,\n) -> None:\n    route = router.add_route(\"GET\", \"/get/{_name}\", make_handler())\n    assert URL(\"/get/John\") == route.url_for(_name=\"John\")\n\n\ndef test_cannot_add_subapp_with_empty_prefix(app: web.Application) -> None:\n    subapp = web.Application()\n    with pytest.raises(ValueError):\n        app.add_subapp(\"\", subapp)\n\n\ndef test_cannot_add_subapp_with_slash_prefix(app: web.Application) -> None:\n    subapp = web.Application()\n    with pytest.raises(ValueError):\n        app.add_subapp(\"/\", subapp)\n\n\nasync def test_convert_empty_path_to_slash_on_freezing(\n    router: web.UrlDispatcher,\n) -> None:\n    handler = make_handler()\n    route = router.add_get(\"\", handler)\n    resource = route.resource\n    assert resource is not None\n    assert resource.get_info() == {\"path\": \"\"}\n    router.freeze()\n    assert resource.get_info() == {\"path\": \"/\"}\n\n\ndef test_plain_resource_canonical() -> None:\n    canonical = \"/plain/path\"\n    res = web.PlainResource(path=canonical)\n    assert res.canonical == canonical\n\n\ndef test_dynamic_resource_canonical() -> None:\n    canonicals = {\n        \"/get/{name}\": \"/get/{name}\",\n        r\"/get/{num:^\\d+}\": \"/get/{num}\",\n        r\"/handler/{to:\\d+}\": r\"/handler/{to}\",\n        r\"/{one}/{two:.+}\": r\"/{one}/{two}\",\n    }\n    for pattern, canonical in canonicals.items():\n        res = web.DynamicResource(path=pattern)\n        assert res.canonical == canonical\n\n\ndef test_static_resource_canonical() -> None:\n    prefix = \"/prefix\"\n    directory = str(pathlib.Path(aiohttp.__file__).parent)\n    canonical = prefix\n    res = web.StaticResource(prefix=prefix, directory=directory)\n    assert res.canonical == canonical\n\n\ndef test_prefixed_subapp_resource_canonical(app: web.Application) -> None:\n    canonical = \"/prefix\"\n    subapp = web.Application()\n    res = subapp.add_subapp(canonical, subapp)\n    assert res.canonical == canonical\n\n\nasync def test_prefixed_subapp_overlap(app: web.Application) -> None:\n    # Subapp should not overshadow other subapps with overlapping prefixes\n    subapp1 = web.Application()\n    handler1 = make_handler()\n    subapp1.router.add_get(\"/a\", handler1)\n    app.add_subapp(\"/s\", subapp1)\n\n    subapp2 = web.Application()\n    handler2 = make_handler()\n    subapp2.router.add_get(\"/b\", handler2)\n    app.add_subapp(\"/ss\", subapp2)\n\n    subapp3 = web.Application()\n    handler3 = make_handler()\n    subapp3.router.add_get(\"/c\", handler3)\n    app.add_subapp(\"/s/s\", subapp3)\n\n    match_info = await app.router.resolve(make_mocked_request(\"GET\", \"/s/a\"))\n    assert match_info.route.handler is handler1\n    match_info = await app.router.resolve(make_mocked_request(\"GET\", \"/ss/b\"))\n    assert match_info.route.handler is handler2\n    match_info = await app.router.resolve(make_mocked_request(\"GET\", \"/s/s/c\"))\n    assert match_info.route.handler is handler3\n\n\nasync def test_prefixed_subapp_empty_route(app: web.Application) -> None:\n    subapp = web.Application()\n    handler = make_handler()\n    subapp.router.add_get(\"\", handler)\n    app.add_subapp(\"/s\", subapp)\n\n    match_info = await app.router.resolve(make_mocked_request(\"GET\", \"/s\"))\n    assert match_info.route.handler is handler\n    match_info = await app.router.resolve(make_mocked_request(\"GET\", \"/s/\"))\n    assert \"<MatchInfoError 404: Not Found>\" == repr(match_info)\n\n\nasync def test_prefixed_subapp_root_route(app: web.Application) -> None:\n    subapp = web.Application()\n    handler = make_handler()\n    subapp.router.add_get(\"/\", handler)\n    app.add_subapp(\"/s\", subapp)\n\n    match_info = await app.router.resolve(make_mocked_request(\"GET\", \"/s/\"))\n    assert match_info.route.handler is handler\n    match_info = await app.router.resolve(make_mocked_request(\"GET\", \"/s\"))\n    assert \"<MatchInfoError 404: Not Found>\" == repr(match_info)\n"
  },
  {
    "path": "tests/test_web_app.py",
    "content": "import asyncio\nimport sys\nfrom collections.abc import AsyncIterator, Callable, Iterator\nfrom contextlib import asynccontextmanager\nfrom typing import NoReturn\nfrom unittest import mock\n\nimport pytest\n\nfrom aiohttp import log, web\nfrom aiohttp.pytest_plugin import AiohttpClient\nfrom aiohttp.typedefs import Handler\n\n\nasync def test_app_ctor() -> None:\n    app = web.Application()\n    assert app.logger is log.web_logger\n\n\ndef test_app_call() -> None:\n    app = web.Application()\n    assert app is app()\n\n\nasync def test_app_register_on_finish() -> None:\n    app = web.Application()\n    cb1 = mock.AsyncMock(return_value=None)\n    cb2 = mock.AsyncMock(return_value=None)\n    app.on_cleanup.append(cb1)\n    app.on_cleanup.append(cb2)\n    app.freeze()\n    await app.cleanup()\n    cb1.assert_called_once_with(app)\n    cb2.assert_called_once_with(app)\n\n\nasync def test_app_register_coro() -> None:\n    app = web.Application()\n    fut = asyncio.get_event_loop().create_future()\n\n    async def cb(app: web.Application) -> None:\n        await asyncio.sleep(0.001)\n        fut.set_result(123)\n\n    app.on_cleanup.append(cb)\n    app.freeze()\n    await app.cleanup()\n    assert fut.done()\n    assert 123 == fut.result()\n\n\ndef test_logging() -> None:\n    logger = mock.Mock()\n    app = web.Application()\n    app.logger = logger\n    assert app.logger is logger\n\n\nasync def test_on_shutdown() -> None:\n    app = web.Application()\n    called = False\n\n    async def on_shutdown(app_param: web.Application) -> None:\n        nonlocal called\n        assert app is app_param\n        called = True\n\n    app.on_shutdown.append(on_shutdown)\n    app.freeze()\n    await app.shutdown()\n    assert called\n\n\nasync def test_on_startup() -> None:\n    app = web.Application()\n\n    long_running1_called = False\n    long_running2_called = False\n    all_long_running_called = False\n\n    async def long_running1(app_param: web.Application) -> None:\n        nonlocal long_running1_called\n        assert app is app_param\n        long_running1_called = True\n\n    async def long_running2(app_param: web.Application) -> None:\n        nonlocal long_running2_called\n        assert app is app_param\n        long_running2_called = True\n\n    async def on_startup_all_long_running(app_param: web.Application) -> None:\n        nonlocal all_long_running_called\n        assert app is app_param\n        all_long_running_called = True\n        await asyncio.gather(long_running1(app_param), long_running2(app_param))\n\n    app.on_startup.append(on_startup_all_long_running)\n    app.freeze()\n\n    await app.startup()\n    assert long_running1_called\n    assert long_running2_called\n    assert all_long_running_called\n\n\ndef test_appkey() -> None:\n    key = web.AppKey(\"key\", str)\n    app = web.Application()\n    app[key] = \"value\"\n    assert app[key] == \"value\"\n    assert len(app) == 1\n    del app[key]\n    assert len(app) == 0\n\n\ndef test_appkey_repr_concrete() -> None:\n    key = web.AppKey(\"key\", int)\n    assert repr(key) in (\n        \"<AppKey(__channelexec__.key, type=int)>\",  # pytest-xdist\n        \"<AppKey(__main__.key, type=int)>\",\n    )\n    key2 = web.AppKey(\"key\", web.Request)\n    assert repr(key2) in (\n        # pytest-xdist:\n        \"<AppKey(__channelexec__.key, type=aiohttp.web_request.Request)>\",\n        \"<AppKey(__main__.key, type=aiohttp.web_request.Request)>\",\n    )\n\n\ndef test_appkey_repr_nonconcrete() -> None:\n    key = web.AppKey(\"key\", Iterator[int])\n    if sys.version_info < (3, 11):\n        assert repr(key) in (\n            # pytest-xdist:\n            \"<AppKey(__channelexec__.key, type=collections.abc.Iterator)>\",\n            \"<AppKey(__main__.key, type=collections.abc.Iterator)>\",\n        )\n    else:\n        assert repr(key) in (\n            # pytest-xdist:\n            \"<AppKey(__channelexec__.key, type=collections.abc.Iterator[int])>\",\n            \"<AppKey(__main__.key, type=collections.abc.Iterator[int])>\",\n        )\n\n\ndef test_appkey_repr_annotated() -> None:\n    key = web.AppKey[Iterator[int]](\"key\")\n    if sys.version_info < (3, 11):\n        assert repr(key) in (\n            # pytest-xdist:\n            \"<AppKey(__channelexec__.key, type=collections.abc.Iterator)>\",\n            \"<AppKey(__main__.key, type=collections.abc.Iterator)>\",\n        )\n    else:\n        assert repr(key) in (\n            # pytest-xdist:\n            \"<AppKey(__channelexec__.key, type=collections.abc.Iterator[int])>\",\n            \"<AppKey(__main__.key, type=collections.abc.Iterator[int])>\",\n        )\n\n\ndef test_app_str_keys() -> None:\n    app = web.Application()\n    with pytest.warns(\n        UserWarning, match=r\"web_advanced\\.html#application-s-config\"\n    ) as checker:\n        app[\"key\"] = \"value\"\n        # Check that the error is emitted at the call site (stacklevel=2)\n        assert checker[0].filename == __file__\n    assert app[\"key\"] == \"value\"\n\n\ndef test_app_get() -> None:\n    key = web.AppKey(\"key\", int)\n    app = web.Application()\n    assert app.get(key, \"foo\") == \"foo\"\n    app[key] = 5\n    assert app.get(key, \"foo\") == 5\n\n\ndef test_app_freeze() -> None:\n    app = web.Application()\n    subapp = mock.Mock()\n    subapp._middlewares = ()\n    app._subapps.append(subapp)\n\n    app.freeze()\n    assert subapp.freeze.called\n\n    app.freeze()\n    assert len(subapp.freeze.call_args_list) == 1\n\n\ndef test_equality() -> None:\n    app1 = web.Application()\n    app2 = web.Application()\n\n    assert app1 == app1\n    assert app1 != app2\n\n\ndef test_app_run_middlewares() -> None:\n    root = web.Application()\n    sub = web.Application()\n    root.add_subapp(\"/sub\", sub)\n    root.freeze()\n    assert root._run_middlewares is False\n\n    async def middleware(request: web.Request, handler: Handler) -> web.StreamResponse:\n        assert False\n\n    root = web.Application(middlewares=[middleware])\n    sub = web.Application()\n    root.add_subapp(\"/sub\", sub)\n    root.freeze()\n    assert root._run_middlewares is True\n\n    root = web.Application()\n    sub = web.Application(middlewares=[middleware])\n    root.add_subapp(\"/sub\", sub)\n    root.freeze()\n    assert root._run_middlewares is True\n\n\ndef test_subapp_pre_frozen_after_adding() -> None:\n    app = web.Application()\n    subapp = web.Application()\n\n    app.add_subapp(\"/prefix\", subapp)\n    assert subapp.pre_frozen\n    assert not subapp.frozen\n\n\ndef test_app_inheritance() -> None:\n    with pytest.raises(TypeError):\n\n        class A(web.Application):  # type: ignore[misc]\n            pass\n\n\ndef test_app_custom_attr() -> None:\n    app = web.Application()\n    with pytest.raises(AttributeError):\n        app.custom = None  # type: ignore[attr-defined]\n\n\nasync def test_cleanup_ctx() -> None:\n    app = web.Application()\n    out = []\n\n    def f(num: int) -> Callable[[web.Application], AsyncIterator[None]]:\n        async def inner(app: web.Application) -> AsyncIterator[None]:\n            out.append(\"pre_\" + str(num))\n            yield None\n            out.append(\"post_\" + str(num))\n\n        return inner\n\n    app.cleanup_ctx.append(f(1))\n    app.cleanup_ctx.append(f(2))\n    app.freeze()\n    await app.startup()\n    assert out == [\"pre_1\", \"pre_2\"]\n    await app.cleanup()\n    assert out == [\"pre_1\", \"pre_2\", \"post_2\", \"post_1\"]\n\n\nasync def test_cleanup_ctx_exception_on_startup() -> None:\n    app = web.Application()\n    out = []\n\n    exc = Exception(\"fail\")\n\n    def f(\n        num: int, fail: bool = False\n    ) -> Callable[[web.Application], AsyncIterator[None]]:\n        async def inner(app: web.Application) -> AsyncIterator[None]:\n            out.append(\"pre_\" + str(num))\n            if fail:\n                raise exc\n            yield None\n            out.append(\"post_\" + str(num))\n\n        return inner\n\n    app.cleanup_ctx.append(f(1))\n    app.cleanup_ctx.append(f(2, True))\n    app.cleanup_ctx.append(f(3))\n    app.freeze()\n    with pytest.raises(Exception) as ctx:\n        await app.startup()\n    assert ctx.value is exc\n    assert out == [\"pre_1\", \"pre_2\"]\n    await app.cleanup()\n    assert out == [\"pre_1\", \"pre_2\", \"post_1\"]\n\n\nasync def test_cleanup_ctx_exception_on_cleanup() -> None:\n    app = web.Application()\n    out = []\n\n    exc = Exception(\"fail\")\n\n    def f(\n        num: int, fail: bool = False\n    ) -> Callable[[web.Application], AsyncIterator[None]]:\n        async def inner(app: web.Application) -> AsyncIterator[None]:\n            out.append(\"pre_\" + str(num))\n            yield None\n            out.append(\"post_\" + str(num))\n            if fail:\n                raise exc\n\n        return inner\n\n    app.cleanup_ctx.append(f(1))\n    app.cleanup_ctx.append(f(2, True))\n    app.cleanup_ctx.append(f(3))\n    app.freeze()\n    await app.startup()\n    assert out == [\"pre_1\", \"pre_2\", \"pre_3\"]\n    with pytest.raises(Exception) as ctx:\n        await app.cleanup()\n    assert ctx.value is exc\n    assert out == [\"pre_1\", \"pre_2\", \"pre_3\", \"post_3\", \"post_2\", \"post_1\"]\n\n\nasync def test_cleanup_ctx_cleanup_after_exception() -> None:\n    app = web.Application()\n    ctx_state = None\n\n    async def success_ctx(app: web.Application) -> AsyncIterator[None]:\n        nonlocal ctx_state\n        ctx_state = \"START\"\n        yield\n        ctx_state = \"CLEAN\"\n\n    async def fail_ctx(app: web.Application) -> AsyncIterator[NoReturn]:\n        raise Exception()\n        yield  # type: ignore[unreachable]  # pragma: no cover\n\n    app.cleanup_ctx.append(success_ctx)\n    app.cleanup_ctx.append(fail_ctx)\n    runner = web.AppRunner(app)\n    try:\n        with pytest.raises(Exception):\n            await runner.setup()\n    finally:\n        await runner.cleanup()\n\n    assert ctx_state == \"CLEAN\"\n\n\n@pytest.mark.parametrize(\"exc_cls\", (Exception, asyncio.CancelledError))\nasync def test_cleanup_ctx_exception_on_cleanup_multiple(\n    exc_cls: type[BaseException],\n) -> None:\n    app = web.Application()\n    out = []\n\n    def f(\n        num: int, fail: bool = False\n    ) -> Callable[[web.Application], AsyncIterator[None]]:\n        async def inner(app: web.Application) -> AsyncIterator[None]:\n            out.append(\"pre_\" + str(num))\n            yield None\n            out.append(\"post_\" + str(num))\n            if fail:\n                raise exc_cls(\"fail_\" + str(num))\n\n        return inner\n\n    app.cleanup_ctx.append(f(1))\n    app.cleanup_ctx.append(f(2, True))\n    app.cleanup_ctx.append(f(3, True))\n    app.freeze()\n    await app.startup()\n    assert out == [\"pre_1\", \"pre_2\", \"pre_3\"]\n    with pytest.raises(web.CleanupError) as ctx:\n        await app.cleanup()\n    exc = ctx.value\n    assert len(exc.exceptions) == 2\n    assert str(exc.exceptions[0]) == \"fail_3\"\n    assert str(exc.exceptions[1]) == \"fail_2\"\n    assert out == [\"pre_1\", \"pre_2\", \"pre_3\", \"post_3\", \"post_2\", \"post_1\"]\n\n\nasync def test_cleanup_ctx_multiple_yields() -> None:\n    app = web.Application()\n    out = []\n\n    def f(num: int) -> Callable[[web.Application], AsyncIterator[None]]:\n        async def inner(app: web.Application) -> AsyncIterator[None]:\n            out.append(\"pre_\" + str(num))\n            yield None\n            out.append(\"post_\" + str(num))\n            yield None\n\n        return inner\n\n    app.cleanup_ctx.append(f(1))\n    app.freeze()\n    await app.startup()\n    assert out == [\"pre_1\"]\n    with pytest.raises(RuntimeError):\n        await app.cleanup()\n    assert out == [\"pre_1\", \"post_1\"]\n\n\nasync def test_cleanup_ctx_with_async_generator_and_asynccontextmanager() -> None:\n    entered = []\n\n    async def gen_ctx(app: web.Application) -> AsyncIterator[None]:\n        entered.append(\"enter-gen\")\n        try:\n            yield\n        finally:\n            entered.append(\"exit-gen\")\n\n    @asynccontextmanager\n    async def cm_ctx(app: web.Application) -> AsyncIterator[None]:\n        entered.append(\"enter-cm\")\n        try:\n            yield\n        finally:\n            entered.append(\"exit-cm\")\n\n    app = web.Application()\n    app.cleanup_ctx.append(gen_ctx)\n    app.cleanup_ctx.append(cm_ctx)\n    app.freeze()\n    await app.startup()\n    assert \"enter-gen\" in entered and \"enter-cm\" in entered\n    await app.cleanup()\n    assert \"exit-gen\" in entered and \"exit-cm\" in entered\n\n\nasync def test_cleanup_ctx_exception_in_cm_exit() -> None:\n    app = web.Application()\n\n    exc = RuntimeError(\"exit failed\")\n\n    @asynccontextmanager\n    async def failing_exit_ctx(app: web.Application) -> AsyncIterator[None]:\n        yield\n        raise exc\n\n    app.cleanup_ctx.append(failing_exit_ctx)\n    app.freeze()\n    await app.startup()\n    with pytest.raises(RuntimeError) as ctx:\n        await app.cleanup()\n    assert ctx.value is exc\n\n\nasync def test_cleanup_ctx_mixed_with_exception_in_cm_exit() -> None:\n    app = web.Application()\n    out = []\n\n    async def working_gen(app: web.Application) -> AsyncIterator[None]:\n        out.append(\"pre_gen\")\n        yield\n        out.append(\"post_gen\")\n\n    exc = RuntimeError(\"cm exit failed\")\n\n    @asynccontextmanager\n    async def failing_exit_cm(app: web.Application) -> AsyncIterator[None]:\n        out.append(\"pre_cm\")\n        yield\n        out.append(\"post_cm\")\n        raise exc\n\n    app.cleanup_ctx.append(working_gen)\n    app.cleanup_ctx.append(failing_exit_cm)\n    app.freeze()\n    await app.startup()\n    with pytest.raises(RuntimeError) as ctx:\n        await app.cleanup()\n    assert ctx.value is exc\n    assert out == [\"pre_gen\", \"pre_cm\", \"post_cm\", \"post_gen\"]\n\n\nasync def test_subapp_chained_config_dict_visibility(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    key1 = web.AppKey(\"key1\", str)\n    key2 = web.AppKey(\"key2\", str)\n\n    async def main_handler(request: web.Request) -> web.Response:\n        assert request.config_dict[key1] == \"val1\"\n        assert key2 not in request.config_dict\n        return web.Response(status=200)\n\n    root = web.Application()\n    root[key1] = \"val1\"\n    root.add_routes([web.get(\"/\", main_handler)])\n\n    async def sub_handler(request: web.Request) -> web.Response:\n        assert request.config_dict[key1] == \"val1\"\n        assert request.config_dict[key2] == \"val2\"\n        return web.Response(status=201)\n\n    sub = web.Application()\n    sub[key2] = \"val2\"\n    sub.add_routes([web.get(\"/\", sub_handler)])\n    root.add_subapp(\"/sub\", sub)\n\n    client = await aiohttp_client(root)\n\n    resp = await client.get(\"/\")\n    assert resp.status == 200\n    resp = await client.get(\"/sub/\")\n    assert resp.status == 201\n\n\nasync def test_subapp_chained_config_dict_overriding(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    key = web.AppKey(\"key\", str)\n\n    async def main_handler(request: web.Request) -> web.Response:\n        assert request.config_dict[key] == \"val1\"\n        return web.Response(status=200)\n\n    root = web.Application()\n    root[key] = \"val1\"\n    root.add_routes([web.get(\"/\", main_handler)])\n\n    async def sub_handler(request: web.Request) -> web.Response:\n        assert request.config_dict[key] == \"val2\"\n        return web.Response(status=201)\n\n    sub = web.Application()\n    sub[key] = \"val2\"\n    sub.add_routes([web.get(\"/\", sub_handler)])\n    root.add_subapp(\"/sub\", sub)\n\n    client = await aiohttp_client(root)\n\n    resp = await client.get(\"/\")\n    assert resp.status == 200\n    resp = await client.get(\"/sub/\")\n    assert resp.status == 201\n\n\nasync def test_subapp_on_startup(aiohttp_client: AiohttpClient) -> None:\n    subapp = web.Application()\n    startup = web.AppKey(\"startup\", bool)\n    cleanup = web.AppKey(\"cleanup\", bool)\n\n    startup_called = False\n\n    async def on_startup(app: web.Application) -> None:\n        nonlocal startup_called\n        startup_called = True\n        app[startup] = True\n\n    subapp.on_startup.append(on_startup)\n\n    ctx_pre_called = False\n    ctx_post_called = False\n\n    async def cleanup_ctx(app: web.Application) -> AsyncIterator[None]:\n        nonlocal ctx_pre_called, ctx_post_called\n        ctx_pre_called = True\n        app[cleanup] = True\n        yield None\n        ctx_post_called = True\n\n    subapp.cleanup_ctx.append(cleanup_ctx)\n\n    shutdown_called = False\n\n    async def on_shutdown(app: web.Application) -> None:\n        nonlocal shutdown_called\n        shutdown_called = True\n\n    subapp.on_shutdown.append(on_shutdown)\n\n    cleanup_called = False\n\n    async def on_cleanup(app: web.Application) -> None:\n        nonlocal cleanup_called\n        cleanup_called = True\n\n    subapp.on_cleanup.append(on_cleanup)\n\n    app = web.Application()\n\n    app.add_subapp(\"/subapp\", subapp)\n\n    assert not startup_called\n    assert not ctx_pre_called\n    assert not ctx_post_called\n    assert not shutdown_called\n    assert not cleanup_called\n\n    assert subapp.on_startup.frozen\n    assert subapp.cleanup_ctx.frozen\n    assert subapp.on_shutdown.frozen\n    assert subapp.on_cleanup.frozen\n    assert subapp.router.frozen\n\n    client = await aiohttp_client(app)\n\n    assert startup_called\n    assert ctx_pre_called  # type: ignore[unreachable]\n    assert not ctx_post_called\n    assert not shutdown_called\n    assert not cleanup_called\n\n    await client.close()\n\n    assert startup_called\n    assert ctx_pre_called\n    assert ctx_post_called\n    assert shutdown_called\n    assert cleanup_called\n\n\n@pytest.mark.filterwarnings(r\"ignore:.*web\\.AppKey:UserWarning\")\ndef test_app_iter() -> None:\n    app = web.Application()\n    b = web.AppKey(\"b\", str)\n    c = web.AppKey(\"c\", str)\n    app[\"a\"] = \"0\"\n    app[b] = \"1\"\n    app[c] = \"2\"\n    app[\"d\"] = \"4\"\n    assert sorted(list(app)) == [b, c, \"a\", \"d\"]\n\n\ndef test_app_forbid_nonslot_attr() -> None:\n    app = web.Application()\n    with pytest.raises(AttributeError):\n        app.unknow_attr  # type: ignore[attr-defined]\n    with pytest.raises(AttributeError):\n        app.unknow_attr = 1  # type: ignore[attr-defined]\n\n\ndef test_forbid_changing_frozen_app() -> None:\n    app = web.Application()\n    app.freeze()\n    with pytest.raises(RuntimeError):\n        app[\"key\"] = \"value\"\n\n\ndef test_app_boolean() -> None:\n    app = web.Application()\n    assert app\n"
  },
  {
    "path": "tests/test_web_cli.py",
    "content": "import sys\nfrom unittest import mock\n\nimport pytest\nfrom pytest_mock import MockerFixture\n\nfrom aiohttp import web\n\n\ndef test_entry_func_empty(mocker: MockerFixture) -> None:\n    error = mocker.patch(\"aiohttp.web.ArgumentParser.error\", side_effect=SystemExit)\n    argv = [\"\"]\n\n    with pytest.raises(SystemExit):\n        web.main(argv)\n\n    error.assert_called_with(\"'entry-func' not in 'module:function' syntax\")\n\n\ndef test_entry_func_only_module(mocker: MockerFixture) -> None:\n    argv = [\"test\"]\n    error = mocker.patch(\"aiohttp.web.ArgumentParser.error\", side_effect=SystemExit)\n\n    with pytest.raises(SystemExit):\n        web.main(argv)\n\n    error.assert_called_with(\"'entry-func' not in 'module:function' syntax\")\n\n\ndef test_entry_func_only_function(mocker: MockerFixture) -> None:\n    argv = [\":test\"]\n    error = mocker.patch(\"aiohttp.web.ArgumentParser.error\", side_effect=SystemExit)\n\n    with pytest.raises(SystemExit):\n        web.main(argv)\n\n    error.assert_called_with(\"'entry-func' not in 'module:function' syntax\")\n\n\ndef test_entry_func_only_separator(mocker: MockerFixture) -> None:\n    argv = [\":\"]\n    error = mocker.patch(\"aiohttp.web.ArgumentParser.error\", side_effect=SystemExit)\n\n    with pytest.raises(SystemExit):\n        web.main(argv)\n\n    error.assert_called_with(\"'entry-func' not in 'module:function' syntax\")\n\n\ndef test_entry_func_relative_module(mocker: MockerFixture) -> None:\n    argv = [\".a.b:c\"]\n\n    error = mocker.patch(\"aiohttp.web.ArgumentParser.error\", side_effect=SystemExit)\n    with pytest.raises(SystemExit):\n        web.main(argv)\n\n    error.assert_called_with(\"relative module names not supported\")\n\n\ndef test_entry_func_non_existent_module(mocker: MockerFixture) -> None:\n    argv = [\"alpha.beta:func\"]\n\n    mocker.patch(\"aiohttp.web.import_module\", side_effect=ImportError(\"Test Error\"))\n    error = mocker.patch(\"aiohttp.web.ArgumentParser.error\", side_effect=SystemExit)\n\n    with pytest.raises(SystemExit):\n        web.main(argv)\n\n    error.assert_called_with(\"unable to import alpha.beta: Test Error\")\n\n\ndef test_entry_func_non_existent_attribute(mocker: MockerFixture) -> None:\n    argv = [\"alpha.beta:func\"]\n    import_module = mocker.patch(\"aiohttp.web.import_module\")\n    error = mocker.patch(\"aiohttp.web.ArgumentParser.error\", side_effect=SystemExit)\n    module = import_module(\"alpha.beta\")\n    del module.func\n\n    with pytest.raises(SystemExit):\n        web.main(argv)\n\n    error.assert_called_with(\n        \"module {!r} has no attribute {!r}\".format(\"alpha.beta\", \"func\")\n    )\n\n\n@pytest.mark.skipif(sys.platform.startswith(\"win32\"), reason=\"Windows not Unix\")\ndef test_path_no_host(mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch) -> None:\n    argv = \"--path=test_path.sock alpha.beta:func\".split()\n    mocker.patch(\"aiohttp.web.import_module\")\n\n    run_app = mocker.patch(\"aiohttp.web.run_app\")\n    with pytest.raises(SystemExit):\n        web.main(argv)\n\n    run_app.assert_called_with(mock.ANY, path=\"test_path.sock\", host=None, port=None)\n\n\n@pytest.mark.skipif(sys.platform.startswith(\"win32\"), reason=\"Windows not Unix\")\ndef test_path_and_host(mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch) -> None:\n    argv = \"--path=test_path.sock --host=localhost --port=8000 alpha.beta:func\".split()\n    mocker.patch(\"aiohttp.web.import_module\")\n\n    run_app = mocker.patch(\"aiohttp.web.run_app\")\n    with pytest.raises(SystemExit):\n        web.main(argv)\n\n    run_app.assert_called_with(\n        mock.ANY, path=\"test_path.sock\", host=\"localhost\", port=8000\n    )\n\n\ndef test_path_when_unsupported(\n    mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch\n) -> None:\n    argv = \"--path=test_path.sock alpha.beta:func\".split()\n    mocker.patch(\"aiohttp.web.import_module\")\n    monkeypatch.delattr(\"socket.AF_UNIX\", raising=False)\n\n    error = mocker.patch(\"aiohttp.web.ArgumentParser.error\", side_effect=SystemExit)\n    with pytest.raises(SystemExit):\n        web.main(argv)\n\n    error.assert_called_with(\n        \"file system paths not supported by your operating environment\"\n    )\n\n\ndef test_entry_func_call(mocker: MockerFixture) -> None:\n    mocker.patch(\"aiohttp.web.run_app\")\n    import_module = mocker.patch(\"aiohttp.web.import_module\")\n    argv = (\n        \"-H testhost -P 6666 --extra-optional-eins alpha.beta:func \"\n        \"--extra-optional-zwei extra positional args\"\n    ).split()\n    module = import_module(\"alpha.beta\")\n\n    with pytest.raises(SystemExit):\n        web.main(argv)\n\n    module.func.assert_called_with(\n        (\"--extra-optional-eins --extra-optional-zwei extra positional args\").split()\n    )\n\n\ndef test_running_application(mocker: MockerFixture) -> None:\n    run_app = mocker.patch(\"aiohttp.web.run_app\")\n    import_module = mocker.patch(\"aiohttp.web.import_module\")\n    exit = mocker.patch(\"aiohttp.web.ArgumentParser.exit\", side_effect=SystemExit)\n    argv = (\n        \"-H testhost -P 6666 --extra-optional-eins alpha.beta:func \"\n        \"--extra-optional-zwei extra positional args\"\n    ).split()\n    module = import_module(\"alpha.beta\")\n    app = module.func()\n\n    with pytest.raises(SystemExit):\n        web.main(argv)\n\n    run_app.assert_called_with(app, host=\"testhost\", port=6666, path=None)\n    exit.assert_called_with(message=\"Stopped\\n\")\n"
  },
  {
    "path": "tests/test_web_exceptions.py",
    "content": "import collections\nimport pickle\nfrom collections.abc import Mapping\nfrom traceback import format_exception\nfrom typing import NoReturn\n\nimport pytest\nfrom yarl import URL\n\nfrom aiohttp import web\nfrom aiohttp.pytest_plugin import AiohttpClient\n\n\ndef test_all_http_exceptions_exported() -> None:\n    assert \"HTTPException\" in web.__all__\n    for name in dir(web):\n        if name.startswith(\"_\"):\n            continue\n        obj = getattr(web, name)\n        if isinstance(obj, type) and issubclass(obj, web.HTTPException):\n            assert name in web.__all__\n\n\nasync def test_ctor() -> None:\n    resp = web.HTTPOk()\n    assert resp.text == \"200: OK\"\n    compare: Mapping[str, str] = {\"Content-Type\": \"text/plain\"}\n    assert resp.headers == compare\n    assert resp.reason == \"OK\"\n    assert resp.status == 200\n    assert bool(resp)\n\n\nasync def test_ctor_with_headers() -> None:\n    resp = web.HTTPOk(headers={\"X-Custom\": \"value\"})\n    assert resp.text == \"200: OK\"\n    compare: Mapping[str, str] = {\"Content-Type\": \"text/plain\", \"X-Custom\": \"value\"}\n    assert resp.headers == compare\n    assert resp.reason == \"OK\"\n    assert resp.status == 200\n\n\nasync def test_ctor_content_type() -> None:\n    resp = web.HTTPOk(text=\"text\", content_type=\"custom\")\n    assert resp.text == \"text\"\n    compare: Mapping[str, str] = {\"Content-Type\": \"custom\"}\n    assert resp.headers == compare\n    assert resp.reason == \"OK\"\n    assert resp.status == 200\n    assert bool(resp)\n\n\nasync def test_ctor_content_type_without_text() -> None:\n    with pytest.deprecated_call(\n        match=r\"^content_type without text is deprecated since \"\n        r\"4\\.0 and scheduled for removal in 5\\.0 \\(#3462\\)$\",\n    ):\n        resp = web.HTTPResetContent(content_type=\"custom\")\n    assert resp.text is None\n    compare: Mapping[str, str] = {\"Content-Type\": \"custom\"}\n    assert resp.headers == compare\n    assert resp.reason == \"Reset Content\"\n    assert resp.status == 205\n    assert bool(resp)\n\n\nasync def test_ctor_text_for_empty_body() -> None:\n    with pytest.deprecated_call(\n        match=r\"^text argument is deprecated for HTTP status 205 since \"\n        r\"4\\.0 and scheduled for removal in 5\\.0 \\(#3462\\),the \"\n        r\"response should be provided without a body$\",\n    ):\n        resp = web.HTTPResetContent(text=\"text\")\n    assert resp.text == \"text\"\n    compare: Mapping[str, str] = {\"Content-Type\": \"text/plain\"}\n    assert resp.headers == compare\n    assert resp.reason == \"Reset Content\"\n    assert resp.status == 205\n\n\ndef test_terminal_classes_has_status_code() -> None:\n    terminals = set()\n    for name in dir(web):\n        obj = getattr(web, name)\n        if isinstance(obj, type) and issubclass(obj, web.HTTPException):\n            terminals.add(obj)\n\n    dup = frozenset(terminals)\n    for cls1 in dup:\n        for cls2 in dup:\n            if cls1 in cls2.__bases__:\n                terminals.discard(cls1)\n\n    for cls in terminals:\n        assert cls.status_code is not None\n    codes = collections.Counter(cls.status_code for cls in terminals)\n    assert None not in codes\n    assert 1 == codes.most_common(1)[0][1]\n\n\ndef test_with_text() -> None:\n    resp = web.HTTPNotFound(text=\"Page not found\")\n    assert 404 == resp.status\n    assert \"Page not found\" == resp.text\n    assert \"text/plain\" == resp.headers[\"Content-Type\"]\n\n\ndef test_default_text() -> None:\n    resp = web.HTTPOk()\n    assert \"200: OK\" == resp.text\n\n\ndef test_empty_text_204() -> None:\n    resp = web.HTTPNoContent()\n    assert resp.text is None\n\n\ndef test_empty_text_205() -> None:\n    resp = web.HTTPResetContent()\n    assert resp.text is None\n\n\ndef test_empty_text_304() -> None:\n    resp = web.HTTPNoContent()\n    resp.text is None\n\n\ndef test_no_link_451() -> None:\n    with pytest.raises(TypeError):\n        web.HTTPUnavailableForLegalReasons()  # type: ignore[call-arg]\n\n\ndef test_link_none_451() -> None:\n    resp = web.HTTPUnavailableForLegalReasons(link=None)\n    assert resp.link is None\n    assert \"Link\" not in resp.headers\n\n\ndef test_link_empty_451() -> None:\n    resp = web.HTTPUnavailableForLegalReasons(link=\"\")\n    assert resp.link is None\n    assert \"Link\" not in resp.headers\n\n\ndef test_link_str_451() -> None:\n    resp = web.HTTPUnavailableForLegalReasons(link=\"http://warning.or.kr/\")\n    assert resp.link == URL(\"http://warning.or.kr/\")\n    assert resp.headers[\"Link\"] == '<http://warning.or.kr/>; rel=\"blocked-by\"'\n\n\ndef test_link_url_451() -> None:\n    resp = web.HTTPUnavailableForLegalReasons(link=URL(\"http://warning.or.kr/\"))\n    assert resp.link == URL(\"http://warning.or.kr/\")\n    assert resp.headers[\"Link\"] == '<http://warning.or.kr/>; rel=\"blocked-by\"'\n\n\ndef test_link_CRLF_451() -> None:\n    resp = web.HTTPUnavailableForLegalReasons(link=\"http://warning.or.kr/\\r\\n\")\n    assert \"\\r\\n\" not in resp.headers[\"Link\"]\n\n\ndef test_HTTPException_retains_cause() -> None:\n    with pytest.raises(web.HTTPException) as ei:\n        try:\n            raise Exception(\"CustomException\")\n        except Exception as exc:\n            raise web.HTTPException() from exc\n    tb = \"\".join(format_exception(ei.type, ei.value, ei.tb))\n    assert \"CustomException\" in tb\n    assert \"direct cause\" in tb\n\n\nclass TestHTTPOk:\n    def test_ctor_all(self) -> None:\n        resp = web.HTTPOk(\n            headers={\"X-Custom\": \"value\"},\n            reason=\"Done\",\n            text=\"text\",\n            content_type=\"custom\",\n        )\n        assert resp.text == \"text\"\n        compare: Mapping[str, str] = {\"X-Custom\": \"value\", \"Content-Type\": \"custom\"}\n        assert resp.headers == compare\n        assert resp.reason == \"Done\"\n        assert resp.status == 200\n\n    def test_multiline_reason(self) -> None:\n        with pytest.raises(ValueError, match=r\"Reason cannot contain\"):\n            web.HTTPOk(reason=\"Bad\\r\\nInjected-header: foo\")\n\n    def test_reason_with_cr(self) -> None:\n        with pytest.raises(ValueError, match=r\"Reason cannot contain\"):\n            web.HTTPOk(reason=\"OK\\rSet-Cookie: evil=1\")\n\n    def test_reason_with_lf(self) -> None:\n        with pytest.raises(ValueError, match=r\"Reason cannot contain\"):\n            web.HTTPOk(reason=\"OK\\nSet-Cookie: evil=1\")\n\n    def test_pickle(self) -> None:\n        resp = web.HTTPOk(\n            headers={\"X-Custom\": \"value\"},\n            reason=\"Done\",\n            text=\"text\",\n            content_type=\"custom\",\n        )\n        resp.foo = \"bar\"  # type: ignore[attr-defined]\n        for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(resp, proto)\n            resp2 = pickle.loads(pickled)\n            assert resp2.text == \"text\"\n            assert resp2.headers == resp.headers\n            assert resp2.reason == \"Done\"\n            assert resp2.status == 200\n            assert resp2.foo == \"bar\"\n\n    async def test_app(self, aiohttp_client: AiohttpClient) -> None:\n        async def handler(request: web.Request) -> NoReturn:\n            raise web.HTTPOk()\n\n        app = web.Application()\n        app.router.add_get(\"/\", handler)\n        cli = await aiohttp_client(app)\n\n        resp = await cli.get(\"/\")\n        assert 200 == resp.status\n        txt = await resp.text()\n        assert \"200: OK\" == txt\n\n\nclass TestHTTPFound:\n    def test_location_str(self) -> None:\n        exc = web.HTTPFound(location=\"/redirect\")\n        assert exc.location == URL(\"/redirect\")\n        assert exc.headers[\"Location\"] == \"/redirect\"\n\n    def test_location_url(self) -> None:\n        exc = web.HTTPFound(location=URL(\"/redirect\"))\n        assert exc.location == URL(\"/redirect\")\n        assert exc.headers[\"Location\"] == \"/redirect\"\n\n    def test_empty_location(self) -> None:\n        with pytest.raises(ValueError):\n            web.HTTPFound(location=\"\")\n        with pytest.raises(ValueError):\n            web.HTTPFound(location=None)  # type: ignore[arg-type]\n\n    def test_location_CRLF(self) -> None:\n        exc = web.HTTPFound(location=\"/redirect\\r\\n\")\n        assert \"\\r\\n\" not in exc.headers[\"Location\"]\n\n    def test_pickle(self) -> None:\n        resp = web.HTTPFound(\n            location=\"http://example.com\",\n            headers={\"X-Custom\": \"value\"},\n            reason=\"Wow\",\n            text=\"text\",\n            content_type=\"custom\",\n        )\n        resp.foo = \"bar\"  # type: ignore[attr-defined]\n        for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(resp, proto)\n            resp2 = pickle.loads(pickled)\n            assert resp2.location == URL(\"http://example.com\")\n            assert resp2.text == \"text\"\n            assert resp2.headers == resp.headers\n            assert resp2.reason == \"Wow\"\n            assert resp2.status == 302\n            assert resp2.foo == \"bar\"\n\n    async def test_app(self, aiohttp_client: AiohttpClient) -> None:\n        async def handler(request: web.Request) -> NoReturn:\n            raise web.HTTPFound(location=\"/redirect\")\n\n        app = web.Application()\n        app.router.add_get(\"/\", handler)\n        cli = await aiohttp_client(app)\n\n        resp = await cli.get(\"/\", allow_redirects=False)\n        assert 302 == resp.status\n        txt = await resp.text()\n        assert \"302: Found\" == txt\n        assert \"/redirect\" == resp.headers[\"location\"]\n\n\nclass TestHTTPMethodNotAllowed:\n    async def test_ctor(self) -> None:\n        resp = web.HTTPMethodNotAllowed(\n            \"GET\",\n            [\"POST\", \"PUT\"],\n            headers={\"X-Custom\": \"value\"},\n            reason=\"Unsupported\",\n            text=\"text\",\n            content_type=\"custom\",\n        )\n        assert resp.method == \"GET\"\n        assert resp.allowed_methods == {\"POST\", \"PUT\"}\n        assert resp.text == \"text\"\n        compare: Mapping[str, str] = {\n            \"X-Custom\": \"value\",\n            \"Content-Type\": \"custom\",\n            \"Allow\": \"POST,PUT\",\n        }\n        assert resp.headers == compare\n        assert resp.reason == \"Unsupported\"\n        assert resp.status == 405\n\n    def test_pickle(self) -> None:\n        resp = web.HTTPMethodNotAllowed(\n            method=\"GET\",\n            allowed_methods=(\"POST\", \"PUT\"),\n            headers={\"X-Custom\": \"value\"},\n            reason=\"Unsupported\",\n            text=\"text\",\n            content_type=\"custom\",\n        )\n        resp.foo = \"bar\"  # type: ignore[attr-defined]\n        for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(resp, proto)\n            resp2 = pickle.loads(pickled)\n            assert resp2.method == \"GET\"\n            assert resp2.allowed_methods == {\"POST\", \"PUT\"}\n            assert resp2.text == \"text\"\n            assert resp2.headers == resp.headers\n            assert resp2.reason == \"Unsupported\"\n            assert resp2.status == 405\n            assert resp2.foo == \"bar\"\n\n\nclass TestHTTPRequestEntityTooLarge:\n    def test_ctor(self) -> None:\n        resp = web.HTTPRequestEntityTooLarge(\n            max_size=100,\n            actual_size=123,\n            headers={\"X-Custom\": \"value\"},\n            reason=\"Too large\",\n        )\n        assert resp.text == (\n            \"Maximum request body size 100 exceeded, actual body size 123\"\n        )\n        compare: Mapping[str, str] = {\"X-Custom\": \"value\", \"Content-Type\": \"text/plain\"}\n        assert resp.headers == compare\n        assert resp.reason == \"Too large\"\n        assert resp.status == 413\n\n    def test_pickle(self) -> None:\n        resp = web.HTTPRequestEntityTooLarge(\n            100, actual_size=123, headers={\"X-Custom\": \"value\"}, reason=\"Too large\"\n        )\n        resp.foo = \"bar\"  # type: ignore[attr-defined]\n        for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(resp, proto)\n            resp2 = pickle.loads(pickled)\n            assert resp2.text == resp.text\n            assert resp2.headers == resp.headers\n            assert resp2.reason == \"Too large\"\n            assert resp2.status == 413\n            assert resp2.foo == \"bar\"\n\n\nclass TestHTTPUnavailableForLegalReasons:\n    def test_ctor(self) -> None:\n        exc = web.HTTPUnavailableForLegalReasons(\n            link=\"http://warning.or.kr/\",\n            headers={\"X-Custom\": \"value\"},\n            reason=\"Zaprescheno\",\n            text=\"text\",\n            content_type=\"custom\",\n        )\n        assert exc.link == URL(\"http://warning.or.kr/\")\n        assert exc.text == \"text\"\n        compare: Mapping[str, str] = {\n            \"X-Custom\": \"value\",\n            \"Content-Type\": \"custom\",\n            \"Link\": '<http://warning.or.kr/>; rel=\"blocked-by\"',\n        }\n        assert exc.headers == compare\n        assert exc.reason == \"Zaprescheno\"\n        assert exc.status == 451\n\n    def test_no_link(self) -> None:\n        with pytest.raises(TypeError):\n            web.HTTPUnavailableForLegalReasons()  # type: ignore[call-arg]\n\n    def test_none_link(self) -> None:\n        exc = web.HTTPUnavailableForLegalReasons(link=None)\n        assert exc.link is None\n        assert \"Link\" not in exc.headers\n\n    def test_empty_link(self) -> None:\n        exc = web.HTTPUnavailableForLegalReasons(link=\"\")\n        assert exc.link is None\n        assert \"Link\" not in exc.headers\n\n    def test_link_str(self) -> None:\n        exc = web.HTTPUnavailableForLegalReasons(link=\"http://warning.or.kr/\")\n        assert exc.link == URL(\"http://warning.or.kr/\")\n        assert exc.headers[\"Link\"] == '<http://warning.or.kr/>; rel=\"blocked-by\"'\n\n    def test_link_url(self) -> None:\n        exc = web.HTTPUnavailableForLegalReasons(link=URL(\"http://warning.or.kr/\"))\n        assert exc.link == URL(\"http://warning.or.kr/\")\n        assert exc.headers[\"Link\"] == '<http://warning.or.kr/>; rel=\"blocked-by\"'\n\n    def test_link_CRLF(self) -> None:\n        exc = web.HTTPUnavailableForLegalReasons(link=\"http://warning.or.kr/\\r\\n\")\n        assert \"\\r\\n\" not in exc.headers[\"Link\"]\n\n    def test_pickle(self) -> None:\n        resp = web.HTTPUnavailableForLegalReasons(\n            link=\"http://warning.or.kr/\",\n            headers={\"X-Custom\": \"value\"},\n            reason=\"Zaprescheno\",\n            text=\"text\",\n            content_type=\"custom\",\n        )\n        resp.foo = \"bar\"  # type: ignore[attr-defined]\n        for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(resp, proto)\n            resp2 = pickle.loads(pickled)\n            assert resp2.link == URL(\"http://warning.or.kr/\")\n            assert resp2.text == \"text\"\n            assert resp2.headers == resp.headers\n            assert resp2.reason == \"Zaprescheno\"\n            assert resp2.status == 451\n            assert resp2.foo == \"bar\"\n"
  },
  {
    "path": "tests/test_web_functional.py",
    "content": "import asyncio\nimport io\nimport json\nimport pathlib\nimport socket\nimport sys\nfrom collections.abc import AsyncIterator, Awaitable, Callable, Generator\nfrom typing import NoReturn\nfrom unittest import mock\n\nimport pytest\nfrom multidict import CIMultiDictProxy, MultiDict\nfrom pytest_mock import MockerFixture\nfrom yarl import URL\n\nimport aiohttp\nfrom aiohttp import (\n    FormData,\n    HttpVersion,\n    HttpVersion10,\n    HttpVersion11,\n    TraceConfig,\n    multipart,\n    web,\n)\nfrom aiohttp.abc import AbstractResolver, ResolveResult\nfrom aiohttp.compression_utils import ZLibBackend, ZLibCompressObjProtocol\nfrom aiohttp.hdrs import CONTENT_LENGTH, CONTENT_TYPE, TRANSFER_ENCODING\nfrom aiohttp.pytest_plugin import AiohttpClient, AiohttpServer\nfrom aiohttp.typedefs import Handler, Middleware\nfrom aiohttp.web_protocol import RequestHandler\n\ntry:\n    import brotlicffi as brotli\nexcept ImportError:\n    import brotli\n\ntry:\n    import ssl\nexcept ImportError:\n    ssl = None  # type: ignore[assignment]\n\n\n@pytest.fixture\ndef here() -> pathlib.Path:\n    return pathlib.Path(__file__).parent\n\n\n@pytest.fixture\ndef fname(here: pathlib.Path) -> pathlib.Path:\n    return here / \"conftest.py\"\n\n\ndef new_dummy_form() -> FormData:\n    form = FormData()\n    form.add_field(\"name\", b\"123\")\n    return form\n\n\nasync def test_simple_get(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.read()\n        assert b\"\" == body\n        return web.Response(body=b\"OK\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    txt = await resp.text()\n    assert \"OK\" == txt\n\n    resp.release()\n\n\nasync def test_simple_get_with_text(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.read()\n        assert b\"\" == body\n        return web.Response(text=\"OK\", headers={\"content-type\": \"text/plain\"})\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    txt = await resp.text()\n    assert \"OK\" == txt\n\n    resp.release()\n\n\nasync def test_handler_returns_not_response(\n    aiohttp_server: AiohttpServer, aiohttp_client: AiohttpClient\n) -> None:\n    asyncio.get_event_loop().set_debug(True)\n    logger = mock.Mock()\n\n    async def handler(request: web.Request) -> str:\n        return \"abc\"\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)  # type: ignore[arg-type]\n    server = await aiohttp_server(app, logger=logger)\n    client = await aiohttp_client(server)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 500\n\n\nasync def test_handler_returns_none(\n    aiohttp_server: AiohttpServer, aiohttp_client: AiohttpClient\n) -> None:\n    asyncio.get_event_loop().set_debug(True)\n    logger = mock.Mock()\n\n    async def handler(request: web.Request) -> None:\n        return None\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)  # type: ignore[arg-type]\n    server = await aiohttp_server(app, logger=logger)\n    client = await aiohttp_client(server)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 500\n\n\nasync def test_handler_returns_not_response_after_100expect(\n    aiohttp_server: AiohttpServer, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        raise Exception(\"foo\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\", expect100=True) as resp:\n        assert resp.status == 500\n\n\nasync def test_head_returns_empty_body(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"test\")\n\n    app = web.Application()\n    app.router.add_head(\"/\", handler)\n    client = await aiohttp_client(app, version=HttpVersion11)\n\n    resp = await client.head(\"/\")\n    assert 200 == resp.status\n    txt = await resp.text()\n    assert \"\" == txt\n    # The Content-Length header should be set to 4 which is\n    # the length of the response body if it would have been\n    # returned by a GET request.\n    assert resp.headers[\"Content-Length\"] == \"4\"\n\n\n@pytest.mark.parametrize(\"status\", (201, 204, 404))\nasync def test_default_content_type_no_body(\n    aiohttp_client: AiohttpClient, status: int\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(status=status)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == status\n        assert await resp.read() == b\"\"\n        assert \"Content-Type\" not in resp.headers\n\n\nasync def test_response_before_complete(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"OK\")\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    data = b\"0\" * 1024 * 1024\n\n    resp = await client.post(\"/\", data=data)\n    assert 200 == resp.status\n    text = await resp.text()\n    assert \"OK\" == text\n\n    resp.release()\n\n\n@pytest.mark.skipif(sys.version_info < (3, 11), reason=\"Needs Task.cancelling()\")\nasync def test_cancel_shutdown(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        t = asyncio.create_task(request.protocol.shutdown())\n        # Ensure it's started waiting\n        await asyncio.sleep(0)\n\n        t.cancel()\n        # Cancellation should not be suppressed\n        with pytest.raises(asyncio.CancelledError):\n            await t\n\n        # Repeat for second waiter in shutdown()\n        with mock.patch.object(request.protocol, \"_request_in_progress\", False):\n            with mock.patch.object(request.protocol, \"_current_request\", None):\n                t = asyncio.create_task(request.protocol.shutdown())\n                await asyncio.sleep(0)\n\n                t.cancel()\n                with pytest.raises(asyncio.CancelledError):\n                    await t\n\n        return web.Response(body=b\"OK\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as resp:\n        assert resp.status == 200\n        txt = await resp.text()\n        assert txt == \"OK\"\n\n\nasync def test_post_form(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        assert {\"a\": \"1\", \"b\": \"2\", \"c\": \"\"} == data\n        return web.Response(body=b\"OK\")\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.post(\"/\", data={\"a\": \"1\", \"b\": \"2\", \"c\": \"\"})\n    assert 200 == resp.status\n    txt = await resp.text()\n    assert \"OK\" == txt\n\n    resp.release()\n\n\nasync def test_post_text(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.text()\n        assert \"русский\" == data\n        data2 = await request.text()\n        assert data == data2\n        return web.Response(text=data)\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.post(\"/\", data=\"русский\")\n    assert 200 == resp.status\n    txt = await resp.text()\n    assert \"русский\" == txt\n\n    resp.release()\n\n\nasync def test_post_json(aiohttp_client: AiohttpClient) -> None:\n    dct = {\"key\": \"текст\"}\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.json()\n        assert dct == data\n        data2 = await request.json(loads=json.loads)\n        assert data == data2\n        resp = web.Response()\n        resp.content_type = \"application/json\"\n        resp.body = json.dumps(data).encode(\"utf8\")\n        return resp\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    headers = {\"Content-Type\": \"application/json\"}\n    resp = await client.post(\"/\", data=json.dumps(dct), headers=headers)\n    assert 200 == resp.status\n    data = await resp.json()\n    assert dct == data\n\n    resp.release()\n\n\nasync def test_multipart(aiohttp_client: AiohttpClient) -> None:\n    with multipart.MultipartWriter() as writer:\n        writer.append(\"test\")\n        writer.append_json({\"passed\": True})\n\n    async def handler(request: web.Request) -> web.Response:\n        reader = await request.multipart()\n        assert isinstance(reader, multipart.MultipartReader)\n\n        part = await reader.next()\n        assert isinstance(part, multipart.BodyPartReader)\n        thing = await part.text()\n        assert thing == \"test\"\n\n        part = await reader.next()\n        assert isinstance(part, multipart.BodyPartReader)\n        assert part.headers[\"Content-Type\"] == \"application/json\"\n        json_thing = await part.json()\n        assert json_thing == {\"passed\": True}\n\n        resp = web.Response()\n        resp.content_type = \"application/json\"\n        resp.body = b\"\"\n        return resp\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.post(\"/\", data=writer)\n    assert 200 == resp.status\n    resp.release()\n\n\nasync def test_multipart_empty(aiohttp_client: AiohttpClient) -> None:\n    with multipart.MultipartWriter() as writer:\n        pass\n\n    async def handler(request: web.Request) -> web.Response:\n        reader = await request.multipart()\n        assert isinstance(reader, multipart.MultipartReader)\n        async for part in reader:\n            assert False, f\"Unexpected part found in reader: {part!r}\"\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.post(\"/\", data=writer)\n    assert 200 == resp.status\n    resp.release()\n\n\nasync def test_multipart_content_transfer_encoding(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    # For issue #1168\n    with multipart.MultipartWriter() as writer:\n        writer.append(\n            b\"\\x00\" * 10,\n            headers={\"Content-Transfer-Encoding\": \"binary\"},\n        )\n\n    async def handler(request: web.Request) -> web.Response:\n        reader = await request.multipart()\n        assert isinstance(reader, multipart.MultipartReader)\n\n        part = await reader.next()\n        assert isinstance(part, multipart.BodyPartReader)\n        assert part.headers[\"Content-Transfer-Encoding\"] == \"binary\"\n        thing = await part.read()\n        assert thing == b\"\\x00\" * 10\n\n        resp = web.Response()\n        resp.content_type = \"application/json\"\n        resp.body = b\"\"\n        return resp\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.post(\"/\", data=writer)\n    assert 200 == resp.status\n    resp.release()\n\n\nasync def test_render_redirect(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        raise web.HTTPMovedPermanently(location=\"/path\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\", allow_redirects=False)\n    assert 301 == resp.status\n    txt = await resp.text()\n    assert \"301: Moved Permanently\" == txt\n    assert \"/path\" == resp.headers[\"location\"]\n\n    resp.release()\n\n\nasync def test_post_single_file(aiohttp_client: AiohttpClient) -> None:\n    here = pathlib.Path(__file__).parent\n\n    def check_file(fs: aiohttp.web_request.FileField) -> None:\n        fullname = here / fs.filename\n        with fullname.open(\"rb\") as f:\n            test_data = f.read()\n            data = fs.file.read()\n            assert test_data == data\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        assert [\"data.unknown_mime_type\"] == list(data.keys())\n        for fs in data.values():\n            assert isinstance(fs, aiohttp.web_request.FileField)\n            await asyncio.to_thread(check_file, fs)\n            fs.file.close()\n        resp = web.Response(body=b\"OK\")\n        return resp\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    fname = here / \"data.unknown_mime_type\"\n\n    with fname.open(\"rb\") as fd:\n        resp = await client.post(\"/\", data=[fd])\n    assert 200 == resp.status\n\n    resp.release()\n\n\nasync def test_files_upload_with_same_key(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        files = data.getall(\"file\")\n        file_names = set()\n        for _file in files:\n            assert isinstance(_file, aiohttp.web_request.FileField)\n            assert not _file.file.closed\n            if _file.filename == \"test1.jpeg\":\n                assert await asyncio.to_thread(_file.file.read) == b\"binary data 1\"\n            if _file.filename == \"test2.jpeg\":\n                assert await asyncio.to_thread(_file.file.read) == b\"binary data 2\"\n            file_names.add(_file.filename)\n            _file.file.close()\n        assert len(files) == 2\n        assert file_names == {\"test1.jpeg\", \"test2.jpeg\"}\n        resp = web.Response(body=b\"OK\")\n        return resp\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    data = FormData()\n    data.add_field(\n        \"file\", b\"binary data 1\", content_type=\"image/jpeg\", filename=\"test1.jpeg\"\n    )\n    data.add_field(\n        \"file\", b\"binary data 2\", content_type=\"image/jpeg\", filename=\"test2.jpeg\"\n    )\n    resp = await client.post(\"/\", data=data)\n    assert 200 == resp.status\n\n    resp.release()\n\n\nasync def test_post_files(aiohttp_client: AiohttpClient) -> None:\n    here = pathlib.Path(__file__).parent\n\n    def check_file(fs: aiohttp.web_request.FileField) -> None:\n        fullname = here / fs.filename\n        with fullname.open(\"rb\") as f:\n            test_data = f.read()\n            data = fs.file.read()\n            assert test_data == data\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        assert [\"data.unknown_mime_type\", \"conftest.py\"] == list(data.keys())\n        for fs in data.values():\n            assert isinstance(fs, aiohttp.web_request.FileField)\n            await asyncio.to_thread(check_file, fs)\n            fs.file.close()\n        resp = web.Response(body=b\"OK\")\n        return resp\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with (here / \"data.unknown_mime_type\").open(\"rb\") as f1:\n        with (here / \"conftest.py\").open(\"rb\") as f2:\n            resp = await client.post(\"/\", data=[f1, f2])\n            assert 200 == resp.status\n\n            resp.release()\n\n\nasync def test_release_post_data(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        await request.release()\n        chunk = await request.content.readany()\n        assert chunk == b\"\"\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.post(\"/\", data=\"post text\")\n    assert 200 == resp.status\n\n    resp.release()\n\n\nasync def test_post_form_with_duplicate_keys(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        lst = list(data.items())\n        assert [(\"a\", \"1\"), (\"a\", \"2\")] == lst\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.post(\"/\", data=MultiDict([(\"a\", \"1\"), (\"a\", \"2\")]))\n    assert 200 == resp.status\n\n    resp.release()\n\n\ndef test_repr_for_application() -> None:\n    app = web.Application()\n    assert f\"<Application 0x{id(app):x}>\" == repr(app)\n\n\nasync def test_expect_default_handler_unknown(aiohttp_client: AiohttpClient) -> None:\n    # Test default Expect handler for unknown Expect value.\n\n    # A server that does not understand or is unable to comply with any of\n    # the expectation values in the Expect field of a request MUST respond\n    # with appropriate error status. The server MUST respond with a 417\n    # (Expectation Failed) status if any of the expectations cannot be met\n    # or, if there are other problems with the request, some other 4xx\n    # status.\n\n    # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.20\n    async def handler(request: web.Request) -> web.Response:\n        assert False\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.post(\"/\", headers={\"Expect\": \"SPAM\"})\n    assert 417 == resp.status\n\n    resp.release()\n\n\nasync def test_100_continue(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        assert b\"123\" == data[\"name\"]\n        return web.Response()\n\n    form = FormData()\n    form.add_field(\"name\", b\"123\")\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.post(\"/\", data=form, expect100=True)\n    assert 200 == resp.status\n\n    resp.release()\n\n\nasync def test_100_continue_custom(aiohttp_client: AiohttpClient) -> None:\n    expect_received = False\n\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        assert b\"123\" == data[\"name\"]\n        return web.Response()\n\n    async def expect_handler(request: web.Request) -> None:\n        nonlocal expect_received\n        expect_received = True\n        assert request.version == HttpVersion11\n        await request.writer.write(b\"HTTP/1.1 100 Continue\\r\\n\\r\\n\")\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler, expect_handler=expect_handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.post(\"/\", data=new_dummy_form(), expect100=True)\n    assert 200 == resp.status\n    assert expect_received\n\n    resp.release()\n\n\nasync def test_100_continue_custom_response(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.post()\n        assert b\"123\", data[\"name\"]\n        return web.Response()\n\n    async def expect_handler(request: web.Request) -> None:\n        assert request.version == HttpVersion11\n        if auth_err:\n            raise web.HTTPForbidden()\n\n        await request.writer.write(b\"HTTP/1.1 100 Continue\\r\\n\\r\\n\")\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler, expect_handler=expect_handler)\n    client = await aiohttp_client(app)\n\n    auth_err = False\n    resp = await client.post(\"/\", data=new_dummy_form(), expect100=True)\n    assert 200 == resp.status\n    resp.release()\n\n    auth_err = True\n    resp = await client.post(\"/\", data=new_dummy_form(), expect100=True)\n    assert 403 == resp.status\n    resp.release()\n\n\nasync def test_expect_handler_custom_response(aiohttp_client: AiohttpClient) -> None:\n    cache = {\"foo\": \"bar\"}\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"handler\")\n\n    async def expect_handler(request: web.Request) -> web.Response | None:\n        k = request.headers[\"X-Key\"]\n        cached_value = cache.get(k)\n        return web.Response(text=cached_value) if cached_value else None\n\n    app = web.Application()\n    # expect_handler is only typed on add_route().\n    app.router.add_route(\"POST\", \"/\", handler, expect_handler=expect_handler)\n    client = await aiohttp_client(app)\n\n    async with client.post(\"/\", expect100=True, headers={\"X-Key\": \"foo\"}) as resp:\n        assert resp.status == 200\n        assert await resp.text() == \"bar\"\n\n    async with client.post(\"/\", expect100=True, headers={\"X-Key\": \"spam\"}) as resp:\n        assert resp.status == 200\n        assert await resp.text() == \"handler\"\n\n\nasync def test_100_continue_for_not_found(aiohttp_client: AiohttpClient) -> None:\n    app = web.Application()\n    client = await aiohttp_client(app)\n\n    resp = await client.post(\"/not_found\", data=\"data\", expect100=True)\n    assert 404 == resp.status\n\n    resp.release()\n\n\nasync def test_100_continue_for_not_allowed(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\", expect100=True)\n    assert 405 == resp.status\n\n    resp.release()\n\n\nasync def test_http11_keep_alive_default(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app, version=HttpVersion11)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    assert resp.version == HttpVersion11\n    assert \"Connection\" not in resp.headers\n\n    resp.release()\n\n\nasync def test_http10_keep_alive_default(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app, version=HttpVersion10)\n\n    async with client.get(\"/\") as resp:\n        assert 200 == resp.status\n        assert resp.version == HttpVersion10\n        assert resp.headers[\"Connection\"] == \"keep-alive\"\n\n\nasync def test_http10_keep_alive_with_headers_close(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        await request.read()\n        return web.Response(body=b\"OK\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app, version=HttpVersion10)\n\n    headers = {\"Connection\": \"close\"}\n    resp = await client.get(\"/\", headers=headers)\n    assert 200 == resp.status\n    assert resp.version == HttpVersion10\n    assert \"Connection\" not in resp.headers\n\n    resp.release()\n\n\nasync def test_http10_keep_alive_with_headers(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        await request.read()\n        return web.Response(body=b\"OK\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app, version=HttpVersion10)\n\n    headers = {\"Connection\": \"keep-alive\"}\n    resp = await client.get(\"/\", headers=headers)\n    assert 200 == resp.status\n    assert resp.version == HttpVersion10\n    assert resp.headers[\"Connection\"] == \"keep-alive\"\n\n    resp.release()\n\n\nasync def test_upload_file(aiohttp_client: AiohttpClient) -> None:\n    here = pathlib.Path(__file__).parent\n    fname = here / \"aiohttp.png\"\n    with fname.open(\"rb\") as f:\n        data = f.read()\n\n    async def handler(request: web.Request) -> web.Response:\n        form = await request.post()\n        form_file = form[\"file\"]\n        assert isinstance(form_file, aiohttp.web_request.FileField)\n        raw_data = await asyncio.to_thread(form_file.file.read)\n        form_file.file.close()\n        assert data == raw_data\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.post(\"/\", data={\"file\": io.BytesIO(data)})\n    assert 200 == resp.status\n\n    resp.release()\n\n\nasync def test_upload_file_object(aiohttp_client: AiohttpClient) -> None:\n    here = pathlib.Path(__file__).parent\n    fname = here / \"aiohttp.png\"\n    with fname.open(\"rb\") as f:\n        data = f.read()\n\n    async def handler(request: web.Request) -> web.Response:\n        form = await request.post()\n        form_file = form[\"file\"]\n        assert isinstance(form_file, aiohttp.web_request.FileField)\n        raw_data = await asyncio.to_thread(form_file.file.read)\n        form_file.file.close()\n        assert data == raw_data\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with fname.open(\"rb\") as f:\n        resp = await client.post(\"/\", data={\"file\": f})\n        assert 200 == resp.status\n\n        resp.release()\n\n\n@pytest.mark.parametrize(\n    \"method\", [\"get\", \"post\", \"options\", \"post\", \"put\", \"patch\", \"delete\"]\n)\nasync def test_empty_content_for_query_without_body(\n    method: str, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert not request.body_exists\n        assert not request.can_read_body\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(method, \"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.request(method, \"/\")\n    assert 200 == resp.status\n\n\nasync def test_empty_content_for_query_with_body(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert request.body_exists\n        assert request.can_read_body\n        body = await request.read()\n        return web.Response(body=body)\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.post(\"/\", data=b\"data\")\n    assert 200 == resp.status\n\n    resp.release()\n\n\nasync def test_get_with_empty_arg(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert \"arg\" in request.query\n        assert \"\" == request.query[\"arg\"]\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/?arg\")\n    assert 200 == resp.status\n\n    resp.release()\n\n\nasync def test_large_header(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    headers = {\"Long-Header\": \"ab\" * 8129}\n    resp = await client.get(\"/\", headers=headers)\n    assert 400 == resp.status\n\n    resp.release()\n\n\nasync def test_large_header_allowed(\n    aiohttp_client: AiohttpClient, aiohttp_server: AiohttpServer\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    server = await aiohttp_server(app, max_field_size=81920)\n    client = await aiohttp_client(server)\n\n    headers = {\"Long-Header\": \"ab\" * 8129}\n    resp = await client.post(\"/\", headers=headers)\n    assert 200 == resp.status\n\n    resp.release()\n\n\nasync def test_get_with_empty_arg_with_equal(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert \"arg\" in request.query\n        assert \"\" == request.query[\"arg\"]\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/?arg=\")\n    assert 200 == resp.status\n\n    resp.release()\n\n\nasync def test_response_with_async_gen(\n    aiohttp_client: AiohttpClient, fname: pathlib.Path\n) -> None:\n    with fname.open(\"rb\") as f:\n        data = f.read()\n\n    data_size = len(data)\n\n    async def stream(f_name: pathlib.Path) -> AsyncIterator[bytes]:\n        with f_name.open(\"rb\") as f:\n            data = await asyncio.to_thread(f.read, 100)\n            while data:\n                yield data\n                data = await asyncio.to_thread(f.read, 100)\n\n    async def handler(request: web.Request) -> web.Response:\n        headers = {\"Content-Length\": str(data_size)}\n        return web.Response(body=stream(fname), headers=headers)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    resp_data = await resp.read()\n    assert resp_data == data\n    assert resp.headers.get(\"Content-Length\") == str(len(resp_data))\n\n    resp.release()\n\n\nasync def test_response_with_async_gen_no_params(\n    aiohttp_client: AiohttpClient, fname: pathlib.Path\n) -> None:\n    with fname.open(\"rb\") as f:\n        data = f.read()\n\n    data_size = len(data)\n\n    async def stream() -> AsyncIterator[bytes]:\n        with fname.open(\"rb\") as f:\n            data = await asyncio.to_thread(f.read, 100)\n            while data:\n                yield data\n                data = await asyncio.to_thread(f.read, 100)\n\n    async def handler(request: web.Request) -> web.Response:\n        headers = {\"Content-Length\": str(data_size)}\n        return web.Response(body=stream(), headers=headers)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    resp_data = await resp.read()\n    assert resp_data == data\n    assert resp.headers.get(\"Content-Length\") == str(len(resp_data))\n\n    resp.release()\n\n\nasync def test_response_with_file(\n    aiohttp_client: AiohttpClient, fname: pathlib.Path\n) -> None:\n    outer_file_descriptor = None\n\n    with fname.open(\"rb\") as f:\n        data = f.read()\n\n    async def handler(request: web.Request) -> web.Response:\n        nonlocal outer_file_descriptor\n        outer_file_descriptor = fname.open(\"rb\")\n        return web.Response(body=outer_file_descriptor)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    resp_data = await resp.read()\n    expected_content_disposition = 'attachment; filename=\"conftest.py\"'\n    assert resp_data == data\n    assert resp.headers.get(\"Content-Type\") in (\n        \"application/octet-stream\",\n        \"text/x-python\",\n        \"text/plain\",\n    )\n    assert resp.headers.get(\"Content-Length\") == str(len(resp_data))\n    assert resp.headers.get(\"Content-Disposition\") == expected_content_disposition\n\n    resp.release()\n\n    assert outer_file_descriptor is not None\n    outer_file_descriptor.close()\n\n\nasync def test_response_with_file_ctype(\n    aiohttp_client: AiohttpClient, fname: pathlib.Path\n) -> None:\n    outer_file_descriptor = None\n\n    with fname.open(\"rb\") as f:\n        data = f.read()\n\n    async def handler(request: web.Request) -> web.Response:\n        nonlocal outer_file_descriptor\n        outer_file_descriptor = fname.open(\"rb\")\n\n        return web.Response(\n            body=outer_file_descriptor, headers={\"content-type\": \"text/binary\"}\n        )\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    resp_data = await resp.read()\n    expected_content_disposition = 'attachment; filename=\"conftest.py\"'\n    assert resp_data == data\n    assert resp.headers.get(\"Content-Type\") == \"text/binary\"\n    assert resp.headers.get(\"Content-Length\") == str(len(resp_data))\n    assert resp.headers.get(\"Content-Disposition\") == expected_content_disposition\n\n    resp.release()\n\n    assert outer_file_descriptor is not None\n    outer_file_descriptor.close()\n\n\nasync def test_response_with_payload_disp(\n    aiohttp_client: AiohttpClient, fname: pathlib.Path\n) -> None:\n    outer_file_descriptor = None\n\n    with fname.open(\"rb\") as f:\n        data = f.read()\n\n    async def handler(request: web.Request) -> web.Response:\n        nonlocal outer_file_descriptor\n        outer_file_descriptor = fname.open(\"rb\")\n        pl = aiohttp.get_payload(outer_file_descriptor)\n        pl.set_content_disposition(\"inline\", filename=\"test.txt\")\n        return web.Response(body=pl, headers={\"content-type\": \"text/binary\"})\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    resp_data = await resp.read()\n    assert resp_data == data\n    assert resp.headers.get(\"Content-Type\") == \"text/binary\"\n    assert resp.headers.get(\"Content-Length\") == str(len(resp_data))\n    assert resp.headers.get(\"Content-Disposition\") == 'inline; filename=\"test.txt\"'\n\n    resp.release()\n\n    assert outer_file_descriptor is not None\n    outer_file_descriptor.close()\n\n\nasync def test_response_with_payload_stringio(\n    aiohttp_client: AiohttpClient, fname: pathlib.Path\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=io.StringIO(\"test\"))\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    resp_data = await resp.read()\n    assert resp_data == b\"test\"\n\n    resp.release()\n\n\n@pytest.fixture(params=[\"gzip\", \"deflate\", \"deflate-raw\"])\ndef compressor_case(\n    request: pytest.FixtureRequest,\n    parametrize_zlib_backend: None,\n) -> Generator[tuple[ZLibCompressObjProtocol, str], None, None]:\n    encoding: str = request.param\n    max_wbits: int = ZLibBackend.MAX_WBITS\n\n    encoding_to_wbits: dict[str, int] = {\n        \"deflate\": max_wbits,\n        \"deflate-raw\": -max_wbits,\n        \"gzip\": 16 + max_wbits,\n    }\n\n    compressor = ZLibBackend.compressobj(wbits=encoding_to_wbits[encoding])\n    yield (compressor, \"deflate\" if encoding.startswith(\"deflate\") else encoding)\n\n\nasync def test_response_with_precompressed_body(\n    aiohttp_client: AiohttpClient,\n    compressor_case: tuple[ZLibCompressObjProtocol, str],\n) -> None:\n    compressor, encoding = compressor_case\n\n    async def handler(request: web.Request) -> web.Response:\n        headers = {\"Content-Encoding\": encoding}\n        data = compressor.compress(b\"mydata\") + compressor.flush()\n        return web.Response(body=data, headers=headers)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    data = await resp.read()\n    assert b\"mydata\" == data\n    assert resp.headers.get(\"Content-Encoding\") == encoding\n\n    resp.release()\n\n\nasync def test_response_with_precompressed_body_brotli(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        headers = {\"Content-Encoding\": \"br\"}\n        return web.Response(body=brotli.compress(b\"mydata\"), headers=headers)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    data = await resp.read()\n    assert b\"mydata\" == data\n    assert resp.headers.get(\"Content-Encoding\") == \"br\"\n\n    resp.release()\n\n\nasync def test_bad_request_payload(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert request.method == \"POST\"\n\n        with pytest.raises(aiohttp.web.RequestPayloadError):\n            await request.content.read()\n\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.post(\"/\", data=b\"test\", headers={\"content-encoding\": \"gzip\"})\n    assert 200 == resp.status\n\n    resp.release()\n\n\nasync def test_stream_response_multiple_chunks(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse()\n        resp.enable_chunked_encoding()\n        await resp.prepare(request)\n        await resp.write(b\"x\")\n        await resp.write(b\"y\")\n        await resp.write(b\"z\")\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    data = await resp.read()\n    assert b\"xyz\" == data\n\n    resp.release()\n\n\nasync def test_start_without_routes(aiohttp_client: AiohttpClient) -> None:\n    app = web.Application()\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert 404 == resp.status\n\n    resp.release()\n\n\nasync def test_requests_count(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n    assert client.server.handler.requests_count == 0\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    assert client.server.handler.requests_count == 1\n    resp.release()\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    assert client.server.handler.requests_count == 2\n    resp.release()\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    assert client.server.handler.requests_count == 3\n    resp.release()\n\n\nasync def test_redirect_url(aiohttp_client: AiohttpClient) -> None:\n    async def redirector(request: web.Request) -> NoReturn:\n        raise web.HTTPFound(location=URL(\"/redirected\"))\n\n    async def redirected(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/redirector\", redirector)\n    app.router.add_get(\"/redirected\", redirected)\n\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/redirector\")\n    assert resp.status == 200\n\n    resp.release()\n\n\nasync def test_simple_subapp(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"OK\")\n\n    app = web.Application()\n    subapp = web.Application()\n    subapp.router.add_get(\"/to\", handler)\n    app.add_subapp(\"/path\", subapp)\n\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/path/to\")\n    assert resp.status == 200\n    txt = await resp.text()\n    assert \"OK\" == txt\n\n    resp.release()\n\n\nasync def test_subapp_reverse_url(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        raise web.HTTPMovedPermanently(location=subapp.router[\"name\"].url_for())\n\n    async def handler2(request: web.Request) -> web.Response:\n        return web.Response(text=\"OK\")\n\n    app = web.Application()\n    subapp = web.Application()\n    subapp.router.add_get(\"/to\", handler)\n    subapp.router.add_get(\"/final\", handler2, name=\"name\")\n    app.add_subapp(\"/path\", subapp)\n\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/path/to\")\n    assert resp.status == 200\n    txt = await resp.text()\n    assert \"OK\" == txt\n    assert resp.url.path == \"/path/final\"\n\n    resp.release()\n\n\nasync def test_subapp_reverse_variable_url(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        raise web.HTTPMovedPermanently(\n            location=subapp.router[\"name\"].url_for(part=\"final\")\n        )\n\n    async def handler2(request: web.Request) -> web.Response:\n        return web.Response(text=\"OK\")\n\n    app = web.Application()\n    subapp = web.Application()\n    subapp.router.add_get(\"/to\", handler)\n    subapp.router.add_get(\"/{part}\", handler2, name=\"name\")\n    app.add_subapp(\"/path\", subapp)\n\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/path/to\")\n    assert resp.status == 200\n    txt = await resp.text()\n    assert \"OK\" == txt\n    assert resp.url.path == \"/path/final\"\n\n    resp.release()\n\n\nasync def test_subapp_reverse_static_url(aiohttp_client: AiohttpClient) -> None:\n    fname = \"aiohttp.png\"\n\n    async def handler(request: web.Request) -> NoReturn:\n        raise web.HTTPMovedPermanently(\n            location=subapp.router[\"name\"].url_for(filename=fname)\n        )\n\n    app = web.Application()\n    subapp = web.Application()\n    subapp.router.add_get(\"/to\", handler)\n    here = pathlib.Path(__file__).parent\n    subapp.router.add_static(\"/static\", here, name=\"name\")\n    app.add_subapp(\"/path\", subapp)\n\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/path/to\")\n    assert resp.url.path == \"/path/static/\" + fname\n    assert resp.status == 200\n    body = await resp.read()\n\n    resp.release()\n\n    with (here / fname).open(\"rb\") as f:\n        assert body == f.read()\n\n\nasync def test_subapp_app(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert request.app is subapp\n        return web.Response(text=\"OK\")\n\n    app = web.Application()\n    subapp = web.Application()\n    subapp.router.add_get(\"/to\", handler)\n    app.add_subapp(\"/path/\", subapp)\n\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/path/to\")\n    assert resp.status == 200\n    txt = await resp.text()\n    assert \"OK\" == txt\n\n    resp.release()\n\n\nasync def test_subapp_not_found(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    subapp = web.Application()\n    subapp.router.add_get(\"/to\", handler)\n    app.add_subapp(\"/path/\", subapp)\n\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/path/other\")\n    assert resp.status == 404\n\n    resp.release()\n\n\nasync def test_subapp_not_found2(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    subapp = web.Application()\n    subapp.router.add_get(\"/to\", handler)\n    app.add_subapp(\"/path/\", subapp)\n\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/invalid/other\")\n    assert resp.status == 404\n\n    resp.release()\n\n\nasync def test_subapp_not_allowed(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    subapp = web.Application()\n    subapp.router.add_get(\"/to\", handler)\n    app.add_subapp(\"/path/\", subapp)\n\n    client = await aiohttp_client(app)\n    resp = await client.post(\"/path/to\")\n    assert resp.status == 405\n    assert resp.headers[\"Allow\"] == \"GET,HEAD\"\n\n    resp.release()\n\n\nasync def test_subapp_cannot_add_app_in_handler(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        request.match_info.add_app(app)\n        assert False\n\n    app = web.Application()\n    subapp = web.Application()\n    subapp.router.add_get(\"/to\", handler)\n    app.add_subapp(\"/path/\", subapp)\n\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/path/to\")\n    assert resp.status == 500\n\n    resp.release()\n\n\nasync def test_old_style_subapp_middlewares(aiohttp_client: AiohttpClient) -> None:\n    order = []\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"OK\")\n\n    with pytest.deprecated_call(\n        match=r\"^Middleware decorator is deprecated since 4\\.0 and \"\n        r\"its behaviour is default, you can simply remove \"\n        r\"this decorator\\.$\",\n    ):\n\n        @web.middleware\n        async def middleware(\n            request: web.Request, handler: Handler\n        ) -> web.StreamResponse:\n            order.append((1, request.app[name]))\n            resp = await handler(request)\n            assert 200 == resp.status\n            order.append((2, request.app[name]))\n            return resp\n\n    app = web.Application(middlewares=[middleware])\n    name = web.AppKey(\"app\", str)\n    subapp1 = web.Application(middlewares=[middleware])\n    subapp2 = web.Application(middlewares=[middleware])\n    app[name] = \"app\"\n    subapp1[name] = \"subapp1\"\n    subapp2[name] = \"subapp2\"\n\n    subapp2.router.add_get(\"/to\", handler)\n    subapp1.add_subapp(\"/b/\", subapp2)\n    app.add_subapp(\"/a/\", subapp1)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/a/b/to\")\n    assert resp.status == 200\n    assert [\n        (1, \"app\"),\n        (1, \"subapp1\"),\n        (1, \"subapp2\"),\n        (2, \"subapp2\"),\n        (2, \"subapp1\"),\n        (2, \"app\"),\n    ] == order\n\n    resp.release()\n\n\nasync def test_subapp_on_response_prepare(aiohttp_client: AiohttpClient) -> None:\n    order = []\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"OK\")\n\n    def make_signal(\n        app: web.Application,\n    ) -> Callable[[web.Request, web.StreamResponse], Awaitable[None]]:\n        async def on_response(\n            request: web.Request, response: web.StreamResponse\n        ) -> None:\n            order.append(app)\n\n        return on_response\n\n    app = web.Application()\n    app.on_response_prepare.append(make_signal(app))\n    subapp1 = web.Application()\n    subapp1.on_response_prepare.append(make_signal(subapp1))\n    subapp2 = web.Application()\n    subapp2.on_response_prepare.append(make_signal(subapp2))\n    subapp2.router.add_get(\"/to\", handler)\n    subapp1.add_subapp(\"/b/\", subapp2)\n    app.add_subapp(\"/a/\", subapp1)\n\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/a/b/to\")\n    assert resp.status == 200\n    assert [app, subapp1, subapp2] == order\n\n    resp.release()\n\n\nasync def test_subapp_on_startup(aiohttp_server: AiohttpServer) -> None:\n    order = []\n\n    async def on_signal(app: web.Application) -> None:\n        order.append(app)\n\n    app = web.Application()\n    app.on_startup.append(on_signal)\n    subapp1 = web.Application()\n    subapp1.on_startup.append(on_signal)\n    subapp2 = web.Application()\n    subapp2.on_startup.append(on_signal)\n    subapp1.add_subapp(\"/b/\", subapp2)\n    app.add_subapp(\"/a/\", subapp1)\n\n    await aiohttp_server(app)\n\n    assert [app, subapp1, subapp2] == order\n\n\nasync def test_subapp_on_shutdown(aiohttp_server: AiohttpServer) -> None:\n    order = []\n\n    async def on_signal(app: web.Application) -> None:\n        order.append(app)\n\n    app = web.Application()\n    app.on_shutdown.append(on_signal)\n    subapp1 = web.Application()\n    subapp1.on_shutdown.append(on_signal)\n    subapp2 = web.Application()\n    subapp2.on_shutdown.append(on_signal)\n    subapp1.add_subapp(\"/b/\", subapp2)\n    app.add_subapp(\"/a/\", subapp1)\n\n    server = await aiohttp_server(app)\n    await server.close()\n\n    assert [app, subapp1, subapp2] == order\n\n\nasync def test_subapp_on_cleanup(aiohttp_server: AiohttpServer) -> None:\n    order = []\n\n    async def on_signal(app: web.Application) -> None:\n        order.append(app)\n\n    app = web.Application()\n    app.on_cleanup.append(on_signal)\n    subapp1 = web.Application()\n    subapp1.on_cleanup.append(on_signal)\n    subapp2 = web.Application()\n    subapp2.on_cleanup.append(on_signal)\n    subapp1.add_subapp(\"/b/\", subapp2)\n    app.add_subapp(\"/a/\", subapp1)\n\n    server = await aiohttp_server(app)\n    await server.close()\n\n    assert [app, subapp1, subapp2] == order\n\n\n@pytest.mark.parametrize(\n    \"route,expected,middlewares\",\n    [\n        (\"/sub/\", [\"A: root\", \"C: sub\", \"D: sub\"], \"AC\"),\n        (\"/\", [\"A: root\", \"B: root\"], \"AC\"),\n        (\"/sub/\", [\"A: root\", \"D: sub\"], \"A\"),\n        (\"/\", [\"A: root\", \"B: root\"], \"A\"),\n        (\"/sub/\", [\"C: sub\", \"D: sub\"], \"C\"),\n        (\"/\", [\"B: root\"], \"C\"),\n        (\"/sub/\", [\"D: sub\"], \"\"),\n        (\"/\", [\"B: root\"], \"\"),\n    ],\n)\nasync def test_subapp_middleware_context(\n    aiohttp_client: AiohttpClient, route: str, expected: list[str], middlewares: str\n) -> None:\n    values = []\n\n    def show_app_context(appname: str) -> Middleware:\n        async def middleware(\n            request: web.Request, handler: Handler\n        ) -> web.StreamResponse:\n            values.append(f\"{appname}: {request.app[my_value]}\")\n            return await handler(request)\n\n        return middleware\n\n    def make_handler(appname: str) -> Handler:\n        async def handler(request: web.Request) -> web.Response:\n            values.append(f\"{appname}: {request.app[my_value]}\")\n            return web.Response(text=\"Ok\")\n\n        return handler\n\n    app = web.Application()\n    my_value = web.AppKey(\"my_value\", str)\n    app[my_value] = \"root\"\n    if \"A\" in middlewares:\n        app.middlewares.append(show_app_context(\"A\"))\n    app.router.add_get(\"/\", make_handler(\"B\"))\n\n    subapp = web.Application()\n    subapp[my_value] = \"sub\"\n    if \"C\" in middlewares:\n        subapp.middlewares.append(show_app_context(\"C\"))\n    subapp.router.add_get(\"/\", make_handler(\"D\"))\n    app.add_subapp(\"/sub/\", subapp)\n\n    client = await aiohttp_client(app)\n    resp = await client.get(route)\n    assert 200 == resp.status\n    assert \"Ok\" == await resp.text()\n    assert expected == values\n\n    resp.release()\n\n\nasync def test_custom_date_header(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(headers={\"Date\": \"Sun, 30 Oct 2016 03:13:52 GMT\"})\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    assert resp.headers[\"Date\"] == \"Sun, 30 Oct 2016 03:13:52 GMT\"\n\n    resp.release()\n\n\nasync def test_response_prepared_with_clone(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.StreamResponse:\n        cloned = request.clone()\n        resp = web.StreamResponse()\n        await resp.prepare(cloned)\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n\n    resp.release()\n\n\nasync def test_app_max_client_size(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        await request.post()\n        assert False\n\n    max_size = 1024**2\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n    data = {\"long_string\": max_size * \"x\" + \"xxx\"}\n    with pytest.warns(ResourceWarning):\n        resp = await client.post(\"/\", data=data)\n    assert 413 == resp.status\n    resp_text = await resp.text()\n    assert \"Maximum request body size 1048576 exceeded, actual body size\" in resp_text\n    # Maximum request body size X exceeded, actual body size X\n    body_size = int(resp_text.split()[-1])\n    assert body_size >= max_size\n\n    resp.release()\n\n\nasync def test_app_max_client_size_form(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        await request.post()\n        assert False\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    # Verify that entire multipart form can't exceed client size (not just each field).\n    form = aiohttp.FormData()\n    for i in range(3):\n        form.add_field(f\"f{i}\", b\"A\" * 512000)\n\n    async with client.post(\"/\", data=form) as resp:\n        assert resp.status == 413\n        resp_text = await resp.text()\n    assert \"Maximum request body size 1048576 exceeded, actual body size\" in resp_text\n\n\nasync def test_app_max_client_size_adjusted(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        await request.post()\n        return web.Response(body=b\"ok\")\n\n    default_max_size = 1024**2\n    custom_max_size = default_max_size * 2\n    app = web.Application(client_max_size=custom_max_size)\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n    data = {\"long_string\": default_max_size * \"x\" + \"xxx\"}\n\n    with pytest.warns(ResourceWarning):\n        resp = await client.post(\"/\", data=data)\n    assert 200 == resp.status\n    resp_text = await resp.text()\n    assert \"ok\" == resp_text\n    resp.release()\n\n    too_large_data = {\"log_string\": custom_max_size * \"x\" + \"xxx\"}\n    with pytest.warns(ResourceWarning):\n        resp = await client.post(\"/\", data=too_large_data)\n    assert 413 == resp.status\n    resp_text = await resp.text()\n    assert \"Maximum request body size 2097152 exceeded, actual body size\" in resp_text\n    # Maximum request body size X exceeded, actual body size X\n    body_size = int(resp_text.split()[-1])\n    assert body_size >= custom_max_size\n\n    resp.release()\n\n\nasync def test_app_max_client_size_none(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        await request.post()\n        return web.Response(body=b\"ok\")\n\n    default_max_size = 1024**2\n    app = web.Application(client_max_size=0)\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    data = {\"long_string\": default_max_size * \"x\" + \"xxx\"}\n    with pytest.warns(ResourceWarning):\n        resp = await client.post(\"/\", data=data)\n    assert 200 == resp.status\n    resp_text = await resp.text()\n    assert \"ok\" == resp_text\n    resp.release()\n\n    too_large_data = {\"log_string\": default_max_size * 2 * \"x\"}\n    with pytest.warns(ResourceWarning):\n        resp = await client.post(\"/\", data=too_large_data)\n    assert 200 == resp.status\n    resp_text = await resp.text()\n    assert resp_text == \"ok\"\n    resp.release()\n\n\nasync def test_post_max_client_size(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        await request.post()\n        assert False\n\n    app = web.Application(client_max_size=10)\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with io.BytesIO(b\"test\") as file_handle:\n        data = {\"long_string\": 1024 * \"x\", \"file\": file_handle}\n        resp = await client.post(\"/\", data=data)\n\n        assert 413 == resp.status\n        resp_text = await resp.text()\n        assert (\n            \"Maximum request body size 10 exceeded, \"\n            \"actual body size 1024\" in resp_text\n        )\n        data_file = data[\"file\"]\n        assert isinstance(data_file, io.BytesIO)\n        data_file.close()\n\n        resp.release()\n\n\nasync def test_post_max_client_size_for_file(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        await request.post()\n        assert False\n\n    app = web.Application(client_max_size=2)\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with io.BytesIO(b\"test\") as file_handle:\n        data = {\"file\": file_handle}\n        resp = await client.post(\"/\", data=data)\n\n    assert 413 == resp.status\n\n    resp.release()\n\n\nasync def test_response_with_bodypart(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        reader = await request.multipart()\n        part = await reader.next()\n        return web.Response(body=part)\n\n    app = web.Application(client_max_size=2)\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with io.BytesIO(b\"test\") as file_handle:\n        data = {\"file\": file_handle}\n        resp = await client.post(\"/\", data=data)\n\n        assert 200 == resp.status\n        body = await resp.read()\n        assert body == b\"test\"\n\n        disp = multipart.parse_content_disposition(resp.headers[\"content-disposition\"])\n        assert disp == (\"attachment\", {\"name\": \"file\", \"filename\": \"file\"})\n\n        resp.release()\n\n\nasync def test_response_with_bodypart_named(\n    aiohttp_client: AiohttpClient, tmp_path: pathlib.Path\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        reader = await request.multipart()\n        part = await reader.next()\n        return web.Response(body=part)\n\n    app = web.Application(client_max_size=2)\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    f = tmp_path / \"foobar.txt\"\n    f.write_text(\"test\", encoding=\"utf8\")\n    with f.open(\"rb\") as fd:\n        data = {\"file\": fd}\n        resp = await client.post(\"/\", data=data)\n\n        assert 200 == resp.status\n        body = await resp.read()\n    assert body == b\"test\"\n\n    disp = multipart.parse_content_disposition(resp.headers[\"content-disposition\"])\n    assert disp == (\"attachment\", {\"name\": \"file\", \"filename\": \"foobar.txt\"})\n\n    resp.release()\n\n\nasync def test_response_with_bodypart_invalid_name(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        reader = await request.multipart()\n        part = await reader.next()\n        return web.Response(body=part)\n\n    app = web.Application(client_max_size=2)\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with aiohttp.MultipartWriter() as mpwriter:\n        mpwriter.append(b\"test\")\n        resp = await client.post(\"/\", data=mpwriter)\n\n    assert 200 == resp.status\n    body = await resp.read()\n    assert body == b\"test\"\n\n    assert \"content-disposition\" not in resp.headers\n\n    resp.release()\n\n\nasync def test_request_clone(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        r2 = request.clone(method=\"POST\")\n        assert r2.method == \"POST\"\n        assert r2.match_info is request.match_info\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    resp.release()\n\n\nasync def test_await(aiohttp_server: AiohttpServer) -> None:\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse(headers={\"content-length\": str(4)})\n        await resp.prepare(request)\n        with pytest.deprecated_call(\n            match=r\"^drain method is deprecated, use await resp\\.write\\(\\)$\",\n        ):\n            await resp.drain()\n        await asyncio.sleep(0.01)\n        await resp.write(b\"test\")\n        await asyncio.sleep(0.01)\n        await resp.write_eof()\n        return resp\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with aiohttp.ClientSession() as session:\n        resp = await session.get(server.make_url(\"/\"))\n        assert resp.status == 200\n        assert resp.connection is not None\n        await resp.read()\n        resp.release()\n        assert resp.connection is None\n\n\nasync def test_response_context_manager(aiohttp_server: AiohttpServer) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = await aiohttp_server(app)\n    session = aiohttp.ClientSession()\n    resp = await session.get(server.make_url(\"/\"))\n    async with resp:\n        assert resp.status == 200\n    assert resp.connection is None\n\n    await session.close()\n\n\nasync def test_response_context_manager_error(aiohttp_server: AiohttpServer) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"some text\")\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = await aiohttp_server(app)\n    session = aiohttp.ClientSession()\n    cm = session.get(server.make_url(\"/\"))\n    resp = await cm\n    with pytest.raises(RuntimeError):\n        async with resp:\n            assert resp.status == 200\n            resp.content.set_exception(RuntimeError())\n            await resp.read()\n    assert resp.closed\n\n    # Wait for any pending operations to complete\n    await resp.wait_for_close()\n\n    assert session._connector is not None\n    assert len(session._connector._conns) == 1\n\n    await session.close()\n\n\nasync def test_client_api_context_manager(aiohttp_server: AiohttpServer) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with aiohttp.ClientSession() as session:\n        async with session.get(server.make_url(\"/\")) as resp:\n            assert resp.status == 200\n            assert resp.connection is None\n    assert resp.connection is None\n\n\nasync def test_context_manager_close_on_release(\n    aiohttp_server: AiohttpServer, mocker: MockerFixture\n) -> None:\n    async def handler(request: web.Request) -> web.StreamResponse:\n        resp = web.StreamResponse()\n        await resp.prepare(request)\n        with pytest.deprecated_call(\n            match=r\"^drain method is deprecated, use await resp\\.write\\(\\)$\",\n        ):\n            await resp.drain()\n        await asyncio.sleep(10)\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with aiohttp.ClientSession() as session:\n        resp = await session.get(server.make_url(\"/\"))\n        assert resp.connection is not None\n        proto = resp.connection._protocol\n        mocker.spy(proto, \"close\")\n        async with resp:\n            assert resp.status == 200\n            assert resp.connection is not None\n        assert resp.connection is None\n        assert proto.close.called  # type: ignore[unreachable]\n\n        resp.release()  # Trigger handler completion\n\n\nasync def test_iter_any(aiohttp_server: AiohttpServer) -> None:\n    data = b\"0123456789\" * 1024\n\n    async def handler(request: web.Request) -> web.Response:\n        buf = []\n        async for raw in request.content.iter_any():\n            buf.append(raw)\n        assert b\"\".join(buf) == data\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"POST\", \"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with aiohttp.ClientSession() as session:\n        async with session.post(server.make_url(\"/\"), data=data) as resp:\n            assert resp.status == 200\n\n\nasync def test_request_tracing(aiohttp_server: AiohttpServer) -> None:\n    on_request_start = mock.AsyncMock()\n    on_request_end = mock.AsyncMock()\n    on_dns_resolvehost_start = mock.AsyncMock()\n    on_dns_resolvehost_end = mock.AsyncMock()\n    on_request_redirect = mock.AsyncMock()\n    on_connection_create_start = mock.AsyncMock()\n    on_connection_create_end = mock.AsyncMock()\n\n    async def redirector(request: web.Request) -> NoReturn:\n        raise web.HTTPFound(location=URL(\"/redirected\"))\n\n    async def redirected(request: web.Request) -> web.Response:\n        return web.Response()\n\n    trace_config = TraceConfig()\n\n    trace_config.on_request_start.append(on_request_start)\n    trace_config.on_request_end.append(on_request_end)\n    trace_config.on_request_redirect.append(on_request_redirect)\n    trace_config.on_connection_create_start.append(on_connection_create_start)\n    trace_config.on_connection_create_end.append(on_connection_create_end)\n    trace_config.on_dns_resolvehost_start.append(on_dns_resolvehost_start)\n    trace_config.on_dns_resolvehost_end.append(on_dns_resolvehost_end)\n\n    app = web.Application()\n    app.router.add_get(\"/redirector\", redirector)\n    app.router.add_get(\"/redirected\", redirected)\n    server = await aiohttp_server(app)\n\n    class FakeResolver(AbstractResolver):\n        _LOCAL_HOST = {0: \"127.0.0.1\", socket.AF_INET: \"127.0.0.1\"}\n\n        def __init__(self, fakes: dict[str, int]):\n            # fakes -- dns -> port dict\n            self._fakes = fakes\n            self._resolver = aiohttp.DefaultResolver()\n\n        async def close(self) -> None:\n            assert False\n\n        async def resolve(\n            self,\n            host: str,\n            port: int = 0,\n            family: socket.AddressFamily = socket.AF_INET,\n        ) -> list[ResolveResult]:\n            fake_port = self._fakes.get(host)\n            assert fake_port is not None\n            return [\n                {\n                    \"hostname\": host,\n                    \"host\": self._LOCAL_HOST[family],\n                    \"port\": fake_port,\n                    \"family\": socket.AF_INET,\n                    \"proto\": 0,\n                    \"flags\": socket.AI_NUMERICHOST,\n                }\n            ]\n\n    resolver = FakeResolver({\"example.com\": server.port})\n    connector = aiohttp.TCPConnector(resolver=resolver)\n    client = aiohttp.ClientSession(connector=connector, trace_configs=[trace_config])\n\n    resp = await client.get(\"http://example.com/redirector\", data=\"foo\")\n\n    assert on_request_start.called\n    assert on_request_end.called\n    assert on_dns_resolvehost_start.called\n    assert on_dns_resolvehost_end.called\n    assert on_request_redirect.called\n    assert on_connection_create_start.called\n    assert on_connection_create_end.called\n\n    resp.release()\n    await client.close()\n\n\nasync def test_raise_http_exception(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        raise web.HTTPForbidden()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert resp.status == 403\n    resp.release()\n\n\nasync def test_request_path(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert request.path_qs == \"/path%20to?a=1\"\n        assert request.path == \"/path to\"\n        assert request.raw_path == \"/path%20to?a=1\"\n        return web.Response(body=b\"OK\")\n\n    app = web.Application()\n    app.router.add_get(\"/path to\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/path to\", params={\"a\": \"1\"})\n    assert 200 == resp.status\n    txt = await resp.text()\n    assert \"OK\" == txt\n    resp.release()\n\n\nasync def test_app_add_routes(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app = web.Application()\n    app.add_routes([web.get(\"/get\", handler)])\n\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/get\")\n    assert resp.status == 200\n    resp.release()\n\n\nasync def test_request_headers_type(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        assert isinstance(request.headers, CIMultiDictProxy)\n        return web.Response()\n\n    app = web.Application()\n    app.add_routes([web.get(\"/get\", handler)])\n\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/get\")\n    assert resp.status == 200\n    resp.release()\n\n\nasync def test_signal_on_error_handler(aiohttp_client: AiohttpClient) -> None:\n    async def on_prepare(request: web.Request, response: web.StreamResponse) -> None:\n        response.headers[\"X-Custom\"] = \"val\"\n\n    app = web.Application()\n    app.on_response_prepare.append(on_prepare)\n\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/\")\n    assert resp.status == 404\n    assert resp.headers[\"X-Custom\"] == \"val\"\n    resp.release()\n\n\n@pytest.mark.skipif(\n    \"HttpRequestParserC\" not in dir(aiohttp.http_parser),\n    reason=\"C based HTTP parser not available\",\n)\nasync def test_bad_method_for_c_http_parser_not_hangs(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    app = web.Application()\n    timeout = aiohttp.ClientTimeout(sock_read=0.2)\n    client = await aiohttp_client(app, timeout=timeout)\n    resp = await client.request(\"GET1\", \"/\")\n    assert 400 == resp.status\n\n\nasync def test_read_bufsize(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        ret = request.content.get_read_buffer_limits()\n        data = await request.text()  # read posted data\n        return web.Response(text=f\"{data} {ret!r}\")\n\n    app = web.Application(handler_args={\"read_bufsize\": 2})\n    app.router.add_post(\"/\", handler)\n\n    client = await aiohttp_client(app)\n    resp = await client.post(\"/\", data=b\"data\")\n    assert resp.status == 200\n    assert await resp.text() == \"data (2, 4)\"\n    resp.release()\n\n\n@pytest.mark.parametrize(\n    \"auto_decompress,len_of\", [(True, \"uncompressed\"), (False, \"compressed\")]\n)\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_auto_decompress(\n    aiohttp_client: AiohttpClient,\n    auto_decompress: bool,\n    len_of: str,\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        data = await request.read()\n        return web.Response(text=str(len(data)))\n\n    app = web.Application(handler_args={\"auto_decompress\": auto_decompress})\n    app.router.add_post(\"/\", handler)\n\n    client = await aiohttp_client(app)\n    uncompressed = b\"dataaaaaaaaaaaaaaaaaaaaaaaaa\"\n    compressor = ZLibBackend.compressobj(wbits=16 + ZLibBackend.MAX_WBITS)\n    compressed = compressor.compress(uncompressed) + compressor.flush()\n    assert len(compressed) != len(uncompressed)\n    headers = {\"content-encoding\": \"gzip\"}\n    resp = await client.post(\"/\", data=compressed, headers=headers)\n    assert resp.status == 200\n    assert await resp.text() == str(len(locals()[len_of]))\n    resp.release()\n\n\n@pytest.mark.parametrize(\n    \"status\",\n    [101, 204],\n)\nasync def test_response_101_204_no_content_length_http11(\n    status: int, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(status=status)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app, version=HttpVersion11)\n    resp = await client.get(\"/\")\n    assert CONTENT_LENGTH not in resp.headers\n    assert TRANSFER_ENCODING not in resp.headers\n    resp.release()\n\n\nasync def test_stream_response_headers_204(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.StreamResponse:\n        return web.StreamResponse(status=204)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/\")\n    assert CONTENT_TYPE not in resp.headers\n    assert TRANSFER_ENCODING not in resp.headers\n    resp.release()\n\n\nasync def test_httpfound_cookies_302(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        resp = web.HTTPFound(\"/\")\n        resp.set_cookie(\"my-cookie\", \"cookie-value\")\n        raise resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\", allow_redirects=False)\n    assert \"my-cookie\" in resp.cookies\n    resp.release()\n\n\n@pytest.mark.parametrize(\"status\", (101, 204, 304))\n@pytest.mark.parametrize(\"version\", (HttpVersion10, HttpVersion11))\nasync def test_no_body_for_1xx_204_304_responses(\n    aiohttp_client: AiohttpClient, status: int, version: HttpVersion\n) -> None:\n    \"\"\"Test no body is present for for 1xx, 204, and 304 responses.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(status=status, body=b\"should not get to client\")\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app, version=version)\n    resp = await client.get(\"/\")\n    assert CONTENT_TYPE not in resp.headers\n    assert TRANSFER_ENCODING not in resp.headers\n    await resp.read() == b\"\"\n    resp.release()\n\n\nasync def test_keepalive_race_condition(aiohttp_client: AiohttpClient) -> None:\n    protocol: RequestHandler[web.Request] | None = None\n    orig_data_received = RequestHandler.data_received\n\n    def delay_received(self: RequestHandler[web.Request], data: bytes) -> None:\n        \"\"\"Emulate race condition.\n\n        The keepalive callback needs to be called between data_received() and\n        when start() resumes from the waiter set within data_received().\n        \"\"\"\n        orig_data_received(self, data)\n        if protocol is None:  # First request creating the keepalive connection.\n            return\n\n        assert self is protocol\n        assert protocol._keepalive_handle is not None\n        # Cancel existing callback that would run at some point in future.\n        protocol._keepalive_handle.cancel()\n        protocol._keepalive_handle = None\n\n        # Set next run time into the past and run callback manually.\n        protocol._next_keepalive_close_time = asyncio.get_running_loop().time() - 1\n        protocol._process_keepalive()\n\n    async def handler(request: web.Request) -> web.Response:\n        nonlocal protocol\n        protocol = request.protocol\n        return web.Response()\n\n    target = \"aiohttp.web_protocol.RequestHandler.data_received\"\n    with mock.patch(target, delay_received):\n        app = web.Application()\n        app.router.add_get(\"/\", handler)\n        client = await aiohttp_client(app)\n\n        # Open connection, so we have a keepalive connection and reference to protocol.\n        async with client.get(\"/\") as resp:\n            assert resp.status == 200\n        assert protocol is not None\n        # Make 2nd request which will hit the race condition.\n        async with client.get(\"/\") as resp:\n            assert resp.status == 200\n\n\nasync def test_keepalive_expires_on_time(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that the keepalive handle expires on time.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        body = await request.read()\n        assert b\"\" == body\n        return web.Response(body=b\"OK\")\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n\n    connector = aiohttp.TCPConnector(limit=1)\n    client = await aiohttp_client(app, connector=connector)\n\n    loop = asyncio.get_running_loop()\n    now = loop.time()\n\n    # Patch loop time so we can control when the keepalive timeout is processed\n    with mock.patch.object(loop, \"time\") as loop_time_mock:\n        loop_time_mock.return_value = now\n        resp1 = await client.get(\"/\")\n        await resp1.read()\n        request_handler = client.server.handler.connections[0]\n\n        # Ensure the keep alive handle is set\n        assert request_handler._keepalive_handle is not None\n\n        # Set the loop time to exactly the keepalive timeout\n        loop_time_mock.return_value = request_handler._next_keepalive_close_time\n\n        # sleep twice to ensure the keep alive timeout is processed\n        await asyncio.sleep(0)\n        await asyncio.sleep(0)\n\n        # Ensure the keep alive handle expires\n        assert request_handler._keepalive_handle is None\n"
  },
  {
    "path": "tests/test_web_log.py",
    "content": "import datetime\nimport logging\nimport platform\nimport sys\nfrom contextvars import ContextVar\nfrom typing import NoReturn\nfrom unittest import mock\n\nimport pytest\n\nimport aiohttp\nfrom aiohttp import web\nfrom aiohttp.abc import AbstractAccessLogger, AbstractAsyncAccessLogger\nfrom aiohttp.pytest_plugin import AiohttpClient, AiohttpRawServer, AiohttpServer\nfrom aiohttp.test_utils import make_mocked_request\nfrom aiohttp.typedefs import Handler\nfrom aiohttp.web_log import AccessLogger\nfrom aiohttp.web_response import Response\n\nif sys.version_info >= (3, 11):\n    from typing import Self\nelse:\n    from typing import Any as Self\n\nIS_PYPY = platform.python_implementation() == \"PyPy\"\n\n\ndef test_access_logger_format() -> None:\n    log_format = '%T \"%{ETag}o\" %X {X} %%P'\n    mock_logger = mock.Mock()\n    access_logger = AccessLogger(mock_logger, log_format)\n    expected = '%s \"%s\" %%X {X} %%%s'\n    assert expected == access_logger._log_format\n\n\n@pytest.mark.skipif(\n    IS_PYPY,\n    reason=\"\"\"\n    Because of patching :py:class:`datetime.datetime`, under PyPy it\n    fails in :py:func:`isinstance` call in\n    :py:meth:`datetime.datetime.__sub__` (called from\n    :py:meth:`aiohttp.AccessLogger._format_t`):\n\n    *** TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types\n\n    (Pdb) from datetime import datetime\n    (Pdb) isinstance(now, datetime)\n    *** TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types\n    (Pdb) datetime.__class__\n    <class 'unittest.mock.MagicMock'>\n    (Pdb) isinstance(now, datetime.__class__)\n    False\n\n    Ref: https://bitbucket.org/pypy/pypy/issues/1187/call-to-isinstance-in-__sub__-self-other\n    Ref: https://github.com/celery/celery/issues/811\n    Ref: https://stackoverflow.com/a/46102240/595220\n    \"\"\",\n)\n@pytest.mark.parametrize(\n    \"log_format,expected,extra\",\n    [\n        (\n            \"%t\",\n            \"[01/Jan/1843:00:29:56 +0800]\",\n            {\"request_start_time\": \"[01/Jan/1843:00:29:56 +0800]\"},\n        ),\n        (\n            '%a %t %P %r %s %b %T %Tf %D \"%{H1}i\" \"%{H2}i\"',\n            (\n                \"127.0.0.2 [01/Jan/1843:00:29:56 +0800] <42> \"\n                'GET /path HTTP/1.1 200 42 3 3.141593 3141593 \"a\" \"b\"'\n            ),\n            {\n                \"first_request_line\": \"GET /path HTTP/1.1\",\n                \"process_id\": \"<42>\",\n                \"remote_address\": \"127.0.0.2\",\n                \"request_start_time\": \"[01/Jan/1843:00:29:56 +0800]\",\n                \"request_time\": \"3\",\n                \"request_time_frac\": \"3.141593\",\n                \"request_time_micro\": \"3141593\",\n                \"response_size\": 42,\n                \"response_status\": 200,\n                \"request_header\": {\"H1\": \"a\", \"H2\": \"b\"},\n            },\n        ),\n    ],\n)\ndef test_access_logger_atoms(\n    monkeypatch: pytest.MonkeyPatch,\n    log_format: str,\n    expected: str,\n    extra: dict[str, object],\n) -> None:\n    class PatchedDatetime(datetime.datetime):\n        @classmethod\n        def now(cls, tz: datetime.tzinfo | None = None) -> Self:\n            assert tz is not None\n            # Simulate: real UTC time is 1842-12-31 16:30, convert to local tz\n            utc = datetime.datetime(1842, 12, 31, 16, 30, tzinfo=datetime.timezone.utc)\n            local = utc.astimezone(tz)\n            return cls(\n                local.year,\n                local.month,\n                local.day,\n                local.hour,\n                local.minute,\n                local.second,\n                tzinfo=tz,\n            )\n\n    monkeypatch.setattr(\"datetime.datetime\", PatchedDatetime)\n    # Mock localtime to return CST (+0800 = 28800 seconds)\n    mock_localtime = mock.Mock()\n    mock_localtime.return_value.tm_gmtoff = 28800\n    monkeypatch.setattr(\"aiohttp.web_log.time_mod.localtime\", mock_localtime)\n    # Clear cached timezone so it gets rebuilt with our mock\n    AccessLogger._cached_tz = None\n    AccessLogger._cached_tz_expires = 0.0\n    monkeypatch.setattr(\"os.getpid\", lambda: 42)\n    mock_logger = mock.Mock()\n    access_logger = AccessLogger(mock_logger, log_format)\n    request = mock.Mock(\n        headers={\"H1\": \"a\", \"H2\": \"b\"},\n        method=\"GET\",\n        path_qs=\"/path\",\n        version=aiohttp.HttpVersion(1, 1),\n        remote=\"127.0.0.2\",\n    )\n    response = mock.Mock(headers={}, body_length=42, status=200)\n    access_logger.log(request, response, 3.1415926)\n    assert not mock_logger.exception.called, mock_logger.exception.call_args\n\n    mock_logger.info.assert_called_with(expected, extra=extra)\n\n\n@pytest.mark.skipif(\n    IS_PYPY,\n    reason=\"PyPy has issues with patching datetime.datetime\",\n)\ndef test_access_logger_dst_timezone(monkeypatch: pytest.MonkeyPatch) -> None:\n    \"\"\"Test that _format_t uses the current local UTC offset, not a cached one.\n\n    This ensures timestamps are correct during DST transitions. The old\n    implementation used time.timezone which is a constant and doesn't\n    reflect DST changes.\n    \"\"\"\n    # Simulate a timezone that observes DST (e.g., US Eastern)\n    # During EST: UTC-5 (-18000s), during EDT: UTC-4 (-14400s)\n    gmtoff_est = -18000  # UTC-5\n    gmtoff_edt = -14400  # UTC-4\n\n    class PatchedDatetime(datetime.datetime):\n        @classmethod\n        def now(cls, tz: datetime.tzinfo | None = None) -> Self:\n            assert tz is not None\n            # Simulate: real UTC time is 07:00, convert to local tz\n            utc = datetime.datetime(2024, 3, 10, 7, 0, 0, tzinfo=datetime.timezone.utc)\n            local = utc.astimezone(tz)\n            return cls(\n                local.year,\n                local.month,\n                local.day,\n                local.hour,\n                local.minute,\n                local.second,\n                tzinfo=tz,\n            )\n\n    monkeypatch.setattr(\"datetime.datetime\", PatchedDatetime)\n    mock_localtime = mock.Mock()\n    mock_localtime.return_value.tm_gmtoff = gmtoff_est\n    monkeypatch.setattr(\"aiohttp.web_log.time_mod.localtime\", mock_localtime)\n    # Force cache refresh\n    AccessLogger._cached_tz = None\n    AccessLogger._cached_tz_expires = 0.0\n\n    mock_logger = mock.Mock()\n    access_logger = AccessLogger(mock_logger, \"%t\")\n    request = mock.Mock(\n        headers={}, method=\"GET\", path_qs=\"/\", version=(1, 1), remote=\"127.0.0.1\"\n    )\n    response = mock.Mock(headers={}, body_length=0, status=200)\n\n    # During EST (UTC-5): time is 07:00-05:00 = 02:00 EST\n    access_logger.log(request, response, 0.0)\n    call1 = mock_logger.info.call_args[0][0]\n    assert \"-0500\" in call1, f\"Expected EST offset in {call1}\"\n\n    mock_logger.reset_mock()\n\n    # Switch to EDT (UTC-4): force cache invalidation\n    mock_localtime.return_value.tm_gmtoff = gmtoff_edt\n    AccessLogger._cached_tz = None\n    AccessLogger._cached_tz_expires = 0.0\n    access_logger.log(request, response, 0.0)\n    call2 = mock_logger.info.call_args[0][0]\n    assert \"-0400\" in call2, f\"Expected EDT offset in {call2}\"\n\n    # Verify the hour changed too (02:00 -> 03:00)\n    assert \"02:00:00 -0500\" in call1\n    assert \"03:00:00 -0400\" in call2\n\n    # Verify cached tz works too\n    assert access_logger._cached_tz is not None\n    with mock.patch(\n        \"aiohttp.web_log.time_mod.time\",\n        return_value=access_logger._cached_tz_expires - 1,\n    ):\n        access_logger.log(request, response, 0.0)\n    call3 = mock_logger.info.call_args[0][0]\n    assert \"-0400\" in call3, f\"Expected EDT offset in {call3}\"\n\n\ndef test_access_logger_dicts() -> None:\n    log_format = \"%{User-Agent}i %{Content-Length}o %{None}i\"\n    mock_logger = mock.Mock()\n    access_logger = AccessLogger(mock_logger, log_format)\n    request = mock.Mock(\n        headers={\"User-Agent\": \"Mock/1.0\"}, version=(1, 1), remote=\"127.0.0.2\"\n    )\n    response = mock.Mock(headers={\"Content-Length\": 123})\n    access_logger.log(request, response, 0.0)\n    assert not mock_logger.error.called\n    expected = \"Mock/1.0 123 -\"\n    extra = {\n        \"request_header\": {\"User-Agent\": \"Mock/1.0\", \"None\": \"-\"},\n        \"response_header\": {\"Content-Length\": 123},\n    }\n\n    mock_logger.info.assert_called_with(expected, extra=extra)\n\n\ndef test_access_logger_unix_socket() -> None:\n    log_format = \"|%a|\"\n    mock_logger = mock.Mock()\n    access_logger = AccessLogger(mock_logger, log_format)\n    request = mock.Mock(headers={\"User-Agent\": \"Mock/1.0\"}, version=(1, 1), remote=\"\")\n    response = mock.Mock()\n    access_logger.log(request, response, 0.0)\n    assert not mock_logger.error.called\n    expected = \"||\"\n    mock_logger.info.assert_called_with(expected, extra={\"remote_address\": \"\"})\n\n\ndef test_logger_no_message() -> None:\n    mock_logger = mock.Mock()\n    access_logger = AccessLogger(mock_logger, \"%r %{content-type}i\")\n    extra_dict = {\n        \"first_request_line\": \"GET / HTTP/1.1\",\n        \"request_header\": {\"content-type\": \"-\"},\n    }\n\n    access_logger.log(make_mocked_request(\"GET\", \"/\"), web.Response(), 0.0)\n    mock_logger.info.assert_called_with(\"GET / HTTP/1.1 -\", extra=extra_dict)\n\n\ndef test_logger_internal_error() -> None:\n    mock_logger = mock.Mock()\n    access_logger = AccessLogger(mock_logger, \"%D\")\n    access_logger.log(make_mocked_request(\"GET\", \"/\"), web.Response(), \"invalid\")  # type: ignore[arg-type]\n    mock_logger.exception.assert_called_with(\"Error in logging\")\n\n\ndef test_logger_no_transport() -> None:\n    mock_logger = mock.Mock()\n    access_logger = AccessLogger(mock_logger, \"%a\")\n    access_logger.log(make_mocked_request(\"GET\", \"/\"), web.Response(), 0.0)\n    mock_logger.info.assert_called_with(\"-\", extra={\"remote_address\": \"-\"})\n\n\ndef test_logger_abc() -> None:\n    class Logger(AbstractAccessLogger):\n        def log(\n            self, request: web.BaseRequest, response: web.StreamResponse, time: float\n        ) -> None:\n            1 / 0\n\n    mock_logger = mock.Mock()\n    access_logger: AbstractAccessLogger = Logger(mock_logger, \"\")\n\n    with pytest.raises(ZeroDivisionError):\n        access_logger.log(make_mocked_request(\"GET\", \"/\"), web.Response(), 0.0)\n\n    class Logger2(AbstractAccessLogger):\n        def log(\n            self, request: web.BaseRequest, response: web.StreamResponse, time: float\n        ) -> None:\n            self.logger.info(\n                self.log_format.format(request=request, response=response, time=time)\n            )\n\n    mock_logger = mock.Mock()\n    access_logger = Logger2(mock_logger, \"{request} {response} {time}\")\n    access_logger.log(\"request\", \"response\", 1)  # type: ignore[arg-type]\n    mock_logger.info.assert_called_with(\"request response 1\")\n\n\nasync def test_exc_info_context(\n    aiohttp_raw_server: AiohttpRawServer, aiohttp_client: AiohttpClient\n) -> None:\n    exc_msg = None\n\n    class Logger(AbstractAccessLogger):\n        def log(\n            self, request: web.BaseRequest, response: web.StreamResponse, time: float\n        ) -> None:\n            nonlocal exc_msg\n            exc_msg = \"{0.__name__}: {1}\".format(*sys.exc_info())\n\n    async def handler(request: web.BaseRequest) -> NoReturn:\n        raise RuntimeError(\"intentional runtime error\")\n\n    logger = mock.Mock()\n    server = await aiohttp_raw_server(handler, access_log_class=Logger, logger=logger)\n    cli = await aiohttp_client(server)\n    resp = await cli.get(\"/path/to\", headers={\"Accept\": \"text/html\"})\n    assert resp.status == 500\n    assert exc_msg == \"RuntimeError: intentional runtime error\"\n\n\nasync def test_async_logger(\n    aiohttp_raw_server: AiohttpRawServer, aiohttp_client: AiohttpClient\n) -> None:\n    msg = None\n\n    class Logger(AbstractAsyncAccessLogger):\n        async def log(\n            self, request: web.BaseRequest, response: web.StreamResponse, time: float\n        ) -> None:\n            nonlocal msg\n            msg = f\"{request.path}: {response.status}\"\n\n    async def handler(request: web.BaseRequest) -> web.Response:\n        return Response(text=\"ok\")\n\n    logger = mock.Mock()\n    server = await aiohttp_raw_server(handler, access_log_class=Logger, logger=logger)\n    cli = await aiohttp_client(server)\n    resp = await cli.get(\"/path/to\", headers={\"Accept\": \"text/html\"})\n    assert resp.status == 200\n    assert msg == \"/path/to: 200\"\n\n\nasync def test_contextvars_logger(\n    aiohttp_server: AiohttpServer, aiohttp_client: AiohttpClient\n) -> None:\n    VAR = ContextVar[str](\"VAR\")\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    async def middleware(request: web.Request, handler: Handler) -> web.StreamResponse:\n        VAR.set(\"uuid\")\n        return await handler(request)\n\n    msg = None\n\n    class Logger(AbstractAccessLogger):\n        def log(\n            self, request: web.BaseRequest, response: web.StreamResponse, time: float\n        ) -> None:\n            nonlocal msg\n            msg = f\"contextvars: {VAR.get()}\"\n\n    app = web.Application(middlewares=[middleware])\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app, access_log_class=Logger)\n    client = await aiohttp_client(server)\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    assert msg == \"contextvars: uuid\"\n\n\ndef test_access_logger_feeds_logger(caplog: pytest.LogCaptureFixture) -> None:\n    \"\"\"Test that the logger still works.\"\"\"\n    mock_logger = logging.getLogger(\"test.aiohttp.log\")\n    mock_logger.setLevel(logging.INFO)\n    access_logger = AccessLogger(mock_logger, \"%b\")\n    access_logger.log(\n        mock.Mock(name=\"mock_request\"), mock.Mock(name=\"mock_response\"), 42\n    )\n    assert \"mock_response\" in caplog.text\n\n\nasync def test_logger_does_not_log_when_not_enabled(\n    aiohttp_server: AiohttpServer,\n    aiohttp_client: AiohttpClient,\n    caplog: pytest.LogCaptureFixture,\n) -> None:\n    \"\"\"Test logger does nothing when not enabled.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    class Logger(AbstractAccessLogger):\n\n        def log(\n            self, request: web.BaseRequest, response: web.StreamResponse, time: float\n        ) -> None:\n            self.logger.critical(\"This should not be logged\")  # pragma: no cover\n\n        @property\n        def enabled(self) -> bool:\n            return False\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app, access_log_class=Logger)\n    client = await aiohttp_client(server)\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    assert \"This should not be logged\" not in caplog.text\n\n\nasync def test_logger_set_to_none(\n    aiohttp_server: AiohttpServer,\n    aiohttp_client: AiohttpClient,\n    caplog: pytest.LogCaptureFixture,\n) -> None:\n    \"\"\"Test logger does nothing when access_log is set to None.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    class Logger(AbstractAccessLogger):\n\n        def log(\n            self, request: web.BaseRequest, response: web.StreamResponse, time: float\n        ) -> None:\n            self.logger.critical(\"This should not be logged\")  # pragma: no cover\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app, access_log=None, access_log_class=Logger)\n    client = await aiohttp_client(server)\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    assert \"This should not be logged\" not in caplog.text\n\n\nasync def test_logger_does_not_log_when_enabled_post_init(\n    aiohttp_server: AiohttpServer,\n    aiohttp_client: AiohttpClient,\n    caplog: pytest.LogCaptureFixture,\n) -> None:\n    \"\"\"Test logger does nothing when not enabled even if enabled post init.\"\"\"\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    enabled = False\n\n    class Logger(AbstractAccessLogger):\n\n        def log(\n            self, request: web.BaseRequest, response: web.StreamResponse, time: float\n        ) -> None:\n            self.logger.critical(\"This should not be logged\")  # pragma: no cover\n\n        @property\n        def enabled(self) -> bool:\n            \"\"\"Check if logger is enabled.\"\"\"\n            # Avoid formatting the log line if it will not be emitted.\n            return enabled\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    server = await aiohttp_server(app, access_log_class=Logger)\n    client = await aiohttp_client(server)\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    assert \"This should not be logged\" not in caplog.text\n    assert not server.handler.connections[0]._force_close\n\n    # mock enabling logging post-init\n    enabled = True\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    assert \"This should not be logged\" not in caplog.text\n    assert not server.handler.connections[0]._force_close\n"
  },
  {
    "path": "tests/test_web_middleware.py",
    "content": "import asyncio\nfrom collections.abc import Awaitable, Callable, Iterable\nfrom typing import NoReturn\n\nimport pytest\nfrom yarl import URL\n\nfrom aiohttp import web, web_app\nfrom aiohttp.pytest_plugin import AiohttpClient\nfrom aiohttp.test_utils import TestClient\nfrom aiohttp.typedefs import Handler, Middleware\n\nCLI = Callable[\n    [Iterable[Middleware]], Awaitable[TestClient[web.Request, web.Application]]\n]\n\n\nasync def test_middleware_modifies_response(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"OK\")\n\n    async def middleware(request: web.Request, handler: Handler) -> web.Response:\n        resp = await handler(request)\n        assert 200 == resp.status\n        resp.set_status(201)\n        assert isinstance(resp, web.Response)\n        assert resp.text is not None\n        resp.text = resp.text + \"[MIDDLEWARE]\"\n        return resp\n\n    app = web.Application()\n    app.middlewares.append(middleware)\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    # Call twice to verify cache works\n    for _ in range(2):\n        resp = await client.get(\"/\")\n        assert 201 == resp.status\n        txt = await resp.text()\n        assert \"OK[MIDDLEWARE]\" == txt\n\n\nasync def test_middleware_handles_exception(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        raise RuntimeError(\"Error text\")\n\n    async def middleware(request: web.Request, handler: Handler) -> web.Response:\n        with pytest.raises(RuntimeError) as ctx:\n            await handler(request)\n        return web.Response(status=501, text=str(ctx.value) + \"[MIDDLEWARE]\")\n\n    app = web.Application()\n    app.middlewares.append(middleware)\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    # Call twice to verify cache works\n    for _ in range(2):\n        resp = await client.get(\"/\")\n        assert 501 == resp.status\n        txt = await resp.text()\n        assert \"Error text[MIDDLEWARE]\" == txt\n\n\nasync def test_middleware_chain(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"OK\")\n\n    handler.annotation = \"annotation_value\"  # type: ignore[attr-defined]\n\n    async def handler2(request: web.Request) -> web.Response:\n        return web.Response(text=\"OK\")\n\n    middleware_annotation_seen_values = []\n\n    def make_middleware(num: int) -> Middleware:\n        async def middleware(request: web.Request, handler: Handler) -> web.Response:\n            middleware_annotation_seen_values.append(\n                getattr(handler, \"annotation\", None)\n            )\n            resp = await handler(request)\n            assert isinstance(resp, web.Response)\n            assert resp.text is not None\n            resp.text = resp.text + f\"[{num}]\"\n            return resp\n\n        return middleware\n\n    app = web.Application()\n    app.middlewares.append(make_middleware(1))\n    app.middlewares.append(make_middleware(2))\n    app.router.add_route(\"GET\", \"/\", handler)\n    app.router.add_route(\"GET\", \"/r2\", handler2)\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    txt = await resp.text()\n    assert \"OK[2][1]\" == txt\n    assert middleware_annotation_seen_values == [\"annotation_value\", \"annotation_value\"]\n\n    # check that attributes from handler are not applied to handler2\n    resp = await client.get(\"/r2\")\n    assert 200 == resp.status\n    assert middleware_annotation_seen_values == [\n        \"annotation_value\",\n        \"annotation_value\",\n        None,\n        None,\n    ]\n\n\nasync def test_middleware_subapp(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def sub_handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"OK\")\n\n    sub_handler.annotation = \"annotation_value\"  # type: ignore[attr-defined]\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"OK\")\n\n    middleware_annotation_seen_values = []\n\n    def make_middleware(num: int) -> Middleware:\n        async def middleware(\n            request: web.Request, handler: Handler\n        ) -> web.StreamResponse:\n            annotation = getattr(handler, \"annotation\", None)\n            if annotation is not None:\n                middleware_annotation_seen_values.append(f\"{annotation}/{num}\")\n            return await handler(request)\n\n        return middleware\n\n    app = web.Application()\n    app.middlewares.append(make_middleware(1))\n    app.router.add_route(\"GET\", \"/r2\", handler)\n\n    subapp = web.Application()\n    subapp.middlewares.append(make_middleware(2))\n    subapp.router.add_route(\"GET\", \"/\", sub_handler)\n    app.add_subapp(\"/sub\", subapp)\n\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/sub/\")\n    assert 200 == resp.status\n    await resp.text()\n    assert middleware_annotation_seen_values == [\n        \"annotation_value/1\",\n        \"annotation_value/2\",\n    ]\n\n    # check that attributes from sub_handler are not applied to handler\n    del middleware_annotation_seen_values[:]\n    resp = await client.get(\"/r2\")\n    assert 200 == resp.status\n    assert middleware_annotation_seen_values == []\n\n\n@pytest.fixture\ndef cli(loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient) -> CLI:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"OK\")\n\n    def wrapper(\n        extra_middlewares: Iterable[Middleware],\n    ) -> Awaitable[TestClient[web.Request, web.Application]]:\n        app = web.Application()\n        app.router.add_route(\"GET\", \"/resource1\", handler)\n        app.router.add_route(\"GET\", \"/resource2/\", handler)\n        app.router.add_route(\"GET\", \"/resource1/a/b\", handler)\n        app.router.add_route(\"GET\", \"/resource2/a/b/\", handler)\n        app.router.add_route(\"GET\", \"/resource2/a/b%2Fc/\", handler)\n        app.middlewares.extend(extra_middlewares)\n        return aiohttp_client(app, server_kwargs={\"skip_url_asserts\": True})\n\n    return wrapper\n\n\nclass TestNormalizePathMiddleware:\n    @pytest.mark.parametrize(\n        \"path, status\",\n        [\n            (\"/resource1\", 200),\n            (\"/resource1/\", 404),\n            (\"/resource2\", 200),\n            (\"/resource2/\", 200),\n            (\"/resource1?p1=1&p2=2\", 200),\n            (\"/resource1/?p1=1&p2=2\", 404),\n            (\"/resource2?p1=1&p2=2\", 200),\n            (\"/resource2/?p1=1&p2=2\", 200),\n            (\"/resource2/a/b%2Fc\", 200),\n            (\"/resource2/a/b%2Fc/\", 200),\n        ],\n    )\n    async def test_add_trailing_when_necessary(\n        self, path: str, status: int, cli: CLI\n    ) -> None:\n        extra_middlewares = [web.normalize_path_middleware(merge_slashes=False)]\n        client = await cli(extra_middlewares)\n\n        resp = await client.get(path)\n        assert resp.status == status\n        assert resp.url.query == URL(path).query\n\n    @pytest.mark.parametrize(\n        \"path, status\",\n        [\n            (\"/resource1\", 200),\n            (\"/resource1/\", 200),\n            (\"/resource2\", 404),\n            (\"/resource2/\", 200),\n            (\"/resource1?p1=1&p2=2\", 200),\n            (\"/resource1/?p1=1&p2=2\", 200),\n            (\"/resource2?p1=1&p2=2\", 404),\n            (\"/resource2/?p1=1&p2=2\", 200),\n            (\"/resource2/a/b%2Fc\", 404),\n            (\"/resource2/a/b%2Fc/\", 200),\n            (\"/resource12\", 404),\n            (\"/resource12345\", 404),\n        ],\n    )\n    async def test_remove_trailing_when_necessary(\n        self, path: str, status: int, cli: CLI\n    ) -> None:\n        extra_middlewares = [\n            web.normalize_path_middleware(\n                append_slash=False, remove_slash=True, merge_slashes=False\n            )\n        ]\n        client = await cli(extra_middlewares)\n\n        resp = await client.get(path)\n        assert resp.status == status\n        assert resp.url.query == URL(path).query\n\n    @pytest.mark.parametrize(\n        \"path, status\",\n        [\n            (\"/resource1\", 200),\n            (\"/resource1/\", 404),\n            (\"/resource2\", 404),\n            (\"/resource2/\", 200),\n            (\"/resource1?p1=1&p2=2\", 200),\n            (\"/resource1/?p1=1&p2=2\", 404),\n            (\"/resource2?p1=1&p2=2\", 404),\n            (\"/resource2/?p1=1&p2=2\", 200),\n            (\"/resource2/a/b%2Fc\", 404),\n            (\"/resource2/a/b%2Fc/\", 200),\n        ],\n    )\n    async def test_no_trailing_slash_when_disabled(\n        self, path: str, status: int, cli: CLI\n    ) -> None:\n        extra_middlewares = [\n            web.normalize_path_middleware(append_slash=False, merge_slashes=False)\n        ]\n        client = await cli(extra_middlewares)\n\n        resp = await client.get(path)\n        assert resp.status == status\n        assert resp.url.query == URL(path).query\n\n    @pytest.mark.parametrize(\n        \"path, status\",\n        [\n            (\"/resource1/a/b\", 200),\n            (\"//resource1//a//b\", 200),\n            (\"//resource1//a//b/\", 404),\n            (\"///resource1//a//b\", 200),\n            (\"/////resource1/a///b\", 200),\n            (\"/////resource1/a//b/\", 404),\n            (\"/resource1/a/b?p=1\", 200),\n            (\"//resource1//a//b?p=1\", 200),\n            (\"//resource1//a//b/?p=1\", 404),\n            (\"///resource1//a//b?p=1\", 200),\n            (\"/////resource1/a///b?p=1\", 200),\n            (\"/////resource1/a//b/?p=1\", 404),\n        ],\n    )\n    async def test_merge_slash(self, path: str, status: int, cli: CLI) -> None:\n        extra_middlewares = [web.normalize_path_middleware(append_slash=False)]\n        client = await cli(extra_middlewares)\n\n        resp = await client.get(path)\n        assert resp.status == status\n        assert resp.url.query == URL(path).query\n\n    @pytest.mark.parametrize(\n        \"path, status\",\n        [\n            (\"/resource1/a/b\", 200),\n            (\"/resource1/a/b/\", 404),\n            (\"//resource2//a//b\", 200),\n            (\"//resource2//a//b/\", 200),\n            (\"///resource1//a//b\", 200),\n            (\"///resource1//a//b/\", 404),\n            (\"/////resource1/a///b\", 200),\n            (\"/////resource1/a///b/\", 404),\n            (\"/resource2/a/b\", 200),\n            (\"//resource2//a//b\", 200),\n            (\"//resource2//a//b/\", 200),\n            (\"///resource2//a//b\", 200),\n            (\"///resource2//a//b/\", 200),\n            (\"/////resource2/a///b\", 200),\n            (\"/////resource2/a///b/\", 200),\n            (\"/resource1/a/b?p=1\", 200),\n            (\"/resource1/a/b/?p=1\", 404),\n            (\"//resource2//a//b?p=1\", 200),\n            (\"//resource2//a//b/?p=1\", 200),\n            (\"///resource1//a//b?p=1\", 200),\n            (\"///resource1//a//b/?p=1\", 404),\n            (\"/////resource1/a///b?p=1\", 200),\n            (\"/////resource1/a///b/?p=1\", 404),\n            (\"/resource2/a/b?p=1\", 200),\n            (\"//resource2//a//b?p=1\", 200),\n            (\"//resource2//a//b/?p=1\", 200),\n            (\"///resource2//a//b?p=1\", 200),\n            (\"///resource2//a//b/?p=1\", 200),\n            (\"/////resource2/a///b?p=1\", 200),\n            (\"/////resource2/a///b/?p=1\", 200),\n        ],\n    )\n    async def test_append_and_merge_slash(\n        self, path: str, status: int, cli: CLI\n    ) -> None:\n        extra_middlewares = [web.normalize_path_middleware()]\n\n        client = await cli(extra_middlewares)\n        resp = await client.get(path)\n        assert resp.status == status\n        assert resp.url.query == URL(path).query\n\n    @pytest.mark.parametrize(\n        \"path, status\",\n        [\n            (\"/resource1/a/b\", 200),\n            (\"/resource1/a/b/\", 200),\n            (\"//resource2//a//b\", 404),\n            (\"//resource2//a//b/\", 200),\n            (\"///resource1//a//b\", 200),\n            (\"///resource1//a//b/\", 200),\n            (\"/////resource1/a///b\", 200),\n            (\"/////resource1/a///b/\", 200),\n            (\"/////resource1/a///b///\", 200),\n            (\"/resource2/a/b\", 404),\n            (\"//resource2//a//b\", 404),\n            (\"//resource2//a//b/\", 200),\n            (\"///resource2//a//b\", 404),\n            (\"///resource2//a//b/\", 200),\n            (\"/////resource2/a///b\", 404),\n            (\"/////resource2/a///b/\", 200),\n            (\"/resource1/a/b?p=1\", 200),\n            (\"/resource1/a/b/?p=1\", 200),\n            (\"//resource2//a//b?p=1\", 404),\n            (\"//resource2//a//b/?p=1\", 200),\n            (\"///resource1//a//b?p=1\", 200),\n            (\"///resource1//a//b/?p=1\", 200),\n            (\"/////resource1/a///b?p=1\", 200),\n            (\"/////resource1/a///b/?p=1\", 200),\n            (\"/resource2/a/b?p=1\", 404),\n            (\"//resource2//a//b?p=1\", 404),\n            (\"//resource2//a//b/?p=1\", 200),\n            (\"///resource2//a//b?p=1\", 404),\n            (\"///resource2//a//b/?p=1\", 200),\n            (\"/////resource2/a///b?p=1\", 404),\n            (\"/////resource2/a///b/?p=1\", 200),\n        ],\n    )\n    async def test_remove_and_merge_slash(\n        self, path: str, status: int, cli: CLI\n    ) -> None:\n        extra_middlewares = [\n            web.normalize_path_middleware(append_slash=False, remove_slash=True)\n        ]\n\n        client = await cli(extra_middlewares)\n        resp = await client.get(path)\n        assert resp.status == status\n        assert resp.url.query == URL(path).query\n\n    async def test_cannot_remove_and_add_slash(self) -> None:\n        with pytest.raises(AssertionError):\n            web.normalize_path_middleware(append_slash=True, remove_slash=True)\n\n    @pytest.mark.parametrize(\n        [\"append_slash\", \"remove_slash\"],\n        [\n            (True, False),\n            (False, True),\n            (False, False),\n        ],\n    )\n    async def test_open_redirects(\n        self, append_slash: bool, remove_slash: bool, aiohttp_client: AiohttpClient\n    ) -> None:\n        async def handle(request: web.Request) -> web.StreamResponse:\n            pytest.fail(\n                \"Security advisory 'GHSA-v6wp-4m6f-gcjg' test handler \"\n                \"matched unexpectedly\",\n                pytrace=False,\n            )\n\n        app = web.Application(\n            middlewares=[\n                web.normalize_path_middleware(\n                    append_slash=append_slash, remove_slash=remove_slash\n                )\n            ]\n        )\n        app.add_routes([web.get(\"/\", handle), web.get(\"/google.com\", handle)])\n        client = await aiohttp_client(app, server_kwargs={\"skip_url_asserts\": True})\n        resp = await client.get(\"//google.com\", allow_redirects=False)\n        assert resp.status == 308\n        assert resp.headers[\"Location\"] == \"/google.com\"\n        assert resp.url.query == URL(\"//google.com\").query\n\n\nasync def test_bug_3669(aiohttp_client: AiohttpClient) -> None:\n    async def paymethod(request: web.Request) -> NoReturn:\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/paymethod\", paymethod)\n    app.middlewares.append(\n        web.normalize_path_middleware(append_slash=False, remove_slash=True)\n    )\n\n    client = await aiohttp_client(app, server_kwargs={\"skip_url_asserts\": True})\n\n    resp = await client.get(\"/paymethods\")\n    assert resp.status == 404\n    assert resp.url.path != \"/paymethod\"\n\n\nasync def test_old_style_middleware(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def view_handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"OK\")\n\n    with pytest.deprecated_call(\n        match=r\"^Middleware decorator is deprecated since 4\\.0 and its \"\n        r\"behaviour is default, you can simply remove this decorator\\.$\",\n    ):\n\n        @web.middleware\n        async def middleware(request: web.Request, handler: Handler) -> web.Response:\n            resp = await handler(request)\n            assert 200 == resp.status\n            resp.set_status(201)\n            assert isinstance(resp, web.Response)\n            assert resp.text is not None\n            resp.text = resp.text + \"[old style middleware]\"\n            return resp\n\n    app = web.Application(middlewares=[middleware])\n    app.router.add_route(\"GET\", \"/\", view_handler)\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/\")\n    assert 201 == resp.status\n    txt = await resp.text()\n    assert \"OK[old style middleware]\" == txt\n\n\nasync def test_new_style_middleware_class(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"OK\")\n\n    class Middleware:\n        async def __call__(\n            self, request: web.Request, handler: Handler\n        ) -> web.Response:\n            resp = await handler(request)\n            assert 200 == resp.status\n            resp.set_status(201)\n            assert isinstance(resp, web.Response)\n            assert resp.text is not None\n            resp.text = resp.text + \"[new style middleware]\"\n            return resp\n\n    app = web.Application()\n    app.middlewares.append(Middleware())\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/\")\n    assert 201 == resp.status\n    txt = await resp.text()\n    assert \"OK[new style middleware]\" == txt\n\n\nasync def test_new_style_middleware_method(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response(body=b\"OK\")\n\n    class Middleware:\n        async def call(self, request: web.Request, handler: Handler) -> web.Response:\n            resp = await handler(request)\n            assert 200 == resp.status\n            resp.set_status(201)\n            assert isinstance(resp, web.Response)\n            assert resp.text is not None\n            resp.text = resp.text + \"[new style middleware]\"\n            return resp\n\n    app = web.Application()\n    app.middlewares.append(Middleware().call)\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.get(\"/\")\n    assert 201 == resp.status\n    txt = await resp.text()\n    assert \"OK[new style middleware]\" == txt\n\n\nasync def test_middleware_does_not_leak(aiohttp_client: AiohttpClient) -> None:\n    async def any_handler(request: web.Request) -> NoReturn:\n        assert False\n\n    class Middleware:\n        async def call(\n            self, request: web.Request, handler: Handler\n        ) -> web.StreamResponse:\n            return await handler(request)\n\n    app = web.Application()\n    app.router.add_route(\"POST\", \"/any\", any_handler)\n    app.middlewares.append(Middleware().call)\n\n    client = await aiohttp_client(app)\n\n    web_app._cached_build_middleware.cache_clear()\n    for _ in range(10):\n        resp = await client.get(\"/any\")\n        assert resp.status == 405\n    assert web_app._cached_build_middleware.cache_info().currsize < 10\n"
  },
  {
    "path": "tests/test_web_protocol.py",
    "content": "import asyncio\nfrom typing import Any, cast\nfrom unittest import mock\n\nfrom aiohttp.web_protocol import RequestHandler\n\n\nclass _DummyManager:\n    def __init__(self) -> None:\n        self.request_handler = mock.Mock()\n        self.request_factory = mock.Mock()\n\n\nclass _DummyParser:\n    def __init__(self) -> None:\n        self.received: list[bytes] = []\n\n    def feed_data(self, data: bytes) -> tuple[bool, bytes]:\n        self.received.append(data)\n        return False, b\"\"\n\n\ndef test_set_parser_does_not_call_data_received_cb_for_tail(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    handler: RequestHandler[Any] = RequestHandler(cast(Any, _DummyManager()), loop=loop)\n    handler._message_tail = b\"tail\"\n    cb = mock.Mock()\n    parser = _DummyParser()\n\n    handler.set_parser(parser, data_received_cb=cb)\n\n    cb.assert_not_called()\n    assert parser.received == [b\"tail\"]\n\n\ndef test_data_received_calls_data_received_cb(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    handler: RequestHandler[Any] = RequestHandler(cast(Any, _DummyManager()), loop=loop)\n    cb = mock.Mock()\n    parser = _DummyParser()\n\n    handler.set_parser(parser, data_received_cb=cb)\n    handler.data_received(b\"x\")\n\n    assert cb.call_count == 1\n    assert parser.received == [b\"x\"]\n"
  },
  {
    "path": "tests/test_web_request.py",
    "content": "import asyncio\nimport datetime\nimport logging\nimport socket\nimport ssl\nimport sys\nimport time\nimport weakref\nfrom collections.abc import Iterator, MutableMapping\nfrom typing import NoReturn\nfrom unittest import mock\n\nimport pytest\nfrom multidict import CIMultiDict, CIMultiDictProxy, MultiDict\nfrom yarl import URL\n\nfrom aiohttp import ETag, HttpVersion, web\nfrom aiohttp.base_protocol import BaseProtocol\nfrom aiohttp.http_exceptions import BadHttpMessage, LineTooLong\nfrom aiohttp.http_parser import RawRequestMessage\nfrom aiohttp.pytest_plugin import AiohttpClient\nfrom aiohttp.streams import StreamReader\nfrom aiohttp.test_utils import make_mocked_request\nfrom aiohttp.web_request import _FORWARDED_PAIR_RE\n\n\n@pytest.fixture\ndef protocol() -> mock.Mock:\n    return mock.Mock(_reading_paused=False)\n\n\ndef test_base_ctor() -> None:\n    message = RawRequestMessage(\n        \"GET\",\n        \"/path/to?a=1&b=2\",\n        HttpVersion(1, 1),\n        CIMultiDictProxy(CIMultiDict()),\n        (),\n        False,\n        None,\n        False,\n        False,\n        URL(\"/path/to?a=1&b=2\"),\n    )\n\n    req = web.BaseRequest(\n        message, mock.Mock(), mock.Mock(), mock.Mock(), mock.Mock(), mock.Mock()\n    )\n\n    assert \"GET\" == req.method\n    assert HttpVersion(1, 1) == req.version\n    # MacOS may return CamelCased host name, need .lower()\n    # FQDN can be wider than host, e.g.\n    # 'fv-az397-495' in 'fv-az397-495.internal.cloudapp.net'\n    assert req.host.lower() in socket.getfqdn().lower()\n    assert \"/path/to?a=1&b=2\" == req.path_qs\n    assert \"/path/to\" == req.path\n    assert \"a=1&b=2\" == req.query_string\n    assert CIMultiDict() == req.headers\n    assert () == req.raw_headers\n\n    get = req.query\n    assert MultiDict([(\"a\", \"1\"), (\"b\", \"2\")]) == get\n    # second call should return the same object\n    assert get is req.query\n\n    assert req.keep_alive\n\n    assert req\n\n\ndef test_ctor() -> None:\n    req = make_mocked_request(\"GET\", \"/path/to?a=1&b=2\")\n\n    assert \"GET\" == req.method\n    assert HttpVersion(1, 1) == req.version\n    # MacOS may return CamelCased host name, need .lower()\n    # FQDN can be wider than host, e.g.\n    # 'fv-az397-495' in 'fv-az397-495.internal.cloudapp.net'\n    assert req.host.lower() in socket.getfqdn().lower()\n    assert \"/path/to?a=1&b=2\" == req.path_qs\n    assert \"/path/to\" == req.path\n    assert \"a=1&b=2\" == req.query_string\n    assert CIMultiDict() == req.headers\n    assert () == req.raw_headers\n\n    get = req.query\n    assert MultiDict([(\"a\", \"1\"), (\"b\", \"2\")]) == get\n    # second call should return the same object\n    assert get is req.query\n\n    assert req.keep_alive\n\n    # just make sure that all lines of make_mocked_request covered\n    headers = CIMultiDict(FOO=\"bar\")\n    payload = mock.Mock()\n    protocol = mock.Mock()\n    app = mock.Mock()\n    req = make_mocked_request(\n        \"GET\",\n        \"/path/to?a=1&b=2\",\n        headers=headers,\n        protocol=protocol,\n        payload=payload,\n        app=app,\n    )\n    assert req.app is app\n    assert req.content is payload\n    assert req.protocol is protocol\n    assert req.transport is protocol.transport\n    assert req.headers == headers\n    assert req.raw_headers == ((b\"FOO\", b\"bar\"),)\n    assert req.task is req._task\n\n\ndef test_doubleslashes() -> None:\n    # NB: //foo/bar is an absolute URL with foo netloc and /bar path\n    req = make_mocked_request(\"GET\", \"/bar//foo/\")\n    assert \"/bar//foo/\" == req.path\n\n\ndef test_content_type_not_specified() -> None:\n    req = make_mocked_request(\"Get\", \"/\")\n    assert \"application/octet-stream\" == req.content_type\n\n\ndef test_content_type_from_spec() -> None:\n    req = make_mocked_request(\n        \"Get\", \"/\", CIMultiDict([(\"CONTENT-TYPE\", \"application/json\")])\n    )\n    assert \"application/json\" == req.content_type\n\n\ndef test_content_type_from_spec_with_charset() -> None:\n    req = make_mocked_request(\n        \"Get\", \"/\", CIMultiDict([(\"CONTENT-TYPE\", \"text/html; charset=UTF-8\")])\n    )\n    assert \"text/html\" == req.content_type\n    assert \"UTF-8\" == req.charset\n\n\ndef test_calc_content_type_on_getting_charset() -> None:\n    req = make_mocked_request(\n        \"Get\", \"/\", CIMultiDict([(\"CONTENT-TYPE\", \"text/html; charset=UTF-8\")])\n    )\n    assert \"UTF-8\" == req.charset\n    assert \"text/html\" == req.content_type\n\n\ndef test_urlencoded_querystring() -> None:\n    req = make_mocked_request(\"GET\", \"/yandsearch?text=%D1%82%D0%B5%D0%BA%D1%81%D1%82\")\n    assert {\"text\": \"текст\"} == req.query\n\n\ndef test_non_ascii_path() -> None:\n    req = make_mocked_request(\"GET\", \"/путь\")\n    assert \"/путь\" == req.path\n\n\ndef test_non_ascii_raw_path() -> None:\n    req = make_mocked_request(\"GET\", \"/путь\")\n    assert \"/путь\" == req.raw_path\n\n\ndef test_absolute_url() -> None:\n    req = make_mocked_request(\"GET\", \"https://example.com/path/to?a=1\")\n    assert req.url == URL(\"https://example.com/path/to?a=1\")\n    assert req.scheme == \"https\"\n    assert req.host == \"example.com\"\n    assert req.rel_url == URL.build(path=\"/path/to\", query={\"a\": \"1\"})\n\n\ndef test_clone_absolute_scheme() -> None:\n    req = make_mocked_request(\"GET\", \"https://example.com/path/to?a=1\")\n    assert req.scheme == \"https\"\n    req2 = req.clone(scheme=\"http\")\n    assert req2.scheme == \"http\"\n    assert req2.url.scheme == \"http\"\n\n\ndef test_clone_absolute_host() -> None:\n    req = make_mocked_request(\"GET\", \"https://example.com/path/to?a=1\")\n    assert req.host == \"example.com\"\n    req2 = req.clone(host=\"foo.test\")\n    assert req2.host == \"foo.test\"\n    assert req2.url.host == \"foo.test\"\n\n\ndef test_content_length() -> None:\n    req = make_mocked_request(\"Get\", \"/\", CIMultiDict([(\"CONTENT-LENGTH\", \"123\")]))\n\n    assert 123 == req.content_length\n\n\ndef test_range_to_slice_head() -> None:\n    req = make_mocked_request(\n        \"GET\", \"/\", headers=CIMultiDict([(\"RANGE\", \"bytes=0-499\")])\n    )\n    assert isinstance(req.http_range, slice)\n    assert req.http_range.start == 0 and req.http_range.stop == 500\n\n\ndef test_range_to_slice_mid() -> None:\n    req = make_mocked_request(\n        \"GET\", \"/\", headers=CIMultiDict([(\"RANGE\", \"bytes=500-999\")])\n    )\n    assert isinstance(req.http_range, slice)\n    assert req.http_range.start == 500 and req.http_range.stop == 1000\n\n\ndef test_range_to_slice_tail_start() -> None:\n    req = make_mocked_request(\n        \"GET\", \"/\", headers=CIMultiDict([(\"RANGE\", \"bytes=9500-\")])\n    )\n    assert isinstance(req.http_range, slice)\n    assert req.http_range.start == 9500 and req.http_range.stop is None\n\n\ndef test_range_to_slice_tail_stop() -> None:\n    req = make_mocked_request(\n        \"GET\", \"/\", headers=CIMultiDict([(\"RANGE\", \"bytes=-500\")])\n    )\n    assert isinstance(req.http_range, slice)\n    assert req.http_range.start == -500 and req.http_range.stop is None\n\n\ndef test_range_non_ascii() -> None:\n    # ५ = DEVANAGARI DIGIT FIVE\n    req = make_mocked_request(\"GET\", \"/\", headers=CIMultiDict([(\"RANGE\", \"bytes=4-५\")]))\n    with pytest.raises(ValueError, match=\"range not in acceptable format\"):\n        req.http_range\n\n\ndef test_non_keepalive_on_http10() -> None:\n    req = make_mocked_request(\"GET\", \"/\", version=HttpVersion(1, 0))\n    assert not req.keep_alive\n\n\ndef test_non_keepalive_on_closing() -> None:\n    req = make_mocked_request(\"GET\", \"/\", closing=True)\n    assert not req.keep_alive\n\n\nasync def test_call_POST_on_GET_request() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n\n    ret = await req.post()\n    assert CIMultiDict() == ret\n\n\nasync def test_call_POST_on_weird_content_type() -> None:\n    req = make_mocked_request(\n        \"POST\", \"/\", headers=CIMultiDict({\"CONTENT-TYPE\": \"something/weird\"})\n    )\n\n    ret = await req.post()\n    assert CIMultiDict() == ret\n\n\nasync def test_call_POST_twice() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n\n    ret1 = await req.post()\n    ret2 = await req.post()\n    assert ret1 is ret2\n\n\ndef test_no_request_cookies() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n\n    assert req.cookies == {}\n\n    cookies = req.cookies\n    assert cookies is req.cookies\n\n\ndef test_request_cookie() -> None:\n    headers = CIMultiDict(COOKIE=\"cookie1=value1; cookie2=value2\")\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n\n    assert req.cookies == {\"cookie1\": \"value1\", \"cookie2\": \"value2\"}\n\n\ndef test_request_cookie__set_item() -> None:\n    headers = CIMultiDict(COOKIE=\"name=value\")\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n\n    assert req.cookies == {\"name\": \"value\"}\n\n    with pytest.raises(TypeError):\n        req.cookies[\"my\"] = \"value\"  # type: ignore[index]\n\n\ndef test_request_cookies_with_special_characters() -> None:\n    \"\"\"Test that cookies with special characters in names are accepted.\n\n    This tests the fix for issue #2683 where cookies with special characters\n    like {, }, / in their names would cause a 500 error. The fix makes the\n    cookie parser more tolerant to handle real-world cookies.\n    \"\"\"\n    # Test cookie names with curly braces (e.g., ISAWPLB{DB45DF86-F806-407C-932C-D52A60E4019E})\n    headers = CIMultiDict(COOKIE=\"{test}=value1; normal=value2\")\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    # Both cookies should be parsed successfully\n    assert req.cookies == {\"{test}\": \"value1\", \"normal\": \"value2\"}\n\n    # Test cookie names with forward slash\n    headers = CIMultiDict(COOKIE=\"test/name=value1; valid=value2\")\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    assert req.cookies == {\"test/name\": \"value1\", \"valid\": \"value2\"}\n\n    # Test cookie names with various special characters\n    headers = CIMultiDict(\n        COOKIE=\"test{foo}bar=value1; test/path=value2; normal_cookie=value3\"\n    )\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    assert req.cookies == {\n        \"test{foo}bar\": \"value1\",\n        \"test/path\": \"value2\",\n        \"normal_cookie\": \"value3\",\n    }\n\n\ndef test_request_cookies_real_world_examples() -> None:\n    \"\"\"Test handling of real-world cookie examples from issue #2683.\"\"\"\n    # Example from the issue: ISAWPLB{DB45DF86-F806-407C-932C-D52A60E4019E}\n    headers = CIMultiDict(\n        COOKIE=\"ISAWPLB{DB45DF86-F806-407C-932C-D52A60E4019E}=val1; normal_cookie=val2\"\n    )\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    # All cookies should be parsed successfully\n    assert req.cookies == {\n        \"ISAWPLB{DB45DF86-F806-407C-932C-D52A60E4019E}\": \"val1\",\n        \"normal_cookie\": \"val2\",\n    }\n\n    # Multiple cookies with special characters\n    headers = CIMultiDict(\n        COOKIE=\"{cookie1}=val1; cookie/2=val2; cookie[3]=val3; cookie(4)=val4\"\n    )\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    assert req.cookies == {\n        \"{cookie1}\": \"val1\",\n        \"cookie/2\": \"val2\",\n        \"cookie[3]\": \"val3\",\n        \"cookie(4)\": \"val4\",\n    }\n\n\ndef test_request_cookies_edge_cases() -> None:\n    \"\"\"Test edge cases for cookie parsing.\"\"\"\n    # Empty cookie value\n    headers = CIMultiDict(COOKIE=\"test=; normal=value\")\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    assert req.cookies == {\"test\": \"\", \"normal\": \"value\"}\n\n    # Cookie with quoted value\n    headers = CIMultiDict(COOKIE='test=\"quoted value\"; normal=unquoted')\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    assert req.cookies == {\"test\": \"quoted value\", \"normal\": \"unquoted\"}\n\n\ndef test_request_cookies_many_invalid(caplog: pytest.LogCaptureFixture) -> None:\n    \"\"\"Test many invalid cookies doesn't cause too many logs.\"\"\"\n    bad = \"bad\" + chr(1) + \"name\"\n    cookie = \"; \".join(f\"{bad}{i}=1\" for i in range(3000))\n    req = make_mocked_request(\"GET\", \"/\", headers=CIMultiDict(COOKIE=cookie))\n\n    with caplog.at_level(logging.DEBUG):\n        cookies = req.cookies\n\n    assert len(caplog.record_tuples) == 1\n    _, level, msg = caplog.record_tuples[0]\n    assert level is logging.DEBUG\n    assert \"Cannot load cookie\" in msg\n    assert cookies == {}\n\n\ndef test_request_cookies_no_500_error() -> None:\n    \"\"\"Test that cookies with special characters don't cause 500 errors.\n\n    This specifically tests that issue #2683 is fixed - previously cookies\n    with characters like { } would cause CookieError and 500 responses.\n    \"\"\"\n    # This cookie format previously caused 500 errors\n    headers = CIMultiDict(COOKIE=\"ISAWPLB{DB45DF86-F806-407C-932C-D52A60E4019E}=test\")\n\n    # Should not raise any exception when accessing cookies\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    cookies = req.cookies  # This used to raise CookieError\n\n    # Verify the cookie was parsed successfully\n    assert \"ISAWPLB{DB45DF86-F806-407C-932C-D52A60E4019E}\" in cookies\n    assert cookies[\"ISAWPLB{DB45DF86-F806-407C-932C-D52A60E4019E}\"] == \"test\"\n\n\ndef test_request_cookies_quoted_values() -> None:\n    \"\"\"Test that quoted cookie values are handled consistently.\n\n    This tests the fix for issue #5397 where quoted cookie values were\n    handled inconsistently based on whether domain attributes were present.\n    The new parser should always unquote cookie values consistently.\n    \"\"\"\n    # Test simple quoted cookie value\n    headers = CIMultiDict(COOKIE='sess=\"quoted_value\"')\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    # Quotes should be removed consistently\n    assert req.cookies == {\"sess\": \"quoted_value\"}\n\n    # Test quoted cookie with semicolon in value\n    headers = CIMultiDict(COOKIE='data=\"value;with;semicolons\"')\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    assert req.cookies == {\"data\": \"value;with;semicolons\"}\n\n    # Test mixed quoted and unquoted cookies\n    headers = CIMultiDict(\n        COOKIE='quoted=\"value1\"; unquoted=value2; also_quoted=\"value3\"'\n    )\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    assert req.cookies == {\n        \"quoted\": \"value1\",\n        \"unquoted\": \"value2\",\n        \"also_quoted\": \"value3\",\n    }\n\n    # Test escaped quotes in cookie value\n    headers = CIMultiDict(COOKIE=r'escaped=\"value with \\\" quote\"')\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    assert req.cookies == {\"escaped\": 'value with \" quote'}\n\n    # Test empty quoted value\n    headers = CIMultiDict(COOKIE='empty=\"\"')\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    assert req.cookies == {\"empty\": \"\"}\n\n\ndef test_request_cookies_with_attributes() -> None:\n    \"\"\"Test that cookie attributes are parsed as cookies per RFC 6265.\n\n    Per RFC 6265 Section 5.4, Cookie headers contain only name-value pairs.\n    Names that match attribute names (Domain, Path, etc.) should be treated\n    as regular cookies, not as attributes.\n    \"\"\"\n    # Cookie with domain - both should be parsed as cookies\n    headers = CIMultiDict(COOKIE='sess=\"quoted_value\"; Domain=.example.com')\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    assert req.cookies == {\"sess\": \"quoted_value\", \"Domain\": \".example.com\"}\n\n    # Cookie with multiple attribute names - all parsed as cookies\n    headers = CIMultiDict(COOKIE='token=\"abc123\"; Path=/; Secure; HttpOnly')\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    assert req.cookies == {\"token\": \"abc123\", \"Path\": \"/\", \"Secure\": \"\", \"HttpOnly\": \"\"}\n\n    # Multiple cookies with attribute names mixed in\n    headers = CIMultiDict(\n        COOKIE='c1=\"v1\"; Domain=.example.com; c2=\"v2\"; Path=/api; c3=v3; Secure'\n    )\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    assert req.cookies == {\n        \"c1\": \"v1\",\n        \"Domain\": \".example.com\",\n        \"c2\": \"v2\",\n        \"Path\": \"/api\",\n        \"c3\": \"v3\",\n        \"Secure\": \"\",\n    }\n\n\ndef test_match_info() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    assert req._match_info is req.match_info\n\n\ndef test_request_is_mutable_mapping() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    assert isinstance(req, MutableMapping)\n    assert req  # even when the MutableMapping is empty, request should always be True\n    req[\"key\"] = \"value\"\n    assert \"value\" == req[\"key\"]\n\n\ndef test_request_delitem() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    req[\"key\"] = \"value\"\n    assert \"value\" == req[\"key\"]\n    del req[\"key\"]\n    assert \"key\" not in req\n\n\ndef test_request_len() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    assert len(req) == 0\n    req[\"key\"] = \"value\"\n    assert len(req) == 1\n\n\ndef test_request_iter() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    req[\"key\"] = \"value\"\n    req[\"key2\"] = \"value2\"\n    key3 = web.RequestKey(\"key3\", str)\n    req[key3] = \"value3\"\n    assert set(req) == {\"key\", \"key2\", key3}\n\n\ndef test_requestkey() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    key = web.RequestKey(\"key\", str)\n    req[key] = \"value\"\n    assert req[key] == \"value\"\n    assert len(req) == 1\n    del req[key]\n    assert len(req) == 0\n\n\ndef test_request_get_requestkey() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    key = web.RequestKey(\"key\", int)\n    assert req.get(key, \"foo\") == \"foo\"\n    req[key] = 5\n    assert req.get(key, \"foo\") == 5\n\n\ndef test_requestkey_repr_concrete() -> None:\n    key = web.RequestKey(\"key\", int)\n    assert repr(key) in (\n        \"<RequestKey(__channelexec__.key, type=int)>\",  # pytest-xdist\n        \"<RequestKey(__main__.key, type=int)>\",\n    )\n    key2 = web.RequestKey(\"key\", web.Request)\n    assert repr(key2) in (\n        # pytest-xdist:\n        \"<RequestKey(__channelexec__.key, type=aiohttp.web_request.Request)>\",\n        \"<RequestKey(__main__.key, type=aiohttp.web_request.Request)>\",\n    )\n\n\ndef test_requestkey_repr_nonconcrete() -> None:\n    key = web.RequestKey(\"key\", Iterator[int])\n    if sys.version_info < (3, 11):\n        assert repr(key) in (\n            # pytest-xdist:\n            \"<RequestKey(__channelexec__.key, type=collections.abc.Iterator)>\",\n            \"<RequestKey(__main__.key, type=collections.abc.Iterator)>\",\n        )\n    else:\n        assert repr(key) in (\n            # pytest-xdist:\n            \"<RequestKey(__channelexec__.key, type=collections.abc.Iterator[int])>\",\n            \"<RequestKey(__main__.key, type=collections.abc.Iterator[int])>\",\n        )\n\n\ndef test_requestkey_repr_annotated() -> None:\n    key = web.RequestKey[Iterator[int]](\"key\")\n    if sys.version_info < (3, 11):\n        assert repr(key) in (\n            # pytest-xdist:\n            \"<RequestKey(__channelexec__.key, type=collections.abc.Iterator)>\",\n            \"<RequestKey(__main__.key, type=collections.abc.Iterator)>\",\n        )\n    else:\n        assert repr(key) in (\n            # pytest-xdist:\n            \"<RequestKey(__channelexec__.key, type=collections.abc.Iterator[int])>\",\n            \"<RequestKey(__main__.key, type=collections.abc.Iterator[int])>\",\n        )\n\n\ndef test___repr__() -> None:\n    req = make_mocked_request(\"GET\", \"/path/to\")\n    assert \"<Request GET /path/to >\" == repr(req)\n\n\ndef test___repr___non_ascii_path() -> None:\n    req = make_mocked_request(\"GET\", \"/path/\\U0001f415\\U0001f308\")\n    assert \"<Request GET /path/\\\\U0001f415\\\\U0001f308 >\" == repr(req)\n\n\ndef test_http_scheme() -> None:\n    req = make_mocked_request(\"GET\", \"/\", headers={\"Host\": \"example.com\"})\n    assert \"http\" == req.scheme\n    assert req.secure is False\n\n\ndef test_https_scheme_by_ssl_transport() -> None:\n    context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n    req = make_mocked_request(\n        \"GET\", \"/\", headers={\"Host\": \"example.com\"}, sslcontext=context\n    )\n    assert \"https\" == req.scheme\n    assert req.secure is True\n\n\ndef test_single_forwarded_header() -> None:\n    header = \"by=identifier;for=identifier;host=identifier;proto=identifier\"\n    req = make_mocked_request(\"GET\", \"/\", headers=CIMultiDict({\"Forwarded\": header}))\n    assert req.forwarded[0][\"by\"] == \"identifier\"\n    assert req.forwarded[0][\"for\"] == \"identifier\"\n    assert req.forwarded[0][\"host\"] == \"identifier\"\n    assert req.forwarded[0][\"proto\"] == \"identifier\"\n\n\ndef test_forwarded_re_performance() -> None:\n    FORWARDED_RE_TIME_THRESHOLD_SECONDS = 0.08\n    value = \"{\" + \"f\" * 54773 + \"z\\x00a=v\"\n    start = time.perf_counter()\n    match = _FORWARDED_PAIR_RE.match(value)\n    elapsed = time.perf_counter() - start\n\n    # If this is taking more time, there's probably a performance/ReDoS issue.\n    assert elapsed < FORWARDED_RE_TIME_THRESHOLD_SECONDS, (\n        f\"Regex took {elapsed * 1000:.1f}ms, \"\n        f\"expected <{FORWARDED_RE_TIME_THRESHOLD_SECONDS * 1000:.0f}ms - potential ReDoS issue\"\n    )\n    # This example shouldn't produce a match either.\n    assert match is None\n\n\n@pytest.mark.parametrize(\n    \"forward_for_in, forward_for_out\",\n    [\n        (\"1.2.3.4:1234\", \"1.2.3.4:1234\"),\n        (\"1.2.3.4\", \"1.2.3.4\"),\n        ('\"[2001:db8:cafe::17]:1234\"', \"[2001:db8:cafe::17]:1234\"),\n        ('\"[2001:db8:cafe::17]\"', \"[2001:db8:cafe::17]\"),\n    ],\n)\ndef test_forwarded_node_identifier(forward_for_in: str, forward_for_out: str) -> None:\n    header = f\"for={forward_for_in}\"\n    req = make_mocked_request(\"GET\", \"/\", headers=CIMultiDict({\"Forwarded\": header}))\n    assert req.forwarded == ({\"for\": forward_for_out},)\n\n\ndef test_single_forwarded_header_camelcase() -> None:\n    header = \"bY=identifier;fOr=identifier;HOst=identifier;pRoTO=identifier\"\n    req = make_mocked_request(\"GET\", \"/\", headers=CIMultiDict({\"Forwarded\": header}))\n    assert req.forwarded[0][\"by\"] == \"identifier\"\n    assert req.forwarded[0][\"for\"] == \"identifier\"\n    assert req.forwarded[0][\"host\"] == \"identifier\"\n    assert req.forwarded[0][\"proto\"] == \"identifier\"\n\n\ndef test_single_forwarded_header_single_param() -> None:\n    header = \"BY=identifier\"\n    req = make_mocked_request(\"GET\", \"/\", headers=CIMultiDict({\"Forwarded\": header}))\n    assert req.forwarded[0][\"by\"] == \"identifier\"\n\n\ndef test_single_forwarded_header_multiple_param() -> None:\n    header = \"By=identifier1,BY=identifier2,  By=identifier3 ,  BY=identifier4\"\n    req = make_mocked_request(\"GET\", \"/\", headers=CIMultiDict({\"Forwarded\": header}))\n    assert len(req.forwarded) == 4\n    assert req.forwarded[0][\"by\"] == \"identifier1\"\n    assert req.forwarded[1][\"by\"] == \"identifier2\"\n    assert req.forwarded[2][\"by\"] == \"identifier3\"\n    assert req.forwarded[3][\"by\"] == \"identifier4\"\n\n\ndef test_single_forwarded_header_quoted_escaped() -> None:\n    header = r'BY=identifier;pROTO=\"\\lala lan\\d\\~ 123\\!&\"'\n    req = make_mocked_request(\"GET\", \"/\", headers=CIMultiDict({\"Forwarded\": header}))\n    assert req.forwarded[0][\"by\"] == \"identifier\"\n    assert req.forwarded[0][\"proto\"] == \"lala land~ 123!&\"\n\n\ndef test_single_forwarded_header_custom_param() -> None:\n    header = r'BY=identifier;PROTO=https;SOME=\"other, \\\"value\\\"\"'\n    req = make_mocked_request(\"GET\", \"/\", headers=CIMultiDict({\"Forwarded\": header}))\n    assert len(req.forwarded) == 1\n    assert req.forwarded[0][\"by\"] == \"identifier\"\n    assert req.forwarded[0][\"proto\"] == \"https\"\n    assert req.forwarded[0][\"some\"] == 'other, \"value\"'\n\n\ndef test_single_forwarded_header_empty_params() -> None:\n    # This is allowed by the grammar given in RFC 7239\n    header = \";For=identifier;;PROTO=https;;;\"\n    req = make_mocked_request(\"GET\", \"/\", headers=CIMultiDict({\"Forwarded\": header}))\n    assert req.forwarded[0][\"for\"] == \"identifier\"\n    assert req.forwarded[0][\"proto\"] == \"https\"\n\n\ndef test_single_forwarded_header_bad_separator() -> None:\n    header = \"BY=identifier PROTO=https\"\n    req = make_mocked_request(\"GET\", \"/\", headers=CIMultiDict({\"Forwarded\": header}))\n    assert \"proto\" not in req.forwarded[0]\n\n\ndef test_single_forwarded_header_injection1() -> None:\n    # We might receive a header like this if we're sitting behind a reverse\n    # proxy that blindly appends a forwarded-element without checking\n    # the syntax of existing field-values. We should be able to recover\n    # the appended element anyway.\n    header = 'for=_injected;by=\", for=_real'\n    req = make_mocked_request(\"GET\", \"/\", headers=CIMultiDict({\"Forwarded\": header}))\n    assert len(req.forwarded) == 2\n    assert \"by\" not in req.forwarded[0]\n    assert req.forwarded[1][\"for\"] == \"_real\"\n\n\ndef test_single_forwarded_header_injection2() -> None:\n    header = \"very bad syntax, for=_real\"\n    req = make_mocked_request(\"GET\", \"/\", headers=CIMultiDict({\"Forwarded\": header}))\n    assert len(req.forwarded) == 2\n    assert \"for\" not in req.forwarded[0]\n    assert req.forwarded[1][\"for\"] == \"_real\"\n\n\ndef test_single_forwarded_header_long_quoted_string() -> None:\n    header = 'for=\"' + \"\\\\\\\\\" * 5000 + '\"'\n    req = make_mocked_request(\"GET\", \"/\", headers=CIMultiDict({\"Forwarded\": header}))\n    assert req.forwarded[0][\"for\"] == \"\\\\\" * 5000\n\n\ndef test_multiple_forwarded_headers() -> None:\n    headers = CIMultiDict[str]()\n    headers.add(\"Forwarded\", \"By=identifier1;for=identifier2, BY=identifier3\")\n    headers.add(\"Forwarded\", \"By=identifier4;fOr=identifier5\")\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    assert len(req.forwarded) == 3\n    assert req.forwarded[0][\"by\"] == \"identifier1\"\n    assert req.forwarded[0][\"for\"] == \"identifier2\"\n    assert req.forwarded[1][\"by\"] == \"identifier3\"\n    assert req.forwarded[2][\"by\"] == \"identifier4\"\n    assert req.forwarded[2][\"for\"] == \"identifier5\"\n\n\ndef test_multiple_forwarded_headers_bad_syntax() -> None:\n    headers = CIMultiDict[str]()\n    headers.add(\"Forwarded\", \"for=_1;by=_2\")\n    headers.add(\"Forwarded\", \"invalid value\")\n    headers.add(\"Forwarded\", \"\")\n    headers.add(\"Forwarded\", \"for=_3;by=_4\")\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    assert len(req.forwarded) == 4\n    assert req.forwarded[0][\"for\"] == \"_1\"\n    assert \"for\" not in req.forwarded[1]\n    assert \"for\" not in req.forwarded[2]\n    assert req.forwarded[3][\"by\"] == \"_4\"\n\n\ndef test_multiple_forwarded_headers_injection() -> None:\n    headers = CIMultiDict[str]()\n    # This could be sent by an attacker, hoping to \"shadow\" the second header.\n    headers.add(\"Forwarded\", 'for=_injected;by=\"')\n    # This is added by our trusted reverse proxy.\n    headers.add(\"Forwarded\", \"for=_real;by=_actual_proxy\")\n    req = make_mocked_request(\"GET\", \"/\", headers=headers)\n    assert len(req.forwarded) == 2\n    assert \"by\" not in req.forwarded[0]\n    assert req.forwarded[1][\"for\"] == \"_real\"\n    assert req.forwarded[1][\"by\"] == \"_actual_proxy\"\n\n\ndef test_host_by_host_header() -> None:\n    req = make_mocked_request(\"GET\", \"/\", headers=CIMultiDict({\"Host\": \"example.com\"}))\n    assert req.host == \"example.com\"\n\n\ndef test_raw_headers() -> None:\n    req = make_mocked_request(\"GET\", \"/\", headers=CIMultiDict({\"X-HEADER\": \"aaa\"}))\n    assert req.raw_headers == ((b\"X-HEADER\", b\"aaa\"),)\n\n\ndef test_rel_url() -> None:\n    req = make_mocked_request(\"GET\", \"/path\")\n    assert URL(\"/path\") == req.rel_url\n\n\ndef test_url_url() -> None:\n    req = make_mocked_request(\"GET\", \"/path\", headers={\"HOST\": \"example.com\"})\n    assert URL(\"http://example.com/path\") == req.url\n\n\ndef test_url_non_default_port() -> None:\n    req = make_mocked_request(\"GET\", \"/path\", headers={\"HOST\": \"example.com:8123\"})\n    assert req.url == URL(\"http://example.com:8123/path\")\n\n\ndef test_url_ipv6() -> None:\n    req = make_mocked_request(\"GET\", \"/path\", headers={\"HOST\": \"[::1]:8123\"})\n    assert req.url == URL(\"http://[::1]:8123/path\")\n\n\ndef test_clone() -> None:\n    req = make_mocked_request(\"GET\", \"/path\")\n    req2 = req.clone()\n    assert req2.method == \"GET\"\n    assert req2.rel_url == URL(\"/path\")\n\n\ndef test_clone_client_max_size() -> None:\n    req = make_mocked_request(\"GET\", \"/path\", client_max_size=1024)\n    req2 = req.clone()\n    assert req._client_max_size == req2._client_max_size\n    assert req2._client_max_size == 1024\n\n\ndef test_clone_override_client_max_size() -> None:\n    req = make_mocked_request(\"GET\", \"/path\", client_max_size=1024)\n    req2 = req.clone(client_max_size=2048)\n    assert req2.client_max_size == 2048\n\n\ndef test_clone_method() -> None:\n    req = make_mocked_request(\"GET\", \"/path\")\n    req2 = req.clone(method=\"POST\")\n    assert req2.method == \"POST\"\n    assert req2.rel_url == URL(\"/path\")\n\n\ndef test_clone_rel_url() -> None:\n    req = make_mocked_request(\"GET\", \"/path\")\n    req2 = req.clone(rel_url=URL(\"/path2\"))\n    assert req2.rel_url == URL(\"/path2\")\n\n\ndef test_clone_rel_url_str() -> None:\n    req = make_mocked_request(\"GET\", \"/path\")\n    req2 = req.clone(rel_url=\"/path2\")\n    assert req2.rel_url == URL(\"/path2\")\n\n\ndef test_clone_headers() -> None:\n    req = make_mocked_request(\"GET\", \"/path\", headers={\"A\": \"B\"})\n    req2 = req.clone(headers=CIMultiDict({\"B\": \"C\"}))\n    assert req2.headers == CIMultiDict({\"B\": \"C\"})\n    assert req2.raw_headers == ((b\"B\", b\"C\"),)\n\n\ndef test_clone_headers_dict() -> None:\n    req = make_mocked_request(\"GET\", \"/path\", headers={\"A\": \"B\"})\n    req2 = req.clone(headers={\"B\": \"C\"})\n    assert req2.headers == CIMultiDict({\"B\": \"C\"})\n    assert req2.raw_headers == ((b\"B\", b\"C\"),)\n\n\nasync def test_cannot_clone_after_read(protocol: BaseProtocol) -> None:\n    payload = StreamReader(protocol, 2**16, loop=asyncio.get_event_loop())\n    payload.feed_data(b\"data\")\n    payload.feed_eof()\n    req = make_mocked_request(\"GET\", \"/path\", payload=payload)\n    await req.read()\n    with pytest.raises(RuntimeError):\n        req.clone()\n\n\nasync def test_make_too_big_request(protocol: BaseProtocol) -> None:\n    payload = StreamReader(protocol, 2**16, loop=asyncio.get_event_loop())\n    large_file = 1024**2 * b\"x\"\n    too_large_file = large_file + b\"x\"\n    payload.feed_data(too_large_file)\n    payload.feed_eof()\n    req = make_mocked_request(\"POST\", \"/\", payload=payload)\n    with pytest.raises(web.HTTPRequestEntityTooLarge) as err:\n        await req.read()\n\n    assert err.value.status_code == 413\n\n\nasync def test_request_with_wrong_content_type_encoding(protocol: BaseProtocol) -> None:\n    payload = StreamReader(protocol, 2**16, loop=asyncio.get_event_loop())\n    payload.feed_data(b\"{}\")\n    payload.feed_eof()\n    headers = {\"Content-Type\": \"text/html; charset=test\"}\n    req = make_mocked_request(\"POST\", \"/\", payload=payload, headers=headers)\n\n    with pytest.raises(web.HTTPUnsupportedMediaType) as err:\n        await req.text()\n    assert err.value.status_code == 415\n\n\nasync def test_make_too_big_request_same_size_to_max(protocol: BaseProtocol) -> None:\n    payload = StreamReader(protocol, 2**16, loop=asyncio.get_event_loop())\n    large_file = 1024**2 * b\"x\"\n    payload.feed_data(large_file)\n    payload.feed_eof()\n    req = make_mocked_request(\"POST\", \"/\", payload=payload)\n    resp_text = await req.read()\n\n    assert resp_text == large_file\n\n\nasync def test_make_too_big_request_adjust_limit(protocol: BaseProtocol) -> None:\n    payload = StreamReader(protocol, 2**16, loop=asyncio.get_event_loop())\n    large_file = 1024**2 * b\"x\"\n    too_large_file = large_file + b\"x\"\n    payload.feed_data(too_large_file)\n    payload.feed_eof()\n    max_size = 1024**2 + 2\n    req = make_mocked_request(\"POST\", \"/\", payload=payload, client_max_size=max_size)\n    txt = await req.read()\n    assert len(txt) == 1024**2 + 1\n\n\nasync def test_multipart_formdata(protocol: BaseProtocol) -> None:\n    payload = StreamReader(protocol, 2**16, loop=asyncio.get_event_loop())\n    payload.feed_data(\n        b\"-----------------------------326931944431359\\r\\n\"\n        b'Content-Disposition: form-data; name=\"a\"\\r\\n'\n        b\"\\r\\n\"\n        b\"b\\r\\n\"\n        b\"-----------------------------326931944431359\\r\\n\"\n        b'Content-Disposition: form-data; name=\"c\"\\r\\n'\n        b\"\\r\\n\"\n        b\"d\\r\\n\"\n        b\"-----------------------------326931944431359--\\r\\n\"\n    )\n    content_type = (\n        \"multipart/form-data; boundary=---------------------------326931944431359\"\n    )\n    payload.feed_eof()\n    req = make_mocked_request(\n        \"POST\", \"/\", headers={\"CONTENT-TYPE\": content_type}, payload=payload\n    )\n    result = await req.post()\n    assert dict(result) == {\"a\": \"b\", \"c\": \"d\"}\n\n\nasync def test_multipart_formdata_field_missing_name(protocol: BaseProtocol) -> None:\n    # Ensure ValueError is raised when Content-Disposition has no name\n    payload = StreamReader(protocol, 2**16, loop=asyncio.get_event_loop())\n    payload.feed_data(\n        b\"-----------------------------326931944431359\\r\\n\"\n        b\"Content-Disposition: form-data\\r\\n\"  # Missing name!\n        b\"\\r\\n\"\n        b\"value\\r\\n\"\n        b\"-----------------------------326931944431359--\\r\\n\"\n    )\n    content_type = (\n        \"multipart/form-data; boundary=---------------------------326931944431359\"\n    )\n    payload.feed_eof()\n    req = make_mocked_request(\n        \"POST\", \"/\", headers={\"CONTENT-TYPE\": content_type}, payload=payload\n    )\n    with pytest.raises(ValueError, match=\"Multipart field missing name\"):\n        await req.post()\n\n\nasync def test_multipart_formdata_file(protocol: BaseProtocol) -> None:\n    # Make sure file uploads work, even without a content type\n    payload = StreamReader(protocol, 2**16, loop=asyncio.get_event_loop())\n    payload.feed_data(\n        b\"-----------------------------326931944431359\\r\\n\"\n        b'Content-Disposition: form-data; name=\"a_file\"; filename=\"binary\"\\r\\n'\n        b\"\\r\\n\"\n        b\"\\ff\\r\\n\"\n        b\"-----------------------------326931944431359--\\r\\n\"\n    )\n    content_type = (\n        \"multipart/form-data; boundary=---------------------------326931944431359\"\n    )\n    payload.feed_eof()\n    req = make_mocked_request(\n        \"POST\", \"/\", headers={\"CONTENT-TYPE\": content_type}, payload=payload\n    )\n    result = await req.post()\n    assert hasattr(result[\"a_file\"], \"file\")\n    content = result[\"a_file\"].file.read()\n    assert content == b\"\\ff\"\n\n    req._finish()\n\n\nasync def test_multipart_formdata_headers_too_many(protocol: BaseProtocol) -> None:\n    many = b\"\".join(f\"X-{i}: a\\r\\n\".encode() for i in range(130))\n    body = (\n        b\"--b\\r\\n\"\n        b'Content-Disposition: form-data; name=\"a\"\\r\\n' + many + b\"\\r\\n1\\r\\n\"\n        b\"--b--\\r\\n\"\n    )\n    content_type = \"multipart/form-data; boundary=b\"\n    payload = StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n    payload.feed_data(body)\n    payload.feed_eof()\n    req = make_mocked_request(\n        \"POST\",\n        \"/\",\n        headers={\"CONTENT-TYPE\": content_type},\n        payload=payload,\n    )\n\n    with pytest.raises(BadHttpMessage, match=\"Too many headers received\"):\n        await req.post()\n\n\nasync def test_multipart_formdata_header_too_long(protocol: BaseProtocol) -> None:\n    k = b\"t\" * 4100\n    body = (\n        b\"--b\\r\\n\"\n        b'Content-Disposition: form-data; name=\"a\"\\r\\n'\n        + k\n        + b\":\"\n        + k\n        + b\"\\r\\n\"\n        + b\"\\r\\n1\\r\\n\"\n        b\"--b--\\r\\n\"\n    )\n    content_type = \"multipart/form-data; boundary=b\"\n    payload = StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())\n    payload.feed_data(body)\n    payload.feed_eof()\n    req = make_mocked_request(\n        \"POST\",\n        \"/\",\n        headers={\"CONTENT-TYPE\": content_type},\n        payload=payload,\n    )\n\n    match = \"400, message:\\n  Got more than 8190 bytes when reading\"\n    with pytest.raises(LineTooLong, match=match):\n        await req.post()\n\n\nasync def test_make_too_big_request_limit_None(protocol: BaseProtocol) -> None:\n    payload = StreamReader(protocol, 2**16, loop=asyncio.get_event_loop())\n    large_file = 1024**2 * b\"x\"\n    too_large_file = large_file + b\"x\"\n    payload.feed_data(too_large_file)\n    payload.feed_eof()\n    req = make_mocked_request(\"POST\", \"/\", payload=payload, client_max_size=0)\n    txt = await req.read()\n    assert len(txt) == 1024**2 + 1\n\n\ndef test_remote_peername_tcp() -> None:\n    transp = mock.Mock()\n    transp.get_extra_info.return_value = (\"10.10.10.10\", 1234)\n    req = make_mocked_request(\"GET\", \"/\", transport=transp)\n    assert req.remote == \"10.10.10.10\"\n\n\ndef test_remote_peername_unix() -> None:\n    transp = mock.Mock()\n    transp.get_extra_info.return_value = \"/path/to/sock\"\n    req = make_mocked_request(\"GET\", \"/\", transport=transp)\n    assert req.remote == \"/path/to/sock\"\n\n\ndef test_save_state_on_clone() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    req[\"key\"] = \"val\"\n    req2 = req.clone()\n    req2[\"key\"] = \"val2\"\n    assert req[\"key\"] == \"val\"\n    assert req2[\"key\"] == \"val2\"\n\n\ndef test_clone_scheme() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    assert req.scheme == \"http\"\n    req2 = req.clone(scheme=\"https\")\n    assert req2.scheme == \"https\"\n    assert req2.url.scheme == \"https\"\n\n\ndef test_clone_host() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    assert req.host != \"example.com\"\n    req2 = req.clone(host=\"example.com\")\n    assert req2.host == \"example.com\"\n    assert req2.url.host == \"example.com\"\n\n\ndef test_clone_remote() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    assert req.remote != \"11.11.11.11\"\n    req2 = req.clone(remote=\"11.11.11.11\")\n    assert req2.remote == \"11.11.11.11\"\n\n\ndef test_remote_with_closed_transport() -> None:\n    transp = mock.Mock()\n    transp.get_extra_info.return_value = (\"10.10.10.10\", 1234)\n    req = make_mocked_request(\"GET\", \"/\", transport=transp)\n    req._protocol = None  # type: ignore[assignment]\n    assert req.remote == \"10.10.10.10\"\n\n\ndef test_url_http_with_closed_transport() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    req._protocol = None  # type: ignore[assignment]\n    assert str(req.url).startswith(\"http://\")\n\n\ndef test_url_https_with_closed_transport() -> None:\n    c = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n    req = make_mocked_request(\"GET\", \"/\", sslcontext=c)\n    req._protocol = None  # type: ignore[assignment]\n    assert str(req.url).startswith(\"https://\")\n\n\nasync def test_get_extra_info() -> None:\n    valid_key = \"test\"\n    valid_value = \"existent\"\n    default_value = \"default\"\n\n    def get_extra_info(name: str, default: object = None) -> object:\n        return {valid_key: valid_value}.get(name, default)\n\n    transp = mock.Mock()\n    transp.get_extra_info.side_effect = get_extra_info\n    req = make_mocked_request(\"GET\", \"/\", transport=transp)\n\n    assert req is not None\n    req_extra_info = req.get_extra_info(valid_key, default_value)\n    assert req._protocol.transport is not None\n    transp_extra_info = req._protocol.transport.get_extra_info(valid_key, default_value)\n    assert req_extra_info == transp_extra_info\n\n    req._protocol.transport = None\n    extra_info = req.get_extra_info(valid_key, default_value)\n    assert extra_info == default_value\n\n\ndef test_eq() -> None:\n    req1 = make_mocked_request(\"GET\", \"/path/to?a=1&b=2\")\n    req2 = make_mocked_request(\"GET\", \"/path/to?a=1&b=2\")\n    assert req1 != req2\n    assert req1 == req1\n\n\nasync def test_json(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> web.Response:\n        body_text = await request.text()\n        assert body_text == '{\"some\": \"data\"}'\n        assert request.headers[\"Content-Type\"] == \"application/json\"\n        body_json = await request.json()\n        assert body_json == {\"some\": \"data\"}\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    json_data = {\"some\": \"data\"}\n    async with client.post(\"/\", json=json_data) as resp:\n        assert 200 == resp.status\n\n\nasync def test_json_invalid_content_type(aiohttp_client: AiohttpClient) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        body_text = await request.text()\n        assert body_text == '{\"some\": \"data\"}'\n        assert request.headers[\"Content-Type\"] == \"text/plain\"\n        await request.json()  # raises HTTP 400\n        assert False\n\n    app = web.Application()\n    app.router.add_post(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    json_data = {\"some\": \"data\"}\n    headers = {\"Content-Type\": \"text/plain\"}\n    async with client.post(\"/\", json=json_data, headers=headers) as resp:\n        assert 400 == resp.status\n        resp_text = await resp.text()\n        assert resp_text == (\n            \"Attempt to decode JSON with unexpected mimetype: text/plain\"\n        )\n\n\ndef test_weakref_creation() -> None:\n    req = make_mocked_request(\"GET\", \"/\")\n    weakref.ref(req)\n\n\n@pytest.mark.parametrize(\n    (\"header\", \"header_attr\"),\n    (\n        pytest.param(\"If-Match\", \"if_match\"),\n        pytest.param(\"If-None-Match\", \"if_none_match\"),\n    ),\n)\n@pytest.mark.parametrize(\n    (\"header_val\", \"expected\"),\n    (\n        pytest.param(\n            '\"67ab43\", W/\"54ed21\", \"7892,dd\"',\n            (\n                ETag(is_weak=False, value=\"67ab43\"),\n                ETag(is_weak=True, value=\"54ed21\"),\n                ETag(is_weak=False, value=\"7892,dd\"),\n            ),\n        ),\n        pytest.param(\n            '\"bfc1ef-5b2c2730249c88ca92d82d\"',\n            (ETag(is_weak=False, value=\"bfc1ef-5b2c2730249c88ca92d82d\"),),\n        ),\n        pytest.param(\n            '\"valid-tag\", \"also-valid-tag\",somegarbage\"last-tag\"',\n            (\n                ETag(is_weak=False, value=\"valid-tag\"),\n                ETag(is_weak=False, value=\"also-valid-tag\"),\n            ),\n        ),\n        pytest.param(\n            '\"ascii\", \"это точно не ascii\", \"ascii again\"',\n            (ETag(is_weak=False, value=\"ascii\"),),\n        ),\n        pytest.param(\n            \"*\",\n            (ETag(is_weak=False, value=\"*\"),),\n        ),\n    ),\n)\ndef test_etag_headers(\n    header: str, header_attr: str, header_val: str, expected: tuple[ETag, ...]\n) -> None:\n    req = make_mocked_request(\"GET\", \"/\", headers={header: header_val})\n    assert getattr(req, header_attr) == expected\n\n\n@pytest.mark.parametrize(\n    (\"header\", \"header_attr\"),\n    (\n        pytest.param(\"If-Modified-Since\", \"if_modified_since\"),\n        pytest.param(\"If-Unmodified-Since\", \"if_unmodified_since\"),\n        pytest.param(\"If-Range\", \"if_range\"),\n    ),\n)\n@pytest.mark.parametrize(\n    (\"header_val\", \"expected\"),\n    (\n        pytest.param(\"xxyyzz\", None),\n        pytest.param(\"Tue, 08 Oct 4446413 00:56:40 GMT\", None),\n        pytest.param(\"Tue, 08 Oct 2000 00:56:80 GMT\", None),\n        pytest.param(\n            \"Tue, 08 Oct 2000 00:56:40 GMT\",\n            datetime.datetime(2000, 10, 8, 0, 56, 40, tzinfo=datetime.timezone.utc),\n        ),\n    ),\n)\ndef test_datetime_headers(\n    header: str,\n    header_attr: str,\n    header_val: str,\n    expected: datetime.datetime | None,\n) -> None:\n    req = make_mocked_request(\"GET\", \"/\", headers={header: header_val})\n    assert getattr(req, header_attr) == expected\n"
  },
  {
    "path": "tests/test_web_request_handler.py",
    "content": "from unittest import mock\n\nfrom aiohttp import web\n\n\nasync def serve(request: web.BaseRequest) -> web.Response:\n    assert False\n\n\nasync def test_repr() -> None:\n    manager = web.Server(serve)\n    handler = manager()\n\n    assert \"<RequestHandler disconnected>\" == repr(handler)\n\n    with mock.patch.object(handler, \"transport\", autospec=True):\n        assert \"<RequestHandler connected>\" == repr(handler)\n\n\nasync def test_connections() -> None:\n    manager = web.Server(serve)\n    assert manager.connections == []\n\n    handler = mock.Mock(spec_set=web.RequestHandler)\n    handler._task_handler = None\n    transport = object()\n    manager.connection_made(handler, transport)  # type: ignore[arg-type]\n    assert manager.connections == [handler]\n\n    manager.connection_lost(handler, None)\n    assert manager.connections == []\n\n\nasync def test_shutdown_no_timeout() -> None:\n    manager = web.Server(serve)\n\n    handler = mock.Mock(spec_set=web.RequestHandler)\n    handler._task_handler = None\n    handler.shutdown = mock.AsyncMock(return_value=mock.Mock())\n    transport = mock.Mock()\n    manager.connection_made(handler, transport)\n\n    await manager.shutdown()\n\n    manager.connection_lost(handler, None)\n    assert manager.connections == []\n    handler.shutdown.assert_called_with(None)\n\n\nasync def test_shutdown_timeout() -> None:\n    manager = web.Server(serve)\n\n    handler = mock.Mock()\n    handler.shutdown = mock.AsyncMock(return_value=mock.Mock())\n    transport = mock.Mock()\n    manager.connection_made(handler, transport)\n\n    await manager.shutdown(timeout=0.1)\n\n    manager.connection_lost(handler, None)\n    assert manager.connections == []\n    handler.shutdown.assert_called_with(0.1)\n"
  },
  {
    "path": "tests/test_web_response.py",
    "content": "import collections.abc\nimport datetime\nimport gzip\nimport io\nimport json\nimport re\nimport sys\nimport weakref\nfrom collections.abc import AsyncIterator, Iterator\nfrom concurrent.futures import ThreadPoolExecutor\nfrom unittest import mock\n\nimport aiosignal\nimport pytest\nfrom multidict import CIMultiDict, CIMultiDictProxy, MultiDict\n\nfrom aiohttp import HttpVersion, HttpVersion10, HttpVersion11, hdrs, web\nfrom aiohttp.abc import AbstractStreamWriter\nfrom aiohttp.helpers import ETag\nfrom aiohttp.http_writer import StreamWriter, _serialize_headers\nfrom aiohttp.multipart import BodyPartReader, MultipartWriter\nfrom aiohttp.payload import BytesPayload, StringPayload\nfrom aiohttp.test_utils import make_mocked_request\nfrom aiohttp.typedefs import LooseHeaders\n\n\ndef make_request(\n    method: str,\n    path: str,\n    headers: LooseHeaders = CIMultiDict(),\n    version: HttpVersion = HttpVersion11,\n    *,\n    app: web.Application | None = None,\n    writer: AbstractStreamWriter | None = None,\n) -> web.Request:\n    if app is None:\n        app = mock.create_autospec(\n            web.Application, spec_set=True, on_response_prepare=aiosignal.Signal(app)\n        )\n    app.on_response_prepare.freeze()\n    return make_mocked_request(\n        method, path, headers, version=version, app=app, writer=writer\n    )\n\n\n@pytest.fixture\ndef buf() -> bytearray:\n    return bytearray()\n\n\n@pytest.fixture\ndef writer(buf: bytearray) -> AbstractStreamWriter:\n    writer = mock.create_autospec(AbstractStreamWriter, spec_set=True)\n\n    async def write_headers(status_line: str, headers: CIMultiDict[str]) -> None:\n        b_headers = _serialize_headers(status_line, headers)\n        buf.extend(b_headers)\n\n    async def write_eof(chunk: bytes = b\"\") -> None:\n        buf.extend(chunk)\n\n    writer.write_eof.side_effect = write_eof\n    writer.write_headers.side_effect = write_headers\n\n    return writer  # type: ignore[no-any-return]\n\n\ndef test_stream_response_ctor() -> None:\n    resp = web.StreamResponse()\n    assert 200 == resp.status\n    assert resp.keep_alive is None\n\n    assert resp.task is None\n\n    req = mock.Mock()\n    resp._req = req\n    assert resp.task is req.task\n\n\ndef test_stream_response_hashable() -> None:\n    # should not raise exception\n    hash(web.StreamResponse())\n\n\ndef test_stream_response_eq() -> None:\n    resp1 = web.StreamResponse()\n    resp2 = web.StreamResponse()\n\n    assert resp1 == resp1\n    assert not resp1 == resp2\n\n\ndef test_stream_response_is_mutable_mapping() -> None:\n    resp = web.StreamResponse()\n    assert isinstance(resp, collections.abc.MutableMapping)\n    assert resp  # even when the MutableMapping is empty, response should always be True\n    resp[\"key\"] = \"value\"\n    assert \"value\" == resp[\"key\"]\n\n\ndef test_stream_response_delitem() -> None:\n    resp = web.StreamResponse()\n    resp[\"key\"] = \"value\"\n    del resp[\"key\"]\n    assert \"key\" not in resp\n\n\ndef test_stream_response_len() -> None:\n    resp = web.StreamResponse()\n    assert len(resp) == 0\n    resp[\"key\"] = \"value\"\n    assert len(resp) == 1\n\n\ndef test_response_iter() -> None:\n    resp = web.StreamResponse()\n    resp[\"key\"] = \"value\"\n    resp[\"key2\"] = \"value2\"\n    key3 = web.ResponseKey(\"key3\", str)\n    resp[key3] = \"value3\"\n    assert set(resp) == {\"key\", \"key2\", key3}\n\n\ndef test_responsekey() -> None:\n    resp = web.StreamResponse()\n    key = web.ResponseKey(\"key\", str)\n    resp[key] = \"value\"\n    assert resp[key] == \"value\"\n    assert len(resp) == 1\n    del resp[key]\n    assert len(resp) == 0\n\n\ndef test_response_get_responsekey() -> None:\n    resp = web.StreamResponse()\n    key = web.ResponseKey(\"key\", int)\n    assert resp.get(key, \"foo\") == \"foo\"\n    resp[key] = 5\n    assert resp.get(key, \"foo\") == 5\n\n\ndef test_responsekey_repr_concrete() -> None:\n    key = web.ResponseKey(\"key\", int)\n    assert repr(key) in (\n        \"<ResponseKey(__channelexec__.key, type=int)>\",  # pytest-xdist\n        \"<ResponseKey(__main__.key, type=int)>\",\n    )\n    key2 = web.ResponseKey(\"key\", web.Request)\n    assert repr(key2) in (\n        # pytest-xdist:\n        \"<ResponseKey(__channelexec__.key, type=aiohttp.web_request.Request)>\",\n        \"<ResponseKey(__main__.key, type=aiohttp.web_request.Request)>\",\n    )\n\n\ndef test_responsekey_repr_nonconcrete() -> None:\n    key = web.ResponseKey(\"key\", Iterator[int])\n    if sys.version_info < (3, 11):\n        assert repr(key) in (\n            # pytest-xdist:\n            \"<ResponseKey(__channelexec__.key, type=collections.abc.Iterator)>\",\n            \"<ResponseKey(__main__.key, type=collections.abc.Iterator)>\",\n        )\n    else:\n        assert repr(key) in (\n            # pytest-xdist:\n            \"<ResponseKey(__channelexec__.key, type=collections.abc.Iterator[int])>\",\n            \"<ResponseKey(__main__.key, type=collections.abc.Iterator[int])>\",\n        )\n\n\ndef test_responsekey_repr_annotated() -> None:\n    key = web.ResponseKey[Iterator[int]](\"key\")\n    if sys.version_info < (3, 11):\n        assert repr(key) in (\n            # pytest-xdist:\n            \"<ResponseKey(__channelexec__.key, type=collections.abc.Iterator)>\",\n            \"<ResponseKey(__main__.key, type=collections.abc.Iterator)>\",\n        )\n    else:\n        assert repr(key) in (\n            # pytest-xdist:\n            \"<ResponseKey(__channelexec__.key, type=collections.abc.Iterator[int])>\",\n            \"<ResponseKey(__main__.key, type=collections.abc.Iterator[int])>\",\n        )\n\n\ndef test_content_length() -> None:\n    resp = web.StreamResponse()\n    assert resp.content_length is None\n\n\ndef test_content_length_setter() -> None:\n    resp = web.StreamResponse()\n\n    resp.content_length = 234\n    assert 234 == resp.content_length\n\n\ndef test_content_length_setter_with_enable_chunked_encoding() -> None:\n    resp = web.StreamResponse()\n\n    resp.enable_chunked_encoding()\n    with pytest.raises(RuntimeError):\n        resp.content_length = 234\n\n\ndef test_drop_content_length_header_on_setting_len_to_None() -> None:\n    resp = web.StreamResponse()\n\n    resp.content_length = 1\n    assert \"1\" == resp.headers[\"Content-Length\"]\n    resp.content_length = None\n    assert \"Content-Length\" not in resp.headers\n\n\ndef test_set_content_length_to_None_on_non_set() -> None:\n    resp = web.StreamResponse()\n\n    resp.content_length = None\n    assert \"Content-Length\" not in resp.headers\n    resp.content_length = None\n    assert \"Content-Length\" not in resp.headers\n\n\ndef test_setting_content_type() -> None:\n    resp = web.StreamResponse()\n\n    resp.content_type = \"text/html\"\n    assert \"text/html\" == resp.headers[\"content-type\"]\n\n\ndef test_setting_charset() -> None:\n    resp = web.StreamResponse()\n\n    resp.content_type = \"text/html\"\n    resp.charset = \"koi8-r\"\n    assert \"text/html; charset=koi8-r\" == resp.headers[\"content-type\"]\n\n\ndef test_default_charset() -> None:\n    resp = web.StreamResponse()\n\n    assert resp.charset is None\n\n\ndef test_reset_charset() -> None:\n    resp = web.StreamResponse()\n\n    resp.content_type = \"text/html\"\n    resp.charset = None\n    assert resp.charset is None\n\n\ndef test_reset_charset_after_setting() -> None:\n    resp = web.StreamResponse()\n\n    resp.content_type = \"text/html\"\n    resp.charset = \"koi8-r\"\n    resp.charset = None\n    assert resp.charset is None\n\n\ndef test_charset_without_content_type() -> None:\n    resp = web.StreamResponse()\n\n    with pytest.raises(RuntimeError):\n        resp.charset = \"koi8-r\"\n\n\ndef test_last_modified_initial() -> None:\n    resp = web.StreamResponse()\n    assert resp.last_modified is None\n\n\ndef test_last_modified_string() -> None:\n    resp = web.StreamResponse()\n\n    dt = datetime.datetime(1990, 1, 2, 3, 4, 5, 0, datetime.timezone.utc)\n    resp.last_modified = \"Mon, 2 Jan 1990 03:04:05 GMT\"\n    assert resp.last_modified == dt\n\n\ndef test_last_modified_timestamp() -> None:\n    resp = web.StreamResponse()\n\n    dt = datetime.datetime(1970, 1, 1, 0, 0, 0, 0, datetime.timezone.utc)\n\n    resp.last_modified = 0\n    assert resp.last_modified == dt\n\n    resp.last_modified = 0.0\n    assert resp.last_modified == dt\n\n\ndef test_last_modified_datetime() -> None:\n    resp = web.StreamResponse()\n\n    dt = datetime.datetime(2001, 2, 3, 4, 5, 6, 0, datetime.timezone.utc)\n    resp.last_modified = dt\n    assert resp.last_modified == dt\n\n\ndef test_last_modified_reset() -> None:\n    resp = web.StreamResponse()\n\n    resp.last_modified = 0\n    resp.last_modified = None\n    assert resp.last_modified is None\n\n\ndef test_last_modified_invalid_type() -> None:\n    resp = web.StreamResponse()\n\n    with pytest.raises(TypeError, match=\"Unsupported type for last_modified: object\"):\n        resp.last_modified = object()  # type: ignore[assignment]\n\n\n@pytest.mark.parametrize(\n    \"header_val\",\n    (\n        \"xxyyzz\",\n        \"Tue, 08 Oct 4446413 00:56:40 GMT\",\n        \"Tue, 08 Oct 2000 00:56:80 GMT\",\n    ),\n)\ndef test_last_modified_string_invalid(header_val: str) -> None:\n    resp = web.StreamResponse(headers={\"Last-Modified\": header_val})\n    assert resp.last_modified is None\n\n\ndef test_etag_initial() -> None:\n    resp = web.StreamResponse()\n    assert resp.etag is None\n\n\ndef test_etag_string() -> None:\n    resp = web.StreamResponse()\n    value = \"0123-kotik\"\n    resp.etag = value\n    assert resp.etag == ETag(value=value)\n    assert resp.headers[hdrs.ETAG] == f'\"{value}\"'\n\n\n@pytest.mark.parametrize(\n    (\"etag\", \"expected_header\"),\n    (\n        (ETag(value=\"0123-weak-kotik\", is_weak=True), 'W/\"0123-weak-kotik\"'),\n        (ETag(value=\"0123-strong-kotik\", is_weak=False), '\"0123-strong-kotik\"'),\n    ),\n)\ndef test_etag_class(etag: ETag, expected_header: str) -> None:\n    resp = web.StreamResponse()\n    resp.etag = etag\n    assert resp.etag == etag\n    assert resp.headers[hdrs.ETAG] == expected_header\n\n\ndef test_etag_any() -> None:\n    resp = web.StreamResponse()\n    resp.etag = \"*\"\n    assert resp.etag == ETag(value=\"*\")\n    assert resp.headers[hdrs.ETAG] == \"*\"\n\n\n@pytest.mark.parametrize(\n    \"invalid_value\",\n    (\n        '\"invalid\"',\n        \"повинен бути ascii\",\n        ETag(value='\"invalid\"', is_weak=True),\n        ETag(value=\"bad ©®\"),\n    ),\n)\ndef test_etag_invalid_value_set(invalid_value: str | ETag) -> None:\n    resp = web.StreamResponse()\n    with pytest.raises(ValueError, match=\"is not a valid etag\"):\n        resp.etag = invalid_value\n\n\n@pytest.mark.parametrize(\n    \"header\",\n    (\n        \"forgotten quotes\",\n        '\"∀ x ∉ ascii\"',\n    ),\n)\ndef test_etag_invalid_value_get(header: str) -> None:\n    resp = web.StreamResponse()\n    resp.headers[\"ETag\"] = header\n    assert resp.etag is None\n\n\n@pytest.mark.parametrize(\"invalid\", (123, ETag(value=123, is_weak=True)))  # type: ignore[arg-type]\ndef test_etag_invalid_value_class(invalid: int | ETag) -> None:\n    resp = web.StreamResponse()\n    with pytest.raises(ValueError, match=\"Unsupported etag type\"):\n        resp.etag = invalid  # type: ignore[assignment]\n\n\ndef test_etag_reset() -> None:\n    resp = web.StreamResponse()\n    resp.etag = \"*\"\n    resp.etag = None\n    assert resp.etag is None\n\n\nasync def test_start() -> None:\n    req = make_request(\"GET\", \"/\")\n    resp = web.StreamResponse()\n    assert resp.keep_alive is None\n\n    msg = await resp.prepare(req)\n\n    assert msg is not None\n    assert msg.write_headers.called  # type: ignore[attr-defined]\n    msg2 = await resp.prepare(req)\n    assert msg is msg2\n\n    assert resp.keep_alive\n\n    req2 = make_request(\"GET\", \"/\")  # type: ignore[unreachable]\n    # with pytest.raises(RuntimeError):\n    msg3 = await resp.prepare(req2)\n    assert msg is msg3\n\n\nasync def test_chunked_encoding() -> None:\n    req = make_request(\"GET\", \"/\")\n    resp = web.StreamResponse()\n    assert not resp.chunked\n\n    resp.enable_chunked_encoding()\n    assert resp.chunked\n\n    msg = await resp.prepare(req)  # type: ignore[unreachable]\n    assert msg.chunked\n\n\ndef test_enable_chunked_encoding_with_content_length() -> None:\n    resp = web.StreamResponse()\n\n    resp.content_length = 234\n    with pytest.raises(RuntimeError):\n        resp.enable_chunked_encoding()\n\n\nasync def test_chunked_encoding_forbidden_for_http_10() -> None:\n    req = make_request(\"GET\", \"/\", version=HttpVersion10)\n    resp = web.StreamResponse()\n    resp.enable_chunked_encoding()\n\n    with pytest.raises(RuntimeError) as ctx:\n        await resp.prepare(req)\n    assert str(ctx.value) == \"Using chunked encoding is forbidden for HTTP/1.0\"\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_compression_no_accept() -> None:\n    req = make_request(\"GET\", \"/\")\n    resp = web.StreamResponse()\n    assert not resp.chunked\n\n    assert not resp.compression\n    resp.enable_compression()\n    assert resp.compression\n\n    msg = await resp.prepare(req)  # type: ignore[unreachable]\n    assert not msg.enable_compression.called\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_compression_default_coding() -> None:\n    req = make_request(\n        \"GET\", \"/\", headers=CIMultiDict({hdrs.ACCEPT_ENCODING: \"gzip, deflate\"})\n    )\n    resp = web.StreamResponse()\n    assert not resp.chunked\n\n    assert not resp.compression\n    resp.enable_compression()\n    assert resp.compression\n\n    msg = await resp.prepare(req)  # type: ignore[unreachable]\n\n    msg.enable_compression.assert_called_with(\"deflate\", None)\n    assert \"deflate\" == resp.headers.get(hdrs.CONTENT_ENCODING)\n    assert msg.filter is not None\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_force_compression_deflate() -> None:\n    req = make_request(\n        \"GET\", \"/\", headers=CIMultiDict({hdrs.ACCEPT_ENCODING: \"gzip, deflate\"})\n    )\n    resp = web.StreamResponse()\n\n    resp.enable_compression(web.ContentCoding.deflate)\n    assert resp.compression\n\n    msg = await resp.prepare(req)\n    assert msg is not None\n    msg.enable_compression.assert_called_with(\"deflate\", None)  # type: ignore[attr-defined]\n    assert \"deflate\" == resp.headers.get(hdrs.CONTENT_ENCODING)\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_force_compression_deflate_large_payload() -> None:\n    \"\"\"Make sure a warning is thrown for large payloads compressed in the event loop.\"\"\"\n    req = make_request(\n        \"GET\", \"/\", headers=CIMultiDict({hdrs.ACCEPT_ENCODING: \"gzip, deflate\"})\n    )\n    resp = web.Response(body=b\"large\")\n\n    resp.enable_compression(web.ContentCoding.deflate)\n    assert resp.compression\n\n    with (\n        pytest.warns(Warning, match=\"Synchronous compression of large response bodies\"),\n        mock.patch(\"aiohttp.web_response.LARGE_BODY_SIZE\", 2),\n    ):\n        msg = await resp.prepare(req)\n        assert msg is not None\n    assert \"deflate\" == resp.headers.get(hdrs.CONTENT_ENCODING)\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_force_compression_no_accept_deflate() -> None:\n    req = make_request(\"GET\", \"/\")\n    resp = web.StreamResponse()\n\n    resp.enable_compression(web.ContentCoding.deflate)\n    assert resp.compression\n\n    msg = await resp.prepare(req)\n    assert msg is not None\n    msg.enable_compression.assert_called_with(\"deflate\", None)  # type: ignore[attr-defined]\n    assert \"deflate\" == resp.headers.get(hdrs.CONTENT_ENCODING)\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_force_compression_gzip() -> None:\n    req = make_request(\n        \"GET\", \"/\", headers=CIMultiDict({hdrs.ACCEPT_ENCODING: \"gzip, deflate\"})\n    )\n    resp = web.StreamResponse()\n\n    resp.enable_compression(web.ContentCoding.gzip)\n    assert resp.compression\n\n    msg = await resp.prepare(req)\n    assert msg is not None\n    msg.enable_compression.assert_called_with(\"gzip\", None)  # type: ignore[attr-defined]\n    assert \"gzip\" == resp.headers.get(hdrs.CONTENT_ENCODING)\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_force_compression_no_accept_gzip() -> None:\n    req = make_request(\"GET\", \"/\")\n    resp = web.StreamResponse()\n\n    resp.enable_compression(web.ContentCoding.gzip)\n    assert resp.compression\n\n    msg = await resp.prepare(req)\n    assert msg is not None\n    msg.enable_compression.assert_called_with(\"gzip\", None)  # type: ignore[attr-defined]\n    assert \"gzip\" == resp.headers.get(hdrs.CONTENT_ENCODING)\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_change_content_threaded_compression_enabled() -> None:\n    req = make_request(\"GET\", \"/\")\n    body_thread_size = 1024\n    body = b\"answer\" * body_thread_size\n    resp = web.Response(body=body, zlib_executor_size=body_thread_size)\n    resp.enable_compression(web.ContentCoding.gzip)\n\n    await resp.prepare(req)\n    assert resp._compressed_body is not None\n    assert gzip.decompress(resp._compressed_body) == body\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_change_content_threaded_compression_enabled_explicit() -> None:\n    req = make_request(\"GET\", \"/\")\n    body_thread_size = 1024\n    body = b\"answer\" * body_thread_size\n    with ThreadPoolExecutor(1) as executor:\n        resp = web.Response(\n            body=body, zlib_executor_size=body_thread_size, zlib_executor=executor\n        )\n        resp.enable_compression(web.ContentCoding.gzip)\n\n        await resp.prepare(req)\n        assert resp._compressed_body is not None\n        assert gzip.decompress(resp._compressed_body) == body\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_change_content_length_if_compression_enabled() -> None:\n    req = make_request(\"GET\", \"/\")\n    resp = web.Response(body=b\"answer\")\n    resp.enable_compression(web.ContentCoding.gzip)\n\n    await resp.prepare(req)\n    assert resp.content_length is not None and resp.content_length != len(b\"answer\")\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_set_content_length_if_compression_enabled() -> None:\n    writer = mock.Mock()\n\n    async def write_headers(status_line: str, headers: CIMultiDict[str]) -> None:\n        assert hdrs.CONTENT_LENGTH in headers\n        assert headers[hdrs.CONTENT_LENGTH] == \"26\"\n        assert hdrs.TRANSFER_ENCODING not in headers\n\n    writer.write_headers.side_effect = write_headers\n    req = make_request(\"GET\", \"/\", writer=writer)\n    resp = web.Response(body=b\"answer\")\n    resp.enable_compression(web.ContentCoding.gzip)\n\n    await resp.prepare(req)\n    assert resp.content_length == 26\n    del resp.headers[hdrs.CONTENT_LENGTH]\n    assert resp.content_length == 26\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_remove_content_length_if_compression_enabled_http11() -> None:\n    writer = mock.Mock()\n\n    async def write_headers(status_line: str, headers: CIMultiDict[str]) -> None:\n        assert hdrs.CONTENT_LENGTH not in headers\n        assert headers.get(hdrs.TRANSFER_ENCODING, \"\") == \"chunked\"\n\n    writer.write_headers.side_effect = write_headers\n    req = make_request(\"GET\", \"/\", writer=writer)\n    resp = web.StreamResponse()\n    resp.content_length = 123\n    resp.enable_compression(web.ContentCoding.gzip)\n    await resp.prepare(req)\n    assert resp.content_length is None\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_remove_content_length_if_compression_enabled_http10() -> None:\n    writer = mock.Mock()\n\n    async def write_headers(status_line: str, headers: CIMultiDict[str]) -> None:\n        assert hdrs.CONTENT_LENGTH not in headers\n        assert hdrs.TRANSFER_ENCODING not in headers\n\n    writer.write_headers.side_effect = write_headers\n    req = make_request(\"GET\", \"/\", version=HttpVersion10, writer=writer)\n    resp = web.StreamResponse()\n    resp.content_length = 123\n    resp.enable_compression(web.ContentCoding.gzip)\n    await resp.prepare(req)\n    assert resp.content_length is None\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_force_compression_identity() -> None:\n    writer = mock.Mock()\n\n    async def write_headers(status_line: str, headers: CIMultiDict[str]) -> None:\n        assert hdrs.CONTENT_LENGTH in headers\n        assert hdrs.TRANSFER_ENCODING not in headers\n\n    writer.write_headers.side_effect = write_headers\n    req = make_request(\"GET\", \"/\", writer=writer)\n    resp = web.StreamResponse()\n    resp.content_length = 123\n    resp.enable_compression(web.ContentCoding.identity)\n    await resp.prepare(req)\n    assert resp.content_length == 123\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_force_compression_identity_response() -> None:\n    writer = mock.Mock()\n\n    async def write_headers(status_line: str, headers: CIMultiDict[str]) -> None:\n        assert headers[hdrs.CONTENT_LENGTH] == \"6\"\n        assert hdrs.TRANSFER_ENCODING not in headers\n\n    writer.write_headers.side_effect = write_headers\n    req = make_request(\"GET\", \"/\", writer=writer)\n    resp = web.Response(body=b\"answer\")\n    resp.enable_compression(web.ContentCoding.identity)\n    await resp.prepare(req)\n    assert resp.content_length == 6\n\n\nasync def test_enable_compression_with_existing_encoding() -> None:\n    \"\"\"Test that enable_compression does not override existing content encoding.\"\"\"\n    writer = mock.Mock()\n\n    async def write_headers(status_line: str, headers: CIMultiDict[str]) -> None:\n        # Should preserve the existing content encoding\n        assert headers[hdrs.CONTENT_ENCODING] == \"gzip\"\n        # Should not have double encoding\n        assert headers.get(hdrs.CONTENT_ENCODING) != \"gzip, deflate\"\n\n    writer.write_headers.side_effect = write_headers\n    req = make_request(\"GET\", \"/\", writer=writer)\n    resp = web.Response(body=b\"answer\")\n\n    # Manually set content encoding (simulating FileResponse with pre-compressed file)\n    resp.headers[hdrs.CONTENT_ENCODING] = \"gzip\"\n\n    # Try to enable compression - should be ignored\n    resp.enable_compression(web.ContentCoding.deflate)\n\n    await resp.prepare(req)\n    # Verify compression was not enabled due to existing encoding\n    assert not resp.compression\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_rm_content_length_if_compression_http11() -> None:\n    writer = mock.Mock()\n\n    async def write_headers(status_line: str, headers: CIMultiDict[str]) -> None:\n        assert hdrs.CONTENT_LENGTH not in headers\n        assert headers.get(hdrs.TRANSFER_ENCODING, \"\") == \"chunked\"\n\n    writer.write_headers.side_effect = write_headers\n    req = make_request(\"GET\", \"/\", writer=writer)\n    payload = BytesPayload(b\"answer\", headers={\"X-Test-Header\": \"test\"})\n    resp = web.Response(body=payload)\n    resp.body = payload\n    resp.enable_compression(web.ContentCoding.gzip)\n    await resp.prepare(req)\n    assert resp.content_length is None\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_rm_content_length_if_compression_http10() -> None:\n    writer = mock.Mock()\n\n    async def write_headers(status_line: str, headers: CIMultiDict[str]) -> None:\n        assert hdrs.CONTENT_LENGTH not in headers\n        assert hdrs.TRANSFER_ENCODING not in headers\n\n    writer.write_headers.side_effect = write_headers\n    req = make_request(\"GET\", \"/\", version=HttpVersion10, writer=writer)\n    resp = web.Response(body=BytesPayload(b\"answer\"))\n    resp.enable_compression(web.ContentCoding.gzip)\n    await resp.prepare(req)\n    assert resp.content_length is None\n\n\nasync def test_rm_content_length_if_204() -> None:\n    \"\"\"Ensure content-length is removed for 204 responses.\"\"\"\n    writer = mock.create_autospec(StreamWriter, spec_set=True, instance=True)\n\n    async def write_headers(status_line: str, headers: CIMultiDict[str]) -> None:\n        assert hdrs.CONTENT_LENGTH not in headers\n\n    writer.write_headers.side_effect = write_headers\n    req = make_request(\"GET\", \"/\", writer=writer)\n    payload = BytesPayload(b\"answer\", headers={\"Content-Length\": \"6\"})\n    resp = web.Response(body=payload, status=204)\n    resp.body = payload\n    await resp.prepare(req)\n    assert resp.content_length is None\n\n\n@pytest.mark.parametrize(\"status\", (100, 101, 204, 304))\nasync def test_rm_transfer_encoding_rfc_9112_6_3_http_11(status: int) -> None:\n    \"\"\"Remove transfer encoding for RFC 9112 sec 6.3 with HTTP/1.1.\"\"\"\n    writer = mock.create_autospec(StreamWriter, spec_set=True, instance=True)\n    req = make_request(\"GET\", \"/\", version=HttpVersion11, writer=writer)\n    resp = web.Response(status=status, headers={hdrs.TRANSFER_ENCODING: \"chunked\"})\n    await resp.prepare(req)\n    assert resp.content_length == 0\n    assert not resp.chunked\n    assert hdrs.CONTENT_LENGTH not in resp.headers\n    assert hdrs.TRANSFER_ENCODING not in resp.headers\n\n\n@pytest.mark.parametrize(\"status\", (100, 101, 102, 204, 304))\nasync def test_rm_content_length_1xx_204_304_responses(status: int) -> None:\n    \"\"\"Remove content length for 1xx, 204, and 304 responses.\n\n    Content-Length is forbidden for 1xx and 204\n    https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2\n\n    Content-Length is discouraged for 304.\n    https://datatracker.ietf.org/doc/html/rfc7232#section-4.1\n    \"\"\"\n    writer = mock.create_autospec(StreamWriter, spec_set=True, instance=True)\n    req = make_request(\"GET\", \"/\", version=HttpVersion11, writer=writer)\n    resp = web.Response(status=status, body=\"answer\")\n    await resp.prepare(req)\n    assert not resp.chunked\n    assert hdrs.CONTENT_LENGTH not in resp.headers\n    assert hdrs.TRANSFER_ENCODING not in resp.headers\n\n\nasync def test_head_response_keeps_content_length_of_original_body() -> None:\n    \"\"\"Verify HEAD response keeps the content length of the original body HTTP/1.1.\"\"\"\n    writer = mock.create_autospec(StreamWriter, spec_set=True, instance=True)\n    req = make_request(\"HEAD\", \"/\", version=HttpVersion11, writer=writer)\n    resp = web.Response(status=200, body=b\"answer\")\n    await resp.prepare(req)\n    assert resp.content_length == 6\n    assert not resp.chunked\n    assert resp.headers[hdrs.CONTENT_LENGTH] == \"6\"\n    assert hdrs.TRANSFER_ENCODING not in resp.headers\n\n\nasync def test_head_response_omits_content_length_when_body_unset() -> None:\n    \"\"\"Verify HEAD response omits content-length body when its unset.\"\"\"\n    writer = mock.create_autospec(StreamWriter, spec_set=True, instance=True)\n    req = make_request(\"HEAD\", \"/\", version=HttpVersion11, writer=writer)\n    resp = web.Response(status=200)\n    await resp.prepare(req)\n    assert resp.content_length == 0\n    assert not resp.chunked\n    assert hdrs.CONTENT_LENGTH not in resp.headers\n    assert hdrs.TRANSFER_ENCODING not in resp.headers\n\n\nasync def test_304_response_omits_content_length_when_body_unset() -> None:\n    \"\"\"Verify 304 response omits content-length body when its unset.\"\"\"\n    writer = mock.create_autospec(StreamWriter, spec_set=True, instance=True)\n    req = make_request(\"GET\", \"/\", version=HttpVersion11, writer=writer)\n    resp = web.Response(status=304)\n    await resp.prepare(req)\n    assert resp.content_length == 0\n    assert not resp.chunked\n    assert hdrs.CONTENT_LENGTH not in resp.headers\n    assert hdrs.TRANSFER_ENCODING not in resp.headers\n\n\nasync def test_content_length_on_chunked() -> None:\n    req = make_request(\"GET\", \"/\")\n    resp = web.Response(body=b\"answer\")\n    assert resp.content_length == 6\n    resp.enable_chunked_encoding()\n    assert resp.content_length is None\n    await resp.prepare(req)  # type: ignore[unreachable]\n\n\nasync def test_write_non_byteish() -> None:\n    resp = web.StreamResponse()\n    await resp.prepare(make_request(\"GET\", \"/\"))\n\n    with pytest.raises(AssertionError):\n        await resp.write(123)  # type: ignore[arg-type]\n\n\nasync def test_write_before_start() -> None:\n    resp = web.StreamResponse()\n\n    with pytest.raises(RuntimeError):\n        await resp.write(b\"data\")\n\n\nasync def test_cannot_write_after_eof() -> None:\n    resp = web.StreamResponse()\n    req = make_request(\"GET\", \"/\")\n    await resp.prepare(req)\n\n    await resp.write(b\"data\")\n    await resp.write_eof()\n    req.writer.write.reset_mock()  # type: ignore[attr-defined]\n\n    with pytest.raises(RuntimeError):\n        await resp.write(b\"next data\")\n    assert not req.writer.write.called  # type: ignore[attr-defined]\n\n\nasync def test___repr___after_eof() -> None:\n    resp = web.StreamResponse()\n    await resp.prepare(make_request(\"GET\", \"/\"))\n\n    await resp.write(b\"data\")\n    await resp.write_eof()\n    resp_repr = repr(resp)\n    assert resp_repr == \"<StreamResponse OK eof>\"\n\n\nasync def test_cannot_write_eof_before_headers() -> None:\n    resp = web.StreamResponse()\n\n    with pytest.raises(AssertionError):\n        await resp.write_eof()\n\n\nasync def test_cannot_write_eof_twice() -> None:\n    resp = web.StreamResponse()\n    writer = mock.create_autospec(AbstractStreamWriter, spec_set=True)\n    writer.write.return_value = None\n    writer.write_eof.return_value = None\n    resp_impl = await resp.prepare(make_request(\"GET\", \"/\", writer=writer))\n\n    await resp.write(b\"data\")\n    assert resp_impl is not None\n    assert resp_impl.write.called  # type: ignore[attr-defined]\n\n    await resp.write_eof()\n\n    resp_impl.write.reset_mock()  # type: ignore[attr-defined]\n    await resp.write_eof()\n    assert not writer.write.called\n\n\ndef test_force_close() -> None:\n    resp = web.StreamResponse()\n\n    assert resp.keep_alive is None\n    resp.force_close()\n    assert resp.keep_alive is False\n\n\ndef test_set_status_with_reason() -> None:\n    resp = web.StreamResponse()\n\n    resp.set_status(200, \"Everything is fine!\")\n    assert 200 == resp.status\n    assert \"Everything is fine!\" == resp.reason\n\n\ndef test_set_status_with_empty_reason() -> None:\n    resp = web.StreamResponse()\n\n    resp.set_status(200, \"\")\n    assert resp.status == 200\n    assert resp.reason == \"\"\n\n\ndef test_set_status_reason_with_cr() -> None:\n    resp = web.StreamResponse()\n\n    with pytest.raises(ValueError, match=\"Reason cannot contain\"):\n        resp.set_status(200, \"OK\\rSet-Cookie: evil=1\")\n\n\ndef test_set_status_reason_with_lf() -> None:\n    resp = web.StreamResponse()\n\n    with pytest.raises(ValueError, match=\"Reason cannot contain\"):\n        resp.set_status(200, \"OK\\nSet-Cookie: evil=1\")\n\n\ndef test_set_status_reason_with_crlf() -> None:\n    resp = web.StreamResponse()\n\n    with pytest.raises(ValueError, match=\"Reason cannot contain\"):\n        resp.set_status(200, \"OK\\r\\nSet-Cookie: evil=1\")\n\n\nasync def test_start_force_close() -> None:\n    req = make_request(\"GET\", \"/\")\n    resp = web.StreamResponse()\n    resp.force_close()\n    assert not resp.keep_alive\n\n    await resp.prepare(req)\n    assert not resp.keep_alive\n\n\nasync def test___repr__() -> None:\n    req = make_request(\"GET\", \"/path/to\")\n    resp = web.StreamResponse(reason=\"foo\")\n    await resp.prepare(req)\n    assert \"<StreamResponse foo GET /path/to >\" == repr(resp)\n\n\ndef test___repr___not_prepared() -> None:\n    resp = web.StreamResponse(reason=\"foo\")\n    assert \"<StreamResponse foo not prepared>\" == repr(resp)\n\n\nasync def test_keep_alive_http10_default() -> None:\n    req = make_request(\"GET\", \"/\", version=HttpVersion10)\n    resp = web.StreamResponse()\n    await resp.prepare(req)\n    assert not resp.keep_alive\n\n\nasync def test_keep_alive_http10_switched_on() -> None:\n    headers = CIMultiDict(Connection=\"keep-alive\")\n    req = make_request(\"GET\", \"/\", version=HttpVersion10, headers=headers)\n    req._message = req._message._replace(should_close=False)\n    resp = web.StreamResponse()\n    await resp.prepare(req)\n    assert resp.keep_alive\n\n\nasync def test_keep_alive_http09() -> None:\n    headers = CIMultiDict(Connection=\"keep-alive\")\n    req = make_request(\"GET\", \"/\", version=HttpVersion(0, 9), headers=headers)\n    resp = web.StreamResponse()\n    await resp.prepare(req)\n    assert not resp.keep_alive\n\n\nasync def test_prepare_twice() -> None:\n    req = make_request(\"GET\", \"/\")\n    resp = web.StreamResponse()\n\n    impl1 = await resp.prepare(req)\n    impl2 = await resp.prepare(req)\n    assert impl1 is impl2\n\n\nasync def test_prepare_calls_signal() -> None:\n    app = mock.create_autospec(web.Application, spec_set=True)\n    sig = mock.AsyncMock()\n    app.on_response_prepare = aiosignal.Signal(app)\n    app.on_response_prepare.append(sig)\n    req = make_request(\"GET\", \"/\", app=app)\n    resp = web.StreamResponse()\n\n    await resp.prepare(req)\n\n    sig.assert_called_with(req, resp)\n\n\n# Response class\n\n\ndef test_response_ctor() -> None:\n    resp = web.Response()\n\n    assert 200 == resp.status\n    assert \"OK\" == resp.reason\n    assert resp.body is None\n    assert resp.content_length == 0\n    assert \"CONTENT-LENGTH\" not in resp.headers\n\n\nasync def test_ctor_with_headers_and_status() -> None:\n    resp = web.Response(body=b\"body\", status=201, headers={\"Age\": \"12\", \"DATE\": \"date\"})\n\n    assert 201 == resp.status\n    assert b\"body\" == resp.body\n    assert resp.headers[\"AGE\"] == \"12\"\n\n    req = make_mocked_request(\"GET\", \"/\")\n    await resp._start(req)\n    assert 4 == resp.content_length\n    assert resp.headers[\"CONTENT-LENGTH\"] == \"4\"\n\n\ndef test_ctor_content_type() -> None:\n    resp = web.Response(content_type=\"application/json\")\n\n    assert 200 == resp.status\n    assert \"OK\" == resp.reason\n    assert 0 == resp.content_length\n    assert CIMultiDict([(\"CONTENT-TYPE\", \"application/json\")]) == resp.headers\n\n\ndef test_ctor_text_body_combined() -> None:\n    with pytest.raises(ValueError):\n        web.Response(body=b\"123\", text=\"test text\")\n\n\nasync def test_ctor_text() -> None:\n    resp = web.Response(text=\"test text\")\n\n    assert 200 == resp.status\n    assert \"OK\" == resp.reason\n    assert 9 == resp.content_length\n    assert CIMultiDict([(\"CONTENT-TYPE\", \"text/plain; charset=utf-8\")]) == resp.headers\n\n    assert resp.body == b\"test text\"\n    assert resp.text == \"test text\"\n\n    resp.headers[\"DATE\"] = \"date\"\n    req = make_mocked_request(\"GET\", \"/\", version=HttpVersion11)\n    await resp._start(req)\n    assert resp.headers[\"CONTENT-LENGTH\"] == \"9\"\n\n\ndef test_ctor_charset() -> None:\n    resp = web.Response(text=\"текст\", charset=\"koi8-r\")\n\n    assert \"текст\".encode(\"koi8-r\") == resp.body\n    assert \"koi8-r\" == resp.charset\n\n\ndef test_ctor_charset_default_utf8() -> None:\n    resp = web.Response(text=\"test test\", charset=None)\n\n    assert \"utf-8\" == resp.charset\n\n\ndef test_ctor_charset_in_content_type() -> None:\n    with pytest.raises(ValueError):\n        web.Response(text=\"test test\", content_type=\"text/plain; charset=utf-8\")\n\n\ndef test_ctor_charset_without_text() -> None:\n    resp = web.Response(content_type=\"text/plain\", charset=\"koi8-r\")\n\n    assert \"koi8-r\" == resp.charset\n\n\ndef test_ctor_content_type_with_extra() -> None:\n    resp = web.Response(text=\"test test\", content_type=\"text/plain; version=0.0.4\")\n\n    assert resp.content_type == \"text/plain\"\n    assert resp.headers[\"content-type\"] == \"text/plain; version=0.0.4; charset=utf-8\"\n\n\ndef test_invalid_content_type_parses_to_application_octect_stream() -> None:\n    resp = web.Response(text=\"test test\", content_type=\"jpeg\")\n\n    assert resp.content_type == \"application/octet-stream\"\n    assert resp.headers[\"content-type\"] == \"jpeg; charset=utf-8\"\n\n\ndef test_ctor_both_content_type_param_and_header_with_text() -> None:\n    with pytest.raises(ValueError):\n        web.Response(\n            headers={\"Content-Type\": \"application/json\"},\n            content_type=\"text/html\",\n            text=\"text\",\n        )\n\n\ndef test_ctor_both_charset_param_and_header_with_text() -> None:\n    with pytest.raises(ValueError):\n        web.Response(\n            headers={\"Content-Type\": \"application/json\"}, charset=\"koi8-r\", text=\"text\"\n        )\n\n\ndef test_ctor_both_content_type_param_and_header() -> None:\n    with pytest.raises(ValueError):\n        web.Response(\n            headers={\"Content-Type\": \"application/json\"}, content_type=\"text/html\"\n        )\n\n\ndef test_ctor_both_charset_param_and_header() -> None:\n    with pytest.raises(ValueError):\n        web.Response(headers={\"Content-Type\": \"application/json\"}, charset=\"koi8-r\")\n\n\nasync def test_assign_nonbyteish_body() -> None:\n    resp = web.Response(body=b\"data\")\n\n    with pytest.raises(ValueError):\n        resp.body = 123\n    assert b\"data\" == resp.body\n    assert 4 == resp.content_length\n\n    resp.headers[\"DATE\"] = \"date\"\n    req = make_mocked_request(\"GET\", \"/\", version=HttpVersion11)\n    await resp._start(req)\n    assert resp.headers[\"CONTENT-LENGTH\"] == \"4\"\n    assert 4 == resp.content_length\n\n\ndef test_assign_nonstr_text() -> None:\n    resp = web.Response(text=\"test\")\n\n    with pytest.raises(AssertionError):\n        resp.text = b\"123\"  # type: ignore[assignment]\n    assert b\"test\" == resp.body\n    assert 4 == resp.content_length\n\n\nmpwriter = MultipartWriter(boundary=\"x\")\nmpwriter.append_payload(StringPayload(\"test\"))\n\n\nasync def async_iter() -> AsyncIterator[str]:\n    yield \"foo\"  # pragma: no cover\n\n\nclass CustomIO(io.IOBase):\n    def __init__(self) -> None:\n        self._lines = [b\"\", b\"\", b\"test\"]\n\n    def read(self, size: int = -1) -> bytes:\n        return self._lines.pop()\n\n\n@pytest.mark.parametrize(\n    \"payload,expected\",\n    (\n        (\"test\", \"test\"),\n        (CustomIO(), \"test\"),\n        (io.StringIO(\"test\"), \"test\"),\n        (io.TextIOWrapper(io.BytesIO(b\"test\")), \"test\"),\n        (io.BytesIO(b\"test\"), \"test\"),\n        (io.BufferedReader(io.BytesIO(b\"test\")), \"test\"),\n        (async_iter(), None),\n        (BodyPartReader(b\"x\", CIMultiDictProxy(CIMultiDict()), mock.Mock()), None),\n        (\n            mpwriter,\n            \"--x\\r\\nContent-Type: text/plain; charset=utf-8\\r\\nContent-Length: 4\\r\\n\\r\\ntest\",\n        ),\n    ),\n)\ndef test_payload_body_get_text(payload: object, expected: str | None) -> None:\n    resp = web.Response(body=payload)\n    if expected is None:\n        with pytest.raises(TypeError):\n            resp.text\n    else:\n        assert resp.text == expected\n\n\ndef test_response_set_content_length() -> None:\n    resp = web.Response()\n    with pytest.raises(RuntimeError):\n        resp.content_length = 1\n\n\nasync def test_send_headers_for_empty_body(\n    buf: bytearray, writer: AbstractStreamWriter\n) -> None:\n    req = make_request(\"GET\", \"/\", writer=writer)\n    resp = web.Response()\n\n    await resp.prepare(req)\n    await resp.write_eof()\n    txt = buf.decode(\"utf8\")\n\n    lines = txt.split(\"\\r\\n\")\n    assert len(lines) == 6\n    assert lines[0] == \"HTTP/1.1 200 OK\"\n    assert lines[1] == \"Content-Length: 0\"\n    assert lines[2].startswith(\"Date: \")\n    assert lines[3].startswith(\"Server: \")\n    assert lines[4] == lines[5] == \"\"\n\n\nasync def test_render_with_body(buf: bytearray, writer: AbstractStreamWriter) -> None:\n    req = make_request(\"GET\", \"/\", writer=writer)\n    resp = web.Response(body=b\"data\")\n\n    await resp.prepare(req)\n    await resp.write_eof()\n    txt = buf.decode(\"utf8\")\n\n    lines = txt.split(\"\\r\\n\")\n    assert len(lines) == 7\n    assert lines[0] == \"HTTP/1.1 200 OK\"\n    assert lines[1] == \"Content-Length: 4\"\n    assert lines[2] == \"Content-Type: application/octet-stream\"\n    assert lines[3].startswith(\"Date: \")\n    assert lines[4].startswith(\"Server: \")\n    assert lines[5] == \"\"\n    assert lines[6] == \"data\"\n\n\nasync def test_multiline_reason(buf: bytearray, writer: AbstractStreamWriter) -> None:\n    with pytest.raises(ValueError, match=r\"Reason cannot contain \\\\r or \\\\n\"):\n        web.Response(reason=\"Bad\\r\\nInjected-header: foo\")\n\n\nasync def test_send_set_cookie_header(\n    buf: bytearray, writer: AbstractStreamWriter\n) -> None:\n    resp = web.Response()\n    resp.cookies[\"name\"] = \"value\"\n    req = make_request(\"GET\", \"/\", writer=writer)\n\n    await resp.prepare(req)\n    await resp.write_eof()\n    txt = buf.decode(\"utf8\")\n\n    lines = txt.split(\"\\r\\n\")\n    assert len(lines) == 7\n    assert lines[0] == \"HTTP/1.1 200 OK\"\n    assert lines[1] == \"Content-Length: 0\"\n    assert lines[2] == \"Set-Cookie: name=value\"\n    assert lines[3].startswith(\"Date: \")\n    assert lines[4].startswith(\"Server: \")\n    assert lines[5] == lines[6] == \"\"\n\n\nasync def test_consecutive_write_eof() -> None:\n    writer = mock.create_autospec(AbstractStreamWriter, spec_set=True, instance=True)\n    req = make_request(\"GET\", \"/\", writer=writer)\n    data = b\"data\"\n    resp = web.Response(body=data)\n\n    await resp.prepare(req)\n    await resp.write_eof()\n    await resp.write_eof()\n    writer.write_eof.assert_called_once_with(data)\n\n\ndef test_set_text_with_content_type() -> None:\n    resp = web.Response()\n    resp.content_type = \"text/html\"\n    resp.text = \"text\"\n\n    assert \"text\" == resp.text\n    assert b\"text\" == resp.body\n    assert \"text/html\" == resp.content_type\n\n\ndef test_set_text_with_charset() -> None:\n    resp = web.Response()\n    resp.content_type = \"text/plain\"\n    resp.charset = \"KOI8-R\"\n    resp.text = \"текст\"\n\n    assert \"текст\" == resp.text\n    assert \"текст\".encode(\"koi8-r\") == resp.body\n    assert \"koi8-r\" == resp.charset\n\n\ndef test_default_content_type_in_stream_response() -> None:\n    resp = web.StreamResponse()\n    assert resp.content_type == \"application/octet-stream\"\n\n\ndef test_default_content_type_in_response() -> None:\n    resp = web.Response()\n    assert resp.content_type == \"application/octet-stream\"\n\n\ndef test_content_type_with_set_text() -> None:\n    resp = web.Response(text=\"text\")\n    assert resp.content_type == \"text/plain\"\n\n\ndef test_content_type_with_set_body() -> None:\n    resp = web.Response(body=b\"body\")\n    assert resp.content_type == \"application/octet-stream\"\n\n\ndef test_prepared_when_not_started() -> None:\n    resp = web.StreamResponse()\n    assert not resp.prepared\n\n\nasync def test_prepared_when_started() -> None:\n    resp = web.StreamResponse()\n    await resp.prepare(make_request(\"GET\", \"/\"))\n    assert resp.prepared\n\n\nasync def test_prepared_after_eof() -> None:\n    resp = web.StreamResponse()\n    await resp.prepare(make_request(\"GET\", \"/\"))\n    await resp.write(b\"data\")\n    await resp.write_eof()\n    assert resp.prepared\n\n\nasync def test_drain_before_start() -> None:\n    resp = web.StreamResponse()\n    with pytest.raises(AssertionError):\n        await resp.drain()\n\n\nasync def test_changing_status_after_prepare_raises() -> None:\n    resp = web.StreamResponse()\n    await resp.prepare(make_request(\"GET\", \"/\"))\n    with pytest.raises(AssertionError):\n        resp.set_status(400)\n\n\ndef test_nonstr_text_in_ctor() -> None:\n    with pytest.raises(TypeError):\n        web.Response(text=b\"data\")  # type: ignore[arg-type]\n\n\ndef test_text_in_ctor_with_content_type() -> None:\n    resp = web.Response(text=\"data\", content_type=\"text/html\")\n    assert \"data\" == resp.text\n    assert \"text/html\" == resp.content_type\n\n\ndef test_text_in_ctor_with_content_type_header() -> None:\n    resp = web.Response(\n        text=\"текст\", headers={\"Content-Type\": \"text/html; charset=koi8-r\"}\n    )\n    assert \"текст\".encode(\"koi8-r\") == resp.body\n    assert \"text/html\" == resp.content_type\n    assert \"koi8-r\" == resp.charset\n\n\ndef test_text_in_ctor_with_content_type_header_multidict() -> None:\n    headers = CIMultiDict({\"Content-Type\": \"text/html; charset=koi8-r\"})\n    resp = web.Response(text=\"текст\", headers=headers)\n    assert \"текст\".encode(\"koi8-r\") == resp.body\n    assert \"text/html\" == resp.content_type\n    assert \"koi8-r\" == resp.charset\n\n\ndef test_body_in_ctor_with_content_type_header_multidict() -> None:\n    headers = CIMultiDict({\"Content-Type\": \"text/html; charset=koi8-r\"})\n    resp = web.Response(body=\"текст\".encode(\"koi8-r\"), headers=headers)\n    assert \"текст\".encode(\"koi8-r\") == resp.body\n    assert \"text/html\" == resp.content_type\n    assert \"koi8-r\" == resp.charset\n\n\ndef test_text_with_empty_payload() -> None:\n    resp = web.Response(status=200)\n    assert resp.body is None\n    assert resp.text is None\n\n\ndef test_response_with_content_length_header_without_body() -> None:\n    resp = web.Response(headers={\"Content-Length\": \"123\"})\n    assert resp.content_length == 123\n\n\ndef test_response_with_immutable_headers() -> None:\n    resp = web.Response(\n        text=\"text\", headers=CIMultiDictProxy(CIMultiDict({\"Header\": \"Value\"}))\n    )\n    assert resp.headers == {\n        \"Header\": \"Value\",\n        \"Content-Type\": \"text/plain; charset=utf-8\",\n    }\n\n\nasync def test_response_prepared_after_header_preparation() -> None:\n    req = make_request(\"GET\", \"/\")\n    resp = web.StreamResponse()\n    await resp.prepare(req)\n\n    assert type(resp.headers[\"Server\"]) is str\n\n    async def _strip_server(req: web.Request, res: web.Response) -> None:\n        assert \"Server\" in res.headers\n        del res.headers[\"Server\"]\n\n    app = mock.create_autospec(web.Application, spec_set=True)\n    app.on_response_prepare = aiosignal.Signal(app)\n    app.on_response_prepare.append(_strip_server)\n\n    req = make_request(\"GET\", \"/\", app=app)\n    resp = web.StreamResponse()\n    await resp.prepare(req)\n\n    assert \"Server\" not in resp.headers\n\n\ndef test_weakref_creation() -> None:\n    resp = web.Response()\n    weakref.ref(resp)\n\n\nclass TestJSONResponse:\n    def test_content_type_is_application_json_by_default(self) -> None:\n        resp = web.json_response(\"\")\n        assert \"application/json\" == resp.content_type\n\n    def test_passing_text_only(self) -> None:\n        resp = web.json_response(text=json.dumps(\"jaysawn\"))\n        assert resp.text == json.dumps(\"jaysawn\")\n\n    def test_data_and_text_raises_value_error(self) -> None:\n        with pytest.raises(ValueError) as excinfo:\n            web.json_response(data=\"foo\", text=\"bar\")\n        expected_message = \"only one of data, text, or body should be specified\"\n        assert expected_message == excinfo.value.args[0]\n\n    def test_data_and_body_raises_value_error(self) -> None:\n        with pytest.raises(ValueError) as excinfo:\n            web.json_response(data=\"foo\", body=b\"bar\")\n        expected_message = \"only one of data, text, or body should be specified\"\n        assert expected_message == excinfo.value.args[0]\n\n    def test_text_is_json_encoded(self) -> None:\n        resp = web.json_response({\"foo\": 42})\n        assert json.dumps({\"foo\": 42}) == resp.text\n\n    def test_content_type_is_overrideable(self) -> None:\n        resp = web.json_response({\"foo\": 42}, content_type=\"application/vnd.json+api\")\n        assert \"application/vnd.json+api\" == resp.content_type\n\n\nclass TestJSONBytesResponse:\n    def test_content_type_is_application_json_by_default(self) -> None:\n        resp = web.json_bytes_response(\n            \"\", dumps=lambda x: json.dumps(x).encode(\"utf-8\")\n        )\n        assert \"application/json\" == resp.content_type\n\n    def test_passing_body_only(self) -> None:\n        resp = web.json_bytes_response(\n            dumps=lambda x: json.dumps(x).encode(\"utf-8\"),\n            body=b'\"jaysawn\"',\n        )\n        assert resp.body == b'\"jaysawn\"'\n\n    def test_data_and_body_raises_value_error(self) -> None:\n        with pytest.raises(ValueError) as excinfo:\n            web.json_bytes_response(\n                data=\"foo\", dumps=lambda x: json.dumps(x).encode(\"utf-8\"), body=b\"bar\"\n            )\n        expected_message = \"only one of data or body should be specified\"\n        assert expected_message == excinfo.value.args[0]\n\n    def test_body_is_json_encoded_bytes(self) -> None:\n        resp = web.json_bytes_response(\n            {\"foo\": 42}, dumps=lambda x: json.dumps(x).encode(\"utf-8\")\n        )\n        assert json.dumps({\"foo\": 42}).encode(\"utf-8\") == resp.body\n\n    def test_content_type_is_overrideable(self) -> None:\n        resp = web.json_bytes_response(\n            {\"foo\": 42},\n            dumps=lambda x: json.dumps(x).encode(\"utf-8\"),\n            content_type=\"application/vnd.json+api\",\n        )\n        assert \"application/vnd.json+api\" == resp.content_type\n\n    def test_custom_dumps(self) -> None:\n        resp = web.json_bytes_response(\n            {\"foo\": 42},\n            dumps=lambda x: json.dumps(x, separators=(\",\", \":\")).encode(\"utf-8\"),\n        )\n        assert b'{\"foo\":42}' == resp.body\n\n\n@pytest.mark.dev_mode\nasync def test_no_warn_small_cookie(\n    buf: bytearray, writer: AbstractStreamWriter\n) -> None:\n    resp = web.Response()\n    resp.set_cookie(\"foo\", \"ÿ\" + \"8\" * 4064, max_age=2600)  # No warning\n    req = make_request(\"GET\", \"/\", writer=writer)\n\n    await resp.prepare(req)\n    await resp.write_eof()\n\n    match = re.search(b\"Set-Cookie: (.*?)\\r\\n\", buf)\n    assert match is not None\n    cookie = match.group(1)\n    assert len(cookie) == 4096\n\n\n@pytest.mark.dev_mode\nasync def test_warn_large_cookie(buf: bytearray, writer: AbstractStreamWriter) -> None:\n    resp = web.Response()\n\n    with pytest.warns(\n        UserWarning,\n        match=\"The size of is too large, it might get ignored by the client.\",\n    ):\n        resp.set_cookie(\"foo\", \"ÿ\" + \"8\" * 4065, max_age=2600)\n    req = make_request(\"GET\", \"/\", writer=writer)\n\n    await resp.prepare(req)\n    await resp.write_eof()\n\n    match = re.search(b\"Set-Cookie: (.*?)\\r\\n\", buf)\n    assert match is not None\n    cookie = match.group(1)\n    assert len(cookie) == 4097\n\n\n@pytest.mark.parametrize(\"loose_header_type\", (MultiDict, CIMultiDict, dict))\nasync def test_passing_cimultidict_to_web_response_not_mutated(\n    loose_header_type: type,\n) -> None:\n    req = make_request(\"GET\", \"/\")\n    headers = loose_header_type({})\n    resp = web.Response(body=b\"answer\", headers=headers)\n    await resp.prepare(req)\n    assert resp.content_length == 6\n    assert not headers\n\n\nasync def test_stream_response_sends_headers_immediately() -> None:\n    \"\"\"Test that StreamResponse sends headers immediately.\"\"\"\n    writer = mock.create_autospec(StreamWriter, spec_set=True, instance=True)\n\n    req = make_request(\"GET\", \"/\", writer=writer)\n    resp = web.StreamResponse()\n\n    # StreamResponse should have _send_headers_immediately = True\n    assert resp._send_headers_immediately is True\n\n    # Prepare the response\n    await resp.prepare(req)\n\n    # Headers should be sent immediately\n    writer.send_headers.assert_called_once()\n\n\nasync def test_response_buffers_headers() -> None:\n    \"\"\"Test that Response buffers headers for packet coalescing.\"\"\"\n    writer = mock.create_autospec(StreamWriter, spec_set=True, instance=True)\n\n    req = make_request(\"GET\", \"/\", writer=writer)\n    resp = web.Response(body=b\"hello\")\n\n    # Response should have _send_headers_immediately = False\n    assert resp._send_headers_immediately is False\n\n    # Prepare the response\n    await resp.prepare(req)\n\n    # Headers should NOT be sent immediately\n    writer.send_headers.assert_not_called()\n\n    # But write_headers should have been called\n    writer.write_headers.assert_called_once()\n"
  },
  {
    "path": "tests/test_web_runner.py",
    "content": "import asyncio\nimport platform\nimport signal\nfrom collections.abc import Iterator\nfrom typing import Any, NoReturn, Protocol\nfrom unittest import mock\n\nimport pytest\n\nfrom aiohttp import web\nfrom aiohttp.abc import AbstractAccessLogger\nfrom aiohttp.test_utils import get_unused_port_socket\nfrom aiohttp.web_log import AccessLogger\n\n\nclass _RunnerMaker(Protocol):\n    def __call__(self, handle_signals: bool = ..., **kwargs: Any) -> web.AppRunner: ...\n\n\n@pytest.fixture\ndef app() -> web.Application:\n    return web.Application()\n\n\n@pytest.fixture\ndef make_runner(\n    loop: asyncio.AbstractEventLoop, app: web.Application\n) -> Iterator[_RunnerMaker]:\n    asyncio.set_event_loop(loop)\n    runners = []\n\n    def go(handle_signals: bool = False, **kwargs: Any) -> web.AppRunner:\n        runner = web.AppRunner(app, handle_signals=handle_signals, **kwargs)\n        runners.append(runner)\n        return runner\n\n    yield go\n    for runner in runners:\n        loop.run_until_complete(runner.cleanup())\n\n\nasync def test_site_for_nonfrozen_app(make_runner: _RunnerMaker) -> None:\n    runner = make_runner()\n    with pytest.raises(RuntimeError):\n        web.TCPSite(runner)\n    assert len(runner.sites) == 0\n\n\n@pytest.mark.skipif(\n    platform.system() == \"Windows\", reason=\"the test is not valid for Windows\"\n)\nasync def test_runner_setup_handle_signals(make_runner: _RunnerMaker) -> None:\n    # Save the original signal handler\n    original_handler = signal.getsignal(signal.SIGTERM)\n    try:\n        # Set a known state for the signal handler to avoid flaky tests\n        signal.signal(signal.SIGTERM, signal.SIG_DFL)\n\n        runner = make_runner(handle_signals=True)\n        await runner.setup()\n        assert signal.getsignal(signal.SIGTERM) is not signal.SIG_DFL\n        await runner.cleanup()\n        assert signal.getsignal(signal.SIGTERM) is signal.SIG_DFL\n    finally:\n        # Restore original signal handler\n        signal.signal(signal.SIGTERM, original_handler)\n\n\n@pytest.mark.skipif(\n    platform.system() == \"Windows\", reason=\"the test is not valid for Windows\"\n)\nasync def test_runner_setup_without_signal_handling(make_runner: _RunnerMaker) -> None:\n    # Save the original signal handler\n    original_handler = signal.getsignal(signal.SIGTERM)\n    try:\n        # Set a known state for the signal handler to avoid flaky tests\n        signal.signal(signal.SIGTERM, signal.SIG_DFL)\n\n        runner = make_runner(handle_signals=False)\n        await runner.setup()\n        assert signal.getsignal(signal.SIGTERM) is signal.SIG_DFL\n        await runner.cleanup()\n        assert signal.getsignal(signal.SIGTERM) is signal.SIG_DFL\n    finally:\n        # Restore original signal handler\n        signal.signal(signal.SIGTERM, original_handler)\n\n\nasync def test_site_double_added(make_runner: _RunnerMaker) -> None:\n    _sock = get_unused_port_socket(\"127.0.0.1\")\n    runner = make_runner()\n    await runner.setup()\n    site = web.SockSite(runner, _sock)\n    await site.start()\n    with pytest.raises(RuntimeError):\n        await site.start()\n\n    assert len(runner.sites) == 1\n\n\nasync def test_site_stop_not_started(make_runner: _RunnerMaker) -> None:\n    runner = make_runner()\n    await runner.setup()\n    site = web.TCPSite(runner)\n    with pytest.raises(RuntimeError):\n        await site.stop()\n\n    assert len(runner.sites) == 0\n\n\nasync def test_custom_log_format(make_runner: _RunnerMaker) -> None:\n    runner = make_runner(access_log_format=\"abc\")\n    await runner.setup()\n    assert runner.server is not None\n    assert runner.server._kwargs[\"access_log_format\"] == \"abc\"\n\n\nasync def test_unreg_site(make_runner: _RunnerMaker) -> None:\n    runner = make_runner()\n    await runner.setup()\n    site = web.TCPSite(runner)\n    with pytest.raises(RuntimeError):\n        runner._unreg_site(site)\n\n\nasync def test_app_property(make_runner: _RunnerMaker, app: web.Application) -> None:\n    runner = make_runner()\n    assert runner.app is app\n\n\ndef test_non_app() -> None:\n    with pytest.raises(TypeError):\n        web.AppRunner(object())  # type: ignore[arg-type]\n\n\ndef test_app_handler_args() -> None:\n    app = web.Application(handler_args={\"test\": True})\n    runner = web.AppRunner(app)\n    assert runner._kwargs == {\"access_log_class\": AccessLogger, \"test\": True}\n\n\nasync def test_app_handler_args_failure() -> None:\n    app = web.Application(handler_args={\"unknown_parameter\": 5})\n    runner = web.AppRunner(app)\n    await runner.setup()\n    assert runner._server\n    rh = runner._server()\n    assert rh._timeout_ceil_threshold == 5\n    await runner.cleanup()\n    assert app\n\n\n@pytest.mark.parametrize(\n    (\"value\", \"expected\"),\n    (\n        (2, 2),\n        (None, 5),\n        (\"2\", 2),\n    ),\n)\nasync def test_app_handler_args_ceil_threshold(\n    value: int | str | None, expected: int\n) -> None:\n    app = web.Application(handler_args={\"timeout_ceil_threshold\": value})\n    runner = web.AppRunner(app)\n    await runner.setup()\n    assert runner._server\n    rh = runner._server()\n    assert rh._timeout_ceil_threshold == expected\n    await runner.cleanup()\n    assert app\n\n\nasync def test_app_make_handler_access_log_class_bad_type1() -> None:\n    class Logger:\n        pass\n\n    app = web.Application()\n\n    with pytest.raises(TypeError):\n        web.AppRunner(app, access_log_class=Logger)  # type: ignore[arg-type]\n\n\nasync def test_app_make_handler_access_log_class_bad_type2() -> None:\n    class Logger:\n        pass\n\n    app = web.Application(handler_args={\"access_log_class\": Logger})\n\n    with pytest.raises(TypeError):\n        web.AppRunner(app)\n\n\nasync def test_app_make_handler_access_log_class1() -> None:\n    class Logger(AbstractAccessLogger):\n        def log(\n            self, request: web.BaseRequest, response: web.StreamResponse, time: float\n        ) -> None:\n            \"\"\"Pass log method.\"\"\"\n\n    app = web.Application()\n    runner = web.AppRunner(app, access_log_class=Logger)\n    assert runner._kwargs[\"access_log_class\"] is Logger\n\n\nasync def test_app_make_handler_access_log_class2() -> None:\n    class Logger(AbstractAccessLogger):\n        def log(\n            self, request: web.BaseRequest, response: web.StreamResponse, time: float\n        ) -> None:\n            \"\"\"Pass log method.\"\"\"\n\n    app = web.Application(handler_args={\"access_log_class\": Logger})\n    runner = web.AppRunner(app)\n    assert runner._kwargs[\"access_log_class\"] is Logger\n\n\nasync def test_app_make_handler_no_access_log_class() -> None:\n    app = web.Application(handler_args={\"access_log\": None})\n    runner = web.AppRunner(app)\n    assert runner._kwargs[\"access_log\"] is None\n\n\nasync def test_addresses(make_runner: _RunnerMaker, unix_sockname: str) -> None:\n    _sock = get_unused_port_socket(\"127.0.0.1\")\n    runner = make_runner()\n    await runner.setup()\n    tcp = web.SockSite(runner, _sock)\n    await tcp.start()\n    unix = web.UnixSite(runner, unix_sockname)\n    await unix.start()\n    actual_addrs = runner.addresses\n    expected_host, expected_post = _sock.getsockname()[:2]\n    assert actual_addrs == [(expected_host, expected_post), unix_sockname]\n\n\n@pytest.mark.skipif(\n    platform.system() != \"Windows\", reason=\"Proactor Event loop present only in Windows\"\n)\nasync def test_named_pipe_runner_wrong_loop(\n    app: web.Application, selector_loop: asyncio.AbstractEventLoop, pipe_name: str\n) -> None:\n    runner = web.AppRunner(app)\n    await runner.setup()\n    with pytest.raises(RuntimeError):\n        web.NamedPipeSite(runner, pipe_name)\n\n\n@pytest.mark.skipif(\n    platform.system() != \"Windows\", reason=\"Proactor Event loop present only in Windows\"\n)\nasync def test_named_pipe_runner_proactor_loop(\n    proactor_loop: asyncio.AbstractEventLoop, app: web.Application, pipe_name: str\n) -> None:\n    runner = web.AppRunner(app)\n    await runner.setup()\n    pipe = web.NamedPipeSite(runner, pipe_name)\n    await pipe.start()\n    await runner.cleanup()\n\n\nasync def test_tcpsite_default_host(make_runner: _RunnerMaker) -> None:\n    runner = make_runner()\n    await runner.setup()\n    site = web.TCPSite(runner)\n    assert site.name == \"http://0.0.0.0:8080\"\n\n    m = mock.create_autospec(asyncio.AbstractEventLoop, spec_set=True, instance=True)\n    m.create_server.return_value = mock.create_autospec(asyncio.Server, spec_set=True)\n    with mock.patch(\n        \"asyncio.get_event_loop\", autospec=True, spec_set=True, return_value=m\n    ):\n        await site.start()\n\n    m.create_server.assert_called_once()\n    args, kwargs = m.create_server.call_args\n    assert args == (runner.server, None, 8080)\n\n\nasync def test_tcpsite_empty_str_host(make_runner: _RunnerMaker) -> None:\n    runner = make_runner()\n    await runner.setup()\n    site = web.TCPSite(runner, host=\"\")\n    assert site.port == 8080\n    assert site.name == \"http://0.0.0.0:8080\"\n\n\nasync def test_tcpsite_ephemeral_port(make_runner: _RunnerMaker) -> None:\n    runner = make_runner()\n    await runner.setup()\n    site = web.TCPSite(runner, port=0)\n    assert site.port == 0\n\n    await site.start()\n    assert site.port != 0\n    assert site.name.startswith(\"http://0.0.0.0:\")\n    await site.stop()\n\n\ndef test_run_after_asyncio_run() -> None:\n    called = False\n\n    async def nothing() -> None:\n        pass\n\n    def spy() -> None:\n        nonlocal called\n        called = True\n\n    async def shutdown() -> NoReturn:\n        spy()\n        raise web.GracefulExit()\n\n    # asyncio.run() creates a new loop and closes it.\n    asyncio.run(nothing())\n\n    app = web.Application()\n    # create_task() will delay the function until app is run.\n    app.on_startup.append(lambda a: asyncio.create_task(shutdown()))\n\n    web.run_app(app)\n    assert called, \"run_app() should work after asyncio.run().\"\n"
  },
  {
    "path": "tests/test_web_sendfile.py",
    "content": "import asyncio\nimport io\nfrom pathlib import Path\nfrom stat import S_IFREG, S_IRUSR, S_IWUSR\nfrom unittest import mock\n\nfrom aiohttp import hdrs\nfrom aiohttp.http_writer import StreamWriter\nfrom aiohttp.test_utils import make_mocked_request\nfrom aiohttp.web_fileresponse import FileResponse\n\nMOCK_MODE = S_IFREG | S_IRUSR | S_IWUSR\n\n\ndef test_using_gzip_if_header_present_and_file_available(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    request = make_mocked_request(\n        \"GET\",\n        \"http://python.org/logo.png\",\n        # Header uses some uppercase to ensure case-insensitive treatment\n        headers={hdrs.ACCEPT_ENCODING: \"GZip\"},\n    )\n\n    gz_filepath = mock.create_autospec(Path, spec_set=True)\n    gz_filepath.lstat.return_value.st_size = 1024\n    gz_filepath.lstat.return_value.st_mtime_ns = 1603733507222449291\n    gz_filepath.lstat.return_value.st_mode = MOCK_MODE\n\n    filepath = mock.create_autospec(Path, spec_set=True)\n    filepath.name = \"logo.png\"\n    filepath.with_suffix.return_value = gz_filepath\n\n    file_sender = FileResponse(filepath)\n    file_sender._path = filepath\n    file_sender._sendfile = mock.AsyncMock(return_value=None)  # type: ignore[method-assign]\n\n    loop.run_until_complete(file_sender.prepare(request))\n\n    assert not filepath.open.called\n    assert gz_filepath.open.called\n\n\ndef test_gzip_if_header_not_present_and_file_available(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    request = make_mocked_request(\"GET\", \"http://python.org/logo.png\", headers={})\n\n    gz_filepath = mock.create_autospec(Path, spec_set=True)\n    gz_filepath.lstat.return_value.st_size = 1024\n    gz_filepath.lstat.return_value.st_mtime_ns = 1603733507222449291\n    gz_filepath.lstat.return_value.st_mode = MOCK_MODE\n\n    filepath = mock.create_autospec(Path, spec_set=True)\n    filepath.name = \"logo.png\"\n    filepath.with_suffix.return_value = gz_filepath\n    filepath.stat.return_value.st_size = 1024\n    filepath.stat.return_value.st_mtime_ns = 1603733507222449291\n    filepath.stat.return_value.st_mode = MOCK_MODE\n\n    file_sender = FileResponse(filepath)\n    file_sender._path = filepath\n    file_sender._sendfile = mock.AsyncMock(return_value=None)  # type: ignore[method-assign]\n\n    loop.run_until_complete(file_sender.prepare(request))\n\n    assert filepath.open.called\n    assert not gz_filepath.open.called\n\n\ndef test_gzip_if_header_not_present_and_file_not_available(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    request = make_mocked_request(\"GET\", \"http://python.org/logo.png\", headers={})\n\n    gz_filepath = mock.create_autospec(Path, spec_set=True)\n    gz_filepath.stat.side_effect = OSError(2, \"No such file or directory\")\n\n    filepath = mock.create_autospec(Path, spec_set=True)\n    filepath.name = \"logo.png\"\n    filepath.with_suffix.return_value = gz_filepath\n    filepath.stat.return_value.st_size = 1024\n    filepath.stat.return_value.st_mtime_ns = 1603733507222449291\n    filepath.stat.return_value.st_mode = MOCK_MODE\n\n    file_sender = FileResponse(filepath)\n    file_sender._path = filepath\n    file_sender._sendfile = mock.AsyncMock(return_value=None)  # type: ignore[method-assign]\n\n    loop.run_until_complete(file_sender.prepare(request))\n\n    assert filepath.open.called\n    assert not gz_filepath.open.called\n\n\ndef test_gzip_if_header_present_and_file_not_available(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    request = make_mocked_request(\n        \"GET\", \"http://python.org/logo.png\", headers={hdrs.ACCEPT_ENCODING: \"gzip\"}\n    )\n\n    gz_filepath = mock.create_autospec(Path, spec_set=True)\n    gz_filepath.lstat.side_effect = OSError(2, \"No such file or directory\")\n\n    filepath = mock.create_autospec(Path, spec_set=True)\n    filepath.name = \"logo.png\"\n    filepath.with_suffix.return_value = gz_filepath\n    filepath.stat.return_value.st_size = 1024\n    filepath.stat.return_value.st_mtime_ns = 1603733507222449291\n    filepath.stat.return_value.st_mode = MOCK_MODE\n\n    file_sender = FileResponse(filepath)\n    file_sender._path = filepath\n    file_sender._sendfile = mock.AsyncMock(return_value=None)  # type: ignore[method-assign]\n\n    loop.run_until_complete(file_sender.prepare(request))\n\n    assert filepath.open.called\n    assert not gz_filepath.open.called\n\n\ndef test_status_controlled_by_user(loop: asyncio.AbstractEventLoop) -> None:\n    request = make_mocked_request(\"GET\", \"http://python.org/logo.png\", headers={})\n\n    filepath = mock.create_autospec(Path, spec_set=True)\n    filepath.name = \"logo.png\"\n    filepath.stat.return_value.st_size = 1024\n    filepath.stat.return_value.st_mtime_ns = 1603733507222449291\n    filepath.stat.return_value.st_mode = MOCK_MODE\n\n    file_sender = FileResponse(filepath, status=203)\n    file_sender._path = filepath\n    file_sender._sendfile = mock.AsyncMock(return_value=None)  # type: ignore[method-assign]\n\n    loop.run_until_complete(file_sender.prepare(request))\n\n    assert file_sender._status == 203\n\n\nasync def test_file_response_sends_headers_immediately() -> None:\n    \"\"\"Test that FileResponse sends headers immediately (inherits from StreamResponse).\"\"\"\n    writer = mock.create_autospec(StreamWriter, spec_set=True, instance=True)\n\n    request = make_mocked_request(\"GET\", \"http://python.org/logo.png\", writer=writer)\n\n    filepath = mock.create_autospec(Path, spec_set=True)\n    filepath.name = \"logo.png\"\n    filepath.stat.return_value.st_size = 1024\n    filepath.stat.return_value.st_mtime_ns = 1603733507222449291\n    filepath.stat.return_value.st_mode = MOCK_MODE\n\n    file_sender = FileResponse(filepath)\n    file_sender._path = filepath\n    file_sender._sendfile = mock.AsyncMock(return_value=None)  # type: ignore[method-assign]\n\n    # FileResponse inherits from StreamResponse, so should send immediately\n    assert file_sender._send_headers_immediately is True\n\n    # Prepare the response\n    await file_sender.prepare(request)\n\n    # Headers should be sent immediately\n    writer.send_headers.assert_called_once()\n\n\nasync def test_sendfile_fallback_respects_count_boundary() -> None:\n    \"\"\"Regression test: _sendfile_fallback should not read beyond the requested count.\n\n    Previously the first chunk used the full chunk_size even when count was smaller,\n    and the loop subtracted chunk_size instead of the actual bytes read.  Both bugs\n    could cause extra data to be sent when serving range requests.\n    \"\"\"\n    file_data = b\"A\" * 100 + b\"B\" * 50  # 150 bytes total\n    fobj = io.BytesIO(file_data)\n\n    writer = mock.AsyncMock()\n    written = bytearray()\n\n    async def capture_write(data: bytes) -> None:\n        written.extend(data)\n\n    writer.write = capture_write\n    writer.drain = mock.AsyncMock()\n\n    file_sender = FileResponse(\"dummy.bin\")\n    file_sender._chunk_size = 64  # smaller than count to test multi-chunk\n\n    # Request only the first 100 bytes (offset=0, count=100)\n    await file_sender._sendfile_fallback(writer, fobj, offset=0, count=100)\n\n    assert bytes(written) == b\"A\" * 100\n    assert len(written) == 100\n"
  },
  {
    "path": "tests/test_web_sendfile_functional.py",
    "content": "import asyncio\nimport bz2\nimport gzip\nimport pathlib\nimport socket\nfrom collections.abc import Iterable, Iterator\nfrom typing import Protocol\nfrom unittest import mock\n\nimport pytest\nfrom _pytest.fixtures import SubRequest\n\nimport aiohttp\nfrom aiohttp import web\nfrom aiohttp.compression_utils import ZLibBackend\nfrom aiohttp.pytest_plugin import AiohttpClient, AiohttpServer\nfrom aiohttp.typedefs import PathLike\n\ntry:\n    import brotlicffi as brotli\nexcept ImportError:\n    import brotli\n\ntry:\n    import ssl\nexcept ImportError:\n    ssl = None  # type: ignore[assignment]\n\n\nclass _Sender(Protocol):\n    def __call__(\n        self, path: PathLike, chunk_size: int = 256 * 1024\n    ) -> web.FileResponse: ...\n\n\nHELLO_AIOHTTP = b\"Hello aiohttp! :-)\\n\"\n\n\n@pytest.fixture(scope=\"module\")\ndef hello_txt(\n    request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory\n) -> pathlib.Path:\n    \"\"\"Create a temp path with hello.txt and compressed versions.\n\n    The uncompressed text file path is returned by default. Alternatively, an\n    indirect parameter can be passed with an encoding to get a compressed path.\n    \"\"\"\n    txt = tmp_path_factory.mktemp(\"hello-\") / \"hello.txt\"\n    hello = {\n        None: txt,\n        \"gzip\": txt.with_suffix(f\"{txt.suffix}.gz\"),\n        \"br\": txt.with_suffix(f\"{txt.suffix}.br\"),\n        \"bzip2\": txt.with_suffix(f\"{txt.suffix}.bz2\"),\n    }\n    # Uncompressed file is not actually written to test it is not required.\n    hello[\"gzip\"].write_bytes(gzip.compress(HELLO_AIOHTTP))\n    hello[\"br\"].write_bytes(brotli.compress(HELLO_AIOHTTP))\n    hello[\"bzip2\"].write_bytes(bz2.compress(HELLO_AIOHTTP))\n    encoding = getattr(request, \"param\", None)\n    return hello[encoding]\n\n\n@pytest.fixture(params=[\"sendfile\", \"no_sendfile\"], ids=[\"sendfile\", \"no_sendfile\"])\ndef sender(request: SubRequest, loop: asyncio.AbstractEventLoop) -> Iterator[_Sender]:\n    sendfile_mock = None\n\n    def maker(path: PathLike, chunk_size: int = 256 * 1024) -> web.FileResponse:\n        ret = web.FileResponse(path, chunk_size=chunk_size)\n        rloop = asyncio.get_running_loop()\n        is_patched = rloop.sendfile is sendfile_mock\n        assert is_patched if request.param == \"no_sendfile\" else not is_patched\n        return ret\n\n    if request.param == \"no_sendfile\":\n        with mock.patch.object(\n            loop,\n            \"sendfile\",\n            autospec=True,\n            spec_set=True,\n            side_effect=NotImplementedError,\n        ) as sendfile_mock:\n            yield maker\n    else:\n        yield maker\n\n\n@pytest.fixture\ndef app_with_static_route(sender: _Sender) -> web.Application:\n    filename = \"data.unknown_mime_type\"\n    filepath = pathlib.Path(__file__).parent / filename\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        return sender(filepath)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    return app\n\n\nasync def test_static_file_ok(\n    aiohttp_client: AiohttpClient, app_with_static_route: web.Application\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    resp = await client.get(\"/\")\n    assert resp.status == 200\n    txt = await resp.text()\n    assert \"file content\" == txt.rstrip()\n    assert \"application/octet-stream\" == resp.headers[\"Content-Type\"]\n    assert resp.headers.get(\"Content-Encoding\") is None\n    resp.release()\n    await client.close()\n\n\nasync def test_zero_bytes_file_ok(\n    aiohttp_client: AiohttpClient, sender: _Sender\n) -> None:\n    filepath = pathlib.Path(__file__).parent / \"data.zero_bytes\"\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        return sender(filepath)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    # Run the request multiple times to ensure\n    # that an untrapped exception is not hidden\n    # because there is no read of the zero bytes\n    for i in range(2):\n        resp = await client.get(\"/\")\n        assert resp.status == 200\n        txt = await resp.text()\n        assert \"\" == txt.rstrip()\n        assert \"application/octet-stream\" == resp.headers[\"Content-Type\"]\n        assert resp.headers.get(\"Content-Encoding\") is None\n        resp.release()\n\n    await client.close()\n\n\nasync def test_zero_bytes_file_mocked_native_sendfile(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    filepath = pathlib.Path(__file__).parent / \"data.zero_bytes\"\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        return web.FileResponse(filepath)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    # Run the request multiple times to ensure\n    # that an untrapped exception is not hidden\n    # because there is no read of the zero bytes\n    for i in range(2):\n        resp = await client.get(\"/\")\n        assert resp.status == 200\n        txt = await resp.text()\n        assert \"\" == txt.rstrip()\n        assert \"application/octet-stream\" == resp.headers[\"Content-Type\"]\n        assert resp.headers.get(\"Content-Encoding\") is None\n        assert resp.headers.get(\"Content-Length\") == \"0\"\n        resp.release()\n\n    await client.close()\n\n\nasync def test_static_file_ok_string_path(\n    aiohttp_client: AiohttpClient, app_with_static_route: web.Application\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    resp = await client.get(\"/\")\n    assert resp.status == 200\n    txt = await resp.text()\n    assert \"file content\" == txt.rstrip()\n    assert \"application/octet-stream\" == resp.headers[\"Content-Type\"]\n    assert resp.headers.get(\"Content-Encoding\") is None\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_not_exists(aiohttp_client: AiohttpClient) -> None:\n    app = web.Application()\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/fake\")\n    assert resp.status == 404\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_name_too_long(aiohttp_client: AiohttpClient) -> None:\n    app = web.Application()\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/x*500\")\n    assert resp.status == 404\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_upper_directory(aiohttp_client: AiohttpClient) -> None:\n    app = web.Application()\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/../../\")\n    assert resp.status == 404\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_with_content_type(\n    aiohttp_client: AiohttpClient, sender: _Sender\n) -> None:\n    filepath = pathlib.Path(__file__).parent / \"aiohttp.jpg\"\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        return sender(filepath, chunk_size=16)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert resp.status == 200\n    body = await resp.read()\n    with filepath.open(\"rb\") as f:\n        content = f.read()\n        assert content == body\n    assert resp.headers[\"Content-Type\"] == \"image/jpeg\"\n    assert resp.headers.get(\"Content-Encoding\") is None\n    resp.close()\n    resp.release()\n    await client.close()\n\n\n@pytest.mark.parametrize(\"hello_txt\", [\"gzip\", \"br\"], indirect=True)\nasync def test_static_file_custom_content_type(\n    hello_txt: pathlib.Path, aiohttp_client: AiohttpClient, sender: _Sender\n) -> None:\n    \"\"\"Test that custom type without encoding is returned for encoded request.\"\"\"\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        resp = sender(hello_txt, chunk_size=16)\n        resp.content_type = \"application/pdf\"\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert resp.status == 200\n    assert resp.headers.get(\"Content-Encoding\") is None\n    assert resp.headers[\"Content-Type\"] == \"application/pdf\"\n    assert await resp.read() == hello_txt.read_bytes()\n    resp.close()\n    resp.release()\n    await client.close()\n\n\n@pytest.mark.parametrize(\n    (\"accept_encoding\", \"expect_encoding\"),\n    [(\"gzip, deflate\", \"gzip\"), (\"gzip, deflate, br\", \"br\")],\n)\nasync def test_static_file_custom_content_type_compress(\n    hello_txt: pathlib.Path,\n    aiohttp_client: AiohttpClient,\n    sender: _Sender,\n    accept_encoding: str,\n    expect_encoding: str,\n) -> None:\n    \"\"\"Test that custom type with encoding is returned for unencoded requests.\"\"\"\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        resp = sender(hello_txt, chunk_size=16)\n        resp.content_type = \"application/pdf\"\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\", headers={\"Accept-Encoding\": accept_encoding})\n    assert resp.status == 200\n    assert resp.headers.get(\"Content-Encoding\") == expect_encoding\n    assert resp.headers[\"Content-Type\"] == \"application/pdf\"\n    assert await resp.read() == HELLO_AIOHTTP\n    resp.close()\n    resp.release()\n    await client.close()\n\n\n@pytest.mark.parametrize(\n    (\"accept_encoding\", \"expect_encoding\"),\n    [(\"gzip, deflate\", \"gzip\"), (\"gzip, deflate, br\", \"br\")],\n)\n@pytest.mark.parametrize(\"forced_compression\", [None, web.ContentCoding.gzip])\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_static_file_with_encoding_and_enable_compression(\n    hello_txt: pathlib.Path,\n    aiohttp_client: AiohttpClient,\n    sender: _Sender,\n    accept_encoding: str,\n    expect_encoding: str,\n    forced_compression: web.ContentCoding | None,\n) -> None:\n    \"\"\"Test that enable_compression does not double compress when an encoded file is also present.\"\"\"\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        resp = sender(hello_txt)\n        resp.enable_compression(forced_compression)\n        return resp\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\", headers={\"Accept-Encoding\": accept_encoding})\n    assert resp.status == 200\n    assert resp.headers.get(\"Content-Encoding\") == expect_encoding\n    assert resp.headers[\"Content-Type\"] == \"text/plain\"\n    assert await resp.read() == HELLO_AIOHTTP\n    resp.close()\n    resp.release()\n    await client.close()\n\n\n@pytest.mark.parametrize(\n    (\"hello_txt\", \"expect_type\"),\n    [\n        (\"gzip\", \"application/gzip\"),\n        (\"br\", \"application/x-brotli\"),\n        (\"bzip2\", \"application/x-bzip2\"),\n    ],\n    indirect=[\"hello_txt\"],\n)\nasync def test_static_file_with_content_encoding(\n    hello_txt: pathlib.Path,\n    aiohttp_client: AiohttpClient,\n    sender: _Sender,\n    expect_type: str,\n) -> None:\n    \"\"\"Test requesting static compressed files returns the correct content type and encoding.\"\"\"\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        return sender(hello_txt)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert resp.status == 200\n    assert resp.headers.get(\"Content-Encoding\") is None\n    assert resp.headers[\"Content-Type\"] == expect_type\n    assert await resp.read() == hello_txt.read_bytes()\n    resp.close()\n\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_if_modified_since(\n    aiohttp_client: AiohttpClient, app_with_static_route: web.Application\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    lastmod = resp.headers.get(\"Last-Modified\")\n    assert lastmod is not None\n    resp.close()\n    resp.release()\n\n    resp = await client.get(\"/\", headers={\"If-Modified-Since\": lastmod})\n    body = await resp.read()\n    assert 304 == resp.status\n    assert resp.headers.get(\"Content-Length\") is None\n    assert resp.headers.get(\"Last-Modified\") == lastmod\n    assert b\"\" == body\n    resp.close()\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_if_modified_since_past_date(\n    aiohttp_client: AiohttpClient, app_with_static_route: web.Application\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    lastmod = \"Mon, 1 Jan 1990 01:01:01 GMT\"\n\n    resp = await client.get(\"/\", headers={\"If-Modified-Since\": lastmod})\n    assert 200 == resp.status\n    resp.close()\n\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_if_modified_since_invalid_date(\n    aiohttp_client: AiohttpClient, app_with_static_route: web.Application\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    lastmod = \"not a valid HTTP-date\"\n\n    resp = await client.get(\"/\", headers={\"If-Modified-Since\": lastmod})\n    assert 200 == resp.status\n    resp.close()\n\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_if_modified_since_future_date(\n    aiohttp_client: AiohttpClient, app_with_static_route: web.Application\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    lastmod = \"Fri, 31 Dec 9999 23:59:59 GMT\"\n\n    resp = await client.get(\"/\", headers={\"If-Modified-Since\": lastmod})\n    body = await resp.read()\n    assert 304 == resp.status\n    assert resp.headers.get(\"Content-Length\") is None\n    assert resp.headers.get(\"Last-Modified\")\n    assert b\"\" == body\n    resp.close()\n\n    resp.release()\n    await client.close()\n\n\n@pytest.mark.parametrize(\"if_unmodified_since\", (\"\", \"Fri, 31 Dec 0000 23:59:59 GMT\"))\nasync def test_static_file_if_match(\n    aiohttp_client: AiohttpClient,\n    app_with_static_route: web.Application,\n    if_unmodified_since: str,\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    original_etag = resp.headers.get(\"ETag\")\n\n    assert original_etag is not None\n    resp.close()\n    resp.release()\n\n    headers = {\"If-Match\": original_etag, \"If-Unmodified-Since\": if_unmodified_since}\n    resp = await client.head(\"/\", headers=headers)\n    body = await resp.read()\n    assert 200 == resp.status\n    assert resp.headers.get(\"ETag\")\n    assert resp.headers.get(\"Last-Modified\")\n    assert b\"\" == body\n    resp.close()\n    resp.release()\n\n    await client.close()\n\n\n@pytest.mark.parametrize(\"if_unmodified_since\", (\"\", \"Fri, 31 Dec 0000 23:59:59 GMT\"))\n@pytest.mark.parametrize(\n    \"etags,expected_status\",\n    [\n        ((\"*\",), 200),\n        (('\"example-tag\"', 'W/\"weak-tag\"'), 412),\n    ],\n)\nasync def test_static_file_if_match_custom_tags(\n    aiohttp_client: AiohttpClient,\n    app_with_static_route: web.Application,\n    if_unmodified_since: str,\n    etags: tuple[str],\n    expected_status: int,\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    if_match = \", \".join(etags)\n    headers = {\"If-Match\": if_match, \"If-Unmodified-Since\": if_unmodified_since}\n    resp = await client.head(\"/\", headers=headers)\n    body = await resp.read()\n    assert expected_status == resp.status\n    assert b\"\" == body\n    resp.close()\n\n    resp.release()\n    await client.close()\n\n\n@pytest.mark.parametrize(\"if_modified_since\", (\"\", \"Fri, 31 Dec 9999 23:59:59 GMT\"))\n@pytest.mark.parametrize(\n    \"additional_etags\",\n    (\n        (),\n        ('\"some-other-strong-etag\"', 'W/\"weak-tag\"', \"invalid-tag\"),\n    ),\n)\nasync def test_static_file_if_none_match(\n    aiohttp_client: AiohttpClient,\n    app_with_static_route: web.Application,\n    if_modified_since: str,\n    additional_etags: Iterable[str],\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    original_etag = resp.headers[\"ETag\"]\n\n    assert resp.headers.get(\"Last-Modified\") is not None\n    resp.close()\n    resp.release()\n\n    etag = \",\".join((original_etag, *additional_etags))\n\n    resp = await client.get(\n        \"/\", headers={\"If-None-Match\": etag, \"If-Modified-Since\": if_modified_since}\n    )\n    body = await resp.read()\n    assert 304 == resp.status\n    assert resp.headers.get(\"Content-Length\") is None\n    assert resp.headers.get(\"ETag\") == original_etag\n    assert b\"\" == body\n    resp.close()\n    resp.release()\n\n    await client.close()\n\n\nasync def test_static_file_if_none_match_star(\n    aiohttp_client: AiohttpClient,\n    app_with_static_route: web.Application,\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    resp = await client.head(\"/\", headers={\"If-None-Match\": \"*\"})\n    body = await resp.read()\n    assert 304 == resp.status\n    assert resp.headers.get(\"Content-Length\") is None\n    assert resp.headers.get(\"ETag\")\n    assert resp.headers.get(\"Last-Modified\")\n    assert b\"\" == body\n    resp.close()\n\n    resp.release()\n    await client.close()\n\n\n@pytest.mark.parametrize(\"if_modified_since\", (\"\", \"Fri, 31 Dec 9999 23:59:59 GMT\"))\nasync def test_static_file_if_none_match_weak(\n    aiohttp_client: AiohttpClient,\n    app_with_static_route: web.Application,\n    if_modified_since: str,\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    resp = await client.get(\"/\")\n    assert 200 == resp.status\n    original_etag = resp.headers[\"ETag\"]\n\n    assert resp.headers.get(\"Last-Modified\") is not None\n    resp.close()\n    resp.release()\n\n    weak_etag = f\"W/{original_etag}\"\n\n    resp = await client.get(\n        \"/\",\n        headers={\"If-None-Match\": weak_etag, \"If-Modified-Since\": if_modified_since},\n    )\n    body = await resp.read()\n    assert 304 == resp.status\n    assert resp.headers.get(\"Content-Length\") is None\n    assert resp.headers.get(\"ETag\") == original_etag\n    assert b\"\" == body\n    resp.close()\n    resp.release()\n\n    await client.close()\n\n\n@pytest.mark.skipif(ssl is None, reason=\"ssl not supported\")\nasync def test_static_file_ssl(\n    aiohttp_server: AiohttpServer,\n    ssl_ctx: ssl.SSLContext,\n    aiohttp_client: AiohttpClient,\n    client_ssl_ctx: ssl.SSLContext,\n) -> None:\n    dirname = pathlib.Path(__file__).parent\n    filename = \"data.unknown_mime_type\"\n    app = web.Application()\n    app.router.add_static(\"/static\", dirname)\n    server = await aiohttp_server(app, ssl=ssl_ctx)\n    conn = aiohttp.TCPConnector(ssl=client_ssl_ctx)\n    client = await aiohttp_client(server, connector=conn)\n\n    resp = await client.get(\"/static/\" + filename)\n    assert 200 == resp.status\n    txt = await resp.text()\n    assert \"file content\" == txt.rstrip()\n    ct = resp.headers[\"CONTENT-TYPE\"]\n    assert \"application/octet-stream\" == ct\n    assert resp.headers.get(\"CONTENT-ENCODING\") is None\n\n    resp.release()\n    await client.close()\n    await conn.close()\n\n\nasync def test_static_file_directory_traversal_attack(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    dirname = pathlib.Path(__file__).parent\n    relpath = \"../README.rst\"\n    full_path = dirname / relpath\n    assert full_path.is_file()\n\n    app = web.Application()\n    app.router.add_static(\"/static\", dirname)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/static/\" + relpath)\n    assert 404 == resp.status\n    resp.release()\n\n    url_relpath2 = \"/static/dir/../\" + relpath\n    resp = await client.get(url_relpath2)\n    assert 404 == resp.status\n    resp.release()\n\n    url_abspath = \"/static/\" + str(full_path.resolve())\n    resp = await client.get(url_abspath)\n    assert resp.status == 404\n    resp.release()\n\n    await client.close()\n\n\n@pytest.mark.skip_blockbuster\nasync def test_static_file_huge(\n    aiohttp_client: AiohttpClient, tmp_path: pathlib.Path\n) -> None:\n    file_path = tmp_path / \"huge_data.unknown_mime_type\"\n\n    # fill 20MB file\n    with file_path.open(\"wb\") as f:\n        for i in range(1024 * 20):\n            f.write((chr(i % 64 + 0x20) * 1024).encode())\n\n    file_st = file_path.stat()\n\n    app = web.Application()\n    app.router.add_static(\"/static\", str(tmp_path))\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/static/\" + file_path.name)\n    assert 200 == resp.status\n    ct = resp.headers[\"CONTENT-TYPE\"]\n    assert \"application/octet-stream\" == ct\n    assert resp.headers.get(\"CONTENT-ENCODING\") is None\n    assert int(resp.headers[\"CONTENT-LENGTH\"]) == file_st.st_size\n\n    f2 = file_path.open(\"rb\")\n    off = 0\n    cnt = 0\n    while off < file_st.st_size:\n        chunk = await resp.content.readany()\n        expected = f2.read(len(chunk))\n        assert chunk == expected\n        off += len(chunk)\n        cnt += 1\n    f2.close()\n\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_range(\n    aiohttp_client: AiohttpClient, sender: _Sender\n) -> None:\n    filepath = pathlib.Path(__file__).parent / \"sample.txt\"\n\n    filesize = filepath.stat().st_size\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        return sender(filepath, chunk_size=16)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with filepath.open(\"rb\") as f:\n        content = f.read()\n\n    # Ensure the whole file requested in parts is correct\n    responses = await asyncio.gather(\n        client.get(\"/\", headers={\"Range\": \"bytes=0-999\"}),\n        client.get(\"/\", headers={\"Range\": \"bytes=1000-1999\"}),\n        client.get(\"/\", headers={\"Range\": \"bytes=2000-\"}),\n    )\n    assert len(responses) == 3\n    assert responses[0].status == 206, \"failed 'bytes=0-999': %s\" % responses[0].reason\n    assert (\n        responses[0].headers[\"Content-Range\"] == f\"bytes 0-999/{filesize}\"\n    ), \"failed: Content-Range Error\"\n    assert responses[1].status == 206, (\n        \"failed 'bytes=1000-1999': %s\" % responses[1].reason\n    )\n    assert (\n        responses[1].headers[\"Content-Range\"] == f\"bytes 1000-1999/{filesize}\"\n    ), \"failed: Content-Range Error\"\n    assert responses[2].status == 206, \"failed 'bytes=2000-': %s\" % responses[2].reason\n    assert (\n        responses[2].headers[\"Content-Range\"] == f\"bytes 2000-{filesize - 1}/{filesize}\"\n    ), \"failed: Content-Range Error\"\n\n    body = await asyncio.gather(\n        *(resp.read() for resp in responses),\n    )\n\n    assert len(body[0]) == 1000, \"failed 'bytes=0-999', received %d bytes\" % len(\n        body[0]\n    )\n    assert len(body[1]) == 1000, \"failed 'bytes=1000-1999', received %d bytes\" % len(\n        body[1]\n    )\n    responses[0].close()\n    responses[1].close()\n    responses[2].close()\n\n    for resp in responses:\n        resp.release()\n\n    assert content == b\"\".join(body)\n\n    await client.close()\n\n\nasync def test_static_file_range_end_bigger_than_size(\n    aiohttp_client: AiohttpClient, sender: _Sender\n) -> None:\n    filepath = pathlib.Path(__file__).parent / \"aiohttp.png\"\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        return sender(filepath, chunk_size=16)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with filepath.open(\"rb\") as f:\n        content = f.read()\n\n        # Ensure the whole file requested in parts is correct\n        response = await client.get(\"/\", headers={\"Range\": \"bytes=54000-55000\"})\n\n        assert response.status == 206, (\n            \"failed 'bytes=54000-55000': %s\" % response.reason\n        )\n        assert (\n            response.headers[\"Content-Range\"] == \"bytes 54000-54996/54997\"\n        ), \"failed: Content-Range Error\"\n\n        body = await response.read()\n        assert len(body) == 997, \"failed 'bytes=54000-55000', received %d bytes\" % len(\n            body\n        )\n\n        assert content[54000:] == body\n\n    response.release()\n    await client.close()\n\n\nasync def test_static_file_range_beyond_eof(\n    aiohttp_client: AiohttpClient, sender: _Sender\n) -> None:\n    filepath = pathlib.Path(__file__).parent / \"aiohttp.png\"\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        return sender(filepath, chunk_size=16)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    # Ensure the whole file requested in parts is correct\n    response = await client.get(\"/\", headers={\"Range\": \"bytes=1000000-1200000\"})\n\n    assert response.status == 416, (\n        \"failed 'bytes=1000000-1200000': %s\" % response.reason\n    )\n\n    response.release()\n    await client.close()\n\n\nasync def test_static_file_range_tail(\n    aiohttp_client: AiohttpClient, sender: _Sender\n) -> None:\n    filepath = pathlib.Path(__file__).parent / \"aiohttp.png\"\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        return sender(filepath, chunk_size=16)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    with filepath.open(\"rb\") as f:\n        content = f.read()\n\n    # Ensure the tail of the file is correct\n    resp = await client.get(\"/\", headers={\"Range\": \"bytes=-500\"})\n    assert resp.status == 206, resp.reason\n    assert (\n        resp.headers[\"Content-Range\"] == \"bytes 54497-54996/54997\"\n    ), \"failed: Content-Range Error\"\n    body4 = await resp.read()\n    resp.close()\n    resp.release()\n    assert content[-500:] == body4\n\n    # Ensure out-of-range tails could be handled\n    resp2 = await client.get(\"/\", headers={\"Range\": \"bytes=-99999999999999\"})\n    assert resp2.status == 206, resp.reason\n    assert (\n        resp2.headers[\"Content-Range\"] == \"bytes 0-54996/54997\"\n    ), \"failed: Content-Range Error\"\n    resp2.release()\n\n    await client.close()\n\n\nasync def test_static_file_invalid_range(\n    aiohttp_client: AiohttpClient, sender: _Sender\n) -> None:\n    filepath = pathlib.Path(__file__).parent / \"aiohttp.png\"\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        return sender(filepath, chunk_size=16)\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    # range must be in bytes\n    resp = await client.get(\"/\", headers={\"Range\": \"blocks=0-10\"})\n    assert resp.status == 416, \"Range must be in bytes\"\n    resp.close()\n    resp.release()\n\n    # start > end\n    resp = await client.get(\"/\", headers={\"Range\": \"bytes=100-0\"})\n    assert resp.status == 416, \"Range start can't be greater than end\"\n    resp.close()\n    resp.release()\n\n    # start > end\n    resp = await client.get(\"/\", headers={\"Range\": \"bytes=10-9\"})\n    assert resp.status == 416, \"Range start can't be greater than end\"\n    resp.close()\n    resp.release()\n\n    # non-number range\n    resp = await client.get(\"/\", headers={\"Range\": \"bytes=a-f\"})\n    assert resp.status == 416, \"Range must be integers\"\n    resp.close()\n    resp.release()\n\n    # double dash range\n    resp = await client.get(\"/\", headers={\"Range\": \"bytes=0--10\"})\n    assert resp.status == 416, \"double dash in range\"\n    resp.close()\n    resp.release()\n\n    # no range\n    resp = await client.get(\"/\", headers={\"Range\": \"bytes=-\"})\n    assert resp.status == 416, \"no range given\"\n    resp.close()\n    resp.release()\n\n    await client.close()\n\n\nasync def test_static_file_if_unmodified_since_past_with_range(\n    aiohttp_client: AiohttpClient, app_with_static_route: web.Application\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    lastmod = \"Mon, 1 Jan 1990 01:01:01 GMT\"\n\n    resp = await client.get(\n        \"/\", headers={\"If-Unmodified-Since\": lastmod, \"Range\": \"bytes=2-\"}\n    )\n    assert 412 == resp.status\n    resp.close()\n    resp.release()\n\n    await client.close()\n\n\nasync def test_static_file_if_unmodified_since_future_with_range(\n    aiohttp_client: AiohttpClient, app_with_static_route: web.Application\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    lastmod = \"Fri, 31 Dec 9999 23:59:59 GMT\"\n\n    resp = await client.get(\n        \"/\", headers={\"If-Unmodified-Since\": lastmod, \"Range\": \"bytes=2-\"}\n    )\n    assert 206 == resp.status\n    assert resp.headers[\"Content-Range\"] == \"bytes 2-12/13\"\n    assert resp.headers[\"Content-Length\"] == \"11\"\n    resp.close()\n    resp.release()\n\n    await client.close()\n\n\nasync def test_static_file_if_range_past_with_range(\n    aiohttp_client: AiohttpClient, app_with_static_route: web.Application\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    lastmod = \"Mon, 1 Jan 1990 01:01:01 GMT\"\n\n    resp = await client.get(\"/\", headers={\"If-Range\": lastmod, \"Range\": \"bytes=2-\"})\n    assert 200 == resp.status\n    assert resp.headers[\"Content-Length\"] == \"13\"\n    resp.close()\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_if_range_future_with_range(\n    aiohttp_client: AiohttpClient, app_with_static_route: web.Application\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    lastmod = \"Fri, 31 Dec 9999 23:59:59 GMT\"\n\n    resp = await client.get(\"/\", headers={\"If-Range\": lastmod, \"Range\": \"bytes=2-\"})\n    assert 206 == resp.status\n    assert resp.headers[\"Content-Range\"] == \"bytes 2-12/13\"\n    assert resp.headers[\"Content-Length\"] == \"11\"\n    resp.close()\n\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_if_unmodified_since_past_without_range(\n    aiohttp_client: AiohttpClient, app_with_static_route: web.Application\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    lastmod = \"Mon, 1 Jan 1990 01:01:01 GMT\"\n\n    resp = await client.get(\"/\", headers={\"If-Unmodified-Since\": lastmod})\n    assert 412 == resp.status\n    resp.close()\n\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_if_unmodified_since_future_without_range(\n    aiohttp_client: AiohttpClient, app_with_static_route: web.Application\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    lastmod = \"Fri, 31 Dec 9999 23:59:59 GMT\"\n\n    resp = await client.get(\"/\", headers={\"If-Unmodified-Since\": lastmod})\n    assert 200 == resp.status\n    assert resp.headers[\"Content-Length\"] == \"13\"\n    resp.close()\n\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_if_range_past_without_range(\n    aiohttp_client: AiohttpClient, app_with_static_route: web.Application\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    lastmod = \"Mon, 1 Jan 1990 01:01:01 GMT\"\n\n    resp = await client.get(\"/\", headers={\"If-Range\": lastmod})\n    assert 200 == resp.status\n    assert resp.headers[\"Content-Length\"] == \"13\"\n    resp.close()\n\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_if_range_future_without_range(\n    aiohttp_client: AiohttpClient, app_with_static_route: web.Application\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    lastmod = \"Fri, 31 Dec 9999 23:59:59 GMT\"\n\n    resp = await client.get(\"/\", headers={\"If-Range\": lastmod})\n    assert 200 == resp.status\n    assert resp.headers[\"Content-Length\"] == \"13\"\n    resp.close()\n\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_if_unmodified_since_invalid_date(\n    aiohttp_client: AiohttpClient, app_with_static_route: web.Application\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    lastmod = \"not a valid HTTP-date\"\n\n    resp = await client.get(\"/\", headers={\"If-Unmodified-Since\": lastmod})\n    assert 200 == resp.status\n    resp.close()\n\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_if_range_invalid_date(\n    aiohttp_client: AiohttpClient, app_with_static_route: web.Application\n) -> None:\n    client = await aiohttp_client(app_with_static_route)\n\n    lastmod = \"not a valid HTTP-date\"\n\n    resp = await client.get(\"/\", headers={\"If-Range\": lastmod})\n    assert 200 == resp.status\n    resp.close()\n    resp.release()\n\n    await client.close()\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_static_file_compression(\n    aiohttp_client: AiohttpClient,\n    sender: _Sender,\n) -> None:\n    filepath = pathlib.Path(__file__).parent / \"data.unknown_mime_type\"\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        ret = sender(filepath)\n        ret.enable_compression()\n        return ret\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app, auto_decompress=False)\n\n    resp = await client.get(\"/\")\n    assert resp.status == 200\n    zcomp = ZLibBackend.compressobj(wbits=ZLibBackend.MAX_WBITS)\n    expected_body = zcomp.compress(b\"file content\\n\") + zcomp.flush()\n    assert expected_body == await resp.read()\n    assert \"application/octet-stream\" == resp.headers[\"Content-Type\"]\n    assert resp.headers.get(\"Content-Encoding\") == \"deflate\"\n    resp.release()\n\n    await client.close()\n\n\n@pytest.mark.skip_blockbuster\nasync def test_static_file_huge_cancel(\n    aiohttp_client: AiohttpClient, tmp_path: pathlib.Path\n) -> None:\n    file_path = tmp_path / \"huge_data.unknown_mime_type\"\n\n    # fill 100MB file\n    with file_path.open(\"wb\") as f:\n        for i in range(1024 * 20):\n            f.write((chr(i % 64 + 0x20) * 1024).encode())\n\n    task = None\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        nonlocal task\n        task = request.task\n        # reduce send buffer size\n        tr = request.transport\n        assert tr is not None\n        sock = tr.get_extra_info(\"socket\")\n        sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1024)\n        ret = web.FileResponse(file_path)\n        return ret\n\n    app = web.Application()\n\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert resp.status == 200\n    assert task is not None\n    task.cancel()\n    await asyncio.sleep(0)\n    data = b\"\"\n    while True:\n        try:\n            data += await resp.content.read(1024)\n        except aiohttp.ClientPayloadError:\n            break\n    assert len(data) < 1024 * 1024 * 20\n\n    resp.release()\n    await client.close()\n\n\nasync def test_static_file_huge_error(\n    aiohttp_client: AiohttpClient, tmp_path: pathlib.Path\n) -> None:\n    file_path = tmp_path / \"huge_data.unknown_mime_type\"\n\n    # fill 20MB file\n    with file_path.open(\"wb\") as f:\n        f.seek(20 * 1024 * 1024)\n        f.write(b\"1\")\n\n    async def handler(request: web.Request) -> web.FileResponse:\n        # reduce send buffer size\n        tr = request.transport\n        assert tr is not None\n        sock = tr.get_extra_info(\"socket\")\n        sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1024)\n        ret = web.FileResponse(file_path)\n        return ret\n\n    app = web.Application()\n\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert resp.status == 200\n    # raise an exception on server side\n    resp.close()\n\n    resp.release()\n    await client.close()\n"
  },
  {
    "path": "tests/test_web_server.py",
    "content": "import asyncio\nimport socket\nfrom contextlib import suppress\nfrom typing import NoReturn\nfrom unittest import mock\n\nimport pytest\n\nfrom aiohttp import client, web\nfrom aiohttp.http_exceptions import BadHttpMethod, BadStatusLine\nfrom aiohttp.pytest_plugin import AiohttpClient, AiohttpRawServer\n\n\nasync def test_simple_server(\n    aiohttp_raw_server: AiohttpRawServer, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.BaseRequest) -> web.Response:\n        return web.Response(text=str(request.rel_url))\n\n    server = await aiohttp_raw_server(handler)\n    cli = await aiohttp_client(server)\n    resp = await cli.get(\"/path/to\")\n    assert resp.status == 200\n    txt = await resp.text()\n    assert txt == \"/path/to\"\n\n\nasync def test_unsupported_upgrade(\n    aiohttp_raw_server: AiohttpRawServer, aiohttp_client: AiohttpClient\n) -> None:\n    # don't fail if a client probes for an unsupported protocol upgrade\n    # https://github.com/aio-libs/aiohttp/issues/6446#issuecomment-999032039\n    async def handler(request: web.BaseRequest) -> web.Response:\n        return web.Response(body=await request.read())\n\n    upgrade_headers = {\"Connection\": \"Upgrade\", \"Upgrade\": \"unsupported_proto\"}\n    server = await aiohttp_raw_server(handler)\n    cli = await aiohttp_client(server)\n    test_data = b\"Test\"\n    resp = await cli.post(\"/path/to\", data=test_data, headers=upgrade_headers)\n    assert resp.status == 200\n    data = await resp.read()\n    assert data == test_data\n\n\nasync def test_raw_server_not_http_exception(\n    aiohttp_raw_server: AiohttpRawServer,\n    aiohttp_client: AiohttpClient,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    # disable debug mode not to print traceback\n    loop.set_debug(False)\n\n    exc = RuntimeError(\"custom runtime error\")\n\n    async def handler(request: web.BaseRequest) -> NoReturn:\n        raise exc\n\n    logger = mock.Mock()\n    server = await aiohttp_raw_server(handler, logger=logger)\n    cli = await aiohttp_client(server)\n    resp = await cli.get(\"/path/to\")\n    assert resp.status == 500\n    assert resp.headers[\"Content-Type\"].startswith(\"text/plain\")\n\n    txt = await resp.text()\n    assert txt.startswith(\"500 Internal Server Error\")\n    assert \"Traceback\" not in txt\n\n    logger.exception.assert_called_with(\n        \"Error handling request from %s\", cli.host, exc_info=exc\n    )\n\n\nasync def test_raw_server_logs_invalid_method_with_loop_debug(\n    aiohttp_raw_server: AiohttpRawServer,\n    aiohttp_client: AiohttpClient,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    exc = BadHttpMethod(b\"\\x16\\x03\\x03\\x01F\\x01\".decode(), \"error\")\n\n    async def handler(request: web.BaseRequest) -> NoReturn:\n        raise exc\n\n    loop = asyncio.get_event_loop()\n    loop.set_debug(True)\n    logger = mock.Mock()\n    server = await aiohttp_raw_server(handler, logger=logger)\n    cli = await aiohttp_client(server)\n    resp = await cli.get(\"/path/to\")\n    assert resp.status == 500\n    assert resp.headers[\"Content-Type\"].startswith(\"text/plain\")\n\n    txt = await resp.text()\n    assert \"Traceback (most recent call last):\\n\" in txt\n\n    # BadHttpMethod should be logged as debug\n    # on the first request since the client may\n    # be probing for TLS/SSL support which is\n    # expected to fail\n    logger.debug.assert_called_with(\n        \"Error handling request from %s\", cli.host, exc_info=exc\n    )\n    logger.debug.reset_mock()\n\n    # Now make another connection to the server\n    # to make sure that the exception is logged\n    # at debug on a second fresh connection\n    cli2 = await aiohttp_client(server)\n    resp = await cli2.get(\"/path/to\")\n    assert resp.status == 500\n    assert resp.headers[\"Content-Type\"].startswith(\"text/plain\")\n    # BadHttpMethod should be logged as debug\n    # on the first request since the client may\n    # be probing for TLS/SSL support which is\n    # expected to fail\n    logger.debug.assert_called_with(\n        \"Error handling request from %s\", cli.host, exc_info=exc\n    )\n\n\nasync def test_raw_server_logs_invalid_method_without_loop_debug(\n    aiohttp_raw_server: AiohttpRawServer,\n    aiohttp_client: AiohttpClient,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    exc = BadHttpMethod(b\"\\x16\\x03\\x03\\x01F\\x01\".decode(), \"error\")\n\n    async def handler(request: web.BaseRequest) -> NoReturn:\n        raise exc\n\n    loop = asyncio.get_event_loop()\n    loop.set_debug(False)\n    logger = mock.Mock()\n    server = await aiohttp_raw_server(handler, logger=logger)\n    cli = await aiohttp_client(server)\n    resp = await cli.get(\"/path/to\")\n    assert resp.status == 500\n    assert resp.headers[\"Content-Type\"].startswith(\"text/plain\")\n\n    txt = await resp.text()\n    assert \"Traceback (most recent call last):\\n\" not in txt\n\n    # BadHttpMethod should be logged as debug\n    # on the first request since the client may\n    # be probing for TLS/SSL support which is\n    # expected to fail\n    logger.debug.assert_called_with(\n        \"Error handling request from %s\", cli.host, exc_info=exc\n    )\n\n\nasync def test_raw_server_logs_invalid_method_second_request(\n    aiohttp_raw_server: AiohttpRawServer,\n    aiohttp_client: AiohttpClient,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    exc = BadHttpMethod(b\"\\x16\\x03\\x03\\x01F\\x01\".decode(), \"error\")\n    request_count = 0\n\n    async def handler(request: web.BaseRequest) -> web.Response:\n        nonlocal request_count\n        request_count += 1\n        if request_count == 2:\n            raise exc\n        return web.Response()\n\n    loop = asyncio.get_event_loop()\n    loop.set_debug(False)\n    logger = mock.Mock()\n    server = await aiohttp_raw_server(handler, logger=logger)\n    cli = await aiohttp_client(server)\n    resp = await cli.get(\"/path/to\")\n    assert resp.status == 200\n    resp = await cli.get(\"/path/to\")\n    assert resp.status == 500\n    assert resp.headers[\"Content-Type\"].startswith(\"text/plain\")\n    # BadHttpMethod should be logged as an exception\n    # if its not the first request since we know\n    # that the client already was speaking HTTP\n    logger.exception.assert_called_with(\n        \"Error handling request from %s\", cli.host, exc_info=exc\n    )\n\n\nasync def test_raw_server_logs_bad_status_line_as_exception(\n    aiohttp_raw_server: AiohttpRawServer,\n    aiohttp_client: AiohttpClient,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    exc = BadStatusLine(b\"\\x16\\x03\\x03\\x01F\\x01\".decode(), \"error\")\n\n    async def handler(request: web.BaseRequest) -> NoReturn:\n        raise exc\n\n    loop = asyncio.get_event_loop()\n    loop.set_debug(False)\n    logger = mock.Mock()\n    server = await aiohttp_raw_server(handler, logger=logger)\n    cli = await aiohttp_client(server)\n    resp = await cli.get(\"/path/to\")\n    assert resp.status == 500\n    assert resp.headers[\"Content-Type\"].startswith(\"text/plain\")\n\n    txt = await resp.text()\n    assert \"Traceback (most recent call last):\\n\" not in txt\n\n    logger.exception.assert_called_with(\n        \"Error handling request from %s\", cli.host, exc_info=exc\n    )\n\n\nasync def test_raw_server_handler_timeout(\n    aiohttp_raw_server: AiohttpRawServer, aiohttp_client: AiohttpClient\n) -> None:\n    loop = asyncio.get_event_loop()\n    loop.set_debug(True)\n    exc = asyncio.TimeoutError(\"error\")\n\n    async def handler(request: web.BaseRequest) -> NoReturn:\n        raise exc\n\n    logger = mock.Mock()\n    server = await aiohttp_raw_server(handler, logger=logger)\n    cli = await aiohttp_client(server)\n    resp = await cli.get(\"/path/to\")\n    assert resp.status == 504\n\n    await resp.text()\n    logger.debug.assert_called_with(\"Request handler timed out.\", exc_info=exc)\n\n\nasync def test_raw_server_do_not_swallow_exceptions(\n    aiohttp_raw_server: AiohttpRawServer, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.BaseRequest) -> NoReturn:\n        raise asyncio.CancelledError()\n\n    loop = asyncio.get_event_loop()\n    loop.set_debug(True)\n    logger = mock.Mock()\n    server = await aiohttp_raw_server(handler, logger=logger)\n    cli = await aiohttp_client(server)\n\n    with pytest.raises(client.ServerDisconnectedError):\n        await cli.get(\"/path/to\")\n\n    logger.debug.assert_called_with(\"Ignored premature client disconnection\")\n\n\nasync def test_raw_server_does_not_swallow_base_exceptions(\n    aiohttp_raw_server: AiohttpRawServer, aiohttp_client: AiohttpClient\n) -> None:\n    class UnexpectedException(BaseException):\n        \"\"\"Dummy base exception.\"\"\"\n\n    async def handler(request: web.BaseRequest) -> NoReturn:\n        raise UnexpectedException()\n\n    loop = asyncio.get_event_loop()\n    loop.set_debug(True)\n    server = await aiohttp_raw_server(handler)\n    cli = await aiohttp_client(server)\n\n    with pytest.raises(client.ServerDisconnectedError):\n        await cli.get(\"/path/to\", timeout=client.ClientTimeout(10))\n\n\nasync def test_raw_server_cancelled_in_write_eof(\n    aiohttp_raw_server: AiohttpRawServer, aiohttp_client: AiohttpClient\n) -> None:\n    class MyResponse(web.Response):\n        async def write_eof(self, data: bytes = b\"\") -> NoReturn:\n            raise asyncio.CancelledError(\"error\")\n\n    async def handler(request: web.BaseRequest) -> MyResponse:\n        resp = MyResponse(text=str(request.rel_url))\n        return resp\n\n    loop = asyncio.get_event_loop()\n    loop.set_debug(True)\n    logger = mock.Mock()\n    server = await aiohttp_raw_server(handler, logger=logger)\n    cli = await aiohttp_client(server)\n\n    with pytest.raises(client.ServerDisconnectedError):\n        await cli.get(\"/path/to\")\n\n    logger.debug.assert_called_with(\"Ignored premature client disconnection\")\n\n\nasync def test_raw_server_not_http_exception_debug(\n    aiohttp_raw_server: AiohttpRawServer, aiohttp_client: AiohttpClient\n) -> None:\n    exc = RuntimeError(\"custom runtime error\")\n\n    async def handler(request: web.BaseRequest) -> NoReturn:\n        raise exc\n\n    loop = asyncio.get_event_loop()\n    loop.set_debug(True)\n    logger = mock.Mock()\n    server = await aiohttp_raw_server(handler, logger=logger)\n    cli = await aiohttp_client(server)\n    resp = await cli.get(\"/path/to\")\n    assert resp.status == 500\n    assert resp.headers[\"Content-Type\"].startswith(\"text/plain\")\n\n    txt = await resp.text()\n    assert \"Traceback (most recent call last):\\n\" in txt\n\n    logger.exception.assert_called_with(\n        \"Error handling request from %s\", cli.host, exc_info=exc\n    )\n\n\nasync def test_raw_server_html_exception(\n    aiohttp_raw_server: AiohttpRawServer,\n    aiohttp_client: AiohttpClient,\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    # disable debug mode not to print traceback\n    loop.set_debug(False)\n\n    exc = RuntimeError(\"custom runtime error\")\n\n    async def handler(request: web.BaseRequest) -> NoReturn:\n        raise exc\n\n    logger = mock.Mock()\n    server = await aiohttp_raw_server(handler, logger=logger)\n    cli = await aiohttp_client(server)\n    resp = await cli.get(\"/path/to\", headers={\"Accept\": \"text/html\"})\n    assert resp.status == 500\n    assert resp.headers[\"Content-Type\"].startswith(\"text/html\")\n\n    txt = await resp.text()\n    assert txt == (\n        \"<html><head><title>500 Internal Server Error</title></head><body>\\n\"\n        \"<h1>500 Internal Server Error</h1>\\n\"\n        \"Server got itself in trouble\\n\"\n        \"</body></html>\\n\"\n    )\n\n    logger.exception.assert_called_with(\n        \"Error handling request from %s\", cli.host, exc_info=exc\n    )\n\n\nasync def test_raw_server_html_exception_debug(\n    aiohttp_raw_server: AiohttpRawServer, aiohttp_client: AiohttpClient\n) -> None:\n    exc = RuntimeError(\"custom runtime error\")\n\n    async def handler(request: web.BaseRequest) -> NoReturn:\n        raise exc\n\n    loop = asyncio.get_event_loop()\n    loop.set_debug(True)\n    logger = mock.Mock()\n    server = await aiohttp_raw_server(handler, logger=logger)\n    cli = await aiohttp_client(server)\n    resp = await cli.get(\"/path/to\", headers={\"Accept\": \"text/html\"})\n    assert resp.status == 500\n    assert resp.headers[\"Content-Type\"].startswith(\"text/html\")\n\n    txt = await resp.text()\n    assert txt.startswith(\n        \"<html><head><title>500 Internal Server Error</title></head><body>\\n\"\n        \"<h1>500 Internal Server Error</h1>\\n\"\n        \"<h2>Traceback:</h2>\\n\"\n        \"<pre>Traceback (most recent call last):\\n\"\n    )\n\n    logger.exception.assert_called_with(\n        \"Error handling request from %s\", cli.host, exc_info=exc\n    )\n\n\nasync def test_handler_cancellation(unused_port_socket: socket.socket) -> None:\n    event = asyncio.Event()\n    sock = unused_port_socket\n    port = sock.getsockname()[1]\n\n    async def on_request(request: web.Request) -> web.Response:\n        try:\n            await asyncio.sleep(10)\n        except asyncio.CancelledError:\n            event.set()\n            raise\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", on_request)\n\n    runner = web.AppRunner(app, handler_cancellation=True)\n    await runner.setup()\n\n    site = web.SockSite(runner, sock=sock)\n\n    await site.start()\n    assert runner.server is not None\n    try:\n        assert runner.server.handler_cancellation, \"Flag was not propagated\"\n\n        async with client.ClientSession(\n            timeout=client.ClientTimeout(total=0.15)\n        ) as sess:\n            with pytest.raises(asyncio.TimeoutError):\n                await sess.get(f\"http://127.0.0.1:{port}/\")\n\n        with suppress(asyncio.TimeoutError):\n            await asyncio.wait_for(event.wait(), timeout=1)\n        assert event.is_set(), \"Request handler hasn't been cancelled\"\n    finally:\n        await asyncio.gather(runner.shutdown(), site.stop())\n\n\nasync def test_no_handler_cancellation(unused_port_socket: socket.socket) -> None:\n    timeout_event = asyncio.Event()\n    done_event = asyncio.Event()\n    sock = unused_port_socket\n    port = sock.getsockname()[1]\n    started = False\n\n    async def on_request(request: web.Request) -> web.Response:\n        nonlocal started\n        started = True\n        await asyncio.wait_for(timeout_event.wait(), timeout=5)\n        done_event.set()\n        return web.Response()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", on_request)\n\n    runner = web.AppRunner(app)\n    await runner.setup()\n\n    site = web.SockSite(runner, sock=sock)\n\n    await site.start()\n    try:\n        async with client.ClientSession(\n            timeout=client.ClientTimeout(total=0.2)\n        ) as sess:\n            with pytest.raises(asyncio.TimeoutError):\n                await sess.get(f\"http://127.0.0.1:{port}/\")\n        await asyncio.sleep(0.1)\n        timeout_event.set()\n\n        with suppress(asyncio.TimeoutError):\n            await asyncio.wait_for(done_event.wait(), timeout=1)\n        assert started\n        assert done_event.is_set()\n    finally:\n        await asyncio.gather(runner.shutdown(), site.stop())\n"
  },
  {
    "path": "tests/test_web_urldispatcher.py",
    "content": "import asyncio\nimport functools\nimport os\nimport pathlib\nimport socket\nimport sys\nfrom collections.abc import Generator\nfrom stat import S_IFIFO, S_IMODE\nfrom typing import Any, NoReturn\n\nimport pytest\nimport yarl\n\nfrom aiohttp import web\nfrom aiohttp.pytest_plugin import AiohttpClient\nfrom aiohttp.web_urldispatcher import Resource, SystemRoute\n\n\n@pytest.mark.parametrize(\n    \"show_index,status,prefix,request_path,data\",\n    [\n        pytest.param(False, 403, \"/\", \"/\", None, id=\"index_forbidden\"),\n        pytest.param(\n            True,\n            200,\n            \"/\",\n            \"/\",\n            b\"<html>\\n<head>\\n<title>Index of /.</title>\\n</head>\\n<body>\\n<h1>Index of\"\n            b' /.</h1>\\n<ul>\\n<li><a href=\"/my_dir\">my_dir/</a></li>\\n<li><a href=\"/my_file\">'\n            b\"my_file</a></li>\\n</ul>\\n</body>\\n</html>\",\n        ),\n        pytest.param(\n            True,\n            200,\n            \"/static\",\n            \"/static\",\n            b\"<html>\\n<head>\\n<title>Index of /.</title>\\n</head>\\n<body>\\n<h1>Index of\"\n            b' /.</h1>\\n<ul>\\n<li><a href=\"/static/my_dir\">my_dir/</a></li>\\n<li><a href=\"'\n            b'/static/my_file\">my_file</a></li>\\n</ul>\\n</body>\\n</html>',\n            id=\"index_static\",\n        ),\n        pytest.param(\n            True,\n            200,\n            \"/static\",\n            \"/static/my_dir\",\n            b\"<html>\\n<head>\\n<title>Index of /my_dir</title>\\n</head>\\n<body>\\n<h1>\"\n            b'Index of /my_dir</h1>\\n<ul>\\n<li><a href=\"/static/my_dir/my_file_in_dir\">'\n            b\"my_file_in_dir</a></li>\\n</ul>\\n</body>\\n</html>\",\n            id=\"index_subdir\",\n        ),\n        pytest.param(\n            True,\n            200,\n            \"/static\",\n            \"/static/\",\n            b\"<html>\\n<head>\\n<title>Index of /.</title>\\n</head>\\n<body>\\n<h1>Index of\"\n            b' /.</h1>\\n<ul>\\n<li><a href=\"/static/my_dir\">my_dir/</a></li>\\n<li><a href=\"'\n            b'/static/my_file\">my_file</a></li>\\n</ul>\\n</body>\\n</html>',\n            id=\"index_static_trailing_slash\",\n        ),\n        pytest.param(\n            True,\n            200,\n            \"/static\",\n            \"/static/my_dir/\",\n            b\"<html>\\n<head>\\n<title>Index of /my_dir</title>\\n</head>\\n<body>\\n<h1>\"\n            b'Index of /my_dir</h1>\\n<ul>\\n<li><a href=\"/static/my_dir/my_file_in_dir\">'\n            b\"my_file_in_dir</a></li>\\n</ul>\\n</body>\\n</html>\",\n            id=\"index_subdir_trailing_slash\",\n        ),\n    ],\n)\nasync def test_access_root_of_static_handler(\n    tmp_path: pathlib.Path,\n    aiohttp_client: AiohttpClient,\n    show_index: bool,\n    status: int,\n    prefix: str,\n    request_path: str,\n    data: bytes | None,\n) -> None:\n    # Tests the operation of static file server.\n    # Try to access the root of static file server, and make\n    # sure that correct HTTP statuses are returned depending if we directory\n    # index should be shown or not.\n    my_file = tmp_path / \"my_file\"\n    my_dir = tmp_path / \"my_dir\"\n    my_dir.mkdir()\n    my_file_in_dir = my_dir / \"my_file_in_dir\"\n\n    with my_file.open(\"w\") as fw:\n        fw.write(\"hello\")\n\n    with my_file_in_dir.open(\"w\") as fw:\n        fw.write(\"world\")\n\n    app = web.Application()\n\n    # Register global static route:\n    app.router.add_static(prefix, str(tmp_path), show_index=show_index)\n    client = await aiohttp_client(app)\n\n    # Request the root of the static directory.\n    async with await client.get(request_path) as r:\n        assert r.status == status\n\n        if data:\n            assert r.headers[\"Content-Type\"] == \"text/html; charset=utf-8\"\n            read_ = await r.read()\n            assert read_ == data\n\n\n@pytest.mark.internal  # Dependent on filesystem\n@pytest.mark.skipif(\n    not sys.platform.startswith(\"linux\"),\n    reason=\"Invalid filenames on some filesystems (like Windows)\",\n)\n@pytest.mark.parametrize(\n    \"show_index,status,prefix,request_path,data\",\n    [\n        pytest.param(False, 403, \"/\", \"/\", None, id=\"index_forbidden\"),\n        pytest.param(\n            True,\n            200,\n            \"/\",\n            \"/\",\n            b\"<html>\\n<head>\\n<title>Index of /.</title>\\n</head>\\n<body>\\n<h1>Index of\"\n            b' /.</h1>\\n<ul>\\n<li><a href=\"/%3Cimg%20src=0%20onerror=alert(1)%3E.dir\">&l'\n            b't;img src=0 onerror=alert(1)&gt;.dir/</a></li>\\n<li><a href=\"/%3Cimg%20sr'\n            b'c=0%20onerror=alert(1)%3E.txt\">&lt;img src=0 onerror=alert(1)&gt;.txt</a></l'\n            b\"i>\\n</ul>\\n</body>\\n</html>\",\n        ),\n        pytest.param(\n            True,\n            200,\n            \"/static\",\n            \"/static\",\n            b\"<html>\\n<head>\\n<title>Index of /.</title>\\n</head>\\n<body>\\n<h1>Index of\"\n            b' /.</h1>\\n<ul>\\n<li><a href=\"/static/%3Cimg%20src=0%20onerror=alert(1)%3E.'\n            b'dir\">&lt;img src=0 onerror=alert(1)&gt;.dir/</a></li>\\n<li><a href=\"/stat'\n            b'ic/%3Cimg%20src=0%20onerror=alert(1)%3E.txt\">&lt;img src=0 onerror=alert(1)&'\n            b\"gt;.txt</a></li>\\n</ul>\\n</body>\\n</html>\",\n            id=\"index_static\",\n        ),\n        pytest.param(\n            True,\n            200,\n            \"/static\",\n            \"/static/<img src=0 onerror=alert(1)>.dir\",\n            b\"<html>\\n<head>\\n<title>Index of /&lt;img src=0 onerror=alert(1)&gt;.dir</t\"\n            b\"itle>\\n</head>\\n<body>\\n<h1>Index of /&lt;img src=0 onerror=alert(1)&gt;.di\"\n            b'r</h1>\\n<ul>\\n<li><a href=\"/static/%3Cimg%20src=0%20onerror=alert(1)%3E.di'\n            b'r/my_file_in_dir\">my_file_in_dir</a></li>\\n</ul>\\n</body>\\n</html>',\n            id=\"index_subdir\",\n        ),\n    ],\n)\nasync def test_access_root_of_static_handler_xss(\n    tmp_path: pathlib.Path,\n    aiohttp_client: AiohttpClient,\n    show_index: bool,\n    status: int,\n    prefix: str,\n    request_path: str,\n    data: bytes | None,\n) -> None:\n    # Tests the operation of static file server.\n    # Try to access the root of static file server, and make\n    # sure that correct HTTP statuses are returned depending if we directory\n    # index should be shown or not.\n    # Ensure that html in file names is escaped.\n    # Ensure that links are url quoted.\n    my_file = tmp_path / \"<img src=0 onerror=alert(1)>.txt\"\n    my_dir = tmp_path / \"<img src=0 onerror=alert(1)>.dir\"\n    my_dir.mkdir()\n    my_file_in_dir = my_dir / \"my_file_in_dir\"\n\n    with my_file.open(\"w\") as fw:\n        fw.write(\"hello\")\n\n    with my_file_in_dir.open(\"w\") as fw:\n        fw.write(\"world\")\n\n    app = web.Application()\n\n    # Register global static route:\n    app.router.add_static(prefix, str(tmp_path), show_index=show_index)\n    client = await aiohttp_client(app)\n\n    # Request the root of the static directory.\n    async with await client.get(request_path) as r:\n        assert r.status == status\n\n        if data:\n            assert r.headers[\"Content-Type\"] == \"text/html; charset=utf-8\"\n            read_ = await r.read()\n            assert read_ == data\n\n\nasync def test_follow_symlink(\n    tmp_path: pathlib.Path, aiohttp_client: AiohttpClient\n) -> None:\n    # Tests the access to a symlink, in static folder\n    data = \"hello world\"\n\n    my_dir_path = tmp_path / \"my_dir\"\n    my_dir_path.mkdir()\n\n    my_file_path = my_dir_path / \"my_file_in_dir\"\n    with my_file_path.open(\"w\") as fw:\n        fw.write(data)\n\n    my_symlink_path = tmp_path / \"my_symlink\"\n    pathlib.Path(str(my_symlink_path)).symlink_to(str(my_dir_path), True)\n\n    app = web.Application()\n\n    # Register global static route:\n    app.router.add_static(\"/\", str(tmp_path), follow_symlinks=True)\n    client = await aiohttp_client(app)\n\n    # Request the root of the static directory.\n    r = await client.get(\"/my_symlink/my_file_in_dir\")\n    assert r.status == 200\n    assert (await r.text()) == data\n\n\nasync def test_follow_symlink_directory_traversal(\n    tmp_path: pathlib.Path, aiohttp_client: AiohttpClient\n) -> None:\n    # Tests that follow_symlinks does not allow directory transversal\n    data = \"private\"\n\n    private_file = tmp_path / \"private_file\"\n    private_file.write_text(data)\n\n    safe_path = tmp_path / \"safe_dir\"\n    safe_path.mkdir()\n\n    app = web.Application()\n\n    # Register global static route:\n    app.router.add_static(\"/\", str(safe_path), follow_symlinks=True)\n    client = await aiohttp_client(app)\n\n    await client.start_server()\n    # We need to use a raw socket to test this, as the client will normalize\n    # the path before sending it to the server.\n    reader, writer = await asyncio.open_connection(client.host, client.port)\n    writer.write(b\"GET /../private_file HTTP/1.1\\r\\n\\r\\n\")\n    response = await reader.readuntil(b\"\\r\\n\\r\\n\")\n    assert b\"404 Not Found\" in response\n    writer.close()\n    await writer.wait_closed()\n    await client.close()\n\n\nasync def test_follow_symlink_directory_traversal_after_normalization(\n    tmp_path: pathlib.Path, aiohttp_client: AiohttpClient\n) -> None:\n    # Tests that follow_symlinks does not allow directory transversal\n    # after normalization\n    #\n    # Directory structure\n    # |-- secret_dir\n    # |   |-- private_file (should never be accessible)\n    # |   |-- symlink_target_dir\n    # |       |-- symlink_target_file (should be accessible via the my_symlink symlink)\n    # |       |-- sandbox_dir\n    # |           |-- my_symlink -> symlink_target_dir\n    #\n    secret_path = tmp_path / \"secret_dir\"\n    secret_path.mkdir()\n\n    # This file is below the symlink target and should not be reachable\n    private_file = secret_path / \"private_file\"\n    private_file.write_text(\"private\")\n\n    symlink_target_path = secret_path / \"symlink_target_dir\"\n    symlink_target_path.mkdir()\n\n    sandbox_path = symlink_target_path / \"sandbox_dir\"\n    sandbox_path.mkdir()\n\n    # This file should be reachable via the symlink\n    symlink_target_file = symlink_target_path / \"symlink_target_file\"\n    symlink_target_file.write_text(\"readable\")\n\n    my_symlink_path = sandbox_path / \"my_symlink\"\n    pathlib.Path(str(my_symlink_path)).symlink_to(str(symlink_target_path), True)\n\n    app = web.Application()\n\n    # Register global static route:\n    app.router.add_static(\"/\", str(sandbox_path), follow_symlinks=True)\n    client = await aiohttp_client(app)\n\n    await client.start_server()\n    # We need to use a raw socket to test this, as the client will normalize\n    # the path before sending it to the server.\n    reader, writer = await asyncio.open_connection(client.host, client.port)\n    writer.write(b\"GET /my_symlink/../private_file HTTP/1.1\\r\\n\\r\\n\")\n    response = await reader.readuntil(b\"\\r\\n\\r\\n\")\n    assert b\"404 Not Found\" in response\n    writer.close()\n    await writer.wait_closed()\n\n    reader, writer = await asyncio.open_connection(client.host, client.port)\n    writer.write(b\"GET /my_symlink/symlink_target_file HTTP/1.1\\r\\n\\r\\n\")\n    response = await reader.readuntil(b\"\\r\\n\\r\\n\")\n    assert b\"200 OK\" in response\n    response = await reader.readuntil(b\"readable\")\n    assert response == b\"readable\"\n    writer.close()\n    await writer.wait_closed()\n    await client.close()\n\n\n@pytest.mark.parametrize(\n    \"dir_name,filename,data\",\n    [\n        (\"\", \"test file.txt\", \"test text\"),\n        (\"test dir name\", \"test dir file .txt\", \"test text file folder\"),\n    ],\n)\nasync def test_access_to_the_file_with_spaces(\n    tmp_path: pathlib.Path,\n    aiohttp_client: AiohttpClient,\n    dir_name: str,\n    filename: str,\n    data: str,\n) -> None:\n    # Checks operation of static files with spaces\n\n    my_dir_path = tmp_path / dir_name\n    if my_dir_path != tmp_path:\n        my_dir_path.mkdir()\n\n    my_file_path = my_dir_path / filename\n    with my_file_path.open(\"w\") as fw:\n        fw.write(data)\n\n    app = web.Application()\n\n    url = \"/\" + str(pathlib.Path(dir_name, filename))\n\n    app.router.add_static(\"/\", str(tmp_path))\n    client = await aiohttp_client(app)\n\n    r = await client.get(url)\n    assert r.status == 200\n    assert (await r.text()) == data\n\n\nasync def test_access_non_existing_resource(\n    tmp_path: pathlib.Path, aiohttp_client: AiohttpClient\n) -> None:\n    # Tests accessing non-existing resource\n    # Try to access a non-exiting resource and make sure that 404 HTTP status\n    # returned.\n    app = web.Application()\n\n    # Register global static route:\n    app.router.add_static(\"/\", str(tmp_path), show_index=True)\n    client = await aiohttp_client(app)\n\n    # Request the root of the static directory.\n    async with client.get(\"/non_existing_resource\") as r:\n        assert r.status == 404\n\n\n@pytest.mark.parametrize(\n    \"registered_path,request_url\",\n    [\n        (\"/a:b\", \"/a:b\"),\n        (\"/a@b\", \"/a@b\"),\n        (\"/a:b\", \"/a%3Ab\"),\n    ],\n)\nasync def test_url_escaping(\n    aiohttp_client: AiohttpClient, registered_path: str, request_url: str\n) -> None:\n    # Tests accessing a resource with\n    app = web.Application()\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app.router.add_get(registered_path, handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(request_url) as r:\n        assert r.status == 200\n\n\nasync def test_handler_metadata_persistence() -> None:\n    # Tests accessing metadata of a handler after registering it on the app\n    # router.\n    app = web.Application()\n\n    async def async_handler(request: web.Request) -> web.Response:\n        \"\"\"Doc\"\"\"\n        assert False\n\n    app.router.add_get(\"/async\", async_handler)\n\n    for resource in app.router.resources():\n        for route in resource:\n            assert route.handler.__doc__ == \"Doc\"\n\n\n@pytest.mark.skipif(\n    sys.platform.startswith(\"win32\"), reason=\"Cannot remove read access on Windows\"\n)\n@pytest.mark.parametrize(\"file_request\", [\"\", \"my_file.txt\"])\nasync def test_static_directory_without_read_permission(\n    tmp_path: pathlib.Path, aiohttp_client: AiohttpClient, file_request: str\n) -> None:\n    \"\"\"Test static directory without read permission receives forbidden response.\"\"\"\n    my_dir = tmp_path / \"my_dir\"\n    my_dir.mkdir()\n    my_dir.chmod(0o000)\n\n    app = web.Application()\n    app.router.add_static(\"/\", str(tmp_path), show_index=True)\n    client = await aiohttp_client(app)\n\n    async with client.get(f\"/{my_dir.name}/{file_request}\") as r:\n        assert r.status == 403\n\n\n@pytest.mark.parametrize(\"file_request\", [\"\", \"my_file.txt\"])\nasync def test_static_directory_with_mock_permission_error(\n    monkeypatch: pytest.MonkeyPatch,\n    tmp_path: pathlib.Path,\n    aiohttp_client: AiohttpClient,\n    file_request: str,\n) -> None:\n    \"\"\"Test static directory with mock permission errors receives forbidden response.\"\"\"\n    my_dir = tmp_path / \"my_dir\"\n    my_dir.mkdir()\n\n    real_iterdir = pathlib.Path.iterdir\n    real_is_dir = pathlib.Path.is_dir\n\n    def mock_iterdir(self: pathlib.Path) -> Generator[pathlib.Path, None, None]:\n        if my_dir.samefile(self):\n            raise PermissionError()\n        return real_iterdir(self)\n\n    def mock_is_dir(self: pathlib.Path, **kwargs: Any) -> bool:\n        if my_dir.samefile(self.parent):\n            raise PermissionError()\n        return real_is_dir(self, **kwargs)\n\n    monkeypatch.setattr(\"pathlib.Path.iterdir\", mock_iterdir)\n    monkeypatch.setattr(\"pathlib.Path.is_dir\", mock_is_dir)\n\n    app = web.Application()\n    app.router.add_static(\"/\", str(tmp_path), show_index=True)\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/\") as r:\n        assert r.status == 200\n    async with client.get(f\"/{my_dir.name}/{file_request}\") as r:\n        assert r.status == 403\n\n\n@pytest.mark.skipif(\n    sys.platform.startswith(\"win32\"), reason=\"Cannot remove read access on Windows\"\n)\nasync def test_static_file_without_read_permission(\n    tmp_path: pathlib.Path, aiohttp_client: AiohttpClient\n) -> None:\n    \"\"\"Test static file without read permission receives forbidden response.\"\"\"\n    my_file = tmp_path / \"my_file.txt\"\n    my_file.write_text(\"secret\")\n    my_file.chmod(0o000)\n\n    app = web.Application()\n    app.router.add_static(\"/\", str(tmp_path))\n    client = await aiohttp_client(app)\n\n    async with client.get(f\"/{my_file.name}\") as r:\n        assert r.status == 403\n\n\nasync def test_static_file_with_mock_permission_error(\n    monkeypatch: pytest.MonkeyPatch,\n    tmp_path: pathlib.Path,\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test static file with mock permission errors receives forbidden response.\"\"\"\n    my_file = tmp_path / \"my_file.txt\"\n    my_file.write_text(\"secret\")\n    my_readable = tmp_path / \"my_readable.txt\"\n    my_readable.write_text(\"info\")\n\n    real_open = pathlib.Path.open\n\n    def mock_open(self: pathlib.Path, *args: Any, **kwargs: Any) -> Any:\n        if my_file.samefile(self):\n            raise PermissionError()\n        return real_open(self, *args, **kwargs)\n\n    monkeypatch.setattr(\"pathlib.Path.open\", mock_open)\n\n    app = web.Application()\n    app.router.add_static(\"/\", str(tmp_path))\n    client = await aiohttp_client(app)\n\n    # Test the mock only applies to my_file, then test the permission error.\n    async with client.get(f\"/{my_readable.name}\") as r:\n        assert r.status == 200\n    async with client.get(f\"/{my_file.name}\") as r:\n        assert r.status == 403\n\n\nasync def test_access_symlink_loop(\n    tmp_path: pathlib.Path, aiohttp_client: AiohttpClient\n) -> None:\n    # Tests the access to a looped symlink, which could not be resolved.\n    my_dir_path = tmp_path / \"my_symlink\"\n    pathlib.Path(str(my_dir_path)).symlink_to(str(my_dir_path), True)\n\n    app = web.Application()\n\n    # Register global static route:\n    app.router.add_static(\"/\", str(tmp_path), show_index=True)\n    client = await aiohttp_client(app)\n\n    # Request the root of the static directory.\n    async with client.get(\"/\" + my_dir_path.name) as r:\n        assert r.status == 404\n\n\nasync def test_access_compressed_file_as_symlink(\n    tmp_path: pathlib.Path, aiohttp_client: AiohttpClient\n) -> None:\n    \"\"\"Test that compressed file variants as symlinks are ignored.\"\"\"\n    private_file = tmp_path / \"private.txt\"\n    private_file.write_text(\"private info\")\n    www_dir = tmp_path / \"www\"\n    www_dir.mkdir()\n    gz_link = www_dir / \"file.txt.gz\"\n    gz_link.symlink_to(f\"../{private_file.name}\")\n\n    app = web.Application()\n    app.router.add_static(\"/\", www_dir)\n    client = await aiohttp_client(app)\n\n    # Symlink should be ignored; response reflects missing uncompressed file.\n    async with client.get(f\"/{gz_link.stem}\", auto_decompress=False) as resp:\n        assert resp.status == 404\n\n    # Again symlin is ignored, and then uncompressed is served.\n    txt_file = gz_link.with_suffix(\"\")\n    txt_file.write_text(\"public data\")\n    resp = await client.get(f\"/{txt_file.name}\")\n    assert resp.status == 200\n    assert resp.headers.get(\"Content-Encoding\") is None\n    assert resp.content_type == \"text/plain\"\n    assert await resp.text() == \"public data\"\n    resp.release()\n    await client.close()\n\n\nasync def test_access_special_resource(\n    unix_sockname: str, aiohttp_client: AiohttpClient\n) -> None:\n    \"\"\"Test access to non-regular files is forbidden using a UNIX domain socket.\"\"\"\n    if not getattr(socket, \"AF_UNIX\", None):  # pragma: no cover\n        pytest.skip(\"UNIX domain sockets not supported\")\n\n    my_special = pathlib.Path(unix_sockname)\n    tmp_path = my_special.parent\n\n    my_socket = socket.socket(socket.AF_UNIX)\n    my_socket.bind(str(my_special))\n    assert my_special.is_socket()\n\n    app = web.Application()\n    app.router.add_static(\"/\", str(tmp_path))\n\n    client = await aiohttp_client(app)\n    async with client.get(f\"/{my_special.name}\") as r:\n        assert r.status == 403\n    my_socket.close()\n\n\nasync def test_access_mock_special_resource(\n    monkeypatch: pytest.MonkeyPatch,\n    tmp_path: pathlib.Path,\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test access to non-regular files is forbidden using a mock FIFO.\"\"\"\n    my_special = tmp_path / \"my_special\"\n    my_special.touch()\n\n    real_result = my_special.stat()\n    real_stat = os.stat\n\n    def mock_stat(path: Any, **kwargs: Any) -> os.stat_result:\n        s = real_stat(path, **kwargs)\n        if os.path.samestat(s, real_result):\n            mock_mode = S_IFIFO | S_IMODE(s.st_mode)\n            s = os.stat_result([mock_mode] + list(s)[1:])\n        return s\n\n    monkeypatch.setattr(\"pathlib.Path.stat\", mock_stat)\n    monkeypatch.setattr(\"os.stat\", mock_stat)\n\n    app = web.Application()\n    app.router.add_static(\"/\", str(tmp_path))\n    client = await aiohttp_client(app)\n\n    async with client.get(f\"/{my_special.name}\") as r:\n        assert r.status == 403\n\n\nasync def test_partially_applied_handler(aiohttp_client: AiohttpClient) -> None:\n    app = web.Application()\n\n    async def handler(data: bytes, request: web.Request) -> web.Response:\n        return web.Response(body=data)\n\n    app.router.add_route(\"GET\", \"/\", functools.partial(handler, b\"hello\"))\n\n    client = await aiohttp_client(app)\n\n    r = await client.get(\"/\")\n    data = await r.read()\n    assert data == b\"hello\"\n\n\nasync def test_static_head(\n    tmp_path: pathlib.Path, aiohttp_client: AiohttpClient\n) -> None:\n    # Test HEAD on static route\n    my_file_path = tmp_path / \"test.txt\"\n    with my_file_path.open(\"wb\") as fw:\n        fw.write(b\"should_not_see_this\\n\")\n\n    app = web.Application()\n    app.router.add_static(\"/\", str(tmp_path))\n    client = await aiohttp_client(app)\n\n    async with client.head(\"/test.txt\") as r:\n        assert r.status == 200\n\n    # Check that there is no content sent (see #4809). This can't easily be\n    # done with aiohttp_client because the buffering can consume the content.\n    reader, writer = await asyncio.open_connection(client.host, client.port)\n    writer.write(b\"HEAD /test.txt HTTP/1.1\\r\\n\")\n    writer.write(b\"Host: localhost\\r\\n\")\n    writer.write(b\"Connection: close\\r\\n\")\n    writer.write(b\"\\r\\n\")\n    while await reader.readline() != b\"\\r\\n\":\n        pass\n    content = await reader.read()\n    writer.close()\n    assert content == b\"\"\n\n\ndef test_system_route() -> None:\n    route = SystemRoute(web.HTTPCreated(reason=\"test\"))\n    with pytest.raises(RuntimeError):\n        route.url_for()\n    assert route.name is None\n    assert route.resource is None\n    assert \"<SystemRoute 201: test>\" == repr(route)\n    assert 201 == route.status\n    assert \"test\" == route.reason\n\n\nasync def test_allow_head(aiohttp_client: AiohttpClient) -> None:\n    # Test allow_head on routes.\n    app = web.Application()\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app.router.add_get(\"/a\", handler, name=\"a\")\n    app.router.add_get(\"/b\", handler, allow_head=False, name=\"b\")\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/a\") as r:\n        assert r.status == 200\n\n    async with client.head(\"/a\") as r:\n        assert r.status == 200\n\n    async with client.get(\"/b\") as r:\n        assert r.status == 200\n\n    async with client.head(\"/b\") as r:\n        assert r.status == 405\n\n\n@pytest.mark.parametrize(\n    \"path\",\n    (\n        \"/a\",\n        \"/{a}\",\n        \"/{a:.*}\",\n    ),\n)\ndef test_reuse_last_added_resource(path: str) -> None:\n    # Test that adding a route with the same name and path of the last added\n    # resource doesn't create a new resource.\n    app = web.Application()\n\n    async def handler(request: web.Request) -> web.Response:\n        assert False\n\n    app.router.add_get(path, handler, name=\"a\")\n    app.router.add_post(path, handler, name=\"a\")\n\n    assert len(app.router.resources()) == 1\n\n\ndef test_resource_raw_match() -> None:\n    app = web.Application()\n\n    async def handler(request: web.Request) -> web.Response:\n        assert False\n\n    route = app.router.add_get(\"/a\", handler, name=\"a\")\n    assert route.resource is not None\n    assert route.resource.raw_match(\"/a\")\n\n    route = app.router.add_get(\"/{b}\", handler, name=\"b\")\n    assert route.resource is not None\n    assert route.resource.raw_match(\"/{b}\")\n\n    resource = app.router.add_static(\"/static\", \".\")\n    assert not resource.raw_match(\"/static\")\n\n\nasync def test_add_view(aiohttp_client: AiohttpClient) -> None:\n    app = web.Application()\n\n    class MyView(web.View):\n        async def get(self) -> web.Response:\n            return web.Response()\n\n        async def post(self) -> web.Response:\n            return web.Response()\n\n    app.router.add_view(\"/a\", MyView)\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/a\") as r:\n        assert r.status == 200\n\n    async with client.post(\"/a\") as r:\n        assert r.status == 200\n\n    async with client.put(\"/a\") as r:\n        assert r.status == 405\n\n\nasync def test_decorate_view(aiohttp_client: AiohttpClient) -> None:\n    routes = web.RouteTableDef()\n\n    @routes.view(\"/a\")\n    class MyView(web.View):\n        async def get(self) -> web.Response:\n            return web.Response()\n\n        async def post(self) -> web.Response:\n            return web.Response()\n\n    app = web.Application()\n    app.router.add_routes(routes)\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/a\") as r:\n        assert r.status == 200\n\n    async with client.post(\"/a\") as r:\n        assert r.status == 200\n\n    async with client.put(\"/a\") as r:\n        assert r.status == 405\n\n\nasync def test_web_view(aiohttp_client: AiohttpClient) -> None:\n    app = web.Application()\n\n    class MyView(web.View):\n        async def get(self) -> web.Response:\n            return web.Response()\n\n        async def post(self) -> web.Response:\n            return web.Response()\n\n    app.router.add_routes([web.view(\"/a\", MyView)])\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"/a\") as r:\n        assert r.status == 200\n\n    async with client.post(\"/a\") as r:\n        assert r.status == 200\n\n    async with client.put(\"/a\") as r:\n        assert r.status == 405\n\n\nasync def test_static_absolute_url(\n    aiohttp_client: AiohttpClient, tmp_path: pathlib.Path\n) -> None:\n    # requested url is an absolute name like\n    # /static/\\\\machine_name\\c$ or /static/D:\\path\n    # where the static dir is totally different\n    app = web.Application()\n    file_path = tmp_path / \"file.txt\"\n    file_path.write_text(\"sample text\", \"ascii\")\n    here = pathlib.Path(__file__).parent\n    app.router.add_static(\"/static\", here)\n    client = await aiohttp_client(app)\n    async with client.get(\"/static/\" + str(file_path.resolve())) as resp:\n        assert resp.status == 404\n\n\nasync def test_for_issue_5250(\n    aiohttp_client: AiohttpClient, tmp_path: pathlib.Path\n) -> None:\n    app = web.Application()\n    app.router.add_static(\"/foo\", tmp_path)\n\n    async def get_foobar(request: web.Request) -> web.Response:\n        return web.Response(body=\"success!\")\n\n    app.router.add_get(\"/foobar\", get_foobar)\n\n    client = await aiohttp_client(app)\n    async with await client.get(\"/foobar\") as resp:\n        assert resp.status == 200\n        assert (await resp.text()) == \"success!\"\n\n\n@pytest.mark.parametrize(\n    (\"route_definition\", \"urlencoded_path\", \"expected_http_resp_status\"),\n    (\n        (\"/467,802,24834/hello\", \"/467%2C802%2C24834/hello\", 200),\n        (\"/{user_ids:([0-9]+)(,([0-9]+))*}/hello\", \"/467%2C802%2C24834/hello\", 200),\n        (\"/467,802,24834/hello\", \"/467,802,24834/hello\", 200),\n        (\"/{user_ids:([0-9]+)(,([0-9]+))*}/hello\", \"/467,802,24834/hello\", 200),\n        (\"/1%2C3/hello\", \"/1%2C3/hello\", 404),\n    ),\n)\nasync def test_decoded_url_match(\n    aiohttp_client: AiohttpClient,\n    route_definition: str,\n    urlencoded_path: str,\n    expected_http_resp_status: int,\n) -> None:\n    app = web.Application()\n\n    async def handler(request: web.Request) -> web.Response:\n        return web.Response()\n\n    app.router.add_get(route_definition, handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(yarl.URL(urlencoded_path, encoded=True)) as resp:\n        assert resp.status == expected_http_resp_status\n\n\nasync def test_decoded_raw_match_regex(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Verify that raw_match only matches decoded url.\"\"\"\n    app = web.Application()\n\n    async def handler(request: web.Request) -> NoReturn:\n        assert False\n\n    app.router.add_get(\"/467%2C802%2C24834%2C24952%2C25362%2C40574/hello\", handler)\n    client = await aiohttp_client(app)\n\n    async with client.get(\n        yarl.URL(\"/467%2C802%2C24834%2C24952%2C25362%2C40574/hello\", encoded=True)\n    ) as resp:\n        assert resp.status == 404  # should only match decoded url\n\n\nasync def test_order_is_preserved(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test route order is preserved.\n\n    Note that fixed/static paths are always preferred over a regex path.\n    \"\"\"\n    app = web.Application()\n\n    async def handler(request: web.Request) -> web.Response:\n        assert isinstance(request.match_info._route.resource, Resource)\n        return web.Response(text=request.match_info._route.resource.canonical)\n\n    app.router.add_get(\"/first/x/{b}/\", handler)\n    app.router.add_get(r\"/first/{x:.*/b}\", handler)\n\n    app.router.add_get(r\"/second/{user}/info\", handler)\n    app.router.add_get(\"/second/bob/info\", handler)\n\n    app.router.add_get(\"/third/bob/info\", handler)\n    app.router.add_get(r\"/third/{user}/info\", handler)\n\n    app.router.add_get(r\"/forth/{name:\\d+}\", handler)\n    app.router.add_get(\"/forth/42\", handler)\n\n    app.router.add_get(\"/fifth/42\", handler)\n    app.router.add_get(r\"/fifth/{name:\\d+}\", handler)\n\n    client = await aiohttp_client(app)\n\n    r = await client.get(\"/first/x/b/\")\n    assert r.status == 200\n    assert await r.text() == \"/first/x/{b}/\"\n\n    r = await client.get(\"/second/frank/info\")\n    assert r.status == 200\n    assert await r.text() == \"/second/{user}/info\"\n\n    # Fixed/static paths are always preferred over regex paths\n    r = await client.get(\"/second/bob/info\")\n    assert r.status == 200\n    assert await r.text() == \"/second/bob/info\"\n\n    r = await client.get(\"/third/bob/info\")\n    assert r.status == 200\n    assert await r.text() == \"/third/bob/info\"\n\n    r = await client.get(\"/third/frank/info\")\n    assert r.status == 200\n    assert await r.text() == \"/third/{user}/info\"\n\n    r = await client.get(\"/forth/21\")\n    assert r.status == 200\n    assert await r.text() == \"/forth/{name}\"\n\n    # Fixed/static paths are always preferred over regex paths\n    r = await client.get(\"/forth/42\")\n    assert r.status == 200\n    assert await r.text() == \"/forth/42\"\n\n    r = await client.get(\"/fifth/21\")\n    assert r.status == 200\n    assert await r.text() == \"/fifth/{name}\"\n\n    r = await client.get(\"/fifth/42\")\n    assert r.status == 200\n    assert await r.text() == \"/fifth/42\"\n\n\nasync def test_url_with_many_slashes(aiohttp_client: AiohttpClient) -> None:\n    app = web.Application()\n\n    class MyView(web.View):\n        async def get(self) -> web.Response:\n            return web.Response()\n\n    app.router.add_routes([web.view(\"/a\", MyView)])\n\n    client = await aiohttp_client(app)\n\n    async with client.get(\"///a\") as r:\n        assert r.status == 200\n\n\nasync def test_subapp_domain_routing_same_path(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Regression test for #11665.\"\"\"\n    app = web.Application()\n    sub_app = web.Application()\n\n    async def mainapp_handler(request: web.Request) -> web.Response:\n        assert False\n\n    async def subapp_handler(request: web.Request) -> web.Response:\n        return web.Response(text=\"SUBAPP\")\n\n    app.router.add_get(\"/\", mainapp_handler)\n    sub_app.router.add_get(\"/\", subapp_handler)\n    app.add_domain(\"different.example.com\", sub_app)\n\n    client = await aiohttp_client(app)\n    async with client.get(\"/\", headers={\"Host\": \"different.example.com\"}) as r:\n        assert r.status == 200\n        result = await r.text()\n        assert result == \"SUBAPP\"\n\n\nasync def test_route_with_regex(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test a route with a regex preceded by a fixed string.\"\"\"\n    app = web.Application()\n\n    async def handler(request: web.Request) -> web.Response:\n        assert isinstance(request.match_info._route.resource, Resource)\n        return web.Response(text=request.match_info._route.resource.canonical)\n\n    app.router.add_get(\"/core/locations{tail:.*}\", handler)\n    client = await aiohttp_client(app)\n\n    r = await client.get(\"/core/locations/tail/here\")\n    assert r.status == 200\n    assert await r.text() == \"/core/locations{tail}\"\n\n    r = await client.get(\"/core/locations_tail_here\")\n    assert r.status == 200\n    assert await r.text() == \"/core/locations{tail}\"\n\n    r = await client.get(\"/core/locations_tail;id=abcdef\")\n    assert r.status == 200\n    assert await r.text() == \"/core/locations{tail}\"\n"
  },
  {
    "path": "tests/test_web_websocket.py",
    "content": "import asyncio\nimport json\nimport time\nfrom typing import Protocol\nfrom unittest import mock\n\nimport aiosignal\nimport pytest\nfrom multidict import CIMultiDict\nfrom pytest_mock import MockerFixture\n\nfrom aiohttp import WSMessageTypeError, WSMsgType, web\nfrom aiohttp.http import WS_CLOSED_MESSAGE, WS_CLOSING_MESSAGE\nfrom aiohttp.http_websocket import WSMessageClose\nfrom aiohttp.streams import EofStream\nfrom aiohttp.test_utils import make_mocked_request\nfrom aiohttp.web_ws import WebSocketReady\n\n\nclass _RequestMaker(Protocol):\n    def __call__(\n        self,\n        method: str,\n        path: str,\n        headers: CIMultiDict[str] | None = None,\n        protocols: bool = False,\n    ) -> web.Request: ...\n\n\n@pytest.fixture\ndef app(loop: asyncio.AbstractEventLoop) -> web.Application:\n    ret: web.Application = mock.create_autospec(web.Application, spec_set=True)\n    ret.on_response_prepare = aiosignal.Signal(ret)  # type: ignore[misc]\n    ret.on_response_prepare.freeze()\n    return ret\n\n\n@pytest.fixture\ndef protocol() -> web.RequestHandler[web.Request]:\n    ret = mock.Mock()\n    ret.set_parser.return_value = ret\n    ret._timeout_ceil_threshold = 5\n    return ret\n\n\n@pytest.fixture\ndef make_request(\n    app: web.Application, protocol: web.RequestHandler[web.Request]\n) -> _RequestMaker:\n    def maker(\n        method: str,\n        path: str,\n        headers: CIMultiDict[str] | None = None,\n        protocols: bool = False,\n    ) -> web.Request:\n        if headers is None:\n            headers = CIMultiDict(\n                {\n                    \"HOST\": \"server.example.com\",\n                    \"UPGRADE\": \"websocket\",\n                    \"CONNECTION\": \"Upgrade\",\n                    \"SEC-WEBSOCKET-KEY\": \"dGhlIHNhbXBsZSBub25jZQ==\",\n                    \"ORIGIN\": \"http://example.com\",\n                    \"SEC-WEBSOCKET-VERSION\": \"13\",\n                }\n            )\n        if protocols:\n            headers[\"SEC-WEBSOCKET-PROTOCOL\"] = \"chat, superchat\"\n\n        return make_mocked_request(method, path, headers, app=app, protocol=protocol)\n\n    return maker\n\n\nasync def test_nonstarted_ping() -> None:\n    ws = web.WebSocketResponse()\n    with pytest.raises(RuntimeError):\n        await ws.ping()\n\n\nasync def test_nonstarted_pong() -> None:\n    ws = web.WebSocketResponse()\n    with pytest.raises(RuntimeError):\n        await ws.pong()\n\n\nasync def test_nonstarted_send_frame() -> None:\n    ws = web.WebSocketResponse()\n    with pytest.raises(RuntimeError):\n        await ws.send_frame(b\"string\", WSMsgType.TEXT)\n\n\nasync def test_nonstarted_send_str() -> None:\n    ws = web.WebSocketResponse()\n    with pytest.raises(RuntimeError):\n        await ws.send_str(\"string\")\n\n\nasync def test_nonstarted_send_bytes() -> None:\n    ws = web.WebSocketResponse()\n    with pytest.raises(RuntimeError):\n        await ws.send_bytes(b\"bytes\")\n\n\nasync def test_nonstarted_send_json() -> None:\n    ws = web.WebSocketResponse()\n    with pytest.raises(RuntimeError):\n        await ws.send_json({\"type\": \"json\"})\n\n\nasync def test_nonstarted_close() -> None:\n    ws = web.WebSocketResponse()\n    with pytest.raises(RuntimeError):\n        await ws.close()\n\n\nasync def test_nonstarted_receive_str() -> None:\n    ws = web.WebSocketResponse()\n    with pytest.raises(RuntimeError):\n        await ws.receive_str()\n\n\nasync def test_cancel_heartbeat_cancels_pending_heartbeat_reset_handle(\n    loop: asyncio.AbstractEventLoop,\n) -> None:\n    ws = web.WebSocketResponse(heartbeat=0.05)\n    ws._loop = loop\n    ws._on_data_received()\n    handle = ws._heartbeat_reset_handle\n    assert handle is not None\n\n    ws._cancel_heartbeat()\n\n    assert ws._heartbeat_reset_handle is None\n    assert ws._need_heartbeat_reset is False\n    assert handle.cancelled()\n\n\nasync def test_flush_heartbeat_reset_returns_early_when_not_needed() -> None:\n    ws = web.WebSocketResponse(heartbeat=0.05)\n    ws._need_heartbeat_reset = False\n\n    with mock.patch.object(ws, \"_reset_heartbeat\") as reset:\n        ws._flush_heartbeat_reset()\n        reset.assert_not_called()\n\n\nasync def test_send_heartbeat_returns_early_when_reset_is_pending() -> None:\n    ws = web.WebSocketResponse(heartbeat=0.05)\n    ws._need_heartbeat_reset = True\n\n    ws._send_heartbeat()\n\n    assert ws._pong_response_cb is None\n    assert ws._ping_task is None\n\n\nasync def test_nonstarted_receive_bytes() -> None:\n    ws = web.WebSocketResponse()\n    with pytest.raises(RuntimeError):\n        await ws.receive_bytes()\n\n\nasync def test_nonstarted_receive_json() -> None:\n    ws = web.WebSocketResponse()\n    with pytest.raises(RuntimeError):\n        await ws.receive_json()\n\n\nasync def test_send_str_nonstring(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    with pytest.raises(TypeError):\n        await ws.send_str(b\"bytes\")  # type: ignore[arg-type]\n\n\nasync def test_send_bytes_nonbytes(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    with pytest.raises(TypeError):\n        await ws.send_bytes(\"string\")  # type: ignore[arg-type]\n\n\nasync def test_send_json_nonjson(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    with pytest.raises(TypeError):\n        await ws.send_json(set())\n\n\nasync def test_nonstarted_send_json_bytes() -> None:\n    ws = web.WebSocketResponse()\n    with pytest.raises(RuntimeError):\n        await ws.send_json_bytes(\n            {\"type\": \"json\"}, dumps=lambda x: json.dumps(x).encode(\"utf-8\")\n        )\n\n\nasync def test_send_json_bytes_nonjson(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    with pytest.raises(TypeError):\n        await ws.send_json_bytes(set(), dumps=lambda x: json.dumps(x).encode(\"utf-8\"))\n\n    assert ws._reader is not None\n    ws._reader.feed_data(WS_CLOSED_MESSAGE)\n    await ws.close()\n\n\nasync def test_write_non_prepared() -> None:\n    ws = web.WebSocketResponse()\n    with pytest.raises(RuntimeError):\n        await ws.write(b\"data\")\n\n\nasync def test_heartbeat_timeout(make_request: _RequestMaker) -> None:\n    \"\"\"Verify the transport is closed when the heartbeat timeout is reached.\"\"\"\n    loop = asyncio.get_running_loop()\n    future = loop.create_future()\n    req = make_request(\"GET\", \"/\")\n    assert req.transport is not None\n    req.transport.close.side_effect = lambda: future.set_result(None)  # type: ignore[attr-defined]\n    lowest_time = time.get_clock_info(\"monotonic\").resolution\n    req._protocol._timeout_ceil_threshold = lowest_time\n    ws = web.WebSocketResponse(heartbeat=lowest_time, timeout=lowest_time)\n    await ws.prepare(req)\n    await future\n    assert ws.closed\n\n\nasync def test_heartbeat_reset_coalesces_on_data(\n    make_request: _RequestMaker,\n) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse(heartbeat=0.05)\n    await ws.prepare(req)\n\n    with mock.patch.object(ws, \"_reset_heartbeat\") as reset:\n        ws._on_data_received()\n        ws._on_data_received()\n\n        await asyncio.sleep(0)\n\n        assert reset.call_count == 1\n\n\nasync def test_receive_does_not_reset_heartbeat() -> None:\n    ws = web.WebSocketResponse(heartbeat=0.05)\n    msg = mock.Mock(type=WSMsgType.TEXT)\n    reader = mock.Mock()\n    reader.read = mock.AsyncMock(return_value=msg)\n    ws._reader = reader\n\n    with mock.patch.object(ws, \"_reset_heartbeat\") as reset:\n        received = await ws.receive()\n\n    assert received is msg\n    reset.assert_not_called()\n\n\ndef test_websocket_ready() -> None:\n    websocket_ready = WebSocketReady(True, \"chat\")\n    assert websocket_ready.ok is True\n    assert websocket_ready.protocol == \"chat\"\n\n\ndef test_websocket_not_ready() -> None:\n    websocket_ready = WebSocketReady(False, None)\n    assert websocket_ready.ok is False\n    assert websocket_ready.protocol is None\n\n\ndef test_websocket_ready_unknown_protocol() -> None:\n    websocket_ready = WebSocketReady(True, None)\n    assert websocket_ready.ok is True\n    assert websocket_ready.protocol is None\n\n\ndef test_bool_websocket_ready() -> None:\n    websocket_ready = WebSocketReady(True, None)\n    assert bool(websocket_ready) is True\n\n\ndef test_bool_websocket_not_ready() -> None:\n    websocket_ready = WebSocketReady(False, None)\n    assert bool(websocket_ready) is False\n\n\ndef test_can_prepare_ok(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\", protocols=True)\n    ws = web.WebSocketResponse(protocols=(\"chat\",))\n    assert WebSocketReady(True, \"chat\") == ws.can_prepare(req)\n\n\ndef test_can_prepare_unknown_protocol(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    assert WebSocketReady(True, None) == ws.can_prepare(req)\n\n\ndef test_can_prepare_without_upgrade(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\", headers=CIMultiDict({}))\n    ws = web.WebSocketResponse()\n    assert WebSocketReady(False, None) == ws.can_prepare(req)\n\n\nasync def test_can_prepare_started(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    with pytest.raises(RuntimeError) as ctx:\n        ws.can_prepare(req)\n\n    assert \"Already started\" in str(ctx.value)\n\n\ndef test_closed_after_ctor() -> None:\n    ws = web.WebSocketResponse()\n    assert not ws.closed\n    assert ws.close_code is None\n\n\nasync def test_raise_writer_limit(make_request: _RequestMaker) -> None:\n    \"\"\"Test the writer limit can be adjusted.\"\"\"\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse(writer_limit=1234567)\n    await ws.prepare(req)\n    assert ws._reader is not None\n    assert ws._writer is not None\n    assert ws._writer._limit == 1234567\n    ws._reader.feed_data(WS_CLOSED_MESSAGE)\n    await ws.close()\n\n\nasync def test_send_str_closed(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    assert ws._reader is not None\n    ws._reader.feed_data(WS_CLOSED_MESSAGE)\n    await ws.close()\n    assert req.transport is not None\n    assert len(req.transport.close.mock_calls) == 1  # type: ignore[attr-defined]\n\n    with pytest.raises(ConnectionError):\n        await ws.send_str(\"string\")\n\n\nasync def test_recv_str_closed(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    assert ws._reader is not None\n    ws._reader.feed_data(WS_CLOSED_MESSAGE)\n    await ws.close()\n\n    with pytest.raises(\n        WSMessageTypeError,\n        match=f\"Received message {WSMsgType.CLOSED}:.+ is not WSMsgType.TEXT\",\n    ):\n        await ws.receive_str()\n\n\nasync def test_send_bytes_closed(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    assert ws._reader is not None\n    ws._reader.feed_data(WS_CLOSED_MESSAGE)\n    await ws.close()\n\n    with pytest.raises(ConnectionError):\n        await ws.send_bytes(b\"bytes\")\n\n\nasync def test_recv_bytes_closed(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    assert ws._reader is not None\n    ws._reader.feed_data(WS_CLOSED_MESSAGE)\n    await ws.close()\n\n    with pytest.raises(\n        WSMessageTypeError,\n        match=f\"Received message {WSMsgType.CLOSED}:.+ is not WSMsgType.BINARY\",\n    ):\n        await ws.receive_bytes()\n\n\nasync def test_send_json_closed(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    assert ws._reader is not None\n    ws._reader.feed_data(WS_CLOSED_MESSAGE)\n    await ws.close()\n\n    with pytest.raises(ConnectionError):\n        await ws.send_json({\"type\": \"json\"})\n\n\nasync def test_send_json_bytes_closed(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    assert ws._reader is not None\n    ws._reader.feed_data(WS_CLOSED_MESSAGE)\n    await ws.close()\n\n    with pytest.raises(ConnectionError):\n        await ws.send_json_bytes(\n            {\"type\": \"json\"}, dumps=lambda x: json.dumps(x).encode(\"utf-8\")\n        )\n\n\nasync def test_send_frame_closed(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    assert ws._reader is not None\n    ws._reader.feed_data(WS_CLOSED_MESSAGE)\n    await ws.close()\n\n    with pytest.raises(ConnectionError):\n        await ws.send_frame(b'{\"type\": \"json\"}', WSMsgType.TEXT)\n\n\nasync def test_ping_closed(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    assert ws._reader is not None\n    ws._reader.feed_data(WS_CLOSED_MESSAGE)\n    await ws.close()\n\n    with pytest.raises(ConnectionError):\n        await ws.ping()\n\n\nasync def test_pong_closed(make_request: _RequestMaker, mocker: MockerFixture) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    assert ws._reader is not None\n    ws._reader.feed_data(WS_CLOSED_MESSAGE)\n    await ws.close()\n\n    with pytest.raises(ConnectionError):\n        await ws.pong()\n\n\nasync def test_close_idempotent(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    assert ws._reader is not None\n    ws._reader.feed_data(WS_CLOSED_MESSAGE)\n    close_code = await ws.close(code=1, message=b\"message1\")\n    assert close_code == 1\n    assert ws.closed\n    assert req.transport is not None\n    assert len(req.transport.close.mock_calls) == 1  # type: ignore[attr-defined]\n\n    close_code = await ws.close(code=2, message=b\"message2\")\n    assert close_code == 0\n\n\nasync def test_prepare_post_method_ok(make_request: _RequestMaker) -> None:\n    req = make_request(\"POST\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    assert ws.prepared\n\n\nasync def test_prepare_without_upgrade(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\", headers=CIMultiDict({}))\n    ws = web.WebSocketResponse()\n    with pytest.raises(web.HTTPBadRequest):\n        await ws.prepare(req)\n\n\nasync def test_wait_closed_before_start() -> None:\n    ws = web.WebSocketResponse()\n    with pytest.raises(RuntimeError):\n        await ws.close()\n\n\nasync def test_write_eof_not_started() -> None:\n    ws = web.WebSocketResponse()\n    with pytest.raises(RuntimeError):\n        await ws.write_eof()\n\n\nasync def test_write_eof_idempotent(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    assert req.transport is not None\n    assert len(req.transport.close.mock_calls) == 0  # type: ignore[attr-defined]\n\n    assert ws._reader is not None\n    ws._reader.feed_data(WS_CLOSED_MESSAGE)\n    await ws.close()\n\n    await ws.write_eof()\n    await ws.write_eof()\n    await ws.write_eof()\n    assert len(req.transport.close.mock_calls) == 1  # type: ignore[attr-defined]\n\n\nasync def test_receive_eofstream_in_reader(\n    make_request: _RequestMaker, loop: asyncio.AbstractEventLoop\n) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n\n    ws._reader = mock.Mock()\n    exc = EofStream()\n    ws._reader.read = mock.AsyncMock(side_effect=exc)\n    assert ws._payload_writer is not None\n    f = loop.create_future()\n    f.set_result(True)\n    ws._payload_writer.drain.return_value = f  # type: ignore[attr-defined]\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.CLOSED\n    assert ws.closed\n\n\nasync def test_receive_exception_in_reader(\n    make_request: _RequestMaker, loop: asyncio.AbstractEventLoop\n) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n\n    ws._reader = mock.Mock()\n    exc = Exception()\n    ws._reader.read = mock.AsyncMock(side_effect=exc)\n\n    f = loop.create_future()\n    assert ws._payload_writer is not None\n    ws._payload_writer.drain.return_value = f  # type: ignore[attr-defined]\n    f.set_result(True)\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.ERROR\n    assert ws.closed\n    assert req.transport is not None\n    assert len(req.transport.close.mock_calls) == 1  # type: ignore[attr-defined]\n\n\nasync def test_receive_close_but_left_open(\n    make_request: _RequestMaker, loop: asyncio.AbstractEventLoop\n) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    close_message = WSMessageClose(data=1000, size=0, extra=\"close\")\n\n    ws._reader = mock.Mock()\n    ws._reader.read = mock.AsyncMock(return_value=close_message)\n\n    f = loop.create_future()\n    assert ws._payload_writer is not None\n    ws._payload_writer.drain.return_value = f  # type: ignore[attr-defined]\n    f.set_result(True)\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.CLOSE\n    assert ws.closed\n    assert req.transport is not None\n    assert len(req.transport.close.mock_calls) == 1  # type: ignore[attr-defined]\n\n\nasync def test_receive_closing(\n    make_request: _RequestMaker, loop: asyncio.AbstractEventLoop\n) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    closing_message = WS_CLOSING_MESSAGE\n\n    ws._reader = mock.Mock()\n    read_mock = mock.AsyncMock(return_value=closing_message)\n    ws._reader.read = read_mock\n\n    f = loop.create_future()\n    assert ws._payload_writer is not None\n    ws._payload_writer.drain.return_value = f  # type: ignore[attr-defined]\n    f.set_result(True)\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.CLOSING\n    assert not ws.closed\n\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.CLOSING\n    assert not ws.closed\n\n    ws._cancel(ConnectionResetError(\"Connection lost\"))\n\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.CLOSING\n\n\nasync def test_close_after_closing(\n    make_request: _RequestMaker, loop: asyncio.AbstractEventLoop\n) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    closing_message = WS_CLOSING_MESSAGE\n\n    ws._reader = mock.Mock()\n    ws._reader.read = mock.AsyncMock(return_value=closing_message)\n\n    f = loop.create_future()\n    assert ws._payload_writer is not None\n    ws._payload_writer.drain.return_value = f  # type: ignore[attr-defined]\n    f.set_result(True)\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.CLOSING\n    assert not ws.closed\n    assert req.transport is not None\n    assert len(req.transport.close.mock_calls) == 0  # type: ignore[attr-defined]\n\n    await ws.close()\n    assert ws.closed\n    assert len(req.transport.close.mock_calls) == 1  # type: ignore[unreachable]\n\n\nasync def test_receive_timeouterror(\n    make_request: _RequestMaker, loop: asyncio.AbstractEventLoop\n) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    assert req.transport is not None\n    assert len(req.transport.close.mock_calls) == 0  # type: ignore[attr-defined]\n\n    ws._reader = mock.Mock()\n    ws._reader.read = mock.AsyncMock(side_effect=asyncio.TimeoutError())\n\n    with pytest.raises(asyncio.TimeoutError):\n        await ws.receive()\n\n    # Should not close the connection on timeout\n    assert len(req.transport.close.mock_calls) == 0  # type: ignore[attr-defined]\n\n\nasync def test_multiple_receive_on_close_connection(\n    make_request: _RequestMaker,\n) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    assert ws._reader is not None\n    ws._reader.feed_data(WS_CLOSED_MESSAGE)\n    await ws.close()\n\n    await ws.receive()\n    await ws.receive()\n    await ws.receive()\n    await ws.receive()\n\n    with pytest.raises(RuntimeError):\n        await ws.receive()\n\n\nasync def test_concurrent_receive(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    ws._waiting = True\n\n    with pytest.raises(RuntimeError):\n        await ws.receive()\n\n\nasync def test_close_exc(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    assert req.transport is not None\n    assert len(req.transport.close.mock_calls) == 0  # type: ignore[attr-defined]\n\n    exc = ValueError()\n    ws._writer = mock.Mock()\n    ws._writer.close.side_effect = exc\n    await ws.close()\n    assert ws.closed\n    assert ws.exception() is exc\n    assert len(req.transport.close.mock_calls) == 1  # type: ignore[attr-defined]\n\n    ws._closed = False\n    ws._writer.close.side_effect = asyncio.CancelledError()\n    with pytest.raises(asyncio.CancelledError):\n        await ws.close()\n\n\nasync def test_prepare_twice_idempotent(make_request: _RequestMaker) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n\n    impl1 = await ws.prepare(req)\n    impl2 = await ws.prepare(req)\n    assert impl1 is impl2\n\n\nasync def test_send_with_per_message_deflate(\n    make_request: _RequestMaker, mocker: MockerFixture\n) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n    with mock.patch.object(ws._writer, \"send_frame\", autospec=True, spec_set=True) as m:\n        await ws.send_str(\"string\", compress=15)\n        m.assert_called_with(b\"string\", WSMsgType.TEXT, compress=15)\n\n        await ws.send_bytes(b\"bytes\", compress=0)\n        m.assert_called_with(b\"bytes\", WSMsgType.BINARY, compress=0)\n\n        await ws.send_json(\"[{}]\", compress=9)\n        m.assert_called_with(b'\"[{}]\"', WSMsgType.TEXT, compress=9)\n\n        await ws.send_frame(b\"[{}]\", WSMsgType.TEXT, compress=9)\n        m.assert_called_with(b\"[{}]\", WSMsgType.TEXT, compress=9)\n\n\nasync def test_no_transfer_encoding_header(\n    make_request: _RequestMaker, mocker: MockerFixture\n) -> None:\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n    await ws._start(req)\n\n    assert \"Transfer-Encoding\" not in ws.headers\n\n\n@pytest.mark.parametrize(\n    \"ws_transport, expected_result\",\n    [\n        (\n            mock.MagicMock(\n                transport=mock.MagicMock(\n                    get_extra_info=lambda name, default=None: {\"test\": \"existent\"}.get(\n                        name, default\n                    )\n                )\n            ),\n            \"existent\",\n        ),\n        (None, \"default\"),\n    ],\n)\nasync def test_get_extra_info(\n    make_request: _RequestMaker,\n    mocker: MockerFixture,\n    ws_transport: mock.MagicMock | None,\n    expected_result: str,\n) -> None:\n    valid_key = \"test\"\n    default_value = \"default\"\n\n    req = make_request(\"GET\", \"/\")\n    ws = web.WebSocketResponse()\n\n    await ws.prepare(req)\n    ws._writer = ws_transport\n\n    assert expected_result == ws.get_extra_info(valid_key, default_value)\n"
  },
  {
    "path": "tests/test_web_websocket_functional.py",
    "content": "# HTTP websocket server functional tests\n\nimport asyncio\nimport contextlib\nimport json\nimport sys\nimport weakref\nfrom typing import Literal, NoReturn\nfrom unittest import mock\n\nimport pytest\n\nimport aiohttp\nfrom aiohttp import WSServerHandshakeError, web\nfrom aiohttp.http import WSCloseCode, WSMsgType\nfrom aiohttp.pytest_plugin import AiohttpClient, AiohttpServer\n\n\nasync def test_websocket_can_prepare(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        ws = web.WebSocketResponse()\n        assert not ws.can_prepare(request)\n        raise web.HTTPUpgradeRequired()\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    assert resp.status == 426\n\n\nasync def test_websocket_json(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        assert ws.can_prepare(request)\n\n        await ws.prepare(request)\n        msg = await ws.receive()\n\n        assert msg.type is WSMsgType.TEXT\n        msg_json = msg.json()\n        answer = msg_json[\"test\"]\n        await ws.send_str(answer)\n\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\")\n    expected_value = \"value\"\n    payload = '{\"test\": \"%s\"}' % expected_value\n    await ws.send_str(payload)\n\n    resp = await ws.receive()\n    assert resp.data == expected_value\n\n    await ws.receive()  # Handle close\n\n\nasync def test_websocket_json_invalid_message(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        with pytest.raises(ValueError):\n            await ws.receive_json()\n        await ws.send_str(\"ValueError was raised\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\")\n    payload = \"NOT A VALID JSON STRING\"\n    await ws.send_str(payload)\n\n    data = await ws.receive_str()\n    assert \"ValueError was raised\" in data\n\n    await ws.receive()  # Handle close\n\n\nasync def test_websocket_send_json(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        data = await ws.receive_json()\n        await ws.send_json(data)\n\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\")\n    expected_value = \"value\"\n    await ws.send_json({\"test\": expected_value})\n\n    data = await ws.receive_json()\n    assert data[\"test\"] == expected_value\n\n    await ws.receive()  # Handle close\n\n\nasync def test_websocket_receive_json(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        data = await ws.receive_json()\n        answer = data[\"test\"]\n        await ws.send_str(answer)\n\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\")\n    expected_value = \"value\"\n    payload = '{\"test\": \"%s\"}' % expected_value\n    await ws.send_str(payload)\n\n    resp = await ws.receive()\n    assert resp.data == expected_value\n\n    await ws.receive()  # Handle close\n\n\nasync def test_send_recv_text(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        msg = await ws.receive_str()\n        await ws.send_str(msg + \"/answer\")\n        await ws.close()\n        closed.set_result(1)\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\")\n    await ws.send_str(\"ask\")\n    msg = await ws.receive()\n    assert msg.type == aiohttp.WSMsgType.TEXT\n    assert \"ask/answer\" == msg.data\n\n    msg = await ws.receive()\n    assert msg.type == aiohttp.WSMsgType.CLOSE\n    assert msg.data == WSCloseCode.OK\n    assert msg.extra == \"\"\n\n    assert ws.closed\n    assert ws.close_code == WSCloseCode.OK\n\n    await closed\n\n\nasync def test_send_recv_bytes(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive_bytes()\n        await ws.send_bytes(msg + b\"/answer\")\n        await ws.close()\n        closed.set_result(1)\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\")\n    await ws.send_bytes(b\"ask\")\n    msg = await ws.receive()\n    assert msg.type == aiohttp.WSMsgType.BINARY\n    assert b\"ask/answer\" == msg.data\n\n    msg = await ws.receive()\n    assert msg.type == aiohttp.WSMsgType.CLOSE\n    assert msg.data == WSCloseCode.OK\n    assert msg.extra == \"\"\n\n    assert ws.closed\n    assert ws.close_code == WSCloseCode.OK\n\n    await closed\n\n\nasync def test_send_recv_json(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        data = await ws.receive_json()\n        await ws.send_json({\"response\": data[\"request\"]})\n        await ws.close()\n        closed.set_result(1)\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\")\n\n    await ws.send_str('{\"request\": \"test\"}')\n    msg = await ws.receive()\n    assert msg.type is WSMsgType.TEXT\n    data = msg.json()\n    assert msg.type == aiohttp.WSMsgType.TEXT\n    assert data[\"response\"] == \"test\"\n\n    msg = await ws.receive()\n    assert msg.type == aiohttp.WSMsgType.CLOSE\n    assert msg.data == WSCloseCode.OK\n    assert msg.extra == \"\"\n\n    await ws.close()\n\n    await closed\n\n\nasync def test_close_timeout(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    aborted = loop.create_future()\n    elapsed = 1e10  # something big\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        nonlocal elapsed\n        ws = web.WebSocketResponse(timeout=0.1)\n        await ws.prepare(request)\n        assert \"request\" == (await ws.receive_str())\n        await ws.send_str(\"reply\")\n        assert ws._loop is not None\n        begin = ws._loop.time()\n        assert await ws.close()\n        elapsed = ws._loop.time() - begin\n        assert ws.close_code == WSCloseCode.ABNORMAL_CLOSURE\n        assert isinstance(ws.exception(), asyncio.TimeoutError)\n        aborted.set_result(1)\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\")\n    await ws.send_str(\"request\")\n    assert \"reply\" == (await ws.receive_str())\n\n    # The server closes here.  Then the client sends bogus messages with an\n    # interval shorter than server-side close timeout, to make the server\n    # hanging indefinitely.\n    await asyncio.sleep(0.08)\n    msg = await ws._reader.read()\n    assert msg.type == WSMsgType.CLOSE\n\n    await asyncio.sleep(0.08)\n    assert await aborted\n\n    assert elapsed < 0.25, \"close() should have returned before at most 2x timeout.\"\n\n    await ws.close()\n\n\nasync def test_concurrent_close(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    srv_ws = None\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        nonlocal srv_ws\n        ws = srv_ws = web.WebSocketResponse(autoclose=False, protocols=(\"foo\", \"bar\"))\n        await ws.prepare(request)\n\n        msg = await ws.receive()\n        assert msg.type == WSMsgType.CLOSING\n\n        await asyncio.sleep(0)\n\n        msg = await ws.receive()\n        assert msg.type == WSMsgType.CLOSED\n\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\", autoclose=False, protocols=(\"eggs\", \"bar\"))\n\n    assert srv_ws is not None\n    await srv_ws.close(code=WSCloseCode.INVALID_TEXT)\n\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.CLOSE\n\n    await asyncio.sleep(0)\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.CLOSED\n\n\nasync def test_concurrent_close_multiple_tasks(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    srv_ws = None\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        nonlocal srv_ws\n        ws = srv_ws = web.WebSocketResponse(autoclose=False, protocols=(\"foo\", \"bar\"))\n        await ws.prepare(request)\n\n        msg = await ws.receive()\n        assert msg.type == WSMsgType.CLOSING\n\n        await asyncio.sleep(0)\n\n        msg = await ws.receive()\n        assert msg.type == WSMsgType.CLOSED\n\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\", autoclose=False, protocols=(\"eggs\", \"bar\"))\n\n    assert srv_ws is not None\n    task1 = asyncio.create_task(srv_ws.close(code=WSCloseCode.INVALID_TEXT))\n    task2 = asyncio.create_task(srv_ws.close(code=WSCloseCode.INVALID_TEXT))\n\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.CLOSE\n\n    await task1\n    await task2\n\n    await asyncio.sleep(0)\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.CLOSED\n\n\nasync def test_close_op_code_from_client(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    srv_ws: web.WebSocketResponse | None = None\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        nonlocal srv_ws\n        ws = srv_ws = web.WebSocketResponse(protocols=(\"foo\", \"bar\"))\n        await ws.prepare(request)\n\n        msg = await ws.receive()\n        assert msg.type == WSMsgType.CLOSE\n        await asyncio.sleep(0)\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\", protocols=(\"eggs\", \"bar\"))\n\n    await ws._writer.send_frame(b\"\", WSMsgType.CLOSE)\n\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.CLOSE\n\n    await asyncio.sleep(0)\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.CLOSED\n\n\nasync def test_auto_pong_with_closing_by_peer(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        await ws.receive()\n\n        msg = await ws.receive()\n        assert msg.type == WSMsgType.CLOSE\n        assert msg.data == WSCloseCode.OK\n        assert msg.extra == \"exit message\"\n        closed.set_result(None)\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\", autoclose=False, autoping=False)\n    await ws.ping()\n    await ws.send_str(\"ask\")\n\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.PONG\n    await ws.close(code=WSCloseCode.OK, message=b\"exit message\")\n    await closed\n\n\nasync def test_ping(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        await ws.ping(b\"data\")\n        await ws.receive()\n        closed.set_result(None)\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\", autoping=False)\n\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.PING\n    assert msg.data == b\"data\"\n    await ws.pong()\n    await ws.close()\n    await closed\n\n\nasync def test_client_ping(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        await ws.receive()\n        closed.set_result(None)\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\", autoping=False)\n\n    await ws.ping(b\"data\")\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.PONG\n    assert msg.data == b\"data\"\n    await ws.pong()\n    await ws.close()\n\n\nasync def test_pong(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse(autoping=False)\n        await ws.prepare(request)\n\n        msg = await ws.receive()\n        assert msg.type == WSMsgType.PING\n        await ws.pong(b\"data\")\n\n        msg = await ws.receive()\n        assert msg.type == WSMsgType.CLOSE\n        assert msg.data == WSCloseCode.OK\n        assert msg.extra == \"exit message\"\n        closed.set_result(None)\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\", autoping=False)\n\n    await ws.ping(b\"data\")\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.PONG\n    assert msg.data == b\"data\"\n\n    await ws.close(code=WSCloseCode.OK, message=b\"exit message\")\n\n    await closed\n\n\nasync def test_change_status(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        ws.set_status(200)\n        assert 200 == ws.status\n        await ws.prepare(request)\n        assert 101 == ws.status\n        await ws.close()\n        closed.set_result(None)\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\", autoping=False)\n\n    await ws.close()\n    await closed\n    await ws.close()\n\n\nasync def test_handle_protocol(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse(protocols=(\"foo\", \"bar\"))\n        await ws.prepare(request)\n        await ws.close()\n        assert \"bar\" == ws.ws_protocol\n        closed.set_result(None)\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\", protocols=(\"eggs\", \"bar\"))\n\n    await ws.close()\n    await closed\n\n\nasync def test_server_close_handshake(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse(protocols=(\"foo\", \"bar\"))\n        await ws.prepare(request)\n        await ws.close()\n        closed.set_result(None)\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\", autoclose=False, protocols=(\"eggs\", \"bar\"))\n\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.CLOSE\n    await ws.close()\n    await closed\n\n\nasync def test_client_close_handshake(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse(autoclose=False, protocols=(\"foo\", \"bar\"))\n        await ws.prepare(request)\n\n        msg = await ws.receive()\n        assert msg.type == WSMsgType.CLOSE\n        assert not ws.closed\n        await ws.close()\n        assert ws.closed\n        assert ws.close_code == WSCloseCode.INVALID_TEXT  # type: ignore[unreachable]\n\n        msg = await ws.receive()\n        assert msg.type == WSMsgType.CLOSED\n\n        closed.set_result(None)\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\", autoclose=False, protocols=(\"eggs\", \"bar\"))\n\n    await ws.close(code=WSCloseCode.INVALID_TEXT)\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.CLOSED\n    await closed\n\n\nasync def test_server_close_handshake_server_eats_client_messages(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse(protocols=(\"foo\", \"bar\"))\n        await ws.prepare(request)\n        await ws.close()\n        closed.set_result(None)\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\n        \"/\", autoclose=False, autoping=False, protocols=(\"eggs\", \"bar\")\n    )\n\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.CLOSE\n\n    await ws.send_str(\"text\")\n    await ws.send_bytes(b\"bytes\")\n    await ws.ping()\n\n    await ws.close()\n    await closed\n\n\nasync def test_receive_timeout(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    raised = False\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse(receive_timeout=0.1)\n        await ws.prepare(request)\n\n        try:\n            await ws.receive()\n        except asyncio.TimeoutError:\n            nonlocal raised\n            raised = True\n\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\")\n    await ws.receive()\n    await ws.close()\n    assert raised\n\n\nasync def test_custom_receive_timeout(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    raised = False\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse(receive_timeout=None)\n        await ws.prepare(request)\n\n        try:\n            await ws.receive(0.1)\n        except asyncio.TimeoutError:\n            nonlocal raised\n            raised = True\n\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\")\n    await ws.receive()\n    await ws.close()\n    assert raised\n\n\nasync def test_heartbeat(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse(heartbeat=0.05)\n        await ws.prepare(request)\n        await ws.receive()\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n\n    client = await aiohttp_client(app)\n    ws = await client.ws_connect(\"/\", autoping=False)\n    msg = await ws.receive()\n\n    assert msg.type == aiohttp.WSMsgType.PING\n\n    await ws.close()\n\n\nasync def test_heartbeat_no_pong(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse(heartbeat=0.05)\n        await ws.prepare(request)\n\n        await ws.receive()\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n\n    client = await aiohttp_client(app)\n    ws = await client.ws_connect(\"/\", autoping=False)\n    msg = await ws.receive()\n    assert msg.type == aiohttp.WSMsgType.PING\n    await ws.close()\n\n\nasync def test_heartbeat_connection_closed(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    \"\"\"Test that the connection is closed while ping is in progress.\"\"\"\n    ping_count = 0\n\n    async def handler(request: web.Request) -> NoReturn:\n        nonlocal ping_count\n        ws_server = web.WebSocketResponse(heartbeat=0.05)\n        await ws_server.prepare(request)\n        # We patch write here to simulate a connection reset error\n        # since if we closed the connection normally, the server would\n        # would cancel the heartbeat task and we wouldn't get a ping\n        assert ws_server._req is not None\n        assert ws_server._writer is not None\n        with (\n            mock.patch.object(\n                ws_server._req.transport, \"write\", side_effect=ConnectionResetError\n            ),\n            mock.patch.object(\n                ws_server._writer, \"send_frame\", wraps=ws_server._writer.send_frame\n            ) as send_frame,\n        ):\n            try:\n                await ws_server.receive()\n            finally:\n                ping_count = send_frame.call_args_list.count(\n                    mock.call(b\"\", WSMsgType.PING)\n                )\n        assert False\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n\n    client = await aiohttp_client(app)\n    ws = await client.ws_connect(\"/\", autoping=False)\n    msg = await ws.receive()\n    assert msg.type is aiohttp.WSMsgType.CLOSED\n    assert msg.extra is None\n    assert ws.close_code == WSCloseCode.ABNORMAL_CLOSURE\n    assert ping_count == 1\n    await ws.close()\n\n\nasync def test_heartbeat_failure_ends_receive(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    \"\"\"Test that no heartbeat response to the server ends the receive call.\"\"\"\n    ws_server_close_code = None\n    ws_server_exception = None\n\n    async def handler(request: web.Request) -> NoReturn:\n        nonlocal ws_server_close_code, ws_server_exception\n        ws_server = web.WebSocketResponse(heartbeat=0.05)\n        await ws_server.prepare(request)\n        try:\n            await ws_server.receive()\n        finally:\n            ws_server_close_code = ws_server.close_code\n            ws_server_exception = ws_server.exception()\n        assert False\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n\n    client = await aiohttp_client(app)\n    ws = await client.ws_connect(\"/\", autoping=False)\n    msg = await ws.receive()\n    assert msg.type is aiohttp.WSMsgType.PING\n    msg = await ws.receive()\n    assert msg.type is aiohttp.WSMsgType.CLOSED\n    assert ws.close_code == WSCloseCode.ABNORMAL_CLOSURE\n    assert ws_server_close_code == WSCloseCode.ABNORMAL_CLOSURE\n    assert isinstance(ws_server_exception, asyncio.TimeoutError)\n    assert str(ws_server_exception) == \"No PONG received after 0.025 seconds\"\n    await ws.close()\n\n\nasync def test_heartbeat_no_pong_send_many_messages(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    \"\"\"Test no pong after sending many messages.\"\"\"\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse(heartbeat=0.05)\n        await ws.prepare(request)\n        for _ in range(10):\n            await ws.send_str(\"test\")\n\n        await ws.receive()\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n\n    client = await aiohttp_client(app)\n    ws = await client.ws_connect(\"/\", autoping=False)\n    for _ in range(10):\n        msg = await ws.receive()\n        assert msg.type is aiohttp.WSMsgType.TEXT\n        assert msg.data == \"test\"\n\n    msg = await ws.receive()\n    assert msg.type is aiohttp.WSMsgType.PING\n    await ws.close()\n\n\nasync def test_heartbeat_no_pong_receive_many_messages(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    \"\"\"Test no pong after receiving many messages.\"\"\"\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse(heartbeat=0.05)\n        await ws.prepare(request)\n        for _ in range(10):\n            server_msg = await ws.receive()\n            assert server_msg.type is aiohttp.WSMsgType.TEXT\n\n        await ws.receive()\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n\n    client = await aiohttp_client(app)\n    ws = await client.ws_connect(\"/\", autoping=False)\n    for _ in range(10):\n        await ws.send_str(\"test\")\n\n    msg = await ws.receive()\n    assert msg.type is aiohttp.WSMsgType.PING\n    await ws.close()\n\n\nasync def test_server_ws_async_for(\n    loop: asyncio.AbstractEventLoop, aiohttp_server: AiohttpServer\n) -> None:\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        async for msg in ws:\n            assert msg.type == aiohttp.WSMsgType.TEXT\n            s = msg.data\n            await ws.send_str(s + \"/answer\")\n        await ws.close()\n        closed.set_result(1)\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    server = await aiohttp_server(app)\n\n    async with aiohttp.ClientSession() as sm:\n        async with sm.ws_connect(server.make_url(\"/\")) as resp:\n            items = [\"q1\", \"q2\", \"q3\"]\n            for item in items:\n                await resp.send_str(item)\n                msg = await resp.receive()\n                assert msg.type == aiohttp.WSMsgType.TEXT\n                assert item + \"/answer\" == msg.data\n\n            await resp.close()\n            await closed\n\n\nasync def test_closed_async_for(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        messages = []\n        async for msg in ws:\n            messages.append(msg)\n            assert \"stop\" == msg.data\n            await ws.send_str(\"stopping\")\n            await ws.close()\n\n        assert 1 == len(messages)\n        assert messages[0].type == WSMsgType.TEXT\n        assert messages[0].data == \"stop\"\n\n        closed.set_result(None)\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\")\n    await ws.send_str(\"stop\")\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.TEXT\n    assert msg.data == \"stopping\"\n\n    await ws.close()\n    await closed\n\n\nasync def test_websocket_disable_keepalive(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> web.StreamResponse:\n        ws = web.WebSocketResponse()\n        if not ws.can_prepare(request):\n            return web.Response(text=\"OK\")\n        assert request.protocol._keepalive\n        await ws.prepare(request)\n        assert not request.protocol._keepalive\n        assert not request.protocol._keepalive_handle  # type: ignore[unreachable]\n\n        await ws.send_str(\"OK\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/\")\n    txt = await resp.text()\n    assert txt == \"OK\"\n\n    ws = await client.ws_connect(\"/\")\n    data = await ws.receive_str()\n    assert data == \"OK\"\n\n    await ws.receive()  # Handle close\n\n\nasync def test_receive_str_nonstring(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        assert ws.can_prepare(request)\n\n        await ws.prepare(request)\n        await ws.send_bytes(b\"answer\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\")\n    with pytest.raises(TypeError):\n        await ws.receive_str()\n\n    await ws.receive()  # Handle close\n\n\nasync def test_receive_bytes_nonbytes(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handler(request: web.Request) -> NoReturn:\n        ws = web.WebSocketResponse()\n        assert ws.can_prepare(request)\n\n        await ws.prepare(request)\n        await ws.send_str(\"answer\")\n        assert False\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\")\n    with pytest.raises(TypeError):\n        await ws.receive_bytes()\n\n\nasync def test_bug3380(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    async def handle_null(request: web.Request) -> web.Response:\n        return web.json_response({\"err\": None})\n\n    async def ws_handler(request: web.Request) -> web.Response:\n        return web.Response(status=401)\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/ws\", ws_handler)\n    app.router.add_route(\"GET\", \"/api/null\", handle_null)\n\n    client = await aiohttp_client(app)\n\n    resp = await client.get(\"/api/null\")\n    assert (await resp.json()) == {\"err\": None}\n    resp.close()\n\n    with pytest.raises(WSServerHandshakeError):\n        await client.ws_connect(\"/ws\")\n\n    resp = await client.get(\"/api/null\", timeout=aiohttp.ClientTimeout(total=1))\n    assert (await resp.json()) == {\"err\": None}\n    resp.close()\n\n\nasync def test_receive_being_cancelled_keeps_connection_open(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    closed = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse(autoping=False)\n        await ws.prepare(request)\n\n        task = asyncio.create_task(ws.receive())\n        await asyncio.sleep(0)\n        task.cancel()\n        with contextlib.suppress(asyncio.CancelledError):\n            await task\n\n        msg = await ws.receive()\n        assert msg.type == WSMsgType.PING\n        await asyncio.sleep(0)\n        await ws.pong(b\"data\")\n\n        msg = await ws.receive()\n        assert msg.type == WSMsgType.CLOSE\n        assert msg.data == WSCloseCode.OK\n        assert msg.extra == \"exit message\"\n        closed.set_result(None)\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\", autoping=False)\n\n    await asyncio.sleep(0)\n    await ws.ping(b\"data\")\n\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.PONG\n    assert msg.data == b\"data\"\n\n    await ws.close(code=WSCloseCode.OK, message=b\"exit message\")\n\n    await closed\n\n\nasync def test_receive_timeout_keeps_connection_open(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    closed = loop.create_future()\n    timed_out = loop.create_future()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse(autoping=False)\n        await ws.prepare(request)\n\n        task = asyncio.create_task(ws.receive(sys.float_info.min))\n        with contextlib.suppress(asyncio.TimeoutError):\n            await task\n\n        timed_out.set_result(None)\n\n        msg = await ws.receive()\n        assert msg.type == WSMsgType.PING\n        await asyncio.sleep(0)\n        await ws.pong(b\"data\")\n\n        msg = await ws.receive()\n        assert msg.type == WSMsgType.CLOSE\n        assert msg.data == WSCloseCode.OK\n        assert msg.extra == \"exit message\"\n        closed.set_result(None)\n        return ws\n\n    app = web.Application()\n    app.router.add_get(\"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\", autoping=False)\n\n    await timed_out\n    await ws.ping(b\"data\")\n\n    msg = await ws.receive()\n    assert msg.type == WSMsgType.PONG\n    assert msg.data == b\"data\"\n\n    await ws.close(code=WSCloseCode.OK, message=b\"exit message\")\n\n    await closed\n\n\nasync def test_websocket_shutdown(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that the client websocket gets the close message when the server is shutting down.\"\"\"\n    url = \"/ws\"\n    app = web.Application()\n    websockets = web.AppKey(\"websockets\", weakref.WeakSet[web.WebSocketResponse])\n    app[websockets] = weakref.WeakSet()\n\n    # need for send signal shutdown server\n    shutdown_websockets = web.AppKey(\n        \"shutdown_websockets\", weakref.WeakSet[web.WebSocketResponse]\n    )\n    app[shutdown_websockets] = weakref.WeakSet()\n\n    async def websocket_handler(request: web.Request) -> web.WebSocketResponse:\n        websocket = web.WebSocketResponse()\n        await websocket.prepare(request)\n        request.app[websockets].add(websocket)\n        request.app[shutdown_websockets].add(websocket)\n\n        try:\n            async for message in websocket:\n                assert message.type is WSMsgType.TEXT\n                await websocket.send_json({\"ok\": True, \"message\": message.json()})\n        finally:\n            request.app[websockets].discard(websocket)\n\n        return websocket\n\n    async def on_shutdown(app: web.Application) -> None:\n        while app[shutdown_websockets]:\n            websocket = app[shutdown_websockets].pop()\n            await websocket.close(\n                code=aiohttp.WSCloseCode.GOING_AWAY,\n                message=b\"Server shutdown\",\n            )\n\n    app.router.add_get(url, websocket_handler)\n    app.on_shutdown.append(on_shutdown)\n\n    client = await aiohttp_client(app)\n\n    websocket = await client.ws_connect(url)\n\n    message = {\"message\": \"hi\"}\n    await websocket.send_json(message)\n    reply = await websocket.receive_json()\n    assert reply == {\"ok\": True, \"message\": message}\n\n    await app.shutdown()\n\n    assert websocket.closed is False\n\n    reply = await websocket.receive()\n\n    assert reply.type is aiohttp.http.WSMsgType.CLOSE\n    assert reply.data == aiohttp.WSCloseCode.GOING_AWAY\n    assert reply.extra == \"Server shutdown\"\n\n    assert websocket.closed is True\n\n\nasync def test_ws_close_return_code(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that the close code is returned when the server closes the connection.\"\"\"\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n        await ws.receive()\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n    await resp.send_str(\"some data\")\n    msg = await resp.receive()\n    assert msg.type is aiohttp.WSMsgType.CLOSE\n    assert resp.close_code == WSCloseCode.OK\n\n\nasync def test_abnormal_closure_when_server_does_not_receive(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test abnormal closure when the server closes and a message is pending.\"\"\"\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        # Setting close timeout to 0, otherwise the server waits for a\n        # close response for 10 seconds by default.\n        # This would make the client's autoclose in resp.receive() to succeed,\n        # closing the connection cleanly from both sides.\n        ws = web.WebSocketResponse(timeout=0)\n        await ws.prepare(request)\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    resp = await client.ws_connect(\"/\")\n    await resp.send_str(\"some data\")\n    await asyncio.sleep(0.1)\n    msg = await resp.receive()\n    assert msg.type is aiohttp.WSMsgType.CLOSE\n    assert resp.close_code == WSCloseCode.ABNORMAL_CLOSURE\n\n\nasync def test_abnormal_closure_when_client_does_not_close(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test abnormal closure when the server closes and the client doesn't respond.\"\"\"\n    close_code: WSCloseCode | None = None\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        # Setting a short close timeout\n        ws = web.WebSocketResponse(timeout=0.1)\n        await ws.prepare(request)\n        await ws.close()\n\n        nonlocal close_code\n        assert ws.close_code is not None\n        close_code = WSCloseCode(ws.close_code)\n\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    async with client.ws_connect(\"/\", autoclose=False):\n        await asyncio.sleep(0.2)\n    await client.server.close()\n    assert close_code == WSCloseCode.ABNORMAL_CLOSURE\n\n\nasync def test_normal_closure_while_client_sends_msg(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test normal closure when the server closes and the client responds properly.\"\"\"\n    close_code: WSCloseCode | None = None\n    got_close_code = asyncio.Event()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        # Setting a longer close timeout to avoid race conditions\n        ws = web.WebSocketResponse(timeout=1.0)\n        await ws.prepare(request)\n        await ws.close()\n\n        nonlocal close_code\n        assert ws.close_code is not None\n        close_code = WSCloseCode(ws.close_code)\n        got_close_code.set()\n\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n    async with client.ws_connect(\"/\", autoclose=False) as ws:\n        # send text and close message during server close timeout\n        await asyncio.sleep(0.1)\n        await ws.send_str(\"Hello\")\n        await ws.close()\n    # wait for close code to be received by server\n    await asyncio.wait(\n        [\n            asyncio.create_task(asyncio.sleep(0.5)),\n            asyncio.create_task(got_close_code.wait()),\n        ],\n        return_when=asyncio.FIRST_COMPLETED,\n    )\n    await client.server.close()\n    assert close_code == WSCloseCode.OK\n\n\nasync def test_websocket_prepare_timeout_close_issue(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    \"\"\"Test that WebSocket can handle prepare with early returns.\n\n    This is a regression test for issue #6009 where the prepared property\n    incorrectly checked _payload_writer instead of _writer.\n    \"\"\"\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n        assert ws.can_prepare(request)\n        await ws.prepare(request)\n        await ws.send_str(\"test\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/ws\", handler)\n    client = await aiohttp_client(app)\n\n    # Connect via websocket\n    ws = await client.ws_connect(\"/ws\")\n    msg = await ws.receive()\n    assert msg.type is WSMsgType.TEXT\n    assert msg.data == \"test\"\n    await ws.close()\n\n\nasync def test_websocket_prepare_timeout_from_issue_reproducer(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    \"\"\"Test websocket behavior when prepare is interrupted.\n\n    This test verifies the fix for issue #6009 where close() would\n    fail after prepare() was interrupted.\n    \"\"\"\n    prepare_complete = asyncio.Event()\n    close_complete = asyncio.Event()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n\n        # Prepare the websocket\n        await ws.prepare(request)\n        prepare_complete.set()\n\n        # Send a message to confirm connection works\n        await ws.send_str(\"connected\")\n\n        # Wait for client to close\n        msg = await ws.receive()\n        assert msg.type is WSMsgType.CLOSE\n        await ws.close()\n        close_complete.set()\n\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/ws\", handler)\n    client = await aiohttp_client(app)\n\n    # Connect and verify the connection works\n    ws = await client.ws_connect(\"/ws\")\n    await prepare_complete.wait()\n\n    msg = await ws.receive()\n    assert msg.type is WSMsgType.TEXT\n    assert msg.data == \"connected\"\n\n    # Close the connection\n    await ws.close()\n    await close_complete.wait()\n\n\nasync def test_websocket_prepared_property(\n    loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient\n) -> None:\n    \"\"\"Test that WebSocketResponse.prepared property correctly reflects state.\"\"\"\n    prepare_called = asyncio.Event()\n\n    async def handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()\n\n        # Initially not prepared\n        initial_state = ws.prepared\n        assert not initial_state\n\n        # After prepare() is called, should be prepared\n        await ws.prepare(request)\n        prepare_called.set()\n\n        # Check prepared state\n        prepared_state = ws.prepared\n        assert prepared_state\n\n        # Send a message to verify the connection works\n        await ws.send_str(\"test\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\")\n    await prepare_called.wait()\n    msg = await ws.receive()\n    assert msg.type is WSMsgType.TEXT\n    assert msg.data == \"test\"\n    await ws.close()\n\n\nasync def test_receive_text_as_bytes_server_side(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test server receiving TEXT messages as raw bytes with decode_text=False.\"\"\"\n\n    async def websocket_handler(\n        request: web.Request,\n    ) -> web.WebSocketResponse[Literal[False]]:\n        ws: web.WebSocketResponse[Literal[False]] = web.WebSocketResponse(\n            decode_text=False\n        )\n        await ws.prepare(request)\n\n        # Receive TEXT message as bytes\n        msg = await ws.receive()\n        assert msg.type is aiohttp.WSMsgType.TEXT\n        assert isinstance(msg.data, bytes)\n        assert msg.data == b\"test message\"\n\n        # Send response\n        await ws.send_bytes(msg.data + b\"/reply\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", websocket_handler)\n    client = await aiohttp_client(app)\n\n    async with client.ws_connect(\"/\") as ws:\n        await ws.send_str(\"test message\")\n\n        msg = await ws.receive()\n        assert msg.type is aiohttp.WSMsgType.BINARY\n        assert msg.data == b\"test message/reply\"\n\n        await ws.close()\n\n\nasync def test_receive_text_as_bytes_server_iteration(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test server iterating over WebSocket with decode_text=False.\"\"\"\n\n    async def websocket_handler(\n        request: web.Request,\n    ) -> web.WebSocketResponse[Literal[False]]:\n        ws: web.WebSocketResponse[Literal[False]] = web.WebSocketResponse(\n            decode_text=False\n        )\n        await ws.prepare(request)\n\n        async for msg in ws:\n            if msg.type is aiohttp.WSMsgType.TEXT:\n                # msg.data should be bytes\n                assert isinstance(msg.data, bytes)\n                # Echo back\n                await ws.send_bytes(msg.data)\n            else:\n                assert msg.type is aiohttp.WSMsgType.BINARY\n                assert isinstance(msg.data, bytes)\n                await ws.send_bytes(msg.data)\n\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", websocket_handler)\n    client = await aiohttp_client(app)\n\n    async with client.ws_connect(\"/\") as ws:\n        # Send TEXT message\n        await ws.send_str(\"hello\")\n        msg = await ws.receive()\n        assert msg.type is aiohttp.WSMsgType.BINARY\n        assert msg.data == b\"hello\"\n\n        # Send BINARY message\n        await ws.send_bytes(b\"world\")\n        msg = await ws.receive()\n        assert msg.type is aiohttp.WSMsgType.BINARY\n        assert msg.data == b\"world\"\n\n        await ws.close()\n\n\nasync def test_server_decode_text_default_true(aiohttp_client: AiohttpClient) -> None:\n    \"\"\"Test that server decode_text defaults to True for backward compatibility.\"\"\"\n\n    async def websocket_handler(request: web.Request) -> web.WebSocketResponse:\n        # No decode_text parameter - should default to True\n        ws = web.WebSocketResponse()\n        await ws.prepare(request)\n\n        msg = await ws.receive()\n        assert msg.type is aiohttp.WSMsgType.TEXT\n        assert isinstance(msg.data, str)\n        assert msg.data == \"test\"\n\n        await ws.send_str(msg.data + \"/reply\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", websocket_handler)\n    client = await aiohttp_client(app)\n\n    async with client.ws_connect(\"/\") as ws:\n        await ws.send_str(\"test\")\n\n        msg = await ws.receive()\n        assert msg.type is aiohttp.WSMsgType.TEXT\n        assert isinstance(msg.data, str)\n        assert msg.data == \"test/reply\"\n\n        await ws.close()\n\n\nasync def test_server_receive_str_returns_bytes_with_decode_text_false(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that server receive_str() returns bytes when decode_text=False.\"\"\"\n\n    async def websocket_handler(\n        request: web.Request,\n    ) -> web.WebSocketResponse[Literal[False]]:\n        ws: web.WebSocketResponse[Literal[False]] = web.WebSocketResponse(\n            decode_text=False\n        )\n        await ws.prepare(request)\n\n        # receive_str() should return bytes when decode_text=False\n        data = await ws.receive_str()\n        assert isinstance(data, bytes)\n        assert data == b\"hello server\"\n\n        await ws.send_str(\"got bytes\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", websocket_handler)\n    client = await aiohttp_client(app)\n\n    async with client.ws_connect(\"/\") as ws:\n        await ws.send_str(\"hello server\")\n        msg = await ws.receive()\n        assert msg.data == \"got bytes\"\n\n\nasync def test_server_receive_str_returns_str_with_decode_text_true(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test that server receive_str() returns str when decode_text=True (default).\"\"\"\n\n    async def websocket_handler(request: web.Request) -> web.WebSocketResponse:\n        ws = web.WebSocketResponse()  # decode_text=True by default\n        await ws.prepare(request)\n\n        # receive_str() should return str when decode_text=True\n        data = await ws.receive_str()\n        assert isinstance(data, str)\n        assert data == \"hello server\"\n\n        await ws.send_str(\"got string\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", websocket_handler)\n    client = await aiohttp_client(app)\n\n    async with client.ws_connect(\"/\") as ws:\n        await ws.send_str(\"hello server\")\n        msg = await ws.receive()\n        assert msg.data == \"got string\"\n\n\nasync def test_server_receive_json_with_orjson_style_loads(\n    aiohttp_client: AiohttpClient,\n) -> None:\n    \"\"\"Test server receive_json() with orjson-style loads that accepts bytes.\"\"\"\n\n    def orjson_style_loads(data: bytes) -> dict[str, str]:\n        \"\"\"Mock orjson.loads that accepts bytes.\"\"\"\n        assert isinstance(data, bytes)\n        result: dict[str, str] = json.loads(data)\n        return result\n\n    async def websocket_handler(\n        request: web.Request,\n    ) -> web.WebSocketResponse[Literal[False]]:\n        ws: web.WebSocketResponse[Literal[False]] = web.WebSocketResponse(\n            decode_text=False\n        )\n        await ws.prepare(request)\n\n        # receive_json() with orjson-style loads should work with bytes\n        data = await ws.receive_json(loads=orjson_style_loads)\n        assert data == {\"test\": \"value\"}\n\n        await ws.send_str(\"success\")\n        await ws.close()\n        return ws\n\n    app = web.Application()\n    app.router.add_route(\"GET\", \"/\", websocket_handler)\n    client = await aiohttp_client(app)\n\n    ws = await client.ws_connect(\"/\")\n    await ws.send_str('{\"test\": \"value\"}')\n    msg = await ws.receive()\n    assert msg.type is aiohttp.WSMsgType.TEXT\n    assert msg.data == \"success\"\n    await ws.close()\n"
  },
  {
    "path": "tests/test_websocket_data_queue.py",
    "content": "import asyncio\nfrom unittest import mock\n\nimport pytest\n\nfrom aiohttp._websocket.models import WSMessageBinary\nfrom aiohttp._websocket.reader import WebSocketDataQueue\nfrom aiohttp.base_protocol import BaseProtocol\n\n\n@pytest.fixture\ndef protocol() -> BaseProtocol:\n    return mock.create_autospec(BaseProtocol, spec_set=True, instance=True, _reading_paused=False)  # type: ignore[no-any-return]\n\n\n@pytest.fixture\ndef buffer(\n    loop: asyncio.AbstractEventLoop, protocol: BaseProtocol\n) -> WebSocketDataQueue:\n    return WebSocketDataQueue(protocol, limit=1, loop=loop)\n\n\nclass TestWebSocketDataQueue:\n    def test_feed_pause(self, buffer: WebSocketDataQueue) -> None:\n        buffer._protocol._reading_paused = False\n        for _ in range(3):\n            buffer.feed_data(WSMessageBinary(b\"x\", size=1))\n\n        assert buffer._protocol.pause_reading.called  # type: ignore[attr-defined]\n\n    async def test_resume_on_read(self, buffer: WebSocketDataQueue) -> None:\n        buffer.feed_data(WSMessageBinary(b\"x\", size=1))\n\n        buffer._protocol._reading_paused = True\n        await buffer.read()\n        assert buffer._protocol.resume_reading.called  # type: ignore[attr-defined]\n"
  },
  {
    "path": "tests/test_websocket_handshake.py",
    "content": "# Tests for http/websocket.py\n\nimport base64\nimport os\n\nimport pytest\n\nfrom aiohttp import web\nfrom aiohttp.test_utils import make_mocked_request\n\n\ndef gen_ws_headers(\n    protocols: str = \"\",\n    compress: int = 0,\n    extension_text: str = \"\",\n    server_notakeover: bool = False,\n    client_notakeover: bool = False,\n) -> tuple[list[tuple[str, str]], str]:\n    key = base64.b64encode(os.urandom(16)).decode()\n    hdrs = [\n        (\"Upgrade\", \"websocket\"),\n        (\"Connection\", \"upgrade\"),\n        (\"Sec-Websocket-Version\", \"13\"),\n        (\"Sec-Websocket-Key\", key),\n    ]\n    if protocols:\n        hdrs += [(\"Sec-Websocket-Protocol\", protocols)]\n    if compress:\n        params = \"permessage-deflate\"\n        if compress < 15:\n            params += \"; server_max_window_bits=\" + str(compress)\n        if server_notakeover:\n            params += \"; server_no_context_takeover\"\n        if client_notakeover:\n            params += \"; client_no_context_takeover\"\n        if extension_text:\n            params += \"; \" + extension_text\n        hdrs += [(\"Sec-Websocket-Extensions\", params)]\n    return hdrs, key\n\n\nasync def test_no_upgrade() -> None:\n    ws = web.WebSocketResponse()\n    req = make_mocked_request(\"GET\", \"/\")\n    with pytest.raises(web.HTTPBadRequest):\n        await ws.prepare(req)\n\n\nasync def test_no_connection() -> None:\n    ws = web.WebSocketResponse()\n    req = make_mocked_request(\n        \"GET\", \"/\", headers={\"Upgrade\": \"websocket\", \"Connection\": \"keep-alive\"}\n    )\n    with pytest.raises(web.HTTPBadRequest):\n        await ws.prepare(req)\n\n\nasync def test_protocol_version_unset() -> None:\n    ws = web.WebSocketResponse()\n    req = make_mocked_request(\n        \"GET\", \"/\", headers={\"Upgrade\": \"websocket\", \"Connection\": \"upgrade\"}\n    )\n    with pytest.raises(web.HTTPBadRequest):\n        await ws.prepare(req)\n\n\nasync def test_protocol_version_not_supported() -> None:\n    ws = web.WebSocketResponse()\n    req = make_mocked_request(\n        \"GET\",\n        \"/\",\n        headers={\n            \"Upgrade\": \"websocket\",\n            \"Connection\": \"upgrade\",\n            \"Sec-Websocket-Version\": \"1\",\n        },\n    )\n    with pytest.raises(web.HTTPBadRequest):\n        await ws.prepare(req)\n\n\nasync def test_protocol_key_not_present() -> None:\n    ws = web.WebSocketResponse()\n    req = make_mocked_request(\n        \"GET\",\n        \"/\",\n        headers={\n            \"Upgrade\": \"websocket\",\n            \"Connection\": \"upgrade\",\n            \"Sec-Websocket-Version\": \"13\",\n        },\n    )\n    with pytest.raises(web.HTTPBadRequest):\n        await ws.prepare(req)\n\n\nasync def test_protocol_key_invalid() -> None:\n    ws = web.WebSocketResponse()\n    req = make_mocked_request(\n        \"GET\",\n        \"/\",\n        headers={\n            \"Upgrade\": \"websocket\",\n            \"Connection\": \"upgrade\",\n            \"Sec-Websocket-Version\": \"13\",\n            \"Sec-Websocket-Key\": \"123\",\n        },\n    )\n    with pytest.raises(web.HTTPBadRequest):\n        await ws.prepare(req)\n\n\nasync def test_protocol_key_bad_size() -> None:\n    ws = web.WebSocketResponse()\n    sec_key = base64.b64encode(os.urandom(2))\n    val = sec_key.decode()\n    req = make_mocked_request(\n        \"GET\",\n        \"/\",\n        headers={\n            \"Upgrade\": \"websocket\",\n            \"Connection\": \"upgrade\",\n            \"Sec-Websocket-Version\": \"13\",\n            \"Sec-Websocket-Key\": val,\n        },\n    )\n    with pytest.raises(web.HTTPBadRequest):\n        await ws.prepare(req)\n\n\nasync def test_handshake_ok() -> None:\n    hdrs, sec_key = gen_ws_headers()\n    ws = web.WebSocketResponse()\n    req = make_mocked_request(\"GET\", \"/\", headers=hdrs)\n\n    await ws.prepare(req)\n\n    assert ws.ws_protocol is None\n\n\nasync def test_handshake_protocol() -> None:\n    # Tests if one protocol is returned by handshake\n    proto = \"chat\"\n\n    ws = web.WebSocketResponse(protocols={\"chat\"})\n    req = make_mocked_request(\"GET\", \"/\", headers=gen_ws_headers(proto)[0])\n\n    await ws.prepare(req)\n\n    assert ws.ws_protocol == proto\n\n\nasync def test_handshake_protocol_agreement() -> None:\n    # Tests if the right protocol is selected given multiple\n    best_proto = \"worse_proto\"\n    wanted_protos = [\"best\", \"chat\", \"worse_proto\"]\n    server_protos = \"worse_proto,chat\"\n\n    ws = web.WebSocketResponse(protocols=wanted_protos)\n    req = make_mocked_request(\"GET\", \"/\", headers=gen_ws_headers(server_protos)[0])\n\n    await ws.prepare(req)\n\n    assert ws.ws_protocol == best_proto\n\n\nasync def test_handshake_protocol_unsupported(caplog: pytest.LogCaptureFixture) -> None:\n    # Tests if a protocol mismatch handshake warns and returns None\n    proto = \"chat\"\n    req = make_mocked_request(\"GET\", \"/\", headers=gen_ws_headers(\"test\")[0])\n\n    ws = web.WebSocketResponse(protocols=[proto])\n    await ws.prepare(req)\n\n    assert (\n        caplog.records[-1].msg\n        == \"%s: Client protocols %r don’t overlap server-known ones %r\"\n    )\n    assert ws.ws_protocol is None\n\n\nasync def test_handshake_compress() -> None:\n    hdrs, sec_key = gen_ws_headers(compress=15)\n\n    req = make_mocked_request(\"GET\", \"/\", headers=hdrs)\n\n    ws = web.WebSocketResponse()\n    await ws.prepare(req)\n\n    assert ws.compress == 15\n\n\ndef test_handshake_compress_server_notakeover() -> None:\n    hdrs, sec_key = gen_ws_headers(compress=15, server_notakeover=True)\n\n    req = make_mocked_request(\"GET\", \"/\", headers=hdrs)\n\n    ws = web.WebSocketResponse()\n    headers, _, compress, notakeover = ws._handshake(req)\n\n    assert compress == 15\n    assert notakeover is True\n    assert \"Sec-Websocket-Extensions\" in headers\n    assert headers[\"Sec-Websocket-Extensions\"] == (\n        \"permessage-deflate; server_no_context_takeover\"\n    )\n\n\ndef test_handshake_compress_client_notakeover() -> None:\n    hdrs, sec_key = gen_ws_headers(compress=15, client_notakeover=True)\n\n    req = make_mocked_request(\"GET\", \"/\", headers=hdrs)\n\n    ws = web.WebSocketResponse()\n    headers, _, compress, notakeover = ws._handshake(req)\n\n    assert \"Sec-Websocket-Extensions\" in headers\n    assert headers[\"Sec-Websocket-Extensions\"] == (\"permessage-deflate\"), hdrs\n\n    assert compress == 15\n\n\ndef test_handshake_compress_wbits() -> None:\n    hdrs, sec_key = gen_ws_headers(compress=9)\n\n    req = make_mocked_request(\"GET\", \"/\", headers=hdrs)\n\n    ws = web.WebSocketResponse()\n    headers, _, compress, notakeover = ws._handshake(req)\n\n    assert \"Sec-Websocket-Extensions\" in headers\n    assert headers[\"Sec-Websocket-Extensions\"] == (\n        \"permessage-deflate; server_max_window_bits=9\"\n    )\n    assert compress == 9\n\n\ndef test_handshake_compress_wbits_error() -> None:\n    hdrs, sec_key = gen_ws_headers(compress=6)\n\n    req = make_mocked_request(\"GET\", \"/\", headers=hdrs)\n\n    ws = web.WebSocketResponse()\n    headers, _, compress, notakeover = ws._handshake(req)\n\n    assert \"Sec-Websocket-Extensions\" not in headers\n    assert compress == 0\n\n\ndef test_handshake_compress_bad_ext() -> None:\n    hdrs, sec_key = gen_ws_headers(compress=15, extension_text=\"bad\")\n\n    req = make_mocked_request(\"GET\", \"/\", headers=hdrs)\n\n    ws = web.WebSocketResponse()\n    headers, _, compress, notakeover = ws._handshake(req)\n\n    assert \"Sec-Websocket-Extensions\" not in headers\n    assert compress == 0\n\n\ndef test_handshake_compress_multi_ext_bad() -> None:\n    hdrs, sec_key = gen_ws_headers(\n        compress=15, extension_text=\"bad, permessage-deflate\"\n    )\n\n    req = make_mocked_request(\"GET\", \"/\", headers=hdrs)\n\n    ws = web.WebSocketResponse()\n    headers, _, compress, notakeover = ws._handshake(req)\n\n    assert \"Sec-Websocket-Extensions\" in headers\n    assert headers[\"Sec-Websocket-Extensions\"] == \"permessage-deflate\"\n\n\ndef test_handshake_compress_multi_ext_wbits() -> None:\n    hdrs, sec_key = gen_ws_headers(compress=6, extension_text=\", permessage-deflate\")\n\n    req = make_mocked_request(\"GET\", \"/\", headers=hdrs)\n\n    ws = web.WebSocketResponse()\n    headers, _, compress, notakeover = ws._handshake(req)\n\n    assert \"Sec-Websocket-Extensions\" in headers\n    assert headers[\"Sec-Websocket-Extensions\"] == \"permessage-deflate\"\n    assert compress == 15\n\n\ndef test_handshake_no_transfer_encoding() -> None:\n    hdrs, sec_key = gen_ws_headers()\n    req = make_mocked_request(\"GET\", \"/\", headers=hdrs)\n\n    ws = web.WebSocketResponse()\n    headers, _, compress, notakeover = ws._handshake(req)\n\n    assert \"Transfer-Encoding\" not in headers\n"
  },
  {
    "path": "tests/test_websocket_parser.py",
    "content": "import asyncio\nimport pickle\nimport random\nimport struct\nfrom unittest import mock\n\nimport pytest\n\nfrom aiohttp._websocket import helpers as _websocket_helpers\nfrom aiohttp._websocket.helpers import (\n    PACK_CLOSE_CODE,\n    PACK_LEN1,\n    PACK_LEN2,\n    PACK_LEN3,\n    PACK_RANDBITS,\n    websocket_mask,\n)\nfrom aiohttp._websocket.models import WS_DEFLATE_TRAILING\nfrom aiohttp._websocket.reader import WebSocketDataQueue\nfrom aiohttp.base_protocol import BaseProtocol\nfrom aiohttp.compression_utils import ZLibBackend, ZLibBackendWrapper\nfrom aiohttp.http import WebSocketError, WSCloseCode, WSMsgType\nfrom aiohttp.http_websocket import (\n    WebSocketReader,\n    WSMessageBinary,\n    WSMessageClose,\n    WSMessagePing,\n    WSMessagePong,\n    WSMessageText,\n)\n\n\nclass PatchableWebSocketReader(WebSocketReader):\n    \"\"\"WebSocketReader subclass that allows for patching parse_frame.\"\"\"\n\n    def parse_frame(\n        self, data: bytes\n    ) -> list[tuple[bool, int, bytes | bytearray, int]]:\n        # This method is overridden to allow for patching in tests.\n        frames: list[tuple[bool, int, bytes | bytearray, int]] = []\n\n        def _handle_frame(\n            fin: bool,\n            opcode: int,\n            payload: bytes | bytearray,\n            compressed: int,\n        ) -> None:\n            # This method is overridden to allow for patching in tests.\n            frames.append((fin, opcode, payload, compressed))\n\n        with mock.patch.object(self, \"_handle_frame\", _handle_frame):\n            self._feed_data(data)\n        return frames\n\n\ndef build_frame(\n    message: bytes,\n    opcode: int,\n    noheader: bool = False,\n    is_fin: bool = True,\n    ZLibBackend: ZLibBackendWrapper | None = None,\n    mask: bool = False,\n) -> bytes:\n    # Send a frame over the websocket with message as its payload.\n    compress = False\n    if ZLibBackend:\n        compress = True\n        compressobj = ZLibBackend.compressobj(wbits=-9)\n        message = compressobj.compress(message)\n        message = message + compressobj.flush(ZLibBackend.Z_SYNC_FLUSH)\n        assert message.endswith(WS_DEFLATE_TRAILING)\n        message = message[:-4]\n    msg_length = len(message)\n\n    if is_fin:\n        header_first_byte = 0x80 | opcode\n    else:\n        header_first_byte = opcode\n\n    if compress:\n        header_first_byte |= 0x40\n\n    mask_bit = 0x80 if mask else 0\n\n    if msg_length < 126:\n        header = PACK_LEN1(header_first_byte, msg_length | mask_bit)\n    elif msg_length < 65536:\n        header = PACK_LEN2(header_first_byte, 126 | mask_bit, msg_length)\n    else:\n        header = PACK_LEN3(header_first_byte, 127 | mask_bit, msg_length)\n\n    if mask:\n        assert not noheader\n        mask_bytes = PACK_RANDBITS(random.getrandbits(32))\n        message_arr = bytearray(message)\n        websocket_mask(mask_bytes, message_arr)\n        return header + mask_bytes + message_arr\n\n    if noheader:\n        return message\n    else:\n        return header + message\n\n\ndef build_close_frame(\n    code: int = 1000, message: bytes = b\"\", noheader: bool = False\n) -> bytes:\n    # Close the websocket, sending the specified code and message.\n    return build_frame(\n        PACK_CLOSE_CODE(code) + message, opcode=WSMsgType.CLOSE, noheader=noheader\n    )\n\n\n@pytest.fixture()\ndef protocol(loop: asyncio.AbstractEventLoop) -> BaseProtocol:\n    transport = mock.Mock(spec_set=asyncio.Transport)\n    protocol = BaseProtocol(loop)\n    protocol.connection_made(transport)\n    return protocol\n\n\n@pytest.fixture()\ndef out(loop: asyncio.AbstractEventLoop) -> WebSocketDataQueue:\n    return WebSocketDataQueue(mock.Mock(_reading_paused=False), 2**16, loop=loop)\n\n\n@pytest.fixture()\ndef out_low_limit(\n    loop: asyncio.AbstractEventLoop, protocol: BaseProtocol\n) -> WebSocketDataQueue:\n    return WebSocketDataQueue(protocol, 16, loop=loop)\n\n\n@pytest.fixture()\ndef parser_low_limit(\n    out_low_limit: WebSocketDataQueue,\n) -> PatchableWebSocketReader:\n    return PatchableWebSocketReader(out_low_limit, 4 * 1024 * 1024)\n\n\n@pytest.fixture()\ndef parser(out: WebSocketDataQueue) -> PatchableWebSocketReader:\n    return PatchableWebSocketReader(out, 4 * 1024 * 1024)\n\n\ndef test_feed_data_remembers_exception(parser: WebSocketReader) -> None:\n    \"\"\"Verify that feed_data remembers an exception was already raised internally.\"\"\"\n    error, data = parser.feed_data(struct.pack(\"!BB\", 0b01100000, 0b00000000))\n    assert error is True\n    assert data == b\"\"\n\n    error, data = parser.feed_data(b\"\")\n    assert error is True\n    assert data == b\"\"\n\n\ndef test_parse_frame(parser: PatchableWebSocketReader) -> None:\n    parser.parse_frame(struct.pack(\"!BB\", 0b00000001, 0b00000001))\n    res = parser.parse_frame(b\"1\")\n    fin, opcode, payload, compress = res[0]\n\n    assert (0, 1, b\"1\", 0) == (fin, opcode, payload, not not compress)\n\n\ndef test_parse_frame_length0(parser: PatchableWebSocketReader) -> None:\n    fin, opcode, payload, compress = parser.parse_frame(\n        struct.pack(\"!BB\", 0b00000001, 0b00000000)\n    )[0]\n\n    assert (0, 1, b\"\", 0) == (fin, opcode, payload, not not compress)\n\n\ndef test_parse_frame_length2(parser: PatchableWebSocketReader) -> None:\n    parser.parse_frame(struct.pack(\"!BB\", 0b00000001, 126))\n    parser.parse_frame(struct.pack(\"!H\", 4))\n    res = parser.parse_frame(b\"1234\")\n    fin, opcode, payload, compress = res[0]\n\n    assert (0, 1, b\"1234\", 0) == (fin, opcode, payload, not not compress)\n\n\ndef test_parse_frame_length2_multi_byte(parser: PatchableWebSocketReader) -> None:\n    \"\"\"Ensure a multi-byte length is parsed correctly.\"\"\"\n    expected_payload = b\"1\" * 32768\n    parser.parse_frame(struct.pack(\"!BB\", 0b00000001, 126))\n    parser.parse_frame(struct.pack(\"!H\", 32768))\n    res = parser.parse_frame(b\"1\" * 32768)\n    fin, opcode, payload, compress = res[0]\n\n    assert (0, 1, expected_payload, 0) == (fin, opcode, payload, not not compress)\n\n\ndef test_parse_frame_length2_multi_byte_multi_packet(\n    parser: PatchableWebSocketReader,\n) -> None:\n    \"\"\"Ensure a multi-byte length with multiple packets is parsed correctly.\"\"\"\n    expected_payload = b\"1\" * 32768\n    assert parser.parse_frame(struct.pack(\"!BB\", 0b00000001, 126)) == []\n    assert parser.parse_frame(struct.pack(\"!H\", 32768)) == []\n    assert parser.parse_frame(b\"1\" * 8192) == []\n    assert parser.parse_frame(b\"1\" * 8192) == []\n    assert parser.parse_frame(b\"1\" * 8192) == []\n    res = parser.parse_frame(b\"1\" * 8192)\n    fin, opcode, payload, compress = res[0]\n    assert len(payload) == 32768\n    assert (0, 1, expected_payload, 0) == (fin, opcode, payload, not not compress)\n\n\ndef test_parse_frame_length4(parser: PatchableWebSocketReader) -> None:\n    parser.parse_frame(struct.pack(\"!BB\", 0b00000001, 127))\n    parser.parse_frame(struct.pack(\"!Q\", 4))\n    fin, opcode, payload, compress = parser.parse_frame(b\"1234\")[0]\n\n    assert (0, 1, b\"1234\", 0) == (fin, opcode, payload, compress)\n\n\ndef test_parse_frame_mask(parser: PatchableWebSocketReader) -> None:\n    parser.parse_frame(struct.pack(\"!BB\", 0b00000001, 0b10000001))\n    parser.parse_frame(b\"0001\")\n    fin, opcode, payload, compress = parser.parse_frame(b\"1\")[0]\n\n    assert (0, 1, b\"\\x01\", 0) == (fin, opcode, payload, compress)\n\n\ndef test_parse_frame_header_reversed_bits(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    with pytest.raises(WebSocketError):\n        parser.parse_frame(struct.pack(\"!BB\", 0b01100000, 0b00000000))\n\n\ndef test_parse_frame_header_control_frame(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    with pytest.raises(WebSocketError):\n        parser.parse_frame(struct.pack(\"!BB\", 0b00001000, 0b00000000))\n\n\ndef test_parse_frame_header_new_data_err(parser: PatchableWebSocketReader) -> None:\n    with pytest.raises(WebSocketError) as msg:\n        parser._feed_data(struct.pack(\"!BB\", 0b00000000, 0b00000000))\n    assert msg.value.code == WSCloseCode.PROTOCOL_ERROR\n    assert str(msg.value) == \"Continuation frame for non started message\"\n\n\ndef test_parse_frame_header_payload_size(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    with pytest.raises(WebSocketError):\n        parser.parse_frame(struct.pack(\"!BB\", 0b10001000, 0b01111110))\n\n\n# Protractor event loop will call feed_data with bytearray. Since\n# asyncio technically supports memoryview as well, we should test that.\n@pytest.mark.parametrize(\n    argnames=\"data\",\n    argvalues=[b\"\", bytearray(b\"\"), memoryview(b\"\")],\n    ids=[\"bytes\", \"bytearray\", \"memoryview\"],\n)\ndef test_ping_frame(\n    out: WebSocketDataQueue,\n    parser: PatchableWebSocketReader,\n    data: bytes | bytearray | memoryview,\n) -> None:\n    parser._handle_frame(True, WSMsgType.PING, b\"data\", 0)\n    res = out._buffer[0]\n    assert res == WSMessagePing(data=b\"data\", size=4, extra=\"\")\n\n\ndef test_pong_frame(out: WebSocketDataQueue, parser: PatchableWebSocketReader) -> None:\n    parser._handle_frame(True, WSMsgType.PONG, b\"data\", 0)\n    res = out._buffer[0]\n    assert res == WSMessagePong(data=b\"data\", size=4, extra=\"\")\n\n\ndef test_close_frame(out: WebSocketDataQueue, parser: PatchableWebSocketReader) -> None:\n    parser._handle_frame(True, WSMsgType.CLOSE, b\"\", 0)\n    res = out._buffer[0]\n    assert res == WSMessageClose(data=0, size=0, extra=\"\")\n\n\ndef test_close_frame_info(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    parser._handle_frame(True, WSMsgType.CLOSE, b\"0112345\", 0)\n    res = out._buffer[0]\n    assert res == WSMessageClose(data=12337, size=7, extra=\"12345\")\n\n\ndef test_close_frame_invalid(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    with pytest.raises(WebSocketError) as ctx:\n        parser._handle_frame(True, WSMsgType.CLOSE, b\"1\", 0)\n    assert ctx.value.code == WSCloseCode.PROTOCOL_ERROR\n\n\ndef test_close_frame_invalid_2(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    data = build_close_frame(code=1)\n\n    with pytest.raises(WebSocketError) as ctx:\n        parser._feed_data(data)\n\n    assert ctx.value.code == WSCloseCode.PROTOCOL_ERROR\n\n\ndef test_close_frame_unicode_err(parser: PatchableWebSocketReader) -> None:\n    data = build_close_frame(code=1000, message=b\"\\xf4\\x90\\x80\\x80\")\n\n    with pytest.raises(WebSocketError) as ctx:\n        parser._feed_data(data)\n\n    assert ctx.value.code == WSCloseCode.INVALID_TEXT\n\n\ndef test_unknown_frame(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    with pytest.raises(WebSocketError):\n        parser._handle_frame(True, WSMsgType.CONTINUATION, b\"\", 0)\n\n\ndef test_simple_text(out: WebSocketDataQueue, parser: PatchableWebSocketReader) -> None:\n    data = build_frame(b\"text\", WSMsgType.TEXT)\n    parser._feed_data(data)\n    res = out._buffer[0]\n    assert res == WSMessageText(data=\"text\", size=4, extra=\"\")\n\n\ndef test_simple_text_unicode_err(parser: PatchableWebSocketReader) -> None:\n    data = build_frame(b\"\\xf4\\x90\\x80\\x80\", WSMsgType.TEXT)\n\n    with pytest.raises(WebSocketError) as ctx:\n        parser._feed_data(data)\n\n    assert ctx.value.code == WSCloseCode.INVALID_TEXT\n\n\ndef test_simple_binary(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    data = build_frame(b\"binary\", WSMsgType.BINARY)\n    parser._feed_data(data)\n    res = out._buffer[0]\n    assert res == WSMessageBinary(data=b\"binary\", size=6, extra=\"\")\n\n\ndef test_one_byte_at_a_time(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    \"\"\"Send one byte at a time to the parser.\"\"\"\n    data = build_frame(b\"binary\", WSMsgType.BINARY)\n    for i in range(len(data)):\n        parser._feed_data(data[i : i + 1])\n    res = out._buffer[0]\n    assert res == WSMessageBinary(data=b\"binary\", size=6, extra=\"\")\n\n\ndef test_fragmentation_header(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    data = build_frame(b\"a\", WSMsgType.TEXT)\n    parser._feed_data(data[:1])\n    parser._feed_data(data[1:])\n\n    res = out._buffer[0]\n    assert res == WSMessageText(data=\"a\", size=1, extra=\"\")\n\n\ndef test_large_message(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    large_payload = b\"b\" * 131072\n    data = build_frame(large_payload, WSMsgType.BINARY)\n    parser._feed_data(data)\n\n    res = out._buffer[0]\n    assert res == WSMessageBinary(data=large_payload, size=131072, extra=\"\")\n\n\ndef test_large_masked_message(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    large_payload = b\"b\" * 131072\n    data = build_frame(large_payload, WSMsgType.BINARY, mask=True)\n    parser._feed_data(data)\n\n    res = out._buffer[0]\n    assert res == WSMessageBinary(data=large_payload, size=131072, extra=\"\")\n\n\ndef test_fragmented_masked_message(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    large_payload = b\"b\" * 100\n    data = build_frame(large_payload, WSMsgType.BINARY, mask=True)\n    for i in range(len(data)):\n        parser._feed_data(data[i : i + 1])\n\n    res = out._buffer[0]\n    assert res == WSMessageBinary(data=large_payload, size=100, extra=\"\")\n\n\ndef test_large_fragmented_masked_message(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    large_payload = b\"b\" * 131072\n    data = build_frame(large_payload, WSMsgType.BINARY, mask=True)\n    for i in range(0, len(data), 16384):\n        parser._feed_data(data[i : i + 16384])\n    res = out._buffer[0]\n    assert res == WSMessageBinary(data=large_payload, size=131072, extra=\"\")\n\n\ndef test_continuation(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    data1 = build_frame(b\"line1\", WSMsgType.TEXT, is_fin=False)\n    parser._feed_data(data1)\n\n    data2 = build_frame(b\"line2\", WSMsgType.CONTINUATION)\n    parser._feed_data(data2)\n\n    res = out._buffer[0]\n    assert res == WSMessageText(data=\"line1line2\", size=10, extra=\"\")\n\n\ndef test_continuation_with_ping(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    data1 = build_frame(b\"line1\", WSMsgType.TEXT, is_fin=False)\n    parser._feed_data(data1)\n\n    data2 = build_frame(b\"\", WSMsgType.PING)\n    parser._feed_data(data2)\n\n    data3 = build_frame(b\"line2\", WSMsgType.CONTINUATION)\n    parser._feed_data(data3)\n\n    res = out._buffer[0]\n    assert res == WSMessagePing(data=b\"\", size=0, extra=\"\")\n    res = out._buffer[1]\n    assert res == WSMessageText(data=\"line1line2\", size=10, extra=\"\")\n\n\ndef test_continuation_err(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    parser._handle_frame(False, WSMsgType.TEXT, b\"line1\", 0)\n    with pytest.raises(WebSocketError):\n        parser._handle_frame(True, WSMsgType.TEXT, b\"line2\", 0)\n\n\ndef test_continuation_with_close(\n    out: WebSocketDataQueue, parser: WebSocketReader\n) -> None:\n    parser._handle_frame(False, WSMsgType.TEXT, b\"line1\", 0)\n    parser._handle_frame(\n        False,\n        WSMsgType.CLOSE,\n        build_close_frame(1002, b\"test\", noheader=True),\n        False,\n    )\n    parser._handle_frame(True, WSMsgType.CONTINUATION, b\"line2\", 0)\n    res = out._buffer[0]\n    assert res == WSMessageClose(data=1002, size=6, extra=\"test\")\n    res = out._buffer[1]\n    assert res == WSMessageText(data=\"line1line2\", size=10, extra=\"\")\n\n\ndef test_continuation_with_close_unicode_err(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    parser._handle_frame(False, WSMsgType.TEXT, b\"line1\", 0)\n    with pytest.raises(WebSocketError) as ctx:\n        parser._handle_frame(\n            False,\n            WSMsgType.CLOSE,\n            build_close_frame(1000, b\"\\xf4\\x90\\x80\\x80\", noheader=True),\n            0,\n        )\n    parser._handle_frame(True, WSMsgType.CONTINUATION, b\"line2\", 0)\n    assert ctx.value.code == WSCloseCode.INVALID_TEXT\n\n\ndef test_continuation_with_close_bad_code(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    parser._handle_frame(False, WSMsgType.TEXT, b\"line1\", 0)\n    with pytest.raises(WebSocketError) as ctx:\n\n        parser._handle_frame(\n            False, WSMsgType.CLOSE, build_close_frame(1, b\"test\", noheader=True), 0\n        )\n    assert ctx.value.code == WSCloseCode.PROTOCOL_ERROR\n    parser._handle_frame(True, WSMsgType.CONTINUATION, b\"line2\", 0)\n\n\ndef test_continuation_with_close_bad_payload(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    parser._handle_frame(False, WSMsgType.TEXT, b\"line1\", 0)\n    with pytest.raises(WebSocketError) as ctx:\n        parser._handle_frame(False, WSMsgType.CLOSE, b\"1\", 0)\n    assert ctx.value.code == WSCloseCode.PROTOCOL_ERROR\n    parser._handle_frame(True, WSMsgType.CONTINUATION, b\"line2\", 0)\n\n\ndef test_continuation_with_close_empty(\n    out: WebSocketDataQueue, parser: PatchableWebSocketReader\n) -> None:\n    parser._handle_frame(False, WSMsgType.TEXT, b\"line1\", 0)\n    parser._handle_frame(False, WSMsgType.CLOSE, b\"\", 0)\n    parser._handle_frame(True, WSMsgType.CONTINUATION, b\"line2\", 0)\n\n    res = out._buffer[0]\n    assert res == WSMessageClose(data=0, size=0, extra=\"\")\n    res = out._buffer[1]\n    assert res == WSMessageText(data=\"line1line2\", size=10, extra=\"\")\n\n\nwebsocket_mask_data: bytes = b\"some very long data for masking by websocket\"\nwebsocket_mask_mask: bytes = b\"1234\"\nwebsocket_mask_masked: bytes = (\n    b\"B]^Q\\x11DVFH\\x12_[_U\\x13PPFR\\x14W]A\\x14\\\\S@_X\\\\T\\x14SK\\x13CTP@[RYV@\"\n)\n\n\ndef test_websocket_mask_python() -> None:\n    message = bytearray(websocket_mask_data)\n    _websocket_helpers._websocket_mask_python(websocket_mask_mask, message)\n    assert message == websocket_mask_masked\n\n\n@pytest.mark.skipif(\n    not hasattr(_websocket_helpers, \"_websocket_mask_cython\"), reason=\"Requires Cython\"\n)\ndef test_websocket_mask_cython() -> None:\n    message = bytearray(websocket_mask_data)\n    _websocket_helpers._websocket_mask_cython(websocket_mask_mask, message)  # type: ignore[attr-defined]\n    assert message == websocket_mask_masked\n    assert (\n        _websocket_helpers.websocket_mask is _websocket_helpers._websocket_mask_cython  # type: ignore[attr-defined]\n    )\n\n\ndef test_websocket_mask_python_empty() -> None:\n    message = bytearray()\n    _websocket_helpers._websocket_mask_python(websocket_mask_mask, message)\n    assert message == bytearray()\n\n\n@pytest.mark.skipif(\n    not hasattr(_websocket_helpers, \"_websocket_mask_cython\"), reason=\"Requires Cython\"\n)\ndef test_websocket_mask_cython_empty() -> None:\n    message = bytearray()\n    _websocket_helpers._websocket_mask_cython(websocket_mask_mask, message)  # type: ignore[attr-defined]\n    assert message == bytearray()\n\n\ndef test_parse_compress_frame_single(parser: PatchableWebSocketReader) -> None:\n    parser.parse_frame(struct.pack(\"!BB\", 0b11000001, 0b00000001))\n    res = parser.parse_frame(b\"1\")\n    fin, opcode, payload, compress = res[0]\n\n    assert (1, 1, b\"1\", True) == (fin, opcode, payload, not not compress)\n\n\ndef test_parse_compress_frame_multi(parser: PatchableWebSocketReader) -> None:\n    parser.parse_frame(struct.pack(\"!BB\", 0b01000001, 126))\n    parser.parse_frame(struct.pack(\"!H\", 4))\n    res = parser.parse_frame(b\"1234\")\n    fin, opcode, payload, compress = res[0]\n    assert (0, 1, b\"1234\", True) == (fin, opcode, payload, not not compress)\n\n    parser.parse_frame(struct.pack(\"!BB\", 0b10000001, 126))\n    parser.parse_frame(struct.pack(\"!H\", 4))\n    res = parser.parse_frame(b\"1234\")\n    fin, opcode, payload, compress = res[0]\n    assert (1, 1, b\"1234\", True) == (fin, opcode, payload, not not compress)\n\n    parser.parse_frame(struct.pack(\"!BB\", 0b10000001, 126))\n    parser.parse_frame(struct.pack(\"!H\", 4))\n    res = parser.parse_frame(b\"1234\")\n    fin, opcode, payload, compress = res[0]\n    assert (1, 1, b\"1234\", False) == (fin, opcode, payload, not not compress)\n\n\ndef test_parse_compress_error_frame(parser: PatchableWebSocketReader) -> None:\n    parser.parse_frame(struct.pack(\"!BB\", 0b01000001, 0b00000001))\n    parser.parse_frame(b\"1\")\n\n    with pytest.raises(WebSocketError) as ctx:\n        parser.parse_frame(struct.pack(\"!BB\", 0b11000001, 0b00000001))\n\n    assert ctx.value.code == WSCloseCode.PROTOCOL_ERROR\n\n\ndef test_parse_no_compress_frame_single(out: WebSocketDataQueue) -> None:\n    parser_no_compress = PatchableWebSocketReader(out, 0, compress=False)\n    with pytest.raises(WebSocketError) as ctx:\n        parser_no_compress.parse_frame(struct.pack(\"!BB\", 0b11000001, 0b00000001))\n\n    assert ctx.value.code == WSCloseCode.PROTOCOL_ERROR\n\n\ndef test_msg_too_large(out: WebSocketDataQueue) -> None:\n    parser = WebSocketReader(out, 256, compress=False)\n    data = build_frame(b\"text\" * 256, WSMsgType.TEXT)\n    with pytest.raises(WebSocketError) as ctx:\n        parser._feed_data(data)\n    assert ctx.value.code == WSCloseCode.MESSAGE_TOO_BIG\n\n\ndef test_msg_too_large_not_fin(out: WebSocketDataQueue) -> None:\n    parser = WebSocketReader(out, 256, compress=False)\n    data = build_frame(b\"text\" * 256, WSMsgType.TEXT, is_fin=False)\n    with pytest.raises(WebSocketError) as ctx:\n        parser._feed_data(data)\n    assert ctx.value.code == WSCloseCode.MESSAGE_TOO_BIG\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\ndef test_compressed_msg_too_large(out: WebSocketDataQueue) -> None:\n    parser = WebSocketReader(out, 256, compress=True)\n    data = build_frame(b\"aaa\" * 256, WSMsgType.TEXT, ZLibBackend=ZLibBackend)\n    with pytest.raises(WebSocketError) as ctx:\n        parser._feed_data(data)\n    assert ctx.value.code == WSCloseCode.MESSAGE_TOO_BIG\n\n\nclass TestWebSocketError:\n    def test_ctor(self) -> None:\n        err = WebSocketError(WSCloseCode.PROTOCOL_ERROR, \"Something invalid\")\n        assert err.code == WSCloseCode.PROTOCOL_ERROR\n        assert str(err) == \"Something invalid\"\n\n    def test_pickle(self) -> None:\n        err = WebSocketError(WSCloseCode.PROTOCOL_ERROR, \"Something invalid\")\n        err.foo = \"bar\"  # type: ignore[attr-defined]\n        for proto in range(pickle.HIGHEST_PROTOCOL + 1):\n            pickled = pickle.dumps(err, proto)\n            err2 = pickle.loads(pickled)\n            assert err2.code == WSCloseCode.PROTOCOL_ERROR\n            assert str(err2) == \"Something invalid\"\n            assert err2.foo == \"bar\"\n\n\ndef test_flow_control_binary(\n    protocol: BaseProtocol,\n    out_low_limit: WebSocketDataQueue,\n    parser_low_limit: PatchableWebSocketReader,\n) -> None:\n    large_payload = b\"b\" * (1 + 16 * 2)\n    large_payload_size = len(large_payload)\n    parser_low_limit._handle_frame(True, WSMsgType.BINARY, large_payload, 0)\n    res = out_low_limit._buffer[0]\n    assert res == WSMessageBinary(data=large_payload, size=large_payload_size, extra=\"\")\n    assert protocol._reading_paused is True\n\n\ndef test_flow_control_multi_byte_text(\n    protocol: BaseProtocol,\n    out_low_limit: WebSocketDataQueue,\n    parser_low_limit: PatchableWebSocketReader,\n) -> None:\n    large_payload_text = \"𒀁\" * (1 + 16 * 2)\n    large_payload = large_payload_text.encode(\"utf-8\")\n    large_payload_size = len(large_payload)\n    parser_low_limit._handle_frame(True, WSMsgType.TEXT, large_payload, 0)\n    res = out_low_limit._buffer[0]\n    assert res == WSMessageText(\n        data=large_payload_text, size=large_payload_size, extra=\"\"\n    )\n    assert protocol._reading_paused is True\n"
  },
  {
    "path": "tests/test_websocket_writer.py",
    "content": "import asyncio\nimport random\nfrom collections.abc import Callable\nfrom concurrent.futures import ThreadPoolExecutor\nfrom contextlib import suppress\nfrom unittest import mock\n\nimport pytest\n\nfrom aiohttp import WSMsgType\nfrom aiohttp._websocket.reader import WebSocketDataQueue\nfrom aiohttp.base_protocol import BaseProtocol\nfrom aiohttp.compression_utils import ZLibBackend\nfrom aiohttp.http import WebSocketReader, WebSocketWriter\n\n\n@pytest.fixture\ndef protocol() -> mock.Mock:\n    ret = mock.create_autospec(BaseProtocol, spec_set=True, instance=True)\n    return ret  # type: ignore[no-any-return]\n\n\n@pytest.fixture\ndef transport() -> mock.Mock:\n    ret = mock.Mock()\n    ret.is_closing.return_value = False\n    return ret\n\n\n@pytest.fixture\ndef writer(protocol: BaseProtocol, transport: asyncio.Transport) -> WebSocketWriter:\n    return WebSocketWriter(protocol, transport, use_mask=False)\n\n\nasync def test_pong(writer: WebSocketWriter) -> None:\n    await writer.send_frame(b\"\", WSMsgType.PONG)\n    writer.transport.write.assert_called_with(b\"\\x8a\\x00\")  # type: ignore[attr-defined]\n\n\nasync def test_ping(writer: WebSocketWriter) -> None:\n    await writer.send_frame(b\"\", WSMsgType.PING)\n    writer.transport.write.assert_called_with(b\"\\x89\\x00\")  # type: ignore[attr-defined]\n\n\nasync def test_send_text(writer: WebSocketWriter) -> None:\n    await writer.send_frame(b\"text\", WSMsgType.TEXT)\n    writer.transport.write.assert_called_with(b\"\\x81\\x04text\")  # type: ignore[attr-defined]\n\n\nasync def test_send_binary(writer: WebSocketWriter) -> None:\n    await writer.send_frame(b\"binary\", WSMsgType.BINARY)\n    writer.transport.write.assert_called_with(b\"\\x82\\x06binary\")  # type: ignore[attr-defined]\n\n\nasync def test_send_binary_long(writer: WebSocketWriter) -> None:\n    await writer.send_frame(b\"b\" * 127, WSMsgType.BINARY)\n    assert writer.transport.write.call_args[0][0].startswith(b\"\\x82~\\x00\\x7fb\")  # type: ignore[attr-defined]\n\n\nasync def test_send_binary_very_long(writer: WebSocketWriter) -> None:\n    await writer.send_frame(b\"b\" * 65537, WSMsgType.BINARY)\n    assert (\n        writer.transport.write.call_args_list[0][0][0]  # type: ignore[attr-defined]\n        == b\"\\x82\\x7f\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x01\"\n    )\n    assert writer.transport.write.call_args_list[1][0][0] == b\"b\" * 65537  # type: ignore[attr-defined]\n\n\nasync def test_close(writer: WebSocketWriter) -> None:\n    await writer.close(1001, \"msg\")\n    writer.transport.write.assert_called_with(b\"\\x88\\x05\\x03\\xe9msg\")  # type: ignore[attr-defined]\n\n    await writer.close(1001, b\"msg\")\n    writer.transport.write.assert_called_with(b\"\\x88\\x05\\x03\\xe9msg\")  # type: ignore[attr-defined]\n\n    # Test that Service Restart close code is also supported\n    await writer.close(1012, b\"msg\")\n    writer.transport.write.assert_called_with(b\"\\x88\\x05\\x03\\xf4msg\")  # type: ignore[attr-defined]\n\n\nasync def test_send_text_masked(\n    protocol: BaseProtocol, transport: asyncio.Transport\n) -> None:\n    writer = WebSocketWriter(\n        protocol, transport, use_mask=True, random=random.Random(123)\n    )\n    await writer.send_frame(b\"text\", WSMsgType.TEXT)\n    writer.transport.write.assert_called_with(b\"\\x81\\x84\\rg\\xb3fy\\x02\\xcb\\x12\")  # type: ignore[attr-defined]\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_send_compress_text(\n    protocol: BaseProtocol, transport: asyncio.Transport\n) -> None:\n    compress_obj = ZLibBackend.compressobj(level=ZLibBackend.Z_BEST_SPEED, wbits=-15)\n    writer = WebSocketWriter(protocol, transport, compress=15)\n\n    msg = (\n        compress_obj.compress(b\"text\") + compress_obj.flush(ZLibBackend.Z_SYNC_FLUSH)\n    ).removesuffix(b\"\\x00\\x00\\xff\\xff\")\n    await writer.send_frame(b\"text\", WSMsgType.TEXT)\n    writer.transport.write.assert_called_with(  # type: ignore[attr-defined]\n        b\"\\xc1\" + len(msg).to_bytes(1, \"big\") + msg\n    )\n\n    msg = (\n        compress_obj.compress(b\"text\") + compress_obj.flush(ZLibBackend.Z_SYNC_FLUSH)\n    ).removesuffix(b\"\\x00\\x00\\xff\\xff\")\n    await writer.send_frame(b\"text\", WSMsgType.TEXT)\n    writer.transport.write.assert_called_with(  # type: ignore[attr-defined]\n        b\"\\xc1\" + len(msg).to_bytes(1, \"big\") + msg\n    )\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_send_compress_text_notakeover(\n    protocol: BaseProtocol, transport: asyncio.Transport\n) -> None:\n    compress_obj = ZLibBackend.compressobj(level=ZLibBackend.Z_BEST_SPEED, wbits=-15)\n    writer = WebSocketWriter(protocol, transport, compress=15, notakeover=True)\n\n    msg = (\n        compress_obj.compress(b\"text\") + compress_obj.flush(ZLibBackend.Z_FULL_FLUSH)\n    ).removesuffix(b\"\\x00\\x00\\xff\\xff\")\n    await writer.send_frame(b\"text\", WSMsgType.TEXT)\n    writer.transport.write.assert_called_with(  # type: ignore[attr-defined]\n        b\"\\xc1\" + len(msg).to_bytes(1, \"big\") + msg\n    )\n    await writer.send_frame(b\"text\", WSMsgType.TEXT)\n    writer.transport.write.assert_called_with(  # type: ignore[attr-defined]\n        b\"\\xc1\" + len(msg).to_bytes(1, \"big\") + msg\n    )\n\n\nasync def test_send_compress_text_per_message(\n    protocol: BaseProtocol, transport: asyncio.Transport\n) -> None:\n    writer = WebSocketWriter(protocol, transport)\n    await writer.send_frame(b\"text\", WSMsgType.TEXT, compress=15)\n    writer.transport.write.assert_called_with(b\"\\xc1\\x06*I\\xad(\\x01\\x00\")  # type: ignore[attr-defined]\n    await writer.send_frame(b\"text\", WSMsgType.TEXT)\n    writer.transport.write.assert_called_with(b\"\\x81\\x04text\")  # type: ignore[attr-defined]\n    await writer.send_frame(b\"text\", WSMsgType.TEXT, compress=15)\n    writer.transport.write.assert_called_with(b\"\\xc1\\x06*I\\xad(\\x01\\x00\")  # type: ignore[attr-defined]\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_send_compress_cancelled(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    slow_executor: ThreadPoolExecutor,\n    monkeypatch: pytest.MonkeyPatch,\n) -> None:\n    \"\"\"Test that cancelled compression doesn't corrupt subsequent sends.\n\n    Regression test for https://github.com/aio-libs/aiohttp/issues/11725\n    \"\"\"\n    monkeypatch.setattr(\"aiohttp._websocket.writer.WEBSOCKET_MAX_SYNC_CHUNK_SIZE\", 1024)\n    writer = WebSocketWriter(protocol, transport, compress=15)\n    loop = asyncio.get_running_loop()\n    queue = WebSocketDataQueue(mock.Mock(_reading_paused=False), 2**16, loop=loop)\n    reader = WebSocketReader(queue, 50000)\n\n    # Replace executor with slow one to make race condition reproducible\n    writer._compressobj = writer._get_compressor(None)\n    writer._compressobj._executor = slow_executor\n\n    # Create large data that will trigger executor-based compression\n    large_data_1 = b\"A\" * 10000\n    large_data_2 = b\"B\" * 10000\n\n    # Start first send and cancel it during compression\n    async def send_and_cancel() -> None:\n        await writer.send_frame(large_data_1, WSMsgType.BINARY)\n\n    task = asyncio.create_task(send_and_cancel())\n    # Give it a moment to start compression\n    await asyncio.sleep(0.01)\n    task.cancel()\n\n    # Await task cancellation (expected and intentionally ignored)\n    with suppress(asyncio.CancelledError):\n        await task\n\n    # Send second message - this should NOT be corrupted\n    await writer.send_frame(large_data_2, WSMsgType.BINARY)\n\n    # Verify the second send produced correct data\n    last_call = writer.transport.write.call_args_list[-1]  # type: ignore[attr-defined]\n    call_bytes = last_call[0][0]\n    result, _ = reader.feed_data(call_bytes)\n    assert result is False\n    msg = await queue.read()\n    assert msg.type is WSMsgType.BINARY\n    # The data should be all B's, not mixed with A's from the cancelled send\n    assert msg.data == large_data_2\n\n\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_send_compress_multiple_cancelled(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    slow_executor: ThreadPoolExecutor,\n    monkeypatch: pytest.MonkeyPatch,\n) -> None:\n    \"\"\"Test that multiple compressed sends all complete despite cancellation.\n\n    Regression test for https://github.com/aio-libs/aiohttp/issues/11725\n    This verifies that once a send operation enters the shield, it completes\n    even if cancelled. With the lock inside the shield, all tasks that enter\n    the shield will complete their sends, even while waiting for the lock.\n    \"\"\"\n    monkeypatch.setattr(\"aiohttp._websocket.writer.WEBSOCKET_MAX_SYNC_CHUNK_SIZE\", 1024)\n    writer = WebSocketWriter(protocol, transport, compress=15)\n    loop = asyncio.get_running_loop()\n    queue = WebSocketDataQueue(mock.Mock(_reading_paused=False), 2**16, loop=loop)\n    reader = WebSocketReader(queue, 50000)\n\n    # Replace executor with slow one\n    writer._compressobj = writer._get_compressor(None)\n    writer._compressobj._executor = slow_executor\n\n    # Create 5 large messages with different content\n    messages = [bytes([ord(\"A\") + i]) * 10000 for i in range(5)]\n\n    # Start sending all 5 messages - they'll queue due to the lock\n    tasks = [\n        asyncio.create_task(writer.send_frame(msg, WSMsgType.BINARY))\n        for msg in messages\n    ]\n\n    # Cancel all tasks during execution\n    # With lock inside shield, all tasks that enter the shield will complete\n    # even while waiting for the lock\n    await asyncio.sleep(0.1)  # Let tasks enter the shield\n    for task in tasks:\n        task.cancel()\n\n    # Collect results\n    cancelled_count = 0\n    for task in tasks:\n        try:\n            await task\n        except asyncio.CancelledError:\n            cancelled_count += 1\n\n    # Wait for all background tasks to complete\n    # (they continue running even after cancellation due to shield)\n    await asyncio.gather(*writer._background_tasks, return_exceptions=True)\n\n    # All tasks that entered the shield should complete, even if cancelled\n    # With lock inside shield, all tasks enter shield immediately then wait for lock\n    sent_count = len(writer.transport.write.call_args_list)  # type: ignore[attr-defined]\n    assert (\n        sent_count == 5\n    ), \"All 5 sends should complete due to shield protecting lock acquisition\"\n\n    # Verify all sent messages are correct (no corruption)\n    for i in range(sent_count):\n        call = writer.transport.write.call_args_list[i]  # type: ignore[attr-defined]\n        call_bytes = call[0][0]\n        result, _ = reader.feed_data(call_bytes)\n        assert result is False\n        msg = await queue.read()\n        assert msg.type is WSMsgType.BINARY\n        # Verify the data matches the expected message\n        expected_byte = bytes([ord(\"A\") + i])\n        assert msg.data == expected_byte * 10000, f\"Message {i} corrupted\"\n\n\n@pytest.mark.parametrize(\n    (\"max_sync_chunk_size\", \"payload_point_generator\"),\n    (\n        (16, lambda count: count),\n        (4096, lambda count: count),\n        (32, lambda count: 64 + count if count % 2 else count),\n    ),\n)\n@pytest.mark.usefixtures(\"parametrize_zlib_backend\")\nasync def test_concurrent_messages(\n    protocol: BaseProtocol,\n    transport: asyncio.Transport,\n    max_sync_chunk_size: int,\n    payload_point_generator: Callable[[int], int],\n) -> None:\n    \"\"\"Ensure messages are compressed correctly when there are multiple concurrent writers.\n\n    This test generates is parametrized to\n\n    - Generate messages that are larger than patch\n      WEBSOCKET_MAX_SYNC_CHUNK_SIZE of 16\n      where compression will run in the executor\n\n    - Generate messages that are smaller than patch\n      WEBSOCKET_MAX_SYNC_CHUNK_SIZE of 4096\n      where compression will run in the event loop\n\n    - Interleave generated messages with a\n      WEBSOCKET_MAX_SYNC_CHUNK_SIZE of 32\n      where compression will run in the event loop\n      and in the executor\n    \"\"\"\n    with mock.patch(\n        \"aiohttp._websocket.writer.WEBSOCKET_MAX_SYNC_CHUNK_SIZE\", max_sync_chunk_size\n    ):\n        writer = WebSocketWriter(protocol, transport, compress=15)\n        loop = asyncio.get_running_loop()\n        queue = WebSocketDataQueue(mock.Mock(_reading_paused=False), 2**16, loop=loop)\n        reader = WebSocketReader(queue, 50000)\n        writers = []\n        payloads = []\n        for count in range(1, 64 + 1):\n            point = payload_point_generator(count)\n            payload = bytes((point,)) * point\n            payloads.append(payload)\n            writers.append(writer.send_frame(payload, WSMsgType.BINARY))\n        await asyncio.gather(*writers)\n\n    for call in writer.transport.write.call_args_list:  # type: ignore[attr-defined]\n        call_bytes = call[0][0]\n        result, _ = reader.feed_data(call_bytes)\n        assert result is False\n        msg = await queue.read()\n        assert msg.type is WSMsgType.BINARY\n        bytes_data = msg.data\n        first_char = bytes_data[0:1]\n        char_val = ord(first_char)\n        assert len(bytes_data) == char_val\n        # If we have a concurrency problem, the data\n        # tends to get mixed up between messages so\n        # we want to validate that all the bytes are\n        # the same value\n        assert bytes_data == bytes_data[0:1] * char_val\n\n    # Wait for any background tasks to complete\n    await asyncio.gather(*writer._background_tasks, return_exceptions=True)\n"
  },
  {
    "path": "tests/test_worker.py",
    "content": "# Tests for aiohttp/worker.py\nimport asyncio\nimport os\nimport socket\nimport ssl\nfrom typing import TYPE_CHECKING\nfrom unittest import mock\n\nimport pytest\nfrom _pytest.fixtures import SubRequest\n\nfrom aiohttp import web\n\nif TYPE_CHECKING:\n    from aiohttp import worker as base_worker\nelse:\n    base_worker = pytest.importorskip(\"aiohttp.worker\")\n\n\ntry:\n    import uvloop\nexcept ImportError:\n    uvloop = None  # type: ignore[assignment]\n\n\nWRONG_LOG_FORMAT = '%a \"%{Referrer}i\" %(h)s %(l)s %s'\nACCEPTABLE_LOG_FORMAT = '%a \"%{Referrer}i\" %s'\n\n\nclass BaseTestWorker:\n    def __init__(self) -> None:\n        self.servers: dict[object, object] = {}\n        self.exit_code = 0\n        self._notify_waiter: asyncio.Future[bool] | None = None\n        self.cfg = mock.Mock()\n        self.cfg.graceful_timeout = 100\n        self.pid = \"pid\"\n        self.wsgi = web.Application()\n\n\nclass AsyncioWorker(BaseTestWorker, base_worker.GunicornWebWorker):\n    pass\n\n\nPARAMS = [AsyncioWorker]\nif uvloop is not None:\n\n    class UvloopWorker(BaseTestWorker, base_worker.GunicornUVLoopWebWorker):\n        pass\n\n    PARAMS.append(UvloopWorker)\n\n\n@pytest.fixture(params=PARAMS)\ndef worker(\n    request: SubRequest, loop: asyncio.AbstractEventLoop\n) -> base_worker.GunicornWebWorker:\n    asyncio.set_event_loop(loop)\n    ret = request.param()\n    ret.notify = mock.Mock()\n    return ret  # type: ignore[no-any-return]\n\n\ndef test_init_process(worker: base_worker.GunicornWebWorker) -> None:\n    with mock.patch(\"aiohttp.worker.asyncio\") as m_asyncio:\n        try:\n            worker.init_process()\n        except TypeError:\n            pass\n\n        assert m_asyncio.new_event_loop.called\n        assert m_asyncio.set_event_loop.called\n\n\ndef test_run(\n    worker: base_worker.GunicornWebWorker, loop: asyncio.AbstractEventLoop\n) -> None:\n    worker.log = mock.Mock()\n    worker.cfg = mock.Mock()\n    worker.cfg.access_log_format = ACCEPTABLE_LOG_FORMAT\n    worker.cfg.is_ssl = False\n    worker.cfg.graceful_timeout = 100\n    worker.sockets = []\n\n    worker.loop = loop\n    with pytest.raises(SystemExit):\n        worker.run()\n    worker.log.exception.assert_not_called()\n    assert loop.is_closed()\n\n\ndef test_run_async_factory(\n    worker: base_worker.GunicornWebWorker, loop: asyncio.AbstractEventLoop\n) -> None:\n    worker.log = mock.Mock()\n    worker.cfg = mock.Mock()\n    worker.cfg.access_log_format = ACCEPTABLE_LOG_FORMAT\n    worker.cfg.is_ssl = False\n    worker.cfg.graceful_timeout = 100\n    worker.sockets = []\n    app = worker.wsgi\n\n    async def make_app() -> web.Application:\n        return app  # type: ignore[no-any-return]\n\n    worker.wsgi = make_app\n\n    worker.loop = loop\n    worker.alive = False\n    with pytest.raises(SystemExit):\n        worker.run()\n    worker.log.exception.assert_not_called()\n    assert loop.is_closed()\n\n\ndef test_run_not_app(\n    worker: base_worker.GunicornWebWorker, loop: asyncio.AbstractEventLoop\n) -> None:\n    worker.log = mock.Mock()\n    worker.cfg = mock.Mock()\n    worker.cfg.access_log_format = ACCEPTABLE_LOG_FORMAT\n\n    worker.loop = loop\n    worker.wsgi = \"not-app\"\n    worker.alive = False\n    with pytest.raises(SystemExit):\n        worker.run()\n    worker.log.exception.assert_called_with(\"Exception in gunicorn worker\")\n    assert loop.is_closed()\n\n\ndef test_handle_abort(worker: base_worker.GunicornWebWorker) -> None:\n    with mock.patch(\"aiohttp.worker.sys\") as m_sys:\n        worker.handle_abort(0, None)\n        assert not worker.alive\n        assert worker.exit_code == 1\n        m_sys.exit.assert_called_with(1)\n\n\ndef test__wait_next_notify(worker: base_worker.GunicornWebWorker) -> None:\n    worker.loop = mloop = mock.create_autospec(asyncio.AbstractEventLoop)\n    with mock.patch.object(worker, \"_notify_waiter_done\", autospec=True):\n        fut = worker._wait_next_notify()\n\n        assert worker._notify_waiter == fut\n        mloop.call_later.assert_called_with(1.0, worker._notify_waiter_done, fut)\n\n\ndef test__notify_waiter_done(worker: base_worker.GunicornWebWorker) -> None:\n    worker._notify_waiter = None\n    worker._notify_waiter_done()\n    assert worker._notify_waiter is None\n\n    waiter = worker._notify_waiter = mock.Mock()\n    worker._notify_waiter.done.return_value = False\n    worker._notify_waiter_done()\n\n    assert worker._notify_waiter is None\n    waiter.set_result.assert_called_with(True)  # type: ignore[unreachable]\n\n\ndef test__notify_waiter_done_explicit_waiter(\n    worker: base_worker.GunicornWebWorker,\n) -> None:\n    worker._notify_waiter = None\n    assert worker._notify_waiter is None\n\n    waiter = worker._notify_waiter = mock.Mock()\n    waiter.done.return_value = False\n    waiter2 = worker._notify_waiter = mock.Mock()\n    worker._notify_waiter_done(waiter)\n\n    assert worker._notify_waiter is waiter2\n    waiter.set_result.assert_called_with(True)\n    assert not waiter2.set_result.called\n\n\ndef test_init_signals(worker: base_worker.GunicornWebWorker) -> None:\n    worker.loop = mock.Mock()\n    worker.init_signals()\n    assert worker.loop.add_signal_handler.called\n\n\n@pytest.mark.parametrize(\n    \"source,result\",\n    [\n        (ACCEPTABLE_LOG_FORMAT, ACCEPTABLE_LOG_FORMAT),\n        (\n            AsyncioWorker.DEFAULT_GUNICORN_LOG_FORMAT,\n            AsyncioWorker.DEFAULT_AIOHTTP_LOG_FORMAT,\n        ),\n    ],\n)\ndef test__get_valid_log_format_ok(\n    worker: base_worker.GunicornWebWorker, source: str, result: str\n) -> None:\n    assert result == worker._get_valid_log_format(source)\n\n\ndef test__get_valid_log_format_exc(worker: base_worker.GunicornWebWorker) -> None:\n    with pytest.raises(ValueError) as exc:\n        worker._get_valid_log_format(WRONG_LOG_FORMAT)\n    assert \"%(name)s\" in str(exc.value)\n\n\nasync def test__run_ok_parent_changed(\n    worker: base_worker.GunicornWebWorker,\n    loop: asyncio.AbstractEventLoop,\n    unused_port_socket: socket.socket,\n) -> None:\n    worker.ppid = 0\n    worker.alive = True\n    sock = unused_port_socket\n    worker.sockets = [sock]\n    worker.log = mock.Mock()\n    worker.loop = loop\n    worker.max_requests = 0\n    worker.cfg.access_log_format = ACCEPTABLE_LOG_FORMAT\n    worker.cfg.is_ssl = False\n\n    await worker._run()\n\n    worker.notify.assert_called_with()\n    worker.log.info.assert_called_with(\"Parent changed, shutting down: %s\", worker)\n\n\nasync def test__run_exc(\n    worker: base_worker.GunicornWebWorker,\n    loop: asyncio.AbstractEventLoop,\n    unused_port_socket: socket.socket,\n) -> None:\n    worker.ppid = os.getppid()\n    worker.alive = True\n    sock = unused_port_socket\n    worker.sockets = [sock]\n    worker.log = mock.Mock()\n    worker.loop = loop\n    worker.max_requests = 0\n    worker.cfg.access_log_format = ACCEPTABLE_LOG_FORMAT\n    worker.cfg.is_ssl = False\n\n    def raiser() -> None:\n        waiter = worker._notify_waiter\n        worker.alive = False\n        assert waiter is not None\n        waiter.set_exception(RuntimeError())\n\n    loop.call_later(0.1, raiser)\n    await worker._run()\n\n    worker.notify.assert_called_with()\n\n\ndef test__create_ssl_context_without_certs_and_ciphers(\n    worker: base_worker.GunicornWebWorker,\n    tls_certificate_pem_path: str,\n) -> None:\n    worker.cfg.ssl_version = ssl.PROTOCOL_TLS_CLIENT\n    worker.cfg.cert_reqs = ssl.CERT_OPTIONAL\n    worker.cfg.certfile = tls_certificate_pem_path\n    worker.cfg.keyfile = tls_certificate_pem_path\n    worker.cfg.ca_certs = None\n    worker.cfg.ciphers = None\n    ctx = worker._create_ssl_context(worker.cfg)\n    assert isinstance(ctx, ssl.SSLContext)\n\n\ndef test__create_ssl_context_with_ciphers(\n    worker: base_worker.GunicornWebWorker,\n    tls_certificate_pem_path: str,\n) -> None:\n    worker.cfg.ssl_version = ssl.PROTOCOL_TLS_CLIENT\n    worker.cfg.cert_reqs = ssl.CERT_OPTIONAL\n    worker.cfg.certfile = tls_certificate_pem_path\n    worker.cfg.keyfile = tls_certificate_pem_path\n    worker.cfg.ca_certs = None\n    worker.cfg.ciphers = \"3DES PSK\"\n    ctx = worker._create_ssl_context(worker.cfg)\n    assert isinstance(ctx, ssl.SSLContext)\n\n\ndef test__create_ssl_context_with_ca_certs(\n    worker: base_worker.GunicornWebWorker,\n    tls_ca_certificate_pem_path: str,\n    tls_certificate_pem_path: str,\n) -> None:\n    worker.cfg.ssl_version = ssl.PROTOCOL_TLS_CLIENT\n    worker.cfg.cert_reqs = ssl.CERT_OPTIONAL\n    worker.cfg.certfile = tls_certificate_pem_path\n    worker.cfg.keyfile = tls_certificate_pem_path\n    worker.cfg.ca_certs = tls_ca_certificate_pem_path\n    worker.cfg.ciphers = None\n    ctx = worker._create_ssl_context(worker.cfg)\n    assert isinstance(ctx, ssl.SSLContext)\n"
  },
  {
    "path": "tools/bench-asyncio-write.py",
    "content": "import asyncio\nimport atexit\nimport math\nimport os\nimport signal\n\nPORT = 8888\n\nserver = os.fork()\nif server == 0:\n    loop = asyncio.get_event_loop()\n    coro = asyncio.start_server(lambda *_: None, port=PORT)\n    loop.run_until_complete(coro)\n    loop.run_forever()\nelse:\n    atexit.register(os.kill, server, signal.SIGTERM)\n\n\nasync def write_joined_bytearray(writer, chunks):\n    body = bytearray(chunks[0])\n    for c in chunks[1:]:\n        body += c\n    writer.write(body)\n\n\nasync def write_joined_list(writer, chunks):\n    body = b\"\".join(chunks)\n    writer.write(body)\n\n\nasync def write_separately(writer, chunks):\n    for c in chunks:\n        writer.write(c)\n\n\ndef fm_size(s, _fms=(\"\", \"K\", \"M\", \"G\")):\n    i = 0\n    while s >= 1024:\n        s /= 1024\n        i += 1\n    return f\"{s:.0f}{_fms[i]}B\"\n\n\ndef fm_time(s, _fms=(\"\", \"m\", \"µ\", \"n\")):\n    if s == 0:\n        return \"0\"\n    i = 0\n    while s < 1:\n        s *= 1000\n        i += 1\n    return f\"{s:.2f}{_fms[i]}s\"\n\n\ndef _job(j: list[int]) -> tuple[str, list[bytes]]:\n    # Always start with a 256B headers chunk\n    body = [b\"0\" * s for s in [256] + list(j)]\n    job_title = f\"{fm_size(sum(j))} / {len(j)}\"\n    return (job_title, body)\n\n\nwrites = [\n    (\"b''.join\", write_joined_list),\n    (\"bytearray\", write_joined_bytearray),\n    (\"multiple writes\", write_separately),\n]\n\nbodies = (\n    [],\n    [10 * 2**0],\n    [10 * 2**7],\n    [10 * 2**17],\n    [10 * 2**27],\n    [50 * 2**27],\n    [1 * 2**0 for _ in range(10)],\n    [1 * 2**7 for _ in range(10)],\n    [1 * 2**17 for _ in range(10)],\n    [1 * 2**27 for _ in range(10)],\n    [10 * 2**27 for _ in range(5)],\n)\n\n\njobs = [_job(j) for j in bodies]\n\n\nasync def time(loop, fn, *args):\n    spent = []\n    while not spent or sum(spent) < 0.2:\n        s = loop.time()\n        await fn(*args)\n        e = loop.time()\n        spent.append(e - s)\n    mean = sum(spent) / len(spent)\n    sd = sum((x - mean) ** 2 for x in spent) / len(spent)\n    return len(spent), mean, math.sqrt(sd)\n\n\nasync def main(loop):\n    _, writer = await asyncio.open_connection(port=PORT)\n    print(\"Loop:\", loop)\n    print(\"Transport:\", writer._transport)\n    res = [\n        (\"size/chunks\", \"Write option\", \"Mean\", \"Std dev\", \"loops\", \"Variation\"),\n    ]\n    res.append([\":---\", \":---\", \"---:\", \"---:\", \"---:\", \"---:\"])\n\n    async def bench(job_title, w, body, base=None):\n        it, mean, sd = await time(loop, w[1], writer, c)\n        res.append(\n            (\n                job_title,\n                w[0],\n                fm_time(mean),\n                fm_time(sd),\n                str(it),\n                f\"{mean / base - 1:.2%}\" if base is not None else \"\",\n            )\n        )\n        return mean\n\n    for t, c in jobs:\n        print(\"Doing\", t)\n        base = await bench(t, writes[0], c)\n        for w in writes[1:]:\n            await bench(\"\", w, c, base)\n    return res\n\n\nloop = asyncio.get_event_loop()\nresults = loop.run_until_complete(main(loop))\nwith open(\"bench.md\", \"w\") as f:\n    for line in results:\n        f.write(\"| {} |\\n\".format(\" | \".join(line)))\n"
  },
  {
    "path": "tools/check_changes.py",
    "content": "#!/usr/bin/env python3\n\nimport re\nimport sys\nfrom pathlib import Path\n\nALLOWED_SUFFIXES = (\n    \"bugfix\",\n    \"feature\",\n    \"deprecation\",\n    \"breaking\",\n    \"doc\",\n    \"packaging\",\n    \"contrib\",\n    \"misc\",\n)\nPATTERN = re.compile(\n    r\"(\\d+|[0-9a-f]{8}|[0-9a-f]{7}|[0-9a-f]{40})\\.(\"\n    + \"|\".join(ALLOWED_SUFFIXES)\n    + r\")(\\.\\d+)?(\\.rst)?\",\n)\n\n\ndef get_root(script_path):\n    folder = script_path.resolve().parent\n    while not (folder / \".git\").exists():\n        folder = folder.parent\n        if folder == folder.anchor:\n            raise RuntimeError(\"git repo not found\")\n    return folder\n\n\ndef main(argv):\n    print('Check \"CHANGES\" folder... ', end=\"\", flush=True)\n    here = Path(argv[0])\n    root = get_root(here)\n    changes = root / \"CHANGES\"\n    failed = False\n    for fname in changes.iterdir():\n        if fname.name in (\".gitignore\", \".TEMPLATE.rst\", \"README.rst\"):\n            continue\n        if not PATTERN.match(fname.name):\n            if not failed:\n                print(\"\")\n            print(\"Illegal CHANGES record\", fname, file=sys.stderr)\n            failed = True\n\n    if failed:\n        print(\"\", file=sys.stderr)\n        print(\"See ./CHANGES/README.rst for the naming instructions\", file=sys.stderr)\n        print(\"\", file=sys.stderr)\n    else:\n        print(\"OK\")\n\n    return int(failed)\n\n\nif __name__ == \"__main__\":\n    sys.exit(main(sys.argv))\n"
  },
  {
    "path": "tools/check_sum.py",
    "content": "#!/usr/bin/env python\n\nimport argparse\nimport hashlib\nimport pathlib\nimport sys\n\nPARSER = argparse.ArgumentParser(\n    description=\"Helper for check file hashes in Makefile instead of bare timestamps\"\n)\nPARSER.add_argument(\"dst\", metavar=\"DST\", type=pathlib.Path)\nPARSER.add_argument(\"-d\", \"--debug\", action=\"store_true\", default=False)\n\n\ndef main(argv):\n    args = PARSER.parse_args(argv)\n    dst = args.dst\n    assert dst.suffix == \".hash\"\n    dirname = dst.parent\n    if dirname.name != \".hash\":\n        if args.debug:\n            print(f\"Invalid name {dst} -> dirname {dirname}\", file=sys.stderr)\n        return 0\n    dirname.mkdir(exist_ok=True)\n    src_dir = dirname.parent\n    src_name = dst.stem  # drop .hash\n    full_src = src_dir / src_name\n    hasher = hashlib.sha256()\n    try:\n        hasher.update(full_src.read_bytes())\n    except OSError:\n        if args.debug:\n            print(f\"Cannot open {full_src}\", file=sys.stderr)\n        return 0\n    src_hash = hasher.hexdigest()\n    if dst.exists():\n        dst_hash = dst.read_text()\n    else:\n        dst_hash = \"\"\n    if src_hash != dst_hash:\n        dst.write_text(src_hash)\n        print(f\"re-hash {src_hash}\")\n    else:\n        if args.debug:\n            print(f\"Skip {src_hash} checksum, up-to-date\")\n    return 0\n\n\nif __name__ == \"__main__\":\n    sys.exit(main(sys.argv[1:]))\n"
  },
  {
    "path": "tools/cleanup_changes.py",
    "content": "#!/usr/bin/env python\n\n# Run me after the backport branch release to cleanup CHANGES records\n# that was backported and published.\n\nimport re\nimport subprocess\nfrom pathlib import Path\n\nALLOWED_SUFFIXES = (\n    \"bugfix\",\n    \"feature\",\n    \"deprecation\",\n    \"breaking\",\n    \"doc\",\n    \"packaging\",\n    \"contrib\",\n    \"misc\",\n)\nPATTERN = re.compile(\n    r\"(\\d+|[0-9a-f]{8}|[0-9a-f]{7}|[0-9a-f]{40})\\.(\"\n    + \"|\".join(ALLOWED_SUFFIXES)\n    + r\")(\\.\\d+)?(\\.rst)?\",\n)\n\n\ndef main():\n    root = Path(__file__).parent.parent\n    delete = []\n    changes = (root / \"CHANGES.rst\").read_text()\n    for fname in (root / \"CHANGES\").iterdir():\n        match = PATTERN.match(fname.name)\n        if match is not None:\n            commit_issue_or_pr = match.group(1)\n            tst_issue_or_pr = f\":issue:`{commit_issue_or_pr}`\"\n            tst_commit = f\":commit:`{commit_issue_or_pr}`\"\n            if tst_issue_or_pr in changes or tst_commit in changes:\n                subprocess.run([\"git\", \"rm\", fname])\n                delete.append(fname.name)\n    print(\"Deleted CHANGES records:\", \" \".join(delete))\n    print(\"Please verify and commit\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "tools/drop_merged_branches.sh",
    "content": "#!/usr/bin/env bash\n\ngit remote prune origin\n"
  },
  {
    "path": "tools/gen.py",
    "content": "#!/usr/bin/env python\n\nimport io\nimport pathlib\nfrom collections import defaultdict\n\nimport multidict\n\nROOT = pathlib.Path.cwd()\nwhile ROOT.parent != ROOT and not (ROOT / \"pyproject.toml\").exists():\n    ROOT = ROOT.parent\n\n\ndef calc_headers(root):\n    hdrs_file = root / \"aiohttp/hdrs.py\"\n    code = compile(hdrs_file.read_text(), str(hdrs_file), \"exec\")\n    globs = {}\n    exec(code, globs)\n    headers = [val for val in globs.values() if isinstance(val, multidict.istr)]\n    return sorted(headers)\n\n\nheaders = calc_headers(ROOT)\n\n\ndef factory():\n    return defaultdict(factory)\n\n\nTERMINAL = object()\n\n\ndef build(headers):\n    dct = defaultdict(factory)\n    for hdr in headers:\n        d = dct\n        for ch in hdr:\n            d = d[ch]\n        d[TERMINAL] = hdr\n    return dct\n\n\ndct = build(headers)\n\n\nHEADER = \"\"\"\\\n/*  The file is autogenerated from aiohttp/hdrs.py\nRun ./tools/gen.py to update it after the origin changing. */\n\n#include \"_find_header.h\"\n\n#define NEXT_CHAR() \\\\\n{ \\\\\n    count++; \\\\\n    if (count == size) { \\\\\n        /* end of search */ \\\\\n        return -1; \\\\\n    } \\\\\n    pchar++; \\\\\n    ch = *pchar; \\\\\n    last = (count == size -1); \\\\\n} while(0);\n\nint\nfind_header(const char *str, int size)\n{\n    char *pchar = str;\n    int last;\n    char ch;\n    int count = -1;\n    pchar--;\n\"\"\"\n\nBLOCK = \"\"\"\n{label}\n    NEXT_CHAR();\n    switch (ch) {{\n{cases}\n        default:\n            return -1;\n    }}\n\"\"\"\n\nCASE = \"\"\"\\\n        case '{char}':\n            if (last) {{\n                return {index};\n            }}\n            goto {next};\"\"\"\n\nFOOTER = \"\"\"\n{missing}\nmissing:\n    /* nothing found */\n    return -1;\n}}\n\"\"\"\n\n\ndef gen_prefix(prefix, k):\n    if k == \"-\":\n        return prefix + \"_\"\n    else:\n        return prefix + k.upper()\n\n\ndef gen_block(dct, prefix, used_blocks, missing, out):\n    cases = {}\n    for k, v in dct.items():\n        if k is TERMINAL:\n            continue\n        next_prefix = gen_prefix(prefix, k)\n        term = v.get(TERMINAL)\n        if term is not None:\n            index = headers.index(term)\n        else:\n            index = -1\n        hi = k.upper()\n        lo = k.lower()\n        case = CASE.format(char=hi, index=index, next=next_prefix)\n        cases[hi] = case\n        if lo != hi:\n            case = CASE.format(char=lo, index=index, next=next_prefix)\n            cases[lo] = case\n    label = prefix + \":\" if prefix else \"\"\n    if cases:\n        block = BLOCK.format(label=label, cases=\"\\n\".join(cases.values()))\n        out.write(block)\n    else:\n        missing.add(label)\n    for k, v in dct.items():\n        if not isinstance(v, defaultdict):\n            continue\n        block_name = gen_prefix(prefix, k)\n        if block_name in used_blocks:\n            continue\n        used_blocks.add(block_name)\n        gen_block(v, block_name, used_blocks, missing, out)\n\n\ndef gen(dct):\n    out = io.StringIO()\n    out.write(HEADER)\n    missing = set()\n    gen_block(dct, \"\", set(), missing, out)\n    missing_labels = \"\\n\".join(sorted(missing))\n    out.write(FOOTER.format(missing=missing_labels))\n    return out\n\n\ndef gen_headers(headers):\n    out = io.StringIO()\n    out.write(\"# The file is autogenerated from aiohttp/hdrs.py\\n\")\n    out.write(\"# Run ./tools/gen.py to update it after the origin changing.\")\n    out.write(\"\\n\\n\")\n    out.write(\"from . import hdrs\\n\")\n    out.write(\"cdef tuple headers = (\\n\")\n    for hdr in headers:\n        out.write(\"    hdrs.{},\\n\".format(hdr.upper().replace(\"-\", \"_\")))\n    out.write(\")\\n\")\n    return out\n\n\n# print(gen(dct).getvalue())\n# print(gen_headers(headers).getvalue())\n\n\nfolder = ROOT / \"aiohttp\"\n\nwith (folder / \"_find_header.c\").open(\"w\") as f:\n    f.write(gen(dct).getvalue())\n\nwith (folder / \"_headers.pxi\").open(\"w\") as f:\n    f.write(gen_headers(headers).getvalue())\n"
  },
  {
    "path": "tools/testing/Dockerfile",
    "content": "ARG PYTHON_VERSION\nFROM python:$PYTHON_VERSION\n\nARG AIOHTTP_NO_EXTENSIONS\nENV AIOHTTP_NO_EXTENSIONS=$AIOHTTP_NO_EXTENSIONS\n\nWORKDIR /deps\nADD ./requirements ./requirements\nADD Makefile .\nRUN make install\n\nADD ./tools/testing/entrypoint.sh /\n\nWORKDIR /src\nENTRYPOINT [\"/bin/bash\", \"/entrypoint.sh\"]\n"
  },
  {
    "path": "tools/testing/Dockerfile.dockerignore",
    "content": "*\n!/requirements\n!/Makefile\n!/tools/testing/entrypoint.sh\n"
  },
  {
    "path": "tools/testing/entrypoint.sh",
    "content": "#!/bin/bash\n\n[[ \"$AIOHTTP_NO_EXTENSIONS\" != \"y\" ]] && make cythonize\n\npython -m pytest -qx --no-cov $1\n"
  },
  {
    "path": "vendor/README.rst",
    "content": "LLHTTP\n------\n\nWhen building aiohttp from source, there is a pure Python parser used by default.\nFor better performance, you may want to build the higher performance C parser.\n\nTo build this ``llhttp`` parser, first get/update the submodules (to update to a\nnewer release, add ``--remote``)::\n\n    git submodule update --init --recursive\n\nThen build ``llhttp``::\n\n    cd vendor/llhttp/\n    npm ci\n    make\n\nThen build our parser::\n\n    cd -\n    make cythonize\n\nThen you can build or install it with ``python -m build`` or ``pip install -e .``\n"
  }
]