[
  {
    "path": ".coveragerc",
    "content": "# Test coverage configuration.\n# Usage:\n#   pip install coverage\n#   coverage erase  # clears previous data if any\n#   coverage run -m tornado.test.runtests\n#   coverage report  # prints to stdout\n#   coverage html  # creates ./htmlcov/*.html including annotated source\n[run]\nbranch = true\nsource = tornado\nomit = \n    tornado/platform/*\n    tornado/test/*\n    */_auto2to3*\n\n[report]\n# Ignore missing source files, i.e. fake template-generated \"files\"\nignore_errors = true\n"
  },
  {
    "path": ".flake8",
    "content": "[flake8]\nexclude = .git,.tox,__pycache__,.eggs,build\nmax-line-length = 100\nignore =\n    # E265 block comment should start with '# '\n    E265,\n    # E266 too many leading '#' for block comment\n    E266,\n    # E402 module level import not at top of file\n    E402,\n    # E722 do not use bare except\n    E722,\n    # flake8 and black disagree about\n    # W503 line break before binary operator\n    # E203 whitespace before ':'\n    # E701/E704 multiple statements on one line\n    # https://black.readthedocs.io/en/latest/guides/using_black_with_other_tools.html#labels-why-pycodestyle-warnings\n    W503,E203,E701,E704\ndoctests = true\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Tests of static file handling assume unix-style line endings.\ntornado/test/static/*.txt text eol=lf\ntornado/test/static/dir/*.html text eol=lf\ntornado/test/templates/*.html text eol=lf\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "# The \"build\" workflow produces wheels (and the sdist) for all python\n# versions/platforms. Where possible (i.e. the build is not a cross-compile),\n# the test suite is also run for the wheel (this test covers fewer\n# configurations than the \"test\" workflow and tox.ini).\nname: Build\n\non:\n  push:\n    branches:\n      # Run on release branches. This gives us a chance to detect rot in this\n      # configuration before pushing a tag (which we'd rather not have to undo).\n      - \"branch[0-9]*\"\n    tags:\n      # The main purpose of this workflow is to build wheels for release tags.\n      # It runs automatically on tags matching this pattern and pushes to pypi.\n      - \"v*\"\n  workflow_dispatch:\n    # Allow this workflow to be run manually (pushing to testpypi instead of pypi)\n\npermissions: {}\n\nenv:\n  # [[[cog cog.outl(f\"python-version: '3.{default_python_minor}'\")]]]\n  python-version: '3.13'\n  # [[[end]]]\n\njobs:\n  build_sdist:\n    name: Build sdist\n    runs-on: ubuntu-24.04\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n      - uses: actions/setup-python@v5\n        name: Install Python\n        with:\n          python-version: ${{ env.python-version }}\n\n      - name: Install dependencies\n        run: python -m pip install setuptools\n      - name: Check metadata\n        run: \"python setup.py check\"\n      - name: Build sdist\n        run: \"python setup.py sdist && ls -l dist\"\n\n      - uses: actions/upload-artifact@v4\n        with:\n          name: artifacts-sdist\n          path: ./dist/tornado-*.tar.gz\n\n  build_wheels:\n    name: Build wheels on ${{ matrix.os }}${{ matrix.name_suffix || '' }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-24.04, ubuntu-24.04-arm, windows-2025, windows-11-arm, macos-15]\n        arch: [auto]\n        include:\n          - os: ubuntu-24.04\n            arch: \"riscv64\"\n            name_suffix: \"-riscv64\"\n\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n      - uses: actions/setup-python@v5\n        name: Install Python\n        with:\n          python-version: ${{ env.python-version }}\n\n      - name: Set up QEMU\n        if: matrix.arch != 'auto'\n        uses: docker/setup-qemu-action@v3\n        with:\n          platforms: ${{ matrix.arch }}\n\n      - name: Build wheels\n        uses: pypa/cibuildwheel@v3.4.0\n        env:\n          CIBW_ARCHS: ${{ matrix.arch }}\n          # Increase timeouts for emulated archs\n          CIBW_ENVIRONMENT: ${{ matrix.arch != 'auto' && 'ASYNC_TEST_TIMEOUT=30 EMULATION=1' || '' }}\n\n      - name: Audit ABI3 compliance\n        # This may be moved into cibuildwheel itself in the future. See\n        # https://github.com/pypa/cibuildwheel/issues/1342\n        run: python -m pip install abi3audit && abi3audit --verbose --summary ./wheelhouse/*abi3*.whl\n\n      - uses: actions/upload-artifact@v4\n        with:\n          name: artifacts-${{ matrix.os }}${{ matrix.name_suffix || '' }}\n          path: ./wheelhouse/*.whl\n\n  upload_pypi_test:\n    name: Upload to PyPI (test)\n    needs: [build_wheels, build_sdist]\n    runs-on: ubuntu-24.04\n    if: github.repository == 'tornadoweb/tornado' && github.event_name == 'workflow_dispatch'\n    permissions:\n      # This permission is required for pypi's \"trusted publisher\" feature\n      id-token: write\n    steps:\n      - uses: actions/download-artifact@v4\n        with:\n          pattern: artifacts-*\n          path: dist\n          merge-multiple: true\n\n      - uses: pypa/gh-action-pypi-publish@release/v1\n        with:\n          repository-url: https://test.pypi.org/legacy/\n          skip-existing: true\n\n  upload_pypi:\n    name: Upload to PyPI (prod)\n    needs: [build_wheels, build_sdist]\n    runs-on: ubuntu-24.04\n    if: github.repository == 'tornadoweb/tornado' && github.event_name == 'push' && github.ref_type == 'tag' && startsWith(github.ref_name, 'v')\n    permissions:\n      # This permission is required for pypi's \"trusted publisher\" feature\n      id-token: write\n    steps:\n      - uses: actions/download-artifact@v4\n        with:\n          pattern: artifacts-*\n          path: dist\n          merge-multiple: true\n\n      - uses: pypa/gh-action-pypi-publish@release/v1\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "# The \"test\" workflow is run on every PR and runs tests across all\n# supported python versions and a range of configurations\n# specified in tox.ini. Also see the \"build\" workflow which is only\n# run for release branches and covers platforms other than linux-amd64\n# (Platform-specific issues are rare these days so we don't want to\n# take that time on every build).\n\nname: Test\n\non: pull_request\n\npermissions: {}\n\njobs:\n  # Before starting the full build matrix, run one test configuration\n  # and the linter (the `black` linter is especially likely to catch\n  # first-time contributors).\n  test_quick:\n    name: Run quick tests\n    runs-on: ubuntu-24.04\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n      - uses: actions/setup-python@v5\n        name: Install Python\n        with:\n          # [[[cog cog.outl(f\"python-version: '3.{default_python_minor}'\")]]]\n          python-version: '3.13'\n          # [[[end]]]\n      - name: Install tox\n        run: python -m pip install tox -c requirements.txt\n\n      - name: Run test suite\n        # [[[cog cog.outl(f\"run: python -m tox -e py3{default_python_minor},lint\")]]]\n        run: python -m tox -e py313,lint\n        # [[[end]]]\n\n  test_tox:\n    name: Run full tests\n    needs: test_quick\n    runs-on: ubuntu-24.04\n    strategy:\n      matrix:\n        include:\n          # [[[cog\n          #    configs  = []\n          #    for minor in range(int(min_python_minor), int(max_python_minor) + 1):\n          #      if minor == int(dev_python_minor):\n          #          # For python versions in development, specify a range so we install\n          #          # a final version if available, otherwise a prerelease.\n          #          # The .0 is necessary here for unclear reasons.\n          #          configs.append((f\"3.{minor}.0-dev - 3.{minor}\", f\"py3{minor}\"))\n          #      else:\n          #          configs.append((f\"3.{minor}\", f\"py3{minor}-full\"))\n          #      if int(min_python_threaded_minor) <= minor <= int(max_python_threaded_minor):\n          #          if minor == int(dev_python_minor):\n          #              # The range trick above doesn't work for threaded builds, so we'll\n          #              # just be stuck with last prerelease until we update dev_python_minor.\n          #              configs.append((f\"3.{minor}t-dev\", f\"py3{minor}\"))\n          #          else:\n          #              configs.append((f\"3.{minor}t\", f\"py3{minor}\"))\n          #    # Early versions of 3.10 and 3.11 had different deprecation\n          #    # warnings in asyncio. Test with them too to make sure everything\n          #    # works the same way.\n          #    configs.append((\"3.10.8\", \"py310-full\"))\n          #    configs.append((\"3.11.0\", \"py311-full\"))\n          #    # Pypy is a lot slower due to jit warmup costs, so don't run the\n          #    # \"full\" test config there.\n          #    configs.append((\"pypy-3.10\", \"pypy3\"))\n          #    # Docs python version must be synced with tox.ini\n          #    configs.append((f\"3.{default_python_minor}\", \"docs\"))\n          #    for version, tox_env in configs:\n          #      cog.outl(f\"  - python: '{version}'\")\n          #      cog.outl(f\"    tox_env: {tox_env}\")\n          #    ]]]\n          - python: '3.10'\n            tox_env: py310-full\n          - python: '3.11'\n            tox_env: py311-full\n          - python: '3.12'\n            tox_env: py312-full\n          - python: '3.13'\n            tox_env: py313-full\n          - python: '3.14'\n            tox_env: py314\n          - python: '3.14t-dev'\n            tox_env: py314\n          - python: '3.15-dev'\n            tox_env: py315\n          - python: '3.10.8'\n            tox_env: py310-full\n          - python: '3.11.0'\n            tox_env: py311-full\n          - python: 'pypy-3.10'\n            tox_env: pypy3\n          - python: '3.13'\n            tox_env: docs\n          # [[[end]]]\n\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n      - uses: actions/setup-python@v5\n        name: Install Python\n        with:\n          python-version: ${{ matrix.python}}\n      - name: Install apt packages\n        run: sudo apt-get update && sudo apt-get install libcurl4-openssl-dev\n      - name: Install tox\n        run: python -m pip install tox -c requirements.txt\n\n      - name: Run test suite\n        run: python -m tox -e ${{ matrix.tox_env }}\n\n  test_win:\n    # Windows tests are fairly slow, so only run one configuration here.\n    # We test on windows but not mac because even though mac is a more\n    # fully-supported platform, it's similar enough to linux that we\n    # don't generally need to test it separately. Windows is different\n    # enough that we'll break it if we don't test it in CI.\n    name: Run windows tests\n    needs: test_quick\n    runs-on: windows-2025\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n      - uses: actions/setup-python@v5\n        name: Install Python\n        with:\n          # [[[cog cog.outl(f\"python-version: '3.{default_python_minor}'\")]]]\n          python-version: '3.13'\n          # [[[end]]]\n      - name: Run test suite\n        # TODO: figure out what's up with these log messages\n        run: py -m tornado.test --fail-if-logs=false\n\n  zizmor:\n    name: Analyze action configs with zizmor\n    runs-on: ubuntu-24.04\n    needs: test_quick\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n      - uses: astral-sh/setup-uv@v6\n        name: Install uv\n      - name: Run zizmor\n        run: uvx zizmor .github/workflows\n\n  test_cibw:\n    # cibuildwheel is the tool that we use for release builds in build.yml.\n    # Run it in the every-PR workflow because it's slightly different from our\n    # regular build and this gives us easier ways to test freethreading changes.\n    #\n    # Note that test_cibw and test_tox both take about a minute to run, but test_tox runs\n    # more tests; test_cibw spends a lot of its time installing dependencies. Replacing\n    # test_tox with test_cibw would entail either increasing test runtime or reducing\n    # test coverage.\n    name: Test with cibuildwheel\n    runs-on: ubuntu-24.04\n    needs: test_quick\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n      - name: Run cibuildwheel\n        uses: pypa/cibuildwheel@v2.23\n        env:\n          # For speed, we only build one python version and one arch. We throw away the wheels\n          # built here; the real build is defined in build.yml.\n          CIBW_ARCHS: native\n          # [[[cog cog.outl(f\"CIBW_BUILD: cp3{default_python_minor}-manylinux*\")]]]\n          CIBW_BUILD: cp313-manylinux*\n          # [[[end]]]\n"
  },
  {
    "path": ".github/zizmor.yml",
    "content": "rules:\n  unpinned-uses:\n    config:\n      policies:\n        # Allow trusted repositories to use ref-pinning instead of hash-pinning.\n        #\n        # Defaults, from \n        # https://github.com/woodruffw/zizmor/blob/7b4e76e94be2f4d7b455664ba5252b2b4458b91d/src/audit/unpinned_uses.rs#L172-L193\n        actions/*: ref-pin\n        github/*: ref-pin\n        dependabot/*: ref-pin\n        # Additional trusted repositories\n        pypa/*: ref-pin\n        astral-sh/setup-uv: ref-pin\n        docker/setup-qemu-action: ref-pin\n"
  },
  {
    "path": ".gitignore",
    "content": "*.pyc\n*.pyo\n*.so\n*.class\n*~\nbuild/\n/dist/\nMANIFEST\n/tornado.egg-info/\n.tox/\n.vagrant\n/.coverage\n/htmlcov/\n/env/\n# Used in demo apps\nsecrets.cfg\n.mypy_cache/\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "version: 2\n\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.11\"\n\nsphinx:\n  configuration: docs/conf.py\n\nformats:\n  - pdf\n  - epub\n\npython:\n  install:\n    - requirements: requirements.txt\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Tornado\n\n## The basics\n\n* We use `black` as code formatter and recommend configuring your editor to run this automatically\n  (using the version specified in `requirements.txt`). Commits that are not properly formatted\n  by `black` will be rejected in CI.\n* Before committing, it is recommended to run `tox -e lint,docs,py3`. This will verify that the\n  code is formatted correctly, type checking with `mypy` passes, the `sphinx` build for docs has\n  no errors, and the main test suite passes with the current version of python.\n* Nearly all code changes should have new or updated tests covering the changed behavior.\n  Red/green TDD is encouraged.\n\n## Testing\n\n* We use `tox` as a test runner to run tests in various configurations with the correct\n  dependencies. `tox -e py3` runs most of the tests, while `tox -e py3-full` can be used\n  to run a more extensive version of the test suite which as extra dependencies. The\n  `-full` configurations are necessary when working on certain modules, including\n  `curl_httpclient.py`, `twisted.py`, or `pycares.py`.\n* The fastest way to run the tests is to bypass `tox` and run `python3 -m tornado.test`.\n  To run a subset of tests, add a module, class, or method name to the command line:\n  `python3 -m tornado.test.httputil_test`.\n* Tests can also be run with the standard library's `unittest` package CLI. This is useful\n  for integration with some editors.\n* Tornado does not use `pytest`. Some effort has been made to make the tests work with \n  the `pytest` runner, but this is not maintained.\n\n## Documentation\n\nWe use Sphinx with the `autodoc` extension to build our docs. To build the docs run\n`tox -e docs` and find the output in `./.tox/docs/tmp/html/index.html`\n\n## AI policy\n\nTornado has a neutral stance towards AI-generated code. All pull requests, whether human\nor machine-generated, are subject to strict code review standards. However, PRs that appear\nto be AI-generated *and* contain clear flaws (such as failing CI) may be closed without\ndetailed review. "
  },
  {
    "path": "LICENSE",
    "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": "MANIFEST.in",
    "content": "recursive-include docs *\nprune docs/build\ninclude tornado/py.typed\ninclude tornado/speedups.c\ninclude tornado/test/README\ninclude tornado/test/csv_translations/fr_FR.csv\ninclude tornado/test/gettext_translations/fr_FR/LC_MESSAGES/tornado_test.mo\ninclude tornado/test/gettext_translations/fr_FR/LC_MESSAGES/tornado_test.po\ninclude tornado/test/options_test.cfg\ninclude tornado/test/static/robots.txt\ninclude tornado/test/static/sample.xml\ninclude tornado/test/static/sample.xml.gz\ninclude tornado/test/static/sample.xml.bz2\ninclude tornado/test/static/dir/index.html\ninclude tornado/test/static_foo.txt\ninclude tornado/test/templates/utf8.html\ninclude tornado/test/test.crt\ninclude tornado/test/test.key\ninclude LICENSE\ninclude README.rst\ninclude requirements.in\ninclude requirements.txt\ninclude runtests.sh\ninclude tox.ini\n"
  },
  {
    "path": "README.rst",
    "content": "Tornado Web Server\n==================\n\n.. image:: https://badges.gitter.im/Join%20Chat.svg\n   :alt: Join the chat at https://gitter.im/tornadoweb/tornado\n   :target: https://gitter.im/tornadoweb/tornado?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge\n\n`Tornado <http://www.tornadoweb.org>`_ is a Python web framework and\nasynchronous networking library, originally developed at `FriendFeed\n<http://friendfeed.com>`_.  By using non-blocking network I/O, Tornado\ncan scale to tens of thousands of open connections, making it ideal for\n`long polling <http://en.wikipedia.org/wiki/Push_technology#Long_Polling>`_,\n`WebSockets <http://en.wikipedia.org/wiki/WebSocket>`_, and other\napplications that require a long-lived connection to each user.\n\nHello, world\n------------\n\nHere is a simple \"Hello, world\" example web app for Tornado:\n\n.. code-block:: python\n\n    import asyncio\n    import tornado\n\n    class MainHandler(tornado.web.RequestHandler):\n        def get(self):\n            self.write(\"Hello, world\")\n\n    def make_app():\n        return tornado.web.Application([\n            (r\"/\", MainHandler),\n        ])\n\n    async def main():\n        app = make_app()\n        app.listen(8888)\n        await asyncio.Event().wait()\n\n    if __name__ == \"__main__\":\n        asyncio.run(main())\n\nThis example does not use any of Tornado's asynchronous features; for\nthat see this `simple chat room\n<https://github.com/tornadoweb/tornado/tree/stable/demos/chat>`_.\n\nDocumentation\n-------------\n\nDocumentation and links to additional resources are available at\nhttps://www.tornadoweb.org\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nIn general, due to limited maintainer bandwidth, only the latest version of\nTornado is supported with patch releases. Exceptions may be made depending\non the severity of the bug and the feasibility of backporting a fix to\nolder releases. \n\n## Reporting a Vulnerability\n\nTornado uses GitHub's security advisory functionality for private vulnerability\nreports. To make a private report, use the \"Report a vulnerability\" button on\nhttps://github.com/tornadoweb/tornado/security/advisories\n"
  },
  {
    "path": "codecov.yml",
    "content": "comment: off\ncoverage:\n  status: off\n"
  },
  {
    "path": "demos/README.rst",
    "content": "Tornado Demo Apps\n-----------------\n\nThis directory contains several example apps that illustrate the usage of\nvarious Tornado features. If you're not sure where to start, try the ``chat``,\n``blog``, or ``websocket`` demos.\n\nWeb Applications\n~~~~~~~~~~~~~~~~\n\n- ``blog``: A simple database-backed blogging platform, including\n  HTML templates and authentication.\n- ``chat``: A chat room demonstrating live updates via long polling.\n- ``websocket``: Similar to ``chat`` but with WebSockets instead of\n  long polling.\n- ``helloworld``: The simplest possible Tornado web page.\n\nFeature demos\n~~~~~~~~~~~~~\n\n- ``facebook``: Authentication with the Facebook Graph API.\n- ``file_upload``: Client and server support for streaming HTTP request \n  payloads.\n- ``tcpecho``: Using the lower-level ``IOStream`` interfaces for non-HTTP\n  networking.\n- ``webspider``: Concurrent usage of ``AsyncHTTPClient``, using queues and\n  semaphores.\n\n"
  },
  {
    "path": "demos/blog/Dockerfile",
    "content": "FROM python:3.7\n\nEXPOSE 8888\n\nRUN mkdir -p /usr/src/app\nWORKDIR /usr/src/app\n\nCOPY requirements.txt /usr/src/app/\nRUN pip install --no-cache-dir -r requirements.txt\n\nCOPY . .\n\nENTRYPOINT [\"python3\", \"blog.py\"]\n"
  },
  {
    "path": "demos/blog/README",
    "content": "Running the Tornado Blog example app\n====================================\n\nThis demo is a simple blogging engine that uses a database to store posts.\nYou must have PostgreSQL or CockroachDB installed to run this demo.\n\nIf you have `docker` and `docker-compose` installed, the demo and all\nits prerequisites can be installed with `docker-compose up`.\n\n1. Install a database if needed\n\n   Consult the documentation at either https://www.postgresql.org or\n   https://www.cockroachlabs.com to install one of these databases for\n   your platform.\n\n2. Install Python prerequisites\n\n   This demo requires Python 3.6 or newer, and the packages listed in\n   requirements.txt. Install them with `pip -r requirements.txt`\n\n3. Create a database and user for the blog.\n\n   Connect to the database with `psql -U postgres` (for PostgreSQL) or\n   `cockroach sql` (for CockroachDB).\n\n   Create a database and user, and grant permissions:\n\n   CREATE DATABASE blog;\n   CREATE USER blog WITH PASSWORD 'blog';\n   GRANT ALL ON DATABASE blog TO blog;\n\n   (If using CockroachDB in insecure mode, omit the `WITH PASSWORD 'blog'`)\n\n4. Create the tables in your new database (optional):\n\n   The blog application will create its tables automatically when starting up.\n   It's also possible to create them separately.\n\n   You can use the provided schema.sql file by running this command for PostgreSQL:\n\n   psql -U blog -d blog < schema.sql\n\n   Or this one for CockcroachDB:\n\n   cockroach sql -u blog -d blog < schema.sql\n\n   You can run the above command again later if you want to delete the\n   contents of the blog and start over after testing.\n\n5. Run the blog example\n\n   For PostgreSQL, you can just run\n   ./blog.py\n\n   For CockroachDB, run\n   ./blog.py --db_port=26257\n\n   If you've changed anything from the defaults, use the other `--db_*` flags.\n\n6. Visit your new blog\n\n   Open http://localhost:8888/ in your web browser.\n\n   Currently the first user to connect will automatically be given the\n   ability to create and edit posts.\n\n   Once you've created one blog post, subsequent users will not be\n   prompted to sign in.\n"
  },
  {
    "path": "demos/blog/blog.py",
    "content": "#!/usr/bin/env python3\n#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\nimport aiopg\nimport asyncio\nimport bcrypt\nimport markdown\nimport os.path\nimport psycopg2\nimport re\nimport tornado\nimport unicodedata\n\nfrom tornado.options import define, options\n\ndefine(\"port\", default=8888, help=\"run on the given port\", type=int)\ndefine(\"db_host\", default=\"127.0.0.1\", help=\"blog database host\")\ndefine(\"db_port\", default=5432, help=\"blog database port\")\ndefine(\"db_database\", default=\"blog\", help=\"blog database name\")\ndefine(\"db_user\", default=\"blog\", help=\"blog database user\")\ndefine(\"db_password\", default=\"blog\", help=\"blog database password\")\n\n\nclass NoResultError(Exception):\n    pass\n\n\nasync def maybe_create_tables(db):\n    try:\n        with await db.cursor() as cur:\n            await cur.execute(\"SELECT COUNT(*) FROM entries LIMIT 1\")\n            await cur.fetchone()\n    except psycopg2.ProgrammingError:\n        with open(\"schema.sql\") as f:\n            schema = f.read()\n        with await db.cursor() as cur:\n            await cur.execute(schema)\n\n\nclass Application(tornado.web.Application):\n    def __init__(self, db):\n        self.db = db\n        handlers = [\n            (r\"/\", HomeHandler),\n            (r\"/archive\", ArchiveHandler),\n            (r\"/feed\", FeedHandler),\n            (r\"/entry/([^/]+)\", EntryHandler),\n            (r\"/compose\", ComposeHandler),\n            (r\"/auth/create\", AuthCreateHandler),\n            (r\"/auth/login\", AuthLoginHandler),\n            (r\"/auth/logout\", AuthLogoutHandler),\n        ]\n        settings = dict(\n            blog_title=\"Tornado Blog\",\n            template_path=os.path.join(os.path.dirname(__file__), \"templates\"),\n            static_path=os.path.join(os.path.dirname(__file__), \"static\"),\n            ui_modules={\"Entry\": EntryModule},\n            xsrf_cookies=True,\n            cookie_secret=\"__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__\",\n            login_url=\"/auth/login\",\n            debug=True,\n        )\n        super().__init__(handlers, **settings)\n\n\nclass BaseHandler(tornado.web.RequestHandler):\n    def row_to_obj(self, row, cur):\n        \"\"\"Convert a SQL row to an object supporting dict and attribute access.\"\"\"\n        obj = tornado.util.ObjectDict()\n        for val, desc in zip(row, cur.description):\n            obj[desc.name] = val\n        return obj\n\n    async def execute(self, stmt, *args):\n        \"\"\"Execute a SQL statement.\n\n        Must be called with ``await self.execute(...)``\n        \"\"\"\n        with await self.application.db.cursor() as cur:\n            await cur.execute(stmt, args)\n\n    async def query(self, stmt, *args):\n        \"\"\"Query for a list of results.\n\n        Typical usage::\n\n            results = await self.query(...)\n\n        Or::\n\n            for row in await self.query(...)\n        \"\"\"\n        with await self.application.db.cursor() as cur:\n            await cur.execute(stmt, args)\n            return [self.row_to_obj(row, cur) for row in await cur.fetchall()]\n\n    async def queryone(self, stmt, *args):\n        \"\"\"Query for exactly one result.\n\n        Raises NoResultError if there are no results, or ValueError if\n        there are more than one.\n        \"\"\"\n        results = await self.query(stmt, *args)\n        if len(results) == 0:\n            raise NoResultError()\n        elif len(results) > 1:\n            raise ValueError(\"Expected 1 result, got %d\" % len(results))\n        return results[0]\n\n    async def prepare(self):\n        # get_current_user cannot be a coroutine, so set\n        # self.current_user in prepare instead.\n        user_id = self.get_signed_cookie(\"blogdemo_user\")\n        if user_id:\n            self.current_user = await self.queryone(\n                \"SELECT * FROM authors WHERE id = %s\", int(user_id)\n            )\n\n    async def any_author_exists(self):\n        return bool(await self.query(\"SELECT * FROM authors LIMIT 1\"))\n\n    def redirect_to_next(self):\n        next = self.get_argument(\"next\", \"/\")\n        if next.startswith(\"//\") or not next.startswith(\"/\"):\n            # Absolute URLs are not allowed because this would be an open redirect\n            # vulnerability (https://cwe.mitre.org/data/definitions/601.html).\n            raise tornado.web.HTTPError(400)\n        self.redirect(next)\n\n\nclass HomeHandler(BaseHandler):\n    async def get(self):\n        entries = await self.query(\n            \"SELECT * FROM entries ORDER BY published DESC LIMIT 5\"\n        )\n        if not entries:\n            self.redirect(\"/compose\")\n            return\n        self.render(\"home.html\", entries=entries)\n\n\nclass EntryHandler(BaseHandler):\n    async def get(self, slug):\n        entry = await self.queryone(\"SELECT * FROM entries WHERE slug = %s\", slug)\n        if not entry:\n            raise tornado.web.HTTPError(404)\n        self.render(\"entry.html\", entry=entry)\n\n\nclass ArchiveHandler(BaseHandler):\n    async def get(self):\n        entries = await self.query(\"SELECT * FROM entries ORDER BY published DESC\")\n        self.render(\"archive.html\", entries=entries)\n\n\nclass FeedHandler(BaseHandler):\n    async def get(self):\n        entries = await self.query(\n            \"SELECT * FROM entries ORDER BY published DESC LIMIT 10\"\n        )\n        self.set_header(\"Content-Type\", \"application/atom+xml\")\n        self.render(\"feed.xml\", entries=entries)\n\n\nclass ComposeHandler(BaseHandler):\n    @tornado.web.authenticated\n    async def get(self):\n        id = self.get_argument(\"id\", None)\n        entry = None\n        if id:\n            entry = await self.queryone(\"SELECT * FROM entries WHERE id = %s\", int(id))\n        self.render(\"compose.html\", entry=entry)\n\n    @tornado.web.authenticated\n    async def post(self):\n        id = self.get_argument(\"id\", None)\n        title = self.get_argument(\"title\")\n        text = self.get_argument(\"markdown\")\n        html = markdown.markdown(text)\n        if id:\n            try:\n                entry = await self.queryone(\n                    \"SELECT * FROM entries WHERE id = %s\", int(id)\n                )\n            except NoResultError:\n                raise tornado.web.HTTPError(404)\n            slug = entry.slug\n            await self.execute(\n                \"UPDATE entries SET title = %s, markdown = %s, html = %s \"\n                \"WHERE id = %s\",\n                title,\n                text,\n                html,\n                int(id),\n            )\n        else:\n            slug = unicodedata.normalize(\"NFKD\", title)\n            slug = re.sub(r\"[^\\w]+\", \" \", slug)\n            slug = \"-\".join(slug.lower().strip().split())\n            slug = slug.encode(\"ascii\", \"ignore\").decode(\"ascii\")\n            if not slug:\n                slug = \"entry\"\n            while True:\n                e = await self.query(\"SELECT * FROM entries WHERE slug = %s\", slug)\n                if not e:\n                    break\n                slug += \"-2\"\n            await self.execute(\n                \"INSERT INTO entries (author_id,title,slug,markdown,html,published,updated)\"\n                \"VALUES (%s,%s,%s,%s,%s,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP)\",\n                self.current_user.id,\n                title,\n                slug,\n                text,\n                html,\n            )\n        self.redirect(\"/entry/\" + slug)\n\n\nclass AuthCreateHandler(BaseHandler):\n    def get(self):\n        self.render(\"create_author.html\")\n\n    async def post(self):\n        if await self.any_author_exists():\n            raise tornado.web.HTTPError(400, \"author already created\")\n        hashed_password = await tornado.ioloop.IOLoop.current().run_in_executor(\n            None,\n            bcrypt.hashpw,\n            tornado.escape.utf8(self.get_argument(\"password\")),\n            bcrypt.gensalt(),\n        )\n        author = await self.queryone(\n            \"INSERT INTO authors (email, name, hashed_password) \"\n            \"VALUES (%s, %s, %s) RETURNING id\",\n            self.get_argument(\"email\"),\n            self.get_argument(\"name\"),\n            tornado.escape.to_unicode(hashed_password),\n        )\n        self.set_signed_cookie(\"blogdemo_user\", str(author.id))\n        self.redirect_to_next()\n\n\nclass AuthLoginHandler(BaseHandler):\n    async def get(self):\n        # If there are no authors, redirect to the account creation page.\n        if not await self.any_author_exists():\n            self.redirect(\"/auth/create\")\n        else:\n            self.render(\"login.html\", error=None)\n\n    async def post(self):\n        try:\n            author = await self.queryone(\n                \"SELECT * FROM authors WHERE email = %s\", self.get_argument(\"email\")\n            )\n        except NoResultError:\n            self.render(\"login.html\", error=\"email not found\")\n            return\n        password_equal = await tornado.ioloop.IOLoop.current().run_in_executor(\n            None,\n            bcrypt.checkpw,\n            tornado.escape.utf8(self.get_argument(\"password\")),\n            tornado.escape.utf8(author.hashed_password),\n        )\n        if password_equal:\n            self.set_signed_cookie(\"blogdemo_user\", str(author.id))\n            self.redirect_to_next()\n        else:\n            self.render(\"login.html\", error=\"incorrect password\")\n\n\nclass AuthLogoutHandler(BaseHandler):\n    def get(self):\n        self.clear_cookie(\"blogdemo_user\")\n        self.redirect_to_next()\n\n\nclass EntryModule(tornado.web.UIModule):\n    def render(self, entry):\n        return self.render_string(\"modules/entry.html\", entry=entry)\n\n\nasync def main():\n    tornado.options.parse_command_line()\n\n    # Create the global connection pool.\n    async with aiopg.create_pool(\n        host=options.db_host,\n        port=options.db_port,\n        user=options.db_user,\n        password=options.db_password,\n        dbname=options.db_database,\n    ) as db:\n        await maybe_create_tables(db)\n        app = Application(db)\n        app.listen(options.port)\n\n        # In this demo the server will simply run until interrupted\n        # with Ctrl-C, but if you want to shut down more gracefully,\n        # call shutdown_event.set().\n        shutdown_event = tornado.locks.Event()\n        await shutdown_event.wait()\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "demos/blog/docker-compose.yml",
    "content": "postgres:\n  image: postgres:10.3\n  environment:\n    POSTGRES_USER: blog\n    POSTGRES_PASSWORD: blog\n    POSTGRES_DB: blog\n  ports:\n    - \"3306\"\nblog:\n  build: .\n  links:\n    - postgres\n  ports:\n    - \"8888:8888\"\n  command: --db_host=postgres\n"
  },
  {
    "path": "demos/blog/requirements.txt",
    "content": "aiopg\nbcrypt\nmarkdown\npsycopg2\ntornado\n"
  },
  {
    "path": "demos/blog/schema.sql",
    "content": "-- Copyright 2009 FriendFeed\n--\n-- Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n-- not use this file except in compliance with the License. You may obtain\n-- 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, WITHOUT\n-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n-- License for the specific language governing permissions and limitations\n-- under the License.\n\n-- To create the database:\n--   CREATE DATABASE blog;\n--   CREATE USER blog WITH PASSWORD 'blog';\n--   GRANT ALL ON DATABASE blog TO blog;\n--\n-- To reload the tables:\n--   psql -U blog -d blog < schema.sql\n\nDROP TABLE IF EXISTS authors;\nCREATE TABLE authors (\n    id SERIAL PRIMARY KEY,\n    email VARCHAR(100) NOT NULL UNIQUE,\n    name VARCHAR(100) NOT NULL,\n    hashed_password VARCHAR(100) NOT NULL\n);\n\nDROP TABLE IF EXISTS entries;\nCREATE TABLE entries (\n    id SERIAL PRIMARY KEY,\n    author_id INT NOT NULL REFERENCES authors(id),\n    slug VARCHAR(100) NOT NULL UNIQUE,\n    title VARCHAR(512) NOT NULL,\n    markdown TEXT NOT NULL,\n    html TEXT NOT NULL,\n    published TIMESTAMP NOT NULL,\n    updated TIMESTAMP NOT NULL\n);\n\nCREATE INDEX ON entries (published);\n"
  },
  {
    "path": "demos/blog/static/blog.css",
    "content": "/*\n * Copyright 2009 Facebook\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n * not use this file except in compliance with the License. You may obtain\n * 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, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations\n * under the License.\n */\n\nbody {\n  background: white;\n  color: black;\n  margin: 15px;\n  margin-top: 0;\n}\n\nbody,\ninput,\ntextarea {\n  font-family: Georgia, serif;\n  font-size: 12pt;\n}\n\ntable {\n  border-collapse: collapse;\n  border: 0;\n}\n\ntd {\n  border: 0;\n  padding: 0;\n}\n\nh1,\nh2,\nh3,\nh4 {\n  font-family: \"Helvetica Nue\", Helvetica, Arial, sans-serif;\n  margin: 0;\n}\n\nh1 {\n  font-size: 20pt;\n}\n\npre,\ncode {\n  font-family: monospace;\n  color: #060;\n}\n\npre {\n  margin-left: 1em;\n  padding-left: 1em;\n  border-left: 1px solid silver;\n  line-height: 14pt;\n}\n\na,\na code {\n  color: #00c;\n}\n\n#body {\n  max-width: 800px;\n  margin: auto;\n}\n\n#header {\n  background-color: #3b5998;\n  padding: 5px;\n  padding-left: 10px;\n  padding-right: 10px;\n  margin-bottom: 1em;\n}\n\n#header,\n#header a {\n  color: white;\n}\n\n#header h1 a {\n  text-decoration: none;\n}\n\n#footer,\n#content {\n  margin-left: 10px;\n  margin-right: 10px;\n}\n\n#footer {\n  margin-top: 3em;\n}\n\n.entry h1 a {\n  color: black;\n  text-decoration: none;\n}\n\n.entry {\n  margin-bottom: 2em;\n}\n\n.entry .date {\n  margin-top: 3px;\n}\n\n.entry p {\n  margin: 0;\n  margin-bottom: 1em;\n}\n\n.entry .body {\n  margin-top: 1em;\n  line-height: 16pt;\n}\n\n.compose td {\n  vertical-align: middle;\n  padding-bottom: 5px;\n}\n\n.compose td.field {\n  padding-right: 10px;\n}\n\n.compose .title,\n.compose .submit {\n  font-family: \"Helvetica Nue\", Helvetica, Arial, sans-serif;\n  font-weight: bold;\n}\n\n.compose .title {\n  font-size: 20pt;\n}\n\n.compose .title,\n.compose .markdown {\n  width: 100%;\n}\n\n.compose .markdown {\n  height: 500px;\n  line-height: 16pt;\n}\n"
  },
  {
    "path": "demos/blog/templates/archive.html",
    "content": "{% extends \"base.html\" %}\n\n{% block head %}\n  <style type=\"text/css\">\n    ul.archive {\n      list-style-type: none;\n      margin: 0;\n      padding: 0;\n    }\n\n    ul.archive li {\n      margin-bottom: 1em;\n    }\n\n    ul.archive .title {\n      font-family: \"Helvetica Nue\", Helvetica, Arial, sans-serif;\n      font-size: 14pt;\n    }\n  </style>\n{% end %}\n\n{% block body %}\n  <ul class=\"archive\">\n    {% for entry in entries %}\n      <li>\n        <div class=\"title\"><a href=\"/entry/{{ entry.slug }}\">{{ entry.title }}</a></div>\n        <div class=\"date\">{{ locale.format_date(entry.published, full_format=True, shorter=True) }}</div>\n      </li>\n    {% end %}\n  </ul>\n{% end %}\n"
  },
  {
    "path": "demos/blog/templates/base.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>{{ escape(handler.settings[\"blog_title\"]) }}</title>\n  <link rel=\"stylesheet\" href=\"{{ static_url(\" blog.css\") }}\" type=\"text/css\">\n  <link rel=\"alternate\" href=\"/feed\" type=\"application/atom+xml\" title=\"{{ escape(handler.settings[\" blog_title\"]) }}\">\n  {% block head %}{% end %}\n</head>\n\n<body>\n  <div id=\"body\">\n    <div id=\"header\">\n      <div style=\"float:right\">\n        {% if current_user %}\n        <a href=\"/compose\">{{ _(\"New post\") }}</a> -\n        <a href=\"/auth/logout?next={{ url_escape(request.path) }}\">{{ _(\"Sign out\") }}</a>\n        {% else %}\n        {% raw _('<a href=\"%(url)s\">Sign in</a> to compose/edit') % {\"url\": \"/auth/login?next=\" +\n        url_escape(request.path)} %}\n        {% end %}\n      </div>\n      <h1><a href=\"/\">{{ escape(handler.settings[\"blog_title\"]) }}</a></h1>\n    </div>\n    <div id=\"content\">{% block body %}{% end %}</div>\n  </div>\n  {% block bottom %}{% end %}\n</body>\n\n</html>"
  },
  {
    "path": "demos/blog/templates/compose.html",
    "content": "{% extends \"base.html\" %}\n\n{% block body %}\n  <form action=\"{{ request.path }}\" method=\"post\" class=\"compose\">\n    <div style=\"margin-bottom:5px\"><input name=\"title\" type=\"text\" class=\"title\" value=\"{{ entry.title if entry else \"\" }}\"/></div>\n    <div style=\"margin-bottom:5px\"><textarea name=\"markdown\" rows=\"30\" cols=\"40\" class=\"markdown\">{{ entry.markdown if entry else \"\" }}</textarea></div>\n    <div>\n      <div style=\"float:right\"><a href=\"http://daringfireball.net/projects/markdown/syntax\">{{ _(\"Syntax documentation\") }}</a></div>\n      <input type=\"submit\" value=\"{{ _(\"Save changes\") if entry else _(\"Publish post\") }}\" class=\"submit\"/>\n      &nbsp;<a href=\"{{ \"/entry/\" + entry.slug if entry else \"/\" }}\">{{ _(\"Cancel\") }}</a>\n    </div>\n    {% if entry %}\n      <input type=\"hidden\" name=\"id\" value=\"{{ entry.id }}\"/>\n    {% end %}\n    {% module xsrf_form_html() %}\n  </form>\n{% end %}\n\n{% block bottom %}\n  <script src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js\" type=\"text/javascript\"></script>\n  <script type=\"text/javascript\">\n  //<![CDATA[\n\n    $(function() {\n      $(\"input[name=title]\").select();\n      $(\"form.compose\").submit(function() {\n          var required = [\"title\", \"markdown\"];\n          var form = $(this).get(0);\n          for (var i = 0; i < required.length; i++) {\n              if (!form[required[i]].value) {\n                  $(form[required[i]]).select();\n                  return false;\n              }\n          }\n          return true;\n      });\n    });\n\n  //]]>\n  </script>\n{% end %}\n"
  },
  {
    "path": "demos/blog/templates/create_author.html",
    "content": "{% extends \"base.html\" %}\n\n{% block body %}\n<form action=\"/auth/create\" method=\"POST\">\n  Email: <input name=\"email\" type=\"text\"><br>\n  Name: <input name=\"name\" type=\"text\"><br>\n  Password: <input name=\"password\" type=\"password\"><br>\n  {% module xsrf_form_html() %}\n  <input type=\"submit\">\n</form>\n{% end %}\n"
  },
  {
    "path": "demos/blog/templates/entry.html",
    "content": "{% extends \"base.html\" %}\n\n{% block body %}\n  {% module Entry(entry) %}\n{% end %}\n"
  },
  {
    "path": "demos/blog/templates/feed.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<feed xmlns=\"http://www.w3.org/2005/Atom\">\n  {% set date_format = \"%Y-%m-%dT%H:%M:%SZ\" %}\n  <title>{{ handler.settings[\"blog_title\"] }}</title>\n  {% if len(entries) > 0 %}\n    <updated>{{ max(e.updated for e in entries).strftime(date_format) }}</updated>\n  {% else %}\n    <updated>{{ datetime.datetime.now(datetime.timezone.utc).strftime(date_format) }}</updated>\n  {% end %}\n  <id>http://{{ request.host }}/</id>\n  <link rel=\"alternate\" href=\"http://{{ request.host }}/\" title=\"{{ handler.settings[\"blog_title\"] }}\" type=\"text/html\"/>\n  <link rel=\"self\" href=\"{{ request.full_url() }}\" title=\"{{ handler.settings[\"blog_title\"] }}\" type=\"application/atom+xml\"/>\n  <author><name>{{ handler.settings[\"blog_title\"] }}</name></author>\n  {% for entry in entries %}\n    <entry>\n      <id>http://{{ request.host }}/entry/{{ entry.slug }}</id>\n      <title type=\"text\">{{ entry.title }}</title>\n      <link href=\"http://{{ request.host }}/entry/{{ entry.slug }}\" rel=\"alternate\" type=\"text/html\"/>\n      <updated>{{ entry.updated.strftime(date_format) }}</updated>\n      <published>{{ entry.published.strftime(date_format) }}</published>\n      <content type=\"xhtml\" xml:base=\"http://{{ request.host }}/\">\n        <div xmlns=\"http://www.w3.org/1999/xhtml\">{% raw entry.html %}</div>\n      </content>\n    </entry>\n  {% end %}\n</feed>\n"
  },
  {
    "path": "demos/blog/templates/home.html",
    "content": "{% extends \"base.html\" %}\n\n{% block body %}\n  {% for entry in entries %}\n    {% module Entry(entry) %}\n  {% end %}\n  <div><a href=\"/archive\">{{ _(\"Archive\") }}</a></div>\n{% end %}\n"
  },
  {
    "path": "demos/blog/templates/login.html",
    "content": "{% extends \"base.html\" %}\n\n{% block body %}\n{% if error %}\n<span style=\"color: red\">Error: {{ error }}</span><p>\n{% end %}\n\n<form action=\"/auth/login\" method=\"POST\">\n  Email: <input name=\"email\" type=\"text\"><br>\n  Password: <input name=\"password\" type=\"password\"><br>\n  {% module xsrf_form_html() %}\n  <input type=\"submit\">\n</form>\n{% end %}\n"
  },
  {
    "path": "demos/blog/templates/modules/entry.html",
    "content": "<div class=\"entry\">\n  <h1><a href=\"/entry/{{ entry.slug }}\">{{ entry.title }}</a></h1>\n  <div class=\"date\">{{ locale.format_date(entry.published, full_format=True, shorter=True) }}</div>\n  <div class=\"body\">{% raw entry.html %}</div>\n  {% if current_user %}\n    <div class=\"admin\"><a href=\"/compose?id={{ entry.id }}\">{{ _(\"Edit this post\") }}</a></div>\n  {% end %}\n</div>\n"
  },
  {
    "path": "demos/chat/chatdemo.py",
    "content": "#!/usr/bin/env python3\n#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\nimport asyncio\nimport tornado\nimport os.path\nimport uuid\n\nfrom tornado.options import define, options, parse_command_line\n\ndefine(\"port\", default=8888, help=\"run on the given port\", type=int)\ndefine(\"debug\", default=True, help=\"run in debug mode\")\n\n\nclass MessageBuffer:\n    def __init__(self):\n        # cond is notified whenever the message cache is updated\n        self.cond = tornado.locks.Condition()\n        self.cache = []\n        self.cache_size = 200\n\n    def get_messages_since(self, cursor):\n        \"\"\"Returns a list of messages newer than the given cursor.\n\n        ``cursor`` should be the ``id`` of the last message received.\n        \"\"\"\n        results = []\n        for msg in reversed(self.cache):\n            if msg[\"id\"] == cursor:\n                break\n            results.append(msg)\n        results.reverse()\n        return results\n\n    def add_message(self, message):\n        self.cache.append(message)\n        if len(self.cache) > self.cache_size:\n            self.cache = self.cache[-self.cache_size :]\n        self.cond.notify_all()\n\n\n# Making this a non-singleton is left as an exercise for the reader.\nglobal_message_buffer = MessageBuffer()\n\n\nclass MainHandler(tornado.web.RequestHandler):\n    def get(self):\n        self.render(\"index.html\", messages=global_message_buffer.cache)\n\n\nclass MessageNewHandler(tornado.web.RequestHandler):\n    \"\"\"Post a new message to the chat room.\"\"\"\n\n    def post(self):\n        message = {\"id\": str(uuid.uuid4()), \"body\": self.get_argument(\"body\")}\n        # render_string() returns a byte string, which is not supported\n        # in json, so we must convert it to a character string.\n        message[\"html\"] = tornado.escape.to_unicode(\n            self.render_string(\"message.html\", message=message)\n        )\n        if next := self.get_argument(\"next\", None):\n            if next.startswith(\"//\") or not next.startswith(\"/\"):\n                # Absolute URLs are not allowed because this would be an open redirect\n                # vulnerability (https://cwe.mitre.org/data/definitions/601.html).\n                raise tornado.web.HTTPError(400)\n            self.redirect(next)\n        else:\n            self.write(message)\n        global_message_buffer.add_message(message)\n\n\nclass MessageUpdatesHandler(tornado.web.RequestHandler):\n    \"\"\"Long-polling request for new messages.\n\n    Waits until new messages are available before returning anything.\n    \"\"\"\n\n    async def post(self):\n        cursor = self.get_argument(\"cursor\", None)\n        messages = global_message_buffer.get_messages_since(cursor)\n        while not messages:\n            # Save the Future returned here so we can cancel it in\n            # on_connection_close.\n            self.wait_future = global_message_buffer.cond.wait()\n            try:\n                await self.wait_future\n            except asyncio.CancelledError:\n                return\n            messages = global_message_buffer.get_messages_since(cursor)\n        if self.request.connection.stream.closed():\n            return\n        self.write(dict(messages=messages))\n\n    def on_connection_close(self):\n        self.wait_future.cancel()\n\n\nasync def main():\n    parse_command_line()\n    app = tornado.web.Application(\n        [\n            (r\"/\", MainHandler),\n            (r\"/a/message/new\", MessageNewHandler),\n            (r\"/a/message/updates\", MessageUpdatesHandler),\n        ],\n        cookie_secret=\"__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__\",\n        template_path=os.path.join(os.path.dirname(__file__), \"templates\"),\n        static_path=os.path.join(os.path.dirname(__file__), \"static\"),\n        xsrf_cookies=True,\n        debug=options.debug,\n    )\n    app.listen(options.port)\n    await asyncio.Event().wait()\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "demos/chat/static/chat.css",
    "content": "/*\n * Copyright 2009 FriendFeed\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n * not use this file except in compliance with the License. You may obtain\n * 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, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations\n * under the License.\n */\n\nbody {\n  background: white;\n  margin: 10px;\n}\n\nbody,\ninput {\n  font-family: sans-serif;\n  font-size: 10pt;\n  color: black;\n}\n\ntable {\n  border-collapse: collapse;\n  border: 0;\n}\n\ntd {\n  border: 0;\n  padding: 0;\n}\n\n#body {\n  position: absolute;\n  bottom: 10px;\n  left: 10px;\n}\n\n#input {\n  margin-top: 0.5em;\n}\n\n#inbox .message {\n  padding-top: 0.25em;\n}\n\n#nav {\n  float: right;\n  z-index: 99;\n}\n"
  },
  {
    "path": "demos/chat/static/chat.js",
    "content": "// Copyright 2009 FriendFeed\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n// not use this file except in compliance with the License. You may obtain\n// 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, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\n$(document).ready(function() {\n    if (!window.console) window.console = {};\n    if (!window.console.log) window.console.log = function() {};\n\n    $(\"#messageform\").on(\"submit\", function() {\n        newMessage($(this));\n        return false;\n    });\n    $(\"#messageform\").on(\"keypress\", function(e) {\n        if (e.keyCode == 13) {\n            newMessage($(this));\n            return false;\n        }\n        return true;\n    });\n    $(\"#message\").select();\n    updater.poll();\n});\n\nfunction newMessage(form) {\n    var message = form.formToDict();\n    var disabled = form.find(\"input[type=submit]\");\n    disabled.disable();\n    $.postJSON(\"/a/message/new\", message, function(response) {\n        updater.showMessage(response);\n        if (message.id) {\n            form.parent().remove();\n        } else {\n            form.find(\"input[type=text]\").val(\"\").select();\n            disabled.enable();\n        }\n    });\n}\n\nfunction getCookie(name) {\n    var r = document.cookie.match(\"\\\\b\" + name + \"=([^;]*)\\\\b\");\n    return r ? r[1] : undefined;\n}\n\njQuery.postJSON = function(url, args, callback) {\n    args._xsrf = getCookie(\"_xsrf\");\n    $.ajax({url: url, data: $.param(args), dataType: \"text\", type: \"POST\",\n            success: function(response) {\n        if (callback) callback(eval(\"(\" + response + \")\"));\n    }, error: function(response) {\n        console.log(\"ERROR:\", response);\n    }});\n};\n\njQuery.fn.formToDict = function() {\n    var fields = this.serializeArray();\n    var json = {};\n    for (var i = 0; i < fields.length; i++) {\n        json[fields[i].name] = fields[i].value;\n    }\n    if (json.next) delete json.next;\n    return json;\n};\n\njQuery.fn.disable = function() {\n    this.enable(false);\n    return this;\n};\n\njQuery.fn.enable = function(opt_enable) {\n    if (arguments.length && !opt_enable) {\n        this.attr(\"disabled\", \"disabled\");\n    } else {\n        this.removeAttr(\"disabled\");\n    }\n    return this;\n};\n\nvar updater = {\n    errorSleepTime: 500,\n    cursor: null,\n\n    poll: function() {\n        var args = {\"_xsrf\": getCookie(\"_xsrf\")};\n        if (updater.cursor) args.cursor = updater.cursor;\n        $.ajax({url: \"/a/message/updates\", type: \"POST\", dataType: \"text\",\n                data: $.param(args), success: updater.onSuccess,\n                error: updater.onError});\n    },\n\n    onSuccess: function(response) {\n        try {\n            updater.newMessages(eval(\"(\" + response + \")\"));\n        } catch (e) {\n            updater.onError();\n            return;\n        }\n        updater.errorSleepTime = 500;\n        window.setTimeout(updater.poll, 0);\n    },\n\n    onError: function(response) {\n        updater.errorSleepTime *= 2;\n        console.log(\"Poll error; sleeping for\", updater.errorSleepTime, \"ms\");\n        window.setTimeout(updater.poll, updater.errorSleepTime);\n    },\n\n    newMessages: function(response) {\n        if (!response.messages) return;\n        var messages = response.messages;\n        updater.cursor = messages[messages.length - 1].id;\n        console.log(messages.length, \"new messages, cursor:\", updater.cursor);\n        for (var i = 0; i < messages.length; i++) {\n            updater.showMessage(messages[i]);\n        }\n    },\n\n    showMessage: function(message) {\n        var existing = $(\"#m\" + message.id);\n        if (existing.length > 0) return;\n        var node = $(message.html);\n        node.hide();\n        $(\"#inbox\").append(node);\n        node.slideDown();\n    }\n};\n"
  },
  {
    "path": "demos/chat/templates/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\">\n    <title>Tornado Chat Demo</title>\n    <link rel=\"stylesheet\" href=\"{{ static_url(\"chat.css\") }}\" type=\"text/css\">\n  </head>\n  <body>\n    <div id=\"body\">\n      <div id=\"inbox\">\n        {% for message in messages %}\n          {% module Template(\"message.html\", message=message) %}\n        {% end %}\n      </div>\n      <div id=\"input\">\n        <form action=\"/a/message/new\" method=\"post\" id=\"messageform\">\n          <table>\n            <tr>\n              <td><input type=\"text\" name=\"body\" id=\"message\" style=\"width:500px\"></td>\n              <td style=\"padding-left:5px\">\n                <input type=\"submit\" value=\"{{ _(\"Post\") }}\">\n                <input type=\"hidden\" name=\"next\" value=\"{{ request.path }}\">\n                {% module xsrf_form_html() %}\n              </td>\n            </tr>\n          </table>\n        </form>\n      </div>\n    </div>\n    <script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js\" type=\"text/javascript\"></script>\n    <script src=\"{{ static_url(\"chat.js\") }}\" type=\"text/javascript\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/chat/templates/message.html",
    "content": "<div class=\"message\" id=\"m{{ message[\"id\"] }}\">{% module linkify(message[\"body\"]) %}</div>\n"
  },
  {
    "path": "demos/facebook/README",
    "content": "Running the Tornado Facebook example\n====================================\n\nTo run this example, you must register a Facebook application with a\nConnect URL set to the domain the this demo will be running on\n(i.e. http://localhost:8888/ by default).  The API key and secret\nfor this application must be passed on the command line:\n\n    python facebook.py --facebook_api_key=ABC --facebook_secret=XYZ\n"
  },
  {
    "path": "demos/facebook/facebook.py",
    "content": "#!/usr/bin/env python3\n#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\nimport asyncio\nimport os.path\nimport tornado\n\nfrom tornado.options import define, options\n\ndefine(\"port\", default=8888, help=\"run on the given port\", type=int)\ndefine(\"facebook_api_key\", help=\"your Facebook application API key\", type=str)\ndefine(\"facebook_secret\", help=\"your Facebook application secret\", type=str)\n\n\nclass Application(tornado.web.Application):\n    def __init__(self):\n        handlers = [\n            (r\"/\", MainHandler),\n            (r\"/auth/login\", AuthLoginHandler),\n            (r\"/auth/logout\", AuthLogoutHandler),\n        ]\n        settings = dict(\n            cookie_secret=\"__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__\",\n            login_url=\"/auth/login\",\n            template_path=os.path.join(os.path.dirname(__file__), \"templates\"),\n            static_path=os.path.join(os.path.dirname(__file__), \"static\"),\n            xsrf_cookies=True,\n            facebook_api_key=options.facebook_api_key,\n            facebook_secret=options.facebook_secret,\n            ui_modules={\"Post\": PostModule},\n            debug=True,\n            autoescape=None,\n        )\n        tornado.web.Application.__init__(self, handlers, **settings)\n\n\nclass BaseHandler(tornado.web.RequestHandler):\n    def get_current_user(self):\n        user_json = self.get_signed_cookie(\"fbdemo_user\")\n        if not user_json:\n            return None\n        return tornado.escape.json_decode(user_json)\n\n\nclass MainHandler(BaseHandler, tornado.auth.FacebookGraphMixin):\n    @tornado.web.authenticated\n    async def get(self):\n        stream = await self.facebook_request(\n            \"/me/home\", self._on_stream, access_token=self.current_user[\"access_token\"]\n        )\n        if stream is None:\n            # Session may have expired\n            self.redirect(\"/auth/login\")\n            return\n        self.render(\"stream.html\", stream=stream)\n\n\nclass AuthLoginHandler(BaseHandler, tornado.auth.FacebookGraphMixin):\n    async def get(self):\n        my_url = self.request.protocol + \"://\" + self.request.host + \"/auth/login\"\n        if self.get_argument(\"code\", False):\n            user = await self.get_authenticated_user(\n                redirect_uri=my_url,\n                client_id=self.settings[\"facebook_api_key\"],\n                client_secret=self.settings[\"facebook_secret\"],\n                code=self.get_argument(\"code\"),\n            )\n            self.set_signed_cookie(\"fbdemo_user\", tornado.escape.json_encode(user))\n            self.redirect(\"/\")\n            return\n        self.authorize_redirect(\n            redirect_uri=my_url,\n            client_id=self.settings[\"facebook_api_key\"],\n            extra_params={\"scope\": \"user_posts\"},\n        )\n\n\nclass AuthLogoutHandler(BaseHandler, tornado.auth.FacebookGraphMixin):\n    def get(self):\n        self.clear_cookie(\"fbdemo_user\")\n        self.redirect(\"/\")\n\n\nclass PostModule(tornado.web.UIModule):\n    def render(self, post):\n        return self.render_string(\"modules/post.html\", post=post)\n\n\nasync def main():\n    tornado.options.parse_command_line()\n    if not (options.facebook_api_key and options.facebook_secret):\n        print(\"--facebook_api_key and --facebook_secret must be set\")\n        return\n    http_server = tornado.httpserver.HTTPServer(Application())\n    http_server.listen(options.port)\n    await asyncio.Event().wait()\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "demos/facebook/static/facebook.css",
    "content": "/*\n * Copyright 2009 Facebook\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n * not use this file except in compliance with the License. You may obtain\n * 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, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations\n * under the License.\n */\n\nbody {\n  background: white;\n  color: black;\n  margin: 15px;\n}\n\nbody,\ninput,\ntextarea {\n  font-family: \"Lucida Grande\", Tahoma, Verdana, sans-serif;\n  font-size: 10pt;\n}\n\ntable {\n  border-collapse: collapse;\n  border: 0;\n}\n\ntd {\n  border: 0;\n  padding: 0;\n}\n\nimg {\n  border: 0;\n}\n\na {\n  text-decoration: none;\n  color: #3b5998;\n}\n\na:hover {\n  text-decoration: underline;\n}\n\n.post {\n  border-bottom: 1px solid #eeeeee;\n  min-height: 50px;\n  padding-bottom: 10px;\n  margin-top: 10px;\n}\n\n.post .picture {\n  float: left;\n}\n\n.post .picture img {\n  height: 50px;\n  width: 50px;\n}\n\n.post .body {\n  margin-left: 60px;\n}\n\n.post .media img {\n  border: 1px solid #cccccc;\n  padding: 3px;\n}\n\n.post .media:hover img {\n  border: 1px solid #3b5998;\n}\n\n.post a.actor {\n  font-weight: bold;\n}\n\n.post .meta {\n  font-size: 11px;\n}\n\n.post a.permalink {\n  color: #777777;\n}\n\n#body {\n  max-width: 700px;\n  margin: auto;\n}\n"
  },
  {
    "path": "demos/facebook/static/facebook.js",
    "content": ""
  },
  {
    "path": "demos/facebook/templates/modules/post.html",
    "content": "<div class=\"post\">\n  <div class=\"picture\">\n    {% set author_url=\"http://www.facebook.com/profile.php?id=\" + escape(post[\"from\"][\"id\"]) %}\n    <a href=\"{{ author_url }}\"><img src=\"//graph.facebook.com/{{ escape(post[\"from\"][\"id\"]) }}/picture?type=square\"/></a>\n  </div>\n  <div class=\"body\">\n    <a href=\"{{ author_url }}\" class=\"actor\">{{ escape(post[\"from\"][\"name\"]) }}</a>\n    {% if \"message\" in post %}\n      <span class=\"message\">{{ escape(post[\"message\"]) }}</span>\n    {% end %}\n    <div class=\"meta\">\n      {% if \"actions\" in post %}\n        <a href=\"{{ escape(post[\"actions\"][0][\"link\"]) }}\" class=\"permalink\">{{ locale.format_date(datetime.datetime.strptime(post[\"created_time\"], \"%Y-%m-%dT%H:%M:%S+0000\")) }}</a>\n      {% end %}\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "demos/facebook/templates/stream.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\">\n    <title>Tornado Facebook Stream Demo</title>\n    <link rel=\"stylesheet\" href=\"{{ static_url(\"facebook.css\") }}\" type=\"text/css\">\n  </head>\n  <body>\n    <div id=\"body\">\n      <div style=\"float:right\">\n        <b>{{ escape(current_user[\"name\"]) }}</b> -\n        <a href=\"/auth/logout\">{{ _(\"Sign out\") }}</a>\n      </div>\n      <div style=\"margin-bottom:1em\"><a href=\"/\">{{ _(\"Refresh stream\") }}</a></div>\n      <div id=\"stream\">\n        {% for post in stream[\"data\"] %}\n          {{ modules.Post(post) }}\n        {% end %}\n      </div>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/file_upload/file_receiver.py",
    "content": "#!/usr/bin/env python\n\n\"\"\"Usage: python file_receiver.py\n\nDemonstrates a server that receives a multipart-form-encoded set of files in an\nHTTP POST, or streams in the raw data of a single file in an HTTP PUT.\n\nSee file_uploader.py in this directory for code that uploads files in this format.\n\"\"\"\n\nimport asyncio\nimport logging\nfrom urllib.parse import unquote\n\nimport tornado\nfrom tornado import options\n\n\nclass POSTHandler(tornado.web.RequestHandler):\n    def post(self):\n        for field_name, files in self.request.files.items():\n            for info in files:\n                filename, content_type = info[\"filename\"], info[\"content_type\"]\n                body = info[\"body\"]\n                logging.info(\n                    'POST \"%s\" \"%s\" %d bytes', filename, content_type, len(body)\n                )\n\n        self.write(\"OK\")\n\n\n@tornado.web.stream_request_body\nclass PUTHandler(tornado.web.RequestHandler):\n    def initialize(self):\n        self.bytes_read = 0\n\n    def data_received(self, chunk):\n        self.bytes_read += len(chunk)\n\n    def put(self, filename):\n        filename = unquote(filename)\n        mtype = self.request.headers.get(\"Content-Type\")\n        logging.info('PUT \"%s\" \"%s\" %d bytes', filename, mtype, self.bytes_read)\n        self.write(\"OK\")\n\n\ndef make_app():\n    return tornado.web.Application([(r\"/post\", POSTHandler), (r\"/(.*)\", PUTHandler)])\n\n\nasync def main():\n    options.parse_command_line()\n    app = make_app()\n    app.listen(8888)\n    await asyncio.Event().wait()\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "demos/file_upload/file_uploader.py",
    "content": "#!/usr/bin/env python\n\n\"\"\"Usage: python file_uploader.py [--put] file1.txt file2.png ...\n\nDemonstrates uploading files to a server, without concurrency. It can either\nPOST a multipart-form-encoded request containing one or more files, or PUT a\nsingle file without encoding.\n\nSee also file_receiver.py in this directory, a server that receives uploads.\n\"\"\"\n\nimport asyncio\nimport mimetypes\nimport os\nimport sys\nfrom functools import partial\nfrom urllib.parse import quote\nfrom uuid import uuid4\n\nfrom tornado import httpclient\nfrom tornado.options import define, options\n\n\n# Using HTTP POST, upload one or more files in a single multipart-form-encoded\n# request.\nasync def multipart_producer(boundary, filenames, write):\n    boundary_bytes = boundary.encode()\n\n    for filename in filenames:\n        filename_bytes = filename.encode()\n        mtype = mimetypes.guess_type(filename)[0] or \"application/octet-stream\"\n        buf = (\n            (b\"--%s\\r\\n\" % boundary_bytes)\n            + (\n                b'Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\\r\\n'\n                % (filename_bytes, filename_bytes)\n            )\n            + (b\"Content-Type: %s\\r\\n\" % mtype.encode())\n            + b\"\\r\\n\"\n        )\n        await write(buf)\n        with open(filename, \"rb\") as f:\n            while True:\n                # 16k at a time.\n                chunk = f.read(16 * 1024)\n                if not chunk:\n                    break\n                await write(chunk)\n\n        await write(b\"\\r\\n\")\n\n    await write(b\"--%s--\\r\\n\" % (boundary_bytes,))\n\n\n# Using HTTP PUT, upload one raw file. This is preferred for large files since\n# the server can stream the data instead of buffering it entirely in memory.\nasync def post(filenames):\n    client = httpclient.AsyncHTTPClient()\n    boundary = uuid4().hex\n    headers = {\"Content-Type\": \"multipart/form-data; boundary=%s\" % boundary}\n    producer = partial(multipart_producer, boundary, filenames)\n    response = await client.fetch(\n        \"http://localhost:8888/post\",\n        method=\"POST\",\n        headers=headers,\n        body_producer=producer,\n    )\n\n    print(response)\n\n\nasync def raw_producer(filename, write):\n    with open(filename, \"rb\") as f:\n        while True:\n            # 16K at a time.\n            chunk = f.read(16 * 1024)\n            if not chunk:\n                # Complete.\n                break\n\n            await write(chunk)\n\n\nasync def put(filenames):\n    client = httpclient.AsyncHTTPClient()\n    for filename in filenames:\n        mtype = mimetypes.guess_type(filename)[0] or \"application/octet-stream\"\n        headers = {\"Content-Type\": mtype}\n        producer = partial(raw_producer, filename)\n        url_path = quote(os.path.basename(filename))\n        response = await client.fetch(\n            \"http://localhost:8888/%s\" % url_path,\n            method=\"PUT\",\n            headers=headers,\n            body_producer=producer,\n        )\n        print(response)\n\n\nif __name__ == \"__main__\":\n    define(\"put\", type=bool, help=\"Use PUT instead of POST\", group=\"file uploader\")\n\n    # Tornado configures logging from command line opts and returns remaining args.\n    filenames = options.parse_command_line()\n    if not filenames:\n        print(\"Provide a list of filenames to upload.\", file=sys.stderr)\n        sys.exit(1)\n\n    method = put if options.put else post\n    asyncio.run(method(filenames))\n"
  },
  {
    "path": "demos/google_auth/.gitignore",
    "content": "main.cfg\n"
  },
  {
    "path": "demos/google_auth/main.py",
    "content": "\"\"\"Demo app for GoogleOAuth2Mixin\n\nRecommended usage:\n- Register an app with Google following the instructions at\n  https://www.tornadoweb.org/en/stable/auth.html#tornado.auth.GoogleOAuth2Mixin\n- Use \"http://localhost:8888/auth/google\" as the redirect URI.\n- Create a file in this directory called main.cfg, containing two lines (python syntax):\n    google_oauth_key=\"...\"\n    google_oauth_secret=\"...\"\n- Run this file with `python main.py --config_file=main.cfg`\n- Visit \"http://localhost:8888\" in your browser.\n\"\"\"\n\nimport asyncio\nimport json\nimport tornado\nimport urllib.parse\n\nfrom tornado.options import define, options\nfrom tornado.web import url\n\ndefine(\"port\", default=8888, help=\"run on the given port\", type=int)\ndefine(\"google_oauth_key\", help=\"Google OAuth Key\")\ndefine(\"google_oauth_secret\", help=\"Google OAuth Secret\")\ndefine(\n    \"config_file\",\n    help=\"tornado config file\",\n    callback=lambda path: tornado.options.parse_config_file(path, final=False),\n)\n\n\nclass BaseHandler(tornado.web.RequestHandler):\n    def get_current_user(self):\n        user_cookie = self.get_signed_cookie(\"googledemo_user\")\n        if user_cookie:\n            return json.loads(user_cookie)\n        return None\n\n\nclass IndexHandler(BaseHandler, tornado.auth.GoogleOAuth2Mixin):\n    @tornado.web.authenticated\n    async def get(self):\n        try:\n            # This is redundant: we got the userinfo in the login handler.\n            # But this demonstrates the usage of oauth2_request outside of\n            # the login flow, and getting anything more than userinfo\n            # leads to more approval prompts and complexity.\n            user_info = await self.oauth2_request(\n                \"https://www.googleapis.com/oauth2/v1/userinfo\",\n                access_token=self.current_user[\"access_token\"],\n            )\n        except tornado.httpclient.HTTPClientError as e:\n            print(e.response.body)\n            raise\n        self.write(f\"Hello {user_info['name']}\")\n\n\nclass LoginHandler(BaseHandler, tornado.auth.GoogleOAuth2Mixin):\n    async def get(self):\n        redirect_uri = urllib.parse.urljoin(\n            self.application.settings[\"redirect_base_uri\"],\n            self.reverse_url(\"google_oauth\"),\n        )\n        if self.get_argument(\"code\", False):\n            access = await self.get_authenticated_user(\n                redirect_uri=redirect_uri, code=self.get_argument(\"code\")\n            )\n            user = await self.oauth2_request(\n                \"https://www.googleapis.com/oauth2/v1/userinfo\",\n                access_token=access[\"access_token\"],\n            )\n            # Save the user and access token.\n            user_cookie = dict(id=user[\"id\"], access_token=access[\"access_token\"])\n            self.set_signed_cookie(\"googledemo_user\", json.dumps(user_cookie))\n            self.redirect(\"/\")\n        else:\n            self.authorize_redirect(\n                redirect_uri=redirect_uri,\n                client_id=self.get_google_oauth_settings()[\"key\"],\n                scope=[\"profile\", \"email\"],\n                response_type=\"code\",\n                extra_params={\"approval_prompt\": \"auto\"},\n            )\n\n\nclass LogoutHandler(BaseHandler):\n    def get(self):\n        self.clear_cookie(\"user\")\n        self.redirect(\"/\")\n\n\nasync def main():\n    tornado.options.parse_command_line()\n    app = tornado.web.Application(\n        [\n            url(r\"/\", IndexHandler),\n            url(r\"/auth/google\", LoginHandler, name=\"google_oauth\"),\n            url(r\"/logout\", LogoutHandler),\n        ],\n        redirect_base_uri=f\"http://localhost:{options.port}\",\n        google_oauth=dict(\n            key=options.google_oauth_key, secret=options.google_oauth_secret\n        ),\n        debug=True,\n        cookie_secret=\"__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__\",\n        login_url=\"/auth/google\",\n    )\n    app.listen(options.port)\n    shutdown_event = asyncio.Event()\n    await shutdown_event.wait()\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "demos/helloworld/helloworld.py",
    "content": "#!/usr/bin/env python\n#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\nimport asyncio\nimport tornado\n\nfrom tornado.options import define, options\n\ndefine(\"port\", default=8888, help=\"run on the given port\", type=int)\n\n\nclass MainHandler(tornado.web.RequestHandler):\n    def get(self):\n        self.write(\"Hello, world\")\n\n\nasync def main():\n    tornado.options.parse_command_line()\n    application = tornado.web.Application([(r\"/\", MainHandler)])\n    http_server = tornado.httpserver.HTTPServer(application)\n    http_server.listen(options.port)\n    await asyncio.Event().wait()\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "demos/tcpecho/README.md",
    "content": "TCP echo demo\n=============\n\nThis demo shows how to use Tornado's asynchronous TCP client and\nserver by implementing `handle_stream` as a coroutine.\n\nTo run the server:\n\n```\n$ python server.py\n```\n\nThe client will send the message given with the `--message` option\n(which defaults to \"ping\"), wait for a response, then quit. To run:\n\n```\n$ python client.py --message=\"your message here\"\n```\n\nAlternatively, you can interactively send messages to the echo server\nwith a telnet client. For example:\n\n```\n$ telnet localhost 9888\nTrying ::1...\nConnected to localhost.\nEscape character is '^]'.\nping\nping\n```\n"
  },
  {
    "path": "demos/tcpecho/client.py",
    "content": "#!/usr/bin/env python\n\nimport asyncio\nfrom tornado.tcpclient import TCPClient\nfrom tornado.options import options, define\n\ndefine(\"host\", default=\"localhost\", help=\"TCP server host\")\ndefine(\"port\", default=9888, help=\"TCP port to connect to\")\ndefine(\"message\", default=\"ping\", help=\"Message to send\")\n\n\nasync def send_message():\n    stream = await TCPClient().connect(options.host, options.port)\n    await stream.write((options.message + \"\\n\").encode())\n    print(\"Sent to server:\", options.message)\n    reply = await stream.read_until(b\"\\n\")\n    print(\"Response from server:\", reply.decode().strip())\n\n\nif __name__ == \"__main__\":\n    options.parse_command_line()\n    asyncio.run(send_message())\n"
  },
  {
    "path": "demos/tcpecho/server.py",
    "content": "#!/usr/bin/env python\n\nimport asyncio\nimport logging\nfrom tornado import gen\nfrom tornado.iostream import StreamClosedError\nfrom tornado.tcpserver import TCPServer\nfrom tornado.options import options, define\n\ndefine(\"port\", default=9888, help=\"TCP port to listen on\")\nlogger = logging.getLogger(__name__)\n\n\nclass EchoServer(TCPServer):\n    @gen.coroutine\n    def handle_stream(self, stream, address):\n        while True:\n            try:\n                data = yield stream.read_until(b\"\\n\")\n                logger.info(\"Received bytes: %s\", data)\n                if not data.endswith(b\"\\n\"):\n                    data = data + b\"\\n\"\n                yield stream.write(data)\n            except StreamClosedError:\n                logger.warning(\"Lost client at host %s\", address[0])\n                break\n            except Exception as e:\n                print(e)\n\n\nasync def main():\n    options.parse_command_line()\n    logger.info(\"Listening on TCP port %d\", options.port)\n    server = EchoServer()\n    server.listen(options.port)\n    await asyncio.Event().wait()\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "demos/websocket/chatdemo.py",
    "content": "#!/usr/bin/env python\n#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\"\"\"Simplified chat demo for websockets.\n\nAuthentication, error handling, etc are left as an exercise for the reader :)\n\"\"\"\n\nimport asyncio\nimport logging\nimport tornado\nimport os.path\nimport uuid\n\nfrom tornado.options import define, options\n\ndefine(\"port\", default=8888, help=\"run on the given port\", type=int)\n\n\nclass Application(tornado.web.Application):\n    def __init__(self):\n        handlers = [(r\"/\", MainHandler), (r\"/chatsocket\", ChatSocketHandler)]\n        settings = dict(\n            cookie_secret=\"__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__\",\n            template_path=os.path.join(os.path.dirname(__file__), \"templates\"),\n            static_path=os.path.join(os.path.dirname(__file__), \"static\"),\n            xsrf_cookies=True,\n        )\n        super().__init__(handlers, **settings)\n\n\nclass MainHandler(tornado.web.RequestHandler):\n    def get(self):\n        self.render(\"index.html\", messages=ChatSocketHandler.cache)\n\n\nclass ChatSocketHandler(tornado.websocket.WebSocketHandler):\n    waiters = set()\n    cache = []\n    cache_size = 200\n\n    def get_compression_options(self):\n        # Non-None enables compression with default options.\n        return {}\n\n    def open(self):\n        ChatSocketHandler.waiters.add(self)\n\n    def on_close(self):\n        ChatSocketHandler.waiters.remove(self)\n\n    @classmethod\n    def update_cache(cls, chat):\n        cls.cache.append(chat)\n        if len(cls.cache) > cls.cache_size:\n            cls.cache = cls.cache[-cls.cache_size :]\n\n    @classmethod\n    def send_updates(cls, chat):\n        logging.info(\"sending message to %d waiters\", len(cls.waiters))\n        for waiter in cls.waiters:\n            try:\n                waiter.write_message(chat)\n            except:\n                logging.error(\"Error sending message\", exc_info=True)\n\n    def on_message(self, message):\n        logging.info(\"got message %r\", message)\n        parsed = tornado.escape.json_decode(message)\n        chat = {\"id\": str(uuid.uuid4()), \"body\": parsed[\"body\"]}\n        chat[\"html\"] = tornado.escape.to_basestring(\n            self.render_string(\"message.html\", message=chat)\n        )\n\n        ChatSocketHandler.update_cache(chat)\n        ChatSocketHandler.send_updates(chat)\n\n\nasync def main():\n    tornado.options.parse_command_line()\n    app = Application()\n    app.listen(options.port)\n    await asyncio.Event().wait()\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "demos/websocket/static/chat.css",
    "content": "/*\n * Copyright 2009 FriendFeed\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n * not use this file except in compliance with the License. You may obtain\n * 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, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations\n * under the License.\n */\n\nbody {\n  background: white;\n  margin: 10px;\n}\n\nbody,\ninput {\n  font-family: sans-serif;\n  font-size: 10pt;\n  color: black;\n}\n\ntable {\n  border-collapse: collapse;\n  border: 0;\n}\n\ntd {\n  border: 0;\n  padding: 0;\n}\n\n#body {\n  position: absolute;\n  bottom: 10px;\n  left: 10px;\n}\n\n#input {\n  margin-top: 0.5em;\n}\n\n#inbox .message {\n  padding-top: 0.25em;\n}\n\n#nav {\n  float: right;\n  z-index: 99;\n}\n"
  },
  {
    "path": "demos/websocket/static/chat.js",
    "content": "// Copyright 2009 FriendFeed\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n// not use this file except in compliance with the License. You may obtain\n// 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, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\n$(document).ready(function() {\n    if (!window.console) window.console = {};\n    if (!window.console.log) window.console.log = function() {};\n\n    $(\"#messageform\").on(\"submit\", function() {\n        newMessage($(this));\n        return false;\n    });\n    $(\"#messageform\").on(\"keypress\", function(e) {\n        if (e.keyCode == 13) {\n            newMessage($(this));\n            return false;\n        }\n    });\n    $(\"#message\").select();\n    updater.start();\n});\n\nfunction newMessage(form) {\n    var message = form.formToDict();\n    updater.socket.send(JSON.stringify(message));\n    form.find(\"input[type=text]\").val(\"\").select();\n}\n\njQuery.fn.formToDict = function() {\n    var fields = this.serializeArray();\n    var json = {}\n    for (var i = 0; i < fields.length; i++) {\n        json[fields[i].name] = fields[i].value;\n    }\n    if (json.next) delete json.next;\n    return json;\n};\n\nvar updater = {\n    socket: null,\n\n    start: function() {\n        var url = \"ws://\" + location.host + \"/chatsocket\";\n        updater.socket = new WebSocket(url);\n        updater.socket.onmessage = function(event) {\n            updater.showMessage(JSON.parse(event.data));\n        }\n    },\n\n    showMessage: function(message) {\n        var existing = $(\"#m\" + message.id);\n        if (existing.length > 0) return;\n        var node = $(message.html);\n        node.hide();\n        $(\"#inbox\").append(node);\n        node.slideDown();\n    }\n};\n"
  },
  {
    "path": "demos/websocket/templates/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\">\n    <title>Tornado Chat Demo</title>\n    <link rel=\"stylesheet\" href=\"{{ static_url(\"chat.css\") }}\" type=\"text/css\">\n  </head>\n  <body>\n    <div id=\"body\">\n      <div id=\"inbox\">\n        {% for message in messages %}\n          {% include \"message.html\" %}\n        {% end %}\n      </div>\n      <div id=\"input\">\n        <form action=\"/a/message/new\" method=\"post\" id=\"messageform\">\n          <table>\n            <tr>\n              <td><input type=\"text\" name=\"body\" id=\"message\" style=\"width:500px\"></td>\n              <td style=\"padding-left:5px\">\n                <input type=\"submit\" value=\"{{ _(\"Post\") }}\">\n                <input type=\"hidden\" name=\"next\" value=\"{{ request.path }}\">\n                {% module xsrf_form_html() %}\n              </td>\n            </tr>\n          </table>\n        </form>\n      </div>\n    </div>\n    <script src=\"http://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js\" type=\"text/javascript\"></script>\n    <script src=\"{{ static_url(\"chat.js\") }}\" type=\"text/javascript\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/websocket/templates/message.html",
    "content": "<div class=\"message\" id=\"m{{ message[\"id\"] }}\">{% module linkify(message[\"body\"]) %}</div>\n"
  },
  {
    "path": "demos/webspider/webspider.py",
    "content": "#!/usr/bin/env python3\n\nimport asyncio\nimport time\nfrom datetime import timedelta\n\nfrom html.parser import HTMLParser\nfrom urllib.parse import urljoin, urldefrag\n\nfrom tornado import gen, httpclient, queues\n\nbase_url = \"http://www.tornadoweb.org/en/stable/\"\nconcurrency = 10\n\n\nasync def get_links_from_url(url):\n    \"\"\"Download the page at `url` and parse it for links.\n\n    Returned links have had the fragment after `#` removed, and have been made\n    absolute so, e.g. the URL 'gen.html#tornado.gen.coroutine' becomes\n    'http://www.tornadoweb.org/en/stable/gen.html'.\n    \"\"\"\n    response = await httpclient.AsyncHTTPClient().fetch(url)\n    print(\"fetched %s\" % url)\n\n    html = response.body.decode(errors=\"ignore\")\n    return [urljoin(url, remove_fragment(new_url)) for new_url in get_links(html)]\n\n\ndef remove_fragment(url):\n    pure_url, frag = urldefrag(url)\n    return pure_url\n\n\ndef get_links(html):\n    class URLSeeker(HTMLParser):\n        def __init__(self):\n            HTMLParser.__init__(self)\n            self.urls = []\n\n        def handle_starttag(self, tag, attrs):\n            href = dict(attrs).get(\"href\")\n            if href and tag == \"a\":\n                self.urls.append(href)\n\n    url_seeker = URLSeeker()\n    url_seeker.feed(html)\n    return url_seeker.urls\n\n\nasync def main():\n    q = queues.Queue()\n    start = time.time()\n    fetching, fetched, dead = set(), set(), set()\n\n    async def fetch_url(current_url):\n        if current_url in fetching:\n            return\n\n        print(\"fetching %s\" % current_url)\n        fetching.add(current_url)\n        urls = await get_links_from_url(current_url)\n        fetched.add(current_url)\n\n        for new_url in urls:\n            # Only follow links beneath the base URL\n            if new_url.startswith(base_url):\n                await q.put(new_url)\n\n    async def worker():\n        async for url in q:\n            if url is None:\n                return\n            try:\n                await fetch_url(url)\n            except Exception as e:\n                print(f\"Exception: {e} {url}\")\n                dead.add(url)\n            finally:\n                q.task_done()\n\n    await q.put(base_url)\n\n    # Start workers, then wait for the work queue to be empty.\n    workers = gen.multi([worker() for _ in range(concurrency)])\n    await q.join(timeout=timedelta(seconds=300))\n    assert fetching == (fetched | dead)\n    print(\"Done in %d seconds, fetched %s URLs.\" % (time.time() - start, len(fetched)))\n    print(\"Unable to fetch %s URLs.\" % len(dead))\n\n    # Signal all the workers to exit.\n    for _ in range(concurrency):\n        await q.put(None)\n    await workers\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "docs/Makefile",
    "content": ".PHONY: all\nall: sphinx\n\n# No -W for doctests because that disallows tests with empty output.\nSPHINX_DOCTEST_OPTS=-n -d build/doctress .\nSPHINXOPTS=-n -W -d build/doctrees .\n\n.PHONY: sphinx\nsphinx:\n\tsphinx-build -b html $(SPHINXOPTS) build/html\n\n.PHONY: coverage\ncoverage:\n\tsphinx-build -b coverage ${SPHINXOPTS} build/coverage\n\tcat build/coverage/python.txt\n\n.PHONY: latex\nlatex:\n\tsphinx-build -b latex $(SPHINXOPTS) build/latex\n\n# Building a pdf requires a latex installation.  For macports, the needed\n# packages are texlive-latex-extra and texlive-fonts-recommended.\n# The output is in build/latex/tornado.pdf\n.PHONY: pdf\npdf: latex\n\tcd build/latex && pdflatex -interaction=nonstopmode tornado.tex\n\n.PHONY: doctest\ndoctest:\n\tsphinx-build -b doctest $(SPHINX_DOCTEST_OPTS) build/doctest\n\nclean:\n\trm -rf build\n"
  },
  {
    "path": "docs/asyncio.rst",
    "content": "``tornado.platform.asyncio`` --- Bridge between ``asyncio`` and Tornado\n=======================================================================\n\n.. automodule:: tornado.platform.asyncio\n   :members:\n\n\n   ..\n      AnyThreadEventLoopPolicy is created dynamically in getattr, so\n      introspection won't find it automatically. This has the unfortunate\n      side effect of moving it to the top of the page but it's better than\n      having it missing entirely.\n\n   .. autoclass:: AnyThreadEventLoopPolicy\n      :members:"
  },
  {
    "path": "docs/auth.rst",
    "content": "``tornado.auth`` --- Third-party login with OpenID and OAuth\n============================================================\n\n.. testsetup::\n\n   import tornado\n\n.. automodule:: tornado.auth\n\n   Common protocols\n   ----------------\n\n   These classes implement the OpenID and OAuth standards.  They will\n   generally need to be subclassed to use them with any particular site.\n   The degree of customization required will vary, but in most cases\n   overriding the class attributes (which are named beginning with\n   underscores for historical reasons) should be sufficient.\n\n   .. autoclass:: OpenIdMixin\n      :members:\n\n   .. autoclass:: OAuthMixin\n\n      .. automethod:: authorize_redirect\n      .. automethod:: get_authenticated_user\n      .. automethod:: _oauth_consumer_token\n      .. automethod:: _oauth_get_user_future\n      .. automethod:: get_auth_http_client\n\n   .. autoclass:: OAuth2Mixin\n      :members:\n\n   Google\n   ------\n\n   .. autoclass:: GoogleOAuth2Mixin\n      :members:\n\n   Facebook\n   --------\n\n   .. autoclass:: FacebookGraphMixin\n      :members:\n\n   Twitter\n   -------\n\n   .. autoclass:: TwitterMixin\n      :members:\n"
  },
  {
    "path": "docs/autoreload.rst",
    "content": "``tornado.autoreload`` --- Automatically detect code changes in development\n===========================================================================\n\n.. automodule:: tornado.autoreload\n   :members:\n"
  },
  {
    "path": "docs/caresresolver.rst",
    "content": "``tornado.platform.caresresolver`` --- Asynchronous DNS Resolver using C-Ares\n=============================================================================\n\n.. module:: tornado.platform.caresresolver\n\nThis module contains a DNS resolver using the c-ares library (and its\nwrapper ``pycares``).\n\n.. py:class:: CaresResolver\n\n    Name resolver based on the c-ares library.\n\n    This is a non-blocking and non-threaded resolver.  It may not produce\n    the same results as the system resolver, but can be used for non-blocking\n    resolution when threads cannot be used.\n\n    c-ares fails to resolve some names when ``family`` is ``AF_UNSPEC``,\n    so it is only recommended for use in ``AF_INET`` (i.e. IPv4).  This is\n    the default for ``tornado.simple_httpclient``, but other libraries\n    may default to ``AF_UNSPEC``.\n\n    This class requires ``pycares`` version 4. Since this class is deprecated, it will not be\n    updated to support ``pycares`` version 5.\n\n    .. deprecated:: 6.2\n       This class is deprecated and will be removed in Tornado 7.0. Use the default\n       thread-based resolver instead.\n"
  },
  {
    "path": "docs/concurrent.rst",
    "content": "``tornado.concurrent`` --- Work with ``Future`` objects\n=======================================================\n\n.. testsetup::\n\n   from tornado.concurrent import *\n   from tornado import gen\n\n.. automodule:: tornado.concurrent\n    :members:\n\n     .. class:: Future\n\n        ``tornado.concurrent.Future`` is an alias for `asyncio.Future`.\n\n        In Tornado, the main way in which applications interact with\n        ``Future`` objects is by ``awaiting`` or ``yielding`` them in\n        coroutines, instead of calling methods on the ``Future`` objects\n        themselves. For more information on the available methods, see\n        the `asyncio.Future` docs.\n\n        .. versionchanged:: 5.0\n\n           Tornado's implementation of ``Future`` has been replaced by\n           the version from `asyncio` when available.\n\n           - ``Future`` objects can only be created while there is a\n             current `.IOLoop`\n           - The timing of callbacks scheduled with\n             ``Future.add_done_callback`` has changed.\n           - Cancellation is now partially supported (only on Python 3)\n           - The ``exc_info`` and ``set_exc_info`` methods are no longer\n             available on Python 3.\n"
  },
  {
    "path": "docs/conf.py",
    "content": "import os\nimport sphinx.errors\nimport sys\n\n# Ensure we get the local copy of tornado instead of what's on the standard path\nsys.path.insert(0, os.path.abspath(\"..\"))\nimport tornado\n\nmaster_doc = \"index\"\n\nproject = \"Tornado\"\ncopyright = \"The Tornado Authors\"\n\nversion = release = tornado.version\n\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.coverage\",\n    \"sphinx.ext.doctest\",\n    \"sphinx.ext.intersphinx\",\n    \"sphinx.ext.viewcode\",\n]\n\nprimary_domain = \"py\"\ndefault_role = \"py:obj\"\n\nautodoc_member_order = \"bysource\"\nautoclass_content = \"both\"\nautodoc_inherit_docstrings = False\n\n# Without this line sphinx includes a copy of object.__init__'s docstring\n# on any class that doesn't define __init__.\n# https://bitbucket.org/birkenfeld/sphinx/issue/1337/autoclass_content-both-uses-object__init__\nautodoc_docstring_signature = False\n\ncoverage_skip_undoc_in_source = True\ncoverage_show_missing_items = True\ncoverage_ignore_modules = [\n    \"tornado.curl_httpclient\",\n    \"tornado.platform.asyncio\",\n    \"tornado.platform.caresresolver\",\n    \"tornado.platform.twisted\",\n    \"tornado.simple_httpclient\",\n]\n# I wish this could go in a per-module file...\ncoverage_ignore_classes = [\n    # tornado.gen\n    \"Runner\",\n    # tornado.web\n    \"ChunkedTransferEncoding\",\n    \"GZipContentEncoding\",\n    \"OutputTransform\",\n    \"TemplateModule\",\n    \"url\",\n    # tornado.websocket\n    \"WebSocketProtocol\",\n    \"WebSocketProtocol13\",\n    \"WebSocketProtocol76\",\n]\n\ncoverage_ignore_functions = [\n    # various modules\n    \"doctests\",\n    \"main\",\n    # tornado.escape\n    # parse_qs_bytes should probably be documented but it's complicated by\n    # having different implementations between py2 and py3.\n    \"parse_qs_bytes\",\n    # tornado.gen\n    \"Multi\",\n]\n\nhtml_favicon = \"favicon.ico\"\n\nlatex_documents = [\n    (\n        \"index\",\n        \"tornado.tex\",\n        \"Tornado Documentation\",\n        \"The Tornado Authors\",\n        \"manual\",\n        False,\n    )\n]\n\nintersphinx_mapping = {\"python\": (\"https://docs.python.org/3/\", None)}\n\nhtml_theme = \"sphinx_rtd_theme\"\n\n# Suppress warnings about \"class reference target not found\" for these types.\n# In most cases these types come from type annotations and are for mypy's use.\nmissing_references = {\n    # Generic type variables; nothing to link to.\n    \"_IOStreamType\",\n    \"_S\",\n    \"_T\",\n    # Standard library types which are defined in one module and documented\n    # in another. We could probably remap them to their proper location if\n    # there's not an upstream fix in python and/or sphinx.\n    \"_asyncio.Future\",\n    \"_io.BytesIO\",\n    \"asyncio.AbstractEventLoop.run_forever\",\n    \"asyncio.events.AbstractEventLoop\",\n    \"concurrent.futures._base.Executor\",\n    \"concurrent.futures._base.Future\",\n    \"futures.Future\",\n    \"socket.socket\",\n    \"unittest.case.TestCase\",\n    \"TextIO\",\n    # Other stuff. I'm not sure why some of these are showing up, but\n    # I'm just listing everything here to avoid blocking the upgrade of sphinx.\n    \"Future\",\n    \"httputil.HTTPServerConnectionDelegate\",\n    \"httputil.HTTPServerRequest\",\n    \"OutputTransform\",\n    \"Pattern\",\n    \"RAISE\",\n    \"Rule\",\n    \"socket.AddressFamily\",\n    \"tornado.concurrent._T\",\n    \"tornado.gen._T\",\n    \"tornado.ioloop._S\",\n    \"tornado.ioloop._T\",\n    \"tornado.ioloop._Selectable\",\n    \"tornado.iostream._IOStreamType\",\n    \"tornado.locks._ReleasingContextManager\",\n    \"tornado.queues._T\",\n    \"tornado.options._Mockable\",\n    \"tornado.web._ArgDefaultMarker\",\n    \"tornado.web._HandlerDelegate\",\n    \"tornado.web._RequestHandlerType\",\n    \"_RequestHandlerType\",\n    \"traceback\",\n    \"WSGIAppType\",\n    \"Yieldable\",\n}\n\n\ndef missing_reference_handler(app, env, node, contnode):\n    if node[\"reftarget\"] in missing_references:\n        raise sphinx.errors.NoUri\n\n\ndef setup(app):\n    app.connect(\"missing-reference\", missing_reference_handler)\n\n\n# Read the Docs configuration updates from\n# https://about.readthedocs.com/blog/2024/07/addons-by-default/\n\n# Define the canonical URL if you are using a custom domain on Read the Docs\nhtml_baseurl = os.environ.get(\"READTHEDOCS_CANONICAL_URL\", \"\")\n\n# Tell Jinja2 templates the build is running on Read the Docs\nif os.environ.get(\"READTHEDOCS\", \"\") == \"True\":\n    if \"html_context\" not in globals():\n        html_context = {}\n    html_context[\"READTHEDOCS\"] = True\n"
  },
  {
    "path": "docs/coroutine.rst",
    "content": "Coroutines and concurrency\n==========================\n\n.. toctree::\n\n   gen\n   locks\n   queues\n   process\n"
  },
  {
    "path": "docs/escape.rst",
    "content": "``tornado.escape`` --- Escaping and string manipulation\n=======================================================\n\n.. automodule:: tornado.escape\n\n   Escaping functions\n   ------------------\n\n   .. autofunction:: xhtml_escape\n   .. autofunction:: xhtml_unescape\n\n   .. autofunction:: url_escape\n   .. autofunction:: url_unescape\n\n   .. autofunction:: json_encode\n   .. autofunction:: json_decode\n\n   Byte/unicode conversions\n   ------------------------\n\n   .. autofunction:: utf8\n   .. autofunction:: to_unicode\n   .. function:: native_str\n   .. function:: to_basestring\n\n      Converts a byte or unicode string into type `str`. These functions\n      were used to help transition from Python 2 to Python 3 but are now\n      deprecated aliases for `to_unicode`.\n\n   .. autofunction:: recursive_unicode\n\n   Miscellaneous functions\n   -----------------------\n   .. autofunction:: linkify\n   .. autofunction:: squeeze\n"
  },
  {
    "path": "docs/faq.rst",
    "content": "Frequently Asked Questions\n==========================\n\n.. contents::\n   :local:\n\nWhy isn't this example with ``time.sleep()`` running in parallel?\n-----------------------------------------------------------------\n\nMany people's first foray into Tornado's concurrency looks something like\nthis::\n\n   class BadExampleHandler(RequestHandler):\n       def get(self):\n           for i in range(5):\n               print(i)\n               time.sleep(1)\n\nFetch this handler twice at the same time and you'll see that the second\nfive-second countdown doesn't start until the first one has completely\nfinished. The reason for this is that `time.sleep` is a **blocking**\nfunction: it doesn't allow control to return to the `.IOLoop` so that other\nhandlers can be run.\n\nOf course, `time.sleep` is really just a placeholder in these examples,\nthe point is to show what happens when something in a handler gets slow.\nNo matter what the real code is doing, to achieve concurrency blocking\ncode must be replaced with non-blocking equivalents. This means one of three things:\n\n1. *Find a coroutine-friendly equivalent.* For `time.sleep`, use\n   `tornado.gen.sleep` (or `asyncio.sleep`) instead::\n\n    class CoroutineSleepHandler(RequestHandler):\n        async def get(self):\n            for i in range(5):\n                print(i)\n                await gen.sleep(1)\n\n   When this option is available, it is usually the best approach.\n   See the `Tornado wiki <https://github.com/tornadoweb/tornado/wiki/Links>`_\n   for links to asynchronous libraries that may be useful.\n\n2. *Find a callback-based equivalent.* Similar to the first option,\n   callback-based libraries are available for many tasks, although they\n   are slightly more complicated to use than a library designed for\n   coroutines. Adapt the callback-based function into a future::\n\n    class CoroutineTimeoutHandler(RequestHandler):\n        async def get(self):\n            io_loop = IOLoop.current()\n            for i in range(5):\n                print(i)\n                f = tornado.concurrent.Future()\n                do_something_with_callback(f.set_result)\n                result = await f\n\n   Again, the\n   `Tornado wiki <https://github.com/tornadoweb/tornado/wiki/Links>`_\n   can be useful to find suitable libraries.\n\n3. *Run the blocking code on another thread.* When asynchronous libraries\n   are not available, `concurrent.futures.ThreadPoolExecutor` can be used\n   to run any blocking code on another thread. This is a universal solution\n   that can be used for any blocking function whether an asynchronous\n   counterpart exists or not::\n\n    class ThreadPoolHandler(RequestHandler):\n        async def get(self):\n            for i in range(5):\n                print(i)\n                await IOLoop.current().run_in_executor(None, time.sleep, 1)\n\nSee the :doc:`Asynchronous I/O <guide/async>` chapter of the Tornado\nuser's guide for more on blocking and asynchronous functions.\n\n\nMy code is asynchronous. Why is it not running in parallel in two browser tabs?\n-------------------------------------------------------------------------------\n\nEven when a handler is asynchronous and non-blocking, it can be surprisingly\ntricky to verify this. Browsers will recognize that you are trying to\nload the same page in two different tabs and delay the second request\nuntil the first has finished. To work around this and see that the server\nis in fact working in parallel, do one of two things:\n\n* Add something to your urls to make them unique. Instead of\n  ``http://localhost:8888`` in both tabs, load\n  ``http://localhost:8888/?x=1`` in one and\n  ``http://localhost:8888/?x=2`` in the other.\n\n* Use two different browsers. For example, Firefox will be able to load\n  a url even while that same url is being loaded in a Chrome tab.\n"
  },
  {
    "path": "docs/gen.rst",
    "content": "``tornado.gen`` --- Generator-based coroutines\n==============================================\n\n.. testsetup::\n\n   from tornado.web import *\n   from tornado import gen\n\n.. automodule:: tornado.gen\n\n   Decorators\n   ----------\n\n   .. autofunction:: coroutine\n\n   .. autoexception:: Return\n\n   Utility functions\n   -----------------\n\n   .. autofunction:: with_timeout(timeout: Union[float, datetime.timedelta], future: Yieldable, quiet_exceptions: Union[Type[Exception], Tuple[Type[Exception], ...]] = ())\n\n   .. autofunction:: sleep\n\n   .. autoclass:: WaitIterator\n      :members:\n\n   .. autofunction:: multi(Union[List[Yieldable], Dict[Any, Yieldable]], quiet_exceptions: Union[Type[Exception], Tuple[Type[Exception], ...]] = ())\n\n   .. autofunction:: multi_future(Union[List[Yieldable], Dict[Any, Yieldable]], quiet_exceptions: Union[Type[Exception], Tuple[Type[Exception], ...]] = ())\n\n   .. autofunction:: convert_yielded\n\n   .. autofunction:: maybe_future\n\n   .. autofunction:: is_coroutine_function\n\n   .. autodata:: moment\n      :annotation:\n"
  },
  {
    "path": "docs/guide/async.rst",
    "content": "Asynchronous and non-Blocking I/O\n---------------------------------\n\nReal-time web features require a long-lived mostly-idle connection per\nuser.  In a traditional synchronous web server, this implies devoting\none thread to each user, which can be very expensive.\n\nTo minimize the cost of concurrent connections, Tornado uses a\nsingle-threaded event loop.  This means that all application code\nshould aim to be asynchronous and non-blocking because only one\noperation can be active at a time.\n\nThe terms asynchronous and non-blocking are closely related and are\noften used interchangeably, but they are not quite the same thing.\n\nBlocking\n~~~~~~~~\n\nA function **blocks** when it waits for something to happen before\nreturning.  A function may block for many reasons: network I/O, disk\nI/O, mutexes, etc.  In fact, *every* function blocks, at least a\nlittle bit, while it is running and using the CPU (for an extreme\nexample that demonstrates why CPU blocking must be taken as seriously\nas other kinds of blocking, consider password hashing functions like\n`bcrypt <http://bcrypt.sourceforge.net/>`_, which by design use\nhundreds of milliseconds of CPU time, far more than a typical network\nor disk access).\n\nA function can be blocking in some respects and non-blocking in\nothers.  In the context of Tornado we generally talk about\nblocking in the context of network I/O, although all kinds of blocking\nare to be minimized.\n\nAsynchronous\n~~~~~~~~~~~~\n\nAn **asynchronous** function returns before it is finished, and\ngenerally causes some work to happen in the background before\ntriggering some future action in the application (as opposed to normal\n**synchronous** functions, which do everything they are going to do\nbefore returning).  There are many styles of asynchronous interfaces:\n\n* Callback argument\n* Return a placeholder (`.Future`, ``Promise``, ``Deferred``)\n* Deliver to a queue\n* Callback registry (e.g. POSIX signals)\n\nRegardless of which type of interface is used, asynchronous functions\n*by definition* interact differently with their callers; there is no\nfree way to make a synchronous function asynchronous in a way that is\ntransparent to its callers (systems like `gevent\n<http://www.gevent.org>`_ use lightweight threads to offer performance\ncomparable to asynchronous systems, but they do not actually make\nthings asynchronous).\n\nAsynchronous operations in Tornado generally return placeholder\nobjects (``Futures``), with the exception of some low-level components\nlike the `.IOLoop` that use callbacks. ``Futures`` are usually\ntransformed into their result with the ``await`` or ``yield``\nkeywords.\n\nExamples\n~~~~~~~~\n\nHere is a sample synchronous function:\n\n.. testcode::\n\n    from tornado.httpclient import HTTPClient\n\n    def synchronous_fetch(url):\n        http_client = HTTPClient()\n        response = http_client.fetch(url)\n        return response.body\n\nAnd here is the same function rewritten asynchronously as a native coroutine:\n\n.. testcode::\n\n   from tornado.httpclient import AsyncHTTPClient\n\n   async def asynchronous_fetch(url):\n       http_client = AsyncHTTPClient()\n       response = await http_client.fetch(url)\n       return response.body\n\nOr for compatibility with older versions of Python, using the `tornado.gen` module:\n\n..  testcode::\n\n    from tornado.httpclient import AsyncHTTPClient\n    from tornado import gen\n\n    @gen.coroutine\n    def async_fetch_gen(url):\n        http_client = AsyncHTTPClient()\n        response = yield http_client.fetch(url)\n        raise gen.Return(response.body)\n\nCoroutines are a little magical, but what they do internally is something like this:\n\n.. testcode::\n\n    from tornado.concurrent import Future\n\n    def async_fetch_manual(url):\n        http_client = AsyncHTTPClient()\n        my_future = Future()\n        fetch_future = http_client.fetch(url)\n        def on_fetch(f):\n            my_future.set_result(f.result().body)\n        fetch_future.add_done_callback(on_fetch)\n        return my_future\n\nNotice that the coroutine returns its `.Future` before the fetch is\ndone. This is what makes coroutines *asynchronous*.\n\nAnything you can do with coroutines you can also do by passing\ncallback objects around, but coroutines provide an important\nsimplification by letting you organize your code in the same way you\nwould if it were synchronous. This is especially important for error\nhandling, since ``try``/``except`` blocks work as you would expect in\ncoroutines while this is difficult to achieve with callbacks.\nCoroutines will be discussed in depth in the next section of this\nguide.\n"
  },
  {
    "path": "docs/guide/coroutines.rst",
    "content": "Coroutines\n==========\n\n.. testsetup::\n\n   from tornado import gen\n\n**Coroutines** are the recommended way to write asynchronous code in\nTornado. Coroutines use the Python ``await`` keyword to\nsuspend and resume execution instead of a chain of callbacks\n(cooperative lightweight threads as seen in frameworks like `gevent\n<http://www.gevent.org>`_ are sometimes called coroutines as well, but\nin Tornado all coroutines use explicit context switches and are called\nas asynchronous functions).\n\nCoroutines are almost as simple as synchronous code, but without the\nexpense of a thread.  They also `make concurrency easier\n<https://glyph.twistedmatrix.com/2014/02/unyielding.html>`_ to reason\nabout by reducing the number of places where a context switch can\nhappen.\n\nExample::\n\n    async def fetch_coroutine(url):\n        http_client = AsyncHTTPClient()\n        response = await http_client.fetch(url)\n        return response.body\n\n.. _native_coroutines:\n\nNative vs decorated coroutines\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nPython 3.5 introduced the ``async`` and ``await`` keywords (functions\nusing these keywords are also called \"native coroutines\"). For\ncompatibility with older versions of Python, you can use \"decorated\"\nor \"yield-based\" coroutines using the `tornado.gen.coroutine`\ndecorator.\n\nNative coroutines are the recommended form whenever possible. Only use\ndecorated coroutines when compatibility with older versions of Python\nis required. Examples in the Tornado documentation will generally use\nthe native form.\n\nTranslation between the two forms is generally straightforward::\n\n    # Decorated:                    # Native:\n\n    # Normal function declaration\n    # with decorator                # \"async def\" keywords\n    @gen.coroutine\n    def a():                        async def a():\n        # \"yield\" all async funcs       # \"await\" all async funcs\n        b = yield c()                   b = await c()\n        # \"return\" and \"yield\"\n        # cannot be mixed in\n        # Python 2, so raise a\n        # special exception.            # Return normally\n        raise gen.Return(b)             return b\n\nOther differences between the two forms of coroutine are outlined below.\n\n- Native coroutines:\n\n  - are generally faster.\n  - can use ``async for`` and ``async with``\n    statements which make some patterns much simpler.\n  - do not run at all unless you ``await`` or\n    ``yield`` them. Decorated coroutines can start running \"in the\n    background\" as soon as they are called. Note that for both kinds of\n    coroutines it is important to use ``await`` or ``yield`` so that\n    any exceptions have somewhere to go.\n\n- Decorated coroutines:\n\n  - have additional integration with the\n    `concurrent.futures` package, allowing the result of\n    ``executor.submit`` to be yielded directly. For native coroutines,\n    use `.IOLoop.run_in_executor` instead.\n  - support some shorthand for waiting on multiple\n    objects by yielding a list or dict. Use `tornado.gen.multi` to do\n    this in native coroutines.\n  - can support integration with other packages\n    including Twisted via a registry of conversion functions.\n    To access this functionality in native coroutines, use\n    `tornado.gen.convert_yielded`.\n  - always return a `.Future` object. Native\n    coroutines return an *awaitable* object that is not a `.Future`. In\n    Tornado the two are mostly interchangeable.\n\nHow it works\n~~~~~~~~~~~~\n\nThis section explains the operation of decorated coroutines. Native\ncoroutines are conceptually similar, but a little more complicated\nbecause of the extra integration with the Python runtime.\n\nA function containing ``yield`` is a **generator**.  All generators\nare asynchronous; when called they return a generator object instead\nof running to completion.  The ``@gen.coroutine`` decorator\ncommunicates with the generator via the ``yield`` expressions, and\nwith the coroutine's caller by returning a `.Future`.\n\nHere is a simplified version of the coroutine decorator's inner loop::\n\n    # Simplified inner loop of tornado.gen.Runner\n    def run(self):\n        # send(x) makes the current yield return x.\n        # It returns when the next yield is reached\n        future = self.gen.send(self.next)\n        def callback(f):\n            self.next = f.result()\n            self.run()\n        future.add_done_callback(callback)\n\nThe decorator receives a `.Future` from the generator, waits (without\nblocking) for that `.Future` to complete, then \"unwraps\" the `.Future`\nand sends the result back into the generator as the result of the\n``yield`` expression.  Most asynchronous code never touches the `.Future`\nclass directly except to immediately pass the `.Future` returned by\nan asynchronous function to a ``yield`` expression.\n\nHow to call a coroutine\n~~~~~~~~~~~~~~~~~~~~~~~\n\nCoroutines do not raise exceptions in the normal way: any exception\nthey raise will be trapped in the awaitable object until it is\nyielded. This means it is important to call coroutines in the right\nway, or you may have errors that go unnoticed::\n\n    async def divide(x, y):\n        return x / y\n\n    def bad_call():\n        # This should raise a ZeroDivisionError, but it won't because\n        # the coroutine is called incorrectly.\n        divide(1, 0)\n\nIn nearly all cases, any function that calls a coroutine must be a\ncoroutine itself, and use the ``await`` or ``yield`` keyword in the\ncall. When you are overriding a method defined in a superclass,\nconsult the documentation to see if coroutines are allowed (the\ndocumentation should say that the method \"may be a coroutine\" or \"may\nreturn a `.Future`\")::\n\n    async def good_call():\n        # await will unwrap the object returned by divide() and raise\n        # the exception.\n        await divide(1, 0)\n\nSometimes you may want to \"fire and forget\" a coroutine without waiting\nfor its result. In this case it is recommended to use `.IOLoop.spawn_callback`,\nwhich makes the `.IOLoop` responsible for the call. If it fails,\nthe `.IOLoop` will log a stack trace::\n\n    # The IOLoop will catch the exception and print a stack trace in\n    # the logs. Note that this doesn't look like a normal call, since\n    # we pass the function object to be called by the IOLoop.\n    IOLoop.current().spawn_callback(divide, 1, 0)\n\nUsing `.IOLoop.spawn_callback` in this way is *recommended* for\nfunctions using ``@gen.coroutine``, but it is *required* for functions\nusing ``async def`` (otherwise the coroutine runner will not start).\n\nFinally, at the top level of a program, *if the IOLoop is not yet\nrunning,* you can start the `.IOLoop`, run the coroutine, and then\nstop the `.IOLoop` with the `.IOLoop.run_sync` method. This is often\nused to start the ``main`` function of a batch-oriented program::\n\n    # run_sync() doesn't take arguments, so we must wrap the\n    # call in a lambda.\n    IOLoop.current().run_sync(lambda: divide(1, 0))\n\nCoroutine patterns\n~~~~~~~~~~~~~~~~~~\n\nCalling blocking functions\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe simplest way to call a blocking function from a coroutine is to\nuse `.IOLoop.run_in_executor`, which returns\n``Futures`` that are compatible with coroutines::\n\n    async def call_blocking():\n        await IOLoop.current().run_in_executor(None, blocking_func, args)\n\nParallelism\n^^^^^^^^^^^\n\nThe `.multi` function accepts lists and dicts whose values are\n``Futures``, and waits for all of those ``Futures`` in parallel:\n\n.. testcode::\n\n    from tornado.gen import multi\n\n    async def parallel_fetch(url1, url2):\n        resp1, resp2 = await multi([http_client.fetch(url1),\n                                    http_client.fetch(url2)])\n\n    async def parallel_fetch_many(urls):\n        responses = await multi ([http_client.fetch(url) for url in urls])\n        # responses is a list of HTTPResponses in the same order\n\n    async def parallel_fetch_dict(urls):\n        responses = await multi({url: http_client.fetch(url)\n                                 for url in urls})\n        # responses is a dict {url: HTTPResponse}\n\nIn decorated coroutines, it is possible to ``yield`` the list or dict directly::\n\n    @gen.coroutine\n    def parallel_fetch_decorated(url1, url2):\n        resp1, resp2 = yield [http_client.fetch(url1),\n                              http_client.fetch(url2)]\n\nInterleaving\n^^^^^^^^^^^^\n\nSometimes it is useful to save a `.Future` instead of yielding it\nimmediately, so you can start another operation before waiting.\n\n.. testcode::\n\n    from tornado.gen import convert_yielded\n\n    async def get(self):\n        # convert_yielded() starts the native coroutine in the background.\n        # This is equivalent to asyncio.ensure_future() (both work in Tornado).\n        fetch_future = convert_yielded(self.fetch_next_chunk())\n        while True:\n            chunk = await fetch_future\n            if chunk is None: break\n            self.write(chunk)\n            fetch_future = convert_yielded(self.fetch_next_chunk())\n            await self.flush()\n\nThis is a little easier to do with decorated coroutines, because they\nstart immediately when called:\n\n.. testcode::\n\n    @gen.coroutine\n    def get(self):\n        fetch_future = self.fetch_next_chunk()\n        while True:\n            chunk = yield fetch_future\n            if chunk is None: break\n            self.write(chunk)\n            fetch_future = self.fetch_next_chunk()\n            yield self.flush()\n\nLooping\n^^^^^^^\n\nIn native coroutines, ``async for`` can be used. In older versions of\nPython, looping is tricky with coroutines since there is no way to\n``yield`` on every iteration of a ``for`` or ``while`` loop and\ncapture the result of the yield. Instead, you'll need to separate the\nloop condition from accessing the results, as in this example from\n`Motor <https://motor.readthedocs.io/en/stable/>`_::\n\n    import motor\n    db = motor.MotorClient().test\n\n    @gen.coroutine\n    def loop_example(collection):\n        cursor = db.collection.find()\n        while (yield cursor.fetch_next):\n            doc = cursor.next_object()\n\nRunning in the background\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAs an alternative to `.PeriodicCallback`, a\ncoroutine can contain a ``while True:`` loop and use\n`tornado.gen.sleep`::\n\n    async def minute_loop():\n        while True:\n            await do_something()\n            await gen.sleep(60)\n\n    # Coroutines that loop forever are generally started with\n    # spawn_callback().\n    IOLoop.current().spawn_callback(minute_loop)\n\nSometimes a more complicated loop may be desirable. For example, the\nprevious loop runs every ``60+N`` seconds, where ``N`` is the running\ntime of ``do_something()``. To run exactly every 60 seconds, use the\ninterleaving pattern from above::\n\n    async def minute_loop2():\n        while True:\n            nxt = gen.sleep(60)   # Start the clock.\n            await do_something()  # Run while the clock is ticking.\n            await nxt             # Wait for the timer to run out.\n"
  },
  {
    "path": "docs/guide/intro.rst",
    "content": "Introduction\n------------\n\n`Tornado <http://www.tornadoweb.org>`_ is a Python web framework and\nasynchronous networking library, originally developed at `FriendFeed\n<https://en.wikipedia.org/wiki/FriendFeed>`_.  By using non-blocking network I/O, Tornado\ncan scale to tens of thousands of open connections, making it ideal for\n`long polling <http://en.wikipedia.org/wiki/Push_technology#Long_polling>`_,\n`WebSockets <http://en.wikipedia.org/wiki/WebSocket>`_, and other\napplications that require a long-lived connection to each user.\n\nTornado can be roughly divided into three major components:\n\n* A web framework (including `.RequestHandler` which is subclassed to\n  create web applications, and various supporting classes).\n* Client- and server-side implementions of HTTP (`.HTTPServer` and\n  `.AsyncHTTPClient`).\n* An asynchronous networking library including the classes `.IOLoop`\n  and `.IOStream`, which serve as the building blocks for the HTTP\n  components and can also be used to implement other protocols.\n\nThe Tornado web framework and HTTP server together offer a full-stack\nalternative to `WSGI <http://www.python.org/dev/peps/pep-3333/>`_.\nWhile it is possible to use the Tornado HTTP server as a container for\nother WSGI frameworks (`.WSGIContainer`), this combination has\nlimitations and to take full advantage of Tornado you will need to use\nTornado's web framework and HTTP server together.\n"
  },
  {
    "path": "docs/guide/queues.rst",
    "content": ":class:`~tornado.queues.Queue` example - a concurrent web spider\n================================================================\n\n.. currentmodule:: tornado.queues\n\nTornado's `tornado.queues` module (and the very similar ``Queue`` classes in\n`asyncio`) implements an asynchronous producer / consumer pattern for\ncoroutines, analogous to the pattern implemented for threads by the Python\nstandard library's `queue` module.\n\nA coroutine that yields `Queue.get` pauses until there is an item in the queue.\nIf the queue has a maximum size set, a coroutine that yields `Queue.put` pauses\nuntil there is room for another item.\n\nA `~Queue` maintains a count of unfinished tasks, which begins at zero.\n`~Queue.put` increments the count; `~Queue.task_done` decrements it.\n\nIn the web-spider example here, the queue begins containing only base_url. When\na worker fetches a page it parses the links and puts new ones in the queue,\nthen calls `~Queue.task_done` to decrement the counter once. Eventually, a\nworker fetches a page whose URLs have all been seen before, and there is also\nno work left in the queue. Thus that worker's call to `~Queue.task_done`\ndecrements the counter to zero. The main coroutine, which is waiting for\n`~Queue.join`, is unpaused and finishes.\n\n.. literalinclude:: ../../demos/webspider/webspider.py\n"
  },
  {
    "path": "docs/guide/running.rst",
    "content": "Running and deploying\n=====================\n\nSince Tornado supplies its own HTTPServer, running and deploying it is\na little different from other Python web frameworks.  Instead of\nconfiguring a WSGI container to find your application, you write a\n``main()`` function that starts the server:\n\n.. testcode::\n\n    import asyncio\n\n    async def main():\n        app = make_app()\n        app.listen(8888)\n        await asyncio.Event().wait()\n\n    if __name__ == '__main__':\n        asyncio.run(main())\n\nConfigure your operating system or process manager to run this program to\nstart the server. Please note that it may be necessary to increase the number \nof open files per process (to avoid \"Too many open files\"-Error). \nTo raise this limit (setting it to 50000 for example)  you can use the\n``ulimit`` command, modify ``/etc/security/limits.conf`` or set\n``minfds`` in your `supervisord <http://www.supervisord.org>`_ config.\n\nProcesses and ports\n~~~~~~~~~~~~~~~~~~~\n\nDue to the Python GIL (Global Interpreter Lock), it is necessary to run\nmultiple Python processes to take full advantage of multi-CPU machines.\nTypically it is best to run one process per CPU.\n\nThe simplest way to do this is to add ``reuse_port=True`` to your ``listen()``\ncalls and then simply run multiple copies of your application.\n\nTornado also has the ability to start multiple processes from a single parent\nprocess (note that this does not work on Windows). This requires some\nalterations to application startup.\n\n.. testcode::\n\n    def main():\n        sockets = bind_sockets(8888)\n        tornado.process.fork_processes(0)\n        async def post_fork_main():\n            server = TCPServer()\n            server.add_sockets(sockets)\n            await asyncio.Event().wait()\n        asyncio.run(post_fork_main())\n\nThis is another way to start multiple processes and have them all\nshare the same port, although it has some limitations.  First, each\nchild process will have its own ``IOLoop``, so it is important that\nnothing touches the global ``IOLoop`` instance (even indirectly) before the\nfork.  Second, it is difficult to do zero-downtime updates in this model.\nFinally, since all the processes share the same port it is more difficult\nto monitor them individually.\n\nFor more sophisticated deployments, it is recommended to start the processes\nindependently, and have each one listen on a different port.\nThe \"process groups\" feature of `supervisord <http://www.supervisord.org>`_\nis one good way to arrange this.  When each process uses a different port,\nan external load balancer such as HAProxy or nginx is usually needed\nto present a single address to outside visitors.\n\n\nRunning behind a load balancer\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nWhen running behind a load balancer like `nginx <http://nginx.net/>`_,\nit is recommended to pass ``xheaders=True`` to the `.HTTPServer` constructor.\nThis will tell Tornado to use headers like ``X-Real-IP`` to get the user's\nIP address instead of attributing all traffic to the balancer's IP address.\n\nThis is a barebones nginx config file that is structurally similar to\nthe one we use at FriendFeed. It assumes nginx and the Tornado servers\nare running on the same machine, and the four Tornado servers are\nrunning on ports 8000 - 8003::\n\n    user nginx;\n    worker_processes 1;\n\n    error_log /var/log/nginx/error.log;\n    pid /var/run/nginx.pid;\n\n    events {\n        worker_connections 1024;\n        use epoll;\n    }\n\n    http {\n        # Enumerate all the Tornado servers here\n        upstream frontends {\n            server 127.0.0.1:8000;\n            server 127.0.0.1:8001;\n            server 127.0.0.1:8002;\n            server 127.0.0.1:8003;\n        }\n\n        include /etc/nginx/mime.types;\n        default_type application/octet-stream;\n\n        access_log /var/log/nginx/access.log;\n\n        keepalive_timeout 65;\n        proxy_read_timeout 200;\n        sendfile on;\n        tcp_nopush on;\n        tcp_nodelay on;\n        gzip on;\n        gzip_min_length 1000;\n        gzip_proxied any;\n        gzip_types text/plain text/html text/css text/xml\n                   application/x-javascript application/xml\n                   application/atom+xml text/javascript;\n\n        # Only retry if there was a communication error, not a timeout\n        # on the Tornado server (to avoid propagating \"queries of death\"\n        # to all frontends)\n        proxy_next_upstream error;\n\n        server {\n            listen 80;\n\n            # Allow file uploads\n            client_max_body_size 50M;\n\n            location ^~ /static/ {\n                root /var/www;\n                if ($query_string) {\n                    expires max;\n                }\n            }\n            location = /favicon.ico {\n                rewrite (.*) /static/favicon.ico;\n            }\n            location = /robots.txt {\n                rewrite (.*) /static/robots.txt;\n            }\n\n            location / {\n                proxy_pass_header Server;\n                proxy_set_header Host $http_host;\n                proxy_redirect off;\n                proxy_set_header X-Real-IP $remote_addr;\n                proxy_set_header X-Scheme $scheme;\n                proxy_pass http://frontends;\n            }\n        }\n    }\n\nStatic files and aggressive file caching\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYou can serve static files from Tornado by specifying the\n``static_path`` setting in your application::\n\n    settings = {\n        \"static_path\": os.path.join(os.path.dirname(__file__), \"static\"),\n        \"cookie_secret\": \"__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__\",\n        \"login_url\": \"/login\",\n        \"xsrf_cookies\": True,\n    }\n    application = tornado.web.Application([\n        (r\"/\", MainHandler),\n        (r\"/login\", LoginHandler),\n        (r\"/(apple-touch-icon\\.png)\", tornado.web.StaticFileHandler,\n         dict(path=settings['static_path'])),\n    ], **settings)\n\nThis setting will automatically make all requests that start with\n``/static/`` serve from that static directory, e.g.\n``http://localhost:8888/static/foo.png`` will serve the file\n``foo.png`` from the specified static directory. We also automatically\nserve ``/robots.txt`` and ``/favicon.ico`` from the static directory\n(even though they don't start with the ``/static/`` prefix).\n\nIn the above settings, we have explicitly configured Tornado to serve\n``apple-touch-icon.png`` from the root with the `.StaticFileHandler`,\nthough it is physically in the static file directory. (The capturing\ngroup in that regular expression is necessary to tell\n`.StaticFileHandler` the requested filename; recall that capturing\ngroups are passed to handlers as method arguments.) You could do the\nsame thing to serve e.g. ``sitemap.xml`` from the site root. Of\ncourse, you can also avoid faking a root ``apple-touch-icon.png`` by\nusing the appropriate ``<link />`` tag in your HTML.\n\nTo improve performance, it is generally a good idea for browsers to\ncache static resources aggressively so browsers won't send unnecessary\n``If-Modified-Since`` or ``Etag`` requests that might block the\nrendering of the page. Tornado supports this out of the box with *static\ncontent versioning*.\n\nTo use this feature, use the `~.RequestHandler.static_url` method in\nyour templates rather than typing the URL of the static file directly\nin your HTML::\n\n    <html>\n       <head>\n          <title>FriendFeed - {{ _(\"Home\") }}</title>\n       </head>\n       <body>\n         <div><img src=\"{{ static_url(\"images/logo.png\") }}\"/></div>\n       </body>\n     </html>\n\nThe ``static_url()`` function will translate that relative path to a URI\nthat looks like ``/static/images/logo.png?v=aae54``. The ``v`` argument\nis a hash of the content in ``logo.png``, and its presence makes the\nTornado server send cache headers to the user's browser that will make\nthe browser cache the content indefinitely.\n\nSince the ``v`` argument is based on the content of the file, if you\nupdate a file and restart your server, it will start sending a new ``v``\nvalue, so the user's browser will automatically fetch the new file. If\nthe file's contents don't change, the browser will continue to use a\nlocally cached copy without ever checking for updates on the server,\nsignificantly improving rendering performance.\n\nIn production, you probably want to serve static files from a more\noptimized static file server like `nginx <http://nginx.net/>`_. You\ncan configure almost any web server to recognize the version tags used\nby ``static_url()`` and set caching headers accordingly.  Here is the\nrelevant portion of the nginx configuration we use at FriendFeed::\n\n    location /static/ {\n        root /var/friendfeed/static;\n        if ($query_string) {\n            expires max;\n        }\n     }\n\n.. _debug-mode:\n\nDebug mode and automatic reloading\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIf you pass ``debug=True`` to the ``Application`` constructor, the app\nwill be run in debug/development mode. In this mode, several features\nintended for convenience while developing will be enabled (each of which\nis also available as an individual flag; if both are specified the\nindividual flag takes precedence):\n\n* ``autoreload=True``: The app will watch for changes to its source\n  files and reload itself when anything changes. This reduces the need\n  to manually restart the server during development. However, certain\n  failures (such as syntax errors at import time) can still take the\n  server down in a way that debug mode cannot currently recover from.\n* ``compiled_template_cache=False``: Templates will not be cached.\n* ``static_hash_cache=False``: Static file hashes (used by the\n  ``static_url`` function) will not be cached.\n* ``serve_traceback=True``: When an exception in a `.RequestHandler`\n  is not caught, an error page including a stack trace will be\n  generated.\n\nAutoreload mode is not compatible with the multi-process mode of `.HTTPServer`.\nYou must not give `HTTPServer.start <.TCPServer.start>` an argument other than 1 (or\ncall `tornado.process.fork_processes`) if you are using autoreload mode.\n\nThe automatic reloading feature of debug mode is available as a\nstandalone module in `tornado.autoreload`.  The two can be used in\ncombination to provide extra robustness against syntax errors: set\n``autoreload=True`` within the app to detect changes while it is running,\nand start it with ``python -m tornado.autoreload myserver.py`` to catch\nany syntax errors or other errors at startup.\n\nReloading loses any Python interpreter command-line arguments (e.g. ``-u``)\nbecause it re-executes Python using `sys.executable` and `sys.argv`.\nAdditionally, modifying these variables will cause reloading to behave\nincorrectly.\n\nOn some platforms (including Windows and Mac OSX prior to 10.6), the\nprocess cannot be updated \"in-place\", so when a code change is\ndetected the old server exits and a new one starts.  This has been\nknown to confuse some IDEs.\n"
  },
  {
    "path": "docs/guide/security.rst",
    "content": "Authentication and security\n===========================\n\n.. testsetup::\n\n   import tornado\n\nCookies and signed cookies\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYou can set cookies in the user's browser with the ``set_cookie``\nmethod:\n\n.. testcode::\n\n    class MainHandler(tornado.web.RequestHandler):\n        def get(self):\n            if not self.get_cookie(\"mycookie\"):\n                self.set_cookie(\"mycookie\", \"myvalue\")\n                self.write(\"Your cookie was not set yet!\")\n            else:\n                self.write(\"Your cookie was set!\")\n\nCookies are not secure and can easily be modified by clients.  If you\nneed to set cookies to, e.g., identify the currently logged in user,\nyou need to sign your cookies to prevent forgery. Tornado supports\nsigned cookies with the `~.RequestHandler.set_signed_cookie` and\n`~.RequestHandler.get_signed_cookie` methods. To use these methods,\nyou need to specify a secret key named ``cookie_secret`` when you\ncreate your application. You can pass in application settings as\nkeyword arguments to your application:\n\n.. testcode::\n\n    application = tornado.web.Application([\n        (r\"/\", MainHandler),\n    ], cookie_secret=\"__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__\")\n\nSigned cookies contain the encoded value of the cookie in addition to a\ntimestamp and an `HMAC <http://en.wikipedia.org/wiki/HMAC>`_ signature.\nIf the cookie is old or if the signature doesn't match,\n``get_signed_cookie`` will return ``None`` just as if the cookie isn't\nset. The secure version of the example above:\n\n.. testcode::\n\n    class MainHandler(tornado.web.RequestHandler):\n        def get(self):\n            if not self.get_signed_cookie(\"mycookie\"):\n                self.set_signed_cookie(\"mycookie\", \"myvalue\")\n                self.write(\"Your cookie was not set yet!\")\n            else:\n                self.write(\"Your cookie was set!\")\n\nTornado's signed cookies guarantee integrity but not confidentiality.\nThat is, the cookie cannot be modified but its contents can be seen by the\nuser.  The ``cookie_secret`` is a symmetric key and must be kept secret --\nanyone who obtains the value of this key could produce their own signed\ncookies.\n\nBy default, Tornado's signed cookies expire after 30 days.  To change this,\nuse the ``expires_days`` keyword argument to ``set_signed_cookie`` *and* the\n``max_age_days`` argument to ``get_signed_cookie``.  These two values are\npassed separately so that you may e.g. have a cookie that is valid for 30 days\nfor most purposes, but for certain sensitive actions (such as changing billing\ninformation) you use a smaller ``max_age_days`` when reading the cookie.\n\nTornado also supports multiple signing keys to enable signing key\nrotation. ``cookie_secret`` then must be a dict with integer key versions\nas keys and the corresponding secrets as values. The currently used\nsigning key must then be set as ``key_version`` application setting\nbut all other keys in the dict are allowed for cookie signature validation,\nif the correct key version is set in the cookie.\nTo implement cookie updates, the current signing key version can be\nqueried via `~.RequestHandler.get_signed_cookie_key_version`.\n\n.. _user-authentication:\n\nUser authentication\n~~~~~~~~~~~~~~~~~~~\n\nThe currently authenticated user is available in every request handler\nas `self.current_user <.RequestHandler.current_user>`, and in every\ntemplate as ``current_user``. By default, ``current_user`` is\n``None``.\n\nTo implement user authentication in your application, you need to\noverride the ``get_current_user()`` method in your request handlers to\ndetermine the current user based on, e.g., the value of a cookie. Here\nis an example that lets users log into the application simply by\nspecifying a nickname, which is then saved in a cookie:\n\n.. testcode::\n\n    class BaseHandler(tornado.web.RequestHandler):\n        def get_current_user(self):\n            return self.get_signed_cookie(\"user\")\n\n    class MainHandler(BaseHandler):\n        def get(self):\n            if not self.current_user:\n                self.redirect(\"/login\")\n                return\n            name = tornado.escape.xhtml_escape(self.current_user)\n            self.write(\"Hello, \" + name)\n\n    class LoginHandler(BaseHandler):\n        def get(self):\n            self.write('<html><body><form action=\"/login\" method=\"post\">'\n                       'Name: <input type=\"text\" name=\"name\">'\n                       '<input type=\"submit\" value=\"Sign in\">'\n                       '</form></body></html>')\n\n        def post(self):\n            self.set_signed_cookie(\"user\", self.get_argument(\"name\"))\n            self.redirect(\"/\")\n\n    application = tornado.web.Application([\n        (r\"/\", MainHandler),\n        (r\"/login\", LoginHandler),\n    ], cookie_secret=\"__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__\")\n\nYou can require that the user be logged in using the `Python\ndecorator <http://www.python.org/dev/peps/pep-0318/>`_\n`tornado.web.authenticated`. If a request goes to a method with this\ndecorator, and the user is not logged in, they will be redirected to\n``login_url`` (another application setting). The example above could be\nrewritten:\n\n.. testcode::\n\n    class MainHandler(BaseHandler):\n        @tornado.web.authenticated\n        def get(self):\n            name = tornado.escape.xhtml_escape(self.current_user)\n            self.write(\"Hello, \" + name)\n\n    settings = {\n        \"cookie_secret\": \"__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__\",\n        \"login_url\": \"/login\",\n    }\n    application = tornado.web.Application([\n        (r\"/\", MainHandler),\n        (r\"/login\", LoginHandler),\n    ], **settings)\n\nIf you decorate ``post()`` methods with the ``authenticated``\ndecorator, and the user is not logged in, the server will send a\n``403`` response.  The ``@authenticated`` decorator is simply\nshorthand for ``if not self.current_user: self.redirect()`` and may\nnot be appropriate for non-browser-based login schemes.\n\nCheck out the `Tornado Blog example application\n<https://github.com/tornadoweb/tornado/tree/stable/demos/blog>`_ for a\ncomplete example that uses authentication (and stores user data in a\nPostgreSQL database).\n\nThird party authentication\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe `tornado.auth` module implements the authentication and\nauthorization protocols for a number of the most popular sites on the\nweb, including Google/Gmail, Facebook, Twitter, and FriendFeed.\nThe module includes methods to log users in via these sites and, where\napplicable, methods to authorize access to the service so you can, e.g.,\ndownload a user's address book or publish a Twitter message on their\nbehalf.\n\nHere is an example handler that uses Google for authentication, saving\nthe Google credentials in a cookie for later access:\n\n.. testcode::\n\n    class GoogleOAuth2LoginHandler(tornado.web.RequestHandler,\n                                   tornado.auth.GoogleOAuth2Mixin):\n        async def get(self):\n            if self.get_argument('code', False):\n                user = await self.get_authenticated_user(\n                    redirect_uri='http://your.site.com/auth/google',\n                    code=self.get_argument('code'))\n                # Save the user with e.g. set_signed_cookie\n            else:\n                await self.authorize_redirect(\n                    redirect_uri='http://your.site.com/auth/google',\n                    client_id=self.settings['google_oauth']['key'],\n                    scope=['profile', 'email'],\n                    response_type='code',\n                    extra_params={'approval_prompt': 'auto'})\n\nSee the `tornado.auth` module documentation for more details.\n\n.. _xsrf:\n\nCross-site request forgery protection\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n`Cross-site request\nforgery <http://en.wikipedia.org/wiki/Cross-site_request_forgery>`_, or\nXSRF, is a common problem for personalized web applications.\n\nThe generally accepted solution to prevent XSRF is to cookie every user\nwith an unpredictable value and include that value as an additional\nargument with every form submission on your site. If the cookie and the\nvalue in the form submission do not match, then the request is likely\nforged.\n\nTornado comes with built-in XSRF protection. To include it in your site,\ninclude the application setting ``xsrf_cookies``:\n\n.. testcode::\n\n    settings = {\n        \"cookie_secret\": \"__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__\",\n        \"login_url\": \"/login\",\n        \"xsrf_cookies\": True,\n    }\n    application = tornado.web.Application([\n        (r\"/\", MainHandler),\n        (r\"/login\", LoginHandler),\n    ], **settings)\n\nIf ``xsrf_cookies`` is set, the Tornado web application will set the\n``_xsrf`` cookie for all users and reject all ``POST``, ``PUT``, and\n``DELETE`` requests that do not contain a correct ``_xsrf`` value. If\nyou turn this setting on, you need to instrument all forms that submit\nvia ``POST`` to contain this field. You can do this with the special\n`.UIModule` ``xsrf_form_html()``, available in all templates::\n\n    <form action=\"/new_message\" method=\"post\">\n      {% module xsrf_form_html() %}\n      <input type=\"text\" name=\"message\"/>\n      <input type=\"submit\" value=\"Post\"/>\n    </form>\n\nIf you submit AJAX ``POST`` requests, you will also need to instrument\nyour JavaScript to include the ``_xsrf`` value with each request. This\nis the `jQuery <http://jquery.com/>`_ function we use at FriendFeed for\nAJAX ``POST`` requests that automatically adds the ``_xsrf`` value to\nall requests::\n\n    function getCookie(name) {\n        var r = document.cookie.match(\"\\\\b\" + name + \"=([^;]*)\\\\b\");\n        return r ? r[1] : undefined;\n    }\n\n    jQuery.postJSON = function(url, args, callback) {\n        args._xsrf = getCookie(\"_xsrf\");\n        $.ajax({url: url, data: $.param(args), dataType: \"text\", type: \"POST\",\n            success: function(response) {\n            callback(eval(\"(\" + response + \")\"));\n        }});\n    };\n\nFor ``PUT`` and ``DELETE`` requests (as well as ``POST`` requests that\ndo not use form-encoded arguments), the XSRF token may also be passed\nvia an HTTP header named ``X-XSRFToken``.  The XSRF cookie is normally\nset when ``xsrf_form_html`` is used, but in a pure-JavaScript application\nthat does not use any regular forms you may need to access\n``self.xsrf_token`` manually (just reading the property is enough to\nset the cookie as a side effect).\n\nIf you need to customize XSRF behavior on a per-handler basis, you can\noverride `.RequestHandler.check_xsrf_cookie()`. For example, if you\nhave an API whose authentication does not use cookies, you may want to\ndisable XSRF protection by making ``check_xsrf_cookie()`` do nothing.\nHowever, if you support both cookie and non-cookie-based authentication,\nit is important that XSRF protection be used whenever the current\nrequest is authenticated with a cookie.\n\n.. _dnsrebinding:\n\nDNS Rebinding\n~~~~~~~~~~~~~\n\n`DNS rebinding <https://en.wikipedia.org/wiki/DNS_rebinding>`_ is an\nattack that can bypass the same-origin policy and allow external sites\nto access resources on private networks. This attack involves a DNS\nname (with a short TTL) that alternates between returning an IP\naddress controlled by the attacker and one controlled by the victim\n(often a guessable private IP address such as ``127.0.0.1`` or\n``192.168.1.1``).\n\nApplications that use TLS are *not* vulnerable to this attack (because\nthe browser will display certificate mismatch warnings that block\nautomated access to the target site).\n\nApplications that cannot use TLS and rely on network-level access\ncontrols (for example, assuming that a server on ``127.0.0.1`` can\nonly be accessed by the local machine) should guard against DNS\nrebinding by validating the ``Host`` HTTP header. This means passing a\nrestrictive hostname pattern to either a `.HostMatches` router or the\nfirst argument of `.Application.add_handlers`::\n\n    # BAD: uses a default host pattern of r'.*'\n    app = Application([('/foo', FooHandler)])\n\n    # GOOD: only matches localhost or its ip address.\n    app = Application()\n    app.add_handlers(r'(localhost|127\\.0\\.0\\.1)',\n                     [('/foo', FooHandler)])\n\n    # GOOD: same as previous example using tornado.routing.\n    app = Application([\n        (HostMatches(r'(localhost|127\\.0\\.0\\.1)'),\n            [('/foo', FooHandler)]),\n        ])\n\nIn addition, the ``default_host`` argument to `.Application` and the\n`.DefaultHostMatches` router must not be used in applications that may\nbe vulnerable to DNS rebinding, because it has a similar effect to a\nwildcard host pattern.\n"
  },
  {
    "path": "docs/guide/structure.rst",
    "content": ".. currentmodule:: tornado.web\n\n.. testsetup::\n\n   import tornado\n\nStructure of a Tornado web application\n======================================\n\nA Tornado web application generally consists of one or more\n`.RequestHandler` subclasses, an `.Application` object which\nroutes incoming requests to handlers, and a ``main()`` function\nto start the server.\n\nA minimal \"hello world\" example looks something like this:\n\n.. testcode::\n\n    import asyncio\n    import tornado\n\n    class MainHandler(tornado.web.RequestHandler):\n        def get(self):\n            self.write(\"Hello, world\")\n\n    def make_app():\n        return tornado.web.Application([\n            (r\"/\", MainHandler),\n        ])\n\n    async def main():\n        app = make_app()\n        app.listen(8888)\n        shutdown_event = asyncio.Event()\n        await shutdown_event.wait()\n\n    if __name__ == \"__main__\":\n        asyncio.run(main())\n\nThe ``main`` coroutine\n~~~~~~~~~~~~~~~~~~~~~~\n\nBeginning with Tornado 6.2 and Python 3.10, the recommended pattern for starting\na Tornado application is to create a ``main`` coroutine to be run with\n`asyncio.run`. (In older versions, it was common to do initialization in a\nregular function and then start the event loop with\n``IOLoop.current().start()``. However, this pattern produces deprecation\nwarnings starting in Python 3.10 and will break in some future version of\nPython.)\n\nWhen the ``main`` function returns, the program exits, so most of the time for a\nweb server ``main`` should run forever. Waiting on an `asyncio.Event` whose\n``set()`` method is never called is a convenient way to make an asynchronus\nfunction run forever. (and if you wish to have ``main`` exit early as a part of\na graceful shutdown procedure, you can call ``shutdown_event.set()`` to make it\nexit).\n\nThe ``Application`` object\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe `.Application` object is responsible for global configuration, including\nthe routing table that maps requests to handlers.\n\nThe routing table is a list of `.URLSpec` objects (or tuples), each of\nwhich contains (at least) a regular expression and a handler class.\nOrder matters; the first matching rule is used.  If the regular\nexpression contains capturing groups, these groups are the *path\narguments* and will be passed to the handler's HTTP method.  If a\ndictionary is passed as the third element of the `.URLSpec`, it\nsupplies the *initialization arguments* which will be passed to\n`.RequestHandler.initialize`.  Finally, the `.URLSpec` may have a\nname, which will allow it to be used with\n`.RequestHandler.reverse_url`.\n\nFor example, in this fragment the root URL ``/`` is mapped to\n``MainHandler`` and URLs of the form ``/story/`` followed by a number\nare mapped to ``StoryHandler``.  That number is passed (as a string) to\n``StoryHandler.get``.\n\n::\n\n    class MainHandler(RequestHandler):\n        def get(self):\n            self.write('<a href=\"%s\">link to story 1</a>' %\n                       self.reverse_url(\"story\", \"1\"))\n\n    class StoryHandler(RequestHandler):\n        def initialize(self, db):\n            self.db = db\n\n        def get(self, story_id):\n            self.write(\"this is story %s\" % story_id)\n\n    app = Application([\n        url(r\"/\", MainHandler),\n        url(r\"/story/([0-9]+)\", StoryHandler, dict(db=db), name=\"story\")\n        ])\n\nThe `.Application` constructor takes many keyword arguments that\ncan be used to customize the behavior of the application and enable\noptional features; see `.Application.settings` for the complete list.\n\nSubclassing ``RequestHandler``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nMost of the work of a Tornado web application is done in subclasses\nof `.RequestHandler`.  The main entry point for a handler subclass\nis a method named after the HTTP method being handled: ``get()``,\n``post()``, etc.  Each handler may define one or more of these methods\nto handle different HTTP actions.  As described above, these methods\nwill be called with arguments corresponding to the capturing groups\nof the routing rule that matched.\n\nWithin a handler, call methods such as `.RequestHandler.render` or\n`.RequestHandler.write` to produce a response.  ``render()`` loads a\n`.Template` by name and renders it with the given\narguments. ``write()`` is used for non-template-based output; it\naccepts strings, bytes, and dictionaries (dicts will be encoded as\nJSON).\n\nMany methods in `.RequestHandler` are designed to be overridden in\nsubclasses and be used throughout the application.  It is common\nto define a ``BaseHandler`` class that overrides methods such as\n`~.RequestHandler.write_error` and `~.RequestHandler.get_current_user`\nand then subclass your own ``BaseHandler`` instead of `.RequestHandler`\nfor all your specific handlers.\n\nHandling request input\n~~~~~~~~~~~~~~~~~~~~~~\n\nThe request handler can access the object representing the current\nrequest with ``self.request``.  See the class definition for\n`~tornado.httputil.HTTPServerRequest` for a complete list of\nattributes.\n\nRequest data in the formats used by HTML forms will be parsed for you\nand is made available in methods like `~.RequestHandler.get_query_argument`\nand `~.RequestHandler.get_body_argument`.\n\n.. testcode::\n\n    class MyFormHandler(tornado.web.RequestHandler):\n        def get(self):\n            self.write('<html><body><form action=\"/myform\" method=\"POST\">'\n                       '<input type=\"text\" name=\"message\">'\n                       '<input type=\"submit\" value=\"Submit\">'\n                       '</form></body></html>')\n\n        def post(self):\n            self.set_header(\"Content-Type\", \"text/plain\")\n            self.write(\"You wrote \" + self.get_body_argument(\"message\"))\n\nSince the HTML form encoding is ambiguous as to whether an argument is\na single value or a list with one element, `.RequestHandler` has\ndistinct methods to allow the application to indicate whether or not\nit expects a list.  For lists, use\n`~.RequestHandler.get_query_arguments` and\n`~.RequestHandler.get_body_arguments` instead of their singular\ncounterparts.\n\nFiles uploaded via a form are available in ``self.request.files``,\nwhich maps names (the name of the HTML ``<input type=\"file\">``\nelement) to a list of files. Each file is a dictionary of the form\n``{\"filename\":..., \"content_type\":..., \"body\":...}``.  The ``files``\nobject is only present if the files were uploaded with a form wrapper\n(i.e. a ``multipart/form-data`` Content-Type); if this format was not used\nthe raw uploaded data is available in ``self.request.body``.\nBy default uploaded files are fully buffered in memory; if you need to\nhandle files that are too large to comfortably keep in memory see the\n`.stream_request_body` class decorator.\n\nIn the demos directory,\n`file_receiver.py <https://github.com/tornadoweb/tornado/tree/stable/demos/file_upload/>`_\nshows both methods of receiving file uploads.\n\nDue to the quirks of the HTML form encoding (e.g. the ambiguity around\nsingular versus plural arguments), Tornado does not attempt to unify\nform arguments with other types of input.  In particular, we do not\nparse JSON request bodies.  Applications that wish to use JSON instead\nof form-encoding may override `~.RequestHandler.prepare` to parse their\nrequests::\n\n    def prepare(self):\n        if self.request.headers.get(\"Content-Type\", \"\").startswith(\"application/json\"):\n            self.json_args = json.loads(self.request.body)\n        else:\n            self.json_args = None\n\nOverriding RequestHandler methods\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIn addition to ``get()``/``post()``/etc, certain other methods in\n`.RequestHandler` are designed to be overridden by subclasses when\nnecessary. On every request, the following sequence of calls takes\nplace:\n\n1. A new `.RequestHandler` object is created on each request.\n2. `~.RequestHandler.initialize()` is called with the initialization\n   arguments from the `.Application` configuration. ``initialize``\n   should typically just save the arguments passed into member\n   variables; it may not produce any output or call methods like\n   `~.RequestHandler.send_error`.\n3. `~.RequestHandler.prepare()` is called. This is most useful in a\n   base class shared by all of your handler subclasses, as ``prepare``\n   is called no matter which HTTP method is used. ``prepare`` may\n   produce output; if it calls `~.RequestHandler.finish` (or\n   ``redirect``, etc), processing stops here.\n4. One of the HTTP methods is called: ``get()``, ``post()``, ``put()``,\n   etc. If the URL regular expression contains capturing groups, they\n   are passed as arguments to this method.\n5. When the request is finished, `~.RequestHandler.on_finish()` is\n   called. This is generally after ``get()`` or another HTTP method\n   returns.\n\nAll methods designed to be overridden are noted as such in the\n`.RequestHandler` documentation.  Some of the most commonly\noverridden methods include:\n\n- `~.RequestHandler.write_error` -\n  outputs HTML for use on error pages.\n- `~.RequestHandler.on_connection_close` - called when the client\n  disconnects; applications may choose to detect this case and halt\n  further processing.  Note that there is no guarantee that a closed\n  connection can be detected promptly.\n- `~.RequestHandler.get_current_user` - see :ref:`user-authentication`.\n- `~.RequestHandler.get_user_locale` - returns `.Locale` object to use\n  for the current user.\n- `~.RequestHandler.set_default_headers` - may be used to set\n  additional headers on the response (such as a custom ``Server``\n  header).\n\nError Handling\n~~~~~~~~~~~~~~\n\nIf a handler raises an exception, Tornado will call\n`.RequestHandler.write_error` to generate an error page.\n`tornado.web.HTTPError` can be used to generate a specified status\ncode; all other exceptions return a 500 status.\n\nThe default error page includes a stack trace in debug mode and a\none-line description of the error (e.g. \"500: Internal Server Error\")\notherwise.  To produce a custom error page, override\n`RequestHandler.write_error` (probably in a base class shared by all\nyour handlers).  This method may produce output normally via\nmethods such as `~RequestHandler.write` and `~RequestHandler.render`.\nIf the error was caused by an exception, an ``exc_info`` triple will\nbe passed as a keyword argument (note that this exception is not\nguaranteed to be the current exception in `sys.exc_info`, so\n``write_error`` must use e.g.  `traceback.format_exception` instead of\n`traceback.format_exc`).\n\nIt is also possible to generate an error page from regular handler\nmethods instead of ``write_error`` by calling\n`~.RequestHandler.set_status`, writing a response, and returning.\nThe special exception `tornado.web.Finish` may be raised to terminate\nthe handler without calling ``write_error`` in situations where simply\nreturning is not convenient.\n\nFor 404 errors, use the ``default_handler_class`` `Application setting\n<.Application.settings>`.  This handler should override\n`~.RequestHandler.prepare` instead of a more specific method like\n``get()`` so it works with any HTTP method.  It should produce its\nerror page as described above: either by raising a ``HTTPError(404)``\nand overriding ``write_error``, or calling ``self.set_status(404)``\nand producing the response directly in ``prepare()``.\n\nRedirection\n~~~~~~~~~~~\n\nThere are two main ways you can redirect requests in Tornado:\n`.RequestHandler.redirect` and with the `.RedirectHandler`.\n\nYou can use ``self.redirect()`` within a `.RequestHandler` method to\nredirect users elsewhere. There is also an optional parameter\n``permanent`` which you can use to indicate that the redirection is\nconsidered permanent.  The default value of ``permanent`` is\n``False``, which generates a ``302 Found`` HTTP response code and is\nappropriate for things like redirecting users after successful\n``POST`` requests.  If ``permanent`` is ``True``, the ``301 Moved\nPermanently`` HTTP response code is used, which is useful for\ne.g. redirecting to a canonical URL for a page in an SEO-friendly\nmanner.\n\n`.RedirectHandler` lets you configure redirects directly in your\n`.Application` routing table.  For example, to configure a single\nstatic redirect::\n\n    app = tornado.web.Application([\n        url(r\"/app\", tornado.web.RedirectHandler,\n            dict(url=\"http://itunes.apple.com/my-app-id\")),\n        ])\n\n`.RedirectHandler` also supports regular expression substitutions.\nThe following rule redirects all requests beginning with ``/pictures/``\nto the prefix ``/photos/`` instead::\n\n    app = tornado.web.Application([\n        url(r\"/photos/(.*)\", MyPhotoHandler),\n        url(r\"/pictures/(.*)\", tornado.web.RedirectHandler,\n            dict(url=r\"/photos/{0}\")),\n        ])\n\nUnlike `.RequestHandler.redirect`, `.RedirectHandler` uses permanent\nredirects by default.  This is because the routing table does not change\nat runtime and is presumed to be permanent, while redirects found in\nhandlers are likely to be the result of other logic that may change.\nTo send a temporary redirect with a `.RedirectHandler`, add\n``permanent=False`` to the `.RedirectHandler` initialization arguments.\n\nAsynchronous handlers\n~~~~~~~~~~~~~~~~~~~~~\n\nCertain handler methods (including ``prepare()`` and the HTTP verb\nmethods ``get()``/``post()``/etc) may be overridden as coroutines to\nmake the handler asynchronous.\n\nFor example, here is a simple handler using a coroutine:\n\n.. testcode::\n\n    class MainHandler(tornado.web.RequestHandler):\n        async def get(self):\n            http = tornado.httpclient.AsyncHTTPClient()\n            response = await http.fetch(\"http://friendfeed-api.com/v2/feed/bret\")\n            json = tornado.escape.json_decode(response.body)\n            self.write(\"Fetched \" + str(len(json[\"entries\"])) + \" entries \"\n                       \"from the FriendFeed API\")\n\nFor a more advanced asynchronous example, take a look at the `chat\nexample application\n<https://github.com/tornadoweb/tornado/tree/stable/demos/chat>`_, which\nimplements an AJAX chat room using `long polling\n<http://en.wikipedia.org/wiki/Push_technology#Long_polling>`_.  Users\nof long polling may want to override ``on_connection_close()`` to\nclean up after the client closes the connection (but see that method's\ndocstring for caveats).\n"
  },
  {
    "path": "docs/guide/templates.rst",
    "content": "Templates and UI\n================\n\n.. testsetup::\n\n   import tornado\n\nTornado includes a simple, fast, and flexible templating language.\nThis section describes that language as well as related issues\nsuch as internationalization.\n\nTornado can also be used with any other Python template language,\nalthough there is no provision for integrating these systems into\n`.RequestHandler.render`.  Simply render the template to a string\nand pass it to `.RequestHandler.write`\n\nConfiguring templates\n~~~~~~~~~~~~~~~~~~~~~\n\nBy default, Tornado looks for template files in the same directory as\nthe ``.py`` files that refer to them.  To put your template files in a\ndifferent directory, use the ``template_path`` `Application setting\n<.Application.settings>` (or override `.RequestHandler.get_template_path`\nif you have different template paths for different handlers).\n\nTo load templates from a non-filesystem location, subclass\n`tornado.template.BaseLoader` and pass an instance as the\n``template_loader`` application setting.\n\nCompiled templates are cached by default; to turn off this caching\nand reload templates so changes to the underlying files are always\nvisible, use the application settings ``compiled_template_cache=False``\nor ``debug=True``.\n\n\nTemplate syntax\n~~~~~~~~~~~~~~~\n\nA Tornado template is just HTML (or any other text-based format) with\nPython control sequences and expressions embedded within the markup::\n\n    <html>\n       <head>\n          <title>{{ title }}</title>\n       </head>\n       <body>\n         <ul>\n           {% for item in items %}\n             <li>{{ escape(item) }}</li>\n           {% end %}\n         </ul>\n       </body>\n     </html>\n\nIf you saved this template as \"template.html\" and put it in the same\ndirectory as your Python file, you could render this template with:\n\n.. testcode::\n\n    class MainHandler(tornado.web.RequestHandler):\n        def get(self):\n            items = [\"Item 1\", \"Item 2\", \"Item 3\"]\n            self.render(\"template.html\", title=\"My title\", items=items)\n\nTornado templates support *control statements* and *expressions*.\nControl statements are surrounded by ``{%`` and ``%}``, e.g.\n``{% if len(items) > 2 %}``. Expressions are surrounded by ``{{`` and\n``}}``, e.g. ``{{ items[0] }}``.\n\nControl statements more or less map exactly to Python statements. We\nsupport ``if``, ``for``, ``while``, and ``try``, all of which are\nterminated with ``{% end %}``. We also support *template inheritance*\nusing the ``extends`` and ``block`` statements, which are described in\ndetail in the documentation for the `tornado.template`.\n\nExpressions can be any Python expression, including function calls.\nTemplate code is executed in a namespace that includes the following\nobjects and functions. (Note that this list applies to templates\nrendered using `.RequestHandler.render` and\n`~.RequestHandler.render_string`. If you're using the\n`tornado.template` module directly outside of a `.RequestHandler` many\nof these entries are not present).\n\n- ``escape``: alias for `tornado.escape.xhtml_escape`\n- ``xhtml_escape``: alias for `tornado.escape.xhtml_escape`\n- ``url_escape``: alias for `tornado.escape.url_escape`\n- ``json_encode``: alias for `tornado.escape.json_encode`\n- ``squeeze``: alias for `tornado.escape.squeeze`\n- ``linkify``: alias for `tornado.escape.linkify`\n- ``datetime``: the Python `datetime` module\n- ``handler``: the current `.RequestHandler` object\n- ``request``: alias for `handler.request <.HTTPServerRequest>`\n- ``current_user``: alias for `handler.current_user\n  <.RequestHandler.current_user>`\n- ``locale``: alias for `handler.locale <.Locale>`\n- ``_``: alias for `handler.locale.translate <.Locale.translate>`\n- ``static_url``: alias for `handler.static_url <.RequestHandler.static_url>`\n- ``xsrf_form_html``: alias for `handler.xsrf_form_html\n  <.RequestHandler.xsrf_form_html>`\n- ``reverse_url``: alias for `.Application.reverse_url`\n- All entries from the ``ui_methods`` and ``ui_modules``\n  ``Application`` settings\n- Any keyword arguments passed to `~.RequestHandler.render` or\n  `~.RequestHandler.render_string`\n\nWhen you are building a real application, you are going to want to use\nall of the features of Tornado templates, especially template\ninheritance. Read all about those features in the `tornado.template`\nsection (some features, including ``UIModules`` are implemented in the\n`tornado.web` module)\n\nUnder the hood, Tornado templates are translated directly to Python. The\nexpressions you include in your template are copied verbatim into a\nPython function representing your template. We don't try to prevent\nanything in the template language; we created it explicitly to provide\nthe flexibility that other, stricter templating systems prevent.\nConsequently, if you write random stuff inside of your template\nexpressions, you will get random Python errors when you execute the\ntemplate.\n\nSecurity\n~~~~~~~~\n\nInserting untrusted content into a web page can lead to security vulnerabilities such as cross-site\nscripting (XSS). All data that is passed to a template should be *escaped* to prevent these\nvulnerabilities. The correct form of escaping is context-dependent; Tornado's templates are not\naware of the syntax of HTML, JavaScript, etc, and so the template developer must sometimes\nexplicitly apply the correct escaping function.\n\nThe default escaping function is `tornado.escape.xhtml_escape`, which is appropriate for HTML body\ncontent (but not attribute values). In other cases, other functions should be used. In JavaScript,\nuse the `.json_encode` function, e.g. ``<script>var x = {{ json_encode(some_object) }};</script>``.\n`.json_encode` can be used to escape strings, numbers, lists, and dicts. In this example, the\nJavaScript variable ``x`` will be the corresponding JavaScript type (string, number, array, or\nobject), and not the JSON-encoded string representation. Note that it is unsafe to use\n`.json_encode` in the context of a JavaScript string literal (including template strings), only in\nthe top-level syntactic context.\n\nThe automatic escaping behavior can be disabled\nglobally by passing ``autoescape=None`` to the `.Application` or\n`.tornado.template.Loader` constructors, for a template file with the\n``{% autoescape None %}`` directive, or for a single expression by\nreplacing ``{{ ... }}`` with ``{% raw ...%}``. Additionally, in each of\nthese places the name of an alternative escaping function may be used\ninstead of ``None``.\n\nNote that while Tornado's automatic escaping is helpful in avoiding\nXSS vulnerabilities, it is not sufficient in all cases.  Expressions\nthat appear in certain locations, such as in JavaScript or CSS, may need\nadditional escaping.  Additionally, either care must be taken to always\nuse double quotes and `.xhtml_escape` in HTML attributes that may contain\nuntrusted content, or a separate escaping function must be used for\nattributes (see e.g.\n`this blog post <http://wonko.com/post/html-escaping>`_).\n\nInternationalization\n~~~~~~~~~~~~~~~~~~~~\n\nThe locale of the current user (whether they are logged in or not) is\nalways available as ``self.locale`` in the request handler and as\n``locale`` in templates. The name of the locale (e.g., ``en_US``) is\navailable as ``locale.name``, and you can translate strings with the\n`.Locale.translate` method. Templates also have the global function\ncall ``_()`` available for string translation. The translate function\nhas two forms::\n\n    _(\"Translate this string\")\n\nwhich translates the string directly based on the current locale, and::\n\n    _(\"A person liked this\", \"%(num)d people liked this\",\n      len(people)) % {\"num\": len(people)}\n\nwhich translates a string that can be singular or plural based on the\nvalue of the third argument. In the example above, a translation of the\nfirst string will be returned if ``len(people)`` is ``1``, or a\ntranslation of the second string will be returned otherwise.\n\nThe most common pattern for translations is to use Python named\nplaceholders for variables (the ``%(num)d`` in the example above) since\nplaceholders can move around on translation.\n\nHere is a properly internationalized template::\n\n    <html>\n       <head>\n          <title>FriendFeed - {{ _(\"Sign in\") }}</title>\n       </head>\n       <body>\n         <form action=\"{{ request.path }}\" method=\"post\">\n           <div>{{ _(\"Username\") }} <input type=\"text\" name=\"username\"/></div>\n           <div>{{ _(\"Password\") }} <input type=\"password\" name=\"password\"/></div>\n           <div><input type=\"submit\" value=\"{{ _(\"Sign in\") }}\"/></div>\n           {% module xsrf_form_html() %}\n         </form>\n       </body>\n     </html>\n\nBy default, we detect the user's locale using the ``Accept-Language``\nheader sent by the user's browser. We choose ``en_US`` if we can't find\nan appropriate ``Accept-Language`` value. If you let user's set their\nlocale as a preference, you can override this default locale selection\nby overriding `.RequestHandler.get_user_locale`:\n\n.. testcode::\n\n    class BaseHandler(tornado.web.RequestHandler):\n        def get_current_user(self):\n            user_id = self.get_signed_cookie(\"user\")\n            if not user_id: return None\n            return self.backend.get_user_by_id(user_id)\n\n        def get_user_locale(self):\n            if \"locale\" not in self.current_user.prefs:\n                # Use the Accept-Language header\n                return None\n            return self.current_user.prefs[\"locale\"]\n\nIf ``get_user_locale`` returns ``None``, we fall back on the\n``Accept-Language`` header.\n\nThe `tornado.locale` module supports loading translations in two\nformats: the ``.mo`` format used by `gettext` and related tools, and a\nsimple ``.csv`` format.  An application will generally call either\n`tornado.locale.load_translations` or\n`tornado.locale.load_gettext_translations` once at startup; see those\nmethods for more details on the supported formats.\n\nYou can get the list of supported locales in your application with\n`tornado.locale.get_supported_locales()`. The user's locale is chosen\nto be the closest match based on the supported locales. For example, if\nthe user's locale is ``es_GT``, and the ``es`` locale is supported,\n``self.locale`` will be ``es`` for that request. We fall back on\n``en_US`` if no close match can be found.\n\n.. _ui-modules:\n\nUI modules\n~~~~~~~~~~\n\nTornado supports *UI modules* to make it easy to support standard,\nreusable UI widgets across your application. UI modules are like special\nfunction calls to render components of your page, and they can come\npackaged with their own CSS and JavaScript.\n\nFor example, if you are implementing a blog, and you want to have blog\nentries appear on both the blog home page and on each blog entry page,\nyou can make an ``Entry`` module to render them on both pages. First,\ncreate a Python module for your UI modules, e.g. ``uimodules.py``::\n\n    class Entry(tornado.web.UIModule):\n        def render(self, entry, show_comments=False):\n            return self.render_string(\n                \"module-entry.html\", entry=entry, show_comments=show_comments)\n\nTell Tornado to use ``uimodules.py`` using the ``ui_modules`` setting in\nyour application::\n\n    from . import uimodules\n\n    class HomeHandler(tornado.web.RequestHandler):\n        def get(self):\n            entries = self.db.query(\"SELECT * FROM entries ORDER BY date DESC\")\n            self.render(\"home.html\", entries=entries)\n\n    class EntryHandler(tornado.web.RequestHandler):\n        def get(self, entry_id):\n            entry = self.db.get(\"SELECT * FROM entries WHERE id = %s\", entry_id)\n            if not entry: raise tornado.web.HTTPError(404)\n            self.render(\"entry.html\", entry=entry)\n\n    settings = {\n        \"ui_modules\": uimodules,\n    }\n    application = tornado.web.Application([\n        (r\"/\", HomeHandler),\n        (r\"/entry/([0-9]+)\", EntryHandler),\n    ], **settings)\n\nWithin a template, you can call a module with the ``{% module %}``\nstatement.  For example, you could call the ``Entry`` module from both\n``home.html``::\n\n    {% for entry in entries %}\n      {% module Entry(entry) %}\n    {% end %}\n\nand ``entry.html``::\n\n    {% module Entry(entry, show_comments=True) %}\n\nModules can include custom CSS and JavaScript functions by overriding\nthe ``embedded_css``, ``embedded_javascript``, ``javascript_files``, or\n``css_files`` methods::\n\n    class Entry(tornado.web.UIModule):\n        def embedded_css(self):\n            return \".entry { margin-bottom: 1em; }\"\n\n        def render(self, entry, show_comments=False):\n            return self.render_string(\n                \"module-entry.html\", show_comments=show_comments)\n\nModule CSS and JavaScript will be included once no matter how many times\na module is used on a page. CSS is always included in the ``<head>`` of\nthe page, and JavaScript is always included just before the ``</body>``\ntag at the end of the page.\n\nWhen additional Python code is not required, a template file itself may\nbe used as a module. For example, the preceding example could be\nrewritten to put the following in ``module-entry.html``::\n\n    {{ set_resources(embedded_css=\".entry { margin-bottom: 1em; }\") }}\n    <!-- more template html... -->\n\nThis revised template module would be invoked with::\n\n    {% module Template(\"module-entry.html\", show_comments=True) %}\n\nThe ``set_resources`` function is only available in templates invoked\nvia ``{% module Template(...) %}``. Unlike the ``{% include ... %}``\ndirective, template modules have a distinct namespace from their\ncontaining template - they can only see the global template namespace\nand their own keyword arguments.\n"
  },
  {
    "path": "docs/guide.rst",
    "content": "User's guide\n============\n\n.. toctree::\n\n   guide/intro\n   guide/async\n   guide/coroutines\n   guide/queues\n   guide/structure\n   guide/templates\n   guide/security\n   guide/running\n"
  },
  {
    "path": "docs/http.rst",
    "content": "HTTP servers and clients\n========================\n\n.. toctree::\n\n   httpserver\n   httpclient\n   httputil\n   http1connection\n"
  },
  {
    "path": "docs/http1connection.rst",
    "content": "``tornado.http1connection`` -- HTTP/1.x client/server implementation\n====================================================================\n\n.. automodule:: tornado.http1connection\n   :members:\n"
  },
  {
    "path": "docs/httpclient.rst",
    "content": "``tornado.httpclient`` --- Asynchronous HTTP client\n===================================================\n\n.. automodule:: tornado.httpclient\n\n   HTTP client interfaces\n   ----------------------\n\n   .. autoclass:: HTTPClient\n      :members:\n\n   .. autoclass:: AsyncHTTPClient\n      :members:\n\n   Request objects\n   ---------------\n   .. autoclass:: HTTPRequest\n      :members:\n   \n   Response objects\n   ----------------\n   .. autoclass:: HTTPResponse\n      :members:\n\n   Exceptions\n   ----------\n   .. autoexception:: HTTPClientError\n      :members:\n\n   .. exception:: HTTPError\n\n      Alias for `HTTPClientError`.\n\n   Command-line interface\n   ----------------------\n\n   This module provides a simple command-line interface to fetch a url\n   using Tornado's HTTP client.  Example usage::\n\n      # Fetch the url and print its body\n      python -m tornado.httpclient http://www.google.com\n\n      # Just print the headers\n      python -m tornado.httpclient --print_headers --print_body=false http://www.google.com\n\nImplementations\n~~~~~~~~~~~~~~~\n\n.. automodule:: tornado.simple_httpclient\n   \n   .. autoclass:: SimpleAsyncHTTPClient\n      :members:\n\n.. module:: tornado.curl_httpclient\n\n.. class:: CurlAsyncHTTPClient(max_clients=10, defaults=None)\n\n   ``libcurl``-based HTTP client.\n\n   This implementation supports the following arguments, which can be passed\n   to ``configure()`` to control the global singleton, or to the constructor\n   when ``force_instance=True``.\n\n   ``max_clients`` is the number of concurrent requests that can be in progress;\n   when this limit is reached additional requests will be queued.\n\n   ``defaults`` is a dict of parameters that will be used as defaults on all\n   `.HTTPRequest` objects submitted to this client.\n\nExample Code\n~~~~~~~~~~~~\n\n* `A simple webspider <https://github.com/tornadoweb/tornado/blob/stable/demos/webspider/webspider.py>`_\n  shows how to fetch URLs concurrently.\n* `The file uploader demo <https://github.com/tornadoweb/tornado/tree/stable/demos/file_upload/>`_\n  uses either HTTP POST or HTTP PUT to upload files to a server.\n"
  },
  {
    "path": "docs/httpserver.rst",
    "content": "``tornado.httpserver`` --- Non-blocking HTTP server\n===================================================\n\n.. automodule:: tornado.httpserver\n\n   HTTP Server\n   -----------\n   .. autoclass:: HTTPServer(request_callback: Union[httputil.HTTPServerConnectionDelegate, Callable[[httputil.HTTPServerRequest], None]], no_keep_alive: bool = False, xheaders: bool = False, ssl_options: Union[Dict[str, Any], ssl.SSLContext] = None, protocol: Optional[str] = None, decompress_request: bool = False, chunk_size: Optional[int] = None, max_header_size: Optional[int] = None, idle_connection_timeout: Optional[float] = None, body_timeout: Optional[float] = None, max_body_size: Optional[int] = None, max_buffer_size: Optional[int] = None, trusted_downstream: Optional[List[str]] = None)\n      :members:\n\n      The public interface of this class is mostly inherited from\n      `.TCPServer` and is documented under that class.\n"
  },
  {
    "path": "docs/httputil.rst",
    "content": "``tornado.httputil`` --- Manipulate HTTP headers and URLs\n=========================================================\n\n.. testsetup::\n\n   from tornado.httputil import *\n\n.. automodule:: tornado.httputil\n   :members:\n"
  },
  {
    "path": "docs/index.rst",
    "content": ".. title:: Tornado Web Server\n\n.. meta::\n    :google-site-verification: g4bVhgwbVO1d9apCUsT-eKlApg31Cygbp8VGZY8Rf0g\n\n|Tornado Web Server|\n====================\n\n.. |Tornado Web Server| image:: tornado.png\n    :alt: Tornado Web Server\n\n`Tornado <https://www.tornadoweb.org>`_ is a Python web framework and\nasynchronous networking library, originally developed at `FriendFeed\n<https://en.wikipedia.org/wiki/FriendFeed>`_.  By using non-blocking network I/O, Tornado\ncan scale to tens of thousands of open connections, making it ideal for\n`long polling <https://en.wikipedia.org/wiki/Push_technology#Long_polling>`_,\n`WebSockets <https://en.wikipedia.org/wiki/WebSocket>`_, and other\napplications that require a long-lived connection to each user.\n\nQuick links\n-----------\n\n* Current version: |version| (`download from PyPI <https://pypi.python.org/pypi/tornado>`_, :doc:`release notes <releases>`)\n* `Source (GitHub) <https://github.com/tornadoweb/tornado>`_\n* Mailing lists: `discussion <https://groups.google.com/forum/#!forum/python-tornado>`_ and `announcements <https://groups.google.com/forum/#!forum/python-tornado-announce>`_\n* `Stack Overflow <https://stackoverflow.com/questions/tagged/tornado>`_\n* `Wiki <https://github.com/tornadoweb/tornado/wiki/Links>`_\n\nHello, world\n------------\n\nHere is a simple \"Hello, world\" example web app for Tornado::\n\n    import asyncio\n    import tornado\n\n    class MainHandler(tornado.web.RequestHandler):\n        def get(self):\n            self.write(\"Hello, world\")\n\n    def make_app():\n        return tornado.web.Application([\n            (r\"/\", MainHandler),\n        ])\n\n    async def main():\n        app = make_app()\n        app.listen(8888)\n        await asyncio.Event().wait()\n\n    if __name__ == \"__main__\":\n        asyncio.run(main())\n\nThis example does not use any of Tornado's asynchronous features; for\nthat see this `simple chat room\n<https://github.com/tornadoweb/tornado/tree/stable/demos/chat>`_.\n\nThreads and WSGI\n----------------\n\nTornado is different from most Python web frameworks. It is not based\non `WSGI <https://wsgi.readthedocs.io/en/latest/>`_, and it is\ntypically run with only one thread per process. See the :doc:`guide`\nfor more on Tornado's approach to asynchronous programming.\n\nWhile some support of WSGI is available in the `tornado.wsgi` module,\nit is not a focus of development and most applications should be\nwritten to use Tornado's own interfaces (such as `tornado.web`)\ndirectly instead of using WSGI.\n\nIn general, Tornado code is not thread-safe. The only method in\nTornado that is safe to call from other threads is\n`.IOLoop.add_callback`. You can also use `.IOLoop.run_in_executor` to\nasynchronously run a blocking function on another thread, but note\nthat the function passed to ``run_in_executor`` should avoid\nreferencing any Tornado objects. ``run_in_executor`` is the\nrecommended way to interact with blocking code.\n\n``asyncio`` Integration\n-----------------------\n\nTornado is integrated with the standard library `asyncio` module and\nshares the same event loop (by default since Tornado 5.0). In general,\nlibraries designed for use with `asyncio` can be mixed freely with\nTornado.\n\n\nInstallation\n------------\n\n::\n\n    pip install tornado\n\nTornado is listed in `PyPI <https://pypi.org/project/tornado/>`_ and\ncan be installed with ``pip``. Note that the source distribution\nincludes demo applications that are not present when Tornado is\ninstalled in this way, so you may wish to download a copy of the\nsource tarball or clone the `git repository\n<https://github.com/tornadoweb/tornado>`_ as well.\n\n**Prerequisites**: Tornado requires Python 3. The following\noptional packages may be useful:\n\n* `pycurl <http://pycurl.io/>`_ is used by the optional\n  ``tornado.curl_httpclient``.  Libcurl version 7.22 or higher is required.\n* `pycares <https://pypi.org/project/pycares/>`_ is an alternative\n  non-blocking DNS resolver that can be used when threads are not\n  appropriate.\n\n**Platforms**: Tornado is designed for Unix-like platforms, with best\nperformance and scalability on systems supporting ``epoll`` (Linux),\n``kqueue`` (BSD/macOS), or ``/dev/poll`` (Solaris).\n\nTornado will also run on Windows, although this configuration is not\nofficially supported or recommended for production use. Some features\nare missing on Windows (including multi-process mode) and scalability\nis limited (Even though Tornado is built on ``asyncio``, which\nsupports Windows, Tornado does not use the APIs that are necessary for\nscalable networking on Windows).\n\nDocumentation\n-------------\n\nThis documentation is also available in `PDF and Epub formats\n<https://readthedocs.org/projects/tornado/downloads/>`_.\n\n.. toctree::\n   :titlesonly:\n\n   guide\n   webframework\n   http\n   networking\n   coroutine\n   integration\n   utilities\n   faq\n   releases\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n\nDiscussion and support\n----------------------\n\nYou can discuss Tornado on `the Tornado developer mailing list\n<https://groups.google.com/forum/#!forum/python-tornado>`_, and report bugs on\nthe `GitHub issue tracker\n<https://github.com/tornadoweb/tornado/issues>`_.  Links to additional\nresources can be found on the `Tornado wiki\n<https://github.com/tornadoweb/tornado/wiki/Links>`_.  New releases are\nannounced on the `announcements mailing list\n<https://groups.google.com/forum/#!forum/python-tornado-announce>`_.\n\nTornado is available under\nthe `Apache License, Version 2.0\n<http://www.apache.org/licenses/LICENSE-2.0.html>`_.\n\nThis web site and all documentation is licensed under `Creative\nCommons 3.0 <https://creativecommons.org/licenses/by/3.0/>`_.\n"
  },
  {
    "path": "docs/integration.rst",
    "content": "Integration with other services\n===============================\n\n.. toctree::\n\n   auth\n   wsgi\n   caresresolver\n   twisted\n   asyncio\n"
  },
  {
    "path": "docs/ioloop.rst",
    "content": "``tornado.ioloop`` --- Main event loop\n======================================\n\n.. automodule:: tornado.ioloop\n\n   IOLoop objects\n   --------------\n\n   .. autoclass:: IOLoop\n\n   Running an IOLoop\n   ^^^^^^^^^^^^^^^^^\n\n   .. automethod:: IOLoop.current\n   .. automethod:: IOLoop.make_current\n   .. automethod:: IOLoop.clear_current\n   .. automethod:: IOLoop.start\n   .. automethod:: IOLoop.stop\n   .. automethod:: IOLoop.run_sync\n   .. automethod:: IOLoop.close\n   .. automethod:: IOLoop.instance\n   .. automethod:: IOLoop.install\n   .. automethod:: IOLoop.clear_instance\n\n   I/O events\n   ^^^^^^^^^^\n\n   .. automethod:: IOLoop.add_handler\n   .. automethod:: IOLoop.update_handler\n   .. automethod:: IOLoop.remove_handler\n\n   Callbacks and timeouts\n   ^^^^^^^^^^^^^^^^^^^^^^\n\n   .. automethod:: IOLoop.add_callback\n   .. automethod:: IOLoop.add_callback_from_signal\n   .. automethod:: IOLoop.add_future\n   .. automethod:: IOLoop.add_timeout\n   .. automethod:: IOLoop.call_at\n   .. automethod:: IOLoop.call_later\n   .. automethod:: IOLoop.remove_timeout\n   .. automethod:: IOLoop.spawn_callback\n   .. automethod:: IOLoop.run_in_executor\n   .. automethod:: IOLoop.set_default_executor\n   .. automethod:: IOLoop.time\n   .. autoclass:: PeriodicCallback\n      :members:\n"
  },
  {
    "path": "docs/iostream.rst",
    "content": "``tornado.iostream`` --- Convenient wrappers for non-blocking sockets\n=====================================================================\n\n.. automodule:: tornado.iostream\n\n   Base class\n   ----------\n\n   .. autoclass:: BaseIOStream\n\n   Main interface\n   ^^^^^^^^^^^^^^\n\n   .. automethod:: BaseIOStream.write\n   .. automethod:: BaseIOStream.read_bytes\n   .. automethod:: BaseIOStream.read_into\n   .. automethod:: BaseIOStream.read_until\n   .. automethod:: BaseIOStream.read_until_regex\n   .. automethod:: BaseIOStream.read_until_close\n   .. automethod:: BaseIOStream.close\n   .. automethod:: BaseIOStream.set_close_callback\n   .. automethod:: BaseIOStream.closed\n   .. automethod:: BaseIOStream.reading\n   .. automethod:: BaseIOStream.writing\n   .. automethod:: BaseIOStream.set_nodelay\n\n   Methods for subclasses\n   ^^^^^^^^^^^^^^^^^^^^^^\n\n   .. automethod:: BaseIOStream.fileno\n   .. automethod:: BaseIOStream.close_fd\n   .. automethod:: BaseIOStream.write_to_fd\n   .. automethod:: BaseIOStream.read_from_fd\n   .. automethod:: BaseIOStream.get_fd_error\n\n   Implementations\n   ---------------\n\n   .. autoclass:: IOStream\n      :members:\n\n   .. autoclass:: SSLIOStream\n      :members:\n\n   .. autoclass:: PipeIOStream\n      :members:\n\n   Exceptions\n   ----------\n\n   .. autoexception:: StreamBufferFullError\n   .. autoexception:: StreamClosedError\n   .. autoexception:: UnsatisfiableReadError\n"
  },
  {
    "path": "docs/locale.rst",
    "content": "``tornado.locale`` --- Internationalization support\n===================================================\n\n.. automodule:: tornado.locale\n   :members:\n"
  },
  {
    "path": "docs/locks.rst",
    "content": "``tornado.locks`` -- Synchronization primitives\n===============================================\n\n.. versionadded:: 4.2\n\nCoordinate coroutines with synchronization primitives analogous to\nthose the standard library provides to threads. These classes are very\nsimilar to those provided in the standard library's `asyncio package\n<https://docs.python.org/3/library/asyncio-sync.html>`_.\n\n.. warning::\n\n   Note that these primitives are not actually thread-safe and cannot\n   be used in place of those from the standard library's `threading`\n   module--they are meant to coordinate Tornado coroutines in a\n   single-threaded app, not to protect shared objects in a\n   multithreaded app.\n\n.. automodule:: tornado.locks\n\n   Condition\n   ---------\n   .. autoclass:: Condition\n    :members:\n\n   Event\n   -----\n   .. autoclass:: Event\n    :members:\n\n   Semaphore\n   ---------\n   .. autoclass:: Semaphore\n    :members:\n\n   BoundedSemaphore\n   ----------------\n   .. autoclass:: BoundedSemaphore\n    :members:\n    :inherited-members:\n\n   Lock\n   ----\n   .. autoclass:: Lock\n    :members:\n    :inherited-members:\n"
  },
  {
    "path": "docs/log.rst",
    "content": "``tornado.log`` --- Logging support\n===================================\n\n.. automodule:: tornado.log\n   :members:\n"
  },
  {
    "path": "docs/netutil.rst",
    "content": "``tornado.netutil`` --- Miscellaneous network utilities\n=======================================================\n\n.. automodule:: tornado.netutil\n   :members:\n"
  },
  {
    "path": "docs/networking.rst",
    "content": "Asynchronous networking\n=======================\n\n.. toctree::\n\n   ioloop\n   iostream\n   netutil\n   tcpclient\n   tcpserver\n"
  },
  {
    "path": "docs/options.rst",
    "content": "``tornado.options`` --- Command-line parsing\n============================================\n\n.. automodule:: tornado.options\n\n\n\n   Global functions\n   ----------------\n   \n   .. autofunction:: define\n\n   .. py:data:: options\n\n       Global options object.  All defined options are available as attributes\n       on this object.\n\n   .. autofunction:: parse_command_line\n   .. autofunction:: parse_config_file\n   .. autofunction:: print_help(file=sys.stderr)\n   .. autofunction:: add_parse_callback\n   .. autoexception:: Error\n\n   OptionParser class\n   ------------------\n\n   .. autoclass:: OptionParser\n\n   .. automethod:: OptionParser.define\n   .. automethod:: OptionParser.parse_command_line\n   .. automethod:: OptionParser.parse_config_file\n   .. automethod:: OptionParser.print_help\n   .. automethod:: OptionParser.add_parse_callback\n   .. automethod:: OptionParser.mockable\n   .. automethod:: OptionParser.items\n   .. automethod:: OptionParser.as_dict\n   .. automethod:: OptionParser.groups\n   .. automethod:: OptionParser.group_dict\n"
  },
  {
    "path": "docs/process.rst",
    "content": "``tornado.process`` --- Utilities for multiple processes\n========================================================\n\n.. automodule:: tornado.process\n   :members:\n\n   .. exception:: CalledProcessError\n\n      An alias for `subprocess.CalledProcessError`.\n"
  },
  {
    "path": "docs/queues.rst",
    "content": "``tornado.queues`` -- Queues for coroutines\n===========================================\n\n.. versionadded:: 4.2\n\n.. automodule:: tornado.queues\n\n   Classes\n   -------\n\n   Queue\n   ^^^^^\n   .. autoclass:: Queue\n    :members:\n\n   PriorityQueue\n   ^^^^^^^^^^^^^\n   .. autoclass:: PriorityQueue\n    :members:\n\n   LifoQueue\n   ^^^^^^^^^\n   .. autoclass:: LifoQueue\n    :members:\n\n   Exceptions\n   ----------\n\n   QueueEmpty\n   ^^^^^^^^^^\n   .. autoexception:: QueueEmpty\n\n   QueueFull\n   ^^^^^^^^^\n   .. autoexception:: QueueFull\n"
  },
  {
    "path": "docs/releases/v1.0.0.rst",
    "content": "What's new in Tornado 1.0\n=========================\n\nJuly 22, 2010\n-------------\n\n::\n\n    We are pleased to announce the release of Tornado 1.0, available\n    from\n    https://github.com/downloads/facebook/tornado/tornado-1.0.tar.gz.\n    There have been many changes since version 0.2; here are some of\n    the highlights:\n\n    New features:\n    * Improved support for running other WSGI applications in a\n      Tornado server (tested with Django and CherryPy)\n    * Improved performance on Mac OS X and BSD (kqueue-based IOLoop),\n      and experimental support for win32\n    * Rewritten AsyncHTTPClient available as\n      tornado.httpclient.AsyncHTTPClient2 (this will become the\n      default in a future release)\n    * Support for standard .mo files in addition to .csv in the locale\n      module\n    * Pre-forking support for running multiple Tornado processes at\n      once (see HTTPServer.start())\n    * SSL and gzip support in HTTPServer\n    * reverse_url() function refers to urls from the Application\n      config by name from templates and RequestHandlers\n    * RequestHandler.on_connection_close() callback is called when the\n      client has closed the connection (subject to limitations of the\n      underlying network stack, any proxies, etc)\n    * Static files can now be served somewhere other than /static/ via\n      the static_url_prefix application setting\n    * URL regexes can now use named groups (\"(?P<name>)\") to pass\n      arguments to get()/post() via keyword instead of position\n    * HTTP header dictionary-like objects now support multiple values\n      for the same header via the get_all() and add() methods.\n    * Several new options in the httpclient module, including\n      prepare_curl_callback and header_callback\n    * Improved logging configuration in tornado.options.\n    * UIModule.html_body() can be used to return html to be inserted\n      at the end of the document body.\n\n    Backwards-incompatible changes:\n    * RequestHandler.get_error_html() now receives the exception\n      object as a keyword argument if the error was caused by an\n      uncaught exception.\n    * Secure cookies are now more secure, but incompatible with\n      cookies set by Tornado 0.2.  To read cookies set by older\n      versions of Tornado, pass include_name=False to\n      RequestHandler.get_secure_cookie()\n    * Parameters passed to RequestHandler.get/post() by extraction\n      from the path now have %-escapes decoded, for consistency with\n      the processing that was already done with other query\n      parameters.\n\n    Many thanks to everyone who contributed patches, bug reports, and\n    feedback that went into this release!\n\n    -Ben\n"
  },
  {
    "path": "docs/releases/v1.0.1.rst",
    "content": "What's new in Tornado 1.0.1\n===========================\n\nAug 13, 2010\n------------\n\n::\n    \n    This release fixes a bug with RequestHandler.get_secure_cookie, which would\n    in some circumstances allow an attacker to tamper with data stored in the\n    cookie.\n"
  },
  {
    "path": "docs/releases/v1.1.0.rst",
    "content": "What's new in Tornado 1.1\n=========================\n\nSep 7, 2010\n-----------\n\n::\n\n    We are pleased to announce the release of Tornado 1.1, available from\n    https://github.com/downloads/facebook/tornado/tornado-1.1.tar.gz\n\n    Changes in this release:\n    * RequestHandler.async_callback and related functions in other classes\n      are no longer needed in most cases (although it's harmless to continue\n      using them).  Uncaught exceptions will now cause the request to be closed\n      even in a callback.  If you're curious how this works, see the new\n      tornado.stack_context module.\n    * The new tornado.testing module contains support for unit testing\n      asynchronous IOLoop-based code.\n    * AsyncHTTPClient has been rewritten (the new implementation was\n      available as AsyncHTTPClient2 in Tornado 1.0; both names are\n      supported for backwards compatibility).\n    * The tornado.auth module has had a number of updates, including support\n      for OAuth 2.0 and the Facebook Graph API, and upgrading Twitter and\n      Google support to OAuth 1.0a.\n    * The websocket module is back and supports the latest version (76) of the\n      websocket protocol.  Note that this module's interface is different\n      from the websocket module that appeared in pre-1.0 versions of Tornado.\n    * New method RequestHandler.initialize() can be overridden in subclasses\n      to simplify handling arguments from URLSpecs.  The sequence of methods\n      called during initialization is documented at\n      http://tornadoweb.org/documentation#overriding-requesthandler-methods\n    * get_argument() and related methods now work on PUT requests in addition\n      to POST.\n    * The httpclient module now supports HTTP proxies.\n    * When HTTPServer is run in SSL mode, the SSL handshake is now non-blocking.\n    * Many smaller bug fixes and documentation updates\n\n    Backwards-compatibility notes:\n    * While most users of Tornado should not have to deal with the stack_context\n      module directly, users of worker thread pools and similar constructs may\n      need to use stack_context.wrap and/or NullContext to avoid memory leaks.\n    * The new AsyncHTTPClient still works with libcurl version 7.16.x, but it\n      performs better when both libcurl and pycurl are at least version 7.18.2.\n    * OAuth transactions started under previous versions of the auth module\n      cannot be completed under the new module.  This applies only to the\n      initial authorization process; once an authorized token is issued that\n      token works with either version.\n\n    Many thanks to everyone who contributed patches, bug reports, and feedback\n    that went into this release!\n\n    -Ben\n"
  },
  {
    "path": "docs/releases/v1.1.1.rst",
    "content": "What's new in Tornado 1.1.1\n===========================\n\nFeb 8, 2011\n-----------\n\n::\n\n    Tornado 1.1.1 is a BACKWARDS-INCOMPATIBLE security update that fixes an\n    XSRF vulnerability.  It is available at\n    https://github.com/downloads/facebook/tornado/tornado-1.1.1.tar.gz\n\n    This is a backwards-incompatible change.  Applications that previously\n    relied on a blanket exception for XMLHTTPRequest may need to be modified\n    to explicitly include the XSRF token when making ajax requests.\n\n    The tornado chat demo application demonstrates one way of adding this\n    token (specifically the function postJSON in demos/chat/static/chat.js).\n\n    More information about this change and its justification can be found at\n    http://www.djangoproject.com/weblog/2011/feb/08/security/\n    http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails\n"
  },
  {
    "path": "docs/releases/v1.2.0.rst",
    "content": "What's new in Tornado 1.2\n=========================\n\nFeb 20, 2011\n------------\n\n::\n\n    We are pleased to announce the release of Tornado 1.2, available from\n    https://github.com/downloads/facebook/tornado/tornado-1.2.tar.gz\n\n    Backwards compatibility notes:\n    * This release includes the backwards-incompatible security change from\n      version 1.1.1.  Users upgrading from 1.1 or earlier should read the\n      release notes from that release:\n      http://groups.google.com/group/python-tornado/browse_thread/thread/b36191c781580cde\n    * StackContexts that do something other than catch exceptions may need to\n      be modified to be reentrant.\n      https://github.com/tornadoweb/tornado/commit/7a7e24143e77481d140fb5579bc67e4c45cbcfad\n    * When XSRF tokens are used, the token must also be present on PUT and\n      DELETE requests (anything but GET and HEAD)\n\n    New features:\n    * A new HTTP client implementation is available in the module\n      tornado.simple_httpclient.  This HTTP client does not depend on pycurl.\n      It has not yet been tested extensively in production, but is intended\n      to eventually replace the pycurl-based HTTP client in a future release of\n      Tornado.  To transparently replace tornado.httpclient.AsyncHTTPClient with\n      this new implementation, you can set the environment variable\n      USE_SIMPLE_HTTPCLIENT=1 (note that the next release of Tornado will\n      likely include a different way to select HTTP client implementations)\n    * Request logging is now done by the Application rather than the\n      RequestHandler.  Logging behavior may be customized by either overriding\n      Application.log_request in a subclass or by passing log_function\n      as an Application setting\n    * Application.listen(port): Convenience method as an alternative to\n      explicitly creating an HTTPServer\n    * tornado.escape.linkify(): Wrap urls in <a> tags\n    * RequestHandler.create_signed_value(): Create signatures like the\n      secure_cookie methods without setting cookies.\n    * tornado.testing.get_unused_port(): Returns a port selected in the same\n      way as inAsyncHTTPTestCase\n    * AsyncHTTPTestCase.fetch(): Convenience method for synchronous fetches\n    * IOLoop.set_blocking_signal_threshold(): Set a callback to be run when\n      the IOLoop is blocked.\n    * IOStream.connect(): Asynchronously connect a client socket\n    * AsyncHTTPClient.handle_callback_exception(): May be overridden\n      in subclass for custom error handling\n    * httpclient.HTTPRequest has two new keyword arguments, validate_cert and\n      ca_certs. Setting validate_cert=False will disable all certificate checks\n      when fetching https urls.  ca_certs may be set to a filename containing\n      trusted certificate authorities (defaults will be used if this is\n      unspecified)\n    * HTTPRequest.get_ssl_certificate(): Returns the client's SSL certificate\n      (if client certificates were requested in the server's ssl_options\n    * StaticFileHandler can be configured to return a default file (e.g.\n      index.html) when a directory is requested\n    * Template directives of the form \"{% from x import y %}\" are now\n      supported (in addition to the existing support for \"{% import x\n      %}\"\n    * FacebookGraphMixin.get_authenticated_user now accepts a new\n      parameter 'extra_fields' which may be used to request additional\n      information about the user\n\n    Bug fixes:\n    * auth: Fixed KeyError with Facebook offline_access\n    * auth: Uses request.uri instead of request.path as the default redirect\n      so that parameters are preserved.\n    * escape: xhtml_escape() now returns a unicode string, not\n      utf8-encoded bytes\n    * ioloop: Callbacks added with add_callback are now run in the order they\n      were added\n    * ioloop: PeriodicCallback.stop can now be called from inside the callback.\n    * iostream: Fixed several bugs in SSLIOStream\n    * iostream: Detect when the other side has closed the connection even with\n      the select()-based IOLoop\n    * iostream: read_bytes(0) now works as expected\n    * iostream: Fixed bug when writing large amounts of data on windows\n    * iostream: Fixed infinite loop that could occur with unhandled exceptions\n    * httpclient: Fix bugs when some requests use proxies and others don't\n    * httpserver: HTTPRequest.protocol is now set correctly when using the\n      built-in SSL support\n    * httpserver: When using multiple processes, the standard library's\n      random number generator is re-seeded in each child process\n    * httpserver: With xheaders enabled, X-Forwarded-Proto is supported as an\n      alternative to X-Scheme\n    * httpserver: Fixed bugs in multipart/form-data parsing\n    * locale: format_date() now behaves sanely with dates in the future\n    * locale: Updates to the language list\n    * stack_context: Fixed bug with contexts leaking through reused IOStreams\n    * stack_context: Simplified semantics and improved performance\n    * web: The order of css_files from UIModules is now preserved\n    * web: Fixed error with default_host redirect\n    * web: StaticFileHandler works when os.path.sep != '/' (i.e. on Windows)\n    * web: Fixed a caching-related bug in StaticFileHandler when a file's\n      timestamp has changed but its contents have not.\n    * web: Fixed bugs with HEAD requests and e.g. Etag headers\n    * web: Fix bugs when different handlers have different static_paths\n    * web: @removeslash will no longer cause a redirect loop when applied to the\n      root path\n    * websocket: Now works over SSL\n    * websocket: Improved compatibility with proxies\n\n    Many thanks to everyone who contributed patches, bug reports, and feedback\n    that went into this release!\n\n    -Ben\n"
  },
  {
    "path": "docs/releases/v1.2.1.rst",
    "content": "What's new in Tornado 1.2.1\n===========================\n\nMar 3, 2011\n-----------\n\n::\n\n    We are pleased to announce the release of Tornado 1.2.1, available from\n    https://github.com/downloads/facebook/tornado/tornado-1.2.1.tar.gz\n\n    This release contains only two small changes relative to version 1.2:\n    * FacebookGraphMixin has been updated to work with a recent change to the\n      Facebook API.\n    * Running \"setup.py install\" will no longer attempt to automatically\n      install pycurl.  This wasn't working well on platforms where the best way\n      to install pycurl is via something like apt-get instead of easy_install.\n\n    This is an important upgrade if you are using FacebookGraphMixin, but\n    otherwise it can be safely ignored.\n"
  },
  {
    "path": "docs/releases/v2.0.0.rst",
    "content": "What's new in Tornado 2.0\n=========================\n\nJun 21, 2011\n------------\n\n::\n\n    Major changes:\n    * Template output is automatically escaped by default; see backwards\n      compatibility note below.\n    * The default AsyncHTTPClient implementation is now simple_httpclient.\n    * Python 3.2 is now supported.\n\n    Backwards compatibility:\n    * Template autoescaping is enabled by default.  Applications upgrading from\n      a previous release of Tornado must either disable autoescaping or adapt\n      their templates to work with it.  For most applications, the simplest\n      way to do this is to pass autoescape=None to the Application constructor.\n      Note that this affects certain built-in methods, e.g. xsrf_form_html\n      and linkify, which must now be called with {% raw %} instead of {}\n    * Applications that wish to continue using curl_httpclient instead of\n      simple_httpclient may do so by calling\n        AsyncHTTPClient.configure(\"tornado.curl_httpclient.CurlAsyncHTTPClient\")\n      at the beginning of the process.  Users of Python 2.5 will probably want\n      to use curl_httpclient as simple_httpclient only supports ssl on Python 2.6+.\n    * Python 3 compatibility involved many changes throughout the codebase,\n      so users are encouraged to test their applications more thoroughly than\n      usual when upgrading to this release.\n\n    Other changes in this release:\n    * Templates support several new directives:\n      - {% autoescape ...%} to control escaping behavior\n      - {% raw ... %} for unescaped output\n      - {% module ... %} for calling UIModules\n    * {% module Template(path, **kwargs) %} may now be used to call another\n      template with an independent namespace\n    * All IOStream callbacks are now run directly on the IOLoop via add_callback.\n    * HTTPServer now supports IPv6 where available.  To disable, pass\n      family=socket.AF_INET to HTTPServer.bind().\n    * HTTPClient now supports IPv6, configurable via allow_ipv6=bool on the\n      HTTPRequest.  allow_ipv6 defaults to false on simple_httpclient and true\n      on curl_httpclient.\n    * RequestHandlers can use an encoding other than utf-8 for query parameters\n      by overriding decode_argument()\n    * Performance improvements, especially for applications that use a lot of\n      IOLoop timeouts\n    * HTTP OPTIONS method no longer requires an XSRF token.\n    * JSON output (RequestHandler.write(dict)) now sets Content-Type to\n      application/json\n    * Etag computation can now be customized or disabled by overriding\n      RequestHandler.compute_etag\n    * USE_SIMPLE_HTTPCLIENT environment variable is no longer supported.\n      Use AsyncHTTPClient.configure instead.\n"
  },
  {
    "path": "docs/releases/v2.1.0.rst",
    "content": "What's new in Tornado 2.1\n=========================\n\nSep 20, 2011\n------------\n\nBackwards-incompatible changes\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Support for secure cookies written by pre-1.0 releases of Tornado has\n  been removed.  The `.RequestHandler.get_secure_cookie` method no longer\n  takes an ``include_name`` parameter.\n* The ``debug`` application setting now causes stack traces to be displayed\n  in the browser on uncaught exceptions.  Since this may leak sensitive\n  information, debug mode is not recommended for public-facing servers.\n\nSecurity fixes\n~~~~~~~~~~~~~~\n\n* Diginotar has been removed from the default CA certificates file used\n  by ``SimpleAsyncHTTPClient``.\n\nNew modules\n~~~~~~~~~~~\n\n* `tornado.gen`:  A generator-based interface to simplify writing \n  asynchronous functions.\n* `tornado.netutil`:  Parts of `tornado.httpserver` have been extracted into\n  a new module for use with non-HTTP protocols.\n* `tornado.platform.twisted`:  A bridge between the Tornado IOLoop and the\n  Twisted Reactor, allowing code written for Twisted to be run on Tornado.\n* `tornado.process`:  Multi-process mode has been improved, and can now restart\n  crashed child processes.  A new entry point has been added at \n  `tornado.process.fork_processes`, although\n  ``tornado.httpserver.HTTPServer.start`` is still supported.\n\n``tornado.web``\n~~~~~~~~~~~~~~~\n\n* `tornado.web.RequestHandler.write_error` replaces ``get_error_html`` as the\n  preferred way to generate custom error pages (``get_error_html`` is still\n  supported, but deprecated)\n* In `tornado.web.Application`, handlers may be specified by\n  (fully-qualified) name instead of importing and passing the class object\n  itself.\n* It is now possible to use a custom subclass of ``StaticFileHandler``\n  with the ``static_handler_class`` application setting, and this subclass\n  can override the behavior of the ``static_url`` method.\n* `~tornado.web.StaticFileHandler` subclasses can now override \n  ``get_cache_time`` to customize cache control behavior.\n* `tornado.web.RequestHandler.get_secure_cookie` now has a ``max_age_days``\n  parameter to allow applications to override the default one-month expiration.\n* `~tornado.web.RequestHandler.set_cookie` now accepts a ``max_age`` keyword\n  argument to set the ``max-age`` cookie attribute (note underscore vs dash)\n* `tornado.web.RequestHandler.set_default_headers` may be overridden to set\n  headers in a way that does not get reset during error handling.\n* `.RequestHandler.add_header` can now be used to set a header that can\n  appear multiple times in the response.\n* `.RequestHandler.flush` can now take a callback for flow control.\n* The ``application/json`` content type can now be gzipped.\n* The cookie-signing functions are now accessible as static functions\n  ``tornado.web.create_signed_value`` and ``tornado.web.decode_signed_value``.\n\n``tornado.httpserver``\n~~~~~~~~~~~~~~~~~~~~~~\n\n* To facilitate some advanced multi-process scenarios, ``HTTPServer``\n  has a new method ``add_sockets``, and socket-opening code is\n  available separately as `tornado.netutil.bind_sockets`.\n* The ``cookies`` property is now available on ``tornado.httpserver.HTTPRequest``\n  (it is also available in its old location as a property of\n  `~tornado.web.RequestHandler`)\n* ``tornado.httpserver.HTTPServer.bind`` now takes a backlog argument with the\n  same meaning as ``socket.listen``.\n* `~tornado.httpserver.HTTPServer` can now be run on a unix socket as well\n  as TCP.\n* Fixed exception at startup when ``socket.AI_ADDRCONFIG`` is not available,\n  as on Windows XP\n\n``IOLoop`` and ``IOStream``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* `~tornado.iostream.IOStream` performance has been improved, especially for\n  small synchronous requests.\n* New methods ``tornado.iostream.IOStream.read_until_close`` and \n  ``tornado.iostream.IOStream.read_until_regex``.\n* ``IOStream.read_bytes`` and ``IOStream.read_until_close`` now take a\n  ``streaming_callback`` argument to return data as it is received rather\n  than all at once.\n* `.IOLoop.add_timeout` now accepts `datetime.timedelta` objects in addition\n  to absolute timestamps.\n* `~tornado.ioloop.PeriodicCallback` now sticks to the specified period\n  instead of creeping later due to accumulated errors.\n* `tornado.ioloop.IOLoop` and `tornado.httpclient.HTTPClient` now have\n  ``close()`` methods that should be used in applications that create\n  and destroy many of these objects.\n* `.IOLoop.install` can now be used to use a custom subclass of IOLoop\n  as the singleton without monkey-patching.\n* `~tornado.iostream.IOStream` should now always call the close callback\n  instead of the connect callback on a connection error.\n* The `.IOStream` close callback will no longer be called while there\n  are pending read callbacks that can be satisfied with buffered data.\n\n\n``tornado.simple_httpclient``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Now supports client SSL certificates with the ``client_key`` and \n  ``client_cert`` parameters to `tornado.httpclient.HTTPRequest`\n* Now takes a maximum buffer size, to allow reading files larger than 100MB\n* Now works with HTTP 1.0 servers that don't send a Content-Length header\n* The ``allow_nonstandard_methods`` flag on HTTP client requests now\n  permits methods other than ``POST`` and ``PUT`` to contain bodies.\n* Fixed file descriptor leaks and multiple callback invocations in\n  ``SimpleAsyncHTTPClient``\n* No longer consumes extra connection resources when following redirects.\n* Now works with buggy web servers that separate headers with ``\\n`` instead\n  of ``\\r\\n\\r\\n``.\n* Now sets ``response.request_time`` correctly.\n* Connect timeouts now work correctly.\n\n\nOther modules\n~~~~~~~~~~~~~\n\n* `tornado.auth.OpenIdMixin` now uses the correct realm when the\n  callback URI is on a different domain.\n* `tornado.autoreload` has a new command-line interface which can be used\n  to wrap any script.  This replaces the ``--autoreload`` argument to\n  `tornado.testing.main` and is more robust against syntax errors.\n* `tornado.autoreload.watch` can be used to watch files other than\n  the sources of imported modules.\n* ``tornado.database.Connection`` has new variants of ``execute`` and\n  ``executemany`` that return the number of rows affected instead of\n  the last inserted row id.\n* `tornado.locale.load_translations` now accepts any properly-formatted\n  locale name, not just those in the predefined ``LOCALE_NAMES`` list.\n* `tornado.options.define` now takes a ``group`` parameter to group options\n  in ``--help`` output.\n* Template loaders now take a ``namespace`` constructor argument to add\n  entries to the template namespace.\n* `tornado.websocket` now supports the latest (\"hybi-10\") version of the\n  protocol (the old version, \"hixie-76\" is still supported; the correct\n  version is detected automatically).\n* `tornado.websocket` now works on Python 3\n\n\nBug fixes\n~~~~~~~~~\n\n* Windows support has been improved.  Windows is still not an officially\n  supported platform, but the test suite now passes and\n  `tornado.autoreload` works.\n* Uploading files whose names contain special characters will now work.\n* Cookie values containing special characters are now properly quoted\n  and unquoted.\n* Multi-line headers are now supported.\n* Repeated Content-Length headers (which may be added by certain proxies)\n  are now supported in `.HTTPServer`.\n* Unicode string literals now work in template expressions.\n* The template ``{% module %}`` directive now works even if applications\n  use a template variable named ``modules``.\n* Requests with \"Expect: 100-continue\" now work on python 3\n\n"
  },
  {
    "path": "docs/releases/v2.1.1.rst",
    "content": "What's new in Tornado 2.1.1\n===========================\n\nOct 4, 2011\n-----------\n\nBug fixes\n~~~~~~~~~\n\n* Fixed handling of closed connections with the ``epoll`` (i.e. Linux)\n  ``IOLoop``.  Previously, closed connections could be shut down too early,\n  which most often manifested as \"Stream is closed\" exceptions in\n  ``SimpleAsyncHTTPClient``.\n* Fixed a case in which chunked responses could be closed prematurely,\n  leading to truncated output.\n* ``IOStream.connect`` now reports errors more consistently via logging\n  and the close callback (this affects e.g. connections to localhost\n  on FreeBSD).\n* ``IOStream.read_bytes`` again accepts both ``int`` and ``long`` arguments.\n* ``PeriodicCallback`` no longer runs repeatedly when ``IOLoop`` iterations\n  complete faster than the resolution of ``time.time()`` (mainly a problem\n  on Windows).\n\nBackwards-compatibility note\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Listening for ``IOLoop.ERROR`` alone is no longer sufficient for detecting\n  closed connections on an otherwise unused socket.  ``IOLoop.ERROR`` must\n  always be used in combination with ``READ`` or ``WRITE``.\n"
  },
  {
    "path": "docs/releases/v2.2.0.rst",
    "content": "What's new in Tornado 2.2\n=========================\n\nJan 30, 2012\n------------\n\nHighlights\n~~~~~~~~~~\n\n* Updated and expanded WebSocket support.\n* Improved compatibility in the Twisted/Tornado bridge.\n* Template errors now generate better stack traces.\n* Better exception handling in `tornado.gen`.\n\nSecurity fixes\n~~~~~~~~~~~~~~\n\n* ``tornado.simple_httpclient`` now disables SSLv2 in all cases.  Previously\n  SSLv2 would be allowed if the Python interpreter was linked against a\n  pre-1.0 version of OpenSSL.\n\nBackwards-incompatible changes\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* `tornado.process.fork_processes` now raises `SystemExit` if all child\n  processes exit cleanly rather than returning ``None``.  The old behavior\n  was surprising and inconsistent with most of the documented examples\n  of this function (which did not check the return value).\n* On Python 2.6, ``tornado.simple_httpclient`` only supports SSLv3.  This\n  is because Python 2.6 does not expose a way to support both SSLv3 and TLSv1\n  without also supporting the insecure SSLv2.\n* `tornado.websocket` no longer supports the older \"draft 76\" version\n  of the websocket protocol by default, although this version can\n  be enabled by overriding ``tornado.websocket.WebSocketHandler.allow_draft76``.\n\n``tornado.httpclient``\n~~~~~~~~~~~~~~~~~~~~~~\n\n* ``SimpleAsyncHTTPClient`` no longer hangs on ``HEAD`` requests,\n  responses with no content, or empty ``POST``/``PUT`` response bodies.\n* ``SimpleAsyncHTTPClient`` now supports 303 and 307 redirect codes.\n* ``tornado.curl_httpclient`` now accepts non-integer timeouts.\n* ``tornado.curl_httpclient`` now supports basic authentication with an\n  empty password.\n\n``tornado.httpserver``\n~~~~~~~~~~~~~~~~~~~~~~\n\n* `.HTTPServer` with ``xheaders=True`` will no longer accept\n  ``X-Real-IP`` headers that don't look like valid IP addresses.\n* `.HTTPServer` now treats the ``Connection`` request header as\n  case-insensitive.\n\n``tornado.ioloop`` and ``tornado.iostream``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* ``IOStream.write`` now works correctly when given an empty string.\n* ``IOStream.read_until`` (and ``read_until_regex``) now perform better\n  when there is a lot of buffered data, which improves performance of\n  ``SimpleAsyncHTTPClient`` when downloading files with lots of\n  chunks.\n* `.SSLIOStream` now works correctly when ``ssl_version`` is set to\n  a value other than ``SSLv23``.\n* Idle ``IOLoops`` no longer wake up several times a second.\n* `tornado.ioloop.PeriodicCallback` no longer triggers duplicate callbacks\n  when stopped and started repeatedly.\n\n``tornado.template``\n~~~~~~~~~~~~~~~~~~~~\n\n* Exceptions in template code will now show better stack traces that\n  reference lines from the original template file.\n* ``{#`` and ``#}`` can now be used for comments (and unlike the old\n  ``{% comment %}`` directive, these can wrap other template directives).\n* Template directives may now span multiple lines.\n\n``tornado.web``\n~~~~~~~~~~~~~~~\n\n* Now behaves better when given malformed ``Cookie`` headers\n* `.RequestHandler.redirect` now has a ``status`` argument to send\n  status codes other than 301 and 302.\n* New method `.RequestHandler.on_finish` may be overridden for post-request\n  processing (as a counterpart to `.RequestHandler.prepare`)\n* `.StaticFileHandler` now outputs ``Content-Length`` and ``Etag`` headers\n  on ``HEAD`` requests.\n* `.StaticFileHandler` now has overridable ``get_version`` and\n  ``parse_url_path`` methods for use in subclasses.\n* `.RequestHandler.static_url` now takes an ``include_host`` parameter\n  (in addition to the old support for the ``RequestHandler.include_host``\n  attribute).\n\n``tornado.websocket``\n~~~~~~~~~~~~~~~~~~~~~\n\n* Updated to support the latest version of the protocol, as finalized\n  in RFC 6455.\n* Many bugs were fixed in all supported protocol versions.\n* `tornado.websocket` no longer supports the older \"draft 76\" version\n  of the websocket protocol by default, although this version can\n  be enabled by overriding ``tornado.websocket.WebSocketHandler.allow_draft76``.\n* `.WebSocketHandler.write_message` now accepts a ``binary`` argument\n  to send binary messages.\n* Subprotocols (i.e. the ``Sec-WebSocket-Protocol`` header) are now supported;\n  see the `.WebSocketHandler.select_subprotocol` method for details.\n* ``.WebSocketHandler.get_websocket_scheme`` can be used to select the\n  appropriate url scheme (``ws://`` or ``wss://``) in cases where\n  ``HTTPRequest.protocol`` is not set correctly.\n\nOther modules\n~~~~~~~~~~~~~\n\n* `tornado.auth.TwitterMixin.authenticate_redirect` now takes a\n  ``callback_uri`` parameter.\n* `tornado.auth.TwitterMixin.twitter_request` now accepts both URLs and\n  partial paths (complete URLs are useful for the search API which follows\n  different patterns).\n* Exception handling in `tornado.gen` has been improved.  It is now possible\n  to catch exceptions thrown by a ``Task``.\n* `tornado.netutil.bind_sockets` now works when ``getaddrinfo`` returns\n  duplicate addresses.\n* `tornado.platform.twisted` compatibility has been significantly improved.\n  Twisted version 11.1.0 is now supported in addition to 11.0.0.\n* `tornado.process.fork_processes` correctly reseeds the `random` module\n  even when `os.urandom` is not implemented.\n* `tornado.testing.main` supports a new flag ``--exception_on_interrupt``,\n  which can be set to false to make ``Ctrl-C`` kill the process more\n  reliably (at the expense of stack traces when it does so).\n* ``tornado.version_info`` is now a four-tuple so official releases can be\n  distinguished from development branches.\n"
  },
  {
    "path": "docs/releases/v2.2.1.rst",
    "content": "What's new in Tornado 2.2.1\n===========================\n\nApr 23, 2012\n------------\n\nSecurity fixes\n~~~~~~~~~~~~~~\n\n* `tornado.web.RequestHandler.set_header` now properly sanitizes input\n  values to protect against header injection, response splitting, etc.\n  (it has always attempted to do this, but the check was incorrect).\n  Note that redirects, the most likely source of such bugs, are protected\n  by a separate check in `.RequestHandler.redirect`.\n\nBug fixes\n~~~~~~~~~\n\n* Colored logging configuration in `tornado.options` is compatible with\n  Python 3.2.3 (and 3.3).\n"
  },
  {
    "path": "docs/releases/v2.3.0.rst",
    "content": "What's new in Tornado 2.3\n=========================\n\nMay 31, 2012\n------------\n\nHTTP clients\n~~~~~~~~~~~~\n\n* `tornado.httpclient.HTTPClient` now supports the same constructor\n  keyword arguments as `.AsyncHTTPClient`.\n* The ``max_clients`` keyword argument to `.AsyncHTTPClient.configure` now works.\n* ``tornado.simple_httpclient`` now supports the ``OPTIONS`` and ``PATCH``\n  HTTP methods.\n* ``tornado.simple_httpclient`` is better about closing its sockets\n  instead of leaving them for garbage collection.\n* ``tornado.simple_httpclient`` correctly verifies SSL certificates for\n  URLs containing IPv6 literals (This bug affected Python 2.5 and 2.6).\n* ``tornado.simple_httpclient`` no longer includes basic auth credentials\n  in the ``Host`` header when those credentials are extracted from the URL.\n* ``tornado.simple_httpclient`` no longer modifies the caller-supplied header\n  dictionary, which caused problems when following redirects.\n* ``tornado.curl_httpclient`` now supports client SSL certificates (using\n  the same ``client_cert`` and ``client_key`` arguments as\n  ``tornado.simple_httpclient``)\n\nHTTP Server\n~~~~~~~~~~~\n\n* `.HTTPServer` now works correctly with paths starting with ``//``\n* ``HTTPHeaders.copy`` (inherited from `dict.copy`) now works correctly.\n* ``HTTPConnection.address`` is now always the socket address, even for non-IP\n  sockets.  ``HTTPRequest.remote_ip`` is still always an IP-style address\n  (fake data is used for non-IP sockets)\n* Extra data at the end of multipart form bodies is now ignored, which fixes\n  a compatibility problem with an iOS HTTP client library.\n\n\n``IOLoop`` and ``IOStream``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* `.IOStream` now has an ``error`` attribute that can be used to determine\n  why a socket was closed.\n* ``tornado.iostream.IOStream.read_until`` and ``read_until_regex`` are much\n  faster with large input.\n* ``IOStream.write`` performs better when given very large strings.\n* `.IOLoop.instance()` is now thread-safe.\n\n``tornado.options``\n~~~~~~~~~~~~~~~~~~~\n\n* `tornado.options` options with ``multiple=True`` that are set more than\n  once now overwrite rather than append.  This makes it possible to override\n  values set in ``parse_config_file`` with ``parse_command_line``.\n* `tornado.options` ``--help`` output is now prettier.\n* `tornado.options.options` now supports attribute assignment.\n\n``tornado.template``\n~~~~~~~~~~~~~~~~~~~~\n\n* Template files containing non-ASCII (utf8) characters now work on Python 3\n  regardless of the locale environment variables.\n* Templates now support ``else`` clauses in\n  ``try``/``except``/``finally``/``else`` blocks.\n\n``tornado.web``\n~~~~~~~~~~~~~~~\n\n* `tornado.web.RequestHandler` now supports the ``PATCH`` HTTP method.\n  Note that this means any existing methods named ``patch`` in\n  ``RequestHandler`` subclasses will need to be renamed.\n* `tornado.web.addslash` and ``removeslash`` decorators now send permanent\n  redirects (301) instead of temporary (302).\n* `.RequestHandler.flush` now invokes its callback whether there was any data\n  to flush or not.\n* Repeated calls to `.RequestHandler.set_cookie` with the same name now\n  overwrite the previous cookie instead of producing additional copies.\n* ``tornado.web.OutputTransform.transform_first_chunk`` now takes and returns\n  a status code in addition to the headers and chunk.  This is a\n  backwards-incompatible change to an interface that was never technically\n  private, but was not included in the documentation and does not appear\n  to have been used outside Tornado itself.\n* Fixed a bug on python versions before 2.6.5 when `tornado.web.URLSpec` regexes\n  are constructed from unicode strings and keyword arguments are extracted.\n* The ``reverse_url`` function in the template namespace now comes from\n  the `.RequestHandler` rather than the `.Application`.  (Unless overridden,\n  `.RequestHandler.reverse_url` is just an alias for the `.Application`\n  method).\n* The ``Etag`` header is now returned on 304 responses to an ``If-None-Match``\n  request, improving compatibility with some caches.\n* `tornado.web` will no longer produce responses with status code 304\n  that also have entity headers such as ``Content-Length``.\n\nOther modules\n~~~~~~~~~~~~~\n\n* `tornado.auth.FacebookGraphMixin` no longer sends ``post_args`` redundantly\n  in the url.\n* The ``extra_params`` argument to `tornado.escape.linkify` may now be\n  a callable, to allow parameters to be chosen separately for each link.\n* `tornado.gen` no longer leaks ``StackContexts`` when a ``@gen.engine`` wrapped\n  function is called repeatedly.\n* `tornado.locale.get_supported_locales` no longer takes a meaningless\n  ``cls`` argument.\n* ``StackContext`` instances now have a deactivation callback that can be\n  used to prevent further propagation.\n* `tornado.testing.AsyncTestCase.wait` now resets its timeout on each call.\n* ``tornado.wsgi.WSGIApplication`` now parses arguments correctly on Python 3.\n* Exception handling on Python 3 has been improved; previously some exceptions\n  such as `UnicodeDecodeError` would generate ``TypeErrors``\n"
  },
  {
    "path": "docs/releases/v2.4.0.rst",
    "content": "What's new in Tornado 2.4\n=========================\n\nSep 4, 2012\n-----------\n\nGeneral\n~~~~~~~\n\n* Fixed Python 3 bugs in `tornado.auth`, `tornado.locale`, and `tornado.wsgi`.\n\nHTTP clients\n~~~~~~~~~~~~\n\n* Removed ``max_simultaneous_connections`` argument from `tornado.httpclient`\n  (both implementations).  This argument hasn't been useful for some time\n  (if you were using it you probably want ``max_clients`` instead)\n* ``tornado.simple_httpclient`` now accepts and ignores HTTP 1xx status\n  responses.\n\n`tornado.ioloop` and `tornado.iostream`\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Fixed a bug introduced in 2.3 that would cause `.IOStream` close callbacks\n  to not run if there were pending reads.\n* Improved error handling in `.SSLIOStream` and SSL-enabled `.TCPServer`.\n* ``SSLIOStream.get_ssl_certificate`` now has a ``binary_form`` argument\n  which is passed to ``SSLSocket.getpeercert``.\n* ``SSLIOStream.write`` can now be called while the connection is in progress,\n  same as non-SSL `.IOStream` (but be careful not to send sensitive data until\n  the connection has completed and the certificate has been verified).\n* `.IOLoop.add_handler` cannot be called more than once with the same file\n  descriptor.  This was always true for ``epoll``, but now the other\n  implementations enforce it too.\n* On Windows, `.TCPServer` uses ``SO_EXCLUSIVEADDRUSER`` instead of ``SO_REUSEADDR``.\n\n`tornado.template`\n~~~~~~~~~~~~~~~~~~\n\n* ``{% break %}`` and ``{% continue %}`` can now be used looping constructs\n  in templates.\n* It is no longer an error for an if/else/for/etc block in a template to\n  have an empty body.\n\n`tornado.testing`\n~~~~~~~~~~~~~~~~~\n\n* New class `tornado.testing.AsyncHTTPSTestCase` is like `.AsyncHTTPTestCase`.\n  but enables SSL for the testing server (by default using a self-signed\n  testing certificate).\n* `tornado.testing.main` now accepts additional keyword arguments and forwards\n  them to `unittest.main`.\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n* New method `.RequestHandler.get_template_namespace` can be overridden to\n  add additional variables without modifying keyword arguments to\n  ``render_string``.\n* `.RequestHandler.add_header` now works with ``WSGIApplication``.\n* `.RequestHandler.get_secure_cookie` now handles a potential error case.\n* ``RequestHandler.__init__`` now calls ``super().__init__`` to ensure that\n  all constructors are called when multiple inheritance is used.\n* Docs have been updated with a description of all available\n  :py:attr:`Application settings <tornado.web.Application.settings>`\n\nOther modules\n~~~~~~~~~~~~~\n\n* `.OAuthMixin` now accepts ``\"oob\"`` as a ``callback_uri``.\n* `.OpenIdMixin` now also returns the ``claimed_id`` field for the user.\n* `tornado.platform.twisted` shutdown sequence is now more compatible.\n* The logging configuration used in `tornado.options` is now more tolerant\n  of non-ascii byte strings.\n"
  },
  {
    "path": "docs/releases/v2.4.1.rst",
    "content": "What's new in Tornado 2.4.1\n===========================\n\nNov 24, 2012\n------------\n\nBug fixes\n~~~~~~~~~\n\n* Fixed a memory leak in ``tornado.stack_context`` that was especially likely\n  with long-running ``@gen.engine`` functions.\n* `tornado.auth.TwitterMixin` now works on Python 3.\n* Fixed a bug in which ``IOStream.read_until_close`` with a streaming callback\n  would sometimes pass the last chunk of data to the final callback instead\n  of the streaming callback.\n"
  },
  {
    "path": "docs/releases/v3.0.0.rst",
    "content": "What's new in Tornado 3.0\n=========================\n\nMar 29, 2013\n------------\n\nHighlights\n^^^^^^^^^^\n\n* The ``callback`` argument to many asynchronous methods is now\n  optional, and these methods return a `.Future`.  The `tornado.gen`\n  module now understands ``Futures``, and these methods can be used\n  directly without a ``gen.Task`` wrapper.\n* New function `.IOLoop.current` returns the `.IOLoop` that is running\n  on the current thread (as opposed to `.IOLoop.instance`, which\n  returns a specific thread's (usually the main thread's) IOLoop.\n* New class `tornado.netutil.Resolver` provides an asynchronous\n  interface to DNS resolution.  The default implementation is still\n  blocking, but non-blocking implementations are available using one\n  of three optional dependencies: `~tornado.netutil.ThreadedResolver`\n  using the `concurrent.futures` thread pool,\n  ``tornado.platform.caresresolver.CaresResolver`` using the ``pycares``\n  library, or ``tornado.platform.twisted.TwistedResolver`` using ``twisted``\n* Tornado's logging is now less noisy, and it no longer goes directly\n  to the root logger, allowing for finer-grained configuration.\n* New class `tornado.process.Subprocess` wraps `subprocess.Popen` with\n  `.PipeIOStream` access to the child's file descriptors.\n* `.IOLoop` now has a static `configure <.Configurable.configure>`\n  method like the one on `.AsyncHTTPClient`, which can be used to\n  select an `.IOLoop` implementation other than the default.\n* `.IOLoop` can now optionally use a monotonic clock if available\n  (see below for more details).\n\n\nBackwards-incompatible changes\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n* Python 2.5 is no longer supported.  Python 3 is now supported in a single\n  codebase instead of using ``2to3``\n* The ``tornado.database`` module has been removed.  It is now available\n  as a separate package, `torndb <https://github.com/bdarnell/torndb>`_\n* Functions that take an ``io_loop`` parameter now default to\n  `.IOLoop.current()` instead of `.IOLoop.instance()`.\n* Empty HTTP request arguments are no longer ignored.  This applies to\n  ``HTTPRequest.arguments`` and ``RequestHandler.get_argument[s]``\n  in WSGI and non-WSGI modes.\n* On Python 3, `tornado.escape.json_encode` no longer accepts byte strings.\n* On Python 3, the ``get_authenticated_user`` methods in `tornado.auth`\n  now return character strings instead of byte strings.\n* ``tornado.netutil.TCPServer`` has moved to its own module,\n  `tornado.tcpserver`.\n* The Tornado test suite now requires ``unittest2`` when run on Python 2.6.\n* `tornado.options.options` is no longer a subclass of `dict`; attribute-style\n  access is now required.\n\n\nDetailed changes by module\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nMultiple modules\n~~~~~~~~~~~~~~~~\n\n* Tornado no longer logs to the root logger.  Details on the new logging\n  scheme can be found under the `tornado.log` module.  Note that in some\n  cases this will require that you add an explicit logging configuration\n  in order to see any output (perhaps just calling ``logging.basicConfig()``),\n  although both `.IOLoop.start()` and `tornado.options.parse_command_line`\n  will do this for you.\n* On python 3.2+, methods that take an ``ssl_options`` argument (on\n  `.SSLIOStream`, `.TCPServer`, and `.HTTPServer`) now accept either a\n  dictionary of options or an `ssl.SSLContext` object.\n* New optional dependency on `concurrent.futures` to provide better support\n  for working with threads.  `concurrent.futures` is in the standard library\n  for Python 3.2+, and can be installed on older versions with\n  ``pip install futures``.\n\n`tornado.autoreload`\n~~~~~~~~~~~~~~~~~~~~\n\n* `tornado.autoreload` is now more reliable when there are errors at import\n  time.\n* Calling `tornado.autoreload.start` (or creating an `.Application` with\n  ``debug=True``) twice on the same `.IOLoop` now does nothing (instead of\n  creating multiple periodic callbacks).  Starting autoreload on\n  more than one `.IOLoop` in the same process now logs a warning.\n* Scripts run by autoreload no longer inherit ``__future__`` imports\n  used by Tornado.\n\n`tornado.auth`\n~~~~~~~~~~~~~~\n\n* On Python 3, the ``get_authenticated_user`` method family now returns\n  character strings instead of byte strings.\n* Asynchronous methods defined in `tornado.auth` now return a\n  `.Future`, and their ``callback`` argument is optional.  The\n  ``Future`` interface is preferred as it offers better error handling\n  (the previous interface just logged a warning and returned None).\n* The `tornado.auth` mixin classes now define a method\n  ``get_auth_http_client``, which can be overridden to use a non-default\n  `.AsyncHTTPClient` instance (e.g. to use a different `.IOLoop`)\n* Subclasses of `.OAuthMixin` are encouraged to override\n  `.OAuthMixin._oauth_get_user_future` instead of ``_oauth_get_user``,\n  although both methods are still supported.\n\n`tornado.concurrent`\n~~~~~~~~~~~~~~~~~~~~\n\n* New module `tornado.concurrent` contains code to support working with\n  `concurrent.futures`, or to emulate future-based interface when that module\n  is not available.\n\n``tornado.curl_httpclient``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Preliminary support for ``tornado.curl_httpclient`` on Python 3.  The latest\n  official release of pycurl only supports Python 2, but Ubuntu has a\n  port available in 12.10 (``apt-get install python3-pycurl``).  This port\n  currently has bugs that prevent it from handling arbitrary binary data\n  but it should work for textual (utf8) resources.\n* Fix a crash with libcurl 7.29.0 if a curl object is created and closed\n  without being used.\n\n`tornado.escape`\n~~~~~~~~~~~~~~~~\n\n* On Python 3, `~tornado.escape.json_encode` no longer accepts byte strings.\n  This mirrors the behavior of the underlying json module.  Python 2 behavior\n  is unchanged but should be faster.\n\n`tornado.gen`\n~~~~~~~~~~~~~\n\n* New decorator ``@gen.coroutine`` is available as an alternative to\n  ``@gen.engine``.  It automatically returns a\n  `.Future`, and within the function instead of\n  calling a callback you return a value with ``raise\n  gen.Return(value)`` (or simply ``return value`` in Python 3.3).\n* Generators may now yield `.Future` objects.\n* Callbacks produced by ``gen.Callback`` and ``gen.Task`` are now automatically\n  stack-context-wrapped, to minimize the risk of context leaks when used\n  with asynchronous functions that don't do their own wrapping.\n* Fixed a memory leak involving generators, `.RequestHandler.flush`,\n  and clients closing connections while output is being written.\n* Yielding a large list no longer has quadratic performance.\n\n\n`tornado.httpclient`\n~~~~~~~~~~~~~~~~~~~~\n\n* `.AsyncHTTPClient.fetch` now returns a `.Future` and its callback argument\n  is optional.  When the future interface is used, any error will be raised\n  automatically, as if `.HTTPResponse.rethrow` was called.\n* `.AsyncHTTPClient.configure` and all `.AsyncHTTPClient` constructors\n  now take a ``defaults`` keyword argument.  This argument should be a\n  dictionary, and its values will be used in place of corresponding\n  attributes of `~tornado.httpclient.HTTPRequest` that are not set.\n* All unset attributes of `tornado.httpclient.HTTPRequest` are now\n  ``None``.  The default values of some attributes\n  (``connect_timeout``, ``request_timeout``, ``follow_redirects``,\n  ``max_redirects``, ``use_gzip``, ``proxy_password``,\n  ``allow_nonstandard_methods``, and ``validate_cert`` have been moved\n  from `~tornado.httpclient.HTTPRequest` to the client\n  implementations.\n* The ``max_clients`` argument to `.AsyncHTTPClient` is now a keyword-only\n  argument.\n* Keyword arguments to `.AsyncHTTPClient.configure` are no longer used\n  when instantiating an implementation subclass directly.\n* Secondary `.AsyncHTTPClient` callbacks (``streaming_callback``,\n  ``header_callback``, and ``prepare_curl_callback``) now respect\n  ``StackContext``.\n\n`tornado.httpserver`\n~~~~~~~~~~~~~~~~~~~~\n\n* `.HTTPServer` no longer logs an error when it is unable to read a second\n  request from an HTTP 1.1 keep-alive connection.\n* `.HTTPServer` now takes a ``protocol`` keyword argument which can be set\n  to ``https`` if the server is behind an SSL-decoding proxy that does not\n  set any supported X-headers.\n* ``tornado.httpserver.HTTPConnection`` now has a ``set_close_callback``\n  method that should be used instead of reaching into its ``stream``\n  attribute.\n* Empty HTTP request arguments are no longer ignored.  This applies to\n  ``HTTPRequest.arguments`` and ``RequestHandler.get_argument[s]``\n  in WSGI and non-WSGI modes.\n\n`tornado.ioloop`\n~~~~~~~~~~~~~~~~\n\n* New function `.IOLoop.current` returns the ``IOLoop`` that is running\n  on the current thread (as opposed to `.IOLoop.instance`, which returns a\n  specific thread's (usually the main thread's) IOLoop).\n* New method `.IOLoop.add_future` to run a callback on the IOLoop when\n  an asynchronous `.Future` finishes.\n* `.IOLoop` now has a static `configure <.Configurable.configure>`\n  method like the one on `.AsyncHTTPClient`, which can be used to\n  select an `.IOLoop` implementation other than the default.\n* The `.IOLoop` poller implementations (``select``, ``epoll``, ``kqueue``)\n  are now available as distinct subclasses of `.IOLoop`.  Instantiating\n  `.IOLoop` will continue to automatically choose the best available\n  implementation.\n* The `.IOLoop` constructor has a new keyword argument ``time_func``,\n  which can be used to set the time function used when scheduling callbacks.\n  This is most useful with the `time.monotonic` function, introduced\n  in Python 3.3 and backported to older versions via the ``monotime``\n  module.  Using a monotonic clock here avoids problems when the system\n  clock is changed.\n* New function `.IOLoop.time` returns the current time according to the\n  IOLoop.  To use the new monotonic clock functionality, all calls to\n  `.IOLoop.add_timeout` must be either pass a `datetime.timedelta` or\n  a time relative to `.IOLoop.time`, not `time.time`.  (`time.time` will\n  continue to work only as long as the IOLoop's ``time_func`` argument\n  is not used).\n* New convenience method `.IOLoop.run_sync` can be used to start an IOLoop\n  just long enough to run a single coroutine.\n* New method `.IOLoop.add_callback_from_signal` is safe to use in a signal\n  handler (the regular `.add_callback` method may deadlock).\n* `.IOLoop` now uses `signal.set_wakeup_fd` where available (Python 2.6+\n  on Unix) to avoid a race condition that could result in Python signal\n  handlers being delayed.\n* Method ``IOLoop.running()`` has been removed.\n* `.IOLoop` has been refactored to better support subclassing.\n* `.IOLoop.add_callback` and `.add_callback_from_signal` now take\n  ``*args, **kwargs`` to pass along to the callback.\n\n`tornado.iostream`\n~~~~~~~~~~~~~~~~~~\n\n* `.IOStream.connect` now has an optional ``server_hostname`` argument\n  which will be used for SSL certificate validation when applicable.\n  Additionally, when supported (on Python 3.2+), this hostname\n  will be sent via SNI (and this is supported by ``tornado.simple_httpclient``)\n* Much of `.IOStream` has been refactored into a separate class\n  `.BaseIOStream`.\n* New class `tornado.iostream.PipeIOStream` provides the IOStream\n  interface on pipe file descriptors.\n* `.IOStream` now raises a new exception\n  ``tornado.iostream.StreamClosedError`` when you attempt to read or\n  write after the stream has been closed (by either side).\n* `.IOStream` now simply closes the connection when it gets an\n  ``ECONNRESET`` error, rather than logging it as an error.\n* ``IOStream.error`` no longer picks up unrelated exceptions.\n* `.BaseIOStream.close` now has an ``exc_info`` argument (similar to the\n  one used in the `logging` module) that can be used to set the stream's\n  ``error`` attribute when closing it.\n* `.BaseIOStream.read_until_close` now works correctly when it is called\n  while there is buffered data.\n* Fixed a major performance regression when run on PyPy (introduced in\n  Tornado 2.3).\n\n`tornado.log`\n~~~~~~~~~~~~~\n\n* New module containing `.enable_pretty_logging` and `.LogFormatter`,\n  moved from the options module.\n* `.LogFormatter` now handles non-ascii data in messages and tracebacks better.\n\n`tornado.netutil`\n~~~~~~~~~~~~~~~~~\n\n* New class `tornado.netutil.Resolver` provides an asynchronous\n  interface to DNS resolution.  The default implementation is still\n  blocking, but non-blocking implementations are available using one\n  of three optional dependencies: `~tornado.netutil.ThreadedResolver`\n  using the `concurrent.futures` thread pool,\n  `tornado.platform.caresresolver.CaresResolver` using the ``pycares``\n  library, or ``tornado.platform.twisted.TwistedResolver`` using ``twisted``\n* New function `tornado.netutil.is_valid_ip` returns true if a given string\n  is a valid IP (v4 or v6) address.\n* `tornado.netutil.bind_sockets` has a new ``flags`` argument that can\n  be used to pass additional flags to ``getaddrinfo``.\n* `tornado.netutil.bind_sockets` no longer sets ``AI_ADDRCONFIG``; this will\n  cause it to bind to both ipv4 and ipv6 more often than before.\n* `tornado.netutil.bind_sockets` now works when Python was compiled\n  with ``--disable-ipv6`` but IPv6 DNS resolution is available on the\n  system.\n* ``tornado.netutil.TCPServer`` has moved to its own module, `tornado.tcpserver`.\n\n`tornado.options`\n~~~~~~~~~~~~~~~~~\n\n* The class underlying the functions in `tornado.options` is now public\n  (`tornado.options.OptionParser`).  This can be used to create multiple\n  independent option sets, such as for subcommands.\n* `tornado.options.parse_config_file` now configures logging automatically\n  by default, in the same way that `~tornado.options.parse_command_line` does.\n* New function `tornado.options.add_parse_callback` schedules a callback\n  to be run after the command line or config file has been parsed.  The\n  keyword argument ``final=False`` can be used on either parsing function\n  to suppress these callbacks.\n* `tornado.options.define` now takes a ``callback`` argument.  This callback\n  will be run with the new value whenever the option is changed.  This is\n  especially useful for options that set other options, such as by reading\n  from a config file.\n* `tornado.options.parse_command_line` ``--help`` output now goes to ``stderr``\n  rather than ``stdout``.\n* `tornado.options.options` is no longer a subclass of `dict`; attribute-style\n  access is now required.\n* `tornado.options.options` (and `.OptionParser` instances generally) now\n  have a `.mockable()` method that returns a wrapper object compatible with\n  `mock.patch <unittest.mock.patch>`.\n* Function ``tornado.options.enable_pretty_logging`` has been moved to the\n  `tornado.log` module.\n\n`tornado.platform.caresresolver`\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* New module containing an asynchronous implementation of the `.Resolver`\n  interface, using the ``pycares`` library.\n\n`tornado.platform.twisted`\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* New class ``tornado.platform.twisted.TwistedIOLoop`` allows Tornado\n  code to be run on the Twisted reactor (as opposed to the existing\n  ``TornadoReactor``, which bridges the gap in the other direction).\n* New class ``tornado.platform.twisted.TwistedResolver``` is an asynchronous\n  implementation of the `.Resolver` interface.\n\n`tornado.process`\n~~~~~~~~~~~~~~~~~\n\n* New class `tornado.process.Subprocess` wraps `subprocess.Popen` with\n  `.PipeIOStream` access to the child's file descriptors.\n\n``tornado.simple_httpclient``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* ``SimpleAsyncHTTPClient`` now takes a ``resolver`` keyword argument\n  (which may be passed to either the constructor or `configure\n  <.Configurable.configure>`), to allow it to use the new non-blocking\n  `tornado.netutil.Resolver`.\n* When following redirects, ``SimpleAsyncHTTPClient`` now treats a 302\n  response code the same as a 303.  This is contrary to the HTTP spec\n  but consistent with all browsers and other major HTTP clients\n  (including ``CurlAsyncHTTPClient``).\n* The behavior of ``header_callback`` with ``SimpleAsyncHTTPClient`` has\n  changed and is now the same as that of ``CurlAsyncHTTPClient``.  The\n  header callback now receives the first line of the response (e.g.\n  ``HTTP/1.0 200 OK``) and the final empty line.\n* ``tornado.simple_httpclient`` now accepts responses with a 304\n  status code that include a ``Content-Length`` header.\n* Fixed a bug in which ``SimpleAsyncHTTPClient`` callbacks were being run in the\n  client's ``stack_context``.\n\n``tornado.stack_context``\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* ``stack_context.wrap`` now runs the wrapped callback in a more consistent\n  environment by recreating contexts even if they already exist on the\n  stack.\n* Fixed a bug in which stack contexts could leak from one callback\n  chain to another.\n* Yield statements inside a ``with`` statement can cause stack\n  contexts to become inconsistent; an exception will now be raised\n  when this case is detected.\n\n`tornado.template`\n~~~~~~~~~~~~~~~~~~\n\n* Errors while rendering templates no longer log the generated code,\n  since the enhanced stack traces (from version 2.1) should make this\n  unnecessary.\n* The ``{% apply %}`` directive now works properly with functions that return\n  both unicode strings and byte strings (previously only byte strings were\n  supported).\n* Code in templates is no longer affected by Tornado's ``__future__`` imports\n  (which previously included ``absolute_import`` and ``division``).\n\n\n`tornado.testing`\n~~~~~~~~~~~~~~~~~\n\n* New function `tornado.testing.bind_unused_port` both chooses a port\n  and binds a socket to it, so there is no risk of another process\n  using the same port.  ``get_unused_port`` is now deprecated.\n* New decorator `tornado.testing.gen_test` can be used to allow for\n  yielding `tornado.gen` objects in tests, as an alternative to the\n  ``stop`` and ``wait`` methods of `.AsyncTestCase`.\n* `tornado.testing.AsyncTestCase` and friends now extend ``unittest2.TestCase``\n  when it is available (and continue to use the standard ``unittest`` module\n  when ``unittest2`` is not available)\n* `tornado.testing.ExpectLog` can be used as a finer-grained alternative\n  to ``tornado.testing.LogTrapTestCase``\n* The command-line interface to `tornado.testing.main` now supports\n  additional arguments from the underlying `unittest` module:\n  ``verbose``, ``quiet``, ``failfast``, ``catch``, ``buffer``.\n* The deprecated ``--autoreload`` option of `tornado.testing.main` has\n  been removed.  Use ``python -m tornado.autoreload`` as a prefix command\n  instead.\n* The ``--httpclient`` option of `tornado.testing.main` has been moved\n  to ``tornado.test.runtests`` so as not to pollute the application\n  option namespace.  The `tornado.options` module's new callback\n  support now makes it easy to add options from a wrapper script\n  instead of putting all possible options in `tornado.testing.main`.\n* `.AsyncHTTPTestCase` no longer calls `.AsyncHTTPClient.close` for tests\n  that use the singleton `.IOLoop.instance`.\n* ``LogTrapTestCase`` no longer fails when run in unknown logging\n  configurations.  This allows tests to be run under nose, which does its\n  own log buffering (``LogTrapTestCase`` doesn't do anything useful in this\n  case, but at least it doesn't break things any more).\n\n``tornado.util``\n~~~~~~~~~~~~~~~~\n\n* ``tornado.util.b`` (which was only intended for internal use) is gone.\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n* `.RequestHandler.set_header` now overwrites previous header values\n  case-insensitively.\n* `tornado.web.RequestHandler` has new attributes ``path_args`` and\n  ``path_kwargs``, which contain the positional and keyword arguments\n  that are passed to the ``get``/``post``/etc method.  These attributes\n  are set before those methods are called, so they are available during\n  ``prepare()``\n* `tornado.web.ErrorHandler` no longer requires XSRF tokens on ``POST``\n  requests, so posts to an unknown url will always return 404 instead of\n  complaining about XSRF tokens.\n* Several methods related to HTTP status codes now take a ``reason`` keyword\n  argument to specify an alternate \"reason\" string (i.e. the \"Not Found\" in\n  \"HTTP/1.1 404 Not Found\").  It is now possible to set status codes other\n  than those defined in the spec, as long as a reason string is given.\n* The ``Date`` HTTP header is now set by default on all responses.\n* ``Etag``/``If-None-Match`` requests now work with `.StaticFileHandler`.\n* `.StaticFileHandler` no longer sets ``Cache-Control: public`` unnecessarily.\n* When gzip is enabled in a `tornado.web.Application`, appropriate\n  ``Vary: Accept-Encoding`` headers are now sent.\n* It is no longer necessary to pass all handlers for a host in a single\n  `.Application.add_handlers` call.  Now the request will be matched\n  against the handlers for any ``host_pattern`` that includes the request's\n  ``Host`` header.\n\n`tornado.websocket`\n~~~~~~~~~~~~~~~~~~~\n\n* Client-side WebSocket support is now available:\n  `tornado.websocket.websocket_connect`\n* `.WebSocketHandler` has new methods `~.WebSocketHandler.ping` and\n  `~.WebSocketHandler.on_pong` to send pings to the browser (not\n  supported on the ``draft76`` protocol)\n"
  },
  {
    "path": "docs/releases/v3.0.1.rst",
    "content": "What's new in Tornado 3.0.1\n===========================\n\nApr 8, 2013\n-----------\n\n* The interface of `tornado.auth.FacebookGraphMixin` is now consistent\n  with its documentation and the rest of the module.  The\n  ``get_authenticated_user`` and ``facebook_request`` methods return a\n  ``Future`` and the ``callback`` argument is optional.\n* The `tornado.testing.gen_test` decorator will no longer be recognized\n  as a (broken) test by ``nose``.\n* Work around a bug in Ubuntu 13.04 betas involving an incomplete backport\n  of the ``ssl.match_hostname`` function.\n* `tornado.websocket.websocket_connect` now fails cleanly when it attempts\n  to connect to a non-websocket url.\n* ``tornado.testing.LogTrapTestCase`` once again works with byte strings\n  on Python 2.\n* The ``request`` attribute of `tornado.httpclient.HTTPResponse` is\n  now always an `~tornado.httpclient.HTTPRequest`, never a ``_RequestProxy``.\n* Exceptions raised by the `tornado.gen` module now have better messages\n  when tuples are used as callback keys.\n"
  },
  {
    "path": "docs/releases/v3.0.2.rst",
    "content": "What's new in Tornado 3.0.2\n===========================\n\nJun 2, 2013\n-----------\n\n* `tornado.auth.TwitterMixin` now defaults to version 1.1 of the\n  Twitter API, instead of version 1.0 which is being `discontinued on\n  June 11 <https://dev.twitter.com/calendar>`_.  It also now uses HTTPS\n  when talking to Twitter.\n* Fixed a potential memory leak with a long chain of `.gen.coroutine`\n  or ``gen.engine`` functions.\n"
  },
  {
    "path": "docs/releases/v3.1.0.rst",
    "content": "What's new in Tornado 3.1\n=========================\n\nJun 15, 2013\n------------\n\nMultiple modules\n~~~~~~~~~~~~~~~~\n\n* Many reference cycles have been broken up throughout the package,\n  allowing for more efficient garbage collection on CPython.\n* Silenced some log messages when connections are opened and immediately\n  closed (i.e. port scans), or other situations related to closed\n  connections.\n* Various small speedups: `.HTTPHeaders` case normalization, `.UIModule`\n  proxy objects, precompile some regexes.\n\n`tornado.auth`\n~~~~~~~~~~~~~~\n\n* `~tornado.auth.OAuthMixin` always sends ``oauth_version=1.0`` in its\n  request as required by the spec.\n* `~tornado.auth.FacebookGraphMixin` now uses ``self._FACEBOOK_BASE_URL``\n  in `~.FacebookGraphMixin.facebook_request` to allow the base url to be\n  overridden.\n* The ``authenticate_redirect`` and ``authorize_redirect`` methods in the\n  `tornado.auth` mixin classes all now return Futures.  These methods\n  are asynchronous in `.OAuthMixin` and derived classes, although they\n  do not take a callback.  The `.Future` these methods return must be\n  yielded if they are called from a function decorated with `.gen.coroutine`\n  (but not ``gen.engine``).\n* `.TwitterMixin` now uses ``/account/verify_credentials`` to get information\n  about the logged-in user, which is more robust against changing screen\n  names.\n* The ``demos`` directory (in the source distribution) has a new\n  ``twitter`` demo using `.TwitterMixin`.\n\n`tornado.escape`\n~~~~~~~~~~~~~~~~\n\n* `.url_escape` and `.url_unescape` have a new ``plus`` argument (defaulting\n  to True for consistency with the previous behavior) which specifies\n  whether they work like `urllib.parse.unquote` or `urllib.parse.unquote_plus`.\n\n`tornado.gen`\n~~~~~~~~~~~~~\n\n* Fixed a potential memory leak with long chains of `tornado.gen` coroutines.\n\n`tornado.httpclient`\n~~~~~~~~~~~~~~~~~~~~\n\n* `tornado.httpclient.HTTPRequest` takes a new argument ``auth_mode``,\n  which can be either ``basic`` or ``digest``.  Digest authentication\n  is only supported with ``tornado.curl_httpclient``.\n* ``tornado.curl_httpclient`` no longer goes into an infinite loop when\n  pycurl returns a negative timeout.\n* ``curl_httpclient`` now supports the ``PATCH`` and ``OPTIONS`` methods\n  without the use of ``allow_nonstandard_methods=True``.\n* Worked around a class of bugs in libcurl that would result in\n  errors from `.IOLoop.update_handler` in various scenarios including\n  digest authentication and socks proxies.\n* The ``TCP_NODELAY`` flag is now set when appropriate in ``simple_httpclient``.\n* ``simple_httpclient`` no longer logs exceptions, since those exceptions\n  are made available to the caller as ``HTTPResponse.error``.\n\n`tornado.httpserver`\n~~~~~~~~~~~~~~~~~~~~\n\n* `tornado.httpserver.HTTPServer` handles malformed HTTP headers more\n  gracefully.\n* `.HTTPServer` now supports lists of IPs in ``X-Forwarded-For``\n  (it chooses the last, i.e. nearest one).\n* Memory is now reclaimed promptly on CPython when an HTTP request\n  fails because it exceeded the maximum upload size.\n* The ``TCP_NODELAY`` flag is now set when appropriate in `.HTTPServer`.\n* The `.HTTPServer` ``no_keep_alive`` option is now respected with\n  HTTP 1.0 connections that explicitly pass ``Connection: keep-alive``.\n* The ``Connection: keep-alive`` check for HTTP 1.0 connections is now\n  case-insensitive.\n* The `str` and `repr` of ``tornado.httpserver.HTTPRequest`` no longer\n  include the request body, reducing log spam on errors (and potential\n  exposure/retention of private data).\n\n`tornado.httputil`\n~~~~~~~~~~~~~~~~~~\n\n* The cache used in `.HTTPHeaders` will no longer grow without bound.\n\n`tornado.ioloop`\n~~~~~~~~~~~~~~~~\n\n* Some `.IOLoop` implementations (such as ``pyzmq``) accept objects\n  other than integer file descriptors; these objects will now have\n  their ``.close()`` method called when the ``IOLoop` is closed with\n  ``all_fds=True``.\n* The stub handles left behind by `.IOLoop.remove_timeout` will now get\n  cleaned up instead of waiting to expire.\n\n`tornado.iostream`\n~~~~~~~~~~~~~~~~~~\n\n* Fixed a bug in `.BaseIOStream.read_until_close` that would sometimes\n  cause data to be passed to the final callback instead of the streaming\n  callback.\n* The `.IOStream` close callback is now run more reliably if there is\n  an exception in ``_try_inline_read``.\n* New method `.BaseIOStream.set_nodelay` can be used to set the\n  ``TCP_NODELAY`` flag.\n* Fixed a case where errors in ``SSLIOStream.connect`` (and\n  ``SimpleAsyncHTTPClient``) were not being reported correctly.\n\n`tornado.locale`\n~~~~~~~~~~~~~~~~\n\n* `.Locale.format_date` now works on Python 3.\n\n`tornado.netutil`\n~~~~~~~~~~~~~~~~~\n\n* The default `.Resolver` implementation now works on Solaris.\n* `.Resolver` now has a `~.Resolver.close` method.\n* Fixed a potential CPU DoS when ``tornado.netutil.ssl_match_hostname``\n  is used on certificates with an abusive wildcard pattern.\n* All instances of `.ThreadedResolver` now share a single thread pool,\n  whose size is set by the first one to be created (or the static\n  ``Resolver.configure`` method).\n* `.ExecutorResolver` is now documented for public use.\n* `.bind_sockets` now works in configurations with incomplete IPv6 support.\n\n`tornado.options`\n~~~~~~~~~~~~~~~~~\n\n* `tornado.options.define` with ``multiple=True`` now works on Python 3.\n* `tornado.options.options` and other `.OptionParser` instances support some\n  new dict-like methods: `~.OptionParser.items()`, iteration over keys,\n  and (read-only) access to options with square braket syntax.\n  `.OptionParser.group_dict` returns all options with a given group\n  name, and `.OptionParser.as_dict` returns all options.\n\n`tornado.process`\n~~~~~~~~~~~~~~~~~\n\n* `tornado.process.Subprocess` no longer leaks file descriptors into\n  the child process, which fixes a problem in which the child could not\n  detect that the parent process had closed its stdin pipe.\n* `.Subprocess.set_exit_callback` now works for subprocesses created\n  without an explicit ``io_loop`` parameter.\n\n``tornado.stack_context``\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* ``tornado.stack_context`` has been rewritten and is now much faster.\n* New function ``run_with_stack_context`` facilitates the use of stack\n  contexts with coroutines.\n\n`tornado.tcpserver`\n~~~~~~~~~~~~~~~~~~~\n\n* The constructors of `.TCPServer` and `.HTTPServer` now take a\n  ``max_buffer_size`` keyword argument.\n\n`tornado.template`\n~~~~~~~~~~~~~~~~~~\n\n* Some internal names used by the template system have been changed;\n  now all \"reserved\" names in templates start with ``_tt_``.\n\n`tornado.testing`\n~~~~~~~~~~~~~~~~~\n\n* `tornado.testing.AsyncTestCase.wait` now raises the correct exception\n  when it has been modified by ``tornado.stack_context``.\n* `tornado.testing.gen_test` can now be called as ``@gen_test(timeout=60)``\n  to give some tests a longer timeout than others.\n* The environment variable ``ASYNC_TEST_TIMEOUT`` can now be set to\n  override the default timeout for `.AsyncTestCase.wait` and `.gen_test`.\n* `.bind_unused_port` now passes ``None`` instead of ``0`` as the port\n  to ``getaddrinfo``, which works better with some unusual network\n  configurations.\n\n`tornado.util`\n~~~~~~~~~~~~~~\n\n* `tornado.util.import_object` now works with top-level module names that\n  do not contain a dot.\n* `tornado.util.import_object` now consistently raises `ImportError`\n  instead of `AttributeError` when it fails.\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n* The ``handlers`` list passed to the `tornado.web.Application` constructor\n  and `~tornado.web.Application.add_handlers` methods can now contain\n  lists in addition to tuples and `~tornado.web.URLSpec` objects.\n* `tornado.web.StaticFileHandler` now works on Windows when the client\n  passes an ``If-Modified-Since`` timestamp before 1970.\n* New method `.RequestHandler.log_exception` can be overridden to\n  customize the logging behavior when an exception is uncaught.  Most\n  apps that currently override ``_handle_request_exception`` can now\n  use a combination of `.RequestHandler.log_exception` and\n  `.write_error`.\n* `.RequestHandler.get_argument` now raises `.MissingArgumentError`\n  (a subclass of `tornado.web.HTTPError`, which is what it raised previously)\n  if the argument cannot be found.\n* `.Application.reverse_url` now uses `.url_escape` with ``plus=False``,\n  i.e. spaces are encoded as ``%20`` instead of ``+``.\n* Arguments extracted from the url path are now decoded with\n  `.url_unescape` with ``plus=False``, so plus signs are left as-is\n  instead of being turned into spaces.\n* `.RequestHandler.send_error` will now only be called once per request,\n  even if multiple exceptions are caught by the stack context.\n* The ``tornado.web.asynchronous`` decorator is no longer necessary for\n  methods that return a `.Future` (i.e. those that use the `.gen.coroutine`\n  or ``return_future`` decorators)\n* `.RequestHandler.prepare` may now be asynchronous if it returns a\n  `.Future`.  The ``tornado.web.asynchronous`` decorator is not used with\n  ``prepare``; one of the `.Future`-related decorators should be used instead.\n* ``RequestHandler.current_user`` may now be assigned to normally.\n* `.RequestHandler.redirect` no longer silently strips control characters\n  and whitespace.  It is now an error to pass control characters, newlines\n  or tabs.\n* `.StaticFileHandler` has been reorganized internally and now has additional\n  extension points that can be overridden in subclasses.\n* `.StaticFileHandler` now supports HTTP ``Range`` requests.\n  `.StaticFileHandler` is still not suitable for files too large to\n  comfortably fit in memory, but ``Range`` support is necessary in some\n  browsers to enable seeking of HTML5 audio and video.\n* `.StaticFileHandler` now uses longer hashes by default, and uses the same\n  hashes for ``Etag`` as it does for versioned urls.\n* `.StaticFileHandler.make_static_url` and `.RequestHandler.static_url`\n  now have an additional keyword argument ``include_version`` to suppress\n  the url versioning.\n* `.StaticFileHandler` now reads its file in chunks, which will reduce\n  memory fragmentation.\n* Fixed a problem with the ``Date`` header and cookie expiration dates\n  when the system locale is set to a non-english configuration.\n\n`tornado.websocket`\n~~~~~~~~~~~~~~~~~~~\n\n* `.WebSocketHandler` now catches `.StreamClosedError` and runs\n  `~.WebSocketHandler.on_close` immediately instead of logging a\n  stack trace.\n* New method `.WebSocketHandler.set_nodelay` can be used to set the\n  ``TCP_NODELAY`` flag.\n\n`tornado.wsgi`\n~~~~~~~~~~~~~~\n\n* Fixed an exception in `.WSGIContainer` when the connection is closed\n  while output is being written.\n"
  },
  {
    "path": "docs/releases/v3.1.1.rst",
    "content": "What's new in Tornado 3.1.1\n===========================\n\nSep 1, 2013\n-----------\n\n* `.StaticFileHandler` no longer fails if the client requests a ``Range`` that\n  is larger than the entire file (Facebook has a crawler that does this).\n* `.RequestHandler.on_connection_close` now works correctly on subsequent\n  requests of a keep-alive connection.\n"
  },
  {
    "path": "docs/releases/v3.2.0.rst",
    "content": "What's new in Tornado 3.2\n=========================\n\nJan 14, 2014\n------------\n\nInstallation\n~~~~~~~~~~~~\n* Tornado now depends on the `backports.ssl_match_hostname\n  <https://pypi.python.org/pypi/backports.ssl_match_hostname>`_ when\n  running on Python 2.  This will be installed automatically when using ``pip``\n  or ``easy_install``\n* Tornado now includes an optional C extension module, which greatly improves\n  performance of websockets.  This extension will be built automatically\n  if a C compiler is found at install time.\n\nNew modules\n~~~~~~~~~~~\n\n* The `tornado.platform.asyncio` module provides integration with the\n  ``asyncio`` module introduced in Python 3.4 (also available for Python\n  3.3 with ``pip install asyncio``).\n\n`tornado.auth`\n~~~~~~~~~~~~~~\n\n* Added `.GoogleOAuth2Mixin` support authentication to Google services\n  with OAuth 2 instead of OpenID and OAuth 1.\n* `.FacebookGraphMixin` has been updated to use the current Facebook login\n  URL, which saves a redirect.\n\n`tornado.concurrent`\n~~~~~~~~~~~~~~~~~~~~\n\n* ``TracebackFuture`` now accepts a ``timeout`` keyword argument (although\n  it is still incorrect to use a non-zero timeout in non-blocking code).\n\n``tornado.curl_httpclient``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* ``tornado.curl_httpclient`` now works on Python 3 with the\n  soon-to-be-released pycurl 7.19.3, which will officially support\n  Python 3 for the first time.  Note that there are some unofficial\n  Python 3 ports of pycurl (Ubuntu has included one for its past\n  several releases); these are not supported for use with Tornado.\n\n`tornado.escape`\n~~~~~~~~~~~~~~~~\n\n* `.xhtml_escape` now escapes apostrophes as well.\n* `tornado.escape.utf8`, `.to_unicode`, and `.native_str` now raise\n  `TypeError` instead of `AssertionError` when given an invalid value.\n\n`tornado.gen`\n~~~~~~~~~~~~~\n\n* Coroutines may now yield dicts in addition to lists to wait for\n  multiple tasks in parallel.\n* Improved performance of `tornado.gen` when yielding a `.Future` that is\n  already done.\n\n`tornado.httpclient`\n~~~~~~~~~~~~~~~~~~~~\n\n* `tornado.httpclient.HTTPRequest` now uses property setters so that\n  setting attributes after construction applies the same conversions\n  as ``__init__`` (e.g. converting the body attribute to bytes).\n\n`tornado.httpserver`\n~~~~~~~~~~~~~~~~~~~~\n\n* Malformed ``x-www-form-urlencoded`` request bodies will now log a warning\n  and continue instead of causing the request to fail (similar to the existing\n  handling of malformed ``multipart/form-data`` bodies.  This is done mainly\n  because some libraries send this content type by default even when the data\n  is not form-encoded.\n* Fix some error messages for unix sockets (and other non-IP sockets)\n\n`tornado.ioloop`\n~~~~~~~~~~~~~~~~\n\n* `.IOLoop` now uses ``IOLoop.handle_callback_exception`` consistently for\n  error logging.\n* `.IOLoop` now frees callback objects earlier, reducing memory usage\n  while idle.\n* `.IOLoop` will no longer call `logging.basicConfig` if there is a handler\n  defined for the root logger or for the ``tornado`` or ``tornado.application``\n  loggers (previously it only looked at the root logger).\n\n`tornado.iostream`\n~~~~~~~~~~~~~~~~~~\n\n* `.IOStream` now recognizes ``ECONNABORTED`` error codes in more places\n  (which was mainly an issue on Windows).\n* `.IOStream` now frees memory earlier if a connection is closed while\n  there is data in the write buffer.\n* `.PipeIOStream` now handles ``EAGAIN`` error codes correctly.\n* `.SSLIOStream` now initiates the SSL handshake automatically without\n  waiting for the application to try and read or write to the connection.\n* Swallow a spurious exception from ``set_nodelay`` when a connection\n  has been reset.\n\n`tornado.locale`\n~~~~~~~~~~~~~~~~\n\n* `.Locale.format_date` no longer forces the use of absolute\n  dates in Russian.\n\n`tornado.log`\n~~~~~~~~~~~~~\n\n* Fix an error from `tornado.log.enable_pretty_logging` when\n  `sys.stderr` does not have an ``isatty`` method.\n* `tornado.log.LogFormatter` now accepts keyword arguments ``fmt``\n  and ``datefmt``.\n\n`tornado.netutil`\n~~~~~~~~~~~~~~~~~\n\n* `.is_valid_ip` (and therefore ``HTTPRequest.remote_ip``) now rejects\n  empty strings.\n* Synchronously using `.ThreadedResolver` at import time to resolve\n  a unicode hostname no longer deadlocks.\n\n`tornado.platform.twisted`\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* ``.TwistedResolver``` now has better error handling.\n\n`tornado.process`\n~~~~~~~~~~~~~~~~~\n\n* `.Subprocess` no longer leaks file descriptors if `subprocess.Popen` fails.\n\n``tornado.simple_httpclient``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* ``simple_httpclient`` now applies the ``connect_timeout`` to requests\n  that are queued and have not yet started.\n* On Python 2.6, ``simple_httpclient`` now uses TLSv1 instead of SSLv3.\n* ``simple_httpclient`` now enforces the connect timeout during DNS resolution.\n* The embedded ``ca-certificates.crt`` file has been updated with the current\n  Mozilla CA list.\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n* `.StaticFileHandler` no longer fails if the client requests a ``Range`` that\n  is larger than the entire file (Facebook has a crawler that does this).\n* `.RequestHandler.on_connection_close` now works correctly on subsequent\n  requests of a keep-alive connection.\n* New application setting ``default_handler_class`` can be used to easily\n  set up custom 404 pages.\n* New application settings ``autoreload``, ``compiled_template_cache``,\n  ``static_hash_cache``, and ``serve_traceback`` can be used to control\n  individual aspects of debug mode.\n* New methods `.RequestHandler.get_query_argument` and\n  `.RequestHandler.get_body_argument` and new attributes\n  ``HTTPRequest.query_arguments`` and ``HTTPRequest.body_arguments`` allow access\n  to arguments without intermingling those from the query string with those\n  from the request body.\n* `.RequestHandler.decode_argument` and related methods now raise\n  an ``HTTPError(400)`` instead of `UnicodeDecodeError` when the\n  argument could not be decoded.\n* `.RequestHandler.clear_all_cookies` now accepts ``domain`` and ``path``\n  arguments, just like `~.RequestHandler.clear_cookie`.\n* It is now possible to specify handlers by name when using the\n  `tornado.web.URLSpec` class.\n* `.Application` now accepts 4-tuples to specify the ``name`` parameter\n  (which previously required constructing a `tornado.web.URLSpec` object\n  instead of a tuple).\n* Fixed an incorrect error message when handler methods return a value\n  other than None or a Future.\n* Exceptions will no longer be logged twice when using both ``@asynchronous``\n  and ``@gen.coroutine``\n\n\n`tornado.websocket`\n~~~~~~~~~~~~~~~~~~~\n\n* `.WebSocketHandler.write_message` now raises `.WebSocketClosedError` instead\n  of `AttributeError` when the connection has been closed.\n* `.websocket_connect` now accepts preconstructed ``HTTPRequest`` objects.\n* Fix a bug with `.WebSocketHandler` when used with some proxies that\n  unconditionally modify the ``Connection`` header.\n* `.websocket_connect` now returns an error immediately for refused connections\n  instead of waiting for the timeout.\n* `.WebSocketClientConnection` now has a ``close`` method.\n\n`tornado.wsgi`\n~~~~~~~~~~~~~~\n\n* `.WSGIContainer` now calls the iterable's ``close()`` method even if\n  an error is raised, in compliance with the spec.\n"
  },
  {
    "path": "docs/releases/v3.2.1.rst",
    "content": "What's new in Tornado 3.2.1\n===========================\n\nMay 5, 2014\n-----------\n\nSecurity fixes\n~~~~~~~~~~~~~~\n\n* The signed-value format used by `.RequestHandler.set_secure_cookie`\n  and `.RequestHandler.get_secure_cookie` has changed to be more secure.\n  **This is a disruptive change**.  The ``secure_cookie`` functions\n  take new ``version`` parameters to support transitions between cookie\n  formats.\n* The new cookie format fixes a vulnerability that may be present in\n  applications that use multiple cookies where the name of one cookie\n  is a prefix of the name of another.\n* To minimize disruption, cookies in the older format will be accepted\n  by default until they expire.  Applications that may be vulnerable\n  can reject all cookies in the older format by passing ``min_version=2``\n  to `.RequestHandler.get_secure_cookie`.\n* Thanks to Joost Pol of `Certified Secure <https://www.certifiedsecure.com>`_\n  for reporting this issue.\n\nBackwards-compatibility notes\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Signed cookies issued by `.RequestHandler.set_secure_cookie` in Tornado\n  3.2.1 cannot be read by older releases.  If you need to run 3.2.1\n  in parallel with older releases, you can pass ``version=1`` to\n  `.RequestHandler.set_secure_cookie` to issue cookies that are\n  backwards-compatible (but have a known weakness, so this option\n  should only be used for a transitional period).\n\nOther changes\n~~~~~~~~~~~~~\n\n* The C extension used to speed up the websocket module now compiles\n  correctly on Windows with MSVC and 64-bit mode.  The fallback to\n  the pure-Python alternative now works correctly on Mac OS X machines\n  with no C compiler installed.\n"
  },
  {
    "path": "docs/releases/v3.2.2.rst",
    "content": "What's new in Tornado 3.2.2\n===========================\n\nJune 3, 2014\n------------\n\nSecurity fixes\n~~~~~~~~~~~~~~\n\n* The XSRF token is now encoded with a random mask on each request.\n  This makes it safe to include in compressed pages without being\n  vulnerable to the `BREACH attack <http://breachattack.com>`_.\n  This applies to most applications that use both the ``xsrf_cookies``\n  and ``gzip`` options (or have gzip applied by a proxy).\n\nBackwards-compatibility notes\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* If Tornado 3.2.2 is run at the same time as older versions on the same\n  domain, there is some potential for issues with the differing cookie\n  versions.  The `.Application` setting ``xsrf_cookie_version=1`` can\n  be used for a transitional period to generate the older cookie format\n  on newer servers.\n\nOther changes\n~~~~~~~~~~~~~\n\n* ``tornado.platform.asyncio`` is now compatible with ``trollius`` version 0.3.\n"
  },
  {
    "path": "docs/releases/v4.0.0.rst",
    "content": "What's new in Tornado 4.0\n=========================\n\nJuly 15, 2014\n-------------\n\nHighlights\n~~~~~~~~~~\n\n* The `tornado.web.stream_request_body` decorator allows large files to be\n  uploaded with limited memory usage.\n* Coroutines are now faster and are used extensively throughout Tornado itself.\n  More methods now return `Futures <.Future>`, including most `.IOStream`\n  methods and `.RequestHandler.flush`.\n* Many user-overridden methods are now allowed to return a `.Future`\n  for flow control.\n* HTTP-related code is now shared between the `tornado.httpserver`,\n  ``tornado.simple_httpclient`` and `tornado.wsgi` modules, making support\n  for features such as chunked and gzip encoding more consistent.\n  `.HTTPServer` now uses new delegate interfaces defined in `tornado.httputil`\n  in addition to its old single-callback interface.\n* New module `tornado.tcpclient` creates TCP connections with non-blocking\n  DNS, SSL handshaking, and support for IPv6.\n\n\nBackwards-compatibility notes\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* `tornado.concurrent.Future` is no longer thread-safe; use\n  `concurrent.futures.Future` when thread-safety is needed.\n* Tornado now depends on the `certifi <https://pypi.python.org/pypi/certifi>`_\n  package instead of bundling its own copy of the Mozilla CA list. This will\n  be installed automatically when using ``pip`` or ``easy_install``.\n* This version includes the changes to the secure cookie format first\n  introduced in version :doc:`3.2.1 <v3.2.1>`, and the xsrf token change\n  in version :doc:`3.2.2 <v3.2.2>`.  If you are upgrading from an earlier\n  version, see those versions' release notes.\n* WebSocket connections from other origin sites are now rejected by default.\n  To accept cross-origin websocket connections, override\n  the new method `.WebSocketHandler.check_origin`.\n* `.WebSocketHandler` no longer supports the old ``draft 76`` protocol\n  (this mainly affects Safari 5.x browsers).  Applications should use\n  non-websocket workarounds for these browsers.\n* Authors of alternative `.IOLoop` implementations should see the changes\n  to `.IOLoop.add_handler` in this release.\n* The ``RequestHandler.async_callback`` and ``WebSocketHandler.async_callback``\n  wrapper functions have been removed; they have been obsolete for a long\n  time due to stack contexts (and more recently coroutines).\n* ``curl_httpclient`` now requires a minimum of libcurl version 7.21.1 and\n  pycurl 7.18.2.\n* Support for ``RequestHandler.get_error_html`` has been removed;\n  override `.RequestHandler.write_error` instead.\n\n\nOther notes\n~~~~~~~~~~~\n\n* The git repository has moved to https://github.com/tornadoweb/tornado.\n  All old links should be redirected to the new location.\n* An `announcement mailing list\n  <http://groups.google.com/group/python-tornado-announce>`_ is now available.\n* All Tornado modules are now importable on Google App Engine (although\n  the App Engine environment does not allow the system calls used\n  by `.IOLoop` so many modules are still unusable).\n\n`tornado.auth`\n~~~~~~~~~~~~~~\n\n* Fixed a bug in ``.FacebookMixin`` on Python 3.\n* When using the `.Future` interface, exceptions are more reliably delivered\n  to the caller.\n\n`tornado.concurrent`\n~~~~~~~~~~~~~~~~~~~~\n\n* `tornado.concurrent.Future` is now always thread-unsafe (previously\n  it would be thread-safe if the `concurrent.futures` package was available).\n  This improves performance and provides more consistent semantics.\n  The parts of Tornado that accept Futures will accept both Tornado's\n  thread-unsafe Futures and the thread-safe `concurrent.futures.Future`.\n* `tornado.concurrent.Future` now includes all the functionality\n  of the old ``TracebackFuture`` class.  ``TracebackFuture`` is now\n  simply an alias for ``Future``.\n\n``tornado.curl_httpclient``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* ``curl_httpclient`` now passes along the HTTP \"reason\" string\n  in ``response.reason``.\n\n`tornado.gen`\n~~~~~~~~~~~~~\n\n* Performance of coroutines has been improved.\n* Coroutines no longer generate ``StackContexts`` by default, but they\n  will be created on demand when needed.\n* The internals of the `tornado.gen` module have been rewritten to\n  improve performance when using ``Futures``, at the expense of some\n  performance degradation for the older ``YieldPoint`` interfaces.\n* New function `.with_timeout` wraps a `.Future` and raises an exception\n  if it doesn't complete in a given amount of time.\n* New object `.moment` can be yielded to allow the IOLoop to run for\n  one iteration before resuming.\n* ``Task`` is now a function returning a `.Future` instead of a ``YieldPoint``\n  subclass.  This change should be transparent to application code, but\n  allows ``Task`` to take advantage of the newly-optimized `.Future`\n  handling.\n\n`tornado.http1connection`\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* New module contains the HTTP implementation shared by `tornado.httpserver`\n  and ``tornado.simple_httpclient``.\n\n`tornado.httpclient`\n~~~~~~~~~~~~~~~~~~~~\n\n* The command-line HTTP client (``python -m tornado.httpclient $URL``)\n  now works on Python 3.\n* Fixed a memory leak in `.AsyncHTTPClient` shutdown that affected\n  applications that created many HTTP clients and IOLoops.\n* New client request parameter ``decompress_response`` replaces\n  the existing ``use_gzip`` parameter; both names are accepted.\n\n`tornado.httpserver`\n~~~~~~~~~~~~~~~~~~~~\n\n* ``tornado.httpserver.HTTPRequest`` has moved to\n  `tornado.httputil.HTTPServerRequest`.\n* HTTP implementation has been unified with ``tornado.simple_httpclient``\n  in `tornado.http1connection`.\n* Now supports ``Transfer-Encoding: chunked`` for request bodies.\n* Now supports ``Content-Encoding: gzip`` for request bodies if\n  ``decompress_request=True`` is passed to the `.HTTPServer` constructor.\n* The ``connection`` attribute of `.HTTPServerRequest` is now documented\n  for public use; applications are expected to write their responses\n  via the `.HTTPConnection` interface.\n* The ``HTTPServerRequest.write`` and ``HTTPServerRequest.finish`` methods\n  are now deprecated.  (`.RequestHandler.write` and `.RequestHandler.finish`\n  are *not* deprecated; this only applies to the methods on\n  `.HTTPServerRequest`)\n* `.HTTPServer` now supports `.HTTPServerConnectionDelegate` in addition to\n  the old ``request_callback`` interface.  The delegate interface supports\n  streaming of request bodies.\n* `.HTTPServer` now detects the error of an application sending a\n  ``Content-Length`` error that is inconsistent with the actual content.\n* New constructor arguments ``max_header_size`` and ``max_body_size``\n  allow separate limits to be set for different parts of the request.\n  ``max_body_size`` is applied even in streaming mode.\n* New constructor argument ``chunk_size`` can be used to limit the amount\n  of data read into memory at one time per request.\n* New constructor arguments ``idle_connection_timeout`` and ``body_timeout``\n  allow time limits to be placed on the reading of requests.\n* Form-encoded message bodies are now parsed for all HTTP methods, not just\n  ``POST``, ``PUT``, and ``PATCH``.\n\n`tornado.httputil`\n~~~~~~~~~~~~~~~~~~\n\n* `.HTTPServerRequest` was moved to this module from `tornado.httpserver`.\n* New base classes `.HTTPConnection`, `.HTTPServerConnectionDelegate`,\n  and `.HTTPMessageDelegate` define the interaction between applications\n  and the HTTP implementation.\n\n\n`tornado.ioloop`\n~~~~~~~~~~~~~~~~\n\n* `.IOLoop.add_handler` and related methods now accept file-like objects\n  in addition to raw file descriptors.  Passing the objects is recommended\n  (when possible) to avoid a garbage-collection-related problem in unit tests.\n* New method `.IOLoop.clear_instance` makes it possible to uninstall the\n  singleton instance.\n* Timeout scheduling is now more robust against slow callbacks.\n* `.IOLoop.add_timeout` is now a bit more efficient.\n* When a function run by the `.IOLoop` returns a `.Future` and that `.Future`\n  has an exception, the `.IOLoop` will log the exception.\n* New method `.IOLoop.spawn_callback` simplifies the process of launching\n  a fire-and-forget callback that is separated from the caller's stack context.\n* New methods `.IOLoop.call_later` and `.IOLoop.call_at` simplify the\n  specification of relative or absolute timeouts (as opposed to\n  `~.IOLoop.add_timeout`, which used the type of its argument).\n\n`tornado.iostream`\n~~~~~~~~~~~~~~~~~~\n\n* The ``callback`` argument to most `.IOStream` methods is now optional.\n  When called without a callback the method will return a `.Future`\n  for use with coroutines.\n* New method `.IOStream.start_tls` converts an `.IOStream` to an\n  `.SSLIOStream`.\n* No longer gets confused when an ``IOError`` or ``OSError`` without\n  an ``errno`` attribute is raised.\n* `.BaseIOStream.read_bytes` now accepts a ``partial`` keyword argument,\n  which can be used to return before the full amount has been read.\n  This is a more coroutine-friendly alternative to ``streaming_callback``.\n* `.BaseIOStream.read_until` and ``read_until_regex`` now acept a\n  ``max_bytes`` keyword argument which will cause the request to fail if\n  it cannot be satisfied from the given number of bytes.\n* `.IOStream` no longer reads from the socket into memory if it does not\n  need data to satisfy a pending read.  As a side effect, the close callback\n  will not be run immediately if the other side closes the connection\n  while there is unconsumed data in the buffer.\n* The default ``chunk_size`` has been increased to 64KB (from 4KB)\n* The `.IOStream` constructor takes a new keyword argument\n  ``max_write_buffer_size`` (defaults to unlimited).  Calls to\n  `.BaseIOStream.write` will raise `.StreamBufferFullError` if the amount\n  of unsent buffered data exceeds this limit.\n* ``ETIMEDOUT`` errors are no longer logged.  If you need to distinguish\n  timeouts from other forms of closed connections, examine ``stream.error``\n  from a close callback.\n\n`tornado.netutil`\n~~~~~~~~~~~~~~~~~\n\n* When `.bind_sockets` chooses a port automatically, it will now use\n  the same port for IPv4 and IPv6.\n* TLS compression is now disabled by default on Python 3.3 and higher\n  (it is not possible to change this option in older versions).\n\n`tornado.options`\n~~~~~~~~~~~~~~~~~\n\n* It is now possible to disable the default logging configuration\n  by setting ``options.logging`` to ``None`` instead of the string ``\"none\"``.\n\n`tornado.platform.asyncio`\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Now works on Python 2.6.\n* Now works with Trollius version 0.3.\n\n`tornado.platform.twisted`\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* ``TwistedIOLoop`` now works on Python 3.3+ (with Twisted 14.0.0+).\n\n``tornado.simple_httpclient``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* ``simple_httpclient`` has better support for IPv6, which is now enabled\n  by default.\n* Improved default cipher suite selection (Python 2.7+).\n* HTTP implementation has been unified with ``tornado.httpserver``\n  in `tornado.http1connection`\n* Streaming request bodies are now supported via the ``body_producer``\n  keyword argument to `tornado.httpclient.HTTPRequest`.\n* The ``expect_100_continue`` keyword argument to\n  `tornado.httpclient.HTTPRequest` allows the use of the HTTP ``Expect:\n  100-continue`` feature.\n* ``simple_httpclient`` now raises the original exception (e.g. an `IOError`)\n  in more cases, instead of converting everything to ``HTTPError``.\n\n``tornado.stack_context``\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* The stack context system now has less performance overhead when no\n  stack contexts are active.\n\n`tornado.tcpclient`\n~~~~~~~~~~~~~~~~~~~\n\n* New module which creates TCP connections and IOStreams, including\n  name resolution, connecting, and SSL handshakes.\n\n`tornado.testing`\n~~~~~~~~~~~~~~~~~\n\n* `.AsyncTestCase` now attempts to detect test methods that are generators\n  but were not run with ``@gen_test`` or any similar decorator (this would\n  previously result in the test silently being skipped).\n* Better stack traces are now displayed when a test times out.\n* The ``@gen_test`` decorator now passes along ``*args, **kwargs`` so it\n  can be used on functions with arguments.\n* Fixed the test suite when ``unittest2`` is installed on Python 3.\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n* It is now possible to support streaming request bodies with the\n  `.stream_request_body` decorator and the new `.RequestHandler.data_received`\n  method.\n* `.RequestHandler.flush` now returns a `.Future` if no callback is given.\n* New exception `.Finish` may be raised to finish a request without\n  triggering error handling.\n* When gzip support is enabled, all ``text/*`` mime types will be compressed,\n  not just those on a whitelist.\n* `.Application` now implements the `.HTTPMessageDelegate` interface.\n* ``HEAD`` requests in `.StaticFileHandler` no longer read the entire file.\n* `.StaticFileHandler` now streams response bodies to the client.\n* New setting ``compress_response`` replaces the existing ``gzip``\n  setting; both names are accepted.\n* XSRF cookies that were not generated by this module (i.e. strings without\n  any particular formatting) are once again accepted (as long as the\n  cookie and body/header match).  This pattern was common for\n  testing and non-browser clients but was broken by the changes in\n  Tornado 3.2.2.\n\n`tornado.websocket`\n~~~~~~~~~~~~~~~~~~~\n\n* WebSocket connections from other origin sites are now rejected by default.\n  Browsers do not use the same-origin policy for WebSocket connections as they\n  do for most other browser-initiated communications.  This can be surprising\n  and a security risk, so we disallow these connections on the server side\n  by default.  To accept cross-origin websocket connections, override\n  the new method `.WebSocketHandler.check_origin`.\n* `.WebSocketHandler.close` and `.WebSocketClientConnection.close` now\n  support ``code`` and ``reason`` arguments to send a status code and\n  message to the other side of the connection when closing.  Both classes\n  also have ``close_code`` and ``close_reason`` attributes to receive these\n  values when the other side closes.\n* The C speedup module now builds correctly with MSVC, and can support\n  messages larger than 2GB on 64-bit systems.\n* The fallback mechanism for detecting a missing C compiler now\n  works correctly on Mac OS X.\n* Arguments to `.WebSocketHandler.open` are now decoded in the same way\n  as arguments to `.RequestHandler.get` and similar methods.\n* It is now allowed to override ``prepare`` in a `.WebSocketHandler`,\n  and this method may generate HTTP responses (error pages) in the usual\n  way.  The HTTP response methods are still not allowed once the\n  WebSocket handshake has completed.\n\n`tornado.wsgi`\n~~~~~~~~~~~~~~\n\n* New class ``WSGIAdapter`` supports running a Tornado `.Application` on\n  a WSGI server in a way that is more compatible with Tornado's non-WSGI\n  `.HTTPServer`.  ``WSGIApplication`` is deprecated in favor of using\n  ``WSGIAdapter`` with a regular `.Application`.\n* ``WSGIAdapter`` now supports gzipped output.\n"
  },
  {
    "path": "docs/releases/v4.0.1.rst",
    "content": "What's new in Tornado 4.0.1\n===========================\n\nAug 12, 2014\n------------\n\n* The build will now fall back to pure-python mode if the C extension\n  fails to build for any reason (previously it would fall back for some\n  errors but not others).\n* `.IOLoop.call_at` and `.IOLoop.call_later` now always return\n  a timeout handle for use with `.IOLoop.remove_timeout`.\n* If any callback of a `.PeriodicCallback` or `.IOStream` returns a\n  `.Future`, any error raised in that future will now be logged\n  (similar to the behavior of `.IOLoop.add_callback`).\n* Fixed an exception in client-side websocket connections when the\n  connection is closed.\n* ``simple_httpclient`` once again correctly handles 204 status\n  codes with no content-length header.\n* Fixed a regression in ``simple_httpclient`` that would result in\n  timeouts for certain kinds of errors.\n"
  },
  {
    "path": "docs/releases/v4.0.2.rst",
    "content": "What's new in Tornado 4.0.2\n===========================\n\nSept 10, 2014\n-------------\n\nBug fixes\n~~~~~~~~~\n\n* Fixed a bug that could sometimes cause a timeout to fire after being\n  cancelled.\n* `.AsyncTestCase` once again passes along arguments to test methods,\n  making it compatible with extensions such as Nose's test generators.\n* `.StaticFileHandler` can again compress its responses when gzip is enabled.\n* ``simple_httpclient`` passes its ``max_buffer_size`` argument to the\n  underlying stream.\n* Fixed a reference cycle that can lead to increased memory consumption.\n* `.add_accept_handler` will now limit the number of times it will call\n  `~socket.socket.accept` per `.IOLoop` iteration, addressing a potential\n  starvation issue.\n* Improved error handling in `.IOStream.connect` (primarily for FreeBSD\n  systems)\n"
  },
  {
    "path": "docs/releases/v4.1.0.rst",
    "content": "What's new in Tornado 4.1\n=========================\n\nFeb 7, 2015\n-----------\n\nHighlights\n~~~~~~~~~~\n\n* If a `.Future` contains an exception but that exception is never\n  examined or re-raised (e.g. by yielding the `.Future`), a stack\n  trace will be logged when the `.Future` is garbage-collected.\n* New class `tornado.gen.WaitIterator` provides a way to iterate\n  over ``Futures`` in the order they resolve.\n* The `tornado.websocket` module now supports compression via the\n  \"permessage-deflate\" extension.  Override\n  `.WebSocketHandler.get_compression_options` to enable on the server\n  side, and use the ``compression_options`` keyword argument to\n  `.websocket_connect` on the client side.\n* When the appropriate packages are installed, it is possible to yield\n  `asyncio.Future` or Twisted ``Defered`` objects in Tornado coroutines.\n\nBackwards-compatibility notes\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* `.HTTPServer` now calls ``start_request`` with the correct\n  arguments.  This change is backwards-incompatible, affecting any\n  application which implemented `.HTTPServerConnectionDelegate` by\n  following the example of `.Application` instead of the documented\n  method signatures.\n\n`tornado.concurrent`\n~~~~~~~~~~~~~~~~~~~~\n\n* If a `.Future` contains an exception but that exception is never\n  examined or re-raised (e.g. by yielding the `.Future`), a stack\n  trace will be logged when the `.Future` is garbage-collected.\n* `.Future` now catches and logs exceptions in its callbacks.\n\n``tornado.curl_httpclient``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* ``tornado.curl_httpclient`` now supports request bodies for ``PATCH``\n  and custom methods.\n* ``tornado.curl_httpclient`` now supports resubmitting bodies after\n  following redirects for methods other than ``POST``.\n* ``curl_httpclient`` now runs the streaming and header callbacks on\n  the IOLoop.\n* ``tornado.curl_httpclient`` now uses its own logger for debug output\n  so it can be filtered more easily.\n\n`tornado.gen`\n~~~~~~~~~~~~~\n\n* New class `tornado.gen.WaitIterator` provides a way to iterate\n  over ``Futures`` in the order they resolve.\n* When the `~functools.singledispatch` library is available (standard on\n  Python 3.4, available via ``pip install singledispatch`` on older versions),\n  the `.convert_yielded` function can be used to make other kinds of objects\n  yieldable in coroutines.\n* New function `tornado.gen.sleep` is a coroutine-friendly\n  analogue to `time.sleep`.\n* ``gen.engine`` now correctly captures the stack context for its callbacks.\n\n`tornado.httpclient`\n~~~~~~~~~~~~~~~~~~~~\n\n* `tornado.httpclient.HTTPRequest` accepts a new argument\n  ``raise_error=False`` to suppress the default behavior of raising an\n  error for non-200 response codes.\n\n`tornado.httpserver`\n~~~~~~~~~~~~~~~~~~~~\n\n* `.HTTPServer` now calls ``start_request`` with the correct\n  arguments.  This change is backwards-incompatible, afffecting any\n  application which implemented `.HTTPServerConnectionDelegate` by\n  following the example of `.Application` instead of the documented\n  method signatures.\n* `.HTTPServer` now tolerates extra newlines which are sometimes inserted\n  between requests on keep-alive connections.\n* `.HTTPServer` can now use keep-alive connections after a request\n  with a chunked body.\n* `.HTTPServer` now always reports ``HTTP/1.1`` instead of echoing\n  the request version.\n\n`tornado.httputil`\n~~~~~~~~~~~~~~~~~~\n\n* New function `tornado.httputil.split_host_and_port` for parsing\n  the ``netloc`` portion of URLs.\n* The ``context`` argument to `.HTTPServerRequest` is now optional,\n  and if a context is supplied the ``remote_ip`` attribute is also optional.\n* `.HTTPServerRequest.body` is now always a byte string (previously the default\n  empty body would be a unicode string on python 3).\n* Header parsing now works correctly when newline-like unicode characters\n  are present.\n* Header parsing again supports both CRLF and bare LF line separators.\n* Malformed ``multipart/form-data`` bodies will always be logged\n  quietly instead of raising an unhandled exception; previously\n  the behavior was inconsistent depending on the exact error.\n\n`tornado.ioloop`\n~~~~~~~~~~~~~~~~\n\n* The ``kqueue`` and ``select`` IOLoop implementations now report\n  writeability correctly, fixing flow control in IOStream.\n* When a new `.IOLoop` is created, it automatically becomes \"current\"\n  for the thread if there is not already a current instance.\n* New method `.PeriodicCallback.is_running` can be used to see\n  whether the `.PeriodicCallback` has been started.\n\n`tornado.iostream`\n~~~~~~~~~~~~~~~~~~\n\n* `.IOStream.start_tls` now uses the ``server_hostname`` parameter\n  for certificate validation.\n* `.SSLIOStream` will no longer consume 100% CPU after certain error conditions.\n* `.SSLIOStream` no longer logs ``EBADF`` errors during the handshake as they\n  can result from nmap scans in certain modes.\n\n`tornado.options`\n~~~~~~~~~~~~~~~~~\n\n* `~tornado.options.parse_config_file` now always decodes the config\n  file as utf8 on Python 3.\n* `tornado.options.define` more accurately finds the module defining the\n  option.\n\n``tornado.platform.asyncio``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* It is now possible to yield ``asyncio.Future`` objects in coroutines\n  when the `~functools.singledispatch` library is available and\n  ``tornado.platform.asyncio`` has been imported.\n* New methods `tornado.platform.asyncio.to_tornado_future` and\n  `~tornado.platform.asyncio.to_asyncio_future` convert between\n  the two libraries' `.Future` classes.\n\n``tornado.platform.twisted``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* It is now possible to yield ``Deferred`` objects in coroutines\n  when the `~functools.singledispatch` library is available and\n  ``tornado.platform.twisted`` has been imported.\n\n`tornado.tcpclient`\n~~~~~~~~~~~~~~~~~~~\n\n* `.TCPClient` will no longer raise an exception due to an ill-timed\n  timeout.\n\n`tornado.tcpserver`\n~~~~~~~~~~~~~~~~~~~\n\n* `.TCPServer` no longer ignores its ``read_chunk_size`` argument.\n\n`tornado.testing`\n~~~~~~~~~~~~~~~~~\n\n* `.AsyncTestCase` has better support for multiple exceptions. Previously\n  it would silently swallow all but the last; now it raises the first\n  and logs all the rest.\n* `.AsyncTestCase` now cleans up `.Subprocess` state on ``tearDown`` when\n  necessary.\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n* The ``asynchronous`` decorator now understands `concurrent.futures.Future`\n  in addition to `tornado.concurrent.Future`.\n* `.StaticFileHandler` no longer logs a stack trace if the connection is\n  closed while sending the file.\n* `.RequestHandler.send_error` now supports a ``reason`` keyword\n  argument, similar to `tornado.web.HTTPError`.\n* `.RequestHandler.locale` now has a property setter.\n* `.Application.add_handlers` hostname matching now works correctly with\n  IPv6 literals.\n* Redirects for the `.Application` ``default_host`` setting now match\n  the request protocol instead of redirecting HTTPS to HTTP.\n* Malformed ``_xsrf`` cookies are now ignored instead of causing\n  uncaught exceptions.\n* ``Application.start_request`` now has the same signature as\n  `.HTTPServerConnectionDelegate.start_request`.\n\n`tornado.websocket`\n~~~~~~~~~~~~~~~~~~~\n\n* The `tornado.websocket` module now supports compression via the\n  \"permessage-deflate\" extension.  Override\n  `.WebSocketHandler.get_compression_options` to enable on the server\n  side, and use the ``compression_options`` keyword argument to\n  `.websocket_connect` on the client side.\n* `.WebSocketHandler` no longer logs stack traces when the connection\n  is closed.\n* `.WebSocketHandler.open` now accepts ``*args, **kw`` for consistency\n  with ``RequestHandler.get`` and related methods.\n* The ``Sec-WebSocket-Version`` header now includes all supported versions.\n* `.websocket_connect` now has a ``on_message_callback`` keyword argument\n  for callback-style use without ``read_message()``.\n"
  },
  {
    "path": "docs/releases/v4.2.0.rst",
    "content": "What's new in Tornado 4.2\n=========================\n\nMay 26, 2015\n------------\n\nBackwards-compatibility notes\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* ``SSLIOStream.connect`` and `.IOStream.start_tls` now validate certificates\n  by default.\n* Certificate validation will now use the system CA root certificates instead\n  of ``certifi`` when possible (i.e. Python 2.7.9+ or 3.4+). This includes\n  `.IOStream` and ``simple_httpclient``, but not ``curl_httpclient``.\n* The default SSL configuration has become stricter, using\n  `ssl.create_default_context` where available on the client side.\n  (On the server side, applications are encouraged to migrate from the\n  ``ssl_options`` dict-based API to pass an `ssl.SSLContext` instead).\n* The deprecated classes in the `tornado.auth` module, ``GoogleMixin``,\n  ``FacebookMixin``, and ``FriendFeedMixin`` have been removed.\n\nNew modules: `tornado.locks` and `tornado.queues`\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThese modules provide classes for coordinating coroutines, merged from\n`Toro <https://toro.readthedocs.io>`_.\n\nTo port your code from Toro's queues to Tornado 4.2, import `.Queue`,\n`.PriorityQueue`, or `.LifoQueue` from `tornado.queues` instead of from\n``toro``.\n\nUse `.Queue` instead of Toro's ``JoinableQueue``. In Tornado the methods\n`~.Queue.join` and `~.Queue.task_done` are available on all queues, not on a\nspecial ``JoinableQueue``.\n\nTornado queues raise exceptions specific to Tornado instead of reusing\nexceptions from the Python standard library.\nTherefore instead of catching the standard `queue.Empty` exception from\n`.Queue.get_nowait`, catch the special `tornado.queues.QueueEmpty` exception,\nand instead of catching the standard `queue.Full` from `.Queue.get_nowait`,\ncatch `tornado.queues.QueueFull`.\n\nTo port from Toro's locks to Tornado 4.2, import `.Condition`, `.Event`,\n`.Semaphore`, `.BoundedSemaphore`, or `.Lock` from `tornado.locks`\ninstead of from ``toro``.\n\nToro's ``Semaphore.wait`` allowed a coroutine to wait for the semaphore to\nbe unlocked *without* acquiring it. This encouraged unorthodox patterns; in\nTornado, just use `~.Semaphore.acquire`.\n\nToro's ``Event.wait`` raised a ``Timeout`` exception after a timeout. In\nTornado, `.Event.wait` raises ``tornado.gen.TimeoutError``.\n\nToro's ``Condition.wait`` also raised ``Timeout``, but in Tornado, the `.Future`\nreturned by `.Condition.wait` resolves to False after a timeout::\n\n    @gen.coroutine\n    def await_notification():\n        if not (yield condition.wait(timeout=timedelta(seconds=1))):\n            print('timed out')\n        else:\n            print('condition is true')\n\nIn lock and queue methods, wherever Toro accepted ``deadline`` as a keyword\nargument, Tornado names the argument ``timeout`` instead.\n\nToro's ``AsyncResult`` is not merged into Tornado, nor its exceptions\n``NotReady`` and ``AlreadySet``. Use a `.Future` instead. If you wrote code like\nthis::\n\n    from tornado import gen\n    import toro\n\n    result = toro.AsyncResult()\n\n    @gen.coroutine\n    def setter():\n        result.set(1)\n\n    @gen.coroutine\n    def getter():\n        value = yield result.get()\n        print(value)  # Prints \"1\".\n\nThen the Tornado equivalent is::\n\n    from tornado import gen\n    from tornado.concurrent import Future\n\n    result = Future()\n\n    @gen.coroutine\n    def setter():\n        result.set_result(1)\n\n    @gen.coroutine\n    def getter():\n        value = yield result\n        print(value)  # Prints \"1\".\n\n`tornado.autoreload`\n~~~~~~~~~~~~~~~~~~~~\n\n* Improved compatibility with Windows.\n* Fixed a bug in Python 3 if a module was imported during a reload check.\n\n`tornado.concurrent`\n~~~~~~~~~~~~~~~~~~~~\n\n* `.run_on_executor` now accepts arguments to control which attributes\n  it uses to find the `.IOLoop` and executor.\n\n`tornado.curl_httpclient`\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Fixed a bug that would cause the client to stop processing requests\n  if an exception occurred in certain places while there is a queue.\n\n`tornado.escape`\n~~~~~~~~~~~~~~~~\n\n* `.xhtml_escape` now supports numeric character references in hex\n  format (``&#x20;``)\n\n`tornado.gen`\n~~~~~~~~~~~~~\n\n* `.WaitIterator` no longer uses weak references, which fixes several\n  garbage-collection-related bugs.\n* ``tornado.gen.Multi`` and `tornado.gen.multi_future` (which are used when\n  yielding a list or dict in a coroutine) now log any exceptions after the\n  first if more than one `.Future` fails (previously they would be logged\n  when the `.Future` was garbage-collected, but this is more reliable).\n  Both have a new keyword argument ``quiet_exceptions`` to suppress\n  logging of certain exception types; to use this argument you must\n  call ``Multi`` or ``multi_future`` directly instead of simply yielding\n  a list.\n* `.multi_future` now works when given multiple copies of the same `.Future`.\n* On Python 3, catching an exception in a coroutine no longer leads to\n  leaks via ``Exception.__context__``.\n\n`tornado.httpclient`\n~~~~~~~~~~~~~~~~~~~~\n\n* The ``raise_error`` argument now works correctly with the synchronous\n  `.HTTPClient`.\n* The synchronous `.HTTPClient` no longer interferes with `.IOLoop.current()`.\n\n`tornado.httpserver`\n~~~~~~~~~~~~~~~~~~~~\n\n* `.HTTPServer` is now a subclass of `tornado.util.Configurable`.\n\n`tornado.httputil`\n~~~~~~~~~~~~~~~~~~\n\n* `.HTTPHeaders` can now be copied with `copy.copy` and `copy.deepcopy`.\n\n`tornado.ioloop`\n~~~~~~~~~~~~~~~~\n\n* The `.IOLoop` constructor now has a ``make_current`` keyword argument\n  to control whether the new `.IOLoop` becomes `.IOLoop.current()`.\n* Third-party implementations of `.IOLoop` should accept ``**kwargs``\n  in their ``IOLoop.initialize`` methods and pass them to the superclass\n  implementation.\n* `.PeriodicCallback` is now more efficient when the clock jumps forward\n  by a large amount.\n\n`tornado.iostream`\n~~~~~~~~~~~~~~~~~~\n\n* ``SSLIOStream.connect`` and `.IOStream.start_tls` now validate certificates\n  by default.\n* New method `.SSLIOStream.wait_for_handshake` allows server-side applications\n  to wait for the handshake to complete in order to verify client certificates\n  or use NPN/ALPN.\n* The `.Future` returned by ``SSLIOStream.connect`` now resolves after the\n  handshake is complete instead of as soon as the TCP connection is\n  established.\n* Reduced logging of SSL errors.\n* `.BaseIOStream.read_until_close` now works correctly when a\n  ``streaming_callback`` is given but ``callback`` is None (i.e. when\n  it returns a `.Future`)\n\n`tornado.locale`\n~~~~~~~~~~~~~~~~\n\n* New method `.GettextLocale.pgettext` allows additional context to be\n  supplied for gettext translations.\n\n`tornado.log`\n~~~~~~~~~~~~~\n\n* `.define_logging_options` now works correctly when given a non-default\n  ``options`` object.\n\n`tornado.process`\n~~~~~~~~~~~~~~~~~\n\n* New method `.Subprocess.wait_for_exit` is a coroutine-friendly\n  version of `.Subprocess.set_exit_callback`.\n\n`tornado.simple_httpclient`\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Improved performance on Python 3 by reusing a single `ssl.SSLContext`.\n* New constructor argument ``max_body_size`` controls the maximum response\n  size the client is willing to accept. It may be bigger than\n  ``max_buffer_size`` if ``streaming_callback`` is used.\n\n`tornado.tcpserver`\n~~~~~~~~~~~~~~~~~~~\n\n* `.TCPServer.handle_stream` may be a coroutine (so that any exceptions\n  it raises will be logged).\n\n`tornado.util`\n~~~~~~~~~~~~~~\n\n* `.import_object` now supports unicode strings on Python 2.\n* `.Configurable.initialize` now supports positional arguments.\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n* Key versioning support for cookie signing. ``cookie_secret`` application\n  setting can now contain a dict of valid keys with version as key. The\n  current signing key then must be specified via ``key_version`` setting.\n* Parsing of the ``If-None-Match`` header now follows the RFC and supports\n  weak validators.\n* Passing ``secure=False`` or ``httponly=False`` to\n  `.RequestHandler.set_cookie` now works as expected (previously only the\n  presence of the argument was considered and its value was ignored).\n* `.RequestHandler.get_arguments` now requires that its ``strip`` argument\n  be of type bool. This helps prevent errors caused by the slightly dissimilar\n  interfaces between the singular and plural methods.\n* Errors raised in ``_handle_request_exception`` are now logged more reliably.\n* `.RequestHandler.redirect` now works correctly when called from a handler\n  whose path begins with two slashes.\n* Passing messages containing ``%`` characters to `tornado.web.HTTPError`\n  no longer causes broken error messages.\n\n`tornado.websocket`\n~~~~~~~~~~~~~~~~~~~\n\n* The ``on_close`` method will no longer be called more than once.\n* When the other side closes a connection, we now echo the received close\n  code back instead of sending an empty close frame.\n"
  },
  {
    "path": "docs/releases/v4.2.1.rst",
    "content": "What's new in Tornado 4.2.1\n===========================\n\nJul 17, 2015\n------------\n\nSecurity fix\n~~~~~~~~~~~~\n\n* This release fixes a path traversal vulnerability in `.StaticFileHandler`,\n  in which files whose names *started with* the ``static_path`` directory\n  but were not actually *in* that directory could be accessed.\n"
  },
  {
    "path": "docs/releases/v4.3.0.rst",
    "content": "What's new in Tornado 4.3\n=========================\n\nNov 6, 2015\n-----------\n\nHighlights\n~~~~~~~~~~\n\n* The new async/await keywords in Python 3.5 are supported. In most cases,\n  ``async def`` can be used in place of the ``@gen.coroutine`` decorator.\n  Inside a function defined with ``async def``, use ``await`` instead of\n  ``yield`` to wait on an asynchronous operation. Coroutines defined with\n  async/await will be faster than those defined with ``@gen.coroutine`` and\n  ``yield``, but do not support some features including ``Callback``/``Wait`` or\n  the ability to yield a Twisted ``Deferred``. See :ref:`the users'\n  guide <native_coroutines>` for more.\n* The async/await keywords are also available when compiling with Cython in\n  older versions of Python.\n\nDeprecation notice\n~~~~~~~~~~~~~~~~~~\n\n* This will be the last release of Tornado to support Python 2.6 or 3.2.\n  Note that PyPy3 will continue to be supported even though it implements\n  a mix of Python 3.2 and 3.3 features.\n\nInstallation\n~~~~~~~~~~~~\n\n* Tornado has several new dependencies: ``ordereddict`` on Python 2.6,\n  ``singledispatch`` on all Python versions prior to 3.4 (This was an\n  optional dependency in prior versions of Tornado, and is now\n  mandatory), and ``backports_abc>=0.4`` on all versions prior to\n  3.5. These dependencies will be installed automatically when installing\n  with ``pip`` or ``setup.py install``. These dependencies will not\n  be required when running on Google App Engine.\n* Binary wheels are provided for Python 3.5 on Windows (32 and 64 bit).\n\n`tornado.auth`\n~~~~~~~~~~~~~~\n\n* New method `.OAuth2Mixin.oauth2_request` can be used to make authenticated\n  requests with an access token.\n* Now compatible with callbacks that have been compiled with Cython.\n\n`tornado.autoreload`\n~~~~~~~~~~~~~~~~~~~~\n\n* Fixed an issue with the autoreload command-line wrapper in which\n  imports would be incorrectly interpreted as relative.\n\n`tornado.curl_httpclient`\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Fixed parsing of multi-line headers.\n* ``allow_nonstandard_methods=True`` now bypasses body sanity checks,\n  in the same way as in ``simple_httpclient``.\n* The ``PATCH`` method now allows a body without\n  ``allow_nonstandard_methods=True``.\n\n`tornado.gen`\n~~~~~~~~~~~~~\n\n* `.WaitIterator` now supports the ``async for`` statement on Python 3.5.\n* ``@gen.coroutine`` can be applied to functions compiled with Cython.\n  On python versions prior to 3.5, the ``backports_abc`` package must\n  be installed for this functionality.\n* ``Multi`` and `.multi_future` are deprecated and replaced by\n  a unified function `.multi`.\n\n`tornado.httpclient`\n~~~~~~~~~~~~~~~~~~~~\n\n* `tornado.httpclient.HTTPError` is now copyable with the `copy` module.\n\n`tornado.httpserver`\n~~~~~~~~~~~~~~~~~~~~\n\n* Requests containing both ``Content-Length`` and ``Transfer-Encoding``\n  will be treated as an error.\n\n`tornado.httputil`\n~~~~~~~~~~~~~~~~~~\n\n* `.HTTPHeaders` can now be pickled and unpickled.\n\n`tornado.ioloop`\n~~~~~~~~~~~~~~~~\n\n* ``IOLoop(make_current=True)`` now works as intended instead\n  of raising an exception.\n* The Twisted and asyncio IOLoop implementations now clear\n  ``current()`` when they exit, like the standard IOLoops.\n* `.IOLoop.add_callback` is faster in the single-threaded case.\n* `.IOLoop.add_callback` no longer raises an error when called on\n  a closed IOLoop, but the callback will not be invoked.\n\n`tornado.iostream`\n~~~~~~~~~~~~~~~~~~\n\n* Coroutine-style usage of `.IOStream` now converts most errors into\n  `.StreamClosedError`, which has the effect of reducing log noise from\n  exceptions that are outside the application's control (especially\n  SSL errors).\n* `.StreamClosedError` now has a ``real_error`` attribute which indicates\n  why the stream was closed. It is the same as the ``error`` attribute of\n  `.IOStream` but may be more easily accessible than the `.IOStream` itself.\n* Improved error handling in `~.BaseIOStream.read_until_close`.\n* Logging is less noisy when an SSL server is port scanned.\n* ``EINTR`` is now handled on all reads.\n\n`tornado.locale`\n~~~~~~~~~~~~~~~~\n\n* `tornado.locale.load_translations` now accepts encodings other than\n  UTF-8. UTF-16 and UTF-8 will be detected automatically if a BOM is\n  present; for other encodings `.load_translations` has an ``encoding``\n  parameter.\n\n`tornado.locks`\n~~~~~~~~~~~~~~~\n\n* `.Lock` and `.Semaphore` now support the ``async with`` statement on\n  Python 3.5.\n\n`tornado.log`\n~~~~~~~~~~~~~\n\n* A new time-based log rotation mode is available with\n  ``--log_rotate_mode=time``, ``--log-rotate-when``, and\n  ``log-rotate-interval``.\n\n`tornado.netutil`\n~~~~~~~~~~~~~~~~~\n\n* `.bind_sockets` now supports ``SO_REUSEPORT`` with the ``reuse_port=True``\n  argument.\n\n`tornado.options`\n~~~~~~~~~~~~~~~~~\n\n* Dashes and underscores are now fully interchangeable in option names.\n\n`tornado.queues`\n~~~~~~~~~~~~~~~~\n\n* `.Queue` now supports the ``async for`` statement on Python 3.5.\n\n`tornado.simple_httpclient`\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* When following redirects, ``streaming_callback`` and\n  ``header_callback`` will no longer be run on the redirect responses\n  (only the final non-redirect).\n* Responses containing both ``Content-Length`` and ``Transfer-Encoding``\n  will be treated as an error.\n\n`tornado.template`\n~~~~~~~~~~~~~~~~~~\n\n* `tornado.template.ParseError` now includes the filename in addition to\n  line number.\n* Whitespace handling has become more configurable. The `.Loader`\n  constructor now has a ``whitespace`` argument, there is a new\n  ``template_whitespace`` `.Application` setting, and there is a new\n  ``{% whitespace %}`` template directive. All of these options take\n  a mode name defined in the `tornado.template.filter_whitespace` function.\n  The default mode is ``single``, which is the same behavior as prior\n  versions of Tornado.\n* Non-ASCII filenames are now supported.\n\n`tornado.testing`\n~~~~~~~~~~~~~~~~~\n\n* `.ExpectLog` objects now have a boolean ``logged_stack`` attribute to\n  make it easier to test whether an exception stack trace was logged.\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n* The hard limit of 4000 bytes per outgoing header has been removed.\n* `.StaticFileHandler` returns the correct ``Content-Type`` for files\n  with ``.gz``, ``.bz2``, and ``.xz`` extensions.\n* Responses smaller than 1000 bytes will no longer be compressed.\n* The default gzip compression level is now 6 (was 9).\n* Fixed a regression in Tornado 4.2.1 that broke `.StaticFileHandler`\n  with a ``path`` of ``/``.\n* `tornado.web.HTTPError` is now copyable with the `copy` module.\n* The exception `.Finish` now accepts an argument which will be passed to\n  the method `.RequestHandler.finish`.\n* New `.Application` setting ``xsrf_cookie_kwargs`` can be used to set\n  additional attributes such as ``secure`` or ``httponly`` on the\n  XSRF cookie.\n* `.Application.listen` now returns the `.HTTPServer` it created.\n\n`tornado.websocket`\n~~~~~~~~~~~~~~~~~~~\n\n* Fixed handling of continuation frames when compression is enabled.\n"
  },
  {
    "path": "docs/releases/v4.4.0.rst",
    "content": "What's new in Tornado 4.4\n=========================\n\nJul 15, 2016\n------------\n\nGeneral\n~~~~~~~\n\n* Tornado now requires Python 2.7 or 3.3+; versions 2.6 and 3.2 are no\n  longer supported. Pypy3 is still supported even though its latest\n  release is mainly based on Python 3.2.\n* The `monotonic <https://pypi.python.org/pypi/monotonic>`_ package is\n  now supported as an alternative to `Monotime\n  <https://pypi.python.org/pypi/Monotime>`_ for monotonic clock support\n  on Python 2.\n\n``tornado.curl_httpclient``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Failures in ``_curl_setup_request`` no longer cause the\n  ``max_clients`` pool to be exhausted.\n* Non-ascii header values are now handled correctly.\n\n`tornado.gen`\n~~~~~~~~~~~~~\n\n* `.with_timeout` now accepts any yieldable object (except\n  ``YieldPoint``), not just `tornado.concurrent.Future`.\n\n`tornado.httpclient`\n~~~~~~~~~~~~~~~~~~~~\n\n* The errors raised by timeouts now indicate what state the request\n  was in; the error message is no longer simply \"599 Timeout\".\n* Calling `repr` on a `tornado.httpclient.HTTPError` no longer raises\n  an error.\n\n`tornado.httpserver`\n~~~~~~~~~~~~~~~~~~~~\n\n* Int-like enums (including `http.HTTPStatus`) can now be used as\n  status codes.\n* Responses with status code ``204 No Content`` no longer emit a\n  ``Content-Length: 0`` header.\n\n`tornado.ioloop`\n~~~~~~~~~~~~~~~~\n\n* Improved performance when there are large numbers of active timeouts.\n\n`tornado.netutil`\n~~~~~~~~~~~~~~~~~\n\n* All included `.Resolver` implementations raise `IOError` (or a\n  subclass) for any resolution failure.\n\n`tornado.options`\n~~~~~~~~~~~~~~~~~\n\n* Options can now be modified with subscript syntax in addition to\n  attribute syntax.\n* The special variable ``__file__`` is now available inside config files.\n\n``tornado.simple_httpclient``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* HTTP/1.0 (not 1.1) responses without a ``Content-Length`` header now\n  work correctly.\n\n`tornado.tcpserver`\n~~~~~~~~~~~~~~~~~~~\n\n* `.TCPServer.bind` now accepts a ``reuse_port`` argument.\n\n`tornado.testing`\n~~~~~~~~~~~~~~~~~\n\n* Test sockets now always use ``127.0.0.1`` instead of ``localhost``.\n  This avoids conflicts when the automatically-assigned port is\n  available on IPv4 but not IPv6, or in unusual network configurations\n  when ``localhost`` has multiple IP addresses.\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n* ``image/svg+xml`` is now on the list of compressible mime types.\n* Fixed an error on Python 3 when compression is used with multiple\n  ``Vary`` headers.\n\n`tornado.websocket`\n~~~~~~~~~~~~~~~~~~~\n\n* ``WebSocketHandler.__init__`` now uses `super`, which improves\n  support for multiple inheritance.\n"
  },
  {
    "path": "docs/releases/v4.4.1.rst",
    "content": "What's new in Tornado 4.4.1\n===========================\n\nJul 23, 2016\n------------\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n* Fixed a regression in Tornado 4.4 which caused URL regexes\n  containing backslash escapes outside capturing groups to be\n  rejected.\n"
  },
  {
    "path": "docs/releases/v4.4.2.rst",
    "content": "What's new in Tornado 4.4.2\n===========================\n\nOct 1, 2016\n------------\n\nSecurity fixes\n~~~~~~~~~~~~~~\n\n* A difference in cookie parsing between Tornado and web browsers\n  (especially when combined with Google Analytics) could allow an\n  attacker to set arbitrary cookies and bypass XSRF protection. The\n  cookie parser has been rewritten to fix this attack.\n\nBackwards-compatibility notes\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Cookies containing certain special characters (in particular semicolon\n  and square brackets) are now parsed differently.\n* If the cookie header contains a combination of valid and invalid cookies,\n  the valid ones will be returned (older versions of Tornado would reject the\n  entire header for a single invalid cookie).\n"
  },
  {
    "path": "docs/releases/v4.4.3.rst",
    "content": "What's new in Tornado 4.4.3\n===========================\n\nMar 30, 2017\n------------\n\nBug fixes\n~~~~~~~~~\n\n* The `tornado.auth` module has been updated for compatibility with `a\n  change to Facebook's access_token endpoint.\n  <https://github.com/tornadoweb/tornado/pull/1977>`_\n"
  },
  {
    "path": "docs/releases/v4.5.0.rst",
    "content": "What's new in Tornado 4.5\n=========================\n\nApr 16, 2017\n------------\n\nBackwards-compatibility warning\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- The `tornado.websocket` module now imposes a limit on the size of incoming\n  messages, which defaults to 10MiB.\n\nNew module\n~~~~~~~~~~\n\n- `tornado.routing` provides a more flexible routing system than the one built in\n  to `.Application`.\n\nGeneral changes\n~~~~~~~~~~~~~~~\n\n- Reduced the number of circular references, reducing memory usage and\n  improving performance.\n\n`tornado.auth`\n~~~~~~~~~~~~~~\n\n* The `tornado.auth` module has been updated for compatibility with `a\n  change to Facebook's access_token endpoint\n  <https://github.com/tornadoweb/tornado/pull/1977>`_. This includes both\n  the changes initially released in Tornado 4.4.3 and an additional change\n  to support the ```session_expires`` field in the new format.\n  The ``session_expires`` field is currently a string; it should be accessed\n  as ``int(user['session_expires'])`` because it will change from a string to\n  an int in Tornado 5.0.\n\n\n`tornado.autoreload`\n~~~~~~~~~~~~~~~~~~~~\n\n- Autoreload is now compatible with the `asyncio` event loop.\n- Autoreload no longer attempts to close the `.IOLoop` and all registered\n  file descriptors before restarting; it relies on the ``CLOEXEC`` flag\n  being set instead.\n\n`tornado.concurrent`\n~~~~~~~~~~~~~~~~~~~~\n\n- Suppressed some \"'NoneType' object not callback\" messages that could\n  be logged at shutdown.\n\n`tornado.gen`\n~~~~~~~~~~~~~\n\n- ``yield None`` is now equivalent to ``yield gen.moment``.\n  `~tornado.gen.moment` is deprecated. This improves compatibility with\n  `asyncio`.\n- Fixed an issue in which a generator object could be garbage\n  collected prematurely (most often when weak references are used.\n- New function `.is_coroutine_function` identifies functions wrapped\n  by `.coroutine` or ``engine``.\n\n``tornado.http1connection``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- The ``Transfer-Encoding`` header is now parsed case-insensitively.\n\n`tornado.httpclient`\n~~~~~~~~~~~~~~~~~~~~\n\n- ``SimpleAsyncHTTPClient`` now follows 308 redirects.\n- ``CurlAsyncHTTPClient`` will no longer accept protocols other than\n  ``http`` and ``https``. To override this, set ``pycurl.PROTOCOLS``\n  and ``pycurl.REDIR_PROTOCOLS`` in a ``prepare_curl_callback``.\n- ``CurlAsyncHTTPClient`` now supports digest authentication for proxies\n  (in addition to basic auth) via the new ``proxy_auth_mode`` argument.\n- The minimum supported version of ``libcurl`` is now ``7.22.0``.\n\n`tornado.httpserver`\n~~~~~~~~~~~~~~~~~~~~\n\n- `.HTTPServer` now accepts the keyword argument\n  ``trusted_downstream`` which controls the parsing of\n  ``X-Forwarded-For`` headers. This header may be a list or set of IP\n  addresses of trusted proxies which will be skipped in the\n  ``X-Forwarded-For`` list.\n- The ``no_keep_alive`` argument works again.\n\n`tornado.httputil`\n~~~~~~~~~~~~~~~~~~\n\n- `.url_concat` correctly handles fragments and existing query arguments.\n\n`tornado.ioloop`\n~~~~~~~~~~~~~~~~\n\n- Fixed 100% CPU usage after a callback returns an empty list or dict.\n- `.IOLoop.add_callback` now uses a lockless implementation which\n  makes it safe for use from ``__del__`` methods. This improves\n  performance of calls to `~.IOLoop.add_callback` from the `.IOLoop`\n  thread, and slightly decreases it for calls from other threads.\n\n`tornado.iostream`\n~~~~~~~~~~~~~~~~~~\n\n- `memoryview` objects are now permitted as arguments to `~.BaseIOStream.write`.\n- The internal memory buffers used by `.IOStream` now use `bytearray`\n  instead of a list of `bytes`, improving performance.\n- Futures returned by `~.BaseIOStream.write` are no longer orphaned if a second\n  call to ``write`` occurs before the previous one is finished.\n\n`tornado.log`\n~~~~~~~~~~~~~\n\n- Colored log output is now supported on Windows if the\n  `colorama <https://pypi.python.org/pypi/colorama>`_ library\n  is installed  and the application calls ``colorama.init()`` at\n  startup.\n- The signature of the `.LogFormatter` constructor has been changed to\n  make it compatible with `logging.config.dictConfig`.\n\n`tornado.netutil`\n~~~~~~~~~~~~~~~~~\n\n- Worked around an issue that caused \"LookupError: unknown encoding:\n  latin1\" errors on Solaris.\n\n`tornado.process`\n~~~~~~~~~~~~~~~~~\n\n- `.Subprocess` no longer causes \"subprocess still running\" warnings on Python 3.6.\n- Improved error handling in `.cpu_count`.\n\n`tornado.tcpclient`\n~~~~~~~~~~~~~~~~~~~\n\n- `.TCPClient` now supports a ``source_ip`` and ``source_port`` argument.\n- Improved error handling for environments where IPv6 support is incomplete.\n\n`tornado.tcpserver`\n~~~~~~~~~~~~~~~~~~~\n\n- `.TCPServer.handle_stream` implementations may now be native coroutines.\n- Stopping a `.TCPServer` twice no longer raises an exception.\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n- `.RedirectHandler` now supports substituting parts of the matched\n  URL into the redirect location using `str.format` syntax.\n- New methods `.RequestHandler.render_linked_js`,\n  `.RequestHandler.render_embed_js`,\n  `.RequestHandler.render_linked_css`, and\n  `.RequestHandler.render_embed_css` can be overridden to customize\n  the output of `.UIModule`.\n\n\n`tornado.websocket`\n~~~~~~~~~~~~~~~~~~~\n\n- `.WebSocketHandler.on_message` implementations may now be\n  coroutines. New messages will not be processed until the previous\n  ``on_message`` coroutine has finished.\n- The ``websocket_ping_interval`` and ``websocket_ping_timeout``\n  application settings can now be used to enable a periodic ping of\n  the websocket connection, allowing dropped connections to be\n  detected and closed.\n- The new ``websocket_max_message_size`` setting defaults to 10MiB.\n  The connection will be closed if messages larger than this are received.\n- Headers set by `.RequestHandler.prepare` or\n  `.RequestHandler.set_default_headers` are now sent as a part of the\n  websocket handshake.\n- Return values from `.WebSocketHandler.get_compression_options` may now include\n  the keys ``compression_level`` and ``mem_level`` to set gzip parameters.\n  The default compression level is now 6 instead of 9.\n\nDemos\n~~~~~\n\n- A new file upload demo is available in the `file_upload\n  <https://github.com/tornadoweb/tornado/tree/stable/demos/file_upload>`_\n  directory.\n- A new `.TCPClient` and `.TCPServer` demo is available in the\n  `tcpecho <https://github.com/tornadoweb/tornado/tree/stable/demos/tcpecho>`_ directory.\n- Minor updates have been made to several existing demos, including\n  updates to more recent versions of jquery.\n\nCredits\n~~~~~~~\n\nThe following people contributed commits to this release:\n\n- A\\. Jesse Jiryu Davis\n- Aaron Opfer\n- Akihiro Yamazaki\n- Alexander\n- Andreas Røsdal\n- Andrew Rabert\n- Andrew Sumin\n- Antoine Pietri\n- Antoine Pitrou\n- Artur Stawiarski\n- Ben Darnell\n- Brian Mego\n- Dario\n- Doug Vargas\n- Eugene Dubovoy\n- Iver Jordal\n- JZQT\n- James Maier\n- Jeff Hunter\n- Leynos\n- Mark Henderson\n- Michael V. DePalatis\n- Min RK\n- Mircea Ulinic\n- Ping\n- Ping Yang\n- Riccardo Magliocchetti\n- Samuel Chen\n- Samuel Dion-Girardeau\n- Scott Meisburger\n- Shawn Ding\n- TaoBeier\n- Thomas Kluyver\n- Vadim Semenov\n- matee\n- mike820324\n- stiletto\n- zhimin\n- 依云\n"
  },
  {
    "path": "docs/releases/v4.5.1.rst",
    "content": "What's new in Tornado 4.5.1\n===========================\n\nApr 20, 2017\n------------\n\n`tornado.log`\n~~~~~~~~~~~~~\n\n- Improved detection of libraries for colorized logging.\n\n`tornado.httputil`\n~~~~~~~~~~~~~~~~~~\n\n- `.url_concat` once again treats None as equivalent to an empty sequence.\n"
  },
  {
    "path": "docs/releases/v4.5.2.rst",
    "content": "What's new in Tornado 4.5.2\n===========================\n\nAug 27, 2017\n------------\n\nBug Fixes\n~~~~~~~~~\n\n- Tornado now sets the ``FD_CLOEXEC`` flag on all file descriptors it creates. This prevents hanging client connections and resource leaks when the `tornado.autoreload` module (or ``Application(debug=True)``) is used.\n"
  },
  {
    "path": "docs/releases/v4.5.3.rst",
    "content": "What's new in Tornado 4.5.3\n===========================\n\nJan 6, 2018\n------------\n\n`tornado.curl_httpclient`\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- Improved debug logging on Python 3.\n\n`tornado.httpserver`\n~~~~~~~~~~~~~~~~~~~~\n\n- ``Content-Length`` and ``Transfer-Encoding`` headers are no longer\n  sent with 1xx or 204 responses (this was already true of 304\n  responses).\n- Reading chunked requests no longer leaves the connection in a broken\n  state.\n\n`tornado.iostream`\n~~~~~~~~~~~~~~~~~~\n\n- Writing a `memoryview` can no longer result in \"BufferError:\n  Existing exports of data: object cannot be re-sized\".\n\n`tornado.options`\n~~~~~~~~~~~~~~~~~\n\n- Duplicate option names are now detected properly whether they use\n  hyphens or underscores.\n\n`tornado.testing`\n~~~~~~~~~~~~~~~~~\n\n- `.AsyncHTTPTestCase.fetch` now uses ``127.0.0.1`` instead of\n  ``localhost``, improving compatibility with systems that have\n  partially-working ipv6 stacks.\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n- It is no longer allowed to send a body with 1xx or 204 responses.\n\n`tornado.websocket`\n~~~~~~~~~~~~~~~~~~~\n\n- Requests with invalid websocket headers now get a response with\n  status code 400 instead of a closed connection.\n"
  },
  {
    "path": "docs/releases/v5.0.0.rst",
    "content": "What's new in Tornado 5.0\n=========================\n\nMar 5, 2018\n-----------\n\nHighlights\n~~~~~~~~~~\n\n- The focus of this release is improving integration with `asyncio`.\n  On Python 3, the `.IOLoop` is always a wrapper around the `asyncio`\n  event loop, and `asyncio.Future` and `asyncio.Task` are used instead\n  of their Tornado counterparts. This means that libraries based on\n  `asyncio` can be mixed relatively seamlessly with those using\n  Tornado. While care has been taken to minimize the disruption from\n  this change, code changes may be required for compatibility with\n  Tornado 5.0, as detailed in the following section.\n- Tornado 5.0 supports Python 2.7.9+ and 3.4+. Python 2.7 and 3.4 are\n  deprecated and support for them will be removed in Tornado 6.0,\n  which will require Python 3.5+.\n\nBackwards-compatibility notes\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- Python 3.3 is no longer supported.\n- Versions of Python 2.7 that predate the `ssl` module update are no\n  longer supported. (The `ssl` module was updated in version 2.7.9,\n  although in some distributions the updates are present in builds\n  with a lower version number. Tornado requires `ssl.SSLContext`,\n  `ssl.create_default_context`, and ``ssl.match_hostname``)\n- Versions of Python 3.5 prior to 3.5.2 are no longer supported due to\n  a change in the async iterator protocol in that version.\n- The ``trollius`` project (`asyncio` backported to Python 2) is no\n  longer supported.\n- `tornado.concurrent.Future` is now an alias for `asyncio.Future`\n  when running on Python 3. This results in a number of minor\n  behavioral changes:\n\n    - `.Future` objects can only be created while there is a current\n      `.IOLoop`\n    - The timing of callbacks scheduled with\n      ``Future.add_done_callback`` has changed.\n      `tornado.concurrent.future_add_done_callback` can be used to\n      make the behavior more like older versions of Tornado (but not\n      identical). Some of these changes are also present in the Python\n      2 version of `tornado.concurrent.Future` to minimize the\n      difference between Python 2 and 3.\n    - Cancellation is now partially supported, via\n      `asyncio.Future.cancel`. A canceled `.Future` can no longer have\n      its result set. Applications that handle `~asyncio.Future`\n      objects directly may want to use\n      `tornado.concurrent.future_set_result_unless_cancelled`. In\n      native coroutines, cancellation will cause an exception to be\n      raised in the coroutine.\n    - The ``exc_info`` and ``set_exc_info`` methods are no longer\n      present. Use `tornado.concurrent.future_set_exc_info` to replace\n      the latter, and raise the exception with\n      `~asyncio.Future.result` to replace the former.\n- ``io_loop`` arguments to many Tornado functions have been removed.\n  Use `.IOLoop.current()` instead of passing `.IOLoop` objects\n  explicitly.\n- On Python 3, `.IOLoop` is always a wrapper around the `asyncio`\n  event loop. ``IOLoop.configure`` is effectively removed on Python 3\n  (for compatibility, it may be called to redundantly specify the\n  `asyncio`-backed `.IOLoop`)\n- `.IOLoop.instance` is now a deprecated alias for `.IOLoop.current`.\n  Applications that need the cross-thread communication behavior\n  facilitated by `.IOLoop.instance` should use their own global variable\n  instead.\n\n\nOther notes\n~~~~~~~~~~~\n\n- The ``futures`` (`concurrent.futures` backport) package is now required\n  on Python 2.7.\n- The ``certifi`` and ``backports.ssl-match-hostname`` packages are no\n  longer required on Python 2.7.\n- Python 3.6 or higher is recommended, because it features more\n  efficient garbage collection of `asyncio.Future` objects.\n\n`tornado.auth`\n~~~~~~~~~~~~~~\n\n- `.GoogleOAuth2Mixin` now uses a newer set of URLs.\n\n`tornado.autoreload`\n~~~~~~~~~~~~~~~~~~~~\n\n- On Python 3, uses ``__main__.__spec`` to more reliably reconstruct\n  the original command line and avoid modifying ``PYTHONPATH``.\n- The ``io_loop`` argument to `tornado.autoreload.start` has been removed.\n\n`tornado.concurrent`\n~~~~~~~~~~~~~~~~~~~~\n\n- `tornado.concurrent.Future` is now an alias for `asyncio.Future`\n  when running on Python 3. See \"Backwards-compatibility notes\" for\n  more.\n- Setting the result of a ``Future`` no longer blocks while callbacks\n  are being run. Instead, the callbacks are scheduled on the next\n  `.IOLoop` iteration.\n- The deprecated alias ``tornado.concurrent.TracebackFuture`` has been\n  removed.\n- `tornado.concurrent.chain_future` now works with all three kinds of\n  ``Futures`` (Tornado, `asyncio`, and `concurrent.futures`)\n- The ``io_loop`` argument to `tornado.concurrent.run_on_executor` has\n  been removed.\n- New functions `.future_set_result_unless_cancelled`,\n  `.future_set_exc_info`, and `.future_add_done_callback` help mask\n  the difference between `asyncio.Future` and Tornado's previous\n  ``Future`` implementation.\n\n`tornado.curl_httpclient`\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- Improved debug logging on Python 3.\n- The ``time_info`` response attribute now includes ``appconnect`` in\n  addition to other measurements.\n- Closing a `.CurlAsyncHTTPClient` now breaks circular references that\n  could delay garbage collection.\n- The ``io_loop`` argument to the `.CurlAsyncHTTPClient` constructor\n  has been removed.\n\n`tornado.gen`\n~~~~~~~~~~~~~\n\n- ``tornado.gen.TimeoutError`` is now an alias for\n  `tornado.util.TimeoutError`.\n- Leak detection for ``Futures`` created by this module now attributes\n  them to their proper caller instead of the coroutine machinery.\n- Several circular references that could delay garbage collection have\n  been broken up.\n- On Python 3, `asyncio.Task` is used instead of the Tornado coroutine\n  runner. This improves compatibility with some `asyncio` libraries\n  and adds support for cancellation.\n- The ``io_loop`` arguments to ``YieldFuture`` and `.with_timeout` have\n  been removed.\n\n`tornado.httpclient`\n~~~~~~~~~~~~~~~~~~~~\n\n- The ``io_loop`` argument to all `.AsyncHTTPClient` constructors has\n  been removed.\n\n`tornado.httpserver`\n~~~~~~~~~~~~~~~~~~~~\n\n- It is now possible for a client to reuse a connection after sending\n  a chunked request.\n- If a client sends a malformed request, the server now responds with\n  a 400 error instead of simply closing the connection.\n- ``Content-Length`` and ``Transfer-Encoding`` headers are no longer\n  sent with 1xx or 204 responses (this was already true of 304\n  responses).\n- When closing a connection to a HTTP/1.1 client, the ``Connection:\n  close`` header is sent with the response.\n- The ``io_loop`` argument to the `.HTTPServer` constructor has been\n  removed.\n- If more than one ``X-Scheme`` or ``X-Forwarded-Proto`` header is\n  present, only the last is used.\n\n`tornado.httputil`\n~~~~~~~~~~~~~~~~~~\n\n- The string representation of `.HTTPServerRequest` objects (which are\n  sometimes used in log messages) no longer includes the request\n  headers.\n- New function `.qs_to_qsl` converts the result of\n  `urllib.parse.parse_qs` to name-value pairs.\n\n`tornado.ioloop`\n~~~~~~~~~~~~~~~~\n\n- ``tornado.ioloop.TimeoutError`` is now an alias for\n  `tornado.util.TimeoutError`.\n- `.IOLoop.instance` is now a deprecated alias for `.IOLoop.current`.\n- `.IOLoop.install` and `.IOLoop.clear_instance` are deprecated.\n- The ``IOLoop.initialized`` method has been removed.\n- On Python 3, the `asyncio`-backed `.IOLoop` is always used and\n  alternative `.IOLoop` implementations cannot be configured.\n  `.IOLoop.current` and related methods pass through to\n  `asyncio.get_event_loop`.\n- `~.IOLoop.run_sync` cancels its argument on a timeout. This\n  results in better stack traces (and avoids log messages about leaks)\n  in native coroutines.\n- New methods `.IOLoop.run_in_executor` and\n  `.IOLoop.set_default_executor` make it easier to run functions in\n  other threads from native coroutines (since\n  `concurrent.futures.Future` does not support ``await``).\n- ``PollIOLoop`` (the default on Python 2) attempts to detect misuse\n  of `.IOLoop` instances across `os.fork`.\n- The ``io_loop`` argument to `.PeriodicCallback` has been removed.\n- It is now possible to create a `.PeriodicCallback` in one thread\n  and start it in another without passing an explicit event loop.\n- The ``IOLoop.set_blocking_signal_threshold`` and\n  ``IOLoop.set_blocking_log_threshold`` methods are deprecated because\n  they are not implemented for the `asyncio` event loop`. Use the\n  ``PYTHONASYNCIODEBUG=1`` environment variable instead.\n- `.IOLoop.clear_current` now works if it is called before any\n  current loop is established.\n\n`tornado.iostream`\n~~~~~~~~~~~~~~~~~~\n\n- The ``io_loop`` argument to the `.IOStream` constructor has been removed.\n- New method `.BaseIOStream.read_into` provides a minimal-copy alternative to\n  `.BaseIOStream.read_bytes`.\n- `.BaseIOStream.write` is now much more efficient for very large amounts of data.\n- Fixed some cases in which ``IOStream.error`` could be inaccurate.\n- Writing a `memoryview` can no longer result in \"BufferError:\n  Existing exports of data: object cannot be re-sized\".\n\n`tornado.locks`\n~~~~~~~~~~~~~~~\n\n- As a side effect of the ``Future`` changes, waiters are always\n  notified asynchronously with respect to `.Condition.notify`.\n\n`tornado.netutil`\n~~~~~~~~~~~~~~~~~\n\n- The default `.Resolver` now uses `.IOLoop.run_in_executor`.\n  `.ExecutorResolver`, `.BlockingResolver`, and `.ThreadedResolver` are\n  deprecated.\n- The ``io_loop`` arguments to `.add_accept_handler`,\n  `.ExecutorResolver`, and `.ThreadedResolver` have been removed.\n- `.add_accept_handler` returns a callable which can be used to remove\n  all handlers that were added.\n- `.OverrideResolver` now accepts per-family overrides.\n\n`tornado.options`\n~~~~~~~~~~~~~~~~~\n\n- Duplicate option names are now detected properly whether they use\n  hyphens or underscores.\n\n`tornado.platform.asyncio`\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- `.AsyncIOLoop` and `.AsyncIOMainLoop` are now used automatically\n  when appropriate; referencing them explicitly is no longer\n  recommended.\n- Starting an `.IOLoop` or making it current now also sets the\n  `asyncio` event loop for the current thread. Closing an `.IOLoop`\n  closes the corresponding `asyncio` event loop.\n- `.to_tornado_future` and `.to_asyncio_future` are deprecated since\n  they are now no-ops.\n- `~.AnyThreadEventLoopPolicy` can now be used to easily allow the creation\n  of event loops on any thread (similar to Tornado's prior policy).\n\n`tornado.platform.caresresolver`\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- The ``io_loop`` argument to `.CaresResolver` has been removed.\n\n`tornado.platform.twisted`\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- The ``io_loop`` arguments to ``TornadoReactor``, ``TwistedResolver``,\n  and ``tornado.platform.twisted.install`` have been removed.\n\n`tornado.process`\n~~~~~~~~~~~~~~~~~\n\n- The ``io_loop`` argument to the `.Subprocess` constructor and\n  `.Subprocess.initialize` has been removed.\n\n`tornado.routing`\n~~~~~~~~~~~~~~~~~\n\n- A default 404 response is now generated if no delegate is found for\n  a request.\n\n`tornado.simple_httpclient`\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- The ``io_loop`` argument to `.SimpleAsyncHTTPClient` has been removed.\n- TLS is now configured according to `ssl.create_default_context` by\n  default.\n\n`tornado.tcpclient`\n~~~~~~~~~~~~~~~~~~~\n\n- The ``io_loop`` argument to the `.TCPClient` constructor has been\n  removed.\n- `.TCPClient.connect` has a new ``timeout`` argument.\n\n`tornado.tcpserver`\n~~~~~~~~~~~~~~~~~~~\n\n- The ``io_loop`` argument to the `.TCPServer` constructor has been\n  removed.\n- `.TCPServer` no longer logs ``EBADF`` errors during shutdown.\n\n`tornado.testing`\n~~~~~~~~~~~~~~~~~\n\n- The deprecated ``tornado.testing.get_unused_port`` and\n  ``tornado.testing.LogTrapTestCase`` have been removed.\n- `.AsyncHTTPTestCase.fetch` now supports absolute URLs.\n- `.AsyncHTTPTestCase.fetch` now connects to ``127.0.0.1``\n  instead of ``localhost`` to be more robust against faulty\n  ipv6 configurations.\n\n`tornado.util`\n~~~~~~~~~~~~~~\n\n- `tornado.util.TimeoutError` replaces ``tornado.gen.TimeoutError``\n  and ``tornado.ioloop.TimeoutError``.\n- `.Configurable` now supports configuration at multiple levels of an\n  inheritance hierarchy.\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n- `.RequestHandler.set_status` no longer requires that the given\n  status code appear in `http.client.responses`.\n- It is no longer allowed to send a body with 1xx or 204 responses.\n- Exception handling now breaks up reference cycles that could delay\n  garbage collection.\n- `.RedirectHandler` now copies any query arguments from the request\n  to the redirect location.\n- If both ``If-None-Match`` and ``If-Modified-Since`` headers are present\n  in a request to `.StaticFileHandler`, the latter is now ignored.\n\n`tornado.websocket`\n~~~~~~~~~~~~~~~~~~~\n\n- The C accelerator now operates on multiple bytes at a time to\n  improve performance.\n- Requests with invalid websocket headers now get a response with\n  status code 400 instead of a closed connection.\n- `.WebSocketHandler.write_message` now raises `.WebSocketClosedError` if\n  the connection closes while the write is in progress.\n- The ``io_loop`` argument to `.websocket_connect` has been removed.\n"
  },
  {
    "path": "docs/releases/v5.0.1.rst",
    "content": "What's new in Tornado 5.0.1\n===========================\n\nMar 18, 2018\n------------\n\nBug fix\n~~~~~~~\n\n- This release restores support for versions of Python 3.4 prior to\n  3.4.4. This is important for compatibility with Debian Jessie which\n  has 3.4.2 as its version of Python 3.\n"
  },
  {
    "path": "docs/releases/v5.0.2.rst",
    "content": "What's new in Tornado 5.0.2\n===========================\n\nApr 7, 2018\n-----------\n\nBug fixes\n~~~~~~~~~\n\n- Fixed a memory leak when `.IOLoop` objects are created and destroyed.\n- If `.AsyncTestCase.get_new_ioloop` returns a reference to a\n  preexisting event loop (typically when it has been overridden to\n  return `.IOLoop.current()`), the test's ``tearDown`` method will not\n  close this loop.\n- Fixed a confusing error message when the synchronous `.HTTPClient`\n  fails to initialize because an event loop is already running.\n- `.PeriodicCallback` no longer executes twice in a row due to\n  backwards clock adjustments.\n"
  },
  {
    "path": "docs/releases/v5.1.0.rst",
    "content": "What's new in Tornado 5.1\n=========================\n\nJuly 12, 2018\n-------------\n\nDeprecation notice\n~~~~~~~~~~~~~~~~~~\n\n- Tornado 6.0 will drop support for Python 2.7 and 3.4. The minimum\n  supported Python version will be 3.5.2.\n- The ``tornado.stack_context`` module is deprecated and will be removed\n  in Tornado 6.0. The reason for this is that it is not feasible to\n  provide this module's semantics in the presence of ``async def``\n  native coroutines. ``ExceptionStackContext`` is mainly obsolete\n  thanks to coroutines. ``StackContext`` lacks a direct replacement\n  although the new ``contextvars`` package (in the Python standard\n  library beginning in Python 3.7) may be an alternative.\n- Callback-oriented code often relies on ``ExceptionStackContext`` to\n  handle errors and prevent leaked connections. In order to avoid the\n  risk of silently introducing subtle leaks (and to consolidate all of\n  Tornado's interfaces behind the coroutine pattern), ``callback``\n  arguments throughout the package are deprecated and will be removed\n  in version 6.0. All functions that had a ``callback`` argument\n  removed now return a `.Future` which should be used instead.\n- Where possible, deprecation warnings are emitted when any of these\n  deprecated interfaces is used. However, Python does not display\n  deprecation warnings by default. To prepare your application for\n  Tornado 6.0, run Python with the ``-Wd`` argument or set the\n  environment variable ``PYTHONWARNINGS`` to ``d``. If your\n  application runs on Python 3 without deprecation warnings, it should\n  be able to move to Tornado 6.0 without disruption.\n\n`tornado.auth`\n~~~~~~~~~~~~~~\n\n- `.OAuthMixin._oauth_get_user_future` may now be a native coroutine.\n- All ``callback`` arguments in this package are deprecated and will\n  be removed in 6.0. Use the coroutine interfaces instead.\n- The ``OAuthMixin._oauth_get_user`` method is deprecated and will be removed in\n  6.0. Override `~.OAuthMixin._oauth_get_user_future` instead.\n\n`tornado.autoreload`\n~~~~~~~~~~~~~~~~~~~~\n\n- The command-line autoreload wrapper is now preserved if an internal\n  autoreload fires.\n- The command-line wrapper no longer starts duplicated processes on windows\n  when combined with internal autoreload.\n\n`tornado.concurrent`\n~~~~~~~~~~~~~~~~~~~~\n\n- `.run_on_executor` now returns `.Future` objects that are compatible\n  with ``await``.\n- The ``callback`` argument to `.run_on_executor` is deprecated and will\n  be removed in 6.0.\n- ``return_future`` is deprecated and will be removed in 6.0.\n\n`tornado.gen`\n~~~~~~~~~~~~~\n\n- Some older portions of this module are deprecated and will be removed\n  in 6.0. This includes ``engine``, ``YieldPoint``, ``Callback``,\n  ``Wait``, ``WaitAll``, ``MultiYieldPoint``, and ``Task``.\n- Functions decorated with ``@gen.coroutine`` will no longer accept\n  ``callback`` arguments in 6.0.\n\n`tornado.httpclient`\n~~~~~~~~~~~~~~~~~~~~\n\n- The behavior of ``raise_error=False`` is changing in 6.0. Currently\n  it suppresses all errors; in 6.0 it will only suppress the errors\n  raised due to completed responses with non-200 status codes.\n- The ``callback`` argument to `.AsyncHTTPClient.fetch` is deprecated\n  and will be removed in 6.0.\n- `tornado.httpclient.HTTPError` has been renamed to\n  `.HTTPClientError` to avoid ambiguity in code that also has to deal\n  with `tornado.web.HTTPError`. The old name remains as an alias.\n- ``tornado.curl_httpclient`` now supports non-ASCII characters in\n  username and password arguments.\n- ``.HTTPResponse.request_time`` now behaves consistently across\n  ``simple_httpclient`` and ``curl_httpclient``, excluding time spent\n  in the ``max_clients`` queue in both cases (previously this time was\n  included in ``simple_httpclient`` but excluded in\n  ``curl_httpclient``). In both cases the time is now computed using\n  a monotonic clock where available.\n- `.HTTPResponse` now has a ``start_time`` attribute recording a\n  wall-clock (`time.time`) timestamp at which the request started\n  (after leaving the ``max_clients`` queue if applicable).\n\n`tornado.httputil`\n~~~~~~~~~~~~~~~~~~\n\n- `.parse_multipart_form_data` now recognizes non-ASCII filenames in\n  RFC 2231/5987 (``filename*=``) format.\n- ``HTTPServerRequest.write`` is deprecated and will be removed in 6.0. Use\n  the methods of ``request.connection`` instead.\n- Malformed HTTP headers are now logged less noisily.\n\n`tornado.ioloop`\n~~~~~~~~~~~~~~~~\n\n- `.PeriodicCallback` now supports a ``jitter`` argument to randomly\n  vary the timeout.\n- ``IOLoop.set_blocking_signal_threshold``,\n  ``IOLoop.set_blocking_log_threshold``, ``IOLoop.log_stack``,\n  and ``IOLoop.handle_callback_exception`` are deprecated and will\n  be removed in 6.0.\n- Fixed a `KeyError` in `.IOLoop.close` when `.IOLoop` objects are\n  being opened and closed in multiple threads.\n\n`tornado.iostream`\n~~~~~~~~~~~~~~~~~~\n\n- All ``callback`` arguments in this module are deprecated except for\n  `.BaseIOStream.set_close_callback`. They will be removed in 6.0.\n- ``streaming_callback`` arguments to `.BaseIOStream.read_bytes` and\n  `.BaseIOStream.read_until_close` are deprecated and will be removed\n  in 6.0.\n\n`tornado.netutil`\n~~~~~~~~~~~~~~~~~\n\n- Improved compatibility with GNU Hurd.\n\n`tornado.options`\n~~~~~~~~~~~~~~~~~\n\n- `tornado.options.parse_config_file` now allows setting options to\n  strings (which will be parsed the same way as\n  `tornado.options.parse_command_line`) in addition to the specified\n  type for the option.\n\n`tornado.platform.twisted`\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- ``TornadoReactor`` and ``TwistedIOLoop`` are deprecated and will be\n  removed in 6.0. Instead, Tornado will always use the asyncio event loop\n  and twisted can be configured to do so as well.\n\n``tornado.stack_context``\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- The ``tornado.stack_context`` module is deprecated and will be removed\n  in 6.0.\n\n`tornado.testing`\n~~~~~~~~~~~~~~~~~\n\n- `.AsyncHTTPTestCase.fetch` now takes a ``raise_error`` argument.\n  This argument has the same semantics as `.AsyncHTTPClient.fetch`,\n  but defaults to false because tests often need to deal with non-200\n  responses (and for backwards-compatibility).\n- The `.AsyncTestCase.stop` and `.AsyncTestCase.wait` methods are\n  deprecated.\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n- New method `.RequestHandler.detach` can be used from methods\n  that are not decorated with ``@asynchronous`` (the decorator\n  was required to use ``self.request.connection.detach()``.\n- `.RequestHandler.finish` and `.RequestHandler.render` now return\n  ``Futures`` that can be used to wait for the last part of the\n  response to be sent to the client.\n- `.FallbackHandler` now calls ``on_finish`` for the benefit of\n  subclasses that may have overridden it.\n- The ``asynchronous`` decorator is deprecated and will be removed in 6.0.\n- The ``callback`` argument to `.RequestHandler.flush` is deprecated\n  and will be removed in 6.0.\n\n\n`tornado.websocket`\n~~~~~~~~~~~~~~~~~~~\n\n- When compression is enabled, memory limits now apply to the\n  post-decompression size of the data, protecting against DoS attacks.\n- `.websocket_connect` now supports subprotocols.\n- `.WebSocketHandler` and `.WebSocketClientConnection` now have\n  ``selected_subprotocol`` attributes to see the subprotocol in use.\n- The `.WebSocketHandler.select_subprotocol` method is now called with\n  an empty list instead of a list containing an empty string if no\n  subprotocols were requested by the client.\n- `.WebSocketHandler.open` may now be a coroutine.\n- The ``data`` argument to `.WebSocketHandler.ping` is now optional.\n- Client-side websocket connections no longer buffer more than one\n  message in memory at a time.\n- Exception logging now uses `.RequestHandler.log_exception`.\n\n`tornado.wsgi`\n~~~~~~~~~~~~~~\n\n- ``WSGIApplication`` and ``WSGIAdapter`` are deprecated and will be removed\n  in Tornado 6.0.\n"
  },
  {
    "path": "docs/releases/v5.1.1.rst",
    "content": "What's new in Tornado 5.1.1\n===========================\n\nSep 16, 2018\n------------\n\nBug fixes\n~~~~~~~~~\n\n- Fixed an case in which the `.Future` returned by\n  `.RequestHandler.finish` could fail to resolve.\n- The `.TwitterMixin.authenticate_redirect` method works again.\n- Improved error handling in the `tornado.auth` module, fixing hanging\n  requests when a network or other error occurs.\n"
  },
  {
    "path": "docs/releases/v6.0.0.rst",
    "content": "What's new in Tornado 6.0\n=========================\n\nMar 1, 2019\n-----------\n\nBackwards-incompatible changes\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- Python 2.7 and 3.4 are no longer supported; the minimum supported\n  Python version is 3.5.2.\n- APIs deprecated in Tornado 5.1 have been removed. This includes the\n  ``tornado.stack_context`` module and most ``callback`` arguments\n  throughout the package. All removed APIs emitted\n  `DeprecationWarning` when used in Tornado 5.1, so running your\n  application with the ``-Wd`` Python command-line flag or the\n  environment variable ``PYTHONWARNINGS=d`` should tell you whether\n  your application is ready to move to Tornado 6.0.\n- ``.WebSocketHandler.get`` is now a coroutine and must be called\n  accordingly in any subclasses that override this method (but note\n  that overriding ``get`` is not recommended; either ``prepare`` or\n  ``open`` should be used instead).\n\nGeneral changes\n~~~~~~~~~~~~~~~\n\n- Tornado now includes type annotations compatible with ``mypy``.\n  These annotations will be used when type-checking your application\n  with ``mypy``, and may be usable in editors and other tools.\n- Tornado now uses native coroutines internally, improving performance.\n\n`tornado.auth`\n~~~~~~~~~~~~~~\n\n- All ``callback`` arguments in this package have been removed. Use\n  the coroutine interfaces instead.\n- The ``OAuthMixin._oauth_get_user`` method has been removed.\n  Override `~.OAuthMixin._oauth_get_user_future` instead.\n\n`tornado.concurrent`\n~~~~~~~~~~~~~~~~~~~~\n\n- The ``callback`` argument to `.run_on_executor` has been removed.\n- ``return_future`` has been removed.\n\n`tornado.gen`\n~~~~~~~~~~~~~\n\n- Some older portions of this module have been removed. This includes\n  ``engine``, ``YieldPoint``, ``Callback``, ``Wait``, ``WaitAll``,\n  ``MultiYieldPoint``, and ``Task``.\n- Functions decorated with ``@gen.coroutine`` no longer accept\n  ``callback`` arguments.\n\n`tornado.httpclient`\n~~~~~~~~~~~~~~~~~~~~\n\n- The behavior of ``raise_error=False`` has changed. Now only\n  suppresses the errors raised due to completed responses with non-200\n  status codes (previously it suppressed all errors).\n- The ``callback`` argument to `.AsyncHTTPClient.fetch` has been removed.\n\n`tornado.httputil`\n~~~~~~~~~~~~~~~~~~\n\n- ``HTTPServerRequest.write`` has been removed. Use the methods of\n  ``request.connection`` instead.\n- Unrecognized ``Content-Encoding`` values now log warnings only for\n  content types that we would otherwise attempt to parse.\n\n`tornado.ioloop`\n~~~~~~~~~~~~~~~~\n\n- ``IOLoop.set_blocking_signal_threshold``,\n  ``IOLoop.set_blocking_log_threshold``, ``IOLoop.log_stack``,\n  and ``IOLoop.handle_callback_exception`` have been removed.\n- Improved performance of `.IOLoop.add_callback`.\n\n`tornado.iostream`\n~~~~~~~~~~~~~~~~~~\n\n- All ``callback`` arguments in this module have been removed except\n  for `.BaseIOStream.set_close_callback`.\n- ``streaming_callback`` arguments to `.BaseIOStream.read_bytes` and\n  `.BaseIOStream.read_until_close` have been removed.\n- Eliminated unnecessary logging of \"Errno 0\".\n\n`tornado.log`\n~~~~~~~~~~~~~\n\n- Log files opened by this module are now explicitly set to UTF-8 encoding.\n\n`tornado.netutil`\n~~~~~~~~~~~~~~~~~\n\n- The results of ``getaddrinfo`` are now sorted by address family to\n  avoid partial failures and deadlocks.\n\n`tornado.platform.twisted`\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- ``TornadoReactor`` and ``TwistedIOLoop`` have been removed.\n\n``tornado.simple_httpclient``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- The default HTTP client now supports the ``network_interface``\n  request argument to specify the source IP for the connection.\n- If a server returns a 3xx response code without a ``Location``\n  header, the response is raised or returned directly instead of\n  trying and failing to follow the redirect.\n- When following redirects, methods other than ``POST`` will no longer\n  be transformed into ``GET`` requests. 301 (permanent) redirects are\n  now treated the same way as 302 (temporary) and 303 (see other)\n  redirects in this respect.\n- Following redirects now works with ``body_producer``.\n\n``tornado.stack_context``\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- The ``tornado.stack_context`` module has been removed.\n\n`tornado.tcpserver`\n~~~~~~~~~~~~~~~~~~~\n\n- `.TCPServer.start` now supports a ``max_restarts`` argument (same as\n  `.fork_processes`).\n\n`tornado.testing`\n~~~~~~~~~~~~~~~~~\n\n- `.AsyncHTTPTestCase` now drops all references to the `.Application`\n  during ``tearDown``, allowing its memory to be reclaimed sooner.\n- `.AsyncTestCase` now cancels all pending coroutines in ``tearDown``,\n  in an effort to reduce warnings from the python runtime about\n  coroutines that were not awaited. Note that this may cause\n  ``asyncio.CancelledError`` to be logged in other places. Coroutines\n  that expect to be running at test shutdown may need to catch this\n  exception.\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n- The ``asynchronous`` decorator has been removed.\n- The ``callback`` argument to `.RequestHandler.flush` has been removed.\n- `.StaticFileHandler` now supports large negative values for the\n  ``Range`` header and returns an appropriate error for ``end >\n  start``.\n- It is now possible to set ``expires_days`` in ``xsrf_cookie_kwargs``.\n\n`tornado.websocket`\n~~~~~~~~~~~~~~~~~~~\n\n- Pings and other messages sent while the connection is closing are\n  now silently dropped instead of logging exceptions.\n- Errors raised by ``open()`` are now caught correctly when this method\n  is a coroutine.\n\n`tornado.wsgi`\n~~~~~~~~~~~~~~\n\n- ``WSGIApplication`` and ``WSGIAdapter`` have been removed.\n"
  },
  {
    "path": "docs/releases/v6.0.1.rst",
    "content": "What's new in Tornado 6.0.1\n===========================\n\nMar 3, 2019\n-----------\n\nBug fixes\n~~~~~~~~~\n\n- Fixed issues with type annotations that caused errors while\n  importing Tornado on Python 3.5.2.\n"
  },
  {
    "path": "docs/releases/v6.0.2.rst",
    "content": "What's new in Tornado 6.0.2\n===========================\n\nMar 23, 2019\n------------\n\nBug fixes\n~~~~~~~~~\n\n- `.WebSocketHandler.set_nodelay` works again.\n- Accessing ``HTTPResponse.body`` now returns an empty byte string\n  instead of raising ``ValueError`` for error responses that don't\n  have a body (it returned None in this case in Tornado 5).\n"
  },
  {
    "path": "docs/releases/v6.0.3.rst",
    "content": "What's new in Tornado 6.0.3\n===========================\n\nJun 22, 2019\n------------\n\nBug fixes\n~~~~~~~~~\n\n- `.gen.with_timeout` always treats ``asyncio.CancelledError`` as a\n  ``quiet_exception`` (this improves compatibility with Python 3.8,\n  which changed ``CancelledError`` to a ``BaseException``).\n- ``IOStream`` now checks for closed streams earlier, avoiding\n  spurious logged errors in some situations (mainly with websockets).\n"
  },
  {
    "path": "docs/releases/v6.0.4.rst",
    "content": "What's new in Tornado 6.0.4\n===========================\n\nMar 3, 2020\n-----------\n\nGeneral changes\n~~~~~~~~~~~~~~~\n\n- Binary wheels are now available for Python 3.8 on Windows. Note that it is\n  still necessary to use\n  ``asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())`` for\n  this platform/version.\n\nBug fixes\n~~~~~~~~~\n\n- Fixed an issue in `.IOStream` (introduced in 6.0.0) that resulted in\n  ``StreamClosedError`` being incorrectly raised if a stream is closed mid-read\n  but there is enough buffered data to satisfy the read. \n- `.AnyThreadEventLoopPolicy` now always uses the selector event loop on Windows."
  },
  {
    "path": "docs/releases/v6.1.0.rst",
    "content": "What's new in Tornado 6.1.0\n===========================\n\nOct 30, 2020\n------------\n\nDeprecation notice\n~~~~~~~~~~~~~~~~~~\n\n- This is the last release of Tornado to support Python 3.5. Future versions\n  will require Python 3.6 or newer.\n\nGeneral changes\n~~~~~~~~~~~~~~~\n\n- Windows support has been improved. Tornado is now compatible with the proactor\n  event loop (which became the default in Python 3.8) by automatically falling\n  back to running a selector in a second thread. This means that it is no longer\n  necessary to explicitly configure a selector event loop, although doing so may\n  improve performance. This does not change the fact that Tornado is significantly\n  less scalable on Windows than on other platforms. \n- Binary wheels are now provided for Windows, MacOS, and Linux (amd64 and arm64).\n\n`tornado.gen`\n~~~~~~~~~~~~~\n\n- `.coroutine` now has better support for the Python 3.7+ ``contextvars`` module. \n  In particular, the ``ContextVar.reset`` method is now supported.\n\n`tornado.http1connection`\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- ``HEAD`` requests to handlers that used chunked encoding no longer produce malformed output. \n- Certain kinds of malformed ``gzip`` data no longer cause an infinite loop.\n\n`tornado.httpclient`\n~~~~~~~~~~~~~~~~~~~~\n\n- Setting ``decompress_response=False`` now works correctly with\n  ``curl_httpclient``. \n- Mixing requests with and without proxies works correctly in ``curl_httpclient``\n  (assuming the version of pycurl is recent enough).\n- A default ``User-Agent`` of ``Tornado/$VERSION`` is now used if the\n  ``user_agent`` parameter is not specified. \n- After a 303 redirect, ``tornado.simple_httpclient`` always uses ``GET``.\n  Previously this would use ``GET`` if the original request was a ``POST`` and\n  would otherwise reuse the original request method. For ``curl_httpclient``, the\n  behavior depends on the version of ``libcurl`` (with the most recent versions\n  using ``GET`` after 303 regardless of the original method).\n- Setting ``request_timeout`` and/or ``connect_timeout`` to zero is now supported\n  to disable the timeout.\n\n`tornado.httputil`\n~~~~~~~~~~~~~~~~~~\n\n- Header parsing is now faster.\n- `.parse_body_arguments` now accepts incompletely-escaped non-ASCII inputs.\n\n`tornado.iostream`\n~~~~~~~~~~~~~~~~~~\n\n- `ssl.CertificateError` during the SSL handshake is now handled correctly.\n- Reads that are resolved while the stream is closing are now handled correctly.\n\n`tornado.log`\n~~~~~~~~~~~~~\n\n- When colored logging is enabled, ``logging.CRITICAL`` messages are now\n  recognized and colored magenta.\n\n`tornado.netutil`\n~~~~~~~~~~~~~~~~~\n\n- ``EADDRNOTAVAIL`` is now ignored when binding to ``localhost`` with IPv6. This\n  error is common in docker.\n\n`tornado.platform.asyncio`\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- `.AnyThreadEventLoopPolicy` now also configures a selector event loop for\n  these threads (the proactor event loop only works on the main thread)\n\n``tornado.platform.auto``\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- The ``set_close_exec`` function has been removed.\n\n`tornado.testing`\n~~~~~~~~~~~~~~~~~\n\n- `.ExpectLog` now has a ``level`` argument to ensure that the given log level\n  is enabled.\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n- ``RedirectHandler.get`` now accepts keyword arguments.\n- When sending 304 responses, more headers (including ``Allow``) are now preserved.\n- ``reverse_url`` correctly handles escaped characters in the regex route. \n- Default ``Etag`` headers are now generated with SHA-512 instead of MD5.\n\n`tornado.websocket`\n~~~~~~~~~~~~~~~~~~~\n\n- The ``ping_interval`` timer is now stopped when the connection is closed.\n- `.websocket_connect` now raises an error when it encounters a redirect instead of hanging.\n"
  },
  {
    "path": "docs/releases/v6.2.0.rst",
    "content": "What's new in Tornado 6.2.0\n===========================\n\nJul 3, 2022\n-----------\n\nDeprecation notice\n~~~~~~~~~~~~~~~~~~\n\n- April 2023 update: Python 3.12 reversed some of the changes described below.\n  In Tornado 6.3, `.AsyncTestCase`, `.AsyncHTTPTestCase`, and the behavior\n  of the `.IOLoop` constructor related to the ``make_current`` parameter\n  are no longer deprecated. \n- Python 3.10 has begun the process of significant changes to the APIs for\n  managing the event loop. Calls to methods such as `asyncio.get_event_loop` may\n  now raise `DeprecationWarning` if no event loop is running. This has\n  significant impact on the patterns for initializing applications, and in\n  particular invalidates patterns that have long been the norm in Tornado's\n  documentation and actual usage. In the future (with some as-yet-unspecified\n  future version of Python), the old APIs will be removed. The new recommended\n  pattern is to start the event loop with `asyncio.run`. More detailed migration\n  guides will be coming in the future.\n\n  - The `.IOLoop` constructor is deprecated unless the ``make_current=False``\n    argument is used. Use `.IOLoop.current` when the loop is already running\n    instead.\n  - `.AsyncTestCase` (and `.AsyncHTTPTestCase`) are deprecated. Use\n    `unittest.IsolatedAsyncioTestCase` instead.\n  - Multi-process `.TCPServer.bind`/`.TCPServer.start` is deprecated. See\n    `.TCPServer` docs for supported alternatives.\n  - `.AnyThreadEventLoopPolicy` is deprecated. This class controls the creation of\n    the \"current\" event loop so it will be removed when that concept is no longer\n    supported.\n  - `.IOLoop.make_current` and `.IOLoop.clear_current` are deprecated. In the\n    future the concept of a \"current\" event loop as distinct from one that is\n    currently running will be removed.\n\n- ``TwistedResolver`` and ``CaresResolver`` are deprecated and will be\n  removed in Tornado 7.0.\n\nGeneral changes\n~~~~~~~~~~~~~~~\n\n- The minimum supported Python version is now 3.7.\n- Wheels are now published with the Python stable ABI (``abi3``) for\n  compatibility across versions of Python.\n- SSL certificate verification and hostname checks are now enabled by default in\n  more places (primarily in client-side usage of `.SSLIOStream`).\n- Various improvements to type hints throughout the package.\n- CI has moved from Travis and Appveyor to Github Actions.  \n\n`tornado.gen`\n~~~~~~~~~~~~~\n\n- Fixed a bug in which ``WaitIterator.current_index`` could be incorrect.\n- ``tornado.gen.TimeoutError`` is now an alias for `asyncio.TimeoutError`.\n\n`tornado.http1connection`\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- ``max_body_size`` may now be set to zero to disallow a non-empty body.\n- ``Content-Encoding: gzip`` is now recognized case-insensitively.\n\n`tornado.httpclient`\n~~~~~~~~~~~~~~~~~~~~\n\n- ``curl_httpclient`` now supports non-ASCII (ISO-8859-1) header values, same as\n  ``simple_httpclient``.\n\n`tornado.ioloop`\n~~~~~~~~~~~~~~~~\n\n- `.PeriodicCallback` now understands coroutines and will not start multiple\n  copies if a previous invocation runs too long.\n- `.PeriodicCallback` now accepts `datetime.timedelta` objects in addition to\n  numbers of milliseconds.\n- Avoid logging \"Event loop is closed\" during shutdown-related race conditions.\n- Tornado no longer calls `logging.basicConfig` when starting an IOLoop; this\n  has been unnecessary since Python 3.2 added a logger of last resort.\n- The `.IOLoop` constructor now accepts an ``asyncio_loop`` keyword argument to\n  initialize with a specfied asyncio event loop.\n- It is now possible to construct an `.IOLoop` on one thread (with\n  ``make_current=False``) and start it on a different thread.\n\n`tornado.iostream`\n~~~~~~~~~~~~~~~~~~\n\n- `.SSLIOStream` now supports reading more than 2GB at a time.\n- ``IOStream.write`` now supports typed `memoryview` objects.\n\n`tornado.locale`\n~~~~~~~~~~~~~~~~\n\n- `.load_gettext_translations` no longer logs errors when language directories\n  exist but do not contain the expected file.\n\n`tornado.netutil`\n~~~~~~~~~~~~~~~~~\n\n- `.is_valid_ip` no longer raises exceptions when the input is too long.\n- The default resolver now uses the same methods (and thread pool) as `asyncio`.\n\n`tornado.tcpserver`\n~~~~~~~~~~~~~~~~~~~\n\n- `.TCPServer.listen` now supports more arguments to pass through to\n  `.netutil.bind_sockets`.\n\n`tornado.testing`\n~~~~~~~~~~~~~~~~~\n\n- `.bind_unused_port` now takes an optional ``address`` argument.\n- Wrapped test methods now include the ``__wrapped__`` attribute.\n  \n`tornado.web`\n~~~~~~~~~~~~~\n\n- When using a custom `.StaticFileHandler` subclass, the ``reset()`` method is\n  now called on this subclass instead of the base class.\n- Improved handling of the ``Accept-Language`` header.\n- `.Application.listen` now supports more arguments to pass through to\n  `.netutil.bind_sockets`.\n\n`tornado.websocket`\n~~~~~~~~~~~~~~~~~~~\n\n- `.WebSocketClientConnection.write_message` now accepts `dict` arguments for\n  consistency with `.WebSocketHandler.write_message`.\n- `.WebSocketClientConnection.write_message` now raises an exception as\n  documented if the connection is already closed.\n"
  },
  {
    "path": "docs/releases/v6.3.0.rst",
    "content": "What's new in Tornado 6.3.0\n===========================\n\nApr 17, 2023\n------------\n\nHighlights\n~~~~~~~~~~\n\n- The new `.Application` setting ``xsrf_cookie_name`` can now be used to\n  take advantage of the ``__Host`` cookie prefix for improved security.\n  To use it, add ``{\"xsrf_cookie_name\": \"__Host-xsrf\", \"xsrf_cookie_kwargs\": \n  {\"secure\": True}}`` to your `.Application` settings. Note that this feature\n  currently only works when HTTPS is used.\n- `.WSGIContainer` now supports running the application in a ``ThreadPoolExecutor`` so\n  the event loop is no longer blocked.\n- `.AsyncTestCase` and `.AsyncHTTPTestCase`, which were deprecated in Tornado 6.2,\n  are no longer deprecated.\n- WebSockets are now much faster at receiving large messages split into many\n  fragments.\n\nGeneral changes\n~~~~~~~~~~~~~~~\n\n- Python 3.7 is no longer supported; the minimum supported Python version is 3.8.\n  Python 3.12 is now supported.\n- To avoid spurious deprecation warnings, users of Python 3.10 should upgrade\n  to at least version 3.10.9, and users of Python 3.11 should upgrade to at least\n  version 3.11.1. \n- Tornado submodules are now imported automatically on demand. This means it is\n  now possible to use a single ``import tornado`` statement and refer to objects\n  in submodules such as `tornado.web.RequestHandler`.\n\nDeprecation notices\n~~~~~~~~~~~~~~~~~~~\n\n- In Tornado 7.0, `tornado.testing.ExpectLog` will match ``WARNING``\n  and above regardless of the current logging configuration, unless the\n  ``level`` argument is used.\n- `.RequestHandler.get_secure_cookie` is now a deprecated alias for\n  `.RequestHandler.get_signed_cookie`. `.RequestHandler.set_secure_cookie`\n  is now a deprecated alias for `.RequestHandler.set_signed_cookie`.\n- `.RequestHandler.clear_all_cookies` is deprecated. No direct replacement\n  is provided; `.RequestHandler.clear_cookie` should be used on individual\n  cookies.\n- Calling the `.IOLoop` constructor without a ``make_current`` argument, which was\n  deprecated in Tornado 6.2, is no longer deprecated.\n- `.AsyncTestCase` and `.AsyncHTTPTestCase`, which were deprecated in Tornado 6.2,\n  are no longer deprecated.\n- `.AsyncTestCase.get_new_ioloop` is deprecated. \n\n``tornado.auth``\n~~~~~~~~~~~~~~~~\n\n- New method `.GoogleOAuth2Mixin.get_google_oauth_settings` can now be overridden\n  to get credentials from a source other than the `.Application` settings.\n\n``tornado.gen``\n~~~~~~~~~~~~~~~\n\n- `contextvars` now work properly when a ``@gen.coroutine`` calls a native coroutine.\n\n``tornado.options``\n~~~~~~~~~~~~~~~~~~~\n\n- `~.OptionParser.parse_config_file` now recognizes single comma-separated strings (in addition to\n  lists of strings) for options with ``multiple=True``.\n\n``tornado.web``\n~~~~~~~~~~~~~~~\n\n- New `.Application` setting ``xsrf_cookie_name`` can be used to change the\n  name of the XSRF cookie. This is most useful to take advantage of the\n  ``__Host-`` cookie prefix. \n- `.RequestHandler.get_secure_cookie` and `.RequestHandler.set_secure_cookie`\n  (and related methods and attributes) have been renamed to\n  `~.RequestHandler.get_signed_cookie` and `~.RequestHandler.set_signed_cookie`.\n  This makes it more explicit what kind of security is provided, and avoids\n  confusion with the ``Secure`` cookie attribute and ``__Secure-`` cookie prefix.\n  The old names remain supported as deprecated aliases.\n- `.RequestHandler.clear_cookie` now accepts all keyword arguments accepted by\n  `~.RequestHandler.set_cookie`. In some cases clearing a cookie requires certain\n  arguments to be passed the same way in which it was set. \n- `.RequestHandler.clear_all_cookies` now accepts additional keyword arguments\n  for the same reason as ``clear_cookie``. However, since the requirements\n  for additional arguments mean that it cannot reliably clear all cookies,\n  this method is now deprecated.\n\n\n``tornado.websocket``\n~~~~~~~~~~~~~~~~~~~~~\n\n- It is now much faster (no longer quadratic) to receive large messages that\n  have been split into many fragments.\n- `.websocket_connect` now accepts a ``resolver`` parameter.\n\n``tornado.wsgi``\n~~~~~~~~~~~~~~~~\n\n- `.WSGIContainer` now accepts an ``executor`` parameter which can be used\n  to run the WSGI application on a thread pool. "
  },
  {
    "path": "docs/releases/v6.3.1.rst",
    "content": "What's new in Tornado 6.3.1\n===========================\n\nApr 21, 2023\n------------\n\n``tornado.web``\n~~~~~~~~~~~~~~~\n\n- `.RequestHandler.set_cookie` once again accepts capitalized keyword arguments\n  for backwards compatibility. This is deprecated and in Tornado 7.0 only lowercase\n  arguments will be accepted. "
  },
  {
    "path": "docs/releases/v6.3.2.rst",
    "content": "What's new in Tornado 6.3.2\n===========================\n\nMay 13, 2023\n------------\n\nSecurity improvements\n~~~~~~~~~~~~~~~~~~~~~\n\n- Fixed an open redirect vulnerability in StaticFileHandler under certain\n  configurations."
  },
  {
    "path": "docs/releases/v6.3.3.rst",
    "content": "What's new in Tornado 6.3.3\n===========================\n\nAug 11, 2023\n------------\n\nSecurity improvements\n~~~~~~~~~~~~~~~~~~~~~\n\n- The ``Content-Length`` header and ``chunked`` ``Transfer-Encoding`` sizes are now parsed\n  more strictly (according to the relevant RFCs) to avoid potential request-smuggling\n  vulnerabilities when deployed behind certain proxies.\n"
  },
  {
    "path": "docs/releases/v6.4.0.rst",
    "content": "What's new in Tornado 6.4.0\n===========================\n\nNov 28, 2023\n------------\n\nGeneral Changes\n~~~~~~~~~~~~~~~\n\n- Python 3.12 is now supported. Older versions of Tornado will work on Python 3.12 but may log\n  deprecation warnings.\n\nDeprecation Notices\n~~~~~~~~~~~~~~~~~~~\n\n- `.IOLoop.add_callback_from_signal` is suspected to have been broken since Tornado 5.0 and will be\n  removed in version 7.0.  Use `asyncio.loop.add_signal_handler` instead.\n- The ``client_secret`` argument to `.OAuth2Mixin.authorize_redirect` is deprecated and will be\n  removed in Tornado 7.0. This argument has never been used and other similar methods in this module\n  don't have it.\n- `.TwitterMixin` is deprecated and will be removed in the future.\n\n``tornado.auth``\n~~~~~~~~~~~~~~~~\n\n- The ``client_secret`` argument to `.OAuth2Mixin.authorize_redirect` is deprecated and will be\n  removed in Tornado 7.0. This argument has never been used and other similar methods in this module\n  don't have it.\n- `.TwitterMixin` is deprecated and will be removed in the future.\n\n``tornado.autoreload``\n~~~~~~~~~~~~~~~~~~~~~~\n\n- Autoreload can now be used when the program is run as a directory rather than a file or module.\n- New CLI flag ``--until-success`` re-runs the program on any failure but stops after the first\n  successful run.\n\n``tornado.concurrent``\n~~~~~~~~~~~~~~~~~~~~~~\n\n- Fixed reference cycles that could lead to increased memory usage.\n\n``tornado.escape``\n~~~~~~~~~~~~~~~~~~\n\n- Several methods in this module now simply pass through to their equivalents in the standard\n  library.\n\n``tornado.gen``\n~~~~~~~~~~~~~~~\n\n- This module now holds a strong reference to all running `asyncio.Task` objects it creates. This\n  prevents premature garbage collection which could cause warnings like \"Task was destroyed but it\n  is pending!\".\n\n``tornado.ioloop``\n~~~~~~~~~~~~~~~~~~\n\n- `.IOLoop.add_callback_from_signal` is suspected to have been broken since Tornado 5.0 and will be\n  removed in version 7.0.  Use `asyncio.loop.add_signal_handler` instead.\n- The type annotation for `.IOLoop.run_in_executor` has been updated to match the updated signature\n  of `asyncio.loop.run_in_executor`.\n- Fixed reference cycles that could lead to increased memory usage.\n\n``tornado.locale``\n~~~~~~~~~~~~~~~~~~\n\n- `.format_timestamp` now supports \"aware\" datetime objects.\n\n``tornado.platform.asyncio``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- The shutdown protocol for `.AddThreadSelectorEventLoop` now requires the use of `asyncio.run` or\n  `asyncio.loop.shutdown_asyncgens` to avoid leaking the thread.\n- Introduced `.SelectorThread` class containing the core functionality of\n  `.AddThreadSelectorEventLoop`.\n- The ``close()`` method of `.AddThreadSelectorEventLoop` is now idempotent.\n\n``tornado.web``\n~~~~~~~~~~~~~~~\n\n- `.StaticFileHandler.get_modified_time` now supports \"aware\" datetime objects and the default\n  implementation now returns aware objects.\n\n``tornado.websocket``\n~~~~~~~~~~~~~~~~~~~~~\n\n- Unclosed client connections now reliably log a warning. Previously the warning was dependent on\n  garbage collection and whether the ``ping_interval`` option was used.\n- The ``subprotocols`` argument to `.WebSocketClientConnection` now defaults to None instead of an\n  empty list (which was mutable and reused)\n"
  },
  {
    "path": "docs/releases/v6.4.1.rst",
    "content": "What's new in Tornado 6.4.1\n===========================\n\nJun 6, 2024\n-----------\n\nSecurity Improvements\n~~~~~~~~~~~~~~~~~~~~~\n\n- Parsing of the ``Transfer-Encoding`` header is now stricter. Unexpected transfer-encoding values\n  were previously ignored and treated as the HTTP/1.0 default of read-until-close. This can lead to\n  framing issues with certain proxies. We now treat any unexpected value as an error.\n- Handling of whitespace in headers now matches the RFC more closely. Only space and tab characters\n  are treated as whitespace and stripped from the beginning and end of header values. Other unicode\n  whitespace characters are now left alone. This could also lead to framing issues with certain\n  proxies. \n- ``tornado.curl_httpclient`` now prohibits carriage return and linefeed headers in HTTP headers\n  (matching the behavior of ``simple_httpclient``). These characters could be used for header\n  injection or request smuggling if untrusted data were used in headers.\n\nGeneral Changes\n~~~~~~~~~~~~~~~\n\n`tornado.iostream`\n~~~~~~~~~~~~~~~~~~\n\n- `.SSLIOStream` now understands changes to error codes from OpenSSL 3.2. The main result of this\n  change is to reduce the noise in the logs for certain errors.\n\n``tornado.simple_httpclient``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- ``simple_httpclient`` now prohibits carriage return characters in HTTP headers. It had previously\n  prohibited only linefeed characters.\n\n`tornado.testing`\n~~~~~~~~~~~~~~~~~\n\n- `.AsyncTestCase` subclasses can now be instantiated without being associated with a test\n  method. This improves compatibility with test discovery in Pytest 8.2.\n\n"
  },
  {
    "path": "docs/releases/v6.4.2.rst",
    "content": "What's new in Tornado 6.4.2\n===========================\n\nNov 21, 2024\n------------\n\nSecurity Improvements\n~~~~~~~~~~~~~~~~~~~~~\n\n- Parsing of the cookie header is now much more efficient. The older algorithm sometimes had\n  quadratic performance which allowed for a denial-of-service attack in which the server would spend\n  excessive CPU time parsing cookies and block the event loop. This change fixes CVE-2024-7592."
  },
  {
    "path": "docs/releases/v6.5.0.rst",
    "content": "What's new in Tornado 6.5.0\n===========================\n\nMay 15, 2025\n------------\n\nSecurity Improvements\n~~~~~~~~~~~~~~~~~~~~~\n\n- Previously, malformed ``multipart-form-data`` requests could log multiple warnings and\n  constitute a denial-of-service attack. Now an exception is raised at the first error, so there\n  is only one log message per request. This fixes\n  `CVE-2025-47287 <https://github.com/tornadoweb/tornado/security/advisories/GHSA-7cx3-6m66-7c5m>`_.\n\nGeneral Changes\n~~~~~~~~~~~~~~~\n\n- Python 3.14 is now supported. Older versions of Tornado will work on Python 3.14 but may log\n  deprecation warnings.\n- The free-threading mode of Python 3.13 is now supported on an experimental basis. Prebuilt\n  wheels are not yet available for this configuration, but it can be built from source.\n- The minimum supported Python version is 3.9.\n\nDeprecation Notices\n~~~~~~~~~~~~~~~~~~~\n\n- Support for ``obs-fold`` continuation lines in HTTP headers is deprecated and will be removed in\n  Tornado 7.0, as is the use of carriage returns without line feeds as header separators.\n- The ``callback`` argument to `.websocket_connect` is deprecated and will be removed in\n  Tornado 7.0. Note that ``on_message_callback`` is not deprecated.\n- The ``log_message`` and ``args`` attributes of `tornado.web.HTTPError` are deprecated. Use the\n  new ``get_message`` method instead.\n\nType Annotation Changes\n~~~~~~~~~~~~~~~~~~~~~~~\n\n- `tornado.concurrent.chain_future` is now typed as accepting both asyncio and concurrent Futures.\n- `tornado.gen.multi` and ``multi_future`` now take ``Sequences`` and ``Mappings`` instead of\n  ``List`` and ``Dict``.\n- `tornado.httputil.RequestStartLine` and `.ResponseStartLine` now have type annotations for\n  their attributes.\n- `.HTTPHeaders` now has type annotations for its elements.\n- The ``autoescape`` argument to `tornado.template.BaseLoader` is now marked as optional.\n- ``tornado.routing._RuleList`` is now a ``Sequence`` for more flexibility.\n- ``.RequestHandler.SUPPPORTED_METHODS`` is now typed to support overriding in a subclass.\n- Types for `.RequestHandler.get_body_argument` and ``get_query_argument`` are improved and now\n  match the ``get_argument`` method.\n- `.RequestHandler.get_cookie` now has more accurate types.\n- The return type of `.UIModule.render` may now be either `str` or `bytes`.\n\n`tornado.httputil`\n~~~~~~~~~~~~~~~~~~\n\n- Support for ``obs-fold`` continuation lines in HTTP headers is deprecated and will be removed in\n  Tornado 7.0, as is the use of carriage returns without line feeds as header separators.\n- Request start lines may no longer include control characters.\n- Method names containing invalid characters now return error code 400 instead of 405.\n- Header names are now restricted to the set of characters permitted by the RFCs.\n- Control characters are no longer allowed in (incoming) header values.\n- Handling of trailing whitespace in headers has been improved, especially with ``obs-fold``\n  continuation lines.\n- The ``Host`` header is now restricted to the set of characters permitted by the RFCs. It is now an\n  error to send more than one ``Host`` header, or to omit a ``Host`` header for a request that is\n  not using HTTP/1.0.\n\n`tornado.ioloop`\n~~~~~~~~~~~~~~~~\n\n- Fixed a bug in which `contextvars` that were set when the event loop was created were not\n  available inside the event loop on Windows.\n\n`tornado.netutil`\n~~~~~~~~~~~~~~~~~\n\n- `.bind_unix_socket` now supports the Linux abstract namespace.\n\n\n`tornado.platform.twisted`\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- ``TwistedResolver`` has been deleted. It was already deprecated and scheduled for removal\n  in Tornado 7.0, but due to the adoption of RFC 8482 it no longer works for most\n  domain names. This class was primarily intended to provide thread-free non-blocking\n  DNS resolution. If that is still required, ``tornado.platform.caresresolver`` is the\n  next best option, although it has its own limitations which differ from TwistedResolver,\n  and it is also deprecated. Most users should switch to the default resolver, which uses\n  threads.\n\n`tornado.web`\n~~~~~~~~~~~~~\n\n- The set of characters allowed in (outgoing) HTTP headers now matches the RFCs. Specifically, tab\n  characters are now allowed and DEL is not.\n- Invalid ``If-Modified-Since`` headers are now ignored instead of causing a 500 error.\n- ``%`` characters in the log message of `tornado.web.HTTPError` are no longer doubled when no\n  additional arguments are passed. This matches the behavior of `logging.LogRecord`. A new method\n  ``get_message`` has been added to ``HTTPError`` to allow access to the fully-substituted message;\n  directly accessing ``log_message`` and ``args`` is deprecated.\n\n`tornado.websocket`\n~~~~~~~~~~~~~~~~~~~\n\n- Some bugs involving ``ping_interval`` and ``ping_timeout`` have been fixed. Setting the\n  ``ping_timeout`` greater than the ``ping_interval`` is no longer supported. The default\n  ``ping_timeout`` is now equal to the ``ping_interval``. \n- The ``callback`` argument to ``websocket_connect`` is deprecated and will be removed in\n  Tornado 7.0. Note that ``on_message_callback`` is not deprecated.\n"
  },
  {
    "path": "docs/releases/v6.5.1.rst",
    "content": "What's new in Tornado 6.5.1\n===========================\n\nMay 22, 2025\n------------\n\nBug fixes\n~~~~~~~~~\n\n- Fixed a bug in ``multipart/form-data`` parsing that could incorrectly reject filenames containing\n  characters above U+00FF (i.e. most characters outside the Latin alphabet).\n"
  },
  {
    "path": "docs/releases/v6.5.2.rst",
    "content": "What's new in Tornado 6.5.2\n===========================\n\nAug 8, 2025\n-----------\n\nBug fixes\n~~~~~~~~~\n\n- Fixed a bug that resulted in WebSocket pings not being sent at the configured interval.\n- Improved logging for invalid ``Host`` headers. This was previously logged as an uncaught\n  exception with a stack trace, now it is simply a 400 response (logged as a warning in the\n  access log).\n- Restored the ``host`` argument to `.HTTPServerRequest`. This argument is deprecated\n  and will be removed in the future, but its removal with no warning in 6.5.0 was a mistake.\n- Removed a debugging print statement that was left in the code.\n- Improved type hints for ``gen.multi``."
  },
  {
    "path": "docs/releases/v6.5.3.rst",
    "content": "What's new in Tornado 6.5.3\n===========================\n\nDec 10, 2025\n------------\n\nSecurity fixes\n~~~~~~~~~~~~~~\n- Fixed a denial-of-service vulnerability involving quadratic computation when parsing\n  ``multipart/form-data`` request bodies.\n  `CVE-2025-67726 <https://github.com/tornadoweb/tornado/security/advisories/GHSA-jhmp-mqwm-3gq8>`_\n  Thanks to `Finder16 <https://github.com/Finder16>`_ for reporting this issue.\n- Fixed a denial-of-service vulnerability involving quadratic computation when parsing repeated HTTP\n  headers.\n  `CVE-2025-67725 <https://github.com/tornadoweb/tornado/security/advisories/GHSA-c98p-7wgm-6p64>`_.\n  Thanks to `Finder16 <https://github.com/Finder16>`_ for reporting this issue.\n- Fixed a header injection and XSS vulnerability involving the ``reason`` argument to\n  `.RequestHandler.set_status` and `tornado.web.HTTPError`.\n  `CVE-2025-67724 <https://github.com/tornadoweb/tornado/security/advisories/GHSA-pr2v-jx2c-wg9f>`_.\n  Thanks to `Finder16 <https://github.com/Finder16>`_ and\n  `Cheshire1225 <https://github.com/Cheshire1225>`_ for reporting this issue.\n\nDemo changes\n~~~~~~~~~~~~\n- Several demo applications bundled with the Tornado repo (``blog``, ``chat``, ``facebook``) had an\n  open redirect vulnerability which has been fixed. This is not covered by a CVE or security\n  advisory since the demo applications are not included as a part of the Tornado package when\n  installed, but developers who have copied code from these demos may which to review their own\n  applications for open redirects. Thanks to `J1vvoo <https://github.com/J1vvoo>`_ for reporting this\n  issue.\n- The ``s3server`` demo application contained some path traversal vulnerabilities. Since this demo\n  application was not demonstrating any interesting aspects of Tornado, it has been deleted rather\n  than being fixed. Thanks to `J1vvoo <https://github.com/J1vvoo>`_ for reporting this issue.\n"
  },
  {
    "path": "docs/releases/v6.5.4.rst",
    "content": "What's new in Tornado 6.5.4\n===========================\n\nDec 15, 2025\n------------\n\nBug fixes\n~~~~~~~~~\n\n- The ``in`` operator for ``HTTPHeaders`` was incorrectly case-sensitive, causing\n  lookups to fail for headers with different casing than the original header name.\n  This was a regression in version 6.5.3 and has been fixed to restore the intended\n  case-insensitive behavior from version 6.5.2 and earlier.\n"
  },
  {
    "path": "docs/releases/v6.5.5.rst",
    "content": "What's new in Tornado 6.5.5\n===========================\n\nMar 10, 2026\n------------\n\nSecurity fixes\n~~~~~~~~~~~~~~\n\n- ``multipart/form-data`` requests are now limited to 100 parts by default, to prevent a\n  denial-of-service attack via very large requests with many parts. This limit is configurable\n  via `tornado.httputil.ParseMultipartConfig`. Multipart parsing can also be disabled completely\n  if not required for the application. Thanks to `0x-Apollyon <https://github.com/0x-Apollyon>`_ and\n  `bekkaze <https://github.com/bekkaze>`_ for reporting this issue.\n- The ``domain``, ``path``, and ``samesite`` arguments to `.RequestHandler.set_cookie` are now\n  validated for illegal characters, which could be abused to inject other attributes on the cookie.\n  Thanks to Dhiral Vyas (Praetorian) for reporting this issue.\n- Carriage return characters are no longer accepted in ``multipart/form-data`` headers. Thanks to \n  `sergeykochanov <https://github.com/sergeykochanov>`_ for reporting this issue.\n"
  },
  {
    "path": "docs/releases.rst",
    "content": "Release notes\n=============\n\n.. toctree::\n   :maxdepth: 2\n\n   releases/v6.5.5\n   releases/v6.5.4\n   releases/v6.5.3\n   releases/v6.5.2\n   releases/v6.5.1\n   releases/v6.5.0\n   releases/v6.4.2\n   releases/v6.4.1\n   releases/v6.4.0\n   releases/v6.3.3\n   releases/v6.3.2\n   releases/v6.3.1\n   releases/v6.3.0\n   releases/v6.2.0\n   releases/v6.1.0\n   releases/v6.0.4\n   releases/v6.0.3\n   releases/v6.0.2\n   releases/v6.0.1\n   releases/v6.0.0\n   releases/v5.1.1\n   releases/v5.1.0\n   releases/v5.0.2\n   releases/v5.0.1\n   releases/v5.0.0\n   releases/v4.5.3\n   releases/v4.5.2\n   releases/v4.5.1\n   releases/v4.5.0\n   releases/v4.4.3\n   releases/v4.4.2\n   releases/v4.4.1\n   releases/v4.4.0\n   releases/v4.3.0\n   releases/v4.2.1\n   releases/v4.2.0\n   releases/v4.1.0\n   releases/v4.0.2\n   releases/v4.0.1\n   releases/v4.0.0\n   releases/v3.2.2\n   releases/v3.2.1\n   releases/v3.2.0\n   releases/v3.1.1\n   releases/v3.1.0\n   releases/v3.0.2\n   releases/v3.0.1\n   releases/v3.0.0\n   releases/v2.4.1\n   releases/v2.4.0\n   releases/v2.3.0\n   releases/v2.2.1\n   releases/v2.2.0\n   releases/v2.1.1\n   releases/v2.1.0\n   releases/v2.0.0\n   releases/v1.2.1\n   releases/v1.2.0\n   releases/v1.1.1\n   releases/v1.1.0\n   releases/v1.0.1\n   releases/v1.0.0\n"
  },
  {
    "path": "docs/routing.rst",
    "content": "``tornado.routing`` --- Basic routing implementation\n====================================================\n\n.. automodule:: tornado.routing\n   :members:\n"
  },
  {
    "path": "docs/tcpclient.rst",
    "content": "``tornado.tcpclient`` --- `.IOStream` connection factory\n========================================================\n\n.. automodule:: tornado.tcpclient\n   :members:\n"
  },
  {
    "path": "docs/tcpserver.rst",
    "content": "``tornado.tcpserver`` --- Basic `.IOStream`-based TCP server\n============================================================\n\n.. automodule:: tornado.tcpserver\n    :members:\n"
  },
  {
    "path": "docs/template.rst",
    "content": "``tornado.template`` --- Flexible output generation\n===================================================\n\n.. automodule:: tornado.template\n\n   Class reference\n   ---------------\n\n   .. autoclass:: Template(template_string, name=\"<string>\", loader=None, compress_whitespace=None, autoescape=\"xhtml_escape\", whitespace=None)\n      :members:\n\n   .. autoclass:: BaseLoader\n      :members:\n\n   .. autoclass:: Loader\n      :members:\n\n   .. autoclass:: DictLoader\n      :members:\n\n   .. autoexception:: ParseError\n\n   .. autofunction:: filter_whitespace\n"
  },
  {
    "path": "docs/testing.rst",
    "content": "``tornado.testing`` --- Unit testing support for asynchronous code\n==================================================================\n\n.. automodule:: tornado.testing\n\n   Asynchronous test cases\n   -----------------------\n\n   .. autoclass:: AsyncTestCase\n      :members:\n\n   .. autoclass:: AsyncHTTPTestCase\n      :members:\n\n   .. autoclass:: AsyncHTTPSTestCase\n      :members:\n\n   .. autofunction:: gen_test\n\n   Controlling log output\n   ----------------------\n\n   .. autoclass:: ExpectLog\n      :members:\n\n   Test runner\n   -----------\n\n   .. autofunction:: main\n\n   Helper functions\n   ----------------\n\n   .. autofunction:: bind_unused_port\n\n   .. autofunction:: get_async_test_timeout\n\n   .. autofunction:: setup_with_context_manager\n"
  },
  {
    "path": "docs/twisted.rst",
    "content": "``tornado.platform.twisted`` --- Bridges between Twisted and Tornado\n====================================================================\n\n.. module:: tornado.platform.twisted\n\n.. deprecated:: 6.0\n\n   This module is no longer recommended for new code. Instead of using\n   direct integration between Tornado and Twisted, new applications should\n   rely on the integration with ``asyncio`` provided by both packages.\n\nImporting this module has the side effect of registering Twisted's ``Deferred``\nclass with Tornado's ``@gen.coroutine`` so that ``Deferred`` objects can be\nused with ``yield`` in coroutines using this decorator (importing this module has\nno effect on native coroutines using ``async def``). \n\n.. function:: install()\n\n    Install ``AsyncioSelectorReactor`` as the default Twisted reactor.\n\n    .. deprecated:: 5.1\n\n       This function is provided for backwards compatibility; code\n       that does not require compatibility with older versions of\n       Tornado should use\n       ``twisted.internet.asyncioreactor.install()`` directly.\n\n    .. versionchanged:: 6.0.3\n\n       In Tornado 5.x and before, this function installed a reactor\n       based on the Tornado ``IOLoop``. When that reactor\n       implementation was removed in Tornado 6.0.0, this function was\n       removed as well. It was restored in Tornado 6.0.3 using the\n       ``asyncio`` reactor instead.\n"
  },
  {
    "path": "docs/util.rst",
    "content": "``tornado.util`` --- General-purpose utilities\n==============================================\n\n.. testsetup::\n\n   from tornado.util import *\n\n.. automodule:: tornado.util\n    :members:\n\n    .. class:: TimeoutError\n\n        Exception raised by `.gen.with_timeout` and `.IOLoop.run_sync`.\n\n        .. versionchanged:: 5.0\n           Unified ``tornado.gen.TimeoutError`` and\n           ``tornado.ioloop.TimeoutError`` as ``tornado.util.TimeoutError``.\n           Both former names remain as aliases.\n\n        .. versionchanged:: 6.2\n           ``tornado.util.TimeoutError`` is an alias to :py:class:`asyncio.TimeoutError`\n"
  },
  {
    "path": "docs/utilities.rst",
    "content": "Utilities\n=========\n\n.. toctree::\n\n   autoreload\n   concurrent\n   log\n   options\n   testing\n   util\n"
  },
  {
    "path": "docs/web.rst",
    "content": "``tornado.web`` --- ``RequestHandler`` and ``Application`` classes\n==================================================================\n\n.. testsetup::\n\n   from tornado.web import *\n\n.. automodule:: tornado.web\n\n   Request handlers\n   ----------------\n   .. autoclass:: RequestHandler(...)\n\n   Entry points\n   ^^^^^^^^^^^^\n\n   .. automethod:: RequestHandler.initialize\n   .. automethod:: RequestHandler.prepare\n   .. automethod:: RequestHandler.on_finish\n\n   .. _verbs:\n\n   Implement any of the following methods (collectively known as the\n   HTTP verb methods) to handle the corresponding HTTP method. These\n   methods can be made asynchronous with the ``async def`` keyword or\n   `.gen.coroutine` decorator.\n\n   The arguments to these methods come from the `.URLSpec`: Any\n   capturing groups in the regular expression become arguments to the\n   HTTP verb methods (keyword arguments if the group is named,\n   positional arguments if it's unnamed).\n\n   To support a method not on this list, override the class variable\n   ``SUPPORTED_METHODS``::\n\n     class WebDAVHandler(RequestHandler):\n         SUPPORTED_METHODS = RequestHandler.SUPPORTED_METHODS + ('PROPFIND',)\n\n         def propfind(self):\n             pass\n\n   .. automethod:: RequestHandler.get\n   .. automethod:: RequestHandler.head\n   .. automethod:: RequestHandler.post\n   .. automethod:: RequestHandler.delete\n   .. automethod:: RequestHandler.patch\n   .. automethod:: RequestHandler.put\n   .. automethod:: RequestHandler.options\n\n   Input\n   ^^^^^\n\n   The ``argument`` methods provide support for HTML form-style\n   arguments. These methods are available in both singular and plural\n   forms because HTML forms are ambiguous and do not distinguish\n   between a singular argument and a list containing one entry. If you\n   wish to use other formats for arguments (for example, JSON), parse\n   ``self.request.body`` yourself::\n\n       def prepare(self):\n           if self.request.headers['Content-Type'] == 'application/x-json':\n               self.args = json_decode(self.request.body)\n           # Access self.args directly instead of using self.get_argument.\n\n\n   .. automethod:: RequestHandler.get_argument(name: str, default: Union[None, str, RAISE] = RAISE, strip: bool = True) -> Optional[str]\n   .. automethod:: RequestHandler.get_arguments\n   .. automethod:: RequestHandler.get_query_argument(name: str, default: Union[None, str, RAISE] = RAISE, strip: bool = True) -> Optional[str]\n   .. automethod:: RequestHandler.get_query_arguments\n   .. automethod:: RequestHandler.get_body_argument(name: str, default: Union[None, str, RAISE] = RAISE, strip: bool = True) -> Optional[str]\n   .. automethod:: RequestHandler.get_body_arguments\n   .. automethod:: RequestHandler.decode_argument\n   .. attribute:: RequestHandler.request\n\n      The `tornado.httputil.HTTPServerRequest` object containing additional\n      request parameters including e.g. headers and body data.\n\n   .. attribute:: RequestHandler.path_args\n   .. attribute:: RequestHandler.path_kwargs\n\n      The ``path_args`` and ``path_kwargs`` attributes contain the\n      positional and keyword arguments that are passed to the\n      :ref:`HTTP verb methods <verbs>`.  These attributes are set\n      before those methods are called, so the values are available\n      during `prepare`.\n\n   .. automethod:: RequestHandler.data_received\n\n   Output\n   ^^^^^^\n\n   .. automethod:: RequestHandler.set_status\n   .. automethod:: RequestHandler.set_header\n   .. automethod:: RequestHandler.add_header\n   .. automethod:: RequestHandler.clear_header\n   .. automethod:: RequestHandler.set_default_headers\n   .. automethod:: RequestHandler.write\n   .. automethod:: RequestHandler.flush\n   .. automethod:: RequestHandler.finish\n   .. automethod:: RequestHandler.render\n   .. automethod:: RequestHandler.render_string\n   .. automethod:: RequestHandler.get_template_namespace\n   .. automethod:: RequestHandler.redirect\n   .. automethod:: RequestHandler.send_error\n   .. automethod:: RequestHandler.write_error\n   .. automethod:: RequestHandler.clear\n   .. automethod:: RequestHandler.render_linked_js\n   .. automethod:: RequestHandler.render_embed_js\n   .. automethod:: RequestHandler.render_linked_css\n   .. automethod:: RequestHandler.render_embed_css\n\n   Cookies\n   ^^^^^^^\n\n   .. autoattribute:: RequestHandler.cookies\n   .. automethod:: RequestHandler.get_cookie\n   .. automethod:: RequestHandler.set_cookie\n   .. automethod:: RequestHandler.clear_cookie\n   .. automethod:: RequestHandler.clear_all_cookies\n   .. automethod:: RequestHandler.get_signed_cookie\n   .. automethod:: RequestHandler.get_signed_cookie_key_version\n   .. automethod:: RequestHandler.set_signed_cookie\n   .. method:: RequestHandler.get_secure_cookie\n\n      Deprecated alias for ``get_signed_cookie``.\n\n      .. deprecated:: 6.3\n\n   .. method:: RequestHandler.get_secure_cookie_key_version\n\n      Deprecated alias for ``get_signed_cookie_key_version``.\n\n      .. deprecated:: 6.3\n\n   .. method:: RequestHandler.set_secure_cookie\n\n      Deprecated alias for ``set_signed_cookie``.\n\n      .. deprecated:: 6.3\n\n   .. automethod:: RequestHandler.create_signed_value\n   .. autodata:: MIN_SUPPORTED_SIGNED_VALUE_VERSION\n   .. autodata:: MAX_SUPPORTED_SIGNED_VALUE_VERSION\n   .. autodata:: DEFAULT_SIGNED_VALUE_VERSION\n   .. autodata:: DEFAULT_SIGNED_VALUE_MIN_VERSION\n\n   Other\n   ^^^^^\n\n   .. attribute:: RequestHandler.application\n\n      The `Application` object serving this request\n\n   .. automethod:: RequestHandler.check_etag_header\n   .. automethod:: RequestHandler.check_xsrf_cookie\n   .. automethod:: RequestHandler.compute_etag\n   .. automethod:: RequestHandler.create_template_loader\n   .. autoattribute:: RequestHandler.current_user\n   .. automethod:: RequestHandler.detach\n   .. automethod:: RequestHandler.get_browser_locale\n   .. automethod:: RequestHandler.get_current_user\n   .. automethod:: RequestHandler.get_login_url\n   .. automethod:: RequestHandler.get_status\n   .. automethod:: RequestHandler.get_template_path\n   .. automethod:: RequestHandler.get_user_locale\n   .. autoattribute:: RequestHandler.locale\n   .. automethod:: RequestHandler.log_exception\n   .. automethod:: RequestHandler.on_connection_close\n   .. automethod:: RequestHandler.require_setting\n   .. automethod:: RequestHandler.reverse_url\n   .. automethod:: RequestHandler.set_etag_header\n   .. autoattribute:: RequestHandler.settings\n   .. automethod:: RequestHandler.static_url\n   .. automethod:: RequestHandler.xsrf_form_html\n   .. autoattribute:: RequestHandler.xsrf_token\n\n\n\n   Application configuration\n   -------------------------\n\n   .. autoclass:: Application(handlers: Optional[List[Union[Rule, Tuple]]] = None, default_host: Optional[str] = None, transforms: Optional[List[Type[OutputTransform]]] = None, **settings)\n\n      .. attribute:: settings\n\n         Additional keyword arguments passed to the constructor are\n         saved in the `settings` dictionary, and are often referred to\n         in documentation as \"application settings\".  Settings are\n         used to customize various aspects of Tornado (although in\n         some cases richer customization is possible by overriding\n         methods in a subclass of `RequestHandler`).  Some\n         applications also like to use the `settings` dictionary as a\n         way to make application-specific settings available to\n         handlers without using global variables.  Settings used in\n         Tornado are described below.\n\n         General settings:\n\n         * ``autoreload``: If ``True``, the server process will restart\n           when any source files change, as described in :ref:`debug-mode`.\n           This option is new in Tornado 3.2; previously this functionality\n           was controlled by the ``debug`` setting.\n         * ``debug``: Shorthand for several debug mode settings,\n           described in :ref:`debug-mode`.  Setting ``debug=True`` is\n           equivalent to ``autoreload=True``, ``compiled_template_cache=False``,\n           ``static_hash_cache=False``, ``serve_traceback=True``.\n         * ``default_handler_class`` and ``default_handler_args``:\n           This handler will be used if no other match is found;\n           use this to implement custom 404 pages (new in Tornado 3.2).\n         * ``compress_response``: If ``True``, responses in textual formats\n           will be compressed automatically.  New in Tornado 4.0.\n         * ``gzip``: Deprecated alias for ``compress_response`` since\n           Tornado 4.0.\n         * ``log_function``: This function will be called at the end\n           of every request to log the result (with one argument, the\n           `RequestHandler` object).  The default implementation\n           writes to the `logging` module's root logger.  May also be\n           customized by overriding `Application.log_request`.\n         * ``serve_traceback``: If ``True``, the default error page\n           will include the traceback of the error.  This option is new in\n           Tornado 3.2; previously this functionality was controlled by\n           the ``debug`` setting.\n         * ``ui_modules`` and ``ui_methods``: May be set to a mapping\n           of `UIModule` or UI methods to be made available to templates.\n           May be set to a module, dictionary, or a list of modules\n           and/or dicts.  See :ref:`ui-modules` for more details.\n         * ``websocket_ping_interval``: If the ping interval has a non-zero\n           value, a ping will be sent periodically every\n           ``websocket_ping_interval`` seconds, and the connection will be\n           closed if a response is not received before the\n           ``websocket_ping_timeout``.\n           This can help keep the connection alive through certain proxy\n           servers which close idle connections, and it can detect if the\n           websocket has failed without being properly closed.\n         * ``websocket_ping_timeout``: For use with ``websocket_ping_interval``,\n           if the server does not receive a pong within this many seconds, it\n           will close the websocket_ping_timeout.\n           The default timeout is equal to the ping interval. The ping timeout\n           will be turned off if the ping interval is not set or if the\n           timeout is set to ``0``.\n           This can help to detect disconnected clients to avoid keeping\n           inactive connections open.\n\n         Authentication and security settings:\n\n         * ``cookie_secret``: Used by `RequestHandler.get_signed_cookie`\n           and `.set_signed_cookie` to sign cookies.\n         * ``key_version``: Used by requestHandler `.set_signed_cookie`\n           to sign cookies with a specific key when ``cookie_secret``\n           is a key dictionary.\n         * ``login_url``: The `authenticated` decorator will redirect\n           to this url if the user is not logged in.  Can be further\n           customized by overriding `RequestHandler.get_login_url`\n         * ``xsrf_cookies``: If ``True``, :ref:`xsrf` will be enabled.\n         * ``xsrf_cookie_version``: Controls the version of new XSRF\n           cookies produced by this server.  Should generally be left\n           at the default (which will always be the highest supported\n           version), but may be set to a lower value temporarily\n           during version transitions.  New in Tornado 3.2.2, which\n           introduced XSRF cookie version 2.\n         * ``xsrf_cookie_kwargs``: May be set to a dictionary of\n           additional arguments to be passed to `.RequestHandler.set_cookie`\n           for the XSRF cookie.\n         * ``xsrf_cookie_name``: Controls the name used for the XSRF\n           cookie (default ``_xsrf``). The intended use is to take\n           advantage of `cookie prefixes`_. Note that cookie prefixes\n           interact with other cookie flags, so they must be combined\n           with ``xsrf_cookie_kwargs``, such as\n           ``{\"xsrf_cookie_name\": \"__Host-xsrf\", \"xsrf_cookie_kwargs\":\n           {\"secure\": True}}``\n         * ``twitter_consumer_key``, ``twitter_consumer_secret``,\n           ``friendfeed_consumer_key``, ``friendfeed_consumer_secret``,\n           ``google_consumer_key``, ``google_consumer_secret``,\n           ``facebook_api_key``, ``facebook_secret``:  Used in the\n           `tornado.auth` module to authenticate to various APIs.\n\n         .. _cookie prefixes: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie_prefixes\n\n         Template settings:\n\n         * ``autoescape``: Controls automatic escaping for templates.\n           May be set to ``None`` to disable escaping, or to the *name*\n           of a function that all output should be passed through.\n           Defaults to ``\"xhtml_escape\"``.  Can be changed on a per-template\n           basis with the ``{% autoescape %}`` directive.\n         * ``compiled_template_cache``: Default is ``True``; if ``False``\n           templates will be recompiled on every request.  This option\n           is new in Tornado 3.2; previously this functionality was controlled\n           by the ``debug`` setting.\n         * ``template_path``: Directory containing template files.  Can be\n           further customized by overriding `RequestHandler.get_template_path`\n         * ``template_loader``: Assign to an instance of\n           `tornado.template.BaseLoader` to customize template loading.\n           If this setting is used the ``template_path`` and ``autoescape``\n           settings are ignored.  Can be further customized by overriding\n           `RequestHandler.create_template_loader`.\n         * ``template_whitespace``: Controls handling of whitespace in\n           templates; see `tornado.template.filter_whitespace` for allowed\n           values. New in Tornado 4.3.\n\n         Static file settings:\n\n         * ``static_hash_cache``: Default is ``True``; if ``False``\n           static urls will be recomputed on every request.  This option\n           is new in Tornado 3.2; previously this functionality was controlled\n           by the ``debug`` setting.\n         * ``static_path``: Directory from which static files will be\n           served.\n         * ``static_url_prefix``:  Url prefix for static files,\n           defaults to ``\"/static/\"``.\n         * ``static_handler_class``, ``static_handler_args``: May be set to\n           use a different handler for static files instead of the default\n           `tornado.web.StaticFileHandler`.  ``static_handler_args``, if set,\n           should be a dictionary of keyword arguments to be passed to the\n           handler's ``initialize`` method.\n\n   .. automethod:: Application.listen\n   .. automethod:: Application.add_handlers(handlers: List[Union[Rule, Tuple]])\n   .. automethod:: Application.get_handler_delegate\n   .. automethod:: Application.reverse_url\n   .. automethod:: Application.log_request\n\n   .. autoclass:: URLSpec\n\n      The ``URLSpec`` class is also available under the name ``tornado.web.url``.\n\n   Decorators\n   ----------\n   .. autofunction:: authenticated\n   .. autofunction:: addslash\n   .. autofunction:: removeslash\n   .. autofunction:: stream_request_body\n\n   Everything else\n   ---------------\n   .. autoexception:: HTTPError\n   .. autoexception:: Finish\n   .. autoexception:: MissingArgumentError\n   .. autoclass:: UIModule\n      :members:\n\n   .. autoclass:: ErrorHandler\n   .. autoclass:: FallbackHandler\n   .. autoclass:: RedirectHandler\n   .. autoclass:: StaticFileHandler\n      :members:\n"
  },
  {
    "path": "docs/webframework.rst",
    "content": "Web framework\n=============\n\n.. toctree::\n\n   web\n   template\n   routing\n   escape\n   locale\n   websocket\n"
  },
  {
    "path": "docs/websocket.rst",
    "content": "``tornado.websocket`` --- Bidirectional communication to the browser\n====================================================================\n\n.. testsetup::\n\n   import tornado\n\n.. automodule:: tornado.websocket\n\n   .. autoclass:: WebSocketHandler\n\n   Event handlers\n   --------------\n\n   .. automethod:: WebSocketHandler.open\n   .. automethod:: WebSocketHandler.on_message\n   .. automethod:: WebSocketHandler.on_close\n   .. automethod:: WebSocketHandler.select_subprotocol\n   .. autoattribute:: WebSocketHandler.selected_subprotocol\n   .. automethod:: WebSocketHandler.on_ping\n\n   Output\n   ------\n\n   .. automethod:: WebSocketHandler.write_message\n   .. automethod:: WebSocketHandler.close\n\n   Configuration\n   -------------\n\n   .. automethod:: WebSocketHandler.check_origin\n   .. automethod:: WebSocketHandler.get_compression_options\n   .. automethod:: WebSocketHandler.set_nodelay\n\n   Other\n   -----\n\n   .. automethod:: WebSocketHandler.ping\n   .. automethod:: WebSocketHandler.on_pong\n   .. autoexception:: WebSocketClosedError\n\n\n   Client-side support\n   -------------------\n\n   .. autofunction:: websocket_connect\n   .. autoclass:: WebSocketClientConnection\n       :members:\n"
  },
  {
    "path": "docs/wsgi.rst",
    "content": "``tornado.wsgi`` --- Interoperability with other Python frameworks and servers\n==============================================================================\n\n.. automodule:: tornado.wsgi\n\n   .. autoclass:: WSGIContainer\n      :members:\n"
  },
  {
    "path": "maint/README",
    "content": "This directory contains tools and scripts that are used in the development\nand maintenance of Tornado itself, but are probably not of interest to\nTornado users.\n"
  },
  {
    "path": "maint/benchmark/benchmark.py",
    "content": "#!/usr/bin/env python\n#\n# A simple benchmark of tornado's HTTP stack.\n# Requires 'ab' to be installed.\n#\n# Running without profiling:\n# demos/benchmark/benchmark.py\n# demos/benchmark/benchmark.py --quiet --num_runs=5|grep \"Requests per second\"\n#\n# Running with profiling:\n#\n# python -m cProfile -o /tmp/prof demos/benchmark/benchmark.py\n# python -m pstats /tmp/prof\n# % sort time\n# % stats 20\n\nfrom tornado.options import define, options, parse_command_line\nfrom tornado.web import RequestHandler, Application\n\nimport asyncio\nimport random\n\n# choose a random port to avoid colliding with TIME_WAIT sockets left over\n# from previous runs.\ndefine(\"min_port\", type=int, default=8000)\ndefine(\"max_port\", type=int, default=9000)\n\n# Increasing --n without --keepalive will eventually run into problems\n# due to TIME_WAIT sockets\ndefine(\"n\", type=int, default=15000)\ndefine(\"c\", type=int, default=25)\ndefine(\"keepalive\", type=bool, default=False)\ndefine(\"quiet\", type=bool, default=False)\n\n# Repeat the entire benchmark this many times (on different ports)\n# This gives JITs time to warm up, etc.  Pypy needs 3-5 runs at\n# --n=15000 for its JIT to reach full effectiveness\ndefine(\"num_runs\", type=int, default=1)\n\n\nclass RootHandler(RequestHandler):\n    def get(self):\n        self.write(\"Hello, world\")\n\n    def _log(self):\n        pass\n\n\ndef main():\n    parse_command_line()\n    for i in range(options.num_runs):\n        asyncio.run(run())\n\n\nasync def run():\n    app = Application([(\"/\", RootHandler)])\n    port = random.randrange(options.min_port, options.max_port)\n    app.listen(port, address=\"127.0.0.1\")\n    args = [\"ab\"]\n    args.extend([\"-n\", str(options.n)])\n    args.extend([\"-c\", str(options.c)])\n    if options.keepalive:\n        args.append(\"-k\")\n    if options.quiet:\n        # just stops the progress messages printed to stderr\n        args.append(\"-q\")\n    args.append(\"http://127.0.0.1:%d/\" % port)\n    proc = await asyncio.create_subprocess_exec(*args)\n    await proc.wait()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "maint/benchmark/chunk_benchmark.py",
    "content": "#!/usr/bin/env python\n#\n# Downloads a large file in chunked encoding with both curl and simple clients\n\nimport logging\nfrom tornado.curl_httpclient import CurlAsyncHTTPClient\nfrom tornado.simple_httpclient import SimpleAsyncHTTPClient\nfrom tornado.ioloop import IOLoop\nfrom tornado.options import define, options, parse_command_line\nfrom tornado.web import RequestHandler, Application\n\ntry:\n    xrange\nexcept NameError:\n    xrange = range\n\ndefine('port', default=8888)\ndefine('num_chunks', default=1000)\ndefine('chunk_size', default=2048)\n\n\nclass ChunkHandler(RequestHandler):\n    def get(self):\n        for i in xrange(options.num_chunks):\n            self.write('A' * options.chunk_size)\n            self.flush()\n        self.finish()\n\n\ndef main():\n    parse_command_line()\n    app = Application([('/', ChunkHandler)])\n    app.listen(options.port, address='127.0.0.1')\n\n    def callback(response):\n        response.rethrow()\n        assert len(response.body) == (options.num_chunks * options.chunk_size)\n        logging.warning(\"fetch completed in %s seconds\", response.request_time)\n        IOLoop.current().stop()\n\n    logging.warning(\"Starting fetch with curl client\")\n    curl_client = CurlAsyncHTTPClient()\n    curl_client.fetch('http://localhost:%d/' % options.port,\n                      callback=callback)\n    IOLoop.current().start()\n\n    logging.warning(\"Starting fetch with simple client\")\n    simple_client = SimpleAsyncHTTPClient()\n    simple_client.fetch('http://localhost:%d/' % options.port,\n                        callback=callback)\n    IOLoop.current().start()\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "maint/benchmark/gen_benchmark.py",
    "content": "#!/usr/bin/env python\n#\n# A simple benchmark of the tornado.gen module.\n# Runs in two modes, testing new-style (@coroutine and Futures)\n# and old-style (@engine and Tasks) coroutines.\n\nfrom timeit import Timer\n\nfrom tornado import gen\nfrom tornado.options import options, define, parse_command_line\n\ndefine('num', default=10000, help='number of iterations')\n\n# These benchmarks are delicate.  They hit various fast-paths in the gen\n# machinery in order to stay synchronous so we don't need an IOLoop.\n# This removes noise from the results, but it's easy to change things\n# in a way that completely invalidates the results.\n\n\n@gen.engine\ndef e2(callback):\n    callback()\n\n\n@gen.engine\ndef e1():\n    for i in range(10):\n        yield gen.Task(e2)\n\n\n@gen.coroutine\ndef c2():\n    pass\n\n\n@gen.coroutine\ndef c1():\n    for i in range(10):\n        yield c2()\n\n\ndef main():\n    parse_command_line()\n    t = Timer(e1)\n    results = t.timeit(options.num) / options.num\n    print('engine: %0.3f ms per iteration' % (results * 1000))\n    t = Timer(c1)\n    results = t.timeit(options.num) / options.num\n    print('coroutine: %0.3f ms per iteration' % (results * 1000))\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "maint/benchmark/parsing_benchmark.py",
    "content": "#!/usr/bin/env python\r\nimport re\r\nimport timeit\r\nfrom enum import Enum\r\nfrom typing import Callable\r\n\r\nfrom tornado.httputil import HTTPHeaders\r\nfrom tornado.options import define, options, parse_command_line\r\n\r\n\r\ndefine(\"benchmark\", type=str)\r\ndefine(\"num_runs\", type=int, default=1)\r\n\r\n\r\n_CRLF_RE = re.compile(r\"\\r?\\n\")\r\n_TEST_HEADERS = (\r\n    \"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,\"\r\n    \"image/apng,*/*;q=0.8,application/signed-exchange;v=b3\\r\\n\"\r\n    \"Accept-Encoding: gzip, deflate, br\\r\\n\"\r\n    \"Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7\\r\\n\"\r\n    \"Cache-Control: max-age=0\\r\\n\"\r\n    \"Connection: keep-alive\\r\\n\"\r\n    \"Host: example.com\\r\\n\"\r\n    \"Upgrade-Insecure-Requests: 1\\r\\n\"\r\n    \"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \"\r\n    \"(KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36\\r\\n\"\r\n)\r\n\r\n\r\ndef headers_split_re(headers: str) -> None:\r\n    for line in _CRLF_RE.split(headers):\r\n        pass\r\n\r\n\r\ndef headers_split_simple(headers: str) -> None:\r\n    for line in headers.split(\"\\n\"):\r\n        if line.endswith(\"\\r\"):\r\n            line = line[:-1]\r\n\r\n\r\ndef headers_parse_re(headers: str) -> HTTPHeaders:\r\n    h = HTTPHeaders()\r\n    for line in _CRLF_RE.split(headers):\r\n        if line:\r\n            h.parse_line(line)\r\n    return h\r\n\r\n\r\ndef headers_parse_simple(headers: str) -> HTTPHeaders:\r\n    h = HTTPHeaders()\r\n    for line in headers.split(\"\\n\"):\r\n        if line.endswith(\"\\r\"):\r\n            line = line[:-1]\r\n        if line:\r\n            h.parse_line(line)\r\n    return h\r\n\r\n\r\ndef run_headers_split():\r\n    regex_time = timeit.timeit(lambda: headers_split_re(_TEST_HEADERS), number=100000)\r\n    print(\"regex\", regex_time)\r\n\r\n    simple_time = timeit.timeit(\r\n        lambda: headers_split_simple(_TEST_HEADERS), number=100000\r\n    )\r\n    print(\"str.split\", simple_time)\r\n\r\n    print(\"speedup\", regex_time / simple_time)\r\n\r\n\r\ndef run_headers_full():\r\n    regex_time = timeit.timeit(lambda: headers_parse_re(_TEST_HEADERS), number=10000)\r\n    print(\"regex\", regex_time)\r\n\r\n    simple_time = timeit.timeit(\r\n        lambda: headers_parse_simple(_TEST_HEADERS), number=10000\r\n    )\r\n    print(\"str.split\", simple_time)\r\n\r\n    print(\"speedup\", regex_time / simple_time)\r\n\r\n\r\nclass Benchmark(Enum):\r\n    def __new__(cls, arg_value: str, func: Callable[[], None]):\r\n        member = object.__new__(cls)\r\n        member._value_ = arg_value\r\n        member.func = func\r\n        return member\r\n\r\n    HEADERS_SPLIT = (\"headers-split\", run_headers_split)\r\n    HEADERS_FULL = (\"headers-full\", run_headers_full)\r\n\r\n\r\ndef main():\r\n    parse_command_line()\r\n\r\n    try:\r\n        func = Benchmark(options.benchmark).func\r\n    except ValueError:\r\n        known_benchmarks = [benchmark.value for benchmark in Benchmark]\r\n        print(\r\n            \"Unknown benchmark: '{}', supported values are: {}\"\r\n            .format(options.benchmark, \", \".join(known_benchmarks))\r\n        )\r\n        return\r\n\r\n    for _ in range(options.num_runs):\r\n        func()\r\n\r\n\r\nif __name__ == '__main__':\r\n    main()\r\n"
  },
  {
    "path": "maint/benchmark/template_benchmark.py",
    "content": "#!/usr/bin/env python\n#\n# A simple benchmark of tornado template rendering, based on\n# https://github.com/mitsuhiko/jinja2/blob/master/examples/bench.py\n\nimport sys\nfrom timeit import Timer\n\nfrom tornado.options import options, define, parse_command_line\nfrom tornado.template import Template\n\ndefine('num', default=100, help='number of iterations')\ndefine('dump', default=False, help='print template generated code and exit')\n\ncontext = {\n    'page_title': 'mitsuhiko\\'s benchmark',\n    'table': [dict(a=1, b=2, c=3, d=4, e=5,\n                   f=6, g=7, h=8, i=9, j=10) for x in range(1000)]\n}\n\ntmpl = Template(\"\"\"\\\n<!doctype html>\n<html>\n  <head>\n    <title>{{ page_title }}</title>\n  </head>\n  <body>\n    <div class=\"header\">\n      <h1>{{ page_title }}</h1>\n    </div>\n    <ul class=\"navigation\">\n    {% for href, caption in [ \\\n        ('index.html', 'Index'), \\\n        ('downloads.html', 'Downloads'), \\\n        ('products.html', 'Products') \\\n      ] %}\n      <li><a href=\"{{ href }}\">{{ caption }}</a></li>\n    {% end %}\n    </ul>\n    <div class=\"table\">\n      <table>\n      {% for row in table %}\n        <tr>\n        {% for cell in row %}\n          <td>{{ cell }}</td>\n        {% end %}\n        </tr>\n      {% end %}\n      </table>\n    </div>\n  </body>\n</html>\\\n\"\"\")\n\n\ndef render():\n    tmpl.generate(**context)\n\n\ndef main():\n    parse_command_line()\n    if options.dump:\n        print(tmpl.code)\n        sys.exit(0)\n    t = Timer(render)\n    results = t.timeit(options.num) / options.num\n    print('%0.3f ms per iteration' % (results * 1000))\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "maint/scripts/custom_fixers/__init__.py",
    "content": ""
  },
  {
    "path": "maint/scripts/custom_fixers/fix_future_imports.py",
    "content": "\"\"\"Updates all source files to import the same set of __future__ directives.\n\"\"\"\nfrom lib2to3 import fixer_base\nfrom lib2to3 import pytree\nfrom lib2to3.pgen2 import token\nfrom lib2to3.fixer_util import FromImport, Name, Comma, Newline\n\n\n# copied from fix_tuple_params.py\ndef is_docstring(stmt):\n    return isinstance(stmt, pytree.Node) and stmt.children[0].type == token.STRING\n\n\nclass FixFutureImports(fixer_base.BaseFix):\n    BM_compatible = True\n\n    PATTERN = \"\"\"import_from< 'from' module_name=\"__future__\" 'import' any >\"\"\"\n\n    def start_tree(self, tree, filename):\n        self.found_future_import = False\n\n    def new_future_import(self, old):\n        new = FromImport(\"__future__\",\n                         [Name(\"absolute_import\", prefix=\" \"), Comma(),\n                          Name(\"division\", prefix=\" \"), Comma(),\n                          Name(\"print_function\", prefix=\" \")])\n        if old is not None:\n            new.prefix = old.prefix\n        return new\n\n    def transform(self, node, results):\n        self.found_future_import = True\n        return self.new_future_import(node)\n\n    def finish_tree(self, tree, filename):\n        if self.found_future_import:\n            return\n        if not isinstance(tree, pytree.Node):\n            # Empty files (usually __init__.py) show up as a single Leaf\n            # instead of a Node, so leave them alone\n            return\n        first_stmt = tree.children[0]\n        if is_docstring(first_stmt):\n            # Skip a line and add the import after the docstring\n            tree.insert_child(1, Newline())\n            pos = 2\n        elif first_stmt.prefix:\n            # No docstring, but an initial comment (perhaps a #! line).\n            # Transfer the initial comment to a new blank line.\n            newline = Newline()\n            newline.prefix = first_stmt.prefix\n            first_stmt.prefix = \"\"\n            tree.insert_child(0, newline)\n            pos = 1\n        else:\n            # No comments or docstring, just insert at the start\n            pos = 0\n        tree.insert_child(pos, self.new_future_import(None))\n        tree.insert_child(pos + 1, Newline())  # terminates the import stmt\n"
  },
  {
    "path": "maint/scripts/custom_fixers/fix_unicode_literal.py",
    "content": "from lib2to3 import fixer_base\nfrom lib2to3.fixer_util import String\n\n\nclass FixUnicodeLiteral(fixer_base.BaseFix):\n    BM_compatible = True\n    PATTERN = \"\"\"\n    power< 'u'\n        trailer<\n            '('\n                arg=any\n            ')'\n        >\n    >\n    \"\"\"\n\n    def transform(self, node, results):\n        arg = results[\"arg\"]\n        node.replace(String('u' + arg.value, prefix=node.prefix))\n"
  },
  {
    "path": "maint/scripts/run_autopep8.sh",
    "content": "#!/bin/sh\n\n# Runs autopep8 in the configuration used for tornado.\n#\n# W602 is \"deprecated form of raising exception\", but the fix is incorrect\n# (and I'm not sure if the three-argument form of raise is really deprecated\n# in the first place)\n# E501 is \"line longer than 80 chars\" but the automated fix is ugly.\n# E301 adds a blank line between docstring and first method\n# E309 adds a blank line between class declaration and docstring (?)\nautopep8 --ignore=W602,E501,E301,E309 -i tornado/*.py tornado/platform/*.py tornado/test/*.py\n"
  },
  {
    "path": "maint/scripts/run_fixers.py",
    "content": "#!/usr/bin/env python\n# Usage is like 2to3:\n# $ maint/scripts/run_fixers.py -wn --no-diffs tornado\n\nimport sys\nfrom lib2to3.main import main\n\nsys.exit(main(\"custom_fixers\"))\n"
  },
  {
    "path": "maint/scripts/runcog.sh",
    "content": "#!/bin/sh\n\n# max/min_python_minor are the range of Python 3.x versions we support.\n# max_min_python_threaded_minor are the range of free-threaded python\n# versions we support.\n# default_python_minor is used in various parts of the build/CI pipeline,\n# most significantly in the docs and lint builds which can be sensitive\n# to minor version changes. We use the same version for all miscellaneous\n# tasks for consistency.\n# dev_python_minor is the version of Python that is currently under development\n# and is used to install pre-release versions of Python in CI.\nuvx --from cogapp cog \\\n    -D min_python_minor=10 \\\n    -D max_python_minor=14 \\\n    -D min_python_threaded_minor=14 \\\n    -D max_python_threaded_minor=14 \\\n    -D default_python_minor=13 \\\n    -D dev_python_minor=14 \\\n    -r $(git grep -l '\\[\\[\\[cog')\n"
  },
  {
    "path": "maint/scripts/test_resolvers.py",
    "content": "#!/usr/bin/env python\n\"\"\"Basic test for Tornado resolvers.\n\nQueries real domain names and prints the results from each resolver.\nRequires a working internet connection, which is why it's not in a\nunit test.\n\nWill be removed in Tornado 7.0 when the pluggable resolver system is\nremoved.\n\"\"\"\nimport pprint\nimport socket\n\nfrom tornado import gen\nfrom tornado.ioloop import IOLoop\nfrom tornado.netutil import Resolver, ThreadedResolver, DefaultExecutorResolver\nfrom tornado.options import parse_command_line, define, options\n\ntry:\n    import pycares\nexcept ImportError:\n    pycares = None\n\ndefine(\n    \"family\", default=\"unspec\", help=\"Address family to query: unspec, inet, or inet6\"\n)\n\n\n@gen.coroutine\ndef main():\n    args = parse_command_line()\n\n    if not args:\n        args = [\"localhost\", \"www.google.com\", \"www.facebook.com\", \"www.dropbox.com\"]\n\n    resolvers = [Resolver(), ThreadedResolver(), DefaultExecutorResolver()]\n\n    if pycares is not None:\n        from tornado.platform.caresresolver import CaresResolver\n\n        resolvers.append(CaresResolver())\n\n    family = {\n        \"unspec\": socket.AF_UNSPEC,\n        \"inet\": socket.AF_INET,\n        \"inet6\": socket.AF_INET6,\n    }[options.family]\n\n    for host in args:\n        print(\"Resolving %s\" % host)\n        for resolver in resolvers:\n            try:\n                addrinfo = yield resolver.resolve(host, 80, family)\n            except Exception as e:\n                print(\"%s: %s: %s\" % (resolver.__class__.__name__, type(e), e))\n            else:\n                print(\n                    \"%s: %s\" % (resolver.__class__.__name__, pprint.pformat(addrinfo))\n                )\n        print()\n\n\nif __name__ == \"__main__\":\n    IOLoop.instance().run_sync(main)\n"
  },
  {
    "path": "maint/test/README",
    "content": "This directory contains additional tests that are not included in the main\nsuite (because e.g. they have extra dependencies, run slowly, or produce\nmore output than a simple pass/fail)\n"
  },
  {
    "path": "maint/test/cython/.gitignore",
    "content": ".eggs\ncythonapp.egg-info\ndist\n"
  },
  {
    "path": "maint/test/cython/MANIFEST.in",
    "content": "include cythonapp.pyx\n"
  },
  {
    "path": "maint/test/cython/cythonapp.pyx",
    "content": "import cython\nfrom tornado import gen\nimport pythonmodule\n\nasync def native_coroutine():\n    x = await pythonmodule.hello()\n    if x != \"hello\":\n        raise ValueError(\"expected hello, got %r\" % x)\n    return \"goodbye\"\n\n@gen.coroutine\ndef decorated_coroutine():\n    x = yield pythonmodule.hello()\n    if x != \"hello\":\n        raise ValueError(\"expected hello, got %r\" % x)\n    return \"goodbye\"\n\n# The binding directive is necessary for compatibility with\n# ArgReplacer (and therefore return_future), but only because\n# this is a static function.\n@cython.binding(True)\ndef function_with_args(one, two, three):\n    return (one, two, three)\n\n\nclass AClass:\n    # methods don't need the binding directive.\n    def method_with_args(one, two, three):\n        return (one, two, three)\n"
  },
  {
    "path": "maint/test/cython/cythonapp_test.py",
    "content": "from tornado.testing import AsyncTestCase, gen_test\nfrom tornado.util import ArgReplacer\nimport unittest\n\nimport cythonapp\n\n\nclass CythonCoroutineTest(AsyncTestCase):\n    @gen_test\n    def test_native_coroutine(self):\n        x = yield cythonapp.native_coroutine()\n        self.assertEqual(x, \"goodbye\")\n\n    @gen_test\n    def test_decorated_coroutine(self):\n        x = yield cythonapp.decorated_coroutine()\n        self.assertEqual(x, \"goodbye\")\n\n\nclass CythonArgReplacerTest(unittest.TestCase):\n    def test_arg_replacer_function(self):\n        replacer = ArgReplacer(cythonapp.function_with_args, 'two')\n        args = (1, 'old', 3)\n        kwargs = {}\n        self.assertEqual(replacer.get_old_value(args, kwargs), 'old')\n        self.assertEqual(replacer.replace('new', args, kwargs),\n                         ('old', [1, 'new', 3], {}))\n\n    def test_arg_replacer_method(self):\n        replacer = ArgReplacer(cythonapp.AClass().method_with_args, 'two')\n        args = (1, 'old', 3)\n        kwargs = {}\n        self.assertEqual(replacer.get_old_value(args, kwargs), 'old')\n        self.assertEqual(replacer.replace('new', args, kwargs),\n                         ('old', [1, 'new', 3], {}))\n"
  },
  {
    "path": "maint/test/cython/pythonmodule.py",
    "content": "from tornado import gen\n\n\n@gen.coroutine\ndef hello():\n    yield gen.sleep(0.001)\n    raise gen.Return(\"hello\")\n"
  },
  {
    "path": "maint/test/cython/setup.py",
    "content": "from setuptools import setup\n\ntry:\n    import Cython.Build\nexcept:\n    Cython = None\n\nif Cython is None:\n    ext_modules = None\nelse:\n    ext_modules = Cython.Build.cythonize('cythonapp.pyx')\n\nsetup(\n    name='cythonapp',\n    py_modules=['cythonapp_test', 'pythonmodule'],\n    ext_modules=ext_modules,\n    setup_requires='Cython>=0.23.1',\n)\n"
  },
  {
    "path": "maint/test/cython/tox.ini",
    "content": "[tox]\n# This currently segfaults on pypy.\nenvlist = py27,py36\n\n[testenv]\ndeps =\n     ../../..\n     Cython>=0.23.3\n     backports_abc>=0.4\n     singledispatch\ncommands = python -m unittest cythonapp_test\n# Most of these are defaults, but if you specify any you can't fall back\n# defaults for the others.\nbasepython =\n           py27: python2.7\n           py36: python3.6\n"
  },
  {
    "path": "maint/test/mypy/.gitignore",
    "content": "UNKNOWN.egg-info\n"
  },
  {
    "path": "maint/test/mypy/bad.py",
    "content": "from tornado.web import RequestHandler\n\n\nclass MyHandler(RequestHandler):\n    def get(self) -> str:  # Deliberate type error\n        return \"foo\"\n"
  },
  {
    "path": "maint/test/mypy/good.py",
    "content": "from tornado import gen\nfrom tornado.web import RequestHandler\n\n\nclass MyHandler(RequestHandler):\n    def get(self) -> None:\n        self.write(\"foo\")\n\n    async def post(self) -> None:\n        await gen.sleep(1)\n        self.write(\"foo\")\n"
  },
  {
    "path": "maint/test/mypy/setup.py",
    "content": "from setuptools import setup\n\nsetup()\n"
  },
  {
    "path": "maint/test/mypy/tox.ini",
    "content": "# Test that the py.typed marker file is respected and client\n# application code can be typechecked using tornado's published\n# annotations.\n[tox]\nenvlist = py37\n\n[testenv]\ndeps =\n     ../../..\n     mypy\nwhitelist_externals = /bin/sh\ncommands =\n         mypy good.py\n         /bin/sh -c '! mypy bad.py'\n"
  },
  {
    "path": "maint/test/redbot/README",
    "content": "Redbot is an HTTP validator that checks for common problems, especially\nrelated to cacheability.  These tests ensure that Tornado's default behavior\nis correct (but note that this guarantee does not automatically extend\nto applications built on Tornado since application behavior can impact\ncacheability.\n\nhttp://redbot.org/about"
  },
  {
    "path": "maint/test/redbot/red_test.py",
    "content": "#!/usr/bin/env python\n\nimport logging\nfrom redbot.resource import HttpResource\nimport redbot.speak as rs\nimport thor\nimport threading\nfrom tornado import gen\nfrom tornado.options import parse_command_line\nfrom tornado.testing import AsyncHTTPTestCase\nfrom tornado.web import RequestHandler, Application, asynchronous\nimport unittest\n\n\nclass HelloHandler(RequestHandler):\n    def get(self):\n        self.write(\"Hello world\")\n\n\nclass RedirectHandler(RequestHandler):\n    def get(self, path):\n        self.redirect(path, status=int(self.get_argument('status', '302')))\n\n\nclass PostHandler(RequestHandler):\n    def post(self):\n        assert self.get_argument('foo') == 'bar'\n        self.redirect('/hello', status=303)\n\n\nclass ChunkedHandler(RequestHandler):\n    @asynchronous\n    @gen.engine\n    def get(self):\n        self.write('hello ')\n        yield gen.Task(self.flush)\n        self.write('world')\n        yield gen.Task(self.flush)\n        self.finish()\n\n\nclass CacheHandler(RequestHandler):\n    def get(self, computed_etag):\n        self.write(computed_etag)\n\n    def compute_etag(self):\n        return self._write_buffer[0]\n\n\nclass TestMixin(object):\n    def get_handlers(self):\n        return [\n            ('/hello', HelloHandler),\n            ('/redirect(/.*)', RedirectHandler),\n            ('/post', PostHandler),\n            ('/chunked', ChunkedHandler),\n            ('/cache/(.*)', CacheHandler),\n        ]\n\n    def get_app_kwargs(self):\n        return dict(static_path='.')\n\n    def get_allowed_warnings(self):\n        return [\n            # We can't set a non-heuristic freshness at the framework level,\n            # so just ignore this warning\n            rs.FRESHNESS_HEURISTIC,\n            # For our small test responses the Content-Encoding header\n            # wipes out any gains from compression\n            rs.CONNEG_GZIP_BAD,\n        ]\n\n    def get_allowed_errors(self):\n        return []\n\n    def check_url(self, path, method='GET', body=None, headers=None,\n                  expected_status=200, allowed_warnings=None,\n                  allowed_errors=None):\n        url = self.get_url(path)\n        red = self.run_redbot(url, method, body, headers)\n        if not red.response.complete:\n            if isinstance(red.response.http_error, Exception):\n                logging.warning((red.response.http_error.desc, vars(red.response.http_error), url))\n                raise red.response.http_error.res_error\n            else:\n                raise Exception(\"unknown error; incomplete response\")\n        self.assertEqual(int(red.response.status_code), expected_status)\n\n        allowed_warnings = (allowed_warnings or []) + self.get_allowed_warnings()\n        allowed_errors = (allowed_errors or []) + self.get_allowed_errors()\n\n        errors = []\n        warnings = []\n        for msg in red.response.notes:\n            if msg.level == 'bad':\n                logger = logging.error\n                if not isinstance(msg, tuple(allowed_errors)):\n                    errors.append(msg)\n            elif msg.level == 'warning':\n                logger = logging.warning\n                if not isinstance(msg, tuple(allowed_warnings)):\n                    warnings.append(msg)\n            elif msg.level in ('good', 'info', 'uri'):\n                logger = logging.info\n            else:\n                raise Exception('unknown level' + msg.level)\n            logger('%s: %s (%s)', msg.category, msg.show_summary('en'),\n                   msg.__class__.__name__)\n            logger(msg.show_text('en'))\n\n        self.assertEqual(len(warnings) + len(errors), 0,\n                         'Had %d unexpected warnings and %d errors' %\n                         (len(warnings), len(errors)))\n\n    def run_redbot(self, url, method, body, headers):\n        red = HttpResource(url, method=method, req_body=body,\n                           req_hdrs=headers)\n\n        def work():\n            red.run(thor.stop)\n            thor.run()\n            self.io_loop.add_callback(self.stop)\n\n        thread = threading.Thread(target=work)\n        thread.start()\n        self.wait()\n        thread.join()\n        return red\n\n    def test_hello(self):\n        self.check_url('/hello')\n\n    def test_static(self):\n        # TODO: 304 responses SHOULD return the same etag that a full\n        # response would.  We currently do for If-None-Match, but not\n        # for If-Modified-Since (because IMS does not otherwise\n        # require us to read the file from disk)\n        self.check_url('/static/red_test.py',\n                       allowed_warnings=[rs.MISSING_HDRS_304])\n\n    def test_static_versioned_url(self):\n        self.check_url('/static/red_test.py?v=1234',\n                       allowed_warnings=[rs.MISSING_HDRS_304])\n\n    def test_redirect(self):\n        self.check_url('/redirect/hello', expected_status=302)\n\n    def test_permanent_redirect(self):\n        self.check_url('/redirect/hello?status=301', expected_status=301)\n\n    def test_404(self):\n        self.check_url('/404', expected_status=404)\n\n    def test_post(self):\n        body = 'foo=bar'\n        # Without an explicit Content-Length redbot will try to send the\n        # request chunked.\n        self.check_url(\n            '/post', method='POST', body=body,\n            headers=[('Content-Length', str(len(body))),\n                     ('Content-Type', 'application/x-www-form-urlencoded')],\n            expected_status=303)\n\n    def test_chunked(self):\n        self.check_url('/chunked')\n\n    def test_strong_etag_match(self):\n        computed_etag = '\"xyzzy\"'\n        etags = '\"xyzzy\"'\n        self.check_url(\n            '/cache/' + computed_etag, method='GET',\n            headers=[('If-None-Match', etags)],\n            expected_status=304)\n\n    def test_multiple_strong_etag_match(self):\n        computed_etag = '\"xyzzy1\"'\n        etags = '\"xyzzy1\", \"xyzzy2\"'\n        self.check_url(\n            '/cache/' + computed_etag, method='GET',\n            headers=[('If-None-Match', etags)],\n            expected_status=304)\n\n    def test_strong_etag_not_match(self):\n        computed_etag = '\"xyzzy\"'\n        etags = '\"xyzzy1\"'\n        self.check_url(\n            '/cache/' + computed_etag, method='GET',\n            headers=[('If-None-Match', etags)],\n            expected_status=200)\n\n    def test_multiple_strong_etag_not_match(self):\n        computed_etag = '\"xyzzy\"'\n        etags = '\"xyzzy1\", \"xyzzy2\"'\n        self.check_url(\n            '/cache/' + computed_etag, method='GET',\n            headers=[('If-None-Match', etags)],\n            expected_status=200)\n\n    def test_wildcard_etag(self):\n        computed_etag = '\"xyzzy\"'\n        etags = '*'\n        self.check_url(\n            '/cache/' + computed_etag, method='GET',\n            headers=[('If-None-Match', etags)],\n            expected_status=304,\n            allowed_warnings=[rs.MISSING_HDRS_304])\n\n    def test_weak_etag_match(self):\n        computed_etag = '\"xyzzy1\"'\n        etags = 'W/\"xyzzy1\"'\n        self.check_url(\n            '/cache/' + computed_etag, method='GET',\n            headers=[('If-None-Match', etags)],\n            expected_status=304)\n\n    def test_multiple_weak_etag_match(self):\n        computed_etag = '\"xyzzy2\"'\n        etags = 'W/\"xyzzy1\", W/\"xyzzy2\"'\n        self.check_url(\n            '/cache/' + computed_etag, method='GET',\n            headers=[('If-None-Match', etags)],\n            expected_status=304)\n\n    def test_weak_etag_not_match(self):\n        computed_etag = '\"xyzzy2\"'\n        etags = 'W/\"xyzzy1\"'\n        self.check_url(\n            '/cache/' + computed_etag, method='GET',\n            headers=[('If-None-Match', etags)],\n            expected_status=200)\n\n    def test_multiple_weak_etag_not_match(self):\n        computed_etag = '\"xyzzy3\"'\n        etags = 'W/\"xyzzy1\", W/\"xyzzy2\"'\n        self.check_url(\n            '/cache/' + computed_etag, method='GET',\n            headers=[('If-None-Match', etags)],\n            expected_status=200)\n\n\nclass DefaultHTTPTest(AsyncHTTPTestCase, TestMixin):\n    def get_app(self):\n        return Application(self.get_handlers(), **self.get_app_kwargs())\n\n\nclass GzipHTTPTest(AsyncHTTPTestCase, TestMixin):\n    def get_app(self):\n        return Application(self.get_handlers(), gzip=True, **self.get_app_kwargs())\n\n    def get_allowed_errors(self):\n        return super().get_allowed_errors() + [\n            # TODO: The Etag is supposed to change when Content-Encoding is\n            # used.  This should be fixed, but it's difficult to do with the\n            # way GZipContentEncoding fits into the pipeline, and in practice\n            # it doesn't seem likely to cause any problems as long as we're\n            # using the correct Vary header.\n            rs.VARY_ETAG_DOESNT_CHANGE,\n        ]\n\n\nif __name__ == '__main__':\n    parse_command_line()\n    unittest.main()\n"
  },
  {
    "path": "maint/test/redbot/tox.ini",
    "content": "[tox]\nenvlist = py27\nsetupdir=../../..\n\n[testenv]\ncommands = python red_test.py\ndeps =\n     # Newer versions of thor have a bug with redbot (5/18/13)\n     thor==0.2.0\n     git+https://github.com/mnot/redbot.git\n"
  },
  {
    "path": "maint/test/websocket/.gitignore",
    "content": "reports/\n"
  },
  {
    "path": "maint/test/websocket/client.py",
    "content": "import logging\n\nfrom tornado import gen\nfrom tornado.ioloop import IOLoop\nfrom tornado.options import define, options, parse_command_line\nfrom tornado.websocket import websocket_connect\n\ndefine('url', default='ws://localhost:9001')\ndefine('name', default='Tornado')\n\n\n@gen.engine\ndef run_tests():\n    url = options.url + '/getCaseCount'\n    control_ws = yield websocket_connect(url, None)\n    num_tests = int((yield control_ws.read_message()))\n    logging.info('running %d cases', num_tests)\n    msg = yield control_ws.read_message()\n    assert msg is None\n\n    for i in range(1, num_tests + 1):\n        logging.info('running test case %d', i)\n        url = options.url + '/runCase?case=%d&agent=%s' % (i, options.name)\n        test_ws = yield websocket_connect(url, None, compression_options={})\n        while True:\n            message = yield test_ws.read_message()\n            if message is None:\n                break\n            test_ws.write_message(message, binary=isinstance(message, bytes))\n\n    url = options.url + '/updateReports?agent=%s' % options.name\n    update_ws = yield websocket_connect(url, None)\n    msg = yield update_ws.read_message()\n    assert msg is None\n    IOLoop.instance().stop()\n\n\ndef main():\n    parse_command_line()\n\n    IOLoop.instance().add_callback(run_tests)\n\n    IOLoop.instance().start()\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "maint/test/websocket/fuzzingclient.json",
    "content": "{\n    \"options\": {\n        \"failByDrop\": false\n    },\n    \"outdir\": \"./reports/servers\",\n    \"servers\": [\n        {\n            \"agent\": \"Tornado/py27\",\n            \"url\": \"ws://localhost:9001\",\n            \"options\": {\n                \"version\": 18\n            }\n        },\n        {\n            \"agent\": \"Tornado/py39\",\n            \"url\": \"ws://localhost:9002\",\n            \"options\": {\n                \"version\": 18\n            }\n        },\n        {\n            \"agent\": \"Tornado/pypy\",\n            \"url\": \"ws://localhost:9003\",\n            \"options\": {\n                \"version\": 18\n            }\n        }\n    ],\n    \"cases\": [\n        \"*\"\n    ],\n    \"exclude-cases\": [\n        \"9.*\",\n        \"12.*.1\",\n        \"12.2.*\",\n        \"12.3.*\",\n        \"12.4.*\",\n        \"12.5.*\",\n        \"13.*.1\"\n    ],\n    \"exclude-agent-cases\": {}\n}"
  },
  {
    "path": "maint/test/websocket/fuzzingserver.json",
    "content": "\n{\n   \"url\": \"ws://localhost:9001\",\n\n   \"options\": {\"failByDrop\": false},\n   \"outdir\": \"./reports/clients\",\n   \"webport\": 8080,\n\n   \"cases\": [\"*\"],\n   \"exclude-cases\": [\"9.*\", \"12.*.1\",\"12.2.*\", \"12.3.*\", \"12.4.*\", \"12.5.*\", \"13.*.1\"],\n   \"exclude-agent-cases\": {}\n}\n"
  },
  {
    "path": "maint/test/websocket/run-client.sh",
    "content": "#!/bin/sh\n\nset -e\n\ntox\n\n.tox/py27/bin/wstest -m fuzzingserver &\nFUZZING_SERVER_PID=$!\n\nsleep 1\n\n.tox/py27/bin/python client.py --name='Tornado/py27'\n.tox/py39/bin/python client.py --name='Tornado/py39'\n.tox/pypy/bin/python client.py --name='Tornado/pypy'\n\nkill $FUZZING_SERVER_PID\nwait\n\necho \"Tests complete.  Output is in ./reports/clients/index.html\"\n"
  },
  {
    "path": "maint/test/websocket/run-server.sh",
    "content": "#!/bin/sh\n#\n# Runs the autobahn websocket conformance test against tornado in both\n# python2 and python3.  Output goes in ./reports/servers/index.html.\n#\n# The --cases and --exclude arguments can be used to run only part of\n# the suite.  The default is --exclude=\"9.*\" to skip the relatively slow\n# performance tests; pass --exclude=\"\" to override and include them.\n\nset -e\n\n# build/update the virtualenvs\ntox\n\n.tox/py27/bin/python server.py --port=9001 &\nPY27_SERVER_PID=$!\n\n.tox/py39/bin/python server.py --port=9002 &\nPY39_SERVER_PID=$!\n\n.tox/pypy/bin/python server.py --port=9003 &\nPYPY_SERVER_PID=$!\n\nsleep 1\n\n.tox/py27/bin/wstest -m fuzzingclient\n\nkill $PY27_SERVER_PID\nkill $PY39_SERVER_PID\nkill $PYPY_SERVER_PID\nwait\n\necho \"Tests complete. Output is in ./reports/servers/index.html\"\n"
  },
  {
    "path": "maint/test/websocket/server.py",
    "content": "from tornado.ioloop import IOLoop\nfrom tornado.options import define, options, parse_command_line\nfrom tornado.websocket import WebSocketHandler\nfrom tornado.web import Application\n\ndefine('port', default=9000)\n\n\nclass EchoHandler(WebSocketHandler):\n    def on_message(self, message):\n        self.write_message(message, binary=isinstance(message, bytes))\n\n    def get_compression_options(self):\n        return {}\n\n\nif __name__ == '__main__':\n    parse_command_line()\n    app = Application([\n        ('/', EchoHandler),\n    ])\n    app.listen(options.port, address='127.0.0.1')\n    IOLoop.instance().start()\n"
  },
  {
    "path": "maint/test/websocket/tox.ini",
    "content": "# We don't actually use tox to run this test, but it's the easiest way\n# to install autobahn and build the speedups module.\n# See run.sh for the real test runner.\n[tox]\nenvlist = py27, py39, pypy\nsetupdir=../../..\n\n[testenv]\ncommands = python -c pass\n\n[testenv:py27]\ndeps = autobahntestsuite\n"
  },
  {
    "path": "maint/vm/README",
    "content": "This directory contains virtual machine setup scripts for testing Tornado.\n\nRequirements:\n\nVagrant (http://vagrantup.com) and VirtualBox (http://virtualbox.org).\nVagrant provides an easy download for Ubuntu images, base images for\nother platforms are harder to find and can be built with\nVeeWee (https://github.com/jedi4ever/veewee).\n\nUsage:\n\ncd to the appropriate directory and run `vagrant up`, then `vagrant ssh`.\nFrom there, simply run `tox` to run the full test suite, or cd to /tornado\nand test manually.  Afterwards, use `vagrant suspend` or `vagrant destroy`\nto clean up.\n\nNotes:\n\nPython distutils (and therefore tox) assume that if the platform\nsupports hard links, they can be used in the Tornado source directory.\nVirtualBox's shared folder filesystem does not support hard links (or\nsymlinks), so we have to use NFS shared folders instead.  (which has\nthe unfortunate side effect of requiring sudo on the host machine)\n"
  },
  {
    "path": "maint/vm/freebsd/Vagrantfile",
    "content": "# -*- mode: ruby -*-\n# vi: set ft=ruby :\n\n# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!\nVAGRANTFILE_API_VERSION = \"2\"\n\nVagrant.configure(VAGRANTFILE_API_VERSION) do |config|\n  config.vm.box = \"chef/freebsd-10.0\"\n\n  config.vm.network \"private_network\", type: \"dhcp\"\n\n  # Share an additional folder to the guest VM. The first argument is\n  # the path on the host to the actual folder. The second argument is\n  # the path on the guest to mount the folder. And the optional third\n  # argument is a set of non-required options.\n  config.vm.synced_folder \"../../..\", \"/tornado\", type: \"nfs\"\n\n  # Override the default /vagrant mapping to use nfs, since freebsd doesn't\n  # support other folder types.\n  config.vm.synced_folder \".\", \"/vagrant\", type: \"nfs\"\n\n  config.ssh.shell = \"/bin/sh\"\n\n  config.vm.provision :shell, :path => \"setup.sh\"\nend\n"
  },
  {
    "path": "maint/vm/freebsd/setup.sh",
    "content": "#!/bin/sh\n\nchsh -s bash vagrant\n\nPACKAGES=\"\ncurl\npython\npython34\npy27-pip\npy27-virtualenv\n\"\n\nPIP_PACKAGES=\"\nfutures\npycurl\ntox\n\"\n\nASSUME_ALWAYS_YES=true pkg install $PACKAGES\n\npip install $PIP_PACKAGES\n\n/tornado/maint/vm/shared-setup.sh\n"
  },
  {
    "path": "maint/vm/freebsd/tox.ini",
    "content": "[tox]\nenvlist=py27-full, py27, py34\nsetupdir=/tornado\n# /home is a symlink to /usr/home, but tox doesn't like symlinks here\ntoxworkdir=/usr/home/vagrant/tox-tornado\n\n[testenv]\ncommands = python -m tornado.test.runtests {posargs:}\n\n[testenv:py27-full]\n# twisted's tests fail on freebsd\ndeps =\n     futures\n     pycurl\n"
  },
  {
    "path": "maint/vm/shared-setup.sh",
    "content": "#!/bin/sh\n# Run at the end of each vm's provisioning script\n\nset -e\n\n# Link tox.ini into the home directory so you can run tox immediately\n# after ssh'ing in without cd'ing to /vagrant (since cd'ing to /tornado\n# gets the wrong config)\nln -sf /vagrant/tox.ini ~vagrant/tox.ini\n"
  },
  {
    "path": "maint/vm/ubuntu12.04/Vagrantfile",
    "content": "Vagrant::Config.run do |config|\n    config.vm.box = \"precise64\"\n    config.vm.box_url = \"http://files.vagrantup.com/precise64.box\"\n\n    config.vm.network :hostonly, \"172.19.1.5\"\n    config.vm.share_folder(\"tornado\", \"/tornado\", \"../../..\", :nfs=> true)\n\n    config.vm.provision :shell, :path => \"setup.sh\"\nend"
  },
  {
    "path": "maint/vm/ubuntu12.04/setup.sh",
    "content": "#!/bin/sh\n\nset -e\n\napt-get update\n\n# libcurl4-gnutls-dev is the default if you ask for libcurl4-dev, but it\n# has bugs that make our tests deadlock (the relevant tests detect this and\n# disable themselves, but it means that to get full coverage we have to use\n# the openssl version).\n# The oddly-named python-software-properties includes add-apt-repository.\nAPT_PACKAGES=\"\npython-pip\npython-dev\nlibcurl4-openssl-dev\npython-software-properties\n\"\n\napt-get -y install $APT_PACKAGES\n\n\n# Ubuntu 12.04 has python 2.7 as default; install more from here.\nadd-apt-repository ppa:fkrull/deadsnakes\napt-get update\n\nDEADSNAKES_PACKAGES=\"\npython3.5\npython3.5-dev\n\"\napt-get -y install $DEADSNAKES_PACKAGES\n\n\nPIP_PACKAGES=\"\nfutures\npycurl\ntox\ntwisted\nvirtualenv\n\"\n\npip install $PIP_PACKAGES\n\n/tornado/maint/vm/shared-setup.sh\n"
  },
  {
    "path": "maint/vm/ubuntu12.04/tox.ini",
    "content": "[tox]\nenvlist = py27-full, py27, py27-select, py27-twisted\nsetupdir=/tornado\ntoxworkdir=/home/vagrant/tox-tornado\n\n[testenv]\ncommands = python -m tornado.test.runtests {posargs:}\n\n[testenv:py27-full]\nbasepython = python2.7\ndeps =\n     futures\n     pycurl\n     twisted==12.2.0\n\n[testenv:py27-select]\nbasepython = python2.7\ndeps =\n     futures\n     pycurl\n     twisted==12.2.0\ncommands = python -m tornado.test.runtests --ioloop=tornado.platform.select.SelectIOLoop {posargs:}\n\n[testenv:py27-twisted]\nbasepython = python2.7\ndeps =\n     futures\n     pycurl\n     twisted==12.2.0\ncommands = python -m tornado.test.runtests --ioloop=tornado.platform.twisted.TwistedIOLoop {posargs:}\n"
  },
  {
    "path": "maint/vm/ubuntu14.04/Vagrantfile",
    "content": "Vagrant::Config.run do |config|\n    config.vm.box = \"ubuntu/trusty64\"\n\n    config.vm.network :hostonly, \"172.19.1.8\"\n    config.vm.share_folder(\"tornado\", \"/tornado\", \"../../..\", :nfs=> true)\n\n    config.vm.provision :shell, :path => \"setup.sh\"\nend"
  },
  {
    "path": "maint/vm/ubuntu14.04/setup.sh",
    "content": "#!/bin/sh\n\nset -e\n\napt-get update\n\n# libcurl4-gnutls-dev is the default if you ask for libcurl4-dev, but it\n# has bugs that make our tests deadlock (the relevant tests detect this and\n# disable themselves, but it means that to get full coverage we have to use\n# the openssl version).\nAPT_PACKAGES=\"\npython-pip\npython-dev\npython3-pycurl\nlibcurl4-openssl-dev\n\"\n\napt-get -y install $APT_PACKAGES\n\n# Ubuntu 14.04 includes python 2.7 and 3.4.\n\nPIP_PACKAGES=\"\nfutures\npycurl\ntox\ntwisted\nvirtualenv\n\"\n\npip install $PIP_PACKAGES\n\n/tornado/maint/vm/shared-setup.sh\n"
  },
  {
    "path": "maint/vm/ubuntu14.04/tox.ini",
    "content": "[tox]\nenvlist = py27-full, py34, py27, py27-select, py27-twisted\nsetupdir=/tornado\ntoxworkdir=/home/vagrant/tox-tornado\n\n[testenv]\ncommands = python -m tornado.test.runtests {posargs:}\n\n[testenv:py27-full]\nbasepython = python2.7\ndeps =\n     futures\n     mock\n     pycurl\n     twisted==14.0.0\n\n[testenv:py27-select]\nbasepython = python2.7\ndeps =\n     futures\n     mock\n     pycurl\n     twisted==14.0.0\ncommands = python -m tornado.test.runtests --ioloop=tornado.platform.select.SelectIOLoop {posargs:}\n\n[testenv:py27-twisted]\nbasepython = python2.7\ndeps =\n     futures\n     mock\n     pycurl\n     twisted==14.0.0\ncommands = python -m tornado.test.runtests --ioloop=tornado.platform.twisted.TwistedIOLoop {posargs:}\n"
  },
  {
    "path": "maint/vm/windows/bootstrap.py",
    "content": "#!/usr/bin/env python\n\nr\"\"\"Installs files needed for tornado testing on windows.\n\nThese instructions are compatible with the VMs provided by http://modern.ie.\nThe bootstrapping script works on the WinXP/IE6 and Win8/IE10 configurations,\nalthough tornado's tests do not pass on XP.\n\n1) Install virtualbox guest additions (from the device menu in virtualbox)\n2) Set up a shared folder to the root of your tornado repo.  It must be a\n   read-write mount to use tox, although the tests can be run directly\n   in a read-only mount.  This will probably assign drive letter E:.\n3) Install Python 2.7 from python.org.\n4) Run this script by double-clicking it, or running\n   \"c:\\python27\\python.exe bootstrap.py\" in a shell.\n\nTo run the tests by hand, cd to e:\\ and run\n  c:\\python27\\python.exe -m tornado.test.runtests\nTo run the tests with tox, cd to e:\\maint\\vm\\windows and run\n  c:\\python27\\scripts\\tox\nTo run under cygwin (which must be installed separately), run\n  cd /cygdrive/e; python -m tornado.test.runtests\n\"\"\"\n\nimport os\nimport subprocess\nimport sys\nimport urllib\n\nTMPDIR = r'c:\\tornado_bootstrap'\n\nPYTHON_VERSIONS = [\n    (r'c:\\python27\\python.exe', 'http://www.python.org/ftp/python/2.7.3/python-2.7.3.msi'),\n    (r'c:\\python36\\python.exe', 'http://www.python.org/ftp/python/3.6.0/python-3.6.0.msi'),\n]\n\nSCRIPTS_DIR = r'c:\\python27\\scripts'\nEASY_INSTALL = os.path.join(SCRIPTS_DIR, 'easy_install.exe')\n\nPY_PACKAGES = ['tox', 'virtualenv', 'pip']\n\n\ndef download_to_cache(url, local_name=None):\n    if local_name is None:\n        local_name = url.split('/')[-1]\n    filename = os.path.join(TMPDIR, local_name)\n    if not os.path.exists(filename):\n        data = urllib.urlopen(url).read()\n        with open(filename, 'wb') as f:\n            f.write(data)\n    return filename\n\n\ndef main():\n    if not os.path.exists(TMPDIR):\n        os.mkdir(TMPDIR)\n    os.chdir(TMPDIR)\n    for exe, url in PYTHON_VERSIONS:\n        if os.path.exists(exe):\n            print(\"%s already exists, skipping\" % exe)\n            continue\n        print(\"Installing %s\" % url)\n        filename = download_to_cache(url)\n        # http://blog.jaraco.com/2012/01/how-i-install-python-on-windows.html\n        subprocess.check_call(['msiexec', '/i', filename,\n                               'ALLUSERS=1', '/passive'])\n\n    if not os.path.exists(EASY_INSTALL):\n        filename = download_to_cache('http://python-distribute.org/distribute_setup.py')\n        subprocess.check_call([sys.executable, filename])\n\n    subprocess.check_call([EASY_INSTALL] + PY_PACKAGES)\n\n    # cygwin's setup.exe doesn't like being run from a script (looks\n    # UAC-related).  If it did, something like this might install it.\n    # (install python, python-setuptools, python3, and easy_install\n    # unittest2 (cygwin's python 2 is 2.6))\n    #filename = download_to_cache('http://cygwin.com/setup.exe')\n    #CYGTMPDIR = os.path.join(TMPDIR, 'cygwin')\n    #if not os.path.exists(CYGTMPDIR):\n    #    os.mkdir(CYGTMPDIR)\n    ## http://www.jbmurphy.com/2011/06/16/powershell-script-to-install-cygwin/\n    #CYGWIN_ARGS = [filename, '-q', '-l', CYGTMPDIR,\n    #               '-s', 'http://mirror.nyi.net/cygwin/', '-R', r'c:\\cygwin']\n    #subprocess.check_call(CYGWIN_ARGS)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "maint/vm/windows/tox.ini",
    "content": "[tox]\nenvlist = py27-full, py27, py36, py27-opt, py36-monotonic\nsetupdir = e:\\\ntoxworkdir = c:\\tox-tornado\n\n[testenv]\ncommands = python -m tornado.test.runtests {posargs:}\n\n[testenv:py27-full]\nbasepython = python2.7\ndeps =\n     futures\n     mock\n\n[testenv:py36]\n# TODO: still needed?\n# tox's path mappings haven't been updated for py33 yet.\nbasepython = c:\\python36\\python.exe\n\n[testenv:py36-monotonic]\nbasepython = c:\\python36\\python.exe\ncommands = python -m tornado.test.runtests --ioloop_time_monotonic {posargs:}\n\n[testenv:py27-opt]\nbasepython = python2.7\ndeps =\n     futures\n     mock\ncommands = python -O -m tornado.test.runtests {posargs:}\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[tool.black]\n# [[[cog\n#    cog.outl(f\"target-version = ['py3{min_python_minor}']\")\n# ]]]\ntarget-version = ['py310']\n#[[[end]]]\n\n[tool.cibuildwheel]\n# [[[cog\n#    versions = [f\"cp3{m}*\" for m in range(int(min_python_minor), int(max_python_minor) + 1)]\n#    versions += [\"cp314t*\"]\n#    cog.outl(f\"build = \\\"{\" \".join(versions)}\\\"\")\n# ]]]\nbuild = \"cp310* cp311* cp312* cp313* cp314* cp314t*\"\n#[[[end]]]\ntest-command = \"python -m tornado.test\"\n\n[tool.cibuildwheel.macos]\narchs = \"universal2\"\n\n[tool.cibuildwheel.windows]\n# TODO: figure out what's going on with these occasional log messages.\ntest-command = \"python -m tornado.test --fail-if-logs=false\"\n"
  },
  {
    "path": "requirements.in",
    "content": "black\nflake8\nmypy>=0.941\npip-tools\nsphinx\nsphinx_rtd_theme\ntypes-pycurl\ntox\n"
  },
  {
    "path": "requirements.txt",
    "content": "#\n# This file is autogenerated by pip-compile with Python 3.11\n# by the following command:\n#\n#    pip-compile\n#\nalabaster==1.0.0\n    # via sphinx\nbabel==2.17.0\n    # via sphinx\nblack==26.3.1\n    # via -r requirements.in\nbuild==1.2.2.post1\n    # via pip-tools\ncachetools==5.5.2\n    # via tox\ncertifi==2025.4.26\n    # via requests\nchardet==5.2.0\n    # via tox\ncharset-normalizer==3.4.2\n    # via requests\nclick==8.2.1\n    # via\n    #   black\n    #   pip-tools\ncolorama==0.4.6\n    # via tox\ndistlib==0.3.9\n    # via virtualenv\ndocutils==0.21.2\n    # via\n    #   sphinx\n    #   sphinx-rtd-theme\nfilelock==3.20.3\n    # via\n    #   tox\n    #   virtualenv\nflake8==7.2.0\n    # via -r requirements.in\nidna==3.10\n    # via requests\nimagesize==1.4.1\n    # via sphinx\njinja2==3.1.6\n    # via sphinx\nmarkupsafe==3.0.2\n    # via jinja2\nmccabe==0.7.0\n    # via flake8\nmypy==1.15.0\n    # via -r requirements.in\nmypy-extensions==1.1.0\n    # via\n    #   black\n    #   mypy\npackaging==25.0\n    # via\n    #   black\n    #   build\n    #   pyproject-api\n    #   sphinx\n    #   tox\n    #   wheel\npathspec==1.0.4\n    # via black\npip-tools==7.4.1\n    # via -r requirements.in\nplatformdirs==4.3.8\n    # via\n    #   black\n    #   tox\n    #   virtualenv\npluggy==1.6.0\n    # via tox\npycodestyle==2.13.0\n    # via flake8\npyflakes==3.3.2\n    # via flake8\npygments==2.19.1\n    # via sphinx\npyproject-api==1.9.1\n    # via tox\npyproject-hooks==1.2.0\n    # via\n    #   build\n    #   pip-tools\npytokens==0.4.1\n    # via black\nrequests==2.32.4\n    # via sphinx\nroman-numerals-py==3.1.0\n    # via sphinx\nsnowballstemmer==3.0.1\n    # via sphinx\nsphinx==8.2.3\n    # via\n    #   -r requirements.in\n    #   sphinx-rtd-theme\n    #   sphinxcontrib-jquery\nsphinx-rtd-theme==3.0.2\n    # via -r requirements.in\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-jquery==4.1\n    # via sphinx-rtd-theme\nsphinxcontrib-jsmath==1.0.1\n    # via sphinx\nsphinxcontrib-qthelp==2.0.0\n    # via sphinx\nsphinxcontrib-serializinghtml==2.0.0\n    # via sphinx\ntox==4.26.0\n    # via -r requirements.in\ntypes-pycurl==7.45.6.20250309\n    # via -r requirements.in\ntyping-extensions==4.13.2\n    # via mypy\nurllib3==2.6.3\n    # via requests\nvirtualenv==20.36.1\n    # via tox\nwheel==0.46.2\n    # via pip-tools\n\n# The following packages are considered to be unsafe in a requirements file:\n# pip\n# setuptools\n"
  },
  {
    "path": "runtests.sh",
    "content": "#!/bin/sh\n# Run the Tornado test suite.\n#\n# Also consider using tox, which uses virtualenv to run the test suite\n# under multiple versions of python.\n\ncd $(dirname $0)\n\n# \"python -m\" differs from \"python tornado/test/runtests.py\" in how it sets\n# up the default python path.  \"python -m\" uses the current directory,\n# while \"python file.py\" uses the directory containing \"file.py\" (which is\n# not what you want if file.py appears within a package you want to import\n# from)\npython -m tornado.test.runtests \"$@\"\n"
  },
  {
    "path": "setup.cfg",
    "content": "[metadata]\nlicense_file = LICENSE\n\n[mypy]\n# [[[cog cog.outl(f\"python_version = 3.{min_python_minor}\")]]]\npython_version = 3.10\n# [[[end]]]\nno_implicit_optional = True\n\n[mypy-tornado.*,tornado.platform.*]\ndisallow_untyped_defs = True\n\n# It's generally too tedious to require type annotations in tests, but\n# we do want to type check them as much as type inference allows.\n[mypy-tornado.test.*]\ndisallow_untyped_defs = False\ncheck_untyped_defs = True\n"
  },
  {
    "path": "setup.py",
    "content": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n# type: ignore\n\nimport os\nimport platform\nimport setuptools\nimport sysconfig\n\n\nkwargs = {}\n\nwith open(\"tornado/__init__.py\") as f:\n    ns = {}\n    exec(f.read(), ns)\n    version = ns[\"version\"]\n\nwith open(\"README.rst\") as f:\n    kwargs[\"long_description\"] = f.read()\n    kwargs[\"long_description_content_type\"] = \"text/x-rst\"\n\nif (\n    platform.python_implementation() == \"CPython\"\n    and os.environ.get(\"TORNADO_EXTENSION\") != \"0\"\n):\n\n    can_use_limited_api = not sysconfig.get_config_var(\"Py_GIL_DISABLED\")\n\n    if can_use_limited_api:\n        # [[[cog\n        #   cog.out(f\"define_macros = [(\\\"Py_LIMITED_API\\\", \")\n        #   cog.out(f\"\\\"0x03{int(min_python_minor):02x}0000\\\"\")\n        #   cog.outl(\")]\")\n        #   cog.outl(f\"bdist_wheel_options = {{\\\"py_limited_api\\\": \\\"cp3{min_python_minor}\\\"}}\")\n        #   ]]]\n        define_macros = [(\"Py_LIMITED_API\", \"0x030a0000\")]\n        bdist_wheel_options = {\"py_limited_api\": \"cp310\"}\n        # [[[end]]]\n    else:\n        define_macros = []\n        bdist_wheel_options = {}\n\n    # This extension builds and works on pypy as well, although pypy's jit\n    # produces equivalent performance.\n    kwargs[\"ext_modules\"] = [\n        setuptools.Extension(\n            \"tornado.speedups\",\n            sources=[\"tornado/speedups.c\"],\n            # Unless the user has specified that the extension is mandatory,\n            # fall back to the pure-python implementation on any build failure.\n            optional=os.environ.get(\"TORNADO_EXTENSION\") != \"1\",\n            # Use the stable ABI so our wheels are compatible across python\n            # versions.\n            py_limited_api=can_use_limited_api,\n            define_macros=define_macros,\n        )\n    ]\n\n    if bdist_wheel_options:\n        kwargs[\"options\"] = {\"bdist_wheel\": bdist_wheel_options}\n\n\nsetuptools.setup(\n    name=\"tornado\",\n    version=version,\n    # [[[cog cog.outl(f\"python_requires=\\\">= 3.{min_python_minor}\\\",\")]]]\n    python_requires=\">= 3.10\",\n    # [[[end]]]\n    packages=[\"tornado\", \"tornado.test\", \"tornado.platform\"],\n    package_data={\n        # data files need to be listed both here (which determines what gets\n        # installed) and in MANIFEST.in (which determines what gets included\n        # in the sdist tarball)\n        \"tornado\": [\"py.typed\"],\n        \"tornado.test\": [\n            \"README\",\n            \"csv_translations/fr_FR.csv\",\n            \"gettext_translations/fr_FR/LC_MESSAGES/tornado_test.mo\",\n            \"gettext_translations/fr_FR/LC_MESSAGES/tornado_test.po\",\n            \"options_test.cfg\",\n            \"options_test_types.cfg\",\n            \"options_test_types_str.cfg\",\n            \"static/robots.txt\",\n            \"static/sample.xml\",\n            \"static/sample.xml.gz\",\n            \"static/sample.xml.bz2\",\n            \"static/dir/index.html\",\n            \"static_foo.txt\",\n            \"templates/utf8.html\",\n            \"test.crt\",\n            \"test.key\",\n        ],\n    },\n    author=\"Facebook\",\n    author_email=\"python-tornado@googlegroups.com\",\n    url=\"http://www.tornadoweb.org/\",\n    project_urls={\n        \"Source\": \"https://github.com/tornadoweb/tornado\",\n    },\n    license=\"Apache-2.0\",\n    description=(\n        \"Tornado is a Python web framework and asynchronous networking library,\"\n        \" originally developed at FriendFeed.\"\n    ),\n    classifiers=[\n        \"License :: OSI Approved :: Apache Software License\",\n        \"Programming Language :: Python :: 3\",\n        # [[[cog\n        #   for minor in range(int(min_python_minor), int(max_python_minor) + 1):\n        #     cog.outl(f\"\\\"Programming Language :: Python :: 3.{minor}\\\"\")\n        #   ]]]\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        # [[[end]]]\n        \"Programming Language :: Python :: Implementation :: CPython\",\n        \"Programming Language :: Python :: Implementation :: PyPy\",\n    ],\n    **kwargs,\n)\n"
  },
  {
    "path": "tornado/__init__.py",
    "content": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"The Tornado web server and tools.\"\"\"\n\n# version is a human-readable version number.\n\n# version_info is a four-tuple for programmatic comparison. The first\n# three numbers are the components of the version number.  The fourth\n# is zero for an official release, positive for a development branch,\n# or negative for a release candidate or beta (after the base version\n# number has been incremented)\nversion = \"6.6.dev1\"\nversion_info = (6, 6, 0, -100)\n\nimport importlib\nimport typing\n\n__all__ = [\n    \"auth\",\n    \"autoreload\",\n    \"concurrent\",\n    \"curl_httpclient\",\n    \"escape\",\n    \"gen\",\n    \"http1connection\",\n    \"httpclient\",\n    \"httpserver\",\n    \"httputil\",\n    \"ioloop\",\n    \"iostream\",\n    \"locale\",\n    \"locks\",\n    \"log\",\n    \"netutil\",\n    \"options\",\n    \"platform\",\n    \"process\",\n    \"queues\",\n    \"routing\",\n    \"simple_httpclient\",\n    \"tcpclient\",\n    \"tcpserver\",\n    \"template\",\n    \"testing\",\n    \"util\",\n    \"web\",\n]\n\n\n# Copied from https://peps.python.org/pep-0562/\ndef __getattr__(name: str) -> typing.Any:\n    if name in __all__:\n        return importlib.import_module(\".\" + name, __name__)\n    raise AttributeError(f\"module {__name__!r} has no attribute {name!r}\")\n"
  },
  {
    "path": "tornado/__init__.pyi",
    "content": "import typing\n\nversion: str\nversion_info: typing.Tuple[int, int, int, int]\n\nfrom . import (\n    auth,\n    autoreload,\n    concurrent,\n    curl_httpclient,\n    escape,\n    gen,\n    http1connection,\n    httpclient,\n    httpserver,\n    httputil,\n    ioloop,\n    iostream,\n    locale,\n    locks,\n    log,\n    netutil,\n    options,\n    platform,\n    process,\n    queues,\n    routing,\n    simple_httpclient,\n    tcpclient,\n    tcpserver,\n    template,\n    testing,\n    util,\n    web,\n)\n"
  },
  {
    "path": "tornado/_locale_data.py",
    "content": "# Copyright 2012 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Data used by the tornado.locale module.\"\"\"\n\nLOCALE_NAMES = {\n    \"af_ZA\": {\"name_en\": \"Afrikaans\", \"name\": \"Afrikaans\"},\n    \"am_ET\": {\"name_en\": \"Amharic\", \"name\": \"አማርኛ\"},\n    \"ar_AR\": {\"name_en\": \"Arabic\", \"name\": \"العربية\"},\n    \"bg_BG\": {\"name_en\": \"Bulgarian\", \"name\": \"Български\"},\n    \"bn_IN\": {\"name_en\": \"Bengali\", \"name\": \"বাংলা\"},\n    \"bs_BA\": {\"name_en\": \"Bosnian\", \"name\": \"Bosanski\"},\n    \"ca_ES\": {\"name_en\": \"Catalan\", \"name\": \"Català\"},\n    \"cs_CZ\": {\"name_en\": \"Czech\", \"name\": \"Čeština\"},\n    \"cy_GB\": {\"name_en\": \"Welsh\", \"name\": \"Cymraeg\"},\n    \"da_DK\": {\"name_en\": \"Danish\", \"name\": \"Dansk\"},\n    \"de_DE\": {\"name_en\": \"German\", \"name\": \"Deutsch\"},\n    \"el_GR\": {\"name_en\": \"Greek\", \"name\": \"Ελληνικά\"},\n    \"en_GB\": {\"name_en\": \"English (UK)\", \"name\": \"English (UK)\"},\n    \"en_US\": {\"name_en\": \"English (US)\", \"name\": \"English (US)\"},\n    \"es_ES\": {\"name_en\": \"Spanish (Spain)\", \"name\": \"Español (España)\"},\n    \"es_LA\": {\"name_en\": \"Spanish\", \"name\": \"Español\"},\n    \"et_EE\": {\"name_en\": \"Estonian\", \"name\": \"Eesti\"},\n    \"eu_ES\": {\"name_en\": \"Basque\", \"name\": \"Euskara\"},\n    \"fa_IR\": {\"name_en\": \"Persian\", \"name\": \"فارسی\"},\n    \"fi_FI\": {\"name_en\": \"Finnish\", \"name\": \"Suomi\"},\n    \"fr_CA\": {\"name_en\": \"French (Canada)\", \"name\": \"Français (Canada)\"},\n    \"fr_FR\": {\"name_en\": \"French\", \"name\": \"Français\"},\n    \"ga_IE\": {\"name_en\": \"Irish\", \"name\": \"Gaeilge\"},\n    \"gl_ES\": {\"name_en\": \"Galician\", \"name\": \"Galego\"},\n    \"he_IL\": {\"name_en\": \"Hebrew\", \"name\": \"עברית\"},\n    \"hi_IN\": {\"name_en\": \"Hindi\", \"name\": \"हिन्दी\"},\n    \"hr_HR\": {\"name_en\": \"Croatian\", \"name\": \"Hrvatski\"},\n    \"hu_HU\": {\"name_en\": \"Hungarian\", \"name\": \"Magyar\"},\n    \"id_ID\": {\"name_en\": \"Indonesian\", \"name\": \"Bahasa Indonesia\"},\n    \"is_IS\": {\"name_en\": \"Icelandic\", \"name\": \"Íslenska\"},\n    \"it_IT\": {\"name_en\": \"Italian\", \"name\": \"Italiano\"},\n    \"ja_JP\": {\"name_en\": \"Japanese\", \"name\": \"日本語\"},\n    \"ko_KR\": {\"name_en\": \"Korean\", \"name\": \"한국어\"},\n    \"lt_LT\": {\"name_en\": \"Lithuanian\", \"name\": \"Lietuvių\"},\n    \"lv_LV\": {\"name_en\": \"Latvian\", \"name\": \"Latviešu\"},\n    \"mk_MK\": {\"name_en\": \"Macedonian\", \"name\": \"Македонски\"},\n    \"ml_IN\": {\"name_en\": \"Malayalam\", \"name\": \"മലയാളം\"},\n    \"ms_MY\": {\"name_en\": \"Malay\", \"name\": \"Bahasa Melayu\"},\n    \"nb_NO\": {\"name_en\": \"Norwegian (bokmal)\", \"name\": \"Norsk (bokmål)\"},\n    \"nl_NL\": {\"name_en\": \"Dutch\", \"name\": \"Nederlands\"},\n    \"nn_NO\": {\"name_en\": \"Norwegian (nynorsk)\", \"name\": \"Norsk (nynorsk)\"},\n    \"pa_IN\": {\"name_en\": \"Punjabi\", \"name\": \"ਪੰਜਾਬੀ\"},\n    \"pl_PL\": {\"name_en\": \"Polish\", \"name\": \"Polski\"},\n    \"pt_BR\": {\"name_en\": \"Portuguese (Brazil)\", \"name\": \"Português (Brasil)\"},\n    \"pt_PT\": {\"name_en\": \"Portuguese (Portugal)\", \"name\": \"Português (Portugal)\"},\n    \"ro_RO\": {\"name_en\": \"Romanian\", \"name\": \"Română\"},\n    \"ru_RU\": {\"name_en\": \"Russian\", \"name\": \"Русский\"},\n    \"sk_SK\": {\"name_en\": \"Slovak\", \"name\": \"Slovenčina\"},\n    \"sl_SI\": {\"name_en\": \"Slovenian\", \"name\": \"Slovenščina\"},\n    \"sq_AL\": {\"name_en\": \"Albanian\", \"name\": \"Shqip\"},\n    \"sr_RS\": {\"name_en\": \"Serbian\", \"name\": \"Српски\"},\n    \"sv_SE\": {\"name_en\": \"Swedish\", \"name\": \"Svenska\"},\n    \"sw_KE\": {\"name_en\": \"Swahili\", \"name\": \"Kiswahili\"},\n    \"ta_IN\": {\"name_en\": \"Tamil\", \"name\": \"தமிழ்\"},\n    \"te_IN\": {\"name_en\": \"Telugu\", \"name\": \"తెలుగు\"},\n    \"th_TH\": {\"name_en\": \"Thai\", \"name\": \"ภาษาไทย\"},\n    \"tl_PH\": {\"name_en\": \"Filipino\", \"name\": \"Filipino\"},\n    \"tr_TR\": {\"name_en\": \"Turkish\", \"name\": \"Türkçe\"},\n    \"uk_UA\": {\"name_en\": \"Ukraini \", \"name\": \"Українська\"},\n    \"vi_VN\": {\"name_en\": \"Vietnamese\", \"name\": \"Tiếng Việt\"},\n    \"zh_CN\": {\"name_en\": \"Chinese (Simplified)\", \"name\": \"中文(简体)\"},\n    \"zh_TW\": {\"name_en\": \"Chinese (Traditional)\", \"name\": \"中文(繁體)\"},\n}\n"
  },
  {
    "path": "tornado/auth.py",
    "content": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"This module contains implementations of various third-party\nauthentication schemes.\n\nAll the classes in this file are class mixins designed to be used with\nthe `tornado.web.RequestHandler` class.  They are used in two ways:\n\n* On a login handler, use methods such as ``authenticate_redirect()``,\n  ``authorize_redirect()``, and ``get_authenticated_user()`` to\n  establish the user's identity and store authentication tokens to your\n  database and/or cookies.\n* In non-login handlers, use methods such as ``facebook_request()``\n  or ``twitter_request()`` to use the authentication tokens to make\n  requests to the respective services.\n\nThey all take slightly different arguments due to the fact all these\nservices implement authentication and authorization slightly differently.\nSee the individual service classes below for complete documentation.\n\nExample usage for Google OAuth:\n\n.. testsetup::\n\n    import urllib\n\n.. testcode::\n\n    class GoogleOAuth2LoginHandler(tornado.web.RequestHandler,\n                                    tornado.auth.GoogleOAuth2Mixin):\n        async def get(self):\n            # Google requires an exact match for redirect_uri, so it's\n            # best to get it from your app configuration instead of from\n            # self.request.full_uri().\n            redirect_uri = urllib.parse.urljoin(self.application.settings['redirect_base_uri'],\n                self.reverse_url('google_oauth'))\n            async def get(self):\n                if self.get_argument('code', False):\n                    access = await self.get_authenticated_user(\n                        redirect_uri=redirect_uri,\n                        code=self.get_argument('code'))\n                    user = await self.oauth2_request(\n                        \"https://www.googleapis.com/oauth2/v1/userinfo\",\n                        access_token=access[\"access_token\"])\n                    # Save the user and access token. For example:\n                    user_cookie = dict(id=user[\"id\"], access_token=access[\"access_token\"])\n                    self.set_signed_cookie(\"user\", json.dumps(user_cookie))\n                    self.redirect(\"/\")\n                else:\n                    self.authorize_redirect(\n                        redirect_uri=redirect_uri,\n                        client_id=self.get_google_oauth_settings()['key'],\n                        scope=['profile', 'email'],\n                        response_type='code',\n                        extra_params={'approval_prompt': 'auto'})\n\n\"\"\"\n\nimport base64\nimport binascii\nimport hashlib\nimport hmac\nimport time\nimport urllib.parse\nimport uuid\nimport warnings\nfrom collections.abc import Iterable\nfrom typing import Any, cast\n\nfrom tornado import escape, httpclient\nfrom tornado.httputil import url_concat\nfrom tornado.util import unicode_type\nfrom tornado.web import RequestHandler\n\n\nclass AuthError(Exception):\n    pass\n\n\nclass OpenIdMixin:\n    \"\"\"Abstract implementation of OpenID and Attribute Exchange.\n\n    Class attributes:\n\n    * ``_OPENID_ENDPOINT``: the identity provider's URI.\n    \"\"\"\n\n    def authenticate_redirect(\n        self,\n        callback_uri: str | None = None,\n        ax_attrs: list[str] = [\"name\", \"email\", \"language\", \"username\"],\n    ) -> None:\n        \"\"\"Redirects to the authentication URL for this service.\n\n        After authentication, the service will redirect back to the given\n        callback URI with additional parameters including ``openid.mode``.\n\n        We request the given attributes for the authenticated user by\n        default (name, email, language, and username). If you don't need\n        all those attributes for your app, you can request fewer with\n        the ax_attrs keyword argument.\n\n        .. versionchanged:: 6.0\n\n            The ``callback`` argument was removed and this method no\n            longer returns an awaitable object. It is now an ordinary\n            synchronous function.\n        \"\"\"\n        handler = cast(RequestHandler, self)\n        callback_uri = callback_uri or handler.request.uri\n        assert callback_uri is not None\n        args = self._openid_args(callback_uri, ax_attrs=ax_attrs)\n        endpoint = self._OPENID_ENDPOINT  # type: ignore\n        handler.redirect(endpoint + \"?\" + urllib.parse.urlencode(args))\n\n    async def get_authenticated_user(\n        self, http_client: httpclient.AsyncHTTPClient | None = None\n    ) -> dict[str, Any]:\n        \"\"\"Fetches the authenticated user data upon redirect.\n\n        This method should be called by the handler that receives the\n        redirect from the `authenticate_redirect()` method (which is\n        often the same as the one that calls it; in that case you would\n        call `get_authenticated_user` if the ``openid.mode`` parameter\n        is present and `authenticate_redirect` if it is not).\n\n        The result of this method will generally be used to set a cookie.\n\n        .. versionchanged:: 6.0\n\n            The ``callback`` argument was removed. Use the returned\n            awaitable object instead.\n        \"\"\"\n        handler = cast(RequestHandler, self)\n        # Verify the OpenID response via direct request to the OP\n        args: dict[str, str | bytes] = {\n            k: v[-1] for k, v in handler.request.arguments.items()\n        }\n        args[\"openid.mode\"] = \"check_authentication\"\n        url = self._OPENID_ENDPOINT  # type: ignore\n        if http_client is None:\n            http_client = self.get_auth_http_client()\n        resp = await http_client.fetch(\n            url, method=\"POST\", body=urllib.parse.urlencode(args)\n        )\n        return self._on_authentication_verified(resp)\n\n    def _openid_args(\n        self,\n        callback_uri: str,\n        ax_attrs: Iterable[str] = [],\n        oauth_scope: str | None = None,\n    ) -> dict[str, str]:\n        handler = cast(RequestHandler, self)\n        url = urllib.parse.urljoin(handler.request.full_url(), callback_uri)\n        args = {\n            \"openid.ns\": \"http://specs.openid.net/auth/2.0\",\n            \"openid.claimed_id\": \"http://specs.openid.net/auth/2.0/identifier_select\",\n            \"openid.identity\": \"http://specs.openid.net/auth/2.0/identifier_select\",\n            \"openid.return_to\": url,\n            \"openid.realm\": urllib.parse.urljoin(url, \"/\"),\n            \"openid.mode\": \"checkid_setup\",\n        }\n        if ax_attrs:\n            args.update(\n                {\n                    \"openid.ns.ax\": \"http://openid.net/srv/ax/1.0\",\n                    \"openid.ax.mode\": \"fetch_request\",\n                }\n            )\n            ax_attrs = set(ax_attrs)\n            required: list[str] = []\n            if \"name\" in ax_attrs:\n                ax_attrs -= {\"name\", \"firstname\", \"fullname\", \"lastname\"}\n                required += [\"firstname\", \"fullname\", \"lastname\"]\n                args.update(\n                    {\n                        \"openid.ax.type.firstname\": \"http://axschema.org/namePerson/first\",\n                        \"openid.ax.type.fullname\": \"http://axschema.org/namePerson\",\n                        \"openid.ax.type.lastname\": \"http://axschema.org/namePerson/last\",\n                    }\n                )\n            known_attrs = {\n                \"email\": \"http://axschema.org/contact/email\",\n                \"language\": \"http://axschema.org/pref/language\",\n                \"username\": \"http://axschema.org/namePerson/friendly\",\n            }\n            for name in ax_attrs:\n                args[\"openid.ax.type.\" + name] = known_attrs[name]\n                required.append(name)\n            args[\"openid.ax.required\"] = \",\".join(required)\n        if oauth_scope:\n            args.update(\n                {\n                    \"openid.ns.oauth\": \"http://specs.openid.net/extensions/oauth/1.0\",\n                    \"openid.oauth.consumer\": handler.request.host.split(\":\")[0],\n                    \"openid.oauth.scope\": oauth_scope,\n                }\n            )\n        return args\n\n    def _on_authentication_verified(\n        self, response: httpclient.HTTPResponse\n    ) -> dict[str, Any]:\n        handler = cast(RequestHandler, self)\n        if b\"is_valid:true\" not in response.body:\n            raise AuthError(\"Invalid OpenID response: %r\" % response.body)\n\n        # Make sure we got back at least an email from attribute exchange\n        ax_ns = None\n        for key in handler.request.arguments:\n            if (\n                key.startswith(\"openid.ns.\")\n                and handler.get_argument(key) == \"http://openid.net/srv/ax/1.0\"\n            ):\n                ax_ns = key[10:]\n                break\n\n        def get_ax_arg(uri: str) -> str:\n            if not ax_ns:\n                return \"\"\n            prefix = \"openid.\" + ax_ns + \".type.\"\n            ax_name = None\n            for name in handler.request.arguments.keys():\n                if handler.get_argument(name) == uri and name.startswith(prefix):\n                    part = name[len(prefix) :]\n                    ax_name = \"openid.\" + ax_ns + \".value.\" + part\n                    break\n            if not ax_name:\n                return \"\"\n            return handler.get_argument(ax_name, \"\")\n\n        email = get_ax_arg(\"http://axschema.org/contact/email\")\n        name = get_ax_arg(\"http://axschema.org/namePerson\")\n        first_name = get_ax_arg(\"http://axschema.org/namePerson/first\")\n        last_name = get_ax_arg(\"http://axschema.org/namePerson/last\")\n        username = get_ax_arg(\"http://axschema.org/namePerson/friendly\")\n        locale = get_ax_arg(\"http://axschema.org/pref/language\").lower()\n        user = dict()\n        name_parts = []\n        if first_name:\n            user[\"first_name\"] = first_name\n            name_parts.append(first_name)\n        if last_name:\n            user[\"last_name\"] = last_name\n            name_parts.append(last_name)\n        if name:\n            user[\"name\"] = name\n        elif name_parts:\n            user[\"name\"] = \" \".join(name_parts)\n        elif email:\n            user[\"name\"] = email.split(\"@\")[0]\n        if email:\n            user[\"email\"] = email\n        if locale:\n            user[\"locale\"] = locale\n        if username:\n            user[\"username\"] = username\n        claimed_id = handler.get_argument(\"openid.claimed_id\", None)\n        if claimed_id:\n            user[\"claimed_id\"] = claimed_id\n        return user\n\n    def get_auth_http_client(self) -> httpclient.AsyncHTTPClient:\n        \"\"\"Returns the `.AsyncHTTPClient` instance to be used for auth requests.\n\n        May be overridden by subclasses to use an HTTP client other than\n        the default.\n        \"\"\"\n        return httpclient.AsyncHTTPClient()\n\n\nclass OAuthMixin:\n    \"\"\"Abstract implementation of OAuth 1.0 and 1.0a.\n\n    See `TwitterMixin` below for an example implementation.\n\n    Class attributes:\n\n    * ``_OAUTH_AUTHORIZE_URL``: The service's OAuth authorization url.\n    * ``_OAUTH_ACCESS_TOKEN_URL``: The service's OAuth access token url.\n    * ``_OAUTH_VERSION``: May be either \"1.0\" or \"1.0a\".\n    * ``_OAUTH_NO_CALLBACKS``: Set this to True if the service requires\n      advance registration of callbacks.\n\n    Subclasses must also override the `_oauth_get_user_future` and\n    `_oauth_consumer_token` methods.\n    \"\"\"\n\n    async def authorize_redirect(\n        self,\n        callback_uri: str | None = None,\n        extra_params: dict[str, Any] | None = None,\n        http_client: httpclient.AsyncHTTPClient | None = None,\n    ) -> None:\n        \"\"\"Redirects the user to obtain OAuth authorization for this service.\n\n        The ``callback_uri`` may be omitted if you have previously\n        registered a callback URI with the third-party service. For\n        some services, you must use a previously-registered callback\n        URI and cannot specify a callback via this method.\n\n        This method sets a cookie called ``_oauth_request_token`` which is\n        subsequently used (and cleared) in `get_authenticated_user` for\n        security purposes.\n\n        This method is asynchronous and must be called with ``await``\n        or ``yield`` (This is different from other ``auth*_redirect``\n        methods defined in this module). It calls\n        `.RequestHandler.finish` for you so you should not write any\n        other response after it returns.\n\n        .. versionchanged:: 3.1\n           Now returns a `.Future` and takes an optional callback, for\n           compatibility with `.gen.coroutine`.\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed. Use the returned\n           awaitable object instead.\n\n        \"\"\"\n        if callback_uri and getattr(self, \"_OAUTH_NO_CALLBACKS\", False):\n            raise Exception(\"This service does not support oauth_callback\")\n        if http_client is None:\n            http_client = self.get_auth_http_client()\n        assert http_client is not None\n        if getattr(self, \"_OAUTH_VERSION\", \"1.0a\") == \"1.0a\":\n            response = await http_client.fetch(\n                self._oauth_request_token_url(\n                    callback_uri=callback_uri, extra_params=extra_params\n                )\n            )\n        else:\n            response = await http_client.fetch(self._oauth_request_token_url())\n        url = self._OAUTH_AUTHORIZE_URL  # type: ignore\n        self._on_request_token(url, callback_uri, response)\n\n    async def get_authenticated_user(\n        self, http_client: httpclient.AsyncHTTPClient | None = None\n    ) -> dict[str, Any]:\n        \"\"\"Gets the OAuth authorized user and access token.\n\n        This method should be called from the handler for your\n        OAuth callback URL to complete the registration process. We run the\n        callback with the authenticated user dictionary.  This dictionary\n        will contain an ``access_key`` which can be used to make authorized\n        requests to this service on behalf of the user.  The dictionary will\n        also contain other fields such as ``name``, depending on the service\n        used.\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed. Use the returned\n           awaitable object instead.\n        \"\"\"\n        handler = cast(RequestHandler, self)\n        request_key = escape.utf8(handler.get_argument(\"oauth_token\"))\n        oauth_verifier = handler.get_argument(\"oauth_verifier\", None)\n        request_cookie = handler.get_cookie(\"_oauth_request_token\")\n        if not request_cookie:\n            raise AuthError(\"Missing OAuth request token cookie\")\n        handler.clear_cookie(\"_oauth_request_token\")\n        cookie_key, cookie_secret = (\n            base64.b64decode(escape.utf8(i)) for i in request_cookie.split(\"|\")\n        )\n        if cookie_key != request_key:\n            raise AuthError(\"Request token does not match cookie\")\n        token: dict[str, str | bytes] = dict(key=cookie_key, secret=cookie_secret)\n        if oauth_verifier:\n            token[\"verifier\"] = oauth_verifier\n        if http_client is None:\n            http_client = self.get_auth_http_client()\n        assert http_client is not None\n        response = await http_client.fetch(self._oauth_access_token_url(token))\n        access_token = _oauth_parse_response(response.body)\n        user = await self._oauth_get_user_future(access_token)\n        if not user:\n            raise AuthError(\"Error getting user\")\n        user[\"access_token\"] = access_token\n        return user\n\n    def _oauth_request_token_url(\n        self,\n        callback_uri: str | None = None,\n        extra_params: dict[str, Any] | None = None,\n    ) -> str:\n        handler = cast(RequestHandler, self)\n        consumer_token = self._oauth_consumer_token()\n        url = self._OAUTH_REQUEST_TOKEN_URL  # type: ignore\n        args = dict(\n            oauth_consumer_key=escape.to_basestring(consumer_token[\"key\"]),\n            oauth_signature_method=\"HMAC-SHA1\",\n            oauth_timestamp=str(int(time.time())),\n            oauth_nonce=escape.to_basestring(binascii.b2a_hex(uuid.uuid4().bytes)),\n            oauth_version=\"1.0\",\n        )\n        if getattr(self, \"_OAUTH_VERSION\", \"1.0a\") == \"1.0a\":\n            if callback_uri == \"oob\":\n                args[\"oauth_callback\"] = \"oob\"\n            elif callback_uri:\n                args[\"oauth_callback\"] = urllib.parse.urljoin(\n                    handler.request.full_url(), callback_uri\n                )\n            if extra_params:\n                args.update(extra_params)\n            signature = _oauth10a_signature(consumer_token, \"GET\", url, args)\n        else:\n            signature = _oauth_signature(consumer_token, \"GET\", url, args)\n\n        args[\"oauth_signature\"] = signature\n        return url + \"?\" + urllib.parse.urlencode(args)\n\n    def _on_request_token(\n        self,\n        authorize_url: str,\n        callback_uri: str | None,\n        response: httpclient.HTTPResponse,\n    ) -> None:\n        handler = cast(RequestHandler, self)\n        request_token = _oauth_parse_response(response.body)\n        data = (\n            base64.b64encode(escape.utf8(request_token[\"key\"]))\n            + b\"|\"\n            + base64.b64encode(escape.utf8(request_token[\"secret\"]))\n        )\n        handler.set_cookie(\"_oauth_request_token\", data)\n        args = dict(oauth_token=request_token[\"key\"])\n        if callback_uri == \"oob\":\n            handler.finish(authorize_url + \"?\" + urllib.parse.urlencode(args))\n            return\n        elif callback_uri:\n            args[\"oauth_callback\"] = urllib.parse.urljoin(\n                handler.request.full_url(), callback_uri\n            )\n        handler.redirect(authorize_url + \"?\" + urllib.parse.urlencode(args))\n\n    def _oauth_access_token_url(self, request_token: dict[str, Any]) -> str:\n        consumer_token = self._oauth_consumer_token()\n        url = self._OAUTH_ACCESS_TOKEN_URL  # type: ignore\n        args = dict(\n            oauth_consumer_key=escape.to_basestring(consumer_token[\"key\"]),\n            oauth_token=escape.to_basestring(request_token[\"key\"]),\n            oauth_signature_method=\"HMAC-SHA1\",\n            oauth_timestamp=str(int(time.time())),\n            oauth_nonce=escape.to_basestring(binascii.b2a_hex(uuid.uuid4().bytes)),\n            oauth_version=\"1.0\",\n        )\n        if \"verifier\" in request_token:\n            args[\"oauth_verifier\"] = request_token[\"verifier\"]\n\n        if getattr(self, \"_OAUTH_VERSION\", \"1.0a\") == \"1.0a\":\n            signature = _oauth10a_signature(\n                consumer_token, \"GET\", url, args, request_token\n            )\n        else:\n            signature = _oauth_signature(\n                consumer_token, \"GET\", url, args, request_token\n            )\n\n        args[\"oauth_signature\"] = signature\n        return url + \"?\" + urllib.parse.urlencode(args)\n\n    def _oauth_consumer_token(self) -> dict[str, Any]:\n        \"\"\"Subclasses must override this to return their OAuth consumer keys.\n\n        The return value should be a `dict` with keys ``key`` and ``secret``.\n        \"\"\"\n        raise NotImplementedError()\n\n    async def _oauth_get_user_future(\n        self, access_token: dict[str, Any]\n    ) -> dict[str, Any]:\n        \"\"\"Subclasses must override this to get basic information about the\n        user.\n\n        Should be a coroutine whose result is a dictionary\n        containing information about the user, which may have been\n        retrieved by using ``access_token`` to make a request to the\n        service.\n\n        The access token will be added to the returned dictionary to make\n        the result of `get_authenticated_user`.\n\n        .. versionchanged:: 5.1\n\n           Subclasses may also define this method with ``async def``.\n\n        .. versionchanged:: 6.0\n\n           A synchronous fallback to ``_oauth_get_user`` was removed.\n        \"\"\"\n        raise NotImplementedError()\n\n    def _oauth_request_parameters(\n        self,\n        url: str,\n        access_token: dict[str, Any],\n        parameters: dict[str, Any] = {},\n        method: str = \"GET\",\n    ) -> dict[str, Any]:\n        \"\"\"Returns the OAuth parameters as a dict for the given request.\n\n        parameters should include all POST arguments and query string arguments\n        that will be sent with the request.\n        \"\"\"\n        consumer_token = self._oauth_consumer_token()\n        base_args = dict(\n            oauth_consumer_key=escape.to_basestring(consumer_token[\"key\"]),\n            oauth_token=escape.to_basestring(access_token[\"key\"]),\n            oauth_signature_method=\"HMAC-SHA1\",\n            oauth_timestamp=str(int(time.time())),\n            oauth_nonce=escape.to_basestring(binascii.b2a_hex(uuid.uuid4().bytes)),\n            oauth_version=\"1.0\",\n        )\n        args = {}\n        args.update(base_args)\n        args.update(parameters)\n        if getattr(self, \"_OAUTH_VERSION\", \"1.0a\") == \"1.0a\":\n            signature = _oauth10a_signature(\n                consumer_token, method, url, args, access_token\n            )\n        else:\n            signature = _oauth_signature(\n                consumer_token, method, url, args, access_token\n            )\n        base_args[\"oauth_signature\"] = escape.to_basestring(signature)\n        return base_args\n\n    def get_auth_http_client(self) -> httpclient.AsyncHTTPClient:\n        \"\"\"Returns the `.AsyncHTTPClient` instance to be used for auth requests.\n\n        May be overridden by subclasses to use an HTTP client other than\n        the default.\n        \"\"\"\n        return httpclient.AsyncHTTPClient()\n\n\nclass OAuth2Mixin:\n    \"\"\"Abstract implementation of OAuth 2.0.\n\n    See `FacebookGraphMixin` or `GoogleOAuth2Mixin` below for example\n    implementations.\n\n    Class attributes:\n\n    * ``_OAUTH_AUTHORIZE_URL``: The service's authorization url.\n    * ``_OAUTH_ACCESS_TOKEN_URL``:  The service's access token url.\n    \"\"\"\n\n    def authorize_redirect(\n        self,\n        redirect_uri: str | None = None,\n        client_id: str | None = None,\n        client_secret: str | None = None,\n        extra_params: dict[str, Any] | None = None,\n        scope: list[str] | None = None,\n        response_type: str = \"code\",\n    ) -> None:\n        \"\"\"Redirects the user to obtain OAuth authorization for this service.\n\n        Some providers require that you register a redirect URL with\n        your application instead of passing one via this method. You\n        should call this method to log the user in, and then call\n        ``get_authenticated_user`` in the handler for your\n        redirect URL to complete the authorization process.\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument and returned awaitable were removed;\n           this is now an ordinary synchronous function.\n\n        .. deprecated:: 6.4\n           The ``client_secret`` argument (which has never had any effect)\n           is deprecated and will be removed in Tornado 7.0.\n        \"\"\"\n        if client_secret is not None:\n            warnings.warn(\"client_secret argument is deprecated\", DeprecationWarning)\n        handler = cast(RequestHandler, self)\n        args = {\"response_type\": response_type}\n        if redirect_uri is not None:\n            args[\"redirect_uri\"] = redirect_uri\n        if client_id is not None:\n            args[\"client_id\"] = client_id\n        if extra_params:\n            args.update(extra_params)\n        if scope:\n            args[\"scope\"] = \" \".join(scope)\n        url = self._OAUTH_AUTHORIZE_URL  # type: ignore\n        handler.redirect(url_concat(url, args))\n\n    def _oauth_request_token_url(\n        self,\n        redirect_uri: str | None = None,\n        client_id: str | None = None,\n        client_secret: str | None = None,\n        code: str | None = None,\n        extra_params: dict[str, Any] | None = None,\n    ) -> str:\n        url = self._OAUTH_ACCESS_TOKEN_URL  # type: ignore\n        args: dict[str, str] = {}\n        if redirect_uri is not None:\n            args[\"redirect_uri\"] = redirect_uri\n        if code is not None:\n            args[\"code\"] = code\n        if client_id is not None:\n            args[\"client_id\"] = client_id\n        if client_secret is not None:\n            args[\"client_secret\"] = client_secret\n        if extra_params:\n            args.update(extra_params)\n        return url_concat(url, args)\n\n    async def oauth2_request(\n        self,\n        url: str,\n        access_token: str | None = None,\n        post_args: dict[str, Any] | None = None,\n        **args: Any,\n    ) -> Any:\n        \"\"\"Fetches the given URL auth an OAuth2 access token.\n\n        If the request is a POST, ``post_args`` should be provided. Query\n        string arguments should be given as keyword arguments.\n\n        Example usage:\n\n        ..testcode::\n\n            class MainHandler(tornado.web.RequestHandler,\n                              tornado.auth.FacebookGraphMixin):\n                @tornado.web.authenticated\n                async def get(self):\n                    new_entry = await self.oauth2_request(\n                        \"https://graph.facebook.com/me/feed\",\n                        post_args={\"message\": \"I am posting from my Tornado application!\"},\n                        access_token=self.current_user[\"access_token\"])\n\n                    if not new_entry:\n                        # Call failed; perhaps missing permission?\n                        self.authorize_redirect()\n                        return\n                    self.finish(\"Posted a message!\")\n\n        .. versionadded:: 4.3\n\n        .. versionchanged::: 6.0\n\n           The ``callback`` argument was removed. Use the returned awaitable object instead.\n        \"\"\"\n        all_args = {}\n        if access_token:\n            all_args[\"access_token\"] = access_token\n            all_args.update(args)\n\n        if all_args:\n            url += \"?\" + urllib.parse.urlencode(all_args)\n        http = self.get_auth_http_client()\n        if post_args is not None:\n            response = await http.fetch(\n                url, method=\"POST\", body=urllib.parse.urlencode(post_args)\n            )\n        else:\n            response = await http.fetch(url)\n        return escape.json_decode(response.body)\n\n    def get_auth_http_client(self) -> httpclient.AsyncHTTPClient:\n        \"\"\"Returns the `.AsyncHTTPClient` instance to be used for auth requests.\n\n        May be overridden by subclasses to use an HTTP client other than\n        the default.\n\n        .. versionadded:: 4.3\n        \"\"\"\n        return httpclient.AsyncHTTPClient()\n\n\nclass TwitterMixin(OAuthMixin):\n    \"\"\"Twitter OAuth authentication.\n\n    To authenticate with Twitter, register your application with\n    Twitter at http://twitter.com/apps. Then copy your Consumer Key\n    and Consumer Secret to the application\n    `~tornado.web.Application.settings` ``twitter_consumer_key`` and\n    ``twitter_consumer_secret``. Use this mixin on the handler for the\n    URL you registered as your application's callback URL.\n\n    When your application is set up, you can use this mixin like this\n    to authenticate the user with Twitter and get access to their stream:\n\n    .. testcode::\n\n        class TwitterLoginHandler(tornado.web.RequestHandler,\n                                  tornado.auth.TwitterMixin):\n            async def get(self):\n                if self.get_argument(\"oauth_token\", None):\n                    user = await self.get_authenticated_user()\n                    # Save the user using e.g. set_signed_cookie()\n                else:\n                    await self.authorize_redirect()\n\n    The user object returned by `~OAuthMixin.get_authenticated_user`\n    includes the attributes ``username``, ``name``, ``access_token``,\n    and all of the custom Twitter user attributes described at\n    https://dev.twitter.com/docs/api/1.1/get/users/show\n\n    .. deprecated:: 6.3\n       This class refers to version 1.1 of the Twitter API, which has been\n       deprecated by Twitter. Since Twitter has begun to limit access to its\n       API, this class will no longer be updated and will be removed in the\n       future.\n    \"\"\"\n\n    _OAUTH_REQUEST_TOKEN_URL = \"https://api.twitter.com/oauth/request_token\"\n    _OAUTH_ACCESS_TOKEN_URL = \"https://api.twitter.com/oauth/access_token\"\n    _OAUTH_AUTHORIZE_URL = \"https://api.twitter.com/oauth/authorize\"\n    _OAUTH_AUTHENTICATE_URL = \"https://api.twitter.com/oauth/authenticate\"\n    _OAUTH_NO_CALLBACKS = False\n    _TWITTER_BASE_URL = \"https://api.twitter.com/1.1\"\n\n    async def authenticate_redirect(self, callback_uri: str | None = None) -> None:\n        \"\"\"Just like `~OAuthMixin.authorize_redirect`, but\n        auto-redirects if authorized.\n\n        This is generally the right interface to use if you are using\n        Twitter for single-sign on.\n\n        .. versionchanged:: 3.1\n           Now returns a `.Future` and takes an optional callback, for\n           compatibility with `.gen.coroutine`.\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed. Use the returned\n           awaitable object instead.\n        \"\"\"\n        http = self.get_auth_http_client()\n        response = await http.fetch(\n            self._oauth_request_token_url(callback_uri=callback_uri)\n        )\n        self._on_request_token(self._OAUTH_AUTHENTICATE_URL, None, response)\n\n    async def twitter_request(\n        self,\n        path: str,\n        access_token: dict[str, Any],\n        post_args: dict[str, Any] | None = None,\n        **args: Any,\n    ) -> Any:\n        \"\"\"Fetches the given API path, e.g., ``statuses/user_timeline/btaylor``\n\n        The path should not include the format or API version number.\n        (we automatically use JSON format and API version 1).\n\n        If the request is a POST, ``post_args`` should be provided. Query\n        string arguments should be given as keyword arguments.\n\n        All the Twitter methods are documented at http://dev.twitter.com/\n\n        Many methods require an OAuth access token which you can\n        obtain through `~OAuthMixin.authorize_redirect` and\n        `~OAuthMixin.get_authenticated_user`. The user returned through that\n        process includes an 'access_token' attribute that can be used\n        to make authenticated requests via this method. Example\n        usage:\n\n        .. testcode::\n\n            class MainHandler(tornado.web.RequestHandler,\n                              tornado.auth.TwitterMixin):\n                @tornado.web.authenticated\n                async def get(self):\n                    new_entry = await self.twitter_request(\n                        \"/statuses/update\",\n                        post_args={\"status\": \"Testing Tornado Web Server\"},\n                        access_token=self.current_user[\"access_token\"])\n                    if not new_entry:\n                        # Call failed; perhaps missing permission?\n                        await self.authorize_redirect()\n                        return\n                    self.finish(\"Posted a message!\")\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed. Use the returned\n           awaitable object instead.\n        \"\"\"\n        if path.startswith(\"http:\") or path.startswith(\"https:\"):\n            # Raw urls are useful for e.g. search which doesn't follow the\n            # usual pattern: http://search.twitter.com/search.json\n            url = path\n        else:\n            url = self._TWITTER_BASE_URL + path + \".json\"\n        # Add the OAuth resource request signature if we have credentials\n        if access_token:\n            all_args = {}\n            all_args.update(args)\n            all_args.update(post_args or {})\n            method = \"POST\" if post_args is not None else \"GET\"\n            oauth = self._oauth_request_parameters(\n                url, access_token, all_args, method=method\n            )\n            args.update(oauth)\n        if args:\n            url += \"?\" + urllib.parse.urlencode(args)\n        http = self.get_auth_http_client()\n        if post_args is not None:\n            response = await http.fetch(\n                url, method=\"POST\", body=urllib.parse.urlencode(post_args)\n            )\n        else:\n            response = await http.fetch(url)\n        return escape.json_decode(response.body)\n\n    def _oauth_consumer_token(self) -> dict[str, Any]:\n        handler = cast(RequestHandler, self)\n        handler.require_setting(\"twitter_consumer_key\", \"Twitter OAuth\")\n        handler.require_setting(\"twitter_consumer_secret\", \"Twitter OAuth\")\n        return dict(\n            key=handler.settings[\"twitter_consumer_key\"],\n            secret=handler.settings[\"twitter_consumer_secret\"],\n        )\n\n    async def _oauth_get_user_future(\n        self, access_token: dict[str, Any]\n    ) -> dict[str, Any]:\n        user = await self.twitter_request(\n            \"/account/verify_credentials\", access_token=access_token\n        )\n        if user:\n            user[\"username\"] = user[\"screen_name\"]\n        return user\n\n\nclass GoogleOAuth2Mixin(OAuth2Mixin):\n    \"\"\"Google authentication using OAuth2.\n\n    In order to use, register your application with Google and copy the\n    relevant parameters to your application settings.\n\n    * Go to the Google Dev Console at http://console.developers.google.com\n    * Select a project, or create a new one.\n    * Depending on permissions required, you may need to set your app to\n      \"testing\" mode and add your account as a test user, or go through\n      a verfication process. You may also need to use the \"Enable\n      APIs and Services\" command to enable specific services.\n    * In the sidebar on the left, select Credentials.\n    * Click CREATE CREDENTIALS and click OAuth client ID.\n    * Under Application type, select Web application.\n    * Name OAuth 2.0 client and click Create.\n    * Copy the \"Client secret\" and \"Client ID\" to the application settings as\n      ``{\"google_oauth\": {\"key\": CLIENT_ID, \"secret\": CLIENT_SECRET}}``\n    * You must register the ``redirect_uri`` you plan to use with this class\n      on the Credentials page.\n\n    .. versionadded:: 3.2\n    \"\"\"\n\n    _OAUTH_AUTHORIZE_URL = \"https://accounts.google.com/o/oauth2/v2/auth\"\n    _OAUTH_ACCESS_TOKEN_URL = \"https://www.googleapis.com/oauth2/v4/token\"\n    _OAUTH_USERINFO_URL = \"https://www.googleapis.com/oauth2/v1/userinfo\"\n    _OAUTH_NO_CALLBACKS = False\n    _OAUTH_SETTINGS_KEY = \"google_oauth\"\n\n    def get_google_oauth_settings(self) -> dict[str, str]:\n        \"\"\"Return the Google OAuth 2.0 credentials that you created with\n        [Google Cloud\n        Platform](https://console.cloud.google.com/apis/credentials). The dict\n        format is::\n\n            {\n                \"key\": \"your_client_id\", \"secret\": \"your_client_secret\"\n            }\n\n        If your credentials are stored differently (e.g. in a db) you can\n        override this method for custom provision.\n        \"\"\"\n        handler = cast(RequestHandler, self)\n        return handler.settings[self._OAUTH_SETTINGS_KEY]\n\n    async def get_authenticated_user(\n        self,\n        redirect_uri: str,\n        code: str,\n        client_id: str | None = None,\n        client_secret: str | None = None,\n    ) -> dict[str, Any]:\n        \"\"\"Handles the login for the Google user, returning an access token.\n\n        The result is a dictionary containing an ``access_token`` field\n        ([among others](https://developers.google.com/identity/protocols/OAuth2WebServer#handlingtheresponse)).\n        Unlike other ``get_authenticated_user`` methods in this package,\n        this method does not return any additional information about the user.\n        The returned access token can be used with `OAuth2Mixin.oauth2_request`\n        to request additional information (perhaps from\n        ``https://www.googleapis.com/oauth2/v2/userinfo``)\n\n        Example usage:\n\n        .. testsetup::\n\n            import urllib\n\n        .. testcode::\n\n            class GoogleOAuth2LoginHandler(tornado.web.RequestHandler,\n                                           tornado.auth.GoogleOAuth2Mixin):\n                async def get(self):\n                    # Google requires an exact match for redirect_uri, so it's\n                    # best to get it from your app configuration instead of from\n                    # self.request.full_uri().\n                    redirect_uri = urllib.parse.urljoin(self.application.settings['redirect_base_uri'],\n                        self.reverse_url('google_oauth'))\n                    async def get(self):\n                        if self.get_argument('code', False):\n                            access = await self.get_authenticated_user(\n                                redirect_uri=redirect_uri,\n                                code=self.get_argument('code'))\n                            user = await self.oauth2_request(\n                                \"https://www.googleapis.com/oauth2/v1/userinfo\",\n                                access_token=access[\"access_token\"])\n                            # Save the user and access token. For example:\n                            user_cookie = dict(id=user[\"id\"], access_token=access[\"access_token\"])\n                            self.set_signed_cookie(\"user\", json.dumps(user_cookie))\n                            self.redirect(\"/\")\n                        else:\n                            self.authorize_redirect(\n                                redirect_uri=redirect_uri,\n                                client_id=self.get_google_oauth_settings()['key'],\n                                scope=['profile', 'email'],\n                                response_type='code',\n                                extra_params={'approval_prompt': 'auto'})\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed. Use the returned awaitable object instead.\n        \"\"\"  # noqa: E501\n\n        if client_id is None or client_secret is None:\n            settings = self.get_google_oauth_settings()\n            if client_id is None:\n                client_id = settings[\"key\"]\n            if client_secret is None:\n                client_secret = settings[\"secret\"]\n        http = self.get_auth_http_client()\n        body = urllib.parse.urlencode(\n            {\n                \"redirect_uri\": redirect_uri,\n                \"code\": code,\n                \"client_id\": client_id,\n                \"client_secret\": client_secret,\n                \"grant_type\": \"authorization_code\",\n            }\n        )\n\n        response = await http.fetch(\n            self._OAUTH_ACCESS_TOKEN_URL,\n            method=\"POST\",\n            headers={\"Content-Type\": \"application/x-www-form-urlencoded\"},\n            body=body,\n        )\n        return escape.json_decode(response.body)\n\n\nclass FacebookGraphMixin(OAuth2Mixin):\n    \"\"\"Facebook authentication using the new Graph API and OAuth2.\"\"\"\n\n    _OAUTH_ACCESS_TOKEN_URL = \"https://graph.facebook.com/oauth/access_token?\"\n    _OAUTH_AUTHORIZE_URL = \"https://www.facebook.com/dialog/oauth?\"\n    _OAUTH_NO_CALLBACKS = False\n    _FACEBOOK_BASE_URL = \"https://graph.facebook.com\"\n\n    async def get_authenticated_user(\n        self,\n        redirect_uri: str,\n        client_id: str,\n        client_secret: str,\n        code: str,\n        extra_fields: dict[str, Any] | None = None,\n    ) -> dict[str, Any] | None:\n        \"\"\"Handles the login for the Facebook user, returning a user object.\n\n        Example usage:\n\n        .. testcode::\n\n            class FacebookGraphLoginHandler(tornado.web.RequestHandler,\n                                            tornado.auth.FacebookGraphMixin):\n              async def get(self):\n                redirect_uri = urllib.parse.urljoin(\n                    self.application.settings['redirect_base_uri'],\n                    self.reverse_url('facebook_oauth'))\n                if self.get_argument(\"code\", False):\n                    user = await self.get_authenticated_user(\n                        redirect_uri=redirect_uri,\n                        client_id=self.settings[\"facebook_api_key\"],\n                        client_secret=self.settings[\"facebook_secret\"],\n                        code=self.get_argument(\"code\"))\n                    # Save the user with e.g. set_signed_cookie\n                else:\n                    self.authorize_redirect(\n                        redirect_uri=redirect_uri,\n                        client_id=self.settings[\"facebook_api_key\"],\n                        extra_params={\"scope\": \"user_posts\"})\n\n        This method returns a dictionary which may contain the following fields:\n\n        * ``access_token``, a string which may be passed to `facebook_request`\n        * ``session_expires``, an integer encoded as a string representing\n          the time until the access token expires in seconds. This field should\n          be used like ``int(user['session_expires'])``; in a future version of\n          Tornado it will change from a string to an integer.\n        * ``id``, ``name``, ``first_name``, ``last_name``, ``locale``, ``picture``,\n          ``link``, plus any fields named in the ``extra_fields`` argument. These\n          fields are copied from the Facebook graph API\n          `user object <https://developers.facebook.com/docs/graph-api/reference/user>`_\n\n        .. versionchanged:: 4.5\n           The ``session_expires`` field was updated to support changes made to the\n           Facebook API in March 2017.\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed. Use the returned awaitable object instead.\n        \"\"\"\n        http = self.get_auth_http_client()\n        args = {\n            \"redirect_uri\": redirect_uri,\n            \"code\": code,\n            \"client_id\": client_id,\n            \"client_secret\": client_secret,\n        }\n\n        fields = {\"id\", \"name\", \"first_name\", \"last_name\", \"locale\", \"picture\", \"link\"}\n        if extra_fields:\n            fields.update(extra_fields)\n\n        response = await http.fetch(\n            self._oauth_request_token_url(**args)  # type: ignore\n        )\n        args = escape.json_decode(response.body)\n        session = {\n            \"access_token\": args.get(\"access_token\"),\n            \"expires_in\": args.get(\"expires_in\"),\n        }\n        assert session[\"access_token\"] is not None\n\n        user = await self.facebook_request(\n            path=\"/me\",\n            access_token=session[\"access_token\"],\n            appsecret_proof=hmac.new(\n                key=client_secret.encode(\"utf8\"),\n                msg=session[\"access_token\"].encode(\"utf8\"),\n                digestmod=hashlib.sha256,\n            ).hexdigest(),\n            fields=\",\".join(fields),\n        )\n\n        if user is None:\n            return None\n\n        fieldmap = {}\n        for field in fields:\n            fieldmap[field] = user.get(field)\n\n        # session_expires is converted to str for compatibility with\n        # older versions in which the server used url-encoding and\n        # this code simply returned the string verbatim.\n        # This should change in Tornado 5.0.\n        fieldmap.update(\n            {\n                \"access_token\": session[\"access_token\"],\n                \"session_expires\": str(session.get(\"expires_in\")),\n            }\n        )\n        return fieldmap\n\n    async def facebook_request(\n        self,\n        path: str,\n        access_token: str | None = None,\n        post_args: dict[str, Any] | None = None,\n        **args: Any,\n    ) -> Any:\n        \"\"\"Fetches the given relative API path, e.g., \"/btaylor/picture\"\n\n        If the request is a POST, ``post_args`` should be provided. Query\n        string arguments should be given as keyword arguments.\n\n        An introduction to the Facebook Graph API can be found at\n        http://developers.facebook.com/docs/api\n\n        Many methods require an OAuth access token which you can\n        obtain through `~OAuth2Mixin.authorize_redirect` and\n        `get_authenticated_user`. The user returned through that\n        process includes an ``access_token`` attribute that can be\n        used to make authenticated requests via this method.\n\n        Example usage:\n\n        .. testcode::\n\n            class MainHandler(tornado.web.RequestHandler,\n                              tornado.auth.FacebookGraphMixin):\n                @tornado.web.authenticated\n                async def get(self):\n                    new_entry = await self.facebook_request(\n                        \"/me/feed\",\n                        post_args={\"message\": \"I am posting from my Tornado application!\"},\n                        access_token=self.current_user[\"access_token\"])\n\n                    if not new_entry:\n                        # Call failed; perhaps missing permission?\n                        self.authorize_redirect()\n                        return\n                    self.finish(\"Posted a message!\")\n\n        The given path is relative to ``self._FACEBOOK_BASE_URL``,\n        by default \"https://graph.facebook.com\".\n\n        This method is a wrapper around `OAuth2Mixin.oauth2_request`;\n        the only difference is that this method takes a relative path,\n        while ``oauth2_request`` takes a complete url.\n\n        .. versionchanged:: 3.1\n           Added the ability to override ``self._FACEBOOK_BASE_URL``.\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed. Use the returned awaitable object instead.\n        \"\"\"\n        url = self._FACEBOOK_BASE_URL + path\n        return await self.oauth2_request(\n            url, access_token=access_token, post_args=post_args, **args\n        )\n\n\ndef _oauth_signature(\n    consumer_token: dict[str, Any],\n    method: str,\n    url: str,\n    parameters: dict[str, Any] = {},\n    token: dict[str, Any] | None = None,\n) -> bytes:\n    \"\"\"Calculates the HMAC-SHA1 OAuth signature for the given request.\n\n    See http://oauth.net/core/1.0/#signing_process\n    \"\"\"\n    parts = urllib.parse.urlparse(url)\n    scheme, netloc, path = parts[:3]\n    normalized_url = scheme.lower() + \"://\" + netloc.lower() + path\n\n    base_elems = []\n    base_elems.append(method.upper())\n    base_elems.append(normalized_url)\n    base_elems.append(\n        \"&\".join(f\"{k}={_oauth_escape(str(v))}\" for k, v in sorted(parameters.items()))\n    )\n    base_string = \"&\".join(_oauth_escape(e) for e in base_elems)\n\n    key_elems = [escape.utf8(consumer_token[\"secret\"])]\n    key_elems.append(escape.utf8(token[\"secret\"] if token else \"\"))\n    key = b\"&\".join(key_elems)\n\n    hash = hmac.new(key, escape.utf8(base_string), hashlib.sha1)\n    return binascii.b2a_base64(hash.digest())[:-1]\n\n\ndef _oauth10a_signature(\n    consumer_token: dict[str, Any],\n    method: str,\n    url: str,\n    parameters: dict[str, Any] = {},\n    token: dict[str, Any] | None = None,\n) -> bytes:\n    \"\"\"Calculates the HMAC-SHA1 OAuth 1.0a signature for the given request.\n\n    See http://oauth.net/core/1.0a/#signing_process\n    \"\"\"\n    parts = urllib.parse.urlparse(url)\n    scheme, netloc, path = parts[:3]\n    normalized_url = scheme.lower() + \"://\" + netloc.lower() + path\n\n    base_elems = []\n    base_elems.append(method.upper())\n    base_elems.append(normalized_url)\n    base_elems.append(\n        \"&\".join(f\"{k}={_oauth_escape(str(v))}\" for k, v in sorted(parameters.items()))\n    )\n\n    base_string = \"&\".join(_oauth_escape(e) for e in base_elems)\n    key_elems = [escape.utf8(urllib.parse.quote(consumer_token[\"secret\"], safe=\"~\"))]\n    key_elems.append(\n        escape.utf8(urllib.parse.quote(token[\"secret\"], safe=\"~\") if token else \"\")\n    )\n    key = b\"&\".join(key_elems)\n\n    hash = hmac.new(key, escape.utf8(base_string), hashlib.sha1)\n    return binascii.b2a_base64(hash.digest())[:-1]\n\n\ndef _oauth_escape(val: str | bytes) -> str:\n    if isinstance(val, unicode_type):\n        val = val.encode(\"utf-8\")\n    return urllib.parse.quote(val, safe=\"~\")\n\n\ndef _oauth_parse_response(body: bytes) -> dict[str, Any]:\n    # I can't find an officially-defined encoding for oauth responses and\n    # have never seen anyone use non-ascii.  Leave the response in a byte\n    # string for python 2, and use utf8 on python 3.\n    body_str = escape.native_str(body)\n    p = urllib.parse.parse_qs(body_str, keep_blank_values=False)\n    token = dict(key=p[\"oauth_token\"][0], secret=p[\"oauth_token_secret\"][0])\n\n    # Add the extra parameters the Provider included to the token\n    special = (\"oauth_token\", \"oauth_token_secret\")\n    token.update((k, p[k][0]) for k in p if k not in special)\n    return token\n"
  },
  {
    "path": "tornado/autoreload.py",
    "content": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Automatically restart the server when a source file is modified.\n\nMost applications should not access this module directly.  Instead,\npass the keyword argument ``autoreload=True`` to the\n`tornado.web.Application` constructor (or ``debug=True``, which\nenables this setting and several others).  This will enable autoreload\nmode as well as checking for changes to templates and static\nresources.  Note that restarting is a destructive operation and any\nrequests in progress will be aborted when the process restarts.  (If\nyou want to disable autoreload while using other debug-mode features,\npass both ``debug=True`` and ``autoreload=False``).\n\nThis module can also be used as a command-line wrapper around scripts\nsuch as unit test runners.  See the `main` method for details.\n\nThe command-line wrapper and Application debug modes can be used together.\nThis combination is encouraged as the wrapper catches syntax errors and\nother import-time failures, while debug mode catches changes once\nthe server has started.\n\nThis module will not work correctly when `.HTTPServer`'s multi-process\nmode is used.\n\nReloading loses any Python interpreter command-line arguments (e.g. ``-u``)\nbecause it re-executes Python using ``sys.executable`` and ``sys.argv``.\nAdditionally, modifying these variables will cause reloading to behave\nincorrectly.\n\n\"\"\"\n\nimport os\nimport sys\n\n# sys.path handling\n# -----------------\n#\n# If a module is run with \"python -m\", the current directory (i.e. \"\")\n# is automatically prepended to sys.path, but not if it is run as\n# \"path/to/file.py\".  The processing for \"-m\" rewrites the former to\n# the latter, so subsequent executions won't have the same path as the\n# original.\n#\n# Conversely, when run as path/to/file.py, the directory containing\n# file.py gets added to the path, which can cause confusion as imports\n# may become relative in spite of the future import.\n#\n# We address the former problem by reconstructing the original command\n# line before re-execution so the new process will\n# see the correct path.  We attempt to address the latter problem when\n# tornado.autoreload is run as __main__.\n\nif __name__ == \"__main__\":\n    # This sys.path manipulation must come before our imports (as much\n    # as possible - if we introduced a tornado.sys or tornado.os\n    # module we'd be in trouble), or else our imports would become\n    # relative again despite the future import.\n    #\n    # There is a separate __main__ block at the end of the file to call main().\n    if sys.path[0] == os.path.dirname(__file__):\n        del sys.path[0]\n\nimport functools\nimport importlib.abc\nimport os\nimport pkgutil\nimport subprocess\nimport sys\nimport traceback\nimport types\nimport weakref\n\nfrom tornado import ioloop, process\nfrom tornado.log import gen_log\n\ntry:\n    import signal\nexcept ImportError:\n    signal = None  # type: ignore\n\nfrom collections.abc import Callable\n\n# os.execv is broken on Windows and can't properly parse command line\n# arguments and executable name if they contain whitespaces. subprocess\n# fixes that behavior.\n_has_execv = sys.platform != \"win32\"\n\n_watched_files = set()\n_reload_hooks = []\n_reload_attempted = False\n_io_loops: \"weakref.WeakKeyDictionary[ioloop.IOLoop, bool]\" = (\n    weakref.WeakKeyDictionary()\n)\n_autoreload_is_main = False\n_original_argv: list[str] | None = None\n_original_spec = None\n\n\ndef start(check_time: int = 500) -> None:\n    \"\"\"Begins watching source files for changes.\n\n    .. versionchanged:: 5.0\n       The ``io_loop`` argument (deprecated since version 4.1) has been removed.\n    \"\"\"\n    io_loop = ioloop.IOLoop.current()\n    if io_loop in _io_loops:\n        return\n    _io_loops[io_loop] = True\n    if len(_io_loops) > 1:\n        gen_log.warning(\"tornado.autoreload started more than once in the same process\")\n    modify_times: dict[str, float] = {}\n    callback = functools.partial(_reload_on_update, modify_times)\n    scheduler = ioloop.PeriodicCallback(callback, check_time)\n    scheduler.start()\n\n\ndef wait() -> None:\n    \"\"\"Wait for a watched file to change, then restart the process.\n\n    Intended to be used at the end of scripts like unit test runners,\n    to run the tests again after any source file changes (but see also\n    the command-line interface in `main`)\n    \"\"\"\n    io_loop = ioloop.IOLoop()\n    io_loop.add_callback(start)\n    io_loop.start()\n\n\ndef watch(filename: str) -> None:\n    \"\"\"Add a file to the watch list.\n\n    All imported modules are watched by default.\n    \"\"\"\n    _watched_files.add(filename)\n\n\ndef add_reload_hook(fn: Callable[[], None]) -> None:\n    \"\"\"Add a function to be called before reloading the process.\n\n    Note that for open file and socket handles it is generally\n    preferable to set the ``FD_CLOEXEC`` flag (using `fcntl` or\n    `os.set_inheritable`) instead of using a reload hook to close them.\n    \"\"\"\n    _reload_hooks.append(fn)\n\n\ndef _reload_on_update(modify_times: dict[str, float]) -> None:\n    if _reload_attempted:\n        # We already tried to reload and it didn't work, so don't try again.\n        return\n    if process.task_id() is not None:\n        # We're in a child process created by fork_processes.  If child\n        # processes restarted themselves, they'd all restart and then\n        # all call fork_processes again.\n        return\n    for module in list(sys.modules.values()):\n        # Some modules play games with sys.modules (e.g. email/__init__.py\n        # in the standard library), and occasionally this can cause strange\n        # failures in getattr.  Just ignore anything that's not an ordinary\n        # module.\n        if not isinstance(module, types.ModuleType):\n            continue\n        path = getattr(module, \"__file__\", None)\n        if not path:\n            continue\n        if path.endswith(\".pyc\") or path.endswith(\".pyo\"):\n            path = path[:-1]\n        _check_file(modify_times, path)\n    for path in _watched_files:\n        _check_file(modify_times, path)\n\n\ndef _check_file(modify_times: dict[str, float], path: str) -> None:\n    try:\n        modified = os.stat(path).st_mtime\n    except Exception:\n        return\n    if path not in modify_times:\n        modify_times[path] = modified\n        return\n    if modify_times[path] != modified:\n        gen_log.info(\"%s modified; restarting server\", path)\n        _reload()\n\n\ndef _reload() -> None:\n    global _reload_attempted\n    _reload_attempted = True\n    for fn in _reload_hooks:\n        fn()\n    if sys.platform != \"win32\":\n        # Clear the alarm signal set by\n        # ioloop.set_blocking_log_threshold so it doesn't fire\n        # after the exec.\n        signal.setitimer(signal.ITIMER_REAL, 0, 0)\n    # sys.path fixes: see comments at top of file.  If __main__.__spec__\n    # exists, we were invoked with -m and the effective path is about to\n    # change on re-exec.  Reconstruct the original command line to\n    # ensure that the new process sees the same path we did.\n    if _autoreload_is_main:\n        assert _original_argv is not None\n        spec = _original_spec\n        argv = _original_argv\n    else:\n        spec = getattr(sys.modules[\"__main__\"], \"__spec__\", None)\n        argv = sys.argv\n    if spec and spec.name != \"__main__\":\n        # __spec__ is set in two cases: when running a module, and when running a directory. (when\n        # running a file, there is no spec). In the former case, we must pass -m to maintain the\n        # module-style behavior (setting sys.path), even though python stripped -m from its argv at\n        # startup. If sys.path is exactly __main__, we're running a directory and should fall\n        # through to the non-module behavior.\n        #\n        # Some of this, including the use of exactly __main__ as a spec for directory mode,\n        # is documented at https://docs.python.org/3/library/runpy.html#runpy.run_path\n        argv = [\"-m\", spec.name] + argv[1:]\n\n    if not _has_execv:\n        subprocess.Popen([sys.executable] + argv)\n        os._exit(0)\n    else:\n        os.execv(sys.executable, [sys.executable] + argv)\n\n\n_USAGE = \"\"\"\n  python -m tornado.autoreload -m module.to.run [args...]\n  python -m tornado.autoreload path/to/script.py [args...]\n\"\"\"\n\n\ndef main() -> None:\n    \"\"\"Command-line wrapper to re-run a script whenever its source changes.\n\n    Scripts may be specified by filename or module name::\n\n        python -m tornado.autoreload -m tornado.test.runtests\n        python -m tornado.autoreload tornado/test/runtests.py\n\n    Running a script with this wrapper is similar to calling\n    `tornado.autoreload.wait` at the end of the script, but this wrapper\n    can catch import-time problems like syntax errors that would otherwise\n    prevent the script from reaching its call to `wait`.\n    \"\"\"\n    # Remember that we were launched with autoreload as main.\n    # The main module can be tricky; set the variables both in our globals\n    # (which may be __main__) and the real importable version.\n    #\n    # We use optparse instead of the newer argparse because we want to\n    # mimic the python command-line interface which requires stopping\n    # parsing at the first positional argument. optparse supports\n    # this but as far as I can tell argparse does not.\n    import optparse\n\n    import tornado.autoreload\n\n    global _autoreload_is_main\n    global _original_argv, _original_spec\n    tornado.autoreload._autoreload_is_main = _autoreload_is_main = True\n    original_argv = sys.argv\n    tornado.autoreload._original_argv = _original_argv = original_argv\n    original_spec = getattr(sys.modules[\"__main__\"], \"__spec__\", None)\n    tornado.autoreload._original_spec = _original_spec = original_spec\n\n    parser = optparse.OptionParser(\n        prog=\"python -m tornado.autoreload\",\n        usage=_USAGE,\n        epilog=\"Either -m or a path must be specified, but not both\",\n    )\n    parser.disable_interspersed_args()\n    parser.add_option(\"-m\", dest=\"module\", metavar=\"module\", help=\"module to run\")\n    parser.add_option(\n        \"--until-success\",\n        action=\"store_true\",\n        help=\"stop reloading after the program exist successfully (status code 0)\",\n    )\n    opts, rest = parser.parse_args()\n    if opts.module is None:\n        if not rest:\n            print(\"Either -m or a path must be specified\", file=sys.stderr)\n            sys.exit(1)\n        path = rest[0]\n        sys.argv = rest[:]\n    else:\n        path = None\n        sys.argv = [sys.argv[0]] + rest\n\n    # SystemExit.code is typed funny: https://github.com/python/typeshed/issues/8513\n    # All we care about is truthiness\n    exit_status: int | str | None = 1\n    try:\n        import runpy\n\n        if opts.module is not None:\n            runpy.run_module(opts.module, run_name=\"__main__\", alter_sys=True)\n        else:\n            assert path is not None\n            runpy.run_path(path, run_name=\"__main__\")\n    except SystemExit as e:\n        exit_status = e.code\n        gen_log.info(\"Script exited with status %s\", e.code)\n    except Exception as e:\n        gen_log.warning(\"Script exited with uncaught exception\", exc_info=True)\n        # If an exception occurred at import time, the file with the error\n        # never made it into sys.modules and so we won't know to watch it.\n        # Just to make sure we've covered everything, walk the stack trace\n        # from the exception and watch every file.\n        for filename, lineno, name, line in traceback.extract_tb(sys.exc_info()[2]):\n            watch(filename)\n        if isinstance(e, SyntaxError):\n            # SyntaxErrors are special:  their innermost stack frame is fake\n            # so extract_tb won't see it and we have to get the filename\n            # from the exception object.\n            if e.filename is not None:\n                watch(e.filename)\n    else:\n        exit_status = 0\n        gen_log.info(\"Script exited normally\")\n    # restore sys.argv so subsequent executions will include autoreload\n    sys.argv = original_argv\n\n    if opts.module is not None:\n        assert opts.module is not None\n        # runpy did a fake import of the module as __main__, but now it's\n        # no longer in sys.modules.  Figure out where it is and watch it.\n        loader = pkgutil.get_loader(opts.module)\n        if loader is not None and isinstance(loader, importlib.abc.FileLoader):\n            watch(loader.get_filename())\n    if opts.until_success and not exit_status:\n        return\n    wait()\n\n\nif __name__ == \"__main__\":\n    # See also the other __main__ block at the top of the file, which modifies\n    # sys.path before our imports\n    main()\n"
  },
  {
    "path": "tornado/concurrent.py",
    "content": "#\n# Copyright 2012 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\"\"\"Utilities for working with ``Future`` objects.\n\nTornado previously provided its own ``Future`` class, but now uses\n`asyncio.Future`. This module contains utility functions for working\nwith `asyncio.Future` in a way that is backwards-compatible with\nTornado's old ``Future`` implementation.\n\nWhile this module is an important part of Tornado's internal\nimplementation, applications rarely need to interact with it\ndirectly.\n\n\"\"\"\n\nimport asyncio\nimport functools\nimport sys\nimport types\nimport typing\nfrom collections.abc import Callable\nfrom concurrent import futures\nfrom typing import Any, Union\n\nfrom tornado.log import app_log\n\n_T = typing.TypeVar(\"_T\")\n\n\nclass ReturnValueIgnoredError(Exception):\n    # No longer used; was previously used by @return_future\n    pass\n\n\nFuture = asyncio.Future\n\nFUTURES = (futures.Future, Future)\n\n\ndef is_future(x: Any) -> bool:\n    return isinstance(x, FUTURES)\n\n\nclass DummyExecutor(futures.Executor):\n    def submit(  # type: ignore[override]\n        self, fn: Callable[..., _T], *args: Any, **kwargs: Any\n    ) -> \"futures.Future[_T]\":\n        future: futures.Future[_T] = futures.Future()\n        try:\n            future_set_result_unless_cancelled(future, fn(*args, **kwargs))\n        except Exception:\n            future_set_exc_info(future, sys.exc_info())\n        return future\n\n    def shutdown(self, wait: bool = True, cancel_futures: bool = False) -> None:\n        pass\n\n\ndummy_executor = DummyExecutor()\n\n\ndef run_on_executor(*args: Any, **kwargs: Any) -> Callable:\n    \"\"\"Decorator to run a synchronous method asynchronously on an executor.\n\n    Returns a future.\n\n    The executor to be used is determined by the ``executor``\n    attributes of ``self``. To use a different attribute name, pass a\n    keyword argument to the decorator::\n\n        @run_on_executor(executor='_thread_pool')\n        def foo(self):\n            pass\n\n    This decorator should not be confused with the similarly-named\n    `.IOLoop.run_in_executor`. In general, using ``run_in_executor``\n    when *calling* a blocking method is recommended instead of using\n    this decorator when *defining* a method. If compatibility with older\n    versions of Tornado is required, consider defining an executor\n    and using ``executor.submit()`` at the call site.\n\n    .. versionchanged:: 4.2\n       Added keyword arguments to use alternative attributes.\n\n    .. versionchanged:: 5.0\n       Always uses the current IOLoop instead of ``self.io_loop``.\n\n    .. versionchanged:: 5.1\n       Returns a `.Future` compatible with ``await`` instead of a\n       `concurrent.futures.Future`.\n\n    .. deprecated:: 5.1\n\n       The ``callback`` argument is deprecated and will be removed in\n       6.0. The decorator itself is discouraged in new code but will\n       not be removed in 6.0.\n\n    .. versionchanged:: 6.0\n\n       The ``callback`` argument was removed.\n    \"\"\"\n\n    # Fully type-checking decorators is tricky, and this one is\n    # discouraged anyway so it doesn't have all the generic magic.\n    def run_on_executor_decorator(fn: Callable) -> Callable[..., Future]:\n        executor = kwargs.get(\"executor\", \"executor\")\n\n        @functools.wraps(fn)\n        def wrapper(self: Any, *args: Any, **kwargs: Any) -> Future:\n            async_future: Future = Future()\n            conc_future = getattr(self, executor).submit(fn, self, *args, **kwargs)\n            chain_future(conc_future, async_future)\n            return async_future\n\n        return wrapper\n\n    if args and kwargs:\n        raise ValueError(\"cannot combine positional and keyword args\")\n    if len(args) == 1:\n        return run_on_executor_decorator(args[0])\n    elif len(args) != 0:\n        raise ValueError(\"expected 1 argument, got %d\", len(args))\n    return run_on_executor_decorator\n\n\n_NO_RESULT = object()\n\n\ndef chain_future(\n    a: Union[\"Future[_T]\", \"futures.Future[_T]\"],\n    b: Union[\"Future[_T]\", \"futures.Future[_T]\"],\n) -> None:\n    \"\"\"Chain two futures together so that when one completes, so does the other.\n\n    The result (success or failure) of ``a`` will be copied to ``b``, unless\n    ``b`` has already been completed or cancelled by the time ``a`` finishes.\n\n    .. versionchanged:: 5.0\n\n       Now accepts both Tornado/asyncio `Future` objects and\n       `concurrent.futures.Future`.\n\n    \"\"\"\n\n    def copy(a: \"Future[_T]\") -> None:\n        if b.done():\n            return\n        if hasattr(a, \"exc_info\") and a.exc_info() is not None:  # type: ignore\n            future_set_exc_info(b, a.exc_info())  # type: ignore\n        else:\n            a_exc = a.exception()\n            if a_exc is not None:\n                b.set_exception(a_exc)\n            else:\n                b.set_result(a.result())\n\n    if isinstance(a, Future):\n        future_add_done_callback(a, copy)\n    else:\n        # concurrent.futures.Future\n        from tornado.ioloop import IOLoop\n\n        IOLoop.current().add_future(a, copy)\n\n\ndef future_set_result_unless_cancelled(\n    future: \"Union[futures.Future[_T], Future[_T]]\", value: _T\n) -> None:\n    \"\"\"Set the given ``value`` as the `Future`'s result, if not cancelled.\n\n    Avoids ``asyncio.InvalidStateError`` when calling ``set_result()`` on\n    a cancelled `asyncio.Future`.\n\n    .. versionadded:: 5.0\n    \"\"\"\n    if not future.cancelled():\n        future.set_result(value)\n\n\ndef future_set_exception_unless_cancelled(\n    future: \"Union[futures.Future[_T], Future[_T]]\", exc: BaseException\n) -> None:\n    \"\"\"Set the given ``exc`` as the `Future`'s exception.\n\n    If the Future is already canceled, logs the exception instead. If\n    this logging is not desired, the caller should explicitly check\n    the state of the Future and call ``Future.set_exception`` instead of\n    this wrapper.\n\n    Avoids ``asyncio.InvalidStateError`` when calling ``set_exception()`` on\n    a cancelled `asyncio.Future`.\n\n    .. versionadded:: 6.0\n\n    \"\"\"\n    if not future.cancelled():\n        future.set_exception(exc)\n    else:\n        app_log.error(\"Exception after Future was cancelled\", exc_info=exc)\n\n\ndef future_set_exc_info(\n    future: \"Union[futures.Future[_T], Future[_T]]\",\n    exc_info: tuple[type | None, BaseException | None, types.TracebackType | None],\n) -> None:\n    \"\"\"Set the given ``exc_info`` as the `Future`'s exception.\n\n    Understands both `asyncio.Future` and the extensions in older\n    versions of Tornado to enable better tracebacks on Python 2.\n\n    .. versionadded:: 5.0\n\n    .. versionchanged:: 6.0\n\n       If the future is already cancelled, this function is a no-op.\n       (previously ``asyncio.InvalidStateError`` would be raised)\n\n    \"\"\"\n    if exc_info[1] is None:\n        raise Exception(\"future_set_exc_info called with no exception\")\n    future_set_exception_unless_cancelled(future, exc_info[1])\n\n\n@typing.overload\ndef future_add_done_callback(\n    future: \"futures.Future[_T]\", callback: Callable[[\"futures.Future[_T]\"], None]\n) -> None:\n    pass\n\n\n@typing.overload\ndef future_add_done_callback(\n    future: \"Future[_T]\", callback: Callable[[\"Future[_T]\"], None]\n) -> None:\n    pass\n\n\ndef future_add_done_callback(\n    future: \"Union[futures.Future[_T], Future[_T]]\", callback: Callable[..., None]\n) -> None:\n    \"\"\"Arrange to call ``callback`` when ``future`` is complete.\n\n    ``callback`` is invoked with one argument, the ``future``.\n\n    If ``future`` is already done, ``callback`` is invoked immediately.\n    This may differ from the behavior of ``Future.add_done_callback``,\n    which makes no such guarantee.\n\n    .. versionadded:: 5.0\n    \"\"\"\n    if future.done():\n        callback(future)\n    else:\n        future.add_done_callback(callback)\n"
  },
  {
    "path": "tornado/curl_httpclient.py",
    "content": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Non-blocking HTTP client implementation using pycurl.\"\"\"\n\nimport collections\nimport functools\nimport inspect\nimport logging\nimport re\nimport threading\nimport time\nfrom collections.abc import Callable\nfrom io import BytesIO\nfrom typing import Any\n\nimport pycurl\n\nfrom tornado import gen, httputil, ioloop\nfrom tornado.escape import native_str, utf8\nfrom tornado.httpclient import (\n    AsyncHTTPClient,\n    HTTPError,\n    HTTPRequest,\n    HTTPResponse,\n    main,\n)\nfrom tornado.log import app_log\n\ncurl_log = logging.getLogger(\"tornado.curl_httpclient\")\n\nCR_OR_LF_RE = re.compile(b\"\\r|\\n\")\n\n\nclass CurlAsyncHTTPClient(AsyncHTTPClient):\n    def initialize(  # type: ignore\n        self, max_clients: int = 10, defaults: dict[str, Any] | None = None\n    ) -> None:\n        super().initialize(defaults=defaults)\n        # Typeshed is incomplete for CurlMulti, so just use Any for now.\n        self._multi: Any = pycurl.CurlMulti()\n        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)\n        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)\n        self._curls = [self._curl_create() for i in range(max_clients)]\n        self._free_list = self._curls[:]\n        self._requests: collections.deque[\n            tuple[HTTPRequest, Callable[[HTTPResponse], None], float]\n        ] = collections.deque()\n        self._fds: dict[int, int] = {}\n        self._timeout: object | None = None\n\n        # libcurl has bugs that sometimes cause it to not report all\n        # relevant file descriptors and timeouts to TIMERFUNCTION/\n        # SOCKETFUNCTION.  Mitigate the effects of such bugs by\n        # forcing a periodic scan of all active requests.\n        self._force_timeout_callback = ioloop.PeriodicCallback(\n            self._handle_force_timeout, 1000\n        )\n        self._force_timeout_callback.start()\n\n        # Work around a bug in libcurl 7.29.0: Some fields in the curl\n        # multi object are initialized lazily, and its destructor will\n        # segfault if it is destroyed without having been used.  Add\n        # and remove a dummy handle to make sure everything is\n        # initialized.\n        dummy_curl_handle = pycurl.Curl()\n        self._multi.add_handle(dummy_curl_handle)\n        self._multi.remove_handle(dummy_curl_handle)\n\n    def close(self) -> None:\n        self._force_timeout_callback.stop()\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        for curl in self._curls:\n            curl.close()\n        self._multi.close()\n        super().close()\n\n        # Set below properties to None to reduce the reference count of current\n        # instance, because those properties hold some methods of current\n        # instance that will case circular reference.\n        self._force_timeout_callback = None  # type: ignore\n        self._multi = None\n\n    def fetch_impl(\n        self, request: HTTPRequest, callback: Callable[[HTTPResponse], None]\n    ) -> None:\n        self._requests.append((request, callback, self.io_loop.time()))\n        self._process_queue()\n        self._set_timeout(0)\n\n    def _handle_socket(self, event: int, fd: int, multi: Any, data: bytes) -> None:\n        \"\"\"Called by libcurl when it wants to change the file descriptors\n        it cares about.\n        \"\"\"\n        event_map = {\n            pycurl.POLL_NONE: ioloop.IOLoop.NONE,\n            pycurl.POLL_IN: ioloop.IOLoop.READ,\n            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,\n            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE,\n        }\n        if event == pycurl.POLL_REMOVE:\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n                del self._fds[fd]\n        else:\n            ioloop_event = event_map[event]\n            # libcurl sometimes closes a socket and then opens a new\n            # one using the same FD without giving us a POLL_NONE in\n            # between.  This is a problem with the epoll IOLoop,\n            # because the kernel can tell when a socket is closed and\n            # removes it from the epoll automatically, causing future\n            # update_handler calls to fail.  Since we can't tell when\n            # this has happened, always use remove and re-add\n            # instead of update.\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n            self.io_loop.add_handler(fd, self._handle_events, ioloop_event)\n            self._fds[fd] = ioloop_event\n\n    def _set_timeout(self, msecs: int) -> None:\n        \"\"\"Called by libcurl to schedule a timeout.\"\"\"\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        self._timeout = self.io_loop.add_timeout(\n            self.io_loop.time() + msecs / 1000.0, self._handle_timeout\n        )\n\n    def _handle_events(self, fd: int, events: int) -> None:\n        \"\"\"Called by IOLoop when there is activity on one of our\n        file descriptors.\n        \"\"\"\n        action = 0\n        if events & ioloop.IOLoop.READ:\n            action |= pycurl.CSELECT_IN\n        if events & ioloop.IOLoop.WRITE:\n            action |= pycurl.CSELECT_OUT\n        while True:\n            try:\n                ret, num_handles = self._multi.socket_action(fd, action)\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n    def _handle_timeout(self) -> None:\n        \"\"\"Called by IOLoop when the requested timeout has passed.\"\"\"\n        self._timeout = None\n        while True:\n            try:\n                ret, num_handles = self._multi.socket_action(pycurl.SOCKET_TIMEOUT, 0)\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n        # In theory, we shouldn't have to do this because curl will\n        # call _set_timeout whenever the timeout changes.  However,\n        # sometimes after _handle_timeout we will need to reschedule\n        # immediately even though nothing has changed from curl's\n        # perspective.  This is because when socket_action is\n        # called with SOCKET_TIMEOUT, libcurl decides internally which\n        # timeouts need to be processed by using a monotonic clock\n        # (where available) while tornado uses python's time.time()\n        # to decide when timeouts have occurred.  When those clocks\n        # disagree on elapsed time (as they will whenever there is an\n        # NTP adjustment), tornado might call _handle_timeout before\n        # libcurl is ready.  After each timeout, resync the scheduled\n        # timeout with libcurl's current state.\n        new_timeout = self._multi.timeout()\n        if new_timeout >= 0:\n            self._set_timeout(new_timeout)\n\n    def _handle_force_timeout(self) -> None:\n        \"\"\"Called by IOLoop periodically to ask libcurl to process any\n        events it may have forgotten about.\n        \"\"\"\n        while True:\n            try:\n                ret, num_handles = self._multi.socket_all()\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n    def _finish_pending_requests(self) -> None:\n        \"\"\"Process any requests that were completed by the last\n        call to multi.socket_action.\n        \"\"\"\n        while True:\n            num_q, ok_list, err_list = self._multi.info_read()\n            for curl in ok_list:\n                self._finish(curl)\n            for curl, errnum, errmsg in err_list:\n                self._finish(curl, errnum, errmsg)\n            if num_q == 0:\n                break\n        self._process_queue()\n\n    def _process_queue(self) -> None:\n        while True:\n            started = 0\n            while self._free_list and self._requests:\n                started += 1\n                curl = self._free_list.pop()\n                request, callback, queue_start_time = self._requests.popleft()\n                # TODO: Don't smuggle extra data on an attribute of the Curl object.\n                curl.info = {  # type: ignore\n                    \"headers\": httputil.HTTPHeaders(),\n                    \"buffer\": BytesIO(),\n                    \"request\": request,\n                    \"callback\": callback,\n                    \"queue_start_time\": queue_start_time,\n                    \"curl_start_time\": time.time(),\n                    \"curl_start_ioloop_time\": self.io_loop.current().time(),  # type: ignore\n                }\n                try:\n                    self._curl_setup_request(\n                        curl,\n                        request,\n                        curl.info[\"buffer\"],  # type: ignore\n                        curl.info[\"headers\"],  # type: ignore\n                    )\n                except Exception as e:\n                    # If there was an error in setup, pass it on\n                    # to the callback. Note that allowing the\n                    # error to escape here will appear to work\n                    # most of the time since we are still in the\n                    # caller's original stack frame, but when\n                    # _process_queue() is called from\n                    # _finish_pending_requests the exceptions have\n                    # nowhere to go.\n                    self._free_list.append(curl)\n                    callback(HTTPResponse(request=request, code=599, error=e))\n                else:\n                    self._multi.add_handle(curl)\n\n            if not started:\n                break\n\n    def _finish(\n        self,\n        curl: pycurl.Curl,\n        curl_error: int | None = None,\n        curl_message: str | None = None,\n    ) -> None:\n        info = curl.info  # type: ignore\n        curl.info = None  # type: ignore\n        self._multi.remove_handle(curl)\n        self._free_list.append(curl)\n        buffer = info[\"buffer\"]\n        if curl_error:\n            assert curl_message is not None\n            error: CurlError | None = CurlError(curl_error, curl_message)\n            assert error is not None\n            code = error.code\n            effective_url = None\n            buffer.close()\n            buffer = None\n        else:\n            error = None\n            code = curl.getinfo(pycurl.HTTP_CODE)\n            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)\n            buffer.seek(0)\n        # the various curl timings are documented at\n        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\n        time_info = dict(\n            queue=info[\"curl_start_ioloop_time\"] - info[\"queue_start_time\"],\n            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),\n            connect=curl.getinfo(pycurl.CONNECT_TIME),\n            appconnect=curl.getinfo(pycurl.APPCONNECT_TIME),\n            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),\n            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),\n            total=curl.getinfo(pycurl.TOTAL_TIME),\n            redirect=curl.getinfo(pycurl.REDIRECT_TIME),\n        )\n        try:\n            info[\"callback\"](\n                HTTPResponse(\n                    request=info[\"request\"],\n                    code=code,\n                    headers=info[\"headers\"],\n                    buffer=buffer,\n                    effective_url=effective_url,\n                    error=error,\n                    reason=info[\"headers\"].get(\"X-Http-Reason\", None),\n                    request_time=self.io_loop.time() - info[\"curl_start_ioloop_time\"],\n                    start_time=info[\"curl_start_time\"],\n                    time_info=time_info,\n                )\n            )\n        except Exception:\n            self.handle_callback_exception(info[\"callback\"])\n\n    def handle_callback_exception(self, callback: Any) -> None:\n        app_log.error(\"Exception in callback %r\", callback, exc_info=True)\n\n    def _curl_create(self) -> pycurl.Curl:\n        curl = pycurl.Curl()\n        if curl_log.isEnabledFor(logging.DEBUG):\n            curl.setopt(pycurl.VERBOSE, 1)\n            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)\n        if hasattr(\n            pycurl, \"PROTOCOLS\"\n        ):  # PROTOCOLS first appeared in pycurl 7.19.5 (2014-07-12)\n            curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)\n            curl.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)\n        return curl\n\n    def _curl_setup_request(\n        self,\n        curl: pycurl.Curl,\n        request: HTTPRequest,\n        buffer: BytesIO,\n        headers: httputil.HTTPHeaders,\n    ) -> None:\n        curl.setopt(pycurl.URL, native_str(request.url))\n\n        # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n        # with servers that don't support it (which include, among others,\n        # Google's OpenID endpoint).  Additionally, this behavior has\n        # a bug in conjunction with the curl_multi_socket_action API\n        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n        # which increases the delays.  It's more trouble than it's worth,\n        # so just turn off the feature (yes, setting Expect: to an empty\n        # value is the official way to disable this)\n        if \"Expect\" not in request.headers:\n            request.headers[\"Expect\"] = \"\"\n\n        # libcurl adds Pragma: no-cache by default; disable that too\n        if \"Pragma\" not in request.headers:\n            request.headers[\"Pragma\"] = \"\"\n\n        encoded_headers = [\n            b\"%s: %s\"\n            % (native_str(k).encode(\"ASCII\"), native_str(v).encode(\"ISO8859-1\"))\n            for k, v in request.headers.get_all()\n        ]\n        for line in encoded_headers:\n            if CR_OR_LF_RE.search(line):\n                raise ValueError(\"Illegal characters in header (CR or LF): %r\" % line)\n        curl.setopt(pycurl.HTTPHEADER, encoded_headers)\n\n        curl.setopt(\n            pycurl.HEADERFUNCTION,\n            functools.partial(\n                self._curl_header_callback, headers, request.header_callback\n            ),\n        )\n        if request.streaming_callback:\n\n            if gen.is_coroutine_function(\n                request.streaming_callback\n            ) or inspect.iscoroutinefunction(request.streaming_callback):\n                raise TypeError(\n                    \"'CurlAsyncHTTPClient' does not support async streaming_callbacks.\"\n                )\n\n            def write_function(b: bytes | bytearray) -> int:\n                assert request.streaming_callback is not None\n                self.io_loop.add_callback(request.streaming_callback, b)\n                return len(b)\n\n        else:\n            write_function = buffer.write  # type: ignore\n        curl.setopt(pycurl.WRITEFUNCTION, write_function)\n        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n        assert request.connect_timeout is not None\n        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n        assert request.request_timeout is not None\n        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n        if request.user_agent:\n            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n        else:\n            curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n        if request.network_interface:\n            curl.setopt(pycurl.INTERFACE, request.network_interface)\n        if request.decompress_response:\n            curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n        else:\n            curl.setopt(pycurl.ENCODING, None)\n        if request.proxy_host and request.proxy_port:\n            curl.setopt(pycurl.PROXY, request.proxy_host)\n            curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n            if request.proxy_username:\n                assert request.proxy_password is not None\n                credentials = httputil.encode_username_password(\n                    request.proxy_username, request.proxy_password\n                )\n                curl.setopt(pycurl.PROXYUSERPWD, credentials)\n\n            if request.proxy_auth_mode is None or request.proxy_auth_mode == \"basic\":\n                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.proxy_auth_mode == \"digest\":\n                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\n                    \"Unsupported proxy_auth_mode %s\" % request.proxy_auth_mode\n                )\n        else:\n            try:\n                curl.unsetopt(pycurl.PROXY)\n            except TypeError:  # not supported, disable proxy\n                curl.setopt(pycurl.PROXY, \"\")\n            curl.unsetopt(pycurl.PROXYUSERPWD)\n        if request.validate_cert:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n        else:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n        if request.ca_certs is not None:\n            curl.setopt(pycurl.CAINFO, request.ca_certs)\n        else:\n            # There is no way to restore pycurl.CAINFO to its default value\n            # (Using unsetopt makes it reject all certificates).\n            # I don't see any way to read the default value from python so it\n            # can be restored later.  We'll have to just leave CAINFO untouched\n            # if no ca_certs file was specified, and require that if any\n            # request uses a custom ca_certs file, they all must.\n            pass\n\n        if request.allow_ipv6 is False:\n            # Curl behaves reasonably when DNS resolution gives an ipv6 address\n            # that we can't reach, so allow ipv6 unless the user asks to disable.\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n        else:\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n        # Set the request method through curl's irritating interface which makes\n        # up names for almost every single method\n        curl_options = {\n            \"GET\": pycurl.HTTPGET,\n            \"POST\": pycurl.POST,\n            \"PUT\": pycurl.UPLOAD,\n            \"HEAD\": pycurl.NOBODY,\n        }\n        custom_methods = {\"DELETE\", \"OPTIONS\", \"PATCH\"}\n        for o in curl_options.values():\n            curl.setopt(o, False)\n        if request.method in curl_options:\n            curl.unsetopt(pycurl.CUSTOMREQUEST)\n            curl.setopt(curl_options[request.method], True)\n        elif request.allow_nonstandard_methods or request.method in custom_methods:\n            curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n        else:\n            raise KeyError(\"unknown method \" + request.method)\n\n        body_expected = request.method in (\"POST\", \"PATCH\", \"PUT\")\n        body_present = request.body is not None\n        if not request.allow_nonstandard_methods:\n            # Some HTTP methods nearly always have bodies while others\n            # almost never do. Fail in this case unless the user has\n            # opted out of sanity checks with allow_nonstandard_methods.\n            if (body_expected and not body_present) or (\n                body_present and not body_expected\n            ):\n                raise ValueError(\n                    \"Body must %sbe None for method %s (unless \"\n                    \"allow_nonstandard_methods is true)\"\n                    % (\"not \" if body_expected else \"\", request.method)\n                )\n\n        if body_expected or body_present:\n            if request.method == \"GET\":\n                # Even with `allow_nonstandard_methods` we disallow\n                # GET with a body (because libcurl doesn't allow it\n                # unless we use CUSTOMREQUEST). While the spec doesn't\n                # forbid clients from sending a body, it arguably\n                # disallows the server from doing anything with them.\n                raise ValueError(\"Body must be None for GET request\")\n            request_buffer = BytesIO(utf8(request.body or \"\"))\n\n            def ioctl(cmd: int) -> None:\n                if cmd == curl.IOCMD_RESTARTREAD:  # type: ignore\n                    request_buffer.seek(0)\n\n            curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            if request.method == \"POST\":\n                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or \"\"))\n            else:\n                curl.setopt(pycurl.UPLOAD, True)\n                curl.setopt(pycurl.INFILESIZE, len(request.body or \"\"))\n\n        if request.auth_username is not None:\n            assert request.auth_password is not None\n            if request.auth_mode is None or request.auth_mode == \"basic\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.auth_mode == \"digest\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n            userpwd = httputil.encode_username_password(\n                request.auth_username, request.auth_password\n            )\n            curl.setopt(pycurl.USERPWD, userpwd)\n            curl_log.debug(\n                \"%s %s (username: %r)\",\n                request.method,\n                request.url,\n                request.auth_username,\n            )\n        else:\n            curl.unsetopt(pycurl.USERPWD)\n            curl_log.debug(\"%s %s\", request.method, request.url)\n\n        if request.client_cert is not None:\n            curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n        if request.client_key is not None:\n            curl.setopt(pycurl.SSLKEY, request.client_key)\n\n        if request.ssl_options is not None:\n            raise ValueError(\"ssl_options not supported in curl_httpclient\")\n\n        if threading.active_count() > 1:\n            # libcurl/pycurl is not thread-safe by default.  When multiple threads\n            # are used, signals should be disabled.  This has the side effect\n            # of disabling DNS timeouts in some environments (when libcurl is\n            # not linked against ares), so we don't do it when there is only one\n            # thread.  Applications that use many short-lived threads may need\n            # to set NOSIGNAL manually in a prepare_curl_callback since\n            # there may not be any other threads running at the time we call\n            # threading.activeCount.\n            curl.setopt(pycurl.NOSIGNAL, 1)\n        if request.prepare_curl_callback is not None:\n            request.prepare_curl_callback(curl)\n\n    def _curl_header_callback(\n        self,\n        headers: httputil.HTTPHeaders,\n        header_callback: Callable[[str], None] | None,\n        header_line_bytes: bytes,\n    ) -> None:\n        header_line = native_str(header_line_bytes.decode(\"latin1\"))\n        if header_callback is not None:\n            self.io_loop.add_callback(header_callback, header_line)\n        # header_line as returned by curl includes the end-of-line characters.\n        # whitespace at the start should be preserved to allow multi-line headers\n        header_line = header_line.rstrip()\n        if header_line.startswith(\"HTTP/\"):\n            headers.clear()\n            try:\n                _version, _code, reason = httputil.parse_response_start_line(\n                    header_line\n                )\n                header_line = \"X-Http-Reason: %s\" % reason\n            except httputil.HTTPInputError:\n                return\n        if not header_line:\n            return\n        headers.parse_line(header_line)\n\n    def _curl_debug(self, debug_type: int, debug_msg: str) -> None:\n        debug_types = (\"I\", \"<\", \">\", \"<\", \">\")\n        if debug_type == 0:\n            debug_msg = native_str(debug_msg)\n            curl_log.debug(\"%s\", debug_msg.strip())\n        elif debug_type in (1, 2):\n            debug_msg = native_str(debug_msg)\n            for line in debug_msg.splitlines():\n                curl_log.debug(\"%s %s\", debug_types[debug_type], line)\n        elif debug_type == 4:\n            curl_log.debug(\"%s %r\", debug_types[debug_type], debug_msg)\n\n\nclass CurlError(HTTPError):\n    def __init__(self, errno: int, message: str) -> None:\n        HTTPError.__init__(self, 599, message)\n        self.errno = errno\n\n\nif __name__ == \"__main__\":\n    AsyncHTTPClient.configure(CurlAsyncHTTPClient)\n    main()\n"
  },
  {
    "path": "tornado/escape.py",
    "content": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Escaping/unescaping methods for HTML, JSON, URLs, and others.\n\nAlso includes a few other miscellaneous string manipulation functions that\nhave crept in over time.\n\nMany functions in this module have near-equivalents in the standard library\n(the differences mainly relate to handling of bytes and unicode strings,\nand were more relevant in Python 2). In new code, the standard library\nfunctions are encouraged instead of this module where applicable. See the\ndocstrings on each function for details.\n\"\"\"\n\nimport html\nimport json\nimport re\nimport typing\nimport urllib.parse\nfrom collections.abc import Callable\nfrom typing import Any\n\nfrom tornado.util import unicode_type\n\n\ndef xhtml_escape(value: str | bytes) -> str:\n    \"\"\"Escapes a string so it is valid within HTML or XML.\n\n    Escapes the characters ``<``, ``>``, ``\"``, ``'``, and ``&``.\n    When used in attribute values the escaped strings must be enclosed\n    in quotes.\n\n    Equivalent to `html.escape` except that this function always returns\n    type `str` while `html.escape` returns `bytes` if its input is `bytes`.\n\n    .. versionchanged:: 3.2\n\n       Added the single quote to the list of escaped characters.\n\n    .. versionchanged:: 6.4\n\n       Now simply wraps `html.escape`. This is equivalent to the old behavior\n       except that single quotes are now escaped as ``&#x27;`` instead of\n       ``&#39;`` and performance may be different.\n    \"\"\"\n    return html.escape(to_unicode(value))\n\n\ndef xhtml_unescape(value: str | bytes) -> str:\n    \"\"\"Un-escapes an XML-escaped string.\n\n    Equivalent to `html.unescape` except that this function always returns\n    type `str` while `html.unescape` returns `bytes` if its input is `bytes`.\n\n    .. versionchanged:: 6.4\n\n       Now simply wraps `html.unescape`. This changes behavior for some inputs\n       as required by the HTML 5 specification\n       https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state\n\n       Some invalid inputs such as surrogates now raise an error, and numeric\n       references to certain ISO-8859-1 characters are now handled correctly.\n    \"\"\"\n    return html.unescape(to_unicode(value))\n\n\n# The fact that json_encode wraps json.dumps is an implementation detail.\n# Please see https://github.com/tornadoweb/tornado/pull/706\n# before sending a pull request that adds **kwargs to this function.\ndef json_encode(value: Any) -> str:\n    \"\"\"JSON-encodes the given Python object.\n\n    Equivalent to `json.dumps` with the additional guarantee that the output\n    will never contain the character sequence ``</`` which can be problematic\n    when JSON is embedded in an HTML ``<script>`` tag.\n    \"\"\"\n    # JSON permits but does not require forward slashes to be escaped.\n    # This is useful when json data is emitted in a <script> tag\n    # in HTML, as it prevents </script> tags from prematurely terminating\n    # the JavaScript.  Some json libraries do this escaping by default,\n    # although python's standard library does not, so we do it here.\n    # http://stackoverflow.com/questions/1580647/json-why-are-forward-slashes-escaped\n    return json.dumps(value).replace(\"</\", \"<\\\\/\")\n\n\ndef json_decode(value: str | bytes) -> Any:\n    \"\"\"Returns Python objects for the given JSON string.\n\n    Supports both `str` and `bytes` inputs. Equvalent to `json.loads`.\n    \"\"\"\n    return json.loads(value)\n\n\ndef squeeze(value: str) -> str:\n    \"\"\"Replace all sequences of whitespace chars with a single space.\"\"\"\n    return re.sub(r\"[\\x00-\\x20]+\", \" \", value).strip()\n\n\ndef url_escape(value: str | bytes, plus: bool = True) -> str:\n    \"\"\"Returns a URL-encoded version of the given value.\n\n    Equivalent to either `urllib.parse.quote_plus` or `urllib.parse.quote` depending on the ``plus``\n    argument.\n\n    If ``plus`` is true (the default), spaces will be represented as ``+`` and slashes will be\n    represented as ``%2F``.  This is appropriate for query strings. If ``plus`` is false, spaces\n    will be represented as ``%20`` and slashes are left as-is. This is appropriate for the path\n    component of a URL. Note that the default of ``plus=True`` is effectively the\n    reverse of Python's urllib module.\n\n    .. versionadded:: 3.1\n        The ``plus`` argument\n    \"\"\"\n    quote = urllib.parse.quote_plus if plus else urllib.parse.quote\n    return quote(value)\n\n\n@typing.overload\ndef url_unescape(value: str | bytes, encoding: None, plus: bool = True) -> bytes:\n    pass\n\n\n@typing.overload\ndef url_unescape(value: str | bytes, encoding: str = \"utf-8\", plus: bool = True) -> str:\n    pass\n\n\ndef url_unescape(\n    value: str | bytes, encoding: str | None = \"utf-8\", plus: bool = True\n) -> str | bytes:\n    \"\"\"Decodes the given value from a URL.\n\n    The argument may be either a byte or unicode string.\n\n    If encoding is None, the result will be a byte string and this function is equivalent to\n    `urllib.parse.unquote_to_bytes` if ``plus=False``.  Otherwise, the result is a unicode string in\n    the specified encoding and this function is equivalent to either `urllib.parse.unquote_plus` or\n    `urllib.parse.unquote` except that this function also accepts `bytes` as input.\n\n    If ``plus`` is true (the default), plus signs will be interpreted as spaces (literal plus signs\n    must be represented as \"%2B\").  This is appropriate for query strings and form-encoded values\n    but not for the path component of a URL.  Note that this default is the reverse of Python's\n    urllib module.\n\n    .. versionadded:: 3.1\n       The ``plus`` argument\n    \"\"\"\n    if encoding is None:\n        if plus:\n            # unquote_to_bytes doesn't have a _plus variant\n            value = to_basestring(value).replace(\"+\", \" \")\n        return urllib.parse.unquote_to_bytes(value)\n    else:\n        unquote = urllib.parse.unquote_plus if plus else urllib.parse.unquote\n        return unquote(to_basestring(value), encoding=encoding)\n\n\ndef parse_qs_bytes(\n    qs: str | bytes, keep_blank_values: bool = False, strict_parsing: bool = False\n) -> dict[str, list[bytes]]:\n    \"\"\"Parses a query string like urlparse.parse_qs,\n    but takes bytes and returns the values as byte strings.\n\n    Keys still become type str (interpreted as latin1 in python3!)\n    because it's too painful to keep them as byte strings in\n    python3 and in practice they're nearly always ascii anyway.\n    \"\"\"\n    # This is gross, but python3 doesn't give us another way.\n    # Latin1 is the universal donor of character encodings.\n    if isinstance(qs, bytes):\n        qs = qs.decode(\"latin1\")\n    result = urllib.parse.parse_qs(\n        qs, keep_blank_values, strict_parsing, encoding=\"latin1\", errors=\"strict\"\n    )\n    encoded = {}\n    for k, v in result.items():\n        encoded[k] = [i.encode(\"latin1\") for i in v]\n    return encoded\n\n\n_UTF8_TYPES = (bytes, type(None))\n\n\n@typing.overload\ndef utf8(value: bytes) -> bytes:\n    pass\n\n\n@typing.overload\ndef utf8(value: str) -> bytes:\n    pass\n\n\n@typing.overload\ndef utf8(value: None) -> None:\n    pass\n\n\ndef utf8(value: None | str | bytes) -> bytes | None:\n    \"\"\"Converts a string argument to a byte string.\n\n    If the argument is already a byte string or None, it is returned unchanged.\n    Otherwise it must be a unicode string and is encoded as utf8.\n    \"\"\"\n    if isinstance(value, _UTF8_TYPES):\n        return value\n    if not isinstance(value, unicode_type):\n        raise TypeError(\"Expected bytes, unicode, or None; got %r\" % type(value))\n    return value.encode(\"utf-8\")\n\n\n_TO_UNICODE_TYPES = (unicode_type, type(None))\n\n\n@typing.overload\ndef to_unicode(value: str) -> str:\n    pass\n\n\n@typing.overload\ndef to_unicode(value: bytes) -> str:\n    pass\n\n\n@typing.overload\ndef to_unicode(value: None) -> None:\n    pass\n\n\ndef to_unicode(value: None | str | bytes) -> str | None:\n    \"\"\"Converts a string argument to a unicode string.\n\n    If the argument is already a unicode string or None, it is returned\n    unchanged.  Otherwise it must be a byte string and is decoded as utf8.\n    \"\"\"\n    if isinstance(value, _TO_UNICODE_TYPES):\n        return value\n    if not isinstance(value, bytes):\n        raise TypeError(\"Expected bytes, unicode, or None; got %r\" % type(value))\n    return value.decode(\"utf-8\")\n\n\n# to_unicode was previously named _unicode not because it was private,\n# but to avoid conflicts with the built-in unicode() function/type\n_unicode = to_unicode\n\n# When dealing with the standard library across python 2 and 3 it is\n# sometimes useful to have a direct conversion to the native string type\nnative_str = to_unicode\nto_basestring = to_unicode\n\n\ndef recursive_unicode(obj: Any) -> Any:\n    \"\"\"Walks a simple data structure, converting byte strings to unicode.\n\n    Supports lists, tuples, and dictionaries.\n    \"\"\"\n    if isinstance(obj, dict):\n        return {recursive_unicode(k): recursive_unicode(v) for (k, v) in obj.items()}\n    elif isinstance(obj, list):\n        return list(recursive_unicode(i) for i in obj)\n    elif isinstance(obj, tuple):\n        return tuple(recursive_unicode(i) for i in obj)\n    elif isinstance(obj, bytes):\n        return to_unicode(obj)\n    else:\n        return obj\n\n\n# I originally used the regex from\n# http://daringfireball.net/2010/07/improved_regex_for_matching_urls\n# but it gets all exponential on certain patterns (such as too many trailing\n# dots), causing the regex matcher to never return.\n# This regex should avoid those problems.\n# Use to_unicode instead of tornado.util.u - we don't want backslashes getting\n# processed as escapes.\n_URL_RE = re.compile(\n    to_unicode(\n        r\"\"\"\\b((?:([\\w-]+):(/{1,3})|www[.])(?:(?:(?:[^\\s&()]|&amp;|&quot;)*(?:[^!\"#$%&'()*+,.:;<=>?@\\[\\]^`{|}~\\s]))|(?:\\((?:[^\\s&()]|&amp;|&quot;)*\\)))+)\"\"\"  # noqa: E501\n    )\n)\n\n\ndef linkify(\n    text: str | bytes,\n    shorten: bool = False,\n    extra_params: str | Callable[[str], str] = \"\",\n    require_protocol: bool = False,\n    permitted_protocols: list[str] = [\"http\", \"https\"],\n) -> str:\n    \"\"\"Converts plain text into HTML with links.\n\n    For example: ``linkify(\"Hello http://tornadoweb.org!\")`` would return\n    ``Hello <a href=\"http://tornadoweb.org\">http://tornadoweb.org</a>!``\n\n    Parameters:\n\n    * ``shorten``: Long urls will be shortened for display.\n\n    * ``extra_params``: Extra text to include in the link tag, or a callable\n      taking the link as an argument and returning the extra text\n      e.g. ``linkify(text, extra_params='rel=\"nofollow\" class=\"external\"')``,\n      or::\n\n          def extra_params_cb(url):\n              if url.startswith(\"http://example.com\"):\n                  return 'class=\"internal\"'\n              else:\n                  return 'class=\"external\" rel=\"nofollow\"'\n          linkify(text, extra_params=extra_params_cb)\n\n    * ``require_protocol``: Only linkify urls which include a protocol. If\n      this is False, urls such as www.facebook.com will also be linkified.\n\n    * ``permitted_protocols``: List (or set) of protocols which should be\n      linkified, e.g. ``linkify(text, permitted_protocols=[\"http\", \"ftp\",\n      \"mailto\"])``. It is very unsafe to include protocols such as\n      ``javascript``.\n    \"\"\"\n    if extra_params and not callable(extra_params):\n        extra_params = \" \" + extra_params.strip()\n\n    def make_link(m: typing.Match) -> str:\n        url = m.group(1)\n        proto = m.group(2)\n        if require_protocol and not proto:\n            return url  # not protocol, no linkify\n\n        if proto and proto not in permitted_protocols:\n            return url  # bad protocol, no linkify\n\n        href = m.group(1)\n        if not proto:\n            href = \"http://\" + href  # no proto specified, use http\n\n        if callable(extra_params):\n            params = \" \" + extra_params(href).strip()\n        else:\n            params = extra_params\n\n        # clip long urls. max_len is just an approximation\n        max_len = 30\n        if shorten and len(url) > max_len:\n            before_clip = url\n            if proto:\n                proto_len = len(proto) + 1 + len(m.group(3) or \"\")  # +1 for :\n            else:\n                proto_len = 0\n\n            parts = url[proto_len:].split(\"/\")\n            if len(parts) > 1:\n                # Grab the whole host part plus the first bit of the path\n                # The path is usually not that interesting once shortened\n                # (no more slug, etc), so it really just provides a little\n                # extra indication of shortening.\n                url = (\n                    url[:proto_len]\n                    + parts[0]\n                    + \"/\"\n                    + parts[1][:8].split(\"?\")[0].split(\".\")[0]\n                )\n\n            if len(url) > max_len * 1.5:  # still too long\n                url = url[:max_len]\n\n            if url != before_clip:\n                amp = url.rfind(\"&\")\n                # avoid splitting html char entities\n                if amp > max_len - 5:\n                    url = url[:amp]\n                url += \"...\"\n\n                if len(url) >= len(before_clip):\n                    url = before_clip\n                else:\n                    # full url is visible on mouse-over (for those who don't\n                    # have a status bar, such as Safari by default)\n                    params += ' title=\"%s\"' % href\n\n        return f'<a href=\"{href}\"{params}>{url}</a>'\n\n    # First HTML-escape so that our strings are all safe.\n    # The regex is modified to avoid character entites other than &amp; so\n    # that we won't pick up &quot;, etc.\n    text = _unicode(xhtml_escape(text))\n    return _URL_RE.sub(make_link, text)\n"
  },
  {
    "path": "tornado/gen.py",
    "content": "\"\"\"``tornado.gen`` implements generator-based coroutines.\n\n.. note::\n\n   The \"decorator and generator\" approach in this module is a\n   precursor to native coroutines (using ``async def`` and ``await``)\n   which were introduced in Python 3.5. Applications that do not\n   require compatibility with older versions of Python should use\n   native coroutines instead. Some parts of this module are still\n   useful with native coroutines, notably `multi`, `sleep`,\n   `WaitIterator`, and `with_timeout`. Some of these functions have\n   counterparts in the `asyncio` module which may be used as well,\n   although the two may not necessarily be 100% compatible.\n\nCoroutines provide an easier way to work in an asynchronous\nenvironment than chaining callbacks. Code using coroutines is\ntechnically asynchronous, but it is written as a single generator\ninstead of a collection of separate functions.\n\nFor example, here's a coroutine-based handler:\n\n.. testcode::\n\n    class GenAsyncHandler(RequestHandler):\n        @gen.coroutine\n        def get(self):\n            http_client = AsyncHTTPClient()\n            response = yield http_client.fetch(\"http://example.com\")\n            do_something_with_response(response)\n            self.render(\"template.html\")\n\nAsynchronous functions in Tornado return an ``Awaitable`` or `.Future`;\nyielding this object returns its result.\n\nYou can also yield a list or dict of other yieldable objects, which\nwill be started at the same time and run in parallel; a list or dict\nof results will be returned when they are all finished:\n\n.. testcode::\n\n    @gen.coroutine\n    def get(self):\n        http_client = AsyncHTTPClient()\n        response1, response2 = yield [http_client.fetch(url1),\n                                      http_client.fetch(url2)]\n        response_dict = yield dict(response3=http_client.fetch(url3),\n                                   response4=http_client.fetch(url4))\n        response3 = response_dict['response3']\n        response4 = response_dict['response4']\n\nIf ``tornado.platform.twisted`` is imported, it is also possible to\nyield Twisted's ``Deferred`` objects. See the `convert_yielded`\nfunction to extend this mechanism.\n\n.. versionchanged:: 3.2\n   Dict support added.\n\n.. versionchanged:: 4.1\n   Support added for yielding ``asyncio`` Futures and Twisted Deferreds\n   via ``singledispatch``.\n\n\"\"\"\n\nimport asyncio\nimport builtins\nimport collections\nimport concurrent.futures\nimport contextvars\nimport datetime\nimport functools\nimport sys\nimport types\nimport typing\nfrom collections.abc import Awaitable, Callable, Generator, Iterable, Mapping, Sequence\nfrom functools import singledispatch\nfrom inspect import isawaitable\nfrom typing import (\n    Any,\n    Dict,\n    List,\n    Tuple,\n    Type,\n    Union,\n    overload,\n)\n\nfrom tornado.concurrent import (\n    Future,\n    chain_future,\n    future_add_done_callback,\n    future_set_exc_info,\n    future_set_result_unless_cancelled,\n    is_future,\n)\nfrom tornado.ioloop import IOLoop\nfrom tornado.log import app_log\nfrom tornado.util import TimeoutError\n\n_T = typing.TypeVar(\"_T\")\n\n_Yieldable = Union[\n    None, Awaitable, list[Awaitable], dict[Any, Awaitable], concurrent.futures.Future\n]\n\n\nclass KeyReuseError(Exception):\n    pass\n\n\nclass UnknownKeyError(Exception):\n    pass\n\n\nclass LeakedCallbackError(Exception):\n    pass\n\n\nclass BadYieldError(Exception):\n    pass\n\n\nclass ReturnValueIgnoredError(Exception):\n    pass\n\n\ndef _value_from_stopiteration(e: Union[StopIteration, \"Return\"]) -> Any:\n    try:\n        # StopIteration has a value attribute beginning in py33.\n        # So does our Return class.\n        return e.value\n    except AttributeError:\n        pass\n    try:\n        # Cython backports coroutine functionality by putting the value in\n        # e.args[0].\n        return e.args[0]\n    except (AttributeError, IndexError):\n        return None\n\n\ndef _create_future() -> Future:\n    future: Future = Future()\n    # Fixup asyncio debug info by removing extraneous stack entries\n    source_traceback = getattr(future, \"_source_traceback\", ())\n    while source_traceback:\n        # Each traceback entry is equivalent to a\n        # (filename, self.lineno, self.name, self.line) tuple\n        filename = source_traceback[-1][0]\n        if filename == __file__:\n            del source_traceback[-1]\n        else:\n            break\n    return future\n\n\ndef _fake_ctx_run(f: Callable[..., _T], *args: Any, **kw: Any) -> _T:\n    return f(*args, **kw)\n\n\n@overload\ndef coroutine(\n    func: Callable[..., \"Generator[Any, Any, _T]\"],\n) -> Callable[..., \"Future[_T]\"]: ...\n\n\n@overload\ndef coroutine(func: Callable[..., _T]) -> Callable[..., \"Future[_T]\"]: ...\n\n\ndef coroutine(\n    func: Callable[..., \"Generator[Any, Any, _T]\"] | Callable[..., _T],\n) -> Callable[..., \"Future[_T]\"]:\n    \"\"\"Decorator for asynchronous generators.\n\n    For compatibility with older versions of Python, coroutines may\n    also \"return\" by raising the special exception `Return(value)\n    <Return>`.\n\n    Functions with this decorator return a `.Future`.\n\n    .. warning::\n\n       When exceptions occur inside a coroutine, the exception\n       information will be stored in the `.Future` object. You must\n       examine the result of the `.Future` object, or the exception\n       may go unnoticed by your code. This means yielding the function\n       if called from another coroutine, using something like\n       `.IOLoop.run_sync` for top-level calls, or passing the `.Future`\n       to `.IOLoop.add_future`.\n\n    .. versionchanged:: 6.0\n\n       The ``callback`` argument was removed. Use the returned\n       awaitable object instead.\n\n    \"\"\"\n\n    @functools.wraps(func)\n    def wrapper(*args: Any, **kwargs: Any) -> Future[_T]:\n        # This function is type-annotated with a comment to work around\n        # https://bitbucket.org/pypy/pypy/issues/2868/segfault-with-args-type-annotation-in\n        future = _create_future()\n        ctx_run: Callable = contextvars.copy_context().run\n        try:\n            result = ctx_run(func, *args, **kwargs)\n        except (Return, StopIteration) as e:\n            result = _value_from_stopiteration(e)\n        except Exception:\n            future_set_exc_info(future, sys.exc_info())\n            try:\n                return future\n            finally:\n                # Avoid circular references\n                future = None  # type: ignore\n        else:\n            if isinstance(result, Generator):\n                # Inline the first iteration of Runner.run.  This lets us\n                # avoid the cost of creating a Runner when the coroutine\n                # never actually yields, which in turn allows us to\n                # use \"optional\" coroutines in critical path code without\n                # performance penalty for the synchronous case.\n                try:\n                    yielded = ctx_run(next, result)\n                except (StopIteration, Return) as e:\n                    future_set_result_unless_cancelled(\n                        future, _value_from_stopiteration(e)\n                    )\n                except Exception:\n                    future_set_exc_info(future, sys.exc_info())\n                else:\n                    # Provide strong references to Runner objects as long\n                    # as their result future objects also have strong\n                    # references (typically from the parent coroutine's\n                    # Runner). This keeps the coroutine's Runner alive.\n                    # We do this by exploiting the public API\n                    # add_done_callback() instead of putting a private\n                    # attribute on the Future.\n                    # (GitHub issues #1769, #2229).\n                    runner = Runner(ctx_run, result, future, yielded)\n                    future.add_done_callback(lambda _: runner)\n                yielded = None\n                try:\n                    return future\n                finally:\n                    # Subtle memory optimization: if next() raised an exception,\n                    # the future's exc_info contains a traceback which\n                    # includes this stack frame.  This creates a cycle,\n                    # which will be collected at the next full GC but has\n                    # been shown to greatly increase memory usage of\n                    # benchmarks (relative to the refcount-based scheme\n                    # used in the absence of cycles).  We can avoid the\n                    # cycle by clearing the local variable after we return it.\n                    future = None  # type: ignore\n        future_set_result_unless_cancelled(future, result)\n        return future\n\n    wrapper.__wrapped__ = func  # type: ignore\n    wrapper.__tornado_coroutine__ = True  # type: ignore\n    return wrapper\n\n\ndef is_coroutine_function(func: Any) -> bool:\n    \"\"\"Return whether *func* is a coroutine function, i.e. a function\n    wrapped with `~.gen.coroutine`.\n\n    .. versionadded:: 4.5\n    \"\"\"\n    return getattr(func, \"__tornado_coroutine__\", False)\n\n\nclass Return(Exception):\n    \"\"\"Special exception to return a value from a `coroutine`.\n\n    This exception exists for compatibility with older versions of\n    Python (before 3.3). In newer code use the ``return`` statement\n    instead.\n\n    If this exception is raised, its value argument is used as the\n    result of the coroutine::\n\n        @gen.coroutine\n        def fetch_json(url):\n            response = yield AsyncHTTPClient().fetch(url)\n            raise gen.Return(json_decode(response.body))\n\n    By analogy with the return statement, the value argument is optional.\n    \"\"\"\n\n    def __init__(self, value: Any = None) -> None:\n        super().__init__()\n        self.value = value\n        # Cython recognizes subclasses of StopIteration with a .args tuple.\n        self.args = (value,)\n\n\nclass WaitIterator:\n    \"\"\"Provides an iterator to yield the results of awaitables as they finish.\n\n    Yielding a set of awaitables like this:\n\n    ``results = yield [awaitable1, awaitable2]``\n\n    pauses the coroutine until both ``awaitable1`` and ``awaitable2``\n    return, and then restarts the coroutine with the results of both\n    awaitables. If either awaitable raises an exception, the\n    expression will raise that exception and all the results will be\n    lost.\n\n    If you need to get the result of each awaitable as soon as possible,\n    or if you need the result of some awaitables even if others produce\n    errors, you can use ``WaitIterator``::\n\n      wait_iterator = gen.WaitIterator(awaitable1, awaitable2)\n      while not wait_iterator.done():\n          try:\n              result = yield wait_iterator.next()\n          except Exception as e:\n              print(\"Error {} from {}\".format(e, wait_iterator.current_future))\n          else:\n              print(\"Result {} received from {} at {}\".format(\n                  result, wait_iterator.current_future,\n                  wait_iterator.current_index))\n\n    Because results are returned as soon as they are available the\n    output from the iterator *will not be in the same order as the\n    input arguments*. If you need to know which future produced the\n    current result, you can use the attributes\n    ``WaitIterator.current_future``, or ``WaitIterator.current_index``\n    to get the index of the awaitable from the input list. (if keyword\n    arguments were used in the construction of the `WaitIterator`,\n    ``current_index`` will use the corresponding keyword).\n\n    `WaitIterator` implements the async iterator\n    protocol, so it can be used with the ``async for`` statement (note\n    that in this version the entire iteration is aborted if any value\n    raises an exception, while the previous example can continue past\n    individual errors)::\n\n      async for result in gen.WaitIterator(future1, future2):\n          print(\"Result {} received from {} at {}\".format(\n              result, wait_iterator.current_future,\n              wait_iterator.current_index))\n\n    .. versionadded:: 4.1\n\n    .. versionchanged:: 4.3\n       Added ``async for`` support in Python 3.5.\n\n    \"\"\"\n\n    _unfinished: dict[Future, int | str] = {}\n\n    def __init__(self, *args: Future, **kwargs: Future) -> None:\n        if args and kwargs:\n            raise ValueError(\"You must provide args or kwargs, not both\")\n\n        if kwargs:\n            self._unfinished = {f: k for (k, f) in kwargs.items()}\n            futures: Sequence[Future] = list(kwargs.values())\n        else:\n            self._unfinished = {f: i for (i, f) in enumerate(args)}\n            futures = args\n\n        self._finished: collections.deque[Future] = collections.deque()\n        self.current_index: str | int | None = None\n        self.current_future: Future | None = None\n        self._running_future: Future | None = None\n\n        for future in futures:\n            future_add_done_callback(future, self._done_callback)\n\n    def done(self) -> bool:\n        \"\"\"Returns True if this iterator has no more results.\"\"\"\n        if self._finished or self._unfinished:\n            return False\n        # Clear the 'current' values when iteration is done.\n        self.current_index = self.current_future = None\n        return True\n\n    def next(self) -> Future:\n        \"\"\"Returns a `.Future` that will yield the next available result.\n\n        Note that this `.Future` will not be the same object as any of\n        the inputs.\n        \"\"\"\n        self._running_future = Future()\n\n        if self._finished:\n            return self._return_result(self._finished.popleft())\n\n        return self._running_future\n\n    def _done_callback(self, done: Future) -> None:\n        if self._running_future and not self._running_future.done():\n            self._return_result(done)\n        else:\n            self._finished.append(done)\n\n    def _return_result(self, done: Future) -> Future:\n        \"\"\"Called set the returned future's state that of the future\n        we yielded, and set the current future for the iterator.\n        \"\"\"\n        if self._running_future is None:\n            raise Exception(\"no future is running\")\n        chain_future(done, self._running_future)\n\n        res = self._running_future\n        self._running_future = None\n        self.current_future = done\n        self.current_index = self._unfinished.pop(done)\n\n        return res\n\n    def __aiter__(self) -> typing.AsyncIterator:\n        return self\n\n    def __anext__(self) -> Future:\n        if self.done():\n            # Lookup by name to silence pyflakes on older versions.\n            raise getattr(builtins, \"StopAsyncIteration\")()\n        return self.next()\n\n\n@overload\ndef multi(\n    children: Sequence[_Yieldable],\n    quiet_exceptions: type[Exception] | tuple[type[Exception], ...] = (),\n) -> Future[list]: ...\n\n\n@overload\ndef multi(\n    children: Mapping[Any, _Yieldable],\n    quiet_exceptions: type[Exception] | tuple[type[Exception], ...] = (),\n) -> Future[dict]: ...\n\n\ndef multi(\n    children: Sequence[_Yieldable] | Mapping[Any, _Yieldable],\n    quiet_exceptions: \"Union[Type[Exception], Tuple[Type[Exception], ...]]\" = (),\n) -> \"Union[Future[List], Future[Dict]]\":\n    \"\"\"Runs multiple asynchronous operations in parallel.\n\n    ``children`` may either be a list or a dict whose values are\n    yieldable objects. ``multi()`` returns a new yieldable\n    object that resolves to a parallel structure containing their\n    results. If ``children`` is a list, the result is a list of\n    results in the same order; if it is a dict, the result is a dict\n    with the same keys.\n\n    That is, ``results = yield multi(list_of_futures)`` is equivalent\n    to::\n\n        results = []\n        for future in list_of_futures:\n            results.append(yield future)\n\n    If any children raise exceptions, ``multi()`` will raise the first\n    one. All others will be logged, unless they are of types\n    contained in the ``quiet_exceptions`` argument.\n\n    In a ``yield``-based coroutine, it is not normally necessary to\n    call this function directly, since the coroutine runner will\n    do it automatically when a list or dict is yielded. However,\n    it is necessary in ``await``-based coroutines, or to pass\n    the ``quiet_exceptions`` argument.\n\n    This function is available under the names ``multi()`` and ``Multi()``\n    for historical reasons.\n\n    Cancelling a `.Future` returned by ``multi()`` does not cancel its\n    children. `asyncio.gather` is similar to ``multi()``, but it does\n    cancel its children.\n\n    .. versionchanged:: 4.2\n       If multiple yieldables fail, any exceptions after the first\n       (which is raised) will be logged. Added the ``quiet_exceptions``\n       argument to suppress this logging for selected exception types.\n\n    .. versionchanged:: 4.3\n       Replaced the class ``Multi`` and the function ``multi_future``\n       with a unified function ``multi``. Added support for yieldables\n       other than ``YieldPoint`` and `.Future`.\n\n    \"\"\"\n    return multi_future(children, quiet_exceptions=quiet_exceptions)\n\n\nMulti = multi\n\n\ndef multi_future(\n    children: Sequence[_Yieldable] | Mapping[Any, _Yieldable],\n    quiet_exceptions: \"Union[Type[Exception], Tuple[Type[Exception], ...]]\" = (),\n) -> \"Union[Future[List], Future[Dict]]\":\n    \"\"\"Wait for multiple asynchronous futures in parallel.\n\n    Since Tornado 6.0, this function is exactly the same as `multi`.\n\n    .. versionadded:: 4.0\n\n    .. versionchanged:: 4.2\n       If multiple ``Futures`` fail, any exceptions after the first (which is\n       raised) will be logged. Added the ``quiet_exceptions``\n       argument to suppress this logging for selected exception types.\n\n    .. deprecated:: 4.3\n       Use `multi` instead.\n    \"\"\"\n    if isinstance(children, dict):\n        keys: list | None = list(children.keys())\n        children_seq: Iterable = children.values()\n    else:\n        keys = None\n        children_seq = children\n    children_futs = list(map(convert_yielded, children_seq))\n    assert all(is_future(i) or isinstance(i, _NullFuture) for i in children_futs)\n    unfinished_children = set(children_futs)\n\n    future = _create_future()\n    if not children_futs:\n        future_set_result_unless_cancelled(future, {} if keys is not None else [])\n\n    def callback(fut: Future) -> None:\n        unfinished_children.remove(fut)\n        if not unfinished_children:\n            result_list = []\n            for f in children_futs:\n                try:\n                    result_list.append(f.result())\n                except Exception as e:\n                    if future.done():\n                        if not isinstance(e, quiet_exceptions):\n                            app_log.error(\n                                \"Multiple exceptions in yield list\", exc_info=True\n                            )\n                    else:\n                        future_set_exc_info(future, sys.exc_info())\n            if not future.done():\n                if keys is not None:\n                    future_set_result_unless_cancelled(\n                        future, dict(zip(keys, result_list))\n                    )\n                else:\n                    future_set_result_unless_cancelled(future, result_list)\n\n    listening: set[Future] = set()\n    for f in children_futs:\n        if f not in listening:\n            listening.add(f)\n            future_add_done_callback(f, callback)\n    return future\n\n\ndef maybe_future(x: Any) -> Future:\n    \"\"\"Converts ``x`` into a `.Future`.\n\n    If ``x`` is already a `.Future`, it is simply returned; otherwise\n    it is wrapped in a new `.Future`.  This is suitable for use as\n    ``result = yield gen.maybe_future(f())`` when you don't know whether\n    ``f()`` returns a `.Future` or not.\n\n    .. deprecated:: 4.3\n       This function only handles ``Futures``, not other yieldable objects.\n       Instead of `maybe_future`, check for the non-future result types\n       you expect (often just ``None``), and ``yield`` anything unknown.\n    \"\"\"\n    if is_future(x):\n        return x\n    else:\n        fut = _create_future()\n        fut.set_result(x)\n        return fut\n\n\ndef with_timeout(\n    timeout: float | datetime.timedelta,\n    future: _Yieldable,\n    quiet_exceptions: \"Union[Type[Exception], Tuple[Type[Exception], ...]]\" = (),\n) -> Future:\n    \"\"\"Wraps a `.Future` (or other yieldable object) in a timeout.\n\n    Raises `tornado.util.TimeoutError` if the input future does not\n    complete before ``timeout``, which may be specified in any form\n    allowed by `.IOLoop.add_timeout` (i.e. a `datetime.timedelta` or\n    an absolute time relative to `.IOLoop.time`)\n\n    If the wrapped `.Future` fails after it has timed out, the exception\n    will be logged unless it is either of a type contained in\n    ``quiet_exceptions`` (which may be an exception type or a sequence of\n    types), or an ``asyncio.CancelledError``.\n\n    The wrapped `.Future` is not canceled when the timeout expires,\n    permitting it to be reused. `asyncio.wait_for` is similar to this\n    function but it does cancel the wrapped `.Future` on timeout.\n\n    .. versionadded:: 4.0\n\n    .. versionchanged:: 4.1\n       Added the ``quiet_exceptions`` argument and the logging of unhandled\n       exceptions.\n\n    .. versionchanged:: 4.4\n       Added support for yieldable objects other than `.Future`.\n\n    .. versionchanged:: 6.0.3\n       ``asyncio.CancelledError`` is now always considered \"quiet\".\n\n    .. versionchanged:: 6.2\n       ``tornado.util.TimeoutError`` is now an alias to ``asyncio.TimeoutError``.\n\n    \"\"\"\n    # It's tempting to optimize this by cancelling the input future on timeout\n    # instead of creating a new one, but A) we can't know if we are the only\n    # one waiting on the input future, so cancelling it might disrupt other\n    # callers and B) concurrent futures can only be cancelled while they are\n    # in the queue, so cancellation cannot reliably bound our waiting time.\n    future_converted = convert_yielded(future)\n    result = _create_future()\n    chain_future(future_converted, result)\n    io_loop = IOLoop.current()\n\n    def error_callback(future: Future) -> None:\n        try:\n            future.result()\n        except asyncio.CancelledError:\n            pass\n        except Exception as e:\n            if not isinstance(e, quiet_exceptions):\n                app_log.error(\n                    \"Exception in Future %r after timeout\", future, exc_info=True\n                )\n\n    def timeout_callback() -> None:\n        if not result.done():\n            result.set_exception(TimeoutError(\"Timeout\"))\n        # In case the wrapped future goes on to fail, log it.\n        future_add_done_callback(future_converted, error_callback)\n\n    timeout_handle = io_loop.add_timeout(timeout, timeout_callback)\n    if isinstance(future_converted, Future):\n        # We know this future will resolve on the IOLoop, so we don't\n        # need the extra thread-safety of IOLoop.add_future (and we also\n        # don't care about StackContext here.\n        future_add_done_callback(\n            future_converted, lambda future: io_loop.remove_timeout(timeout_handle)\n        )\n    else:\n        # concurrent.futures.Futures may resolve on any thread, so we\n        # need to route them back to the IOLoop.\n        io_loop.add_future(\n            future_converted, lambda future: io_loop.remove_timeout(timeout_handle)\n        )\n    return result\n\n\ndef sleep(duration: float) -> \"Future[None]\":\n    \"\"\"Return a `.Future` that resolves after the given number of seconds.\n\n    When used with ``yield`` in a coroutine, this is a non-blocking\n    analogue to `time.sleep` (which should not be used in coroutines\n    because it is blocking)::\n\n        yield gen.sleep(0.5)\n\n    Note that calling this function on its own does nothing; you must\n    wait on the `.Future` it returns (usually by yielding it).\n\n    .. versionadded:: 4.1\n    \"\"\"\n    f = _create_future()\n    IOLoop.current().call_later(\n        duration, lambda: future_set_result_unless_cancelled(f, None)\n    )\n    return f\n\n\nclass _NullFuture:\n    \"\"\"_NullFuture resembles a Future that finished with a result of None.\n\n    It's not actually a `Future` to avoid depending on a particular event loop.\n    Handled as a special case in the coroutine runner.\n\n    We lie and tell the type checker that a _NullFuture is a Future so\n    we don't have to leak _NullFuture into lots of public APIs. But\n    this means that the type checker can't warn us when we're passing\n    a _NullFuture into a code path that doesn't understand what to do\n    with it.\n    \"\"\"\n\n    def result(self) -> None:\n        return None\n\n    def done(self) -> bool:\n        return True\n\n\n# _null_future is used as a dummy value in the coroutine runner. It differs\n# from moment in that moment always adds a delay of one IOLoop iteration\n# while _null_future is processed as soon as possible.\n_null_future = typing.cast(Future, _NullFuture())\n\nmoment = typing.cast(Future, _NullFuture())\nmoment.__doc__ = \"\"\"A special object which may be yielded to allow the IOLoop to run for\none iteration.\n\nThis is not needed in normal use but it can be helpful in long-running\ncoroutines that are likely to yield Futures that are ready instantly.\n\nUsage: ``yield gen.moment``\n\nIn native coroutines, the equivalent of ``yield gen.moment`` is\n``await asyncio.sleep(0)``.\n\n.. versionadded:: 4.0\n\n.. deprecated:: 4.5\n   ``yield None`` (or ``yield`` with no argument) is now equivalent to\n    ``yield gen.moment``.\n\"\"\"\n\n\nclass Runner:\n    \"\"\"Internal implementation of `tornado.gen.coroutine`.\n\n    Maintains information about pending callbacks and their results.\n\n    The results of the generator are stored in ``result_future`` (a\n    `.Future`)\n    \"\"\"\n\n    def __init__(\n        self,\n        ctx_run: Callable,\n        gen: \"Generator[_Yieldable, Any, _T]\",\n        result_future: \"Future[_T]\",\n        first_yielded: _Yieldable,\n    ) -> None:\n        self.ctx_run = ctx_run\n        self.gen = gen\n        self.result_future = result_future\n        self.future: None | Future = _null_future\n        self.running = False\n        self.finished = False\n        self.io_loop = IOLoop.current()\n        if self.ctx_run(self.handle_yield, first_yielded):\n            gen = result_future = first_yielded = None  # type: ignore\n            self.ctx_run(self.run)\n\n    def run(self) -> None:\n        \"\"\"Starts or resumes the generator, running until it reaches a\n        yield point that is not ready.\n        \"\"\"\n        if self.running or self.finished:\n            return\n        try:\n            self.running = True\n            while True:\n                future = self.future\n                if future is None:\n                    raise Exception(\"No pending future\")\n                if not future.done():\n                    return\n                self.future = None\n                try:\n                    try:\n                        value = future.result()\n                    except Exception as e:\n                        # Save the exception for later. It's important that\n                        # gen.throw() not be called inside this try/except block\n                        # because that makes sys.exc_info behave unexpectedly.\n                        exc: Exception | None = e\n                    else:\n                        exc = None\n                    finally:\n                        future = None\n\n                    if exc is not None:\n                        try:\n                            yielded = self.gen.throw(exc)\n                        finally:\n                            # Break up a circular reference for faster GC on\n                            # CPython.\n                            del exc\n                    else:\n                        yielded = self.gen.send(value)\n\n                except (StopIteration, Return) as e:\n                    self.finished = True\n                    self.future = _null_future\n                    future_set_result_unless_cancelled(\n                        self.result_future, _value_from_stopiteration(e)\n                    )\n                    self.result_future = None  # type: ignore\n                    return\n                except Exception:\n                    self.finished = True\n                    self.future = _null_future\n                    future_set_exc_info(self.result_future, sys.exc_info())\n                    self.result_future = None  # type: ignore\n                    return\n                if not self.handle_yield(yielded):\n                    return\n                yielded = None\n        finally:\n            self.running = False\n\n    def handle_yield(self, yielded: _Yieldable) -> bool:\n        try:\n            self.future = convert_yielded(yielded)\n        except BadYieldError:\n            self.future = Future()\n            future_set_exc_info(self.future, sys.exc_info())\n\n        if self.future is moment:\n            self.io_loop.add_callback(self.ctx_run, self.run)\n            return False\n        elif self.future is None:\n            raise Exception(\"no pending future\")\n        elif not self.future.done():\n\n            def inner(f: Any) -> None:\n                # Break a reference cycle to speed GC.\n                f = None  # noqa: F841\n                self.ctx_run(self.run)\n\n            self.io_loop.add_future(self.future, inner)\n            return False\n        return True\n\n    def handle_exception(\n        self, typ: type[Exception], value: Exception, tb: types.TracebackType\n    ) -> bool:\n        if not self.running and not self.finished:\n            self.future = Future()\n            future_set_exc_info(self.future, (typ, value, tb))\n            self.ctx_run(self.run)\n            return True\n        else:\n            return False\n\n\ndef _wrap_awaitable(awaitable: Awaitable) -> Future:\n    # Convert Awaitables into Futures.\n    # Note that we use ensure_future, which handles both awaitables\n    # and coroutines, rather than create_task, which only accepts\n    # coroutines. (ensure_future calls create_task if given a coroutine)\n    fut = asyncio.ensure_future(awaitable)\n    # See comments on IOLoop._pending_tasks.\n    loop = IOLoop.current()\n    loop._register_task(fut)\n    fut.add_done_callback(lambda f: loop._unregister_task(f))\n    return fut\n\n\ndef convert_yielded(yielded: _Yieldable) -> Future:\n    \"\"\"Convert a yielded object into a `.Future`.\n\n    The default implementation accepts lists, dictionaries, and\n    Futures. This has the side effect of starting any coroutines that\n    did not start themselves, similar to `asyncio.ensure_future`.\n\n    If the `~functools.singledispatch` library is available, this function\n    may be extended to support additional types. For example::\n\n        @convert_yielded.register(asyncio.Future)\n        def _(asyncio_future):\n            return tornado.platform.asyncio.to_tornado_future(asyncio_future)\n\n    .. versionadded:: 4.1\n\n    \"\"\"\n    if yielded is None or yielded is moment:\n        return moment\n    elif yielded is _null_future:\n        return _null_future\n    elif isinstance(yielded, (list, dict)):\n        return multi(yielded)  # type: ignore\n    elif is_future(yielded):\n        return typing.cast(Future, yielded)\n    elif isawaitable(yielded):\n        return _wrap_awaitable(yielded)  # type: ignore\n    else:\n        raise BadYieldError(f\"yielded unknown object {yielded!r}\")\n\n\nconvert_yielded = singledispatch(convert_yielded)\n"
  },
  {
    "path": "tornado/http1connection.py",
    "content": "#\n# Copyright 2014 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Client and server implementations of HTTP/1.x.\n\n.. versionadded:: 4.0\n\"\"\"\n\nimport asyncio\nimport logging\nimport re\nimport types\nfrom collections.abc import Awaitable, Callable\nfrom typing import Optional, Type, cast\n\nfrom tornado import gen, httputil, iostream\nfrom tornado.concurrent import (\n    Future,\n    future_add_done_callback,\n    future_set_result_unless_cancelled,\n)\nfrom tornado.escape import native_str, utf8\nfrom tornado.log import app_log, gen_log\nfrom tornado.util import GzipDecompressor\n\nCR_OR_LF_RE = re.compile(b\"\\r|\\n\")\n\n\nclass _QuietException(Exception):\n    def __init__(self) -> None:\n        pass\n\n\nclass _ExceptionLoggingContext:\n    \"\"\"Used with the ``with`` statement when calling delegate methods to\n    log any exceptions with the given logger.  Any exceptions caught are\n    converted to _QuietException\n    \"\"\"\n\n    def __init__(self, logger: logging.Logger) -> None:\n        self.logger = logger\n\n    def __enter__(self) -> None:\n        pass\n\n    def __exit__(\n        self,\n        typ: \"Optional[Type[BaseException]]\",\n        value: BaseException | None,\n        tb: types.TracebackType,\n    ) -> None:\n        if value is not None:\n            assert typ is not None\n            # Let HTTPInputError pass through to higher-level handler\n            if isinstance(value, httputil.HTTPInputError):\n                return None\n            self.logger.error(\"Uncaught exception\", exc_info=(typ, value, tb))\n            raise _QuietException\n\n\nclass HTTP1ConnectionParameters:\n    \"\"\"Parameters for `.HTTP1Connection` and `.HTTP1ServerConnection`.\"\"\"\n\n    def __init__(\n        self,\n        no_keep_alive: bool = False,\n        chunk_size: int | None = None,\n        max_header_size: int | None = None,\n        header_timeout: float | None = None,\n        max_body_size: int | None = None,\n        body_timeout: float | None = None,\n        decompress: bool = False,\n    ) -> None:\n        \"\"\"\n        :arg bool no_keep_alive: If true, always close the connection after\n            one request.\n        :arg int chunk_size: how much data to read into memory at once\n        :arg int max_header_size:  maximum amount of data for HTTP headers\n        :arg float header_timeout: how long to wait for all headers (seconds)\n        :arg int max_body_size: maximum amount of data for body\n        :arg float body_timeout: how long to wait while reading body (seconds)\n        :arg bool decompress: if true, decode incoming\n            ``Content-Encoding: gzip``\n        \"\"\"\n        self.no_keep_alive = no_keep_alive\n        self.chunk_size = chunk_size or 65536\n        self.max_header_size = max_header_size or 65536\n        self.header_timeout = header_timeout\n        self.max_body_size = max_body_size\n        self.body_timeout = body_timeout\n        self.decompress = decompress\n\n\nclass HTTP1Connection(httputil.HTTPConnection):\n    \"\"\"Implements the HTTP/1.x protocol.\n\n    This class can be on its own for clients, or via `HTTP1ServerConnection`\n    for servers.\n    \"\"\"\n\n    def __init__(\n        self,\n        stream: iostream.IOStream,\n        is_client: bool,\n        params: HTTP1ConnectionParameters | None = None,\n        context: object | None = None,\n    ) -> None:\n        \"\"\"\n        :arg stream: an `.IOStream`\n        :arg bool is_client: client or server\n        :arg params: a `.HTTP1ConnectionParameters` instance or ``None``\n        :arg context: an opaque application-defined object that can be accessed\n            as ``connection.context``.\n        \"\"\"\n        self.is_client = is_client\n        self.stream = stream\n        if params is None:\n            params = HTTP1ConnectionParameters()\n        self.params = params\n        self.context = context\n        self.no_keep_alive = params.no_keep_alive\n        # The body limits can be altered by the delegate, so save them\n        # here instead of just referencing self.params later.\n        self._max_body_size = (\n            self.params.max_body_size\n            if self.params.max_body_size is not None\n            else self.stream.max_buffer_size\n        )\n        self._body_timeout = self.params.body_timeout\n        # _write_finished is set to True when finish() has been called,\n        # i.e. there will be no more data sent.  Data may still be in the\n        # stream's write buffer.\n        self._write_finished = False\n        # True when we have read the entire incoming body.\n        self._read_finished = False\n        # _finish_future resolves when all data has been written and flushed\n        # to the IOStream.\n        self._finish_future: Future[None] = Future()\n        # If true, the connection should be closed after this request\n        # (after the response has been written in the server side,\n        # and after it has been read in the client)\n        self._disconnect_on_finish = False\n        self._clear_callbacks()\n        # Save the start lines after we read or write them; they\n        # affect later processing (e.g. 304 responses and HEAD methods\n        # have content-length but no bodies)\n        self._request_start_line: httputil.RequestStartLine | None = None\n        self._response_start_line: httputil.ResponseStartLine | None = None\n        self._request_headers: httputil.HTTPHeaders | None = None\n        # True if we are writing output with chunked encoding.\n        self._chunking_output = False\n        # While reading a body with a content-length, this is the\n        # amount left to read.\n        self._expected_content_remaining: int | None = None\n        # A Future for our outgoing writes, returned by IOStream.write.\n        self._pending_write: Future[None] | None = None\n\n    def read_response(self, delegate: httputil.HTTPMessageDelegate) -> Awaitable[bool]:\n        \"\"\"Read a single HTTP response.\n\n        Typical client-mode usage is to write a request using `write_headers`,\n        `write`, and `finish`, and then call ``read_response``.\n\n        :arg delegate: a `.HTTPMessageDelegate`\n\n        Returns a `.Future` that resolves to a bool after the full response has\n        been read. The result is true if the stream is still open.\n        \"\"\"\n        if self.params.decompress:\n            delegate = _GzipMessageDelegate(delegate, self.params.chunk_size)\n        return self._read_message(delegate)\n\n    async def _read_message(self, delegate: httputil.HTTPMessageDelegate) -> bool:\n        need_delegate_close = False\n        try:\n            header_future = self.stream.read_until_regex(\n                b\"\\r?\\n\\r?\\n\", max_bytes=self.params.max_header_size\n            )\n            if self.params.header_timeout is None:\n                header_data = await header_future\n            else:\n                try:\n                    header_data = await gen.with_timeout(\n                        self.stream.io_loop.time() + self.params.header_timeout,\n                        header_future,\n                        quiet_exceptions=iostream.StreamClosedError,\n                    )\n                except gen.TimeoutError:\n                    self.close()\n                    return False\n            start_line_str, headers = self._parse_headers(header_data)\n            if self.is_client:\n                resp_start_line = httputil.parse_response_start_line(start_line_str)\n                self._response_start_line = resp_start_line\n                start_line: httputil.RequestStartLine | httputil.ResponseStartLine = (\n                    resp_start_line\n                )\n                # TODO: this will need to change to support client-side keepalive\n                self._disconnect_on_finish = False\n            else:\n                req_start_line = httputil.parse_request_start_line(start_line_str)\n                self._request_start_line = req_start_line\n                self._request_headers = headers\n                start_line = req_start_line\n                self._disconnect_on_finish = not self._can_keep_alive(\n                    req_start_line, headers\n                )\n            need_delegate_close = True\n            with _ExceptionLoggingContext(app_log):\n                header_recv_future = delegate.headers_received(start_line, headers)\n                if header_recv_future is not None:\n                    await header_recv_future\n            if self.stream is None:\n                # We've been detached.\n                need_delegate_close = False\n                return False\n            skip_body = False\n            if self.is_client:\n                assert isinstance(start_line, httputil.ResponseStartLine)\n                if (\n                    self._request_start_line is not None\n                    and self._request_start_line.method == \"HEAD\"\n                ):\n                    skip_body = True\n                code = start_line.code\n                if code == 304:\n                    # 304 responses may include the content-length header\n                    # but do not actually have a body.\n                    # http://tools.ietf.org/html/rfc7230#section-3.3\n                    skip_body = True\n                if 100 <= code < 200:\n                    # 1xx responses should never indicate the presence of\n                    # a body.\n                    if \"Content-Length\" in headers or \"Transfer-Encoding\" in headers:\n                        raise httputil.HTTPInputError(\n                            \"Response code %d cannot have body\" % code\n                        )\n                    # TODO: client delegates will get headers_received twice\n                    # in the case of a 100-continue.  Document or change?\n                    await self._read_message(delegate)\n            else:\n                if headers.get(\"Expect\") == \"100-continue\" and not self._write_finished:\n                    self.stream.write(b\"HTTP/1.1 100 (Continue)\\r\\n\\r\\n\")\n            if not skip_body:\n                body_future = self._read_body(\n                    resp_start_line.code if self.is_client else 0, headers, delegate\n                )\n                if body_future is not None:\n                    if self._body_timeout is None:\n                        await body_future\n                    else:\n                        try:\n                            await gen.with_timeout(\n                                self.stream.io_loop.time() + self._body_timeout,\n                                body_future,\n                                quiet_exceptions=iostream.StreamClosedError,\n                            )\n                        except gen.TimeoutError:\n                            gen_log.info(\"Timeout reading body from %s\", self.context)\n                            self.stream.close()\n                            return False\n            self._read_finished = True\n            if not self._write_finished or self.is_client:\n                need_delegate_close = False\n                with _ExceptionLoggingContext(app_log):\n                    delegate.finish()\n            # If we're waiting for the application to produce an asynchronous\n            # response, and we're not detached, register a close callback\n            # on the stream (we didn't need one while we were reading)\n            if (\n                not self._finish_future.done()\n                and self.stream is not None\n                and not self.stream.closed()\n            ):\n                self.stream.set_close_callback(self._on_connection_close)\n                await self._finish_future\n            if self.is_client and self._disconnect_on_finish:\n                self.close()\n            if self.stream is None:\n                return False\n        except httputil.HTTPInputError as e:\n            gen_log.info(\"Malformed HTTP message from %s: %s\", self.context, e)\n            if not self.is_client:\n                await self.stream.write(b\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\")\n            self.close()\n            return False\n        finally:\n            if need_delegate_close:\n                with _ExceptionLoggingContext(app_log):\n                    delegate.on_connection_close()\n            header_future = None  # type: ignore\n            self._clear_callbacks()\n        return True\n\n    def _clear_callbacks(self) -> None:\n        \"\"\"Clears the callback attributes.\n\n        This allows the request handler to be garbage collected more\n        quickly in CPython by breaking up reference cycles.\n        \"\"\"\n        self._write_callback = None\n        self._write_future: Future[None] | None = None\n        self._close_callback: Callable[[], None] | None = None\n        if self.stream is not None:\n            self.stream.set_close_callback(None)\n\n    def set_close_callback(self, callback: Callable[[], None] | None) -> None:\n        \"\"\"Sets a callback that will be run when the connection is closed.\n\n        Note that this callback is slightly different from\n        `.HTTPMessageDelegate.on_connection_close`: The\n        `.HTTPMessageDelegate` method is called when the connection is\n        closed while receiving a message. This callback is used when\n        there is not an active delegate (for example, on the server\n        side this callback is used if the client closes the connection\n        after sending its request but before receiving all the\n        response.\n        \"\"\"\n        self._close_callback = callback\n\n    def _on_connection_close(self) -> None:\n        # Note that this callback is only registered on the IOStream\n        # when we have finished reading the request and are waiting for\n        # the application to produce its response.\n        if self._close_callback is not None:\n            callback = self._close_callback\n            self._close_callback = None\n            callback()\n        if not self._finish_future.done():\n            future_set_result_unless_cancelled(self._finish_future, None)\n        self._clear_callbacks()\n\n    def close(self) -> None:\n        if self.stream is not None:\n            self.stream.close()\n        self._clear_callbacks()\n        if not self._finish_future.done():\n            future_set_result_unless_cancelled(self._finish_future, None)\n\n    def detach(self) -> iostream.IOStream:\n        \"\"\"Take control of the underlying stream.\n\n        Returns the underlying `.IOStream` object and stops all further\n        HTTP processing.  May only be called during\n        `.HTTPMessageDelegate.headers_received`.  Intended for implementing\n        protocols like websockets that tunnel over an HTTP handshake.\n        \"\"\"\n        self._clear_callbacks()\n        stream = self.stream\n        self.stream = None  # type: ignore\n        if not self._finish_future.done():\n            future_set_result_unless_cancelled(self._finish_future, None)\n        return stream\n\n    def set_body_timeout(self, timeout: float) -> None:\n        \"\"\"Sets the body timeout for a single request.\n\n        Overrides the value from `.HTTP1ConnectionParameters`.\n        \"\"\"\n        self._body_timeout = timeout\n\n    def set_max_body_size(self, max_body_size: int) -> None:\n        \"\"\"Sets the body size limit for a single request.\n\n        Overrides the value from `.HTTP1ConnectionParameters`.\n        \"\"\"\n        self._max_body_size = max_body_size\n\n    def write_headers(\n        self,\n        start_line: httputil.RequestStartLine | httputil.ResponseStartLine,\n        headers: httputil.HTTPHeaders,\n        chunk: bytes | None = None,\n    ) -> \"Future[None]\":\n        \"\"\"Implements `.HTTPConnection.write_headers`.\"\"\"\n        lines = []\n        if self.is_client:\n            assert isinstance(start_line, httputil.RequestStartLine)\n            self._request_start_line = start_line\n            lines.append(utf8(f\"{start_line[0]} {start_line[1]} HTTP/1.1\"))\n            # Client requests with a non-empty body must have either a\n            # Content-Length or a Transfer-Encoding. If Content-Length is not\n            # present we'll add our Transfer-Encoding below.\n            self._chunking_output = (\n                start_line.method in (\"POST\", \"PUT\", \"PATCH\")\n                and \"Content-Length\" not in headers\n            )\n        else:\n            assert isinstance(start_line, httputil.ResponseStartLine)\n            assert self._request_start_line is not None\n            assert self._request_headers is not None\n            self._response_start_line = start_line\n            lines.append(utf8(\"HTTP/1.1 %d %s\" % (start_line[1], start_line[2])))\n            self._chunking_output = (\n                # TODO: should this use\n                # self._request_start_line.version or\n                # start_line.version?\n                self._request_start_line.version == \"HTTP/1.1\"\n                # Omit payload header field for HEAD request.\n                and self._request_start_line.method != \"HEAD\"\n                # 1xx, 204 and 304 responses have no body (not even a zero-length\n                # body), and so should not have either Content-Length or\n                # Transfer-Encoding headers.\n                and start_line.code not in (204, 304)\n                and (start_line.code < 100 or start_line.code >= 200)\n                # No need to chunk the output if a Content-Length is specified.\n                and \"Content-Length\" not in headers\n            )\n            # If connection to a 1.1 client will be closed, inform client\n            if (\n                self._request_start_line.version == \"HTTP/1.1\"\n                and self._disconnect_on_finish\n            ):\n                headers[\"Connection\"] = \"close\"\n            # If a 1.0 client asked for keep-alive, add the header.\n            if (\n                self._request_start_line.version == \"HTTP/1.0\"\n                and self._request_headers.get(\"Connection\", \"\").lower() == \"keep-alive\"\n            ):\n                headers[\"Connection\"] = \"Keep-Alive\"\n        if self._chunking_output:\n            headers[\"Transfer-Encoding\"] = \"chunked\"\n        if not self.is_client and (\n            self._request_start_line.method == \"HEAD\"\n            or cast(httputil.ResponseStartLine, start_line).code == 304\n        ):\n            self._expected_content_remaining = 0\n        elif \"Content-Length\" in headers:\n            self._expected_content_remaining = parse_int(headers[\"Content-Length\"])\n        else:\n            self._expected_content_remaining = None\n        # TODO: headers are supposed to be of type str, but we still have some\n        # cases that let bytes slip through. Remove these native_str calls when those\n        # are fixed.\n        header_lines = (\n            native_str(n) + \": \" + native_str(v) for n, v in headers.get_all()\n        )\n        lines.extend(line.encode(\"latin1\") for line in header_lines)\n        for line in lines:\n            if CR_OR_LF_RE.search(line):\n                raise ValueError(\"Illegal characters (CR or LF) in header: %r\" % line)\n        future = None\n        if self.stream.closed():\n            future = self._write_future = Future()\n            future.set_exception(iostream.StreamClosedError())\n            future.exception()\n        else:\n            future = self._write_future = Future()\n            data = b\"\\r\\n\".join(lines) + b\"\\r\\n\\r\\n\"\n            if chunk:\n                data += self._format_chunk(chunk)\n            self._pending_write = self.stream.write(data)\n            future_add_done_callback(self._pending_write, self._on_write_complete)\n        return future\n\n    def _format_chunk(self, chunk: bytes) -> bytes:\n        if self._expected_content_remaining is not None:\n            self._expected_content_remaining -= len(chunk)\n            if self._expected_content_remaining < 0:\n                # Close the stream now to stop further framing errors.\n                self.stream.close()\n                raise httputil.HTTPOutputError(\n                    \"Tried to write more data than Content-Length\"\n                )\n        if self._chunking_output and chunk:\n            # Don't write out empty chunks because that means END-OF-STREAM\n            # with chunked encoding\n            return utf8(\"%x\" % len(chunk)) + b\"\\r\\n\" + chunk + b\"\\r\\n\"\n        else:\n            return chunk\n\n    def write(self, chunk: bytes) -> \"Future[None]\":\n        \"\"\"Implements `.HTTPConnection.write`.\n\n        For backwards compatibility it is allowed but deprecated to\n        skip `write_headers` and instead call `write()` with a\n        pre-encoded header block.\n        \"\"\"\n        future = None\n        if self.stream.closed():\n            future = self._write_future = Future()\n            self._write_future.set_exception(iostream.StreamClosedError())\n            self._write_future.exception()\n        else:\n            future = self._write_future = Future()\n            self._pending_write = self.stream.write(self._format_chunk(chunk))\n            future_add_done_callback(self._pending_write, self._on_write_complete)\n        return future\n\n    def finish(self) -> None:\n        \"\"\"Implements `.HTTPConnection.finish`.\"\"\"\n        if (\n            self._expected_content_remaining is not None\n            and self._expected_content_remaining != 0\n            and not self.stream.closed()\n        ):\n            self.stream.close()\n            raise httputil.HTTPOutputError(\n                \"Tried to write %d bytes less than Content-Length\"\n                % self._expected_content_remaining\n            )\n        if self._chunking_output:\n            if not self.stream.closed():\n                self._pending_write = self.stream.write(b\"0\\r\\n\\r\\n\")\n                self._pending_write.add_done_callback(self._on_write_complete)\n        self._write_finished = True\n        # If the app finished the request while we're still reading,\n        # divert any remaining data away from the delegate and\n        # close the connection when we're done sending our response.\n        # Closing the connection is the only way to avoid reading the\n        # whole input body.\n        if not self._read_finished:\n            self._disconnect_on_finish = True\n        # No more data is coming, so instruct TCP to send any remaining\n        # data immediately instead of waiting for a full packet or ack.\n        self.stream.set_nodelay(True)\n        if self._pending_write is None:\n            self._finish_request(None)\n        else:\n            future_add_done_callback(self._pending_write, self._finish_request)\n\n    def _on_write_complete(self, future: \"Future[None]\") -> None:\n        exc = future.exception()\n        if exc is not None and not isinstance(exc, iostream.StreamClosedError):\n            future.result()\n        if self._write_callback is not None:\n            callback = self._write_callback\n            self._write_callback = None\n            self.stream.io_loop.add_callback(callback)\n        if self._write_future is not None:\n            future = self._write_future\n            self._write_future = None\n            future_set_result_unless_cancelled(future, None)\n\n    def _can_keep_alive(\n        self, start_line: httputil.RequestStartLine, headers: httputil.HTTPHeaders\n    ) -> bool:\n        if self.params.no_keep_alive:\n            return False\n        connection_header = headers.get(\"Connection\")\n        if connection_header is not None:\n            connection_header = connection_header.lower()\n        if start_line.version == \"HTTP/1.1\":\n            return connection_header != \"close\"\n        elif (\n            \"Content-Length\" in headers\n            or is_transfer_encoding_chunked(headers)\n            or getattr(start_line, \"method\", None) in (\"HEAD\", \"GET\")\n        ):\n            # start_line may be a request or response start line; only\n            # the former has a method attribute.\n            return connection_header == \"keep-alive\"\n        return False\n\n    def _finish_request(self, future: \"Optional[Future[None]]\") -> None:\n        self._clear_callbacks()\n        if not self.is_client and self._disconnect_on_finish:\n            self.close()\n            return\n        # Turn Nagle's algorithm back on, leaving the stream in its\n        # default state for the next request.\n        self.stream.set_nodelay(False)\n        if not self._finish_future.done():\n            future_set_result_unless_cancelled(self._finish_future, None)\n\n    def _parse_headers(self, data: bytes) -> tuple[str, httputil.HTTPHeaders]:\n        # The lstrip removes newlines that some implementations sometimes\n        # insert between messages of a reused connection.  Per RFC 7230,\n        # we SHOULD ignore at least one empty line before the request.\n        # http://tools.ietf.org/html/rfc7230#section-3.5\n        data_str = native_str(data.decode(\"latin1\")).lstrip(\"\\r\\n\")\n        # RFC 7230 section allows for both CRLF and bare LF.\n        eol = data_str.find(\"\\n\")\n        start_line = data_str[:eol].rstrip(\"\\r\")\n        headers = httputil.HTTPHeaders.parse(data_str[eol:])\n        return start_line, headers\n\n    def _read_body(\n        self,\n        code: int,\n        headers: httputil.HTTPHeaders,\n        delegate: httputil.HTTPMessageDelegate,\n    ) -> Awaitable[None] | None:\n        if \"Content-Length\" in headers:\n            if \",\" in headers[\"Content-Length\"]:\n                # Proxies sometimes cause Content-Length headers to get\n                # duplicated.  If all the values are identical then we can\n                # use them but if they differ it's an error.\n                pieces = re.split(r\",\\s*\", headers[\"Content-Length\"])\n                if any(i != pieces[0] for i in pieces):\n                    raise httputil.HTTPInputError(\n                        \"Multiple unequal Content-Lengths: %r\"\n                        % headers[\"Content-Length\"]\n                    )\n                headers[\"Content-Length\"] = pieces[0]\n\n            try:\n                content_length: int | None = parse_int(headers[\"Content-Length\"])\n            except ValueError:\n                # Handles non-integer Content-Length value.\n                raise httputil.HTTPInputError(\n                    \"Only integer Content-Length is allowed: %s\"\n                    % headers[\"Content-Length\"]\n                )\n\n            if cast(int, content_length) > self._max_body_size:\n                raise httputil.HTTPInputError(\"Content-Length too long\")\n        else:\n            content_length = None\n\n        is_chunked = is_transfer_encoding_chunked(headers)\n\n        if code == 204:\n            # This response code is not allowed to have a non-empty body,\n            # and has an implicit length of zero instead of read-until-close.\n            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3\n            if is_chunked or content_length not in (None, 0):\n                raise httputil.HTTPInputError(\n                    \"Response with code %d should not have body\" % code\n                )\n            content_length = 0\n\n        if is_chunked:\n            return self._read_chunked_body(delegate)\n        if content_length is not None:\n            return self._read_fixed_body(content_length, delegate)\n        if self.is_client:\n            return self._read_body_until_close(delegate)\n        return None\n\n    async def _read_fixed_body(\n        self, content_length: int, delegate: httputil.HTTPMessageDelegate\n    ) -> None:\n        while content_length > 0:\n            body = await self.stream.read_bytes(\n                min(self.params.chunk_size, content_length), partial=True\n            )\n            content_length -= len(body)\n            if not self._write_finished or self.is_client:\n                with _ExceptionLoggingContext(app_log):\n                    ret = delegate.data_received(body)\n                    if ret is not None:\n                        await ret\n\n    async def _read_chunked_body(self, delegate: httputil.HTTPMessageDelegate) -> None:\n        # TODO: \"chunk extensions\" http://tools.ietf.org/html/rfc2616#section-3.6.1\n        total_size = 0\n        while True:\n            chunk_len_str = await self.stream.read_until(b\"\\r\\n\", max_bytes=64)\n            try:\n                chunk_len = parse_hex_int(native_str(chunk_len_str[:-2]))\n            except ValueError:\n                raise httputil.HTTPInputError(\"invalid chunk size\")\n            if chunk_len == 0:\n                crlf = await self.stream.read_bytes(2)\n                if crlf != b\"\\r\\n\":\n                    raise httputil.HTTPInputError(\n                        \"improperly terminated chunked request\"\n                    )\n                return\n            total_size += chunk_len\n            if total_size > self._max_body_size:\n                raise httputil.HTTPInputError(\"chunked body too large\")\n            bytes_to_read = chunk_len\n            while bytes_to_read:\n                chunk = await self.stream.read_bytes(\n                    min(bytes_to_read, self.params.chunk_size), partial=True\n                )\n                bytes_to_read -= len(chunk)\n                if not self._write_finished or self.is_client:\n                    with _ExceptionLoggingContext(app_log):\n                        ret = delegate.data_received(chunk)\n                        if ret is not None:\n                            await ret\n            # chunk ends with \\r\\n\n            crlf = await self.stream.read_bytes(2)\n            assert crlf == b\"\\r\\n\"\n\n    async def _read_body_until_close(\n        self, delegate: httputil.HTTPMessageDelegate\n    ) -> None:\n        body = await self.stream.read_until_close()\n        if not self._write_finished or self.is_client:\n            with _ExceptionLoggingContext(app_log):\n                ret = delegate.data_received(body)\n                if ret is not None:\n                    await ret\n\n\nclass _GzipMessageDelegate(httputil.HTTPMessageDelegate):\n    \"\"\"Wraps an `HTTPMessageDelegate` to decode ``Content-Encoding: gzip``.\"\"\"\n\n    def __init__(self, delegate: httputil.HTTPMessageDelegate, chunk_size: int) -> None:\n        self._delegate = delegate\n        self._chunk_size = chunk_size\n        self._decompressor: GzipDecompressor | None = None\n\n    def headers_received(\n        self,\n        start_line: httputil.RequestStartLine | httputil.ResponseStartLine,\n        headers: httputil.HTTPHeaders,\n    ) -> Awaitable[None] | None:\n        if headers.get(\"Content-Encoding\", \"\").lower() == \"gzip\":\n            self._decompressor = GzipDecompressor()\n            # Downstream delegates will only see uncompressed data,\n            # so rename the content-encoding header.\n            # (but note that curl_httpclient doesn't do this).\n            headers.add(\"X-Consumed-Content-Encoding\", headers[\"Content-Encoding\"])\n            del headers[\"Content-Encoding\"]\n        return self._delegate.headers_received(start_line, headers)\n\n    async def data_received(self, chunk: bytes) -> None:\n        if self._decompressor:\n            compressed_data = chunk\n            while compressed_data:\n                decompressed = self._decompressor.decompress(\n                    compressed_data, self._chunk_size\n                )\n                if decompressed:\n                    ret = self._delegate.data_received(decompressed)\n                    if ret is not None:\n                        await ret\n                compressed_data = self._decompressor.unconsumed_tail\n                if compressed_data and not decompressed:\n                    raise httputil.HTTPInputError(\n                        \"encountered unconsumed gzip data without making progress\"\n                    )\n        else:\n            ret = self._delegate.data_received(chunk)\n            if ret is not None:\n                await ret\n\n    def finish(self) -> None:\n        if self._decompressor is not None:\n            tail = self._decompressor.flush()\n            if tail:\n                # The tail should always be empty: decompress returned\n                # all that it can in data_received and the only\n                # purpose of the flush call is to detect errors such\n                # as truncated input. If we did legitimately get a new\n                # chunk at this point we'd need to change the\n                # interface to make finish() a coroutine.\n                raise ValueError(\n                    \"decompressor.flush returned data; possible truncated input\"\n                )\n        return self._delegate.finish()\n\n    def on_connection_close(self) -> None:\n        return self._delegate.on_connection_close()\n\n\nclass HTTP1ServerConnection:\n    \"\"\"An HTTP/1.x server.\"\"\"\n\n    def __init__(\n        self,\n        stream: iostream.IOStream,\n        params: HTTP1ConnectionParameters | None = None,\n        context: object | None = None,\n    ) -> None:\n        \"\"\"\n        :arg stream: an `.IOStream`\n        :arg params: a `.HTTP1ConnectionParameters` or None\n        :arg context: an opaque application-defined object that is accessible\n            as ``connection.context``\n        \"\"\"\n        self.stream = stream\n        if params is None:\n            params = HTTP1ConnectionParameters()\n        self.params = params\n        self.context = context\n        self._serving_future: Future[None] | None = None\n\n    async def close(self) -> None:\n        \"\"\"Closes the connection.\n\n        Returns a `.Future` that resolves after the serving loop has exited.\n        \"\"\"\n        self.stream.close()\n        # Block until the serving loop is done, but ignore any exceptions\n        # (start_serving is already responsible for logging them).\n        assert self._serving_future is not None\n        try:\n            await self._serving_future\n        except Exception:\n            pass\n\n    def start_serving(self, delegate: httputil.HTTPServerConnectionDelegate) -> None:\n        \"\"\"Starts serving requests on this connection.\n\n        :arg delegate: a `.HTTPServerConnectionDelegate`\n        \"\"\"\n        assert isinstance(delegate, httputil.HTTPServerConnectionDelegate)\n        fut = gen.convert_yielded(self._server_request_loop(delegate))\n        self._serving_future = fut\n        # Register the future on the IOLoop so its errors get logged.\n        self.stream.io_loop.add_future(fut, lambda f: f.result())\n\n    async def _server_request_loop(\n        self, delegate: httputil.HTTPServerConnectionDelegate\n    ) -> None:\n        try:\n            while True:\n                conn = HTTP1Connection(self.stream, False, self.params, self.context)\n                request_delegate = delegate.start_request(self, conn)\n                try:\n                    ret = await conn.read_response(request_delegate)\n                except (\n                    iostream.StreamClosedError,\n                    iostream.UnsatisfiableReadError,\n                    asyncio.CancelledError,\n                ):\n                    return\n                except _QuietException:\n                    # This exception was already logged.\n                    conn.close()\n                    return\n                except Exception:\n                    gen_log.error(\"Uncaught exception\", exc_info=True)\n                    conn.close()\n                    return\n                if not ret:\n                    return\n                await asyncio.sleep(0)\n        finally:\n            delegate.on_close(self)\n\n\nDIGITS = re.compile(r\"[0-9]+\")\nHEXDIGITS = re.compile(r\"[0-9a-fA-F]+\")\n\n\ndef parse_int(s: str) -> int:\n    \"\"\"Parse a non-negative integer from a string.\"\"\"\n    if DIGITS.fullmatch(s) is None:\n        raise ValueError(\"not an integer: %r\" % s)\n    return int(s)\n\n\ndef parse_hex_int(s: str) -> int:\n    \"\"\"Parse a non-negative hexadecimal integer from a string.\"\"\"\n    if HEXDIGITS.fullmatch(s) is None:\n        raise ValueError(\"not a hexadecimal integer: %r\" % s)\n    return int(s, 16)\n\n\ndef is_transfer_encoding_chunked(headers: httputil.HTTPHeaders) -> bool:\n    \"\"\"Returns true if the headers specify Transfer-Encoding: chunked.\n\n    Raise httputil.HTTPInputError if any other transfer encoding is used.\n    \"\"\"\n    # Note that transfer-encoding is an area in which postel's law can lead\n    # us astray. If a proxy and a backend server are liberal in what they accept,\n    # but accept slightly different things, this can lead to mismatched framing\n    # and request smuggling issues. Therefore we are as strict as possible here\n    # (even technically going beyond the requirements of the RFCs: a value of\n    # \",chunked\" is legal but doesn't appear in practice for legitimate traffic)\n    if \"Transfer-Encoding\" not in headers:\n        return False\n    if \"Content-Length\" in headers:\n        # Message cannot contain both Content-Length and\n        # Transfer-Encoding headers.\n        # http://tools.ietf.org/html/rfc7230#section-3.3.3\n        raise httputil.HTTPInputError(\n            \"Message with both Transfer-Encoding and Content-Length\"\n        )\n    if headers[\"Transfer-Encoding\"].lower() == \"chunked\":\n        return True\n    # We do not support any transfer-encodings other than chunked, and we do not\n    # expect to add any support because the concept of transfer-encoding has\n    # been removed in HTTP/2.\n    raise httputil.HTTPInputError(\n        \"Unsupported Transfer-Encoding %s\" % headers[\"Transfer-Encoding\"]\n    )\n"
  },
  {
    "path": "tornado/httpclient.py",
    "content": "\"\"\"Blocking and non-blocking HTTP client interfaces.\n\nThis module defines a common interface shared by two implementations,\n``simple_httpclient`` and ``curl_httpclient``.  Applications may either\ninstantiate their chosen implementation class directly or use the\n`AsyncHTTPClient` class from this module, which selects an implementation\nthat can be overridden with the `AsyncHTTPClient.configure` method.\n\nThe default implementation is ``simple_httpclient``, and this is expected\nto be suitable for most users' needs.  However, some applications may wish\nto switch to ``curl_httpclient`` for reasons such as the following:\n\n* ``curl_httpclient`` has some features not found in ``simple_httpclient``,\n  including support for HTTP proxies and the ability to use a specified\n  network interface.\n\n* ``curl_httpclient`` is more likely to be compatible with sites that are\n  not-quite-compliant with the HTTP spec, or sites that use little-exercised\n  features of HTTP.\n\n* ``curl_httpclient`` is faster.\n\nNote that if you are using ``curl_httpclient``, it is highly\nrecommended that you use a recent version of ``libcurl`` and\n``pycurl``.  Currently the minimum supported version of libcurl is\n7.22.0, and the minimum version of pycurl is 7.18.2.  It is highly\nrecommended that your ``libcurl`` installation is built with\nasynchronous DNS resolver (threaded or c-ares), otherwise you may\nencounter various problems with request timeouts (for more\ninformation, see\nhttp://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCONNECTTIMEOUTMS\nand comments in curl_httpclient.py).\n\nTo select ``curl_httpclient``, call `AsyncHTTPClient.configure` at startup::\n\n    AsyncHTTPClient.configure(\"tornado.curl_httpclient.CurlAsyncHTTPClient\")\n\"\"\"\n\nimport datetime\nimport functools\nimport ssl\nimport time\nimport weakref\nfrom collections.abc import Awaitable, Callable\nfrom io import BytesIO\nfrom typing import Any, Optional, Type, Union, cast\n\nfrom tornado import gen, httputil\nfrom tornado.concurrent import (\n    Future,\n    future_set_exception_unless_cancelled,\n    future_set_result_unless_cancelled,\n)\nfrom tornado.escape import native_str, utf8\nfrom tornado.ioloop import IOLoop\nfrom tornado.util import Configurable\n\n\nclass HTTPClient:\n    \"\"\"A blocking HTTP client.\n\n    This interface is provided to make it easier to share code between\n    synchronous and asynchronous applications. Applications that are\n    running an `.IOLoop` must use `AsyncHTTPClient` instead.\n\n    Typical usage looks like this::\n\n        http_client = httpclient.HTTPClient()\n        try:\n            response = http_client.fetch(\"http://www.google.com/\")\n            print(response.body)\n        except httpclient.HTTPError as e:\n            # HTTPError is raised for non-200 responses; the response\n            # can be found in e.response.\n            print(\"Error: \" + str(e))\n        except Exception as e:\n            # Other errors are possible, such as IOError.\n            print(\"Error: \" + str(e))\n        http_client.close()\n\n    .. versionchanged:: 5.0\n\n       Due to limitations in `asyncio`, it is no longer possible to\n       use the synchronous ``HTTPClient`` while an `.IOLoop` is running.\n       Use `AsyncHTTPClient` instead.\n\n    \"\"\"\n\n    def __init__(\n        self,\n        async_client_class: \"Optional[Type[AsyncHTTPClient]]\" = None,\n        **kwargs: Any,\n    ) -> None:\n        # Initialize self._closed at the beginning of the constructor\n        # so that an exception raised here doesn't lead to confusing\n        # failures in __del__.\n        self._closed = True\n        self._io_loop = IOLoop(make_current=False)\n        if async_client_class is None:\n            async_client_class = AsyncHTTPClient\n\n        # Create the client while our IOLoop is \"current\", without\n        # clobbering the thread's real current IOLoop (if any).\n        async def make_client() -> \"AsyncHTTPClient\":\n            await gen.sleep(0)\n            assert async_client_class is not None\n            return async_client_class(**kwargs)\n\n        self._async_client = self._io_loop.run_sync(make_client)\n        self._closed = False\n\n    def __del__(self) -> None:\n        self.close()\n\n    def close(self) -> None:\n        \"\"\"Closes the HTTPClient, freeing any resources used.\"\"\"\n        if not self._closed:\n            self._async_client.close()\n            self._io_loop.close()\n            self._closed = True\n\n    def fetch(\n        self, request: Union[\"HTTPRequest\", str], **kwargs: Any\n    ) -> \"HTTPResponse\":\n        \"\"\"Executes a request, returning an `HTTPResponse`.\n\n        The request may be either a string URL or an `HTTPRequest` object.\n        If it is a string, we construct an `HTTPRequest` using any additional\n        kwargs: ``HTTPRequest(request, **kwargs)``\n\n        If an error occurs during the fetch, we raise an `HTTPError` unless\n        the ``raise_error`` keyword argument is set to False.\n        \"\"\"\n        response = self._io_loop.run_sync(\n            functools.partial(self._async_client.fetch, request, **kwargs)\n        )\n        return response\n\n\nclass AsyncHTTPClient(Configurable):\n    \"\"\"An non-blocking HTTP client.\n\n    Example usage::\n\n        async def f():\n            http_client = AsyncHTTPClient()\n            try:\n                response = await http_client.fetch(\"http://www.google.com\")\n            except Exception as e:\n                print(\"Error: %s\" % e)\n            else:\n                print(response.body)\n\n    The constructor for this class is magic in several respects: It\n    actually creates an instance of an implementation-specific\n    subclass, and instances are reused as a kind of pseudo-singleton\n    (one per `.IOLoop`). The keyword argument ``force_instance=True``\n    can be used to suppress this singleton behavior. Unless\n    ``force_instance=True`` is used, no arguments should be passed to\n    the `AsyncHTTPClient` constructor. The implementation subclass as\n    well as arguments to its constructor can be set with the static\n    method `configure()`\n\n    All `AsyncHTTPClient` implementations support a ``defaults``\n    keyword argument, which can be used to set default values for\n    `HTTPRequest` attributes.  For example::\n\n        AsyncHTTPClient.configure(\n            None, defaults=dict(user_agent=\"MyUserAgent\"))\n        # or with force_instance:\n        client = AsyncHTTPClient(force_instance=True,\n            defaults=dict(user_agent=\"MyUserAgent\"))\n\n    .. versionchanged:: 5.0\n       The ``io_loop`` argument (deprecated since version 4.1) has been removed.\n\n    \"\"\"\n\n    _instance_cache: dict[IOLoop, \"AsyncHTTPClient\"]\n\n    @classmethod\n    def configurable_base(cls) -> type[Configurable]:\n        return AsyncHTTPClient\n\n    @classmethod\n    def configurable_default(cls) -> type[Configurable]:\n        from tornado.simple_httpclient import SimpleAsyncHTTPClient\n\n        return SimpleAsyncHTTPClient\n\n    @classmethod\n    def _async_clients(cls) -> dict[IOLoop, \"AsyncHTTPClient\"]:\n        attr_name = \"_async_client_dict_\" + cls.__name__\n        if not hasattr(cls, attr_name):\n            setattr(cls, attr_name, weakref.WeakKeyDictionary())\n        return getattr(cls, attr_name)\n\n    def __new__(cls, force_instance: bool = False, **kwargs: Any) -> \"AsyncHTTPClient\":\n        io_loop = IOLoop.current()\n        if force_instance:\n            instance_cache = None\n        else:\n            instance_cache = cls._async_clients()\n        if instance_cache is not None and io_loop in instance_cache:\n            return instance_cache[io_loop]\n        instance = super().__new__(cls, **kwargs)  # type: ignore\n        # Make sure the instance knows which cache to remove itself from.\n        # It can't simply call _async_clients() because we may be in\n        # __new__(AsyncHTTPClient) but instance.__class__ may be\n        # SimpleAsyncHTTPClient.\n        instance._instance_cache = instance_cache\n        if instance_cache is not None:\n            instance_cache[instance.io_loop] = instance\n        return instance\n\n    def initialize(self, defaults: dict[str, Any] | None = None) -> None:\n        self.io_loop = IOLoop.current()\n        self.defaults = dict(HTTPRequest._DEFAULTS)\n        if defaults is not None:\n            self.defaults.update(defaults)\n        self._closed = False\n\n    def close(self) -> None:\n        \"\"\"Destroys this HTTP client, freeing any file descriptors used.\n\n        This method is **not needed in normal use** due to the way\n        that `AsyncHTTPClient` objects are transparently reused.\n        ``close()`` is generally only necessary when either the\n        `.IOLoop` is also being closed, or the ``force_instance=True``\n        argument was used when creating the `AsyncHTTPClient`.\n\n        No other methods may be called on the `AsyncHTTPClient` after\n        ``close()``.\n\n        \"\"\"\n        if self._closed:\n            return\n        self._closed = True\n        if self._instance_cache is not None:\n            cached_val = self._instance_cache.pop(self.io_loop, None)\n            # If there's an object other than self in the instance\n            # cache for our IOLoop, something has gotten mixed up. A\n            # value of None appears to be possible when this is called\n            # from a destructor (HTTPClient.__del__) as the weakref\n            # gets cleared before the destructor runs.\n            if cached_val is not None and cached_val is not self:\n                raise RuntimeError(\"inconsistent AsyncHTTPClient cache\")\n\n    def fetch(\n        self,\n        request: Union[str, \"HTTPRequest\"],\n        raise_error: bool = True,\n        **kwargs: Any,\n    ) -> \"Future[HTTPResponse]\":\n        \"\"\"Executes a request, asynchronously returning an `HTTPResponse`.\n\n        The request may be either a string URL or an `HTTPRequest` object.\n        If it is a string, we construct an `HTTPRequest` using any additional\n        kwargs: ``HTTPRequest(request, **kwargs)``\n\n        This method returns a `.Future` whose result is an\n        `HTTPResponse`. By default, the ``Future`` will raise an\n        `HTTPError` if the request returned a non-200 response code\n        (other errors may also be raised if the server could not be\n        contacted). Instead, if ``raise_error`` is set to False, the\n        response will always be returned regardless of the response\n        code.\n\n        If a ``callback`` is given, it will be invoked with the `HTTPResponse`.\n        In the callback interface, `HTTPError` is not automatically raised.\n        Instead, you must check the response's ``error`` attribute or\n        call its `~HTTPResponse.rethrow` method.\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed. Use the returned\n           `.Future` instead.\n\n           The ``raise_error=False`` argument only affects the\n           `HTTPError` raised when a non-200 response code is used,\n           instead of suppressing all errors.\n        \"\"\"\n        if self._closed:\n            raise RuntimeError(\"fetch() called on closed AsyncHTTPClient\")\n        if not isinstance(request, HTTPRequest):\n            request = HTTPRequest(url=request, **kwargs)\n        else:\n            if kwargs:\n                raise ValueError(\n                    \"kwargs can't be used if request is an HTTPRequest object\"\n                )\n        # We may modify this (to add Host, Accept-Encoding, etc),\n        # so make sure we don't modify the caller's object.  This is also\n        # where normal dicts get converted to HTTPHeaders objects.\n        request.headers = httputil.HTTPHeaders(request.headers)\n        request_proxy = _RequestProxy(request, self.defaults)\n        future: Future[HTTPResponse] = Future()\n\n        def handle_response(response: \"HTTPResponse\") -> None:\n            if response.error:\n                if raise_error or not response._error_is_response_code:\n                    future_set_exception_unless_cancelled(future, response.error)\n                    return\n            future_set_result_unless_cancelled(future, response)\n\n        self.fetch_impl(cast(HTTPRequest, request_proxy), handle_response)\n        return future\n\n    def fetch_impl(\n        self, request: \"HTTPRequest\", callback: Callable[[\"HTTPResponse\"], None]\n    ) -> None:\n        raise NotImplementedError()\n\n    @classmethod\n    def configure(\n        cls, impl: \"Union[None, str, Type[Configurable]]\", **kwargs: Any\n    ) -> None:\n        \"\"\"Configures the `AsyncHTTPClient` subclass to use.\n\n        ``AsyncHTTPClient()`` actually creates an instance of a subclass.\n        This method may be called with either a class object or the\n        fully-qualified name of such a class (or ``None`` to use the default,\n        ``SimpleAsyncHTTPClient``)\n\n        If additional keyword arguments are given, they will be passed\n        to the constructor of each subclass instance created.  The\n        keyword argument ``max_clients`` determines the maximum number\n        of simultaneous `~AsyncHTTPClient.fetch()` operations that can\n        execute in parallel on each `.IOLoop`.  Additional arguments\n        may be supported depending on the implementation class in use.\n\n        Example::\n\n           AsyncHTTPClient.configure(\"tornado.curl_httpclient.CurlAsyncHTTPClient\")\n        \"\"\"\n        super().configure(impl, **kwargs)\n\n\nclass HTTPRequest:\n    \"\"\"HTTP client request object.\"\"\"\n\n    _headers: dict[str, str] | httputil.HTTPHeaders\n\n    # Default values for HTTPRequest parameters.\n    # Merged with the values on the request object by AsyncHTTPClient\n    # implementations.\n    _DEFAULTS = dict(\n        connect_timeout=20.0,\n        request_timeout=20.0,\n        follow_redirects=True,\n        max_redirects=5,\n        decompress_response=True,\n        proxy_password=\"\",\n        allow_nonstandard_methods=False,\n        validate_cert=True,\n    )\n\n    def __init__(\n        self,\n        url: str,\n        method: str = \"GET\",\n        headers: dict[str, str] | httputil.HTTPHeaders | None = None,\n        body: bytes | str | None = None,\n        auth_username: str | None = None,\n        auth_password: str | None = None,\n        auth_mode: str | None = None,\n        connect_timeout: float | None = None,\n        request_timeout: float | None = None,\n        if_modified_since: float | datetime.datetime | None = None,\n        follow_redirects: bool | None = None,\n        max_redirects: int | None = None,\n        user_agent: str | None = None,\n        use_gzip: bool | None = None,\n        network_interface: str | None = None,\n        streaming_callback: None | (Callable[[bytes], Awaitable[None] | None]) = None,\n        header_callback: Callable[[str], None] | None = None,\n        prepare_curl_callback: Callable[[Any], None] | None = None,\n        proxy_host: str | None = None,\n        proxy_port: int | None = None,\n        proxy_username: str | None = None,\n        proxy_password: str | None = None,\n        proxy_auth_mode: str | None = None,\n        allow_nonstandard_methods: bool | None = None,\n        validate_cert: bool | None = None,\n        ca_certs: str | None = None,\n        allow_ipv6: bool | None = None,\n        client_key: str | None = None,\n        client_cert: str | None = None,\n        body_producer: None | (\n            Callable[[Callable[[bytes], None]], \"Future[None]\"]\n        ) = None,\n        expect_100_continue: bool = False,\n        decompress_response: bool | None = None,\n        ssl_options: dict[str, Any] | ssl.SSLContext | None = None,\n    ) -> None:\n        r\"\"\"All parameters except ``url`` are optional.\n\n        :arg str url: URL to fetch\n        :arg str method: HTTP method, e.g. \"GET\" or \"POST\"\n        :arg headers: Additional HTTP headers to pass on the request\n        :type headers: `~tornado.httputil.HTTPHeaders` or `dict`\n        :arg body: HTTP request body as a string (byte or unicode; if unicode\n           the utf-8 encoding will be used)\n        :type body: `str` or `bytes`\n        :arg collections.abc.Callable body_producer: Callable used for\n           lazy/asynchronous request bodies.\n           It is called with one argument, a ``write`` function, and should\n           return a `.Future`.  It should call the write function with new\n           data as it becomes available.  The write function returns a\n           `.Future` which can be used for flow control.\n           Only one of ``body`` and ``body_producer`` may\n           be specified.  ``body_producer`` is not supported on\n           ``curl_httpclient``.  When using ``body_producer`` it is recommended\n           to pass a ``Content-Length`` in the headers as otherwise chunked\n           encoding will be used, and many servers do not support chunked\n           encoding on requests.  New in Tornado 4.0\n        :arg str auth_username: Username for HTTP authentication\n        :arg str auth_password: Password for HTTP authentication\n        :arg str auth_mode: Authentication mode; default is \"basic\".\n           Allowed values are implementation-defined; ``curl_httpclient``\n           supports \"basic\" and \"digest\"; ``simple_httpclient`` only supports\n           \"basic\"\n        :arg float connect_timeout: Timeout for initial connection in seconds,\n           default 20 seconds (0 means no timeout)\n        :arg float request_timeout: Timeout for entire request in seconds,\n           default 20 seconds (0 means no timeout)\n        :arg if_modified_since: Timestamp for ``If-Modified-Since`` header\n        :type if_modified_since: `datetime` or `float`\n        :arg bool follow_redirects: Should redirects be followed automatically\n           or return the 3xx response? Default True.\n        :arg int max_redirects: Limit for ``follow_redirects``, default 5.\n        :arg str user_agent: String to send as ``User-Agent`` header\n        :arg bool decompress_response: Request a compressed response from\n           the server and decompress it after downloading.  Default is True.\n           New in Tornado 4.0.\n        :arg bool use_gzip: Deprecated alias for ``decompress_response``\n           since Tornado 4.0.\n        :arg str network_interface: Network interface or source IP to use for request.\n           See ``curl_httpclient`` note below.\n        :arg collections.abc.Callable streaming_callback: If set, ``streaming_callback`` will\n           be run with each chunk of data as it is received, and\n           ``HTTPResponse.body`` and ``HTTPResponse.buffer`` will be empty in\n           the final response.\n        :arg collections.abc.Callable header_callback: If set, ``header_callback`` will\n           be run with each header line as it is received (including the\n           first line, e.g. ``HTTP/1.0 200 OK\\r\\n``, and a final line\n           containing only ``\\r\\n``.  All lines include the trailing newline\n           characters).  ``HTTPResponse.headers`` will be empty in the final\n           response.  This is most useful in conjunction with\n           ``streaming_callback``, because it's the only way to get access to\n           header data while the request is in progress.\n        :arg collections.abc.Callable prepare_curl_callback: If set, will be called with\n           a ``pycurl.Curl`` object to allow the application to make additional\n           ``setopt`` calls.\n        :arg str proxy_host: HTTP proxy hostname.  To use proxies,\n           ``proxy_host`` and ``proxy_port`` must be set; ``proxy_username``,\n           ``proxy_pass`` and ``proxy_auth_mode`` are optional.  Proxies are\n           currently only supported with ``curl_httpclient``.\n        :arg int proxy_port: HTTP proxy port\n        :arg str proxy_username: HTTP proxy username\n        :arg str proxy_password: HTTP proxy password\n        :arg str proxy_auth_mode: HTTP proxy Authentication mode;\n           default is \"basic\". supports \"basic\" and \"digest\"\n        :arg bool allow_nonstandard_methods: Allow unknown values for ``method``\n           argument? Default is False.\n        :arg bool validate_cert: For HTTPS requests, validate the server's\n           certificate? Default is True.\n        :arg str ca_certs: filename of CA certificates in PEM format,\n           or None to use defaults.  See note below when used with\n           ``curl_httpclient``.\n        :arg str client_key: Filename for client SSL key, if any.  See\n           note below when used with ``curl_httpclient``.\n        :arg str client_cert: Filename for client SSL certificate, if any.\n           See note below when used with ``curl_httpclient``.\n        :arg ssl.SSLContext ssl_options: `ssl.SSLContext` object for use in\n           ``simple_httpclient`` (unsupported by ``curl_httpclient``).\n           Overrides ``validate_cert``, ``ca_certs``, ``client_key``,\n           and ``client_cert``.\n        :arg bool allow_ipv6: Use IPv6 when available?  Default is True.\n        :arg bool expect_100_continue: If true, send the\n           ``Expect: 100-continue`` header and wait for a continue response\n           before sending the request body.  Only supported with\n           ``simple_httpclient``.\n\n        .. note::\n\n            When using ``curl_httpclient`` certain options may be\n            inherited by subsequent fetches because ``pycurl`` does\n            not allow them to be cleanly reset.  This applies to the\n            ``ca_certs``, ``client_key``, ``client_cert``, and\n            ``network_interface`` arguments.  If you use these\n            options, you should pass them on every request (you don't\n            have to always use the same values, but it's not possible\n            to mix requests that specify these options with ones that\n            use the defaults).\n\n        .. versionadded:: 3.1\n           The ``auth_mode`` argument.\n\n        .. versionadded:: 4.0\n           The ``body_producer`` and ``expect_100_continue`` arguments.\n\n        .. versionadded:: 4.2\n           The ``ssl_options`` argument.\n\n        .. versionadded:: 4.5\n           The ``proxy_auth_mode`` argument.\n        \"\"\"\n        # Note that some of these attributes go through property setters\n        # defined below.\n        self.headers = headers  # type: ignore\n        if if_modified_since:\n            self.headers[\"If-Modified-Since\"] = httputil.format_timestamp(\n                if_modified_since\n            )\n        self.proxy_host = proxy_host\n        self.proxy_port = proxy_port\n        self.proxy_username = proxy_username\n        self.proxy_password = proxy_password\n        self.proxy_auth_mode = proxy_auth_mode\n        self.url = url\n        self.method = method\n        self.body = body  # type: ignore\n        self.body_producer = body_producer\n        self.auth_username = auth_username\n        self.auth_password = auth_password\n        self.auth_mode = auth_mode\n        self.connect_timeout = connect_timeout\n        self.request_timeout = request_timeout\n        self.follow_redirects = follow_redirects\n        self.max_redirects = max_redirects\n        self.user_agent = user_agent\n        if decompress_response is not None:\n            self.decompress_response: bool | None = decompress_response\n        else:\n            self.decompress_response = use_gzip\n        self.network_interface = network_interface\n        self.streaming_callback = streaming_callback\n        self.header_callback = header_callback\n        self.prepare_curl_callback = prepare_curl_callback\n        self.allow_nonstandard_methods = allow_nonstandard_methods\n        self.validate_cert = validate_cert\n        self.ca_certs = ca_certs\n        self.allow_ipv6 = allow_ipv6\n        self.client_key = client_key\n        self.client_cert = client_cert\n        self.ssl_options = ssl_options\n        self.expect_100_continue = expect_100_continue\n        self.start_time = time.time()\n\n    @property\n    def headers(self) -> httputil.HTTPHeaders:\n        # TODO: headers may actually be a plain dict until fairly late in\n        # the process (AsyncHTTPClient.fetch), but practically speaking,\n        # whenever the property is used they're already HTTPHeaders.\n        return self._headers  # type: ignore\n\n    @headers.setter\n    def headers(self, value: dict[str, str] | httputil.HTTPHeaders) -> None:\n        if value is None:\n            self._headers = httputil.HTTPHeaders()\n        else:\n            self._headers = value  # type: ignore\n\n    @property\n    def body(self) -> bytes:\n        return self._body\n\n    @body.setter\n    def body(self, value: bytes | str) -> None:\n        self._body = utf8(value)\n\n\nclass HTTPResponse:\n    \"\"\"HTTP Response object.\n\n    Attributes:\n\n    * ``request``: HTTPRequest object\n\n    * ``code``: numeric HTTP status code, e.g. 200 or 404\n\n    * ``reason``: human-readable reason phrase describing the status code\n\n    * ``headers``: `tornado.httputil.HTTPHeaders` object\n\n    * ``effective_url``: final location of the resource after following any\n      redirects\n\n    * ``buffer``: ``cStringIO`` object for response body\n\n    * ``body``: response body as bytes (created on demand from ``self.buffer``)\n\n    * ``error``: Exception object, if any\n\n    * ``request_time``: seconds from request start to finish. Includes all\n      network operations from DNS resolution to receiving the last byte of\n      data. Does not include time spent in the queue (due to the\n      ``max_clients`` option). If redirects were followed, only includes\n      the final request.\n\n    * ``start_time``: Time at which the HTTP operation started, based on\n      `time.time` (not the monotonic clock used by `.IOLoop.time`). May\n      be ``None`` if the request timed out while in the queue.\n\n    * ``time_info``: dictionary of diagnostic timing information from the\n      request. Available data are subject to change, but currently uses timings\n      available from http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html,\n      plus ``queue``, which is the delay (if any) introduced by waiting for\n      a slot under `AsyncHTTPClient`'s ``max_clients`` setting.\n\n    .. versionadded:: 5.1\n\n       Added the ``start_time`` attribute.\n\n    .. versionchanged:: 5.1\n\n       The ``request_time`` attribute previously included time spent in the queue\n       for ``simple_httpclient``, but not in ``curl_httpclient``. Now queueing time\n       is excluded in both implementations. ``request_time`` is now more accurate for\n       ``curl_httpclient`` because it uses a monotonic clock when available.\n    \"\"\"\n\n    # I'm not sure why these don't get type-inferred from the references in __init__.\n    error: BaseException | None = None\n    _error_is_response_code = False\n    request: HTTPRequest\n\n    def __init__(\n        self,\n        request: HTTPRequest,\n        code: int,\n        headers: httputil.HTTPHeaders | None = None,\n        buffer: BytesIO | None = None,\n        effective_url: str | None = None,\n        error: BaseException | None = None,\n        request_time: float | None = None,\n        time_info: dict[str, float] | None = None,\n        reason: str | None = None,\n        start_time: float | None = None,\n    ) -> None:\n        if isinstance(request, _RequestProxy):\n            self.request = request.request\n        else:\n            self.request = request\n        self.code = code\n        self.reason = reason or httputil.responses.get(code, \"Unknown\")\n        if headers is not None:\n            self.headers = headers\n        else:\n            self.headers = httputil.HTTPHeaders()\n        self.buffer = buffer\n        self._body: bytes | None = None\n        if effective_url is None:\n            self.effective_url = request.url\n        else:\n            self.effective_url = effective_url\n        self._error_is_response_code = False\n        if error is None:\n            if self.code < 200 or self.code >= 300:\n                self._error_is_response_code = True\n                self.error = HTTPError(self.code, message=self.reason, response=self)\n            else:\n                self.error = None\n        else:\n            self.error = error\n        self.start_time = start_time\n        self.request_time = request_time\n        self.time_info = time_info or {}\n\n    @property\n    def body(self) -> bytes:\n        if self.buffer is None:\n            return b\"\"\n        elif self._body is None:\n            self._body = self.buffer.getvalue()\n\n        return self._body\n\n    def rethrow(self) -> None:\n        \"\"\"If there was an error on the request, raise an `HTTPError`.\"\"\"\n        if self.error:\n            raise self.error\n\n    def __repr__(self) -> str:\n        args = \",\".join(\"%s=%r\" % i for i in sorted(self.__dict__.items()))\n        return f\"{self.__class__.__name__}({args})\"\n\n\nclass HTTPClientError(Exception):\n    \"\"\"Exception thrown for an unsuccessful HTTP request.\n\n    Attributes:\n\n    * ``code`` - HTTP error integer error code, e.g. 404.  Error code 599 is\n      used when no HTTP response was received, e.g. for a timeout.\n\n    * ``response`` - `HTTPResponse` object, if any.\n\n    Note that if ``follow_redirects`` is False, redirects become HTTPErrors,\n    and you can look at ``error.response.headers['Location']`` to see the\n    destination of the redirect.\n\n    .. versionchanged:: 5.1\n\n       Renamed from ``HTTPError`` to ``HTTPClientError`` to avoid collisions with\n       `tornado.web.HTTPError`. The name ``tornado.httpclient.HTTPError`` remains\n       as an alias.\n    \"\"\"\n\n    def __init__(\n        self,\n        code: int,\n        message: str | None = None,\n        response: HTTPResponse | None = None,\n    ) -> None:\n        self.code = code\n        self.message = message or httputil.responses.get(code, \"Unknown\")\n        self.response = response\n        super().__init__(code, message, response)\n\n    def __str__(self) -> str:\n        return \"HTTP %d: %s\" % (self.code, self.message)\n\n    # There is a cyclic reference between self and self.response,\n    # which breaks the default __repr__ implementation.\n    # (especially on pypy, which doesn't have the same recursion\n    # detection as cpython).\n    __repr__ = __str__\n\n\nHTTPError = HTTPClientError\n\n\nclass _RequestProxy:\n    \"\"\"Combines an object with a dictionary of defaults.\n\n    Used internally by AsyncHTTPClient implementations.\n    \"\"\"\n\n    def __init__(self, request: HTTPRequest, defaults: dict[str, Any] | None) -> None:\n        self.request = request\n        self.defaults = defaults\n\n    def __getattr__(self, name: str) -> Any:\n        request_attr = getattr(self.request, name)\n        if request_attr is not None:\n            return request_attr\n        elif self.defaults is not None:\n            return self.defaults.get(name, None)\n        else:\n            return None\n\n\ndef main() -> None:\n    from tornado.options import define, options, parse_command_line\n\n    define(\"print_headers\", type=bool, default=False)\n    define(\"print_body\", type=bool, default=True)\n    define(\"follow_redirects\", type=bool, default=True)\n    define(\"validate_cert\", type=bool, default=True)\n    define(\"proxy_host\", type=str)\n    define(\"proxy_port\", type=int)\n    args = parse_command_line()\n    client = HTTPClient()\n    for arg in args:\n        try:\n            response = client.fetch(\n                arg,\n                follow_redirects=options.follow_redirects,\n                validate_cert=options.validate_cert,\n                proxy_host=options.proxy_host,\n                proxy_port=options.proxy_port,\n            )\n        except HTTPError as e:\n            if e.response is not None:\n                response = e.response\n            else:\n                raise\n        if options.print_headers:\n            print(response.headers)\n        if options.print_body:\n            print(native_str(response.body))\n    client.close()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "tornado/httpserver.py",
    "content": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"A non-blocking, single-threaded HTTP server.\n\nTypical applications have little direct interaction with the `HTTPServer`\nclass except to start a server at the beginning of the process\n(and even that is often done indirectly via `tornado.web.Application.listen`).\n\n.. versionchanged:: 4.0\n\n   The ``HTTPRequest`` class that used to live in this module has been moved\n   to `tornado.httputil.HTTPServerRequest`.  The old name remains as an alias.\n\"\"\"\n\nimport socket\nimport ssl\nimport typing\nfrom collections.abc import Awaitable, Callable\nfrom typing import Any\n\nfrom tornado import httputil, iostream, netutil\nfrom tornado.escape import native_str\nfrom tornado.http1connection import HTTP1ConnectionParameters, HTTP1ServerConnection\nfrom tornado.tcpserver import TCPServer\nfrom tornado.util import Configurable\n\n\nclass HTTPServer(TCPServer, Configurable, httputil.HTTPServerConnectionDelegate):\n    r\"\"\"A non-blocking, single-threaded HTTP server.\n\n    A server is defined by a subclass of `.HTTPServerConnectionDelegate`,\n    or, for backwards compatibility, a callback that takes an\n    `.HTTPServerRequest` as an argument. The delegate is usually a\n    `tornado.web.Application`.\n\n    `HTTPServer` supports keep-alive connections by default\n    (automatically for HTTP/1.1, or for HTTP/1.0 when the client\n    requests ``Connection: keep-alive``).\n\n    If ``xheaders`` is ``True``, we support the\n    ``X-Real-Ip``/``X-Forwarded-For`` and\n    ``X-Scheme``/``X-Forwarded-Proto`` headers, which override the\n    remote IP and URI scheme/protocol for all requests.  These headers\n    are useful when running Tornado behind a reverse proxy or load\n    balancer.  The ``protocol`` argument can also be set to ``https``\n    if Tornado is run behind an SSL-decoding proxy that does not set one of\n    the supported ``xheaders``.\n\n    By default, when parsing the ``X-Forwarded-For`` header, Tornado will\n    select the last (i.e., the closest) address on the list of hosts as the\n    remote host IP address.  To select the next server in the chain, a list of\n    trusted downstream hosts may be passed as the ``trusted_downstream``\n    argument.  These hosts will be skipped when parsing the ``X-Forwarded-For``\n    header.\n\n    To make this server serve SSL traffic, send the ``ssl_options`` keyword\n    argument with an `ssl.SSLContext` object. For compatibility with older\n    versions of Python ``ssl_options`` may also be a dictionary of keyword\n    arguments for the `ssl.SSLContext.wrap_socket` method.::\n\n       ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)\n       ssl_ctx.load_cert_chain(os.path.join(data_dir, \"mydomain.crt\"),\n                               os.path.join(data_dir, \"mydomain.key\"))\n       HTTPServer(application, ssl_options=ssl_ctx)\n\n    `HTTPServer` initialization follows one of three patterns (the\n    initialization methods are defined on `tornado.tcpserver.TCPServer`):\n\n    1. `~tornado.tcpserver.TCPServer.listen`: single-process::\n\n            async def main():\n                server = HTTPServer()\n                server.listen(8888)\n                await asyncio.Event().wait()\n\n            asyncio.run(main())\n\n       In many cases, `tornado.web.Application.listen` can be used to avoid\n       the need to explicitly create the `HTTPServer`.\n\n       While this example does not create multiple processes on its own, when\n       the ``reuse_port=True`` argument is passed to ``listen()`` you can run\n       the program multiple times to create a multi-process service.\n\n    2. `~tornado.tcpserver.TCPServer.add_sockets`: multi-process::\n\n            sockets = bind_sockets(8888)\n            tornado.process.fork_processes(0)\n            async def post_fork_main():\n                server = HTTPServer()\n                server.add_sockets(sockets)\n                await asyncio.Event().wait()\n            asyncio.run(post_fork_main())\n\n       The ``add_sockets`` interface is more complicated, but it can be used with\n       `tornado.process.fork_processes` to run a multi-process service with all\n       worker processes forked from a single parent.  ``add_sockets`` can also be\n       used in single-process servers if you want to create your listening\n       sockets in some way other than `~tornado.netutil.bind_sockets`.\n\n       Note that when using this pattern, nothing that touches the event loop\n       can be run before ``fork_processes``.\n\n    3. `~tornado.tcpserver.TCPServer.bind`/`~tornado.tcpserver.TCPServer.start`:\n       simple **deprecated** multi-process::\n\n            server = HTTPServer()\n            server.bind(8888)\n            server.start(0)  # Forks multiple sub-processes\n            IOLoop.current().start()\n\n       This pattern is deprecated because it requires interfaces in the\n       `asyncio` module that have been deprecated since Python 3.10. Support for\n       creating multiple processes in the ``start`` method will be removed in a\n       future version of Tornado.\n\n    .. versionchanged:: 4.0\n       Added ``decompress_request``, ``chunk_size``, ``max_header_size``,\n       ``idle_connection_timeout``, ``body_timeout``, ``max_body_size``\n       arguments.  Added support for `.HTTPServerConnectionDelegate`\n       instances as ``request_callback``.\n\n    .. versionchanged:: 4.1\n       `.HTTPServerConnectionDelegate.start_request` is now called with\n       two arguments ``(server_conn, request_conn)`` (in accordance with the\n       documentation) instead of one ``(request_conn)``.\n\n    .. versionchanged:: 4.2\n       `HTTPServer` is now a subclass of `tornado.util.Configurable`.\n\n    .. versionchanged:: 4.5\n       Added the ``trusted_downstream`` argument.\n\n    .. versionchanged:: 5.0\n       The ``io_loop`` argument has been removed.\n    \"\"\"\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        # Ignore args to __init__; real initialization belongs in\n        # initialize since we're Configurable. (there's something\n        # weird in initialization order between this class,\n        # Configurable, and TCPServer so we can't leave __init__ out\n        # completely)\n        pass\n\n    def initialize(\n        self,\n        request_callback: (\n            httputil.HTTPServerConnectionDelegate\n            | Callable[[httputil.HTTPServerRequest], None]\n        ),\n        no_keep_alive: bool = False,\n        xheaders: bool = False,\n        ssl_options: dict[str, Any] | ssl.SSLContext | None = None,\n        protocol: str | None = None,\n        decompress_request: bool = False,\n        chunk_size: int | None = None,\n        max_header_size: int | None = None,\n        idle_connection_timeout: float | None = None,\n        body_timeout: float | None = None,\n        max_body_size: int | None = None,\n        max_buffer_size: int | None = None,\n        trusted_downstream: list[str] | None = None,\n    ) -> None:\n        # This method's signature is not extracted with autodoc\n        # because we want its arguments to appear on the class\n        # constructor. When changing this signature, also update the\n        # copy in httpserver.rst.\n        self.request_callback = request_callback\n        self.xheaders = xheaders\n        self.protocol = protocol\n        self.conn_params = HTTP1ConnectionParameters(\n            decompress=decompress_request,\n            chunk_size=chunk_size,\n            max_header_size=max_header_size,\n            header_timeout=idle_connection_timeout or 3600,\n            max_body_size=max_body_size,\n            body_timeout=body_timeout,\n            no_keep_alive=no_keep_alive,\n        )\n        TCPServer.__init__(\n            self,\n            ssl_options=ssl_options,\n            max_buffer_size=max_buffer_size,\n            read_chunk_size=chunk_size,\n        )\n        self._connections: set[HTTP1ServerConnection] = set()\n        self.trusted_downstream = trusted_downstream\n\n    @classmethod\n    def configurable_base(cls) -> type[Configurable]:\n        return HTTPServer\n\n    @classmethod\n    def configurable_default(cls) -> type[Configurable]:\n        return HTTPServer\n\n    async def close_all_connections(self) -> None:\n        \"\"\"Close all open connections and asynchronously wait for them to finish.\n\n        This method is used in combination with `~.TCPServer.stop` to\n        support clean shutdowns (especially for unittests). Typical\n        usage would call ``stop()`` first to stop accepting new\n        connections, then ``await close_all_connections()`` to wait for\n        existing connections to finish.\n\n        This method does not currently close open websocket connections.\n\n        Note that this method is a coroutine and must be called with ``await``.\n\n        \"\"\"\n        while self._connections:\n            # Peek at an arbitrary element of the set\n            conn = next(iter(self._connections))\n            await conn.close()\n\n    def handle_stream(self, stream: iostream.IOStream, address: tuple) -> None:\n        context = _HTTPRequestContext(\n            stream, address, self.protocol, self.trusted_downstream\n        )\n        conn = HTTP1ServerConnection(stream, self.conn_params, context)\n        self._connections.add(conn)\n        conn.start_serving(self)\n\n    def start_request(\n        self, server_conn: object, request_conn: httputil.HTTPConnection\n    ) -> httputil.HTTPMessageDelegate:\n        if isinstance(self.request_callback, httputil.HTTPServerConnectionDelegate):\n            delegate = self.request_callback.start_request(server_conn, request_conn)\n        else:\n            delegate = _CallableAdapter(self.request_callback, request_conn)\n\n        if self.xheaders:\n            delegate = _ProxyAdapter(delegate, request_conn)\n\n        return delegate\n\n    def on_close(self, server_conn: object) -> None:\n        self._connections.remove(typing.cast(HTTP1ServerConnection, server_conn))\n\n\nclass _CallableAdapter(httputil.HTTPMessageDelegate):\n    def __init__(\n        self,\n        request_callback: Callable[[httputil.HTTPServerRequest], None],\n        request_conn: httputil.HTTPConnection,\n    ) -> None:\n        self.connection = request_conn\n        self.request_callback = request_callback\n        self.request: httputil.HTTPServerRequest | None = None\n        self.delegate = None\n        self._chunks: list[bytes] = []\n\n    def headers_received(\n        self,\n        start_line: httputil.RequestStartLine | httputil.ResponseStartLine,\n        headers: httputil.HTTPHeaders,\n    ) -> Awaitable[None] | None:\n        self.request = httputil.HTTPServerRequest(\n            connection=self.connection,\n            start_line=typing.cast(httputil.RequestStartLine, start_line),\n            headers=headers,\n        )\n        return None\n\n    def data_received(self, chunk: bytes) -> Awaitable[None] | None:\n        self._chunks.append(chunk)\n        return None\n\n    def finish(self) -> None:\n        assert self.request is not None\n        self.request.body = b\"\".join(self._chunks)\n        self.request._parse_body()\n        self.request_callback(self.request)\n\n    def on_connection_close(self) -> None:\n        del self._chunks\n\n\nclass _HTTPRequestContext:\n    def __init__(\n        self,\n        stream: iostream.IOStream,\n        address: tuple,\n        protocol: str | None,\n        trusted_downstream: list[str] | None = None,\n    ) -> None:\n        self.address = address\n        # Save the socket's address family now so we know how to\n        # interpret self.address even after the stream is closed\n        # and its socket attribute replaced with None.\n        if stream.socket is not None:\n            self.address_family = stream.socket.family\n        else:\n            self.address_family = None\n        # In HTTPServerRequest we want an IP, not a full socket address.\n        if (\n            self.address_family in (socket.AF_INET, socket.AF_INET6)\n            and address is not None\n        ):\n            self.remote_ip = address[0]\n        else:\n            # Unix (or other) socket; fake the remote address.\n            self.remote_ip = \"0.0.0.0\"\n        if protocol:\n            self.protocol = protocol\n        elif isinstance(stream, iostream.SSLIOStream):\n            self.protocol = \"https\"\n        else:\n            self.protocol = \"http\"\n        self._orig_remote_ip = self.remote_ip\n        self._orig_protocol = self.protocol\n        self.trusted_downstream = set(trusted_downstream or [])\n\n    def __str__(self) -> str:\n        if self.address_family in (socket.AF_INET, socket.AF_INET6):\n            return self.remote_ip\n        elif isinstance(self.address, bytes):\n            # Python 3 with the -bb option warns about str(bytes),\n            # so convert it explicitly.\n            # Unix socket addresses are str on mac but bytes on linux.\n            return native_str(self.address)\n        else:\n            return str(self.address)\n\n    def _apply_xheaders(self, headers: httputil.HTTPHeaders) -> None:\n        \"\"\"Rewrite the ``remote_ip`` and ``protocol`` fields.\"\"\"\n        # Squid uses X-Forwarded-For, others use X-Real-Ip\n        ip = headers.get(\"X-Forwarded-For\", self.remote_ip)\n        # Skip trusted downstream hosts in X-Forwarded-For list\n        for ip in (cand.strip() for cand in reversed(ip.split(\",\"))):\n            if ip not in self.trusted_downstream:\n                break\n        ip = headers.get(\"X-Real-Ip\", ip)\n        if netutil.is_valid_ip(ip):\n            self.remote_ip = ip\n        # AWS uses X-Forwarded-Proto\n        proto_header = headers.get(\n            \"X-Scheme\", headers.get(\"X-Forwarded-Proto\", self.protocol)\n        )\n        if proto_header:\n            # use only the last proto entry if there is more than one\n            # TODO: support trusting multiple layers of proxied protocol\n            proto_header = proto_header.split(\",\")[-1].strip()\n        if proto_header in (\"http\", \"https\"):\n            self.protocol = proto_header\n\n    def _unapply_xheaders(self) -> None:\n        \"\"\"Undo changes from `_apply_xheaders`.\n\n        Xheaders are per-request so they should not leak to the next\n        request on the same connection.\n        \"\"\"\n        self.remote_ip = self._orig_remote_ip\n        self.protocol = self._orig_protocol\n\n\nclass _ProxyAdapter(httputil.HTTPMessageDelegate):\n    def __init__(\n        self,\n        delegate: httputil.HTTPMessageDelegate,\n        request_conn: httputil.HTTPConnection,\n    ) -> None:\n        self.connection = request_conn\n        self.delegate = delegate\n\n    def headers_received(\n        self,\n        start_line: httputil.RequestStartLine | httputil.ResponseStartLine,\n        headers: httputil.HTTPHeaders,\n    ) -> Awaitable[None] | None:\n        # TODO: either make context an official part of the\n        # HTTPConnection interface or figure out some other way to do this.\n        self.connection.context._apply_xheaders(headers)  # type: ignore\n        return self.delegate.headers_received(start_line, headers)\n\n    def data_received(self, chunk: bytes) -> Awaitable[None] | None:\n        return self.delegate.data_received(chunk)\n\n    def finish(self) -> None:\n        self.delegate.finish()\n        self._cleanup()\n\n    def on_connection_close(self) -> None:\n        self.delegate.on_connection_close()\n        self._cleanup()\n\n    def _cleanup(self) -> None:\n        self.connection.context._unapply_xheaders()  # type: ignore\n\n\nHTTPRequest = httputil.HTTPServerRequest\n"
  },
  {
    "path": "tornado/httputil.py",
    "content": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"HTTP utility code shared by clients and servers.\n\nThis module also defines the `HTTPServerRequest` class which is exposed\nvia `tornado.web.RequestHandler.request`.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport calendar\nimport collections.abc\nimport copy\nimport dataclasses\nimport datetime\nimport email.utils\nimport http.cookies\nimport re\nimport time\nimport unicodedata\nfrom functools import lru_cache\nfrom http.client import responses\nfrom ssl import SSLError\nfrom urllib.parse import parse_qsl, urlencode, urlparse, urlunparse\n\nfrom tornado.escape import native_str, parse_qs_bytes, to_unicode, utf8\nfrom tornado.util import ObjectDict, unicode_type\n\n# responses is unused in this file, but we re-export it to other files.\n# Reference it so pyflakes doesn't complain.\nresponses\n\nimport typing\nfrom collections.abc import Awaitable, Generator, Iterable, Iterator, Mapping\nfrom typing import (\n    AnyStr,\n)\n\nif typing.TYPE_CHECKING:\n    # These are relatively heavy imports and aren't needed in this file\n    # unless we're type-checking.\n    import unittest\n    from asyncio import Future\n\n# To be used with str.strip() and related methods.\nHTTP_WHITESPACE = \" \\t\"\n\n# Roughly the inverse of RequestHandler._VALID_HEADER_CHARS, but permits\n# chars greater than \\xFF (which may appear after decoding utf8).\n_FORBIDDEN_HEADER_CHARS_RE = re.compile(r\"[\\x00-\\x08\\x0A-\\x1F\\x7F]\")\n\n\nclass _ABNF:\n    \"\"\"Class that holds a subset of ABNF rules from RFC 9110 and friends.\n\n    Class attributes are re.Pattern objects, with the same name as in the RFC\n    (with hyphens changed to underscores). Currently contains only the subset\n    we use (which is why this class is not public). Unfortunately the fields\n    cannot be alphabetized as they are in the RFCs because of dependencies.\n    \"\"\"\n\n    # RFC 3986 (URI)\n    # The URI hostname ABNF is both complex (including detailed vaildation of IPv4 and IPv6\n    # literals) and not strict enough (a lot of punctuation is allowed by the ABNF even though\n    # it is not allowed by DNS). We simplify it by allowing square brackets and colons in any\n    # position, not only for their use in IPv6 literals.\n    uri_unreserved = re.compile(r\"[A-Za-z0-9\\-._~]\")\n    uri_sub_delims = re.compile(r\"[!$&'()*+,;=]\")\n    uri_pct_encoded = re.compile(r\"%[0-9A-Fa-f]{2}\")\n    uri_host = re.compile(\n        rf\"(?:[\\[\\]:]|{uri_unreserved.pattern}|{uri_sub_delims.pattern}|{uri_pct_encoded.pattern})*\"\n    )\n    uri_port = re.compile(r\"[0-9]*\")\n\n    # RFC 5234 (ABNF)\n    VCHAR = re.compile(r\"[\\x21-\\x7E]\")\n\n    # RFC 9110 (HTTP Semantics)\n    obs_text = re.compile(r\"[\\x80-\\xFF]\")\n    field_vchar = re.compile(rf\"(?:{VCHAR.pattern}|{obs_text.pattern})\")\n    # Not exactly from the RFC to simplify and combine field-content and field-value.\n    field_value = re.compile(\n        rf\"|\"\n        rf\"{field_vchar.pattern}|\"\n        rf\"{field_vchar.pattern}(?:{field_vchar.pattern}| |\\t)*{field_vchar.pattern}\"\n    )\n    tchar = re.compile(r\"[!#$%&'*+\\-.^_`|~0-9A-Za-z]\")\n    token = re.compile(rf\"{tchar.pattern}+\")\n    field_name = token\n    method = token\n    host = re.compile(rf\"(?:{uri_host.pattern})(?::{uri_port.pattern})?\")\n\n    # RFC 9112 (HTTP/1.1)\n    HTTP_version = re.compile(r\"HTTP/[0-9]\\.[0-9]\")\n    reason_phrase = re.compile(rf\"(?:[\\t ]|{VCHAR.pattern}|{obs_text.pattern})+\")\n    # request_target delegates to the URI RFC 3986, which is complex and may be\n    # too restrictive (for example, the WHATWG version of the URL spec allows non-ASCII\n    # characters). Instead, we allow everything but control chars and whitespace.\n    request_target = re.compile(rf\"{field_vchar.pattern}+\")\n    request_line = re.compile(\n        rf\"({method.pattern}) ({request_target.pattern}) ({HTTP_version.pattern})\"\n    )\n    status_code = re.compile(r\"[0-9]{3}\")\n    status_line = re.compile(\n        rf\"({HTTP_version.pattern}) ({status_code.pattern}) ({reason_phrase.pattern})?\"\n    )\n\n\n@lru_cache(1000)\ndef _normalize_header(name: str) -> str:\n    \"\"\"Map a header name to Http-Header-Case.\n\n    >>> _normalize_header(\"coNtent-TYPE\")\n    'Content-Type'\n    \"\"\"\n    return \"-\".join([w.capitalize() for w in name.split(\"-\")])\n\n\nclass HTTPHeaders(collections.abc.MutableMapping[str, str]):\n    \"\"\"A dictionary that maintains ``Http-Header-Case`` for all keys.\n\n    Supports multiple values per key via a pair of new methods,\n    `add()` and `get_list()`.  The regular dictionary interface\n    returns a single value per key, with multiple values joined by a\n    comma.\n\n    >>> h = HTTPHeaders({\"content-type\": \"text/html\"})\n    >>> list(h.keys())\n    ['Content-Type']\n    >>> h[\"Content-Type\"]\n    'text/html'\n\n    >>> h.add(\"Set-Cookie\", \"A=B\")\n    >>> h.add(\"Set-Cookie\", \"C=D\")\n    >>> h[\"set-cookie\"]\n    'A=B,C=D'\n    >>> h.get_list(\"set-cookie\")\n    ['A=B', 'C=D']\n\n    >>> for (k,v) in sorted(h.get_all()):\n    ...    print('%s: %s' % (k,v))\n    ...\n    Content-Type: text/html\n    Set-Cookie: A=B\n    Set-Cookie: C=D\n    \"\"\"\n\n    @typing.overload\n    def __init__(self, __arg: Mapping[str, list[str]]) -> None:\n        pass\n\n    @typing.overload\n    def __init__(self, __arg: Mapping[str, str]) -> None:\n        pass\n\n    @typing.overload\n    def __init__(self, *args: tuple[str, str]) -> None:\n        pass\n\n    @typing.overload\n    def __init__(self, **kwargs: str) -> None:\n        pass\n\n    def __init__(self, *args: typing.Any, **kwargs: str) -> None:\n        # Formally, HTTP headers are a mapping from a field name to a \"combined field value\",\n        # which may be constructed from multiple field lines by joining them with commas.\n        # In practice, however, some headers (notably Set-Cookie) do not follow this convention,\n        # so we maintain a mapping from field name to a list of field lines in self._as_list.\n        # self._combined_cache is a cache of the combined field values derived from self._as_list\n        # on demand (and cleared whenever the list is modified).\n        self._as_list: dict[str, list[str]] = {}\n        self._combined_cache: dict[str, str] = {}\n        self._last_key: str | None = None\n        if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], HTTPHeaders):\n            # Copy constructor\n            for k, v in args[0].get_all():\n                self.add(k, v)\n        else:\n            # Dict-style initialization\n            self.update(*args, **kwargs)\n\n    # new public methods\n\n    def add(self, name: str, value: str, *, _chars_are_bytes: bool = True) -> None:\n        \"\"\"Adds a new value for the given key.\"\"\"\n        if not _ABNF.field_name.fullmatch(name):\n            raise HTTPInputError(\"Invalid header name %r\" % name)\n        if _chars_are_bytes:\n            if not _ABNF.field_value.fullmatch(to_unicode(value)):\n                # TODO: the fact we still support bytes here (contrary to type annotations)\n                # and still test for it should probably be changed.\n                raise HTTPInputError(\"Invalid header value %r\" % value)\n        else:\n            if _FORBIDDEN_HEADER_CHARS_RE.search(value):\n                raise HTTPInputError(\"Invalid header value %r\" % value)\n        norm_name = _normalize_header(name)\n        self._last_key = norm_name\n        if norm_name in self:\n            self._combined_cache.pop(norm_name, None)\n            self._as_list[norm_name].append(value)\n        else:\n            self[norm_name] = value\n\n    def get_list(self, name: str) -> list[str]:\n        \"\"\"Returns all values for the given header as a list.\"\"\"\n        norm_name = _normalize_header(name)\n        return self._as_list.get(norm_name, [])\n\n    def get_all(self) -> Iterable[tuple[str, str]]:\n        \"\"\"Returns an iterable of all (name, value) pairs.\n\n        If a header has multiple values, multiple pairs will be\n        returned with the same name.\n        \"\"\"\n        for name, values in self._as_list.items():\n            for value in values:\n                yield (name, value)\n\n    def parse_line(self, line: str, *, _chars_are_bytes: bool = True) -> None:\n        r\"\"\"Updates the dictionary with a single header line.\n\n        >>> h = HTTPHeaders()\n        >>> h.parse_line(\"Content-Type: text/html\")\n        >>> h.get('content-type')\n        'text/html'\n        >>> h.parse_line(\"Content-Length: 42\\r\\n\")\n        >>> h.get('content-type')\n        'text/html'\n\n        .. versionchanged:: 6.5\n            Now supports lines with or without the trailing CRLF, making it possible\n            to pass lines from AsyncHTTPClient's header_callback directly to this method.\n\n        .. deprecated:: 6.5\n           In Tornado 7.0, certain deprecated features of HTTP will become errors.\n           Specifically, line folding and the use of LF (with CR) as a line separator\n           will be removed.\n        \"\"\"\n        if m := re.search(r\"\\r?\\n$\", line):\n            # RFC 9112 section 2.2: a recipient MAY recognize a single LF as a line\n            # terminator and ignore any preceding CR.\n            # TODO(7.0): Remove this support for LF-only line endings.\n            line = line[: m.start()]\n        if not line:\n            # Empty line, or the final CRLF of a header block.\n            return\n        if line[0] in HTTP_WHITESPACE:\n            # continuation of a multi-line header\n            # TODO(7.0): Remove support for line folding.\n            if self._last_key is None:\n                raise HTTPInputError(\"first header line cannot start with whitespace\")\n            new_part = \" \" + line.strip(HTTP_WHITESPACE)\n            if _chars_are_bytes:\n                if not _ABNF.field_value.fullmatch(new_part[1:]):\n                    raise HTTPInputError(\"Invalid header continuation %r\" % new_part)\n            else:\n                if _FORBIDDEN_HEADER_CHARS_RE.search(new_part):\n                    raise HTTPInputError(\"Invalid header value %r\" % new_part)\n            self._as_list[self._last_key][-1] += new_part\n            self._combined_cache.pop(self._last_key, None)\n        else:\n            try:\n                name, value = line.split(\":\", 1)\n            except ValueError:\n                raise HTTPInputError(\"no colon in header line\")\n            self.add(\n                name, value.strip(HTTP_WHITESPACE), _chars_are_bytes=_chars_are_bytes\n            )\n\n    @classmethod\n    def parse(cls, headers: str, *, _chars_are_bytes: bool = True) -> HTTPHeaders:\n        \"\"\"Returns a dictionary from HTTP header text.\n\n        >>> h = HTTPHeaders.parse(\"Content-Type: text/html\\\\r\\\\nContent-Length: 42\\\\r\\\\n\")\n        >>> sorted(h.items())\n        [('Content-Length', '42'), ('Content-Type', 'text/html')]\n\n        .. versionchanged:: 5.1\n\n           Raises `HTTPInputError` on malformed headers instead of a\n           mix of `KeyError`, and `ValueError`.\n\n        \"\"\"\n        # _chars_are_bytes is a hack. This method is used in two places, HTTP headers (in which\n        # non-ascii characters are to be interpreted as latin-1) and multipart/form-data (in which\n        # they are to be interpreted as utf-8). For historical reasons, this method handled this by\n        # expecting both callers to decode the headers to strings before parsing them. This wasn't a\n        # problem until we started doing stricter validation of the characters allowed in HTTP\n        # headers (using ABNF rules defined in terms of byte values), which inadvertently started\n        # disallowing non-latin1 characters in multipart/form-data filenames.\n        #\n        # This method should have accepted bytes and a desired encoding, but this change is being\n        # introduced in a patch release that shouldn't change the API. Instead, the _chars_are_bytes\n        # flag decides whether to use HTTP-style ABNF validation (treating the string as bytes\n        # smuggled through the latin1 encoding) or to accept any non-control unicode characters\n        # as required by multipart/form-data. This method will change to accept bytes in a future\n        # release.\n        h = cls()\n\n        start = 0\n        while True:\n            lf = headers.find(\"\\n\", start)\n            if lf == -1:\n                h.parse_line(headers[start:], _chars_are_bytes=_chars_are_bytes)\n                break\n            line = headers[start : lf + 1]\n            start = lf + 1\n            h.parse_line(line, _chars_are_bytes=_chars_are_bytes)\n        return h\n\n    # MutableMapping abstract method implementations.\n\n    def __setitem__(self, name: str, value: str) -> None:\n        norm_name = _normalize_header(name)\n        self._combined_cache[norm_name] = value\n        self._as_list[norm_name] = [value]\n\n    def __contains__(self, name: object) -> bool:\n        # This is an important optimization to avoid the expensive concatenation\n        # in __getitem__ when it's not needed.\n        if not isinstance(name, str):\n            return False\n        norm_name = _normalize_header(name)\n        return norm_name in self._as_list\n\n    def __getitem__(self, name: str) -> str:\n        header = _normalize_header(name)\n        if header not in self._combined_cache:\n            self._combined_cache[header] = \",\".join(self._as_list[header])\n        return self._combined_cache[header]\n\n    def __delitem__(self, name: str) -> None:\n        norm_name = _normalize_header(name)\n        del self._combined_cache[norm_name]\n        del self._as_list[norm_name]\n\n    def __len__(self) -> int:\n        return len(self._as_list)\n\n    def __iter__(self) -> Iterator[typing.Any]:\n        return iter(self._as_list)\n\n    def copy(self) -> HTTPHeaders:\n        # defined in dict but not in MutableMapping.\n        return HTTPHeaders(self)\n\n    # Use our overridden copy method for the copy.copy module.\n    # This makes shallow copies one level deeper, but preserves\n    # the appearance that HTTPHeaders is a single container.\n    __copy__ = copy\n\n    def __str__(self) -> str:\n        lines = []\n        for name, value in self.get_all():\n            lines.append(f\"{name}: {value}\\n\")\n        return \"\".join(lines)\n\n    __unicode__ = __str__\n\n\nclass HTTPServerRequest:\n    \"\"\"A single HTTP request.\n\n    All attributes are type `str` unless otherwise noted.\n\n    .. attribute:: method\n\n       HTTP request method, e.g. \"GET\" or \"POST\"\n\n    .. attribute:: uri\n\n       The requested uri.\n\n    .. attribute:: path\n\n       The path portion of `uri`\n\n    .. attribute:: query\n\n       The query portion of `uri`\n\n    .. attribute:: version\n\n       HTTP version specified in request, e.g. \"HTTP/1.1\"\n\n    .. attribute:: headers\n\n       `.HTTPHeaders` dictionary-like object for request headers.  Acts like\n       a case-insensitive dictionary with additional methods for repeated\n       headers.\n\n    .. attribute:: body\n\n       Request body, if present, as a byte string.\n\n    .. attribute:: remote_ip\n\n       Client's IP address as a string.  If ``HTTPServer.xheaders`` is set,\n       will pass along the real IP address provided by a load balancer\n       in the ``X-Real-Ip`` or ``X-Forwarded-For`` header.\n\n    .. versionchanged:: 3.1\n       The list format of ``X-Forwarded-For`` is now supported.\n\n    .. attribute:: protocol\n\n       The protocol used, either \"http\" or \"https\".  If ``HTTPServer.xheaders``\n       is set, will pass along the protocol used by a load balancer if\n       reported via an ``X-Scheme`` header.\n\n    .. attribute:: host\n\n       The requested hostname, usually taken from the ``Host`` header.\n\n    .. attribute:: arguments\n\n       GET/POST arguments are available in the arguments property, which\n       maps arguments names to lists of values (to support multiple values\n       for individual names). Names are of type `str`, while arguments\n       are byte strings.  Note that this is different from\n       `.RequestHandler.get_argument`, which returns argument values as\n       unicode strings.\n\n    .. attribute:: query_arguments\n\n       Same format as ``arguments``, but contains only arguments extracted\n       from the query string.\n\n       .. versionadded:: 3.2\n\n    .. attribute:: body_arguments\n\n       Same format as ``arguments``, but contains only arguments extracted\n       from the request body.\n\n       .. versionadded:: 3.2\n\n    .. attribute:: files\n\n       File uploads are available in the files property, which maps file\n       names to lists of `.HTTPFile`.\n\n    .. attribute:: connection\n\n       An HTTP request is attached to a single HTTP connection, which can\n       be accessed through the \"connection\" attribute. Since connections\n       are typically kept open in HTTP/1.1, multiple requests can be handled\n       sequentially on a single connection.\n\n    .. versionchanged:: 4.0\n       Moved from ``tornado.httpserver.HTTPRequest``.\n\n    .. deprecated:: 6.5.2\n       The ``host`` argument to the ``HTTPServerRequest`` constructor is deprecated. Use\n       ``headers[\"Host\"]`` instead. This argument was mistakenly removed in Tornado 6.5.0 and\n       temporarily restored in 6.5.2.\n    \"\"\"\n\n    path: str\n    query: str\n\n    # HACK: Used for stream_request_body\n    _body_future: Future[None]\n\n    def __init__(\n        self,\n        method: str | None = None,\n        uri: str | None = None,\n        version: str = \"HTTP/1.0\",\n        headers: HTTPHeaders | None = None,\n        body: bytes | None = None,\n        host: str | None = None,\n        files: dict[str, list[HTTPFile]] | None = None,\n        connection: HTTPConnection | None = None,\n        start_line: RequestStartLine | None = None,\n        server_connection: object | None = None,\n    ) -> None:\n        if start_line is not None:\n            method, uri, version = start_line\n        assert method\n        self.method = method\n        assert uri\n        self.uri = uri\n        self.version = version\n        self.headers = headers or HTTPHeaders()\n        self.body = body or b\"\"\n\n        # set remote IP and protocol\n        context = getattr(connection, \"context\", None)\n        self.remote_ip = getattr(context, \"remote_ip\", None)\n        self.protocol = getattr(context, \"protocol\", \"http\")\n\n        try:\n            self.host = host or self.headers[\"Host\"]\n        except KeyError:\n            if version == \"HTTP/1.0\":\n                # HTTP/1.0 does not require the Host header.\n                self.host = \"127.0.0.1\"\n            else:\n                raise HTTPInputError(\"Missing Host header\")\n        if not _ABNF.host.fullmatch(self.host):\n            raise HTTPInputError(\"Invalid Host header: %r\" % self.host)\n        if \",\" in self.host:\n            # https://www.rfc-editor.org/rfc/rfc9112.html#name-request-target\n            # Server MUST respond with 400 Bad Request if multiple\n            # Host headers are present.\n            #\n            # We test for the presence of a comma instead of the number of\n            # headers received because a proxy may have converted\n            # multiple headers into a single comma-separated value\n            # (per RFC 9110 section 5.3).\n            #\n            # This is technically a departure from the RFC since the ABNF\n            # does not forbid commas in the host header. However, since\n            # commas are not allowed in DNS names, it is appropriate to\n            # disallow them. (The same argument could be made for other special\n            # characters, but commas are the most problematic since they could\n            # be used to exploit differences between proxies when multiple headers\n            # are supplied).\n            raise HTTPInputError(\"Multiple host headers not allowed: %r\" % self.host)\n        self.host_name = split_host_and_port(self.host.lower())[0]\n        self.files = files or {}\n        self.connection = connection\n        self.server_connection = server_connection\n        self._start_time = time.time()\n        self._finish_time = None\n\n        if uri is not None:\n            self.path, sep, self.query = uri.partition(\"?\")\n        self.arguments = parse_qs_bytes(self.query, keep_blank_values=True)\n        self.query_arguments = copy.deepcopy(self.arguments)\n        self.body_arguments: dict[str, list[bytes]] = {}\n\n    @property\n    def cookies(self) -> dict[str, http.cookies.Morsel]:\n        \"\"\"A dictionary of ``http.cookies.Morsel`` objects.\"\"\"\n        if not hasattr(self, \"_cookies\"):\n            self._cookies: http.cookies.SimpleCookie = http.cookies.SimpleCookie()\n            if \"Cookie\" in self.headers:\n                try:\n                    parsed = parse_cookie(self.headers[\"Cookie\"])\n                except Exception:\n                    pass\n                else:\n                    for k, v in parsed.items():\n                        try:\n                            self._cookies[k] = v\n                        except Exception:\n                            # SimpleCookie imposes some restrictions on keys;\n                            # parse_cookie does not. Discard any cookies\n                            # with disallowed keys.\n                            pass\n        return self._cookies\n\n    def full_url(self) -> str:\n        \"\"\"Reconstructs the full URL for this request.\"\"\"\n        return self.protocol + \"://\" + self.host + self.uri  # type: ignore[operator]\n\n    def request_time(self) -> float:\n        \"\"\"Returns the amount of time it took for this request to execute.\"\"\"\n        if self._finish_time is None:\n            return time.time() - self._start_time\n        else:\n            return self._finish_time - self._start_time\n\n    def get_ssl_certificate(self, binary_form: bool = False) -> None | dict | bytes:\n        \"\"\"Returns the client's SSL certificate, if any.\n\n        To use client certificates, the HTTPServer's\n        `ssl.SSLContext.verify_mode` field must be set, e.g.::\n\n            ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)\n            ssl_ctx.load_cert_chain(\"foo.crt\", \"foo.key\")\n            ssl_ctx.load_verify_locations(\"cacerts.pem\")\n            ssl_ctx.verify_mode = ssl.CERT_REQUIRED\n            server = HTTPServer(app, ssl_options=ssl_ctx)\n\n        By default, the return value is a dictionary (or None, if no\n        client certificate is present).  If ``binary_form`` is true, a\n        DER-encoded form of the certificate is returned instead.  See\n        SSLSocket.getpeercert() in the standard library for more\n        details.\n        http://docs.python.org/library/ssl.html#sslsocket-objects\n        \"\"\"\n        try:\n            if self.connection is None:\n                return None\n            # TODO: add a method to HTTPConnection for this so it can work with HTTP/2\n            return self.connection.stream.socket.getpeercert(  # type: ignore\n                binary_form=binary_form\n            )\n        except SSLError:\n            return None\n\n    def _parse_body(self) -> None:\n        parse_body_arguments(\n            self.headers.get(\"Content-Type\", \"\"),\n            self.body,\n            self.body_arguments,\n            self.files,\n            self.headers,\n        )\n\n        for k, v in self.body_arguments.items():\n            self.arguments.setdefault(k, []).extend(v)\n\n    def __repr__(self) -> str:\n        attrs = (\"protocol\", \"host\", \"method\", \"uri\", \"version\", \"remote_ip\")\n        args = \", \".join([f\"{n}={getattr(self, n)!r}\" for n in attrs])\n        return f\"{self.__class__.__name__}({args})\"\n\n\nclass HTTPInputError(Exception):\n    \"\"\"Exception class for malformed HTTP requests or responses\n    from remote sources.\n\n    .. versionadded:: 4.0\n    \"\"\"\n\n    pass\n\n\nclass HTTPOutputError(Exception):\n    \"\"\"Exception class for errors in HTTP output.\n\n    .. versionadded:: 4.0\n    \"\"\"\n\n    pass\n\n\nclass HTTPServerConnectionDelegate:\n    \"\"\"Implement this interface to handle requests from `.HTTPServer`.\n\n    .. versionadded:: 4.0\n    \"\"\"\n\n    def start_request(\n        self, server_conn: object, request_conn: HTTPConnection\n    ) -> HTTPMessageDelegate:\n        \"\"\"This method is called by the server when a new request has started.\n\n        :arg server_conn: is an opaque object representing the long-lived\n            (e.g. tcp-level) connection.\n        :arg request_conn: is a `.HTTPConnection` object for a single\n            request/response exchange.\n\n        This method should return a `.HTTPMessageDelegate`.\n        \"\"\"\n        raise NotImplementedError()\n\n    def on_close(self, server_conn: object) -> None:\n        \"\"\"This method is called when a connection has been closed.\n\n        :arg server_conn: is a server connection that has previously been\n            passed to ``start_request``.\n        \"\"\"\n        pass\n\n\nclass HTTPMessageDelegate:\n    \"\"\"Implement this interface to handle an HTTP request or response.\n\n    .. versionadded:: 4.0\n    \"\"\"\n\n    # TODO: genericize this class to avoid exposing the Union.\n    def headers_received(\n        self,\n        start_line: RequestStartLine | ResponseStartLine,\n        headers: HTTPHeaders,\n    ) -> Awaitable[None] | None:\n        \"\"\"Called when the HTTP headers have been received and parsed.\n\n        :arg start_line: a `.RequestStartLine` or `.ResponseStartLine`\n            depending on whether this is a client or server message.\n        :arg headers: a `.HTTPHeaders` instance.\n\n        Some `.HTTPConnection` methods can only be called during\n        ``headers_received``.\n\n        May return a `.Future`; if it does the body will not be read\n        until it is done.\n        \"\"\"\n        pass\n\n    def data_received(self, chunk: bytes) -> Awaitable[None] | None:\n        \"\"\"Called when a chunk of data has been received.\n\n        May return a `.Future` for flow control.\n        \"\"\"\n        pass\n\n    def finish(self) -> None:\n        \"\"\"Called after the last chunk of data has been received.\"\"\"\n        pass\n\n    def on_connection_close(self) -> None:\n        \"\"\"Called if the connection is closed without finishing the request.\n\n        If ``headers_received`` is called, either ``finish`` or\n        ``on_connection_close`` will be called, but not both.\n        \"\"\"\n        pass\n\n\nclass HTTPConnection:\n    \"\"\"Applications use this interface to write their responses.\n\n    .. versionadded:: 4.0\n    \"\"\"\n\n    def write_headers(\n        self,\n        start_line: RequestStartLine | ResponseStartLine,\n        headers: HTTPHeaders,\n        chunk: bytes | None = None,\n    ) -> Future[None]:\n        \"\"\"Write an HTTP header block.\n\n        :arg start_line: a `.RequestStartLine` or `.ResponseStartLine`.\n        :arg headers: a `.HTTPHeaders` instance.\n        :arg chunk: the first (optional) chunk of data.  This is an optimization\n            so that small responses can be written in the same call as their\n            headers.\n\n        The ``version`` field of ``start_line`` is ignored.\n\n        Returns a future for flow control.\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed.\n        \"\"\"\n        raise NotImplementedError()\n\n    def write(self, chunk: bytes) -> Future[None]:\n        \"\"\"Writes a chunk of body data.\n\n        Returns a future for flow control.\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed.\n        \"\"\"\n        raise NotImplementedError()\n\n    def finish(self) -> None:\n        \"\"\"Indicates that the last body data has been written.\"\"\"\n        raise NotImplementedError()\n\n\ndef url_concat(\n    url: str,\n    args: None | dict[str, str] | list[tuple[str, str]] | tuple[tuple[str, str], ...],\n) -> str:\n    \"\"\"Concatenate url and arguments regardless of whether\n    url has existing query parameters.\n\n    ``args`` may be either a dictionary or a list of key-value pairs\n    (the latter allows for multiple values with the same key.\n\n    >>> url_concat(\"http://example.com/foo\", dict(c=\"d\"))\n    'http://example.com/foo?c=d'\n    >>> url_concat(\"http://example.com/foo?a=b\", dict(c=\"d\"))\n    'http://example.com/foo?a=b&c=d'\n    >>> url_concat(\"http://example.com/foo?a=b\", [(\"c\", \"d\"), (\"c\", \"d2\")])\n    'http://example.com/foo?a=b&c=d&c=d2'\n    \"\"\"\n    if args is None:\n        return url\n    parsed_url = urlparse(url)\n    if isinstance(args, dict):\n        parsed_query = parse_qsl(parsed_url.query, keep_blank_values=True)\n        parsed_query.extend(args.items())\n    elif isinstance(args, list) or isinstance(args, tuple):\n        parsed_query = parse_qsl(parsed_url.query, keep_blank_values=True)\n        parsed_query.extend(args)\n    else:\n        err = \"'args' parameter should be dict, list or tuple. Not {0}\".format(\n            type(args)\n        )\n        raise TypeError(err)\n    final_query = urlencode(parsed_query)\n    url = urlunparse(\n        (\n            parsed_url[0],\n            parsed_url[1],\n            parsed_url[2],\n            parsed_url[3],\n            final_query,\n            parsed_url[5],\n        )\n    )\n    return url\n\n\nclass HTTPFile(ObjectDict):\n    \"\"\"Represents a file uploaded via a form.\n\n    For backwards compatibility, its instance attributes are also\n    accessible as dictionary keys.\n\n    * ``filename``\n    * ``body``\n    * ``content_type``\n    \"\"\"\n\n    filename: str\n    body: bytes\n    content_type: str\n\n\ndef _parse_request_range(\n    range_header: str,\n) -> tuple[int | None, int | None] | None:\n    \"\"\"Parses a Range header.\n\n    Returns either ``None`` or tuple ``(start, end)``.\n    Note that while the HTTP headers use inclusive byte positions,\n    this method returns indexes suitable for use in slices.\n\n    >>> start, end = _parse_request_range(\"bytes=1-2\")\n    >>> start, end\n    (1, 3)\n    >>> [0, 1, 2, 3, 4][start:end]\n    [1, 2]\n    >>> _parse_request_range(\"bytes=6-\")\n    (6, None)\n    >>> _parse_request_range(\"bytes=-6\")\n    (-6, None)\n    >>> _parse_request_range(\"bytes=-0\")\n    (None, 0)\n    >>> _parse_request_range(\"bytes=\")\n    (None, None)\n    >>> _parse_request_range(\"foo=42\")\n    >>> _parse_request_range(\"bytes=1-2,6-10\")\n\n    Note: only supports one range (ex, ``bytes=1-2,6-10`` is not allowed).\n\n    See [0] for the details of the range header.\n\n    [0]: http://greenbytes.de/tech/webdav/draft-ietf-httpbis-p5-range-latest.html#byte.ranges\n    \"\"\"\n    unit, _, value = range_header.partition(\"=\")\n    unit, value = unit.strip(), value.strip()\n    if unit != \"bytes\":\n        return None\n    start_b, _, end_b = value.partition(\"-\")\n    try:\n        start = _int_or_none(start_b)\n        end = _int_or_none(end_b)\n    except ValueError:\n        return None\n    if end is not None:\n        if start is None:\n            if end != 0:\n                start = -end\n                end = None\n        else:\n            end += 1\n    return (start, end)\n\n\ndef _get_content_range(start: int | None, end: int | None, total: int) -> str:\n    \"\"\"Returns a suitable Content-Range header:\n\n    >>> print(_get_content_range(None, 1, 4))\n    bytes 0-0/4\n    >>> print(_get_content_range(1, 3, 4))\n    bytes 1-2/4\n    >>> print(_get_content_range(None, None, 4))\n    bytes 0-3/4\n    \"\"\"\n    start = start or 0\n    end = (end or total) - 1\n    return f\"bytes {start}-{end}/{total}\"\n\n\ndef _int_or_none(val: str) -> int | None:\n    val = val.strip()\n    if val == \"\":\n        return None\n    return int(val)\n\n\n@dataclasses.dataclass\nclass ParseMultipartConfig:\n    \"\"\"This class configures the parsing of ``multipart/form-data`` request bodies.\n\n    Its primary purpose is to place limits on the size and complexity of request messages\n    to avoid potential denial-of-service attacks.\n\n    .. versionadded:: 6.5.5\n    \"\"\"\n\n    enabled: bool = True\n    \"\"\"Set this to false to disable the parsing of ``multipart/form-data`` requests entirely.\n\n    This may be desirable for applications that do not need to handle this format, since\n    multipart request have a history of DoS vulnerabilities in Tornado. Multipart requests\n    are used primarily for ``<input type=\"file\">`` in HTML forms, or in APIs that mimic this\n    format. File uploads that use the HTTP ``PUT`` method generally do not use the multipart\n    format.\n    \"\"\"\n\n    max_parts: int = 100\n    \"\"\"The maximum number of parts accepted in a multipart request.\n\n    Each ``<input>`` element in an HTML form corresponds to at least one \"part\".\n    \"\"\"\n\n    max_part_header_size: int = 10 * 1024\n    \"\"\"The maximum size of the headers for each part of a multipart request.\n\n    The header for a part contains the name of the form field and optionally the filename\n    and content type of the uploaded file.\n    \"\"\"\n\n\n@dataclasses.dataclass\nclass ParseBodyConfig:\n    \"\"\"This class configures the parsing of request bodies.\n\n    .. versionadded:: 6.5.5\n    \"\"\"\n\n    multipart: ParseMultipartConfig = dataclasses.field(\n        default_factory=ParseMultipartConfig\n    )\n    \"\"\"Configuration for ``multipart/form-data`` request bodies.\"\"\"\n\n\n_DEFAULT_PARSE_BODY_CONFIG = ParseBodyConfig()\n\n\ndef set_parse_body_config(config: ParseBodyConfig) -> None:\n    r\"\"\"Sets the **global** default configuration for parsing request bodies.\n\n    This global setting is provided as a stopgap for applications that need to raise the limits\n    introduced in Tornado 6.5.5, or who wish to disable the parsing of multipart/form-data bodies\n    entirely. Non-global configuration for this functionality will be introduced in a future\n    release.\n\n    >>> content_type = \"multipart/form-data; boundary=foo\"\n    >>> multipart_body = b\"--foo--\\r\\n\"\n    >>> parse_body_arguments(content_type, multipart_body, {}, {})\n    >>> multipart_config = ParseMultipartConfig(enabled=False)\n    >>> config = ParseBodyConfig(multipart=multipart_config)\n    >>> set_parse_body_config(config)\n    >>> parse_body_arguments(content_type, multipart_body, {}, {})\n    Traceback (most recent call last):\n        ...\n    tornado.httputil.HTTPInputError: ...: multipart/form-data parsing is disabled\n    >>> set_parse_body_config(ParseBodyConfig())  # reset to defaults\n\n    .. versionadded:: 6.5.5\n    \"\"\"\n    global _DEFAULT_PARSE_BODY_CONFIG\n    _DEFAULT_PARSE_BODY_CONFIG = config\n\n\ndef parse_body_arguments(\n    content_type: str,\n    body: bytes,\n    arguments: dict[str, list[bytes]],\n    files: dict[str, list[HTTPFile]],\n    headers: HTTPHeaders | None = None,\n    *,\n    config: ParseBodyConfig | None = None,\n) -> None:\n    \"\"\"Parses a form request body.\n\n    Supports ``application/x-www-form-urlencoded`` and\n    ``multipart/form-data``.  The ``content_type`` parameter should be\n    a string and ``body`` should be a byte string.  The ``arguments``\n    and ``files`` parameters are dictionaries that will be updated\n    with the parsed contents.\n    \"\"\"\n    if config is None:\n        config = _DEFAULT_PARSE_BODY_CONFIG\n    if content_type.startswith(\"application/x-www-form-urlencoded\"):\n        if headers and \"Content-Encoding\" in headers:\n            raise HTTPInputError(\n                \"Unsupported Content-Encoding: %s\" % headers[\"Content-Encoding\"]\n            )\n        try:\n            # real charset decoding will happen in RequestHandler.decode_argument()\n            uri_arguments = parse_qs_bytes(body, keep_blank_values=True)\n        except Exception as e:\n            raise HTTPInputError(\"Invalid x-www-form-urlencoded body: %s\" % e) from e\n        for name, values in uri_arguments.items():\n            if values:\n                arguments.setdefault(name, []).extend(values)\n    elif content_type.startswith(\"multipart/form-data\"):\n        if headers and \"Content-Encoding\" in headers:\n            raise HTTPInputError(\n                \"Unsupported Content-Encoding: %s\" % headers[\"Content-Encoding\"]\n            )\n        try:\n            fields = content_type.split(\";\")\n            if fields[0].strip() != \"multipart/form-data\":\n                # This catches \"Content-Type: multipart/form-dataxyz\"\n                raise HTTPInputError(\"Invalid content type\")\n            for field in fields:\n                k, sep, v = field.strip().partition(\"=\")\n                if k == \"boundary\" and v:\n                    parse_multipart_form_data(\n                        utf8(v), body, arguments, files, config=config.multipart\n                    )\n                    break\n            else:\n                raise HTTPInputError(\"multipart boundary not found\")\n        except Exception as e:\n            raise HTTPInputError(\"Invalid multipart/form-data: %s\" % e) from e\n\n\ndef parse_multipart_form_data(\n    boundary: bytes,\n    data: bytes,\n    arguments: dict[str, list[bytes]],\n    files: dict[str, list[HTTPFile]],\n    *,\n    config: ParseMultipartConfig | None = None,\n) -> None:\n    \"\"\"Parses a ``multipart/form-data`` body.\n\n    The ``boundary`` and ``data`` parameters are both byte strings.\n    The dictionaries given in the arguments and files parameters\n    will be updated with the contents of the body.\n\n    .. versionchanged:: 5.1\n\n       Now recognizes non-ASCII filenames in RFC 2231/5987\n       (``filename*=``) format.\n    \"\"\"\n    if config is None:\n        config = _DEFAULT_PARSE_BODY_CONFIG.multipart\n    if not config.enabled:\n        raise HTTPInputError(\"multipart/form-data parsing is disabled\")\n    # The standard allows for the boundary to be quoted in the header,\n    # although it's rare (it happens at least for google app engine\n    # xmpp).  I think we're also supposed to handle backslash-escapes\n    # here but I'll save that until we see a client that uses them\n    # in the wild.\n    if boundary.startswith(b'\"') and boundary.endswith(b'\"'):\n        boundary = boundary[1:-1]\n    final_boundary_index = data.rfind(b\"--\" + boundary + b\"--\")\n    if final_boundary_index == -1:\n        raise HTTPInputError(\"Invalid multipart/form-data: no final boundary found\")\n    parts = data[:final_boundary_index].split(b\"--\" + boundary + b\"\\r\\n\")\n    if len(parts) > config.max_parts:\n        raise HTTPInputError(\"multipart/form-data has too many parts\")\n    for part in parts:\n        if not part:\n            continue\n        eoh = part.find(b\"\\r\\n\\r\\n\")\n        if eoh == -1:\n            raise HTTPInputError(\"multipart/form-data missing headers\")\n        if eoh > config.max_part_header_size:\n            raise HTTPInputError(\"multipart/form-data part header too large\")\n        headers = HTTPHeaders.parse(part[:eoh].decode(\"utf-8\"), _chars_are_bytes=False)\n        disp_header = headers.get(\"Content-Disposition\", \"\")\n        disposition, disp_params = _parse_header(disp_header)\n        if disposition != \"form-data\" or not part.endswith(b\"\\r\\n\"):\n            raise HTTPInputError(\"Invalid multipart/form-data\")\n        value = part[eoh + 4 : -2]\n        if not disp_params.get(\"name\"):\n            raise HTTPInputError(\"multipart/form-data missing name\")\n        name = disp_params[\"name\"]\n        if disp_params.get(\"filename\"):\n            ctype = headers.get(\"Content-Type\", \"application/unknown\")\n            files.setdefault(name, []).append(\n                HTTPFile(\n                    filename=disp_params[\"filename\"], body=value, content_type=ctype\n                )\n            )\n        else:\n            arguments.setdefault(name, []).append(value)\n\n\ndef format_timestamp(\n    ts: int | float | tuple | time.struct_time | datetime.datetime,\n) -> str:\n    \"\"\"Formats a timestamp in the format used by HTTP.\n\n    The argument may be a numeric timestamp as returned by `time.time`,\n    a time tuple as returned by `time.gmtime`, or a `datetime.datetime`\n    object. Naive `datetime.datetime` objects are assumed to represent\n    UTC; aware objects are converted to UTC before formatting.\n\n    >>> format_timestamp(1359312200)\n    'Sun, 27 Jan 2013 18:43:20 GMT'\n    \"\"\"\n    if isinstance(ts, (int, float)):\n        time_num = ts\n    elif isinstance(ts, (tuple, time.struct_time)):\n        time_num = calendar.timegm(ts)\n    elif isinstance(ts, datetime.datetime):\n        time_num = calendar.timegm(ts.utctimetuple())\n    else:\n        raise TypeError(\"unknown timestamp type: %r\" % ts)\n    return email.utils.formatdate(time_num, usegmt=True)\n\n\nclass RequestStartLine(typing.NamedTuple):\n    method: str\n    path: str\n    version: str\n\n\ndef parse_request_start_line(line: str) -> RequestStartLine:\n    \"\"\"Returns a (method, path, version) tuple for an HTTP 1.x request line.\n\n    The response is a `typing.NamedTuple`.\n\n    >>> parse_request_start_line(\"GET /foo HTTP/1.1\")\n    RequestStartLine(method='GET', path='/foo', version='HTTP/1.1')\n    \"\"\"\n    match = _ABNF.request_line.fullmatch(line)\n    if not match:\n        # https://tools.ietf.org/html/rfc7230#section-3.1.1\n        # invalid request-line SHOULD respond with a 400 (Bad Request)\n        raise HTTPInputError(\"Malformed HTTP request line\")\n    r = RequestStartLine(match.group(1), match.group(2), match.group(3))\n    if not r.version.startswith(\"HTTP/1\"):\n        # HTTP/2 and above doesn't use parse_request_start_line.\n        # This could be folded into the regex but we don't want to deviate\n        # from the ABNF in the RFCs.\n        raise HTTPInputError(\"Unexpected HTTP version %r\" % r.version)\n    return r\n\n\nclass ResponseStartLine(typing.NamedTuple):\n    version: str\n    code: int\n    reason: str\n\n\ndef parse_response_start_line(line: str) -> ResponseStartLine:\n    \"\"\"Returns a (version, code, reason) tuple for an HTTP 1.x response line.\n\n    The response is a `typing.NamedTuple`.\n\n    >>> parse_response_start_line(\"HTTP/1.1 200 OK\")\n    ResponseStartLine(version='HTTP/1.1', code=200, reason='OK')\n    \"\"\"\n    match = _ABNF.status_line.fullmatch(line)\n    if not match:\n        raise HTTPInputError(\"Error parsing response start line\")\n    r = ResponseStartLine(match.group(1), int(match.group(2)), match.group(3))\n    if not r.version.startswith(\"HTTP/1\"):\n        # HTTP/2 and above doesn't use parse_response_start_line.\n        raise HTTPInputError(\"Unexpected HTTP version %r\" % r.version)\n    return r\n\n\n# _parseparam and _parse_header are copied and modified from python2.7's cgi.py\n# The original 2.7 version of this code did not correctly support some\n# combinations of semicolons and double quotes.\n# It has also been modified to support valueless parameters as seen in\n# websocket extension negotiations, and to support non-ascii values in\n# RFC 2231/5987 format.\n#\n# _parseparam has been further modified with the logic from\n# https://github.com/python/cpython/pull/136072/files\n# to avoid quadratic behavior when parsing semicolons in quoted strings.\n#\n# TODO: See if we can switch to email.message.Message for this functionality.\n# This is the suggested replacement for the cgi.py module now that cgi has\n# been removed from recent versions of Python.  We need to verify that\n# the email module is consistent with our existing behavior (and all relevant\n# RFCs for multipart/form-data) before making this change.\n\n\ndef _parseparam(s: str) -> Generator[str]:\n    start = 0\n    while s.find(\";\", start) == start:\n        start += 1\n        end = s.find(\";\", start)\n        ind, diff = start, 0\n        while end > 0:\n            diff += s.count('\"', ind, end) - s.count('\\\\\"', ind, end)\n            if diff % 2 == 0:\n                break\n            end, ind = ind, s.find(\";\", end + 1)\n        if end < 0:\n            end = len(s)\n        f = s[start:end]\n        yield f.strip()\n        start = end\n\n\ndef _parse_header(line: str) -> tuple[str, dict[str, str]]:\n    r\"\"\"Parse a Content-type like header.\n\n    Return the main content-type and a dictionary of options.\n\n    >>> d = \"form-data; foo=\\\"b\\\\\\\\a\\\\\\\"r\\\"; file*=utf-8''T%C3%A4st\"\n    >>> ct, d = _parse_header(d)\n    >>> ct\n    'form-data'\n    >>> d['file'] == r'T\\u00e4st'.encode('ascii').decode('unicode_escape')\n    True\n    >>> d['foo']\n    'b\\\\a\"r'\n    \"\"\"\n    parts = _parseparam(\";\" + line)\n    key = next(parts)\n    # decode_params treats first argument special, but we already stripped key\n    params = [(\"Dummy\", \"value\")]\n    for p in parts:\n        i = p.find(\"=\")\n        if i >= 0:\n            name = p[:i].strip().lower()\n            value = p[i + 1 :].strip()\n            params.append((name, native_str(value)))\n    decoded_params = email.utils.decode_params(params)\n    decoded_params.pop(0)  # get rid of the dummy again\n    pdict = {}\n    for name, decoded_value in decoded_params:\n        value = email.utils.collapse_rfc2231_value(decoded_value)\n        if len(value) >= 2 and value[0] == '\"' and value[-1] == '\"':\n            value = value[1:-1]\n        pdict[name] = value\n    return key, pdict\n\n\ndef _encode_header(key: str, pdict: dict[str, str]) -> str:\n    \"\"\"Inverse of _parse_header.\n\n    >>> _encode_header('permessage-deflate',\n    ...     {'client_max_window_bits': 15, 'client_no_context_takeover': None})\n    'permessage-deflate; client_max_window_bits=15; client_no_context_takeover'\n    \"\"\"\n    if not pdict:\n        return key\n    out = [key]\n    # Sort the parameters just to make it easy to test.\n    for k, v in sorted(pdict.items()):\n        if v is None:\n            out.append(k)\n        else:\n            # TODO: quote if necessary.\n            out.append(f\"{k}={v}\")\n    return \"; \".join(out)\n\n\ndef encode_username_password(username: str | bytes, password: str | bytes) -> bytes:\n    \"\"\"Encodes a username/password pair in the format used by HTTP auth.\n\n    The return value is a byte string in the form ``username:password``.\n\n    .. versionadded:: 5.1\n    \"\"\"\n    if isinstance(username, unicode_type):\n        username = unicodedata.normalize(\"NFC\", username)\n    if isinstance(password, unicode_type):\n        password = unicodedata.normalize(\"NFC\", password)\n    return utf8(username) + b\":\" + utf8(password)\n\n\ndef doctests() -> unittest.TestSuite:\n    import doctest\n\n    return doctest.DocTestSuite(optionflags=doctest.ELLIPSIS)\n\n\n_netloc_re = re.compile(r\"^(.+):(\\d+)$\")\n\n\ndef split_host_and_port(netloc: str) -> tuple[str, int | None]:\n    \"\"\"Returns ``(host, port)`` tuple from ``netloc``.\n\n    Returned ``port`` will be ``None`` if not present.\n\n    .. versionadded:: 4.1\n    \"\"\"\n    match = _netloc_re.match(netloc)\n    if match:\n        host = match.group(1)\n        port: int | None = int(match.group(2))\n    else:\n        host = netloc\n        port = None\n    return (host, port)\n\n\ndef qs_to_qsl(qs: dict[str, list[AnyStr]]) -> Iterable[tuple[str, AnyStr]]:\n    \"\"\"Generator converting a result of ``parse_qs`` back to name-value pairs.\n\n    .. versionadded:: 5.0\n    \"\"\"\n    for k, vs in qs.items():\n        for v in vs:\n            yield (k, v)\n\n\n_unquote_sub = re.compile(r\"\\\\(?:([0-3][0-7][0-7])|(.))\").sub\n\n\ndef _unquote_replace(m: re.Match) -> str:\n    if m[1]:\n        return chr(int(m[1], 8))\n    else:\n        return m[2]\n\n\ndef _unquote_cookie(s: str) -> str:\n    \"\"\"Handle double quotes and escaping in cookie values.\n\n    This method is copied verbatim from the Python 3.13 standard\n    library (http.cookies._unquote) so we don't have to depend on\n    non-public interfaces.\n    \"\"\"\n    # If there aren't any doublequotes,\n    # then there can't be any special characters.  See RFC 2109.\n    if s is None or len(s) < 2:\n        return s\n    if s[0] != '\"' or s[-1] != '\"':\n        return s\n\n    # We have to assume that we must decode this string.\n    # Down to work.\n\n    # Remove the \"s\n    s = s[1:-1]\n\n    # Check for special sequences.  Examples:\n    #    \\012 --> \\n\n    #    \\\"   --> \"\n    #\n    return _unquote_sub(_unquote_replace, s)\n\n\ndef parse_cookie(cookie: str) -> dict[str, str]:\n    \"\"\"Parse a ``Cookie`` HTTP header into a dict of name/value pairs.\n\n    This function attempts to mimic browser cookie parsing behavior;\n    it specifically does not follow any of the cookie-related RFCs\n    (because browsers don't either).\n\n    The algorithm used is identical to that used by Django version 1.9.10.\n\n    .. versionadded:: 4.4.2\n    \"\"\"\n    cookiedict = {}\n    for chunk in cookie.split(\";\"):\n        if \"=\" in chunk:\n            key, val = chunk.split(\"=\", 1)\n        else:\n            # Assume an empty name per\n            # https://bugzilla.mozilla.org/show_bug.cgi?id=169091\n            key, val = \"\", chunk\n        key, val = key.strip(), val.strip()\n        if key or val:\n            # unquote using Python's algorithm.\n            cookiedict[key] = _unquote_cookie(val)\n    return cookiedict\n"
  },
  {
    "path": "tornado/ioloop.py",
    "content": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"An I/O event loop for non-blocking sockets.\n\nIn Tornado 6.0, `.IOLoop` is a wrapper around the `asyncio` event loop, with a\nslightly different interface. The `.IOLoop` interface is now provided primarily\nfor backwards compatibility; new code should generally use the `asyncio` event\nloop interface directly. The `IOLoop.current` class method provides the\n`IOLoop` instance corresponding to the running `asyncio` event loop.\n\n\"\"\"\n\nfrom __future__ import annotations\n\nimport asyncio\nimport concurrent.futures\nimport datetime\nimport functools\nimport math\nimport numbers\nimport os\nimport random\nimport sys\nimport time\nimport typing\nimport warnings\nfrom collections.abc import Awaitable, Callable\nfrom inspect import isawaitable\nfrom typing import Any, Protocol, TypedDict, TypeVar\n\nfrom tornado.concurrent import (\n    Future,\n    chain_future,\n    future_add_done_callback,\n    future_set_exc_info,\n    is_future,\n)\nfrom tornado.log import app_log\nfrom tornado.util import Configurable, TimeoutError, import_object\n\n\nclass _Selectable(Protocol):\n    def fileno(self) -> int:\n        pass\n\n    def close(self) -> None:\n        pass\n\n\n_T = TypeVar(\"_T\")\n_S = TypeVar(\"_S\", bound=_Selectable)\n\n\nclass IOLoop(Configurable):\n    \"\"\"An I/O event loop.\n\n    As of Tornado 6.0, `IOLoop` is a wrapper around the `asyncio` event loop.\n\n    Example usage for a simple TCP server:\n\n    .. testcode::\n\n        import asyncio\n        import errno\n        import functools\n        import socket\n\n        import tornado\n        from tornado.iostream import IOStream\n\n        async def handle_connection(connection, address):\n            stream = IOStream(connection)\n            message = await stream.read_until_close()\n            print(\"message from client:\", message.decode().strip())\n\n        def connection_ready(sock, fd, events):\n            while True:\n                try:\n                    connection, address = sock.accept()\n                except BlockingIOError:\n                    return\n                connection.setblocking(0)\n                io_loop = tornado.ioloop.IOLoop.current()\n                io_loop.spawn_callback(handle_connection, connection, address)\n\n        async def main():\n            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)\n            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n            sock.setblocking(0)\n            sock.bind((\"\", 8888))\n            sock.listen(128)\n\n            io_loop = tornado.ioloop.IOLoop.current()\n            callback = functools.partial(connection_ready, sock)\n            io_loop.add_handler(sock.fileno(), callback, io_loop.READ)\n            await asyncio.Event().wait()\n\n        if __name__ == \"__main__\":\n            asyncio.run(main())\n\n    Most applications should not attempt to construct an `IOLoop` directly,\n    and instead initialize the `asyncio` event loop and use `IOLoop.current()`.\n    In some cases, such as in test frameworks when initializing an `IOLoop`\n    to be run in a secondary thread, it may be appropriate to construct\n    an `IOLoop` with ``IOLoop(make_current=False)``.\n\n    In general, an `IOLoop` cannot survive a fork or be shared across processes\n    in any way. When multiple processes are being used, each process should\n    create its own `IOLoop`, which also implies that any objects which depend on\n    the `IOLoop` (such as `.AsyncHTTPClient`) must also be created in the child\n    processes. As a guideline, anything that starts processes (including the\n    `tornado.process` and `multiprocessing` modules) should do so as early as\n    possible, ideally the first thing the application does after loading its\n    configuration, and *before* any calls to `.IOLoop.start` or `asyncio.run`.\n\n    .. versionchanged:: 4.2\n       Added the ``make_current`` keyword argument to the `IOLoop`\n       constructor.\n\n    .. versionchanged:: 5.0\n\n       Uses the `asyncio` event loop by default. The ``IOLoop.configure`` method\n       cannot be used on Python 3 except to redundantly specify the `asyncio`\n       event loop.\n\n    .. versionchanged:: 6.3\n       ``make_current=True`` is now the default when creating an IOLoop -\n       previously the default was to make the event loop current if there wasn't\n       already a current one.\n    \"\"\"\n\n    # These constants were originally based on constants from the epoll module.\n    NONE = 0\n    READ = 0x001\n    WRITE = 0x004\n    ERROR = 0x018\n\n    # In Python 3, _ioloop_for_asyncio maps from asyncio loops to IOLoops.\n    _ioloop_for_asyncio: dict[asyncio.AbstractEventLoop, IOLoop] = dict()\n\n    # Maintain a set of all pending tasks to follow the warning in the docs\n    # of asyncio.create_tasks:\n    # https://docs.python.org/3.11/library/asyncio-task.html#asyncio.create_task\n    # This ensures that all pending tasks have a strong reference so they\n    # will not be garbage collected before they are finished.\n    # (Thus avoiding \"task was destroyed but it is pending\" warnings)\n    # An analogous change has been proposed in cpython for 3.13:\n    # https://github.com/python/cpython/issues/91887\n    # If that change is accepted, this can eventually be removed.\n    # If it is not, we will consider the rationale and may remove this.\n    _pending_tasks: set[Future] = set()\n\n    @classmethod\n    def configure(cls, impl: None | str | type[Configurable], **kwargs: Any) -> None:\n        from tornado.platform.asyncio import BaseAsyncIOLoop\n\n        if isinstance(impl, str):\n            impl = import_object(impl)\n        if isinstance(impl, type) and not issubclass(impl, BaseAsyncIOLoop):\n            raise RuntimeError(\"only AsyncIOLoop is allowed when asyncio is available\")\n        super().configure(impl, **kwargs)\n\n    @staticmethod\n    def instance() -> IOLoop:\n        \"\"\"Deprecated alias for `IOLoop.current()`.\n\n        .. versionchanged:: 5.0\n\n           Previously, this method returned a global singleton\n           `IOLoop`, in contrast with the per-thread `IOLoop` returned\n           by `current()`. In nearly all cases the two were the same\n           (when they differed, it was generally used from non-Tornado\n           threads to communicate back to the main thread's `IOLoop`).\n           This distinction is not present in `asyncio`, so in order\n           to facilitate integration with that package `instance()`\n           was changed to be an alias to `current()`. Applications\n           using the cross-thread communications aspect of\n           `instance()` should instead set their own global variable\n           to point to the `IOLoop` they want to use.\n\n        .. deprecated:: 5.0\n        \"\"\"\n        return IOLoop.current()\n\n    def install(self) -> None:\n        \"\"\"Deprecated alias for `make_current()`.\n\n        .. versionchanged:: 5.0\n\n           Previously, this method would set this `IOLoop` as the\n           global singleton used by `IOLoop.instance()`. Now that\n           `instance()` is an alias for `current()`, `install()`\n           is an alias for `make_current()`.\n\n        .. deprecated:: 5.0\n        \"\"\"\n        self.make_current()\n\n    @staticmethod\n    def clear_instance() -> None:\n        \"\"\"Deprecated alias for `clear_current()`.\n\n        .. versionchanged:: 5.0\n\n           Previously, this method would clear the `IOLoop` used as\n           the global singleton by `IOLoop.instance()`. Now that\n           `instance()` is an alias for `current()`,\n           `clear_instance()` is an alias for `clear_current()`.\n\n        .. deprecated:: 5.0\n\n        \"\"\"\n        IOLoop.clear_current()\n\n    @typing.overload\n    @staticmethod\n    def current() -> IOLoop:\n        pass\n\n    @typing.overload\n    @staticmethod\n    def current(instance: bool = True) -> IOLoop | None:\n        pass\n\n    @staticmethod\n    def current(instance: bool = True) -> IOLoop | None:\n        \"\"\"Returns the current thread's `IOLoop`.\n\n        If an `IOLoop` is currently running or has been marked as\n        current by `make_current`, returns that instance.  If there is\n        no current `IOLoop` and ``instance`` is true, creates one.\n\n        .. versionchanged:: 4.1\n           Added ``instance`` argument to control the fallback to\n           `IOLoop.instance()`.\n        .. versionchanged:: 5.0\n           On Python 3, control of the current `IOLoop` is delegated\n           to `asyncio`, with this and other methods as pass-through accessors.\n           The ``instance`` argument now controls whether an `IOLoop`\n           is created automatically when there is none, instead of\n           whether we fall back to `IOLoop.instance()` (which is now\n           an alias for this method). ``instance=False`` is deprecated,\n           since even if we do not create an `IOLoop`, this method\n           may initialize the asyncio loop.\n\n        .. deprecated:: 6.2\n           It is deprecated to call ``IOLoop.current()`` when no `asyncio`\n           event loop is running.\n        \"\"\"\n        try:\n            loop = asyncio.get_event_loop()\n        except RuntimeError:\n            if not instance:\n                return None\n            # Create a new asyncio event loop for this thread.\n            loop = asyncio.new_event_loop()\n            asyncio.set_event_loop(loop)\n\n        try:\n            return IOLoop._ioloop_for_asyncio[loop]\n        except KeyError:\n            if instance:\n                from tornado.platform.asyncio import AsyncIOMainLoop\n\n                current: IOLoop | None = AsyncIOMainLoop()\n            else:\n                current = None\n        return current\n\n    def make_current(self) -> None:\n        \"\"\"Makes this the `IOLoop` for the current thread.\n\n        An `IOLoop` automatically becomes current for its thread\n        when it is started, but it is sometimes useful to call\n        `make_current` explicitly before starting the `IOLoop`,\n        so that code run at startup time can find the right\n        instance.\n\n        .. versionchanged:: 4.1\n           An `IOLoop` created while there is no current `IOLoop`\n           will automatically become current.\n\n        .. versionchanged:: 5.0\n           This method also sets the current `asyncio` event loop.\n\n        .. deprecated:: 6.2\n           Setting and clearing the current event loop through Tornado is\n           deprecated. Use ``asyncio.set_event_loop`` instead if you need this.\n        \"\"\"\n        warnings.warn(\n            \"make_current is deprecated; start the event loop first\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n        self._make_current()\n\n    def _make_current(self) -> None:\n        # The asyncio event loops override this method.\n        raise NotImplementedError()\n\n    @staticmethod\n    def clear_current() -> None:\n        \"\"\"Clears the `IOLoop` for the current thread.\n\n        Intended primarily for use by test frameworks in between tests.\n\n        .. versionchanged:: 5.0\n           This method also clears the current `asyncio` event loop.\n        .. deprecated:: 6.2\n        \"\"\"\n        warnings.warn(\n            \"clear_current is deprecated\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n        IOLoop._clear_current()\n\n    @staticmethod\n    def _clear_current() -> None:\n        old = IOLoop.current(instance=False)\n        if old is not None:\n            old._clear_current_hook()\n\n    def _clear_current_hook(self) -> None:\n        \"\"\"Instance method called when an IOLoop ceases to be current.\n\n        May be overridden by subclasses as a counterpart to make_current.\n        \"\"\"\n        pass\n\n    @classmethod\n    def configurable_base(cls) -> type[Configurable]:\n        return IOLoop\n\n    @classmethod\n    def configurable_default(cls) -> type[Configurable]:\n        from tornado.platform.asyncio import AsyncIOLoop\n\n        return AsyncIOLoop\n\n    def initialize(self, make_current: bool = True) -> None:\n        if make_current:\n            self._make_current()\n\n    def close(self, all_fds: bool = False) -> None:\n        \"\"\"Closes the `IOLoop`, freeing any resources used.\n\n        If ``all_fds`` is true, all file descriptors registered on the\n        IOLoop will be closed (not just the ones created by the\n        `IOLoop` itself).\n\n        Many applications will only use a single `IOLoop` that runs for the\n        entire lifetime of the process.  In that case closing the `IOLoop`\n        is not necessary since everything will be cleaned up when the\n        process exits.  `IOLoop.close` is provided mainly for scenarios\n        such as unit tests, which create and destroy a large number of\n        ``IOLoops``.\n\n        An `IOLoop` must be completely stopped before it can be closed.  This\n        means that `IOLoop.stop()` must be called *and* `IOLoop.start()` must\n        be allowed to return before attempting to call `IOLoop.close()`.\n        Therefore the call to `close` will usually appear just after\n        the call to `start` rather than near the call to `stop`.\n\n        .. versionchanged:: 3.1\n           If the `IOLoop` implementation supports non-integer objects\n           for \"file descriptors\", those objects will have their\n           ``close`` method when ``all_fds`` is true.\n        \"\"\"\n        raise NotImplementedError()\n\n    @typing.overload\n    def add_handler(\n        self, fd: int, handler: Callable[[int, int], None], events: int\n    ) -> None:\n        pass\n\n    @typing.overload\n    def add_handler(\n        self, fd: _S, handler: Callable[[_S, int], None], events: int\n    ) -> None:\n        pass\n\n    def add_handler(\n        self, fd: int | _Selectable, handler: Callable[..., None], events: int\n    ) -> None:\n        \"\"\"Registers the given handler to receive the given events for ``fd``.\n\n        The ``fd`` argument may either be an integer file descriptor or\n        a file-like object with a ``fileno()`` and ``close()`` method.\n\n        The ``events`` argument is a bitwise or of the constants\n        ``IOLoop.READ``, ``IOLoop.WRITE``, and ``IOLoop.ERROR``.\n\n        When an event occurs, ``handler(fd, events)`` will be run.\n\n        .. versionchanged:: 4.0\n           Added the ability to pass file-like objects in addition to\n           raw file descriptors.\n        \"\"\"\n        raise NotImplementedError()\n\n    def update_handler(self, fd: int | _Selectable, events: int) -> None:\n        \"\"\"Changes the events we listen for ``fd``.\n\n        .. versionchanged:: 4.0\n           Added the ability to pass file-like objects in addition to\n           raw file descriptors.\n        \"\"\"\n        raise NotImplementedError()\n\n    def remove_handler(self, fd: int | _Selectable) -> None:\n        \"\"\"Stop listening for events on ``fd``.\n\n        .. versionchanged:: 4.0\n           Added the ability to pass file-like objects in addition to\n           raw file descriptors.\n        \"\"\"\n        raise NotImplementedError()\n\n    def start(self) -> None:\n        \"\"\"Starts the I/O loop.\n\n        The loop will run until one of the callbacks calls `stop()`, which\n        will make the loop stop after the current event iteration completes.\n        \"\"\"\n        raise NotImplementedError()\n\n    def stop(self) -> None:\n        \"\"\"Stop the I/O loop.\n\n        If the event loop is not currently running, the next call to `start()`\n        will return immediately.\n\n        Note that even after `stop` has been called, the `IOLoop` is not\n        completely stopped until `IOLoop.start` has also returned.\n        Some work that was scheduled before the call to `stop` may still\n        be run before the `IOLoop` shuts down.\n        \"\"\"\n        raise NotImplementedError()\n\n    def run_sync(self, func: Callable, timeout: float | None = None) -> Any:\n        \"\"\"Starts the `IOLoop`, runs the given function, and stops the loop.\n\n        The function must return either an awaitable object or\n        ``None``. If the function returns an awaitable object, the\n        `IOLoop` will run until the awaitable is resolved (and\n        `run_sync()` will return the awaitable's result). If it raises\n        an exception, the `IOLoop` will stop and the exception will be\n        re-raised to the caller.\n\n        The keyword-only argument ``timeout`` may be used to set\n        a maximum duration for the function.  If the timeout expires,\n        a `asyncio.TimeoutError` is raised.\n\n        This method is useful to allow asynchronous calls in a\n        ``main()`` function::\n\n            async def main():\n                # do stuff...\n\n            if __name__ == '__main__':\n                IOLoop.current().run_sync(main)\n\n        .. versionchanged:: 4.3\n           Returning a non-``None``, non-awaitable value is now an error.\n\n        .. versionchanged:: 5.0\n           If a timeout occurs, the ``func`` coroutine will be cancelled.\n\n        .. versionchanged:: 6.2\n           ``tornado.util.TimeoutError`` is now an alias to ``asyncio.TimeoutError``.\n        \"\"\"\n\n        class FutureCell(TypedDict):\n            future: Future | None\n            timeout_called: bool\n\n        future_cell: FutureCell = {\"future\": None, \"timeout_called\": False}\n\n        def run() -> None:\n            try:\n                result = func()\n                if result is not None:\n                    from tornado.gen import convert_yielded\n\n                    result = convert_yielded(result)\n            except Exception:\n                fut: Future[Any] = Future()\n                future_cell[\"future\"] = fut\n                future_set_exc_info(fut, sys.exc_info())\n            else:\n                if is_future(result):\n                    future_cell[\"future\"] = result\n                else:\n                    fut = Future()\n                    future_cell[\"future\"] = fut\n                    fut.set_result(result)\n            assert future_cell[\"future\"] is not None\n            self.add_future(future_cell[\"future\"], lambda future: self.stop())\n\n        self.add_callback(run)\n        if timeout is not None:\n\n            def timeout_callback() -> None:\n                # signal that timeout is triggered\n                future_cell[\"timeout_called\"] = True\n                # If we can cancel the future, do so and wait on it. If not,\n                # Just stop the loop and return with the task still pending.\n                # (If we neither cancel nor wait for the task, a warning\n                # will be logged).\n                assert future_cell[\"future\"] is not None\n                if not future_cell[\"future\"].cancel():\n                    self.stop()\n\n            timeout_handle = self.add_timeout(self.time() + timeout, timeout_callback)\n        self.start()\n        if timeout is not None:\n            self.remove_timeout(timeout_handle)\n        assert future_cell[\"future\"] is not None\n        if future_cell[\"future\"].cancelled() or not future_cell[\"future\"].done():\n            if future_cell[\"timeout_called\"]:\n                raise TimeoutError(\"Operation timed out after %s seconds\" % timeout)\n            else:\n                # timeout not called; maybe stop() was called explicitly\n                # or some other cancellation\n                raise RuntimeError(\"Event loop stopped before Future completed.\")\n        return future_cell[\"future\"].result()\n\n    def time(self) -> float:\n        \"\"\"Returns the current time according to the `IOLoop`'s clock.\n\n        The return value is a floating-point number relative to an\n        unspecified time in the past.\n\n        Historically, the IOLoop could be customized to use e.g.\n        `time.monotonic` instead of `time.time`, but this is not\n        currently supported and so this method is equivalent to\n        `time.time`.\n\n        \"\"\"\n        return time.time()\n\n    def add_timeout(\n        self,\n        deadline: float | datetime.timedelta,\n        callback: Callable,\n        *args: Any,\n        **kwargs: Any,\n    ) -> object:\n        \"\"\"Runs the ``callback`` at the time ``deadline`` from the I/O loop.\n\n        Returns an opaque handle that may be passed to\n        `remove_timeout` to cancel.\n\n        ``deadline`` may be a number denoting a time (on the same\n        scale as `IOLoop.time`, normally `time.time`), or a\n        `datetime.timedelta` object for a deadline relative to the\n        current time.  Since Tornado 4.0, `call_later` is a more\n        convenient alternative for the relative case since it does not\n        require a timedelta object.\n\n        Note that it is not safe to call `add_timeout` from other threads.\n        Instead, you must use `add_callback` to transfer control to the\n        `IOLoop`'s thread, and then call `add_timeout` from there.\n\n        Subclasses of IOLoop must implement either `add_timeout` or\n        `call_at`; the default implementations of each will call\n        the other.  `call_at` is usually easier to implement, but\n        subclasses that wish to maintain compatibility with Tornado\n        versions prior to 4.0 must use `add_timeout` instead.\n\n        .. versionchanged:: 4.0\n           Now passes through ``*args`` and ``**kwargs`` to the callback.\n        \"\"\"\n        if isinstance(deadline, numbers.Real):\n            return self.call_at(deadline, callback, *args, **kwargs)\n        elif isinstance(deadline, datetime.timedelta):\n            return self.call_at(\n                self.time() + deadline.total_seconds(), callback, *args, **kwargs\n            )\n        else:\n            raise TypeError(\"Unsupported deadline %r\" % deadline)\n\n    def call_later(\n        self, delay: float, callback: Callable, *args: Any, **kwargs: Any\n    ) -> object:\n        \"\"\"Runs the ``callback`` after ``delay`` seconds have passed.\n\n        Returns an opaque handle that may be passed to `remove_timeout`\n        to cancel.  Note that unlike the `asyncio` method of the same\n        name, the returned object does not have a ``cancel()`` method.\n\n        See `add_timeout` for comments on thread-safety and subclassing.\n\n        .. versionadded:: 4.0\n        \"\"\"\n        return self.call_at(self.time() + delay, callback, *args, **kwargs)\n\n    def call_at(\n        self, when: float, callback: Callable, *args: Any, **kwargs: Any\n    ) -> object:\n        \"\"\"Runs the ``callback`` at the absolute time designated by ``when``.\n\n        ``when`` must be a number using the same reference point as\n        `IOLoop.time`.\n\n        Returns an opaque handle that may be passed to `remove_timeout`\n        to cancel.  Note that unlike the `asyncio` method of the same\n        name, the returned object does not have a ``cancel()`` method.\n\n        See `add_timeout` for comments on thread-safety and subclassing.\n\n        .. versionadded:: 4.0\n        \"\"\"\n        return self.add_timeout(when, callback, *args, **kwargs)\n\n    def remove_timeout(self, timeout: object) -> None:\n        \"\"\"Cancels a pending timeout.\n\n        The argument is a handle as returned by `add_timeout`.  It is\n        safe to call `remove_timeout` even if the callback has already\n        been run.\n        \"\"\"\n        raise NotImplementedError()\n\n    def add_callback(self, callback: Callable, *args: Any, **kwargs: Any) -> None:\n        \"\"\"Calls the given callback on the next I/O loop iteration.\n\n        It is safe to call this method from any thread at any time,\n        except from a signal handler.  Note that this is the **only**\n        method in `IOLoop` that makes this thread-safety guarantee; all\n        other interaction with the `IOLoop` must be done from that\n        `IOLoop`'s thread.  `add_callback()` may be used to transfer\n        control from other threads to the `IOLoop`'s thread.\n        \"\"\"\n        raise NotImplementedError()\n\n    def add_callback_from_signal(\n        self, callback: Callable, *args: Any, **kwargs: Any\n    ) -> None:\n        \"\"\"Calls the given callback on the next I/O loop iteration.\n\n        Intended to be afe for use from a Python signal handler; should not be\n        used otherwise.\n\n        .. deprecated:: 6.4\n           Use ``asyncio.AbstractEventLoop.add_signal_handler`` instead.\n           This method is suspected to have been broken since Tornado 5.0 and\n           will be removed in version 7.0.\n        \"\"\"\n        raise NotImplementedError()\n\n    def spawn_callback(self, callback: Callable, *args: Any, **kwargs: Any) -> None:\n        \"\"\"Calls the given callback on the next IOLoop iteration.\n\n        As of Tornado 6.0, this method is equivalent to `add_callback`.\n\n        .. versionadded:: 4.0\n        \"\"\"\n        self.add_callback(callback, *args, **kwargs)\n\n    def add_future(\n        self,\n        future: Future[_T] | concurrent.futures.Future[_T],\n        callback: Callable[[Future[_T]], None],\n    ) -> None:\n        \"\"\"Schedules a callback on the ``IOLoop`` when the given\n        `.Future` is finished.\n\n        The callback is invoked with one argument, the\n        `.Future`.\n\n        This method only accepts `.Future` objects and not other\n        awaitables (unlike most of Tornado where the two are\n        interchangeable).\n        \"\"\"\n        if isinstance(future, Future):\n            # Note that we specifically do not want the inline behavior of\n            # tornado.concurrent.future_add_done_callback. We always want\n            # this callback scheduled on the next IOLoop iteration (which\n            # asyncio.Future always does).\n            #\n            # Wrap the callback in self._run_callback so we control\n            # the error logging (i.e. it goes to tornado.log.app_log\n            # instead of asyncio's log).\n            future.add_done_callback(\n                lambda f: self._run_callback(functools.partial(callback, f))\n            )\n        else:\n            assert is_future(future)\n            # For concurrent futures, we use self.add_callback, so\n            # it's fine if future_add_done_callback inlines that call.\n            future_add_done_callback(future, lambda f: self.add_callback(callback, f))\n\n    def run_in_executor(\n        self,\n        executor: concurrent.futures.Executor | None,\n        func: Callable[..., _T],\n        *args: Any,\n    ) -> Future[_T]:\n        \"\"\"Runs a function in a ``concurrent.futures.Executor``. If\n        ``executor`` is ``None``, the IO loop's default executor will be used.\n\n        Use `functools.partial` to pass keyword arguments to ``func``.\n\n        .. versionadded:: 5.0\n        \"\"\"\n        if executor is None:\n            if not hasattr(self, \"_executor\"):\n                from tornado.process import cpu_count\n\n                self._executor: concurrent.futures.Executor = (\n                    concurrent.futures.ThreadPoolExecutor(max_workers=(cpu_count() * 5))\n                )\n            executor = self._executor\n        c_future = executor.submit(func, *args)\n        # Concurrent Futures are not usable with await. Wrap this in a\n        # Tornado Future instead, using self.add_future for thread-safety.\n        t_future: Future[_T] = Future()\n        self.add_future(c_future, lambda f: chain_future(f, t_future))\n        return t_future\n\n    def set_default_executor(self, executor: concurrent.futures.Executor) -> None:\n        \"\"\"Sets the default executor to use with :meth:`run_in_executor`.\n\n        .. versionadded:: 5.0\n        \"\"\"\n        self._executor = executor\n\n    def _run_callback(self, callback: Callable[[], Any]) -> None:\n        \"\"\"Runs a callback with error handling.\n\n        .. versionchanged:: 6.0\n\n           CancelledErrors are no longer logged.\n        \"\"\"\n        try:\n            ret = callback()\n            if ret is not None:\n                from tornado import gen\n\n                # Functions that return Futures typically swallow all\n                # exceptions and store them in the Future.  If a Future\n                # makes it out to the IOLoop, ensure its exception (if any)\n                # gets logged too.\n                try:\n                    ret = gen.convert_yielded(ret)\n                except gen.BadYieldError:\n                    # It's not unusual for add_callback to be used with\n                    # methods returning a non-None and non-yieldable\n                    # result, which should just be ignored.\n                    pass\n                else:\n                    self.add_future(ret, self._discard_future_result)\n        except asyncio.CancelledError:\n            pass\n        except Exception:\n            app_log.error(\"Exception in callback %r\", callback, exc_info=True)\n\n    def _discard_future_result(self, future: Future) -> None:\n        \"\"\"Avoid unhandled-exception warnings from spawned coroutines.\"\"\"\n        future.result()\n\n    def split_fd(self, fd: int | _Selectable) -> tuple[int, int | _Selectable]:\n        # \"\"\"Returns an (fd, obj) pair from an ``fd`` parameter.\n\n        # We accept both raw file descriptors and file-like objects as\n        # input to `add_handler` and related methods.  When a file-like\n        # object is passed, we must retain the object itself so we can\n        # close it correctly when the `IOLoop` shuts down, but the\n        # poller interfaces favor file descriptors (they will accept\n        # file-like objects and call ``fileno()`` for you, but they\n        # always return the descriptor itself).\n\n        # This method is provided for use by `IOLoop` subclasses and should\n        # not generally be used by application code.\n\n        # .. versionadded:: 4.0\n        # \"\"\"\n        if isinstance(fd, int):\n            return fd, fd\n        return fd.fileno(), fd\n\n    def close_fd(self, fd: int | _Selectable) -> None:\n        # \"\"\"Utility method to close an ``fd``.\n\n        # If ``fd`` is a file-like object, we close it directly; otherwise\n        # we use `os.close`.\n\n        # This method is provided for use by `IOLoop` subclasses (in\n        # implementations of ``IOLoop.close(all_fds=True)`` and should\n        # not generally be used by application code.\n\n        # .. versionadded:: 4.0\n        # \"\"\"\n        try:\n            if isinstance(fd, int):\n                os.close(fd)\n            else:\n                fd.close()\n        except OSError:\n            pass\n\n    def _register_task(self, f: Future) -> None:\n        self._pending_tasks.add(f)\n\n    def _unregister_task(self, f: Future) -> None:\n        self._pending_tasks.discard(f)\n\n\nclass _Timeout:\n    \"\"\"An IOLoop timeout, a UNIX timestamp and a callback\"\"\"\n\n    # Reduce memory overhead when there are lots of pending callbacks\n    __slots__ = [\"deadline\", \"callback\", \"tdeadline\"]\n\n    def __init__(\n        self, deadline: float, callback: Callable[[], None], io_loop: IOLoop\n    ) -> None:\n        if not isinstance(deadline, numbers.Real):\n            raise TypeError(\"Unsupported deadline %r\" % deadline)\n        self.deadline = deadline\n        self.callback = callback\n        self.tdeadline: tuple[float, int] = (\n            deadline,\n            next(io_loop._timeout_counter),\n        )\n\n    # Comparison methods to sort by deadline, with object id as a tiebreaker\n    # to guarantee a consistent ordering.  The heapq module uses __le__\n    # in python2.5, and __lt__ in 2.6+ (sort() and most other comparisons\n    # use __lt__).\n    def __lt__(self, other: _Timeout) -> bool:\n        return self.tdeadline < other.tdeadline\n\n    def __le__(self, other: _Timeout) -> bool:\n        return self.tdeadline <= other.tdeadline\n\n\nclass PeriodicCallback:\n    \"\"\"Schedules the given callback to be called periodically.\n\n    The callback is called every ``callback_time`` milliseconds when\n    ``callback_time`` is a float. Note that the timeout is given in\n    milliseconds, while most other time-related functions in Tornado use\n    seconds. ``callback_time`` may alternatively be given as a\n    `datetime.timedelta` object.\n\n    If ``jitter`` is specified, each callback time will be randomly selected\n    within a window of ``jitter * callback_time`` milliseconds.\n    Jitter can be used to reduce alignment of events with similar periods.\n    A jitter of 0.1 means allowing a 10% variation in callback time.\n    The window is centered on ``callback_time`` so the total number of calls\n    within a given interval should not be significantly affected by adding\n    jitter.\n\n    If the callback runs for longer than ``callback_time`` milliseconds,\n    subsequent invocations will be skipped to get back on schedule.\n\n    `start` must be called after the `PeriodicCallback` is created.\n\n    .. versionchanged:: 5.0\n       The ``io_loop`` argument (deprecated since version 4.1) has been removed.\n\n    .. versionchanged:: 5.1\n       The ``jitter`` argument is added.\n\n    .. versionchanged:: 6.2\n       If the ``callback`` argument is a coroutine, and a callback runs for\n       longer than ``callback_time``, subsequent invocations will be skipped.\n       Previously this was only true for regular functions, not coroutines,\n       which were \"fire-and-forget\" for `PeriodicCallback`.\n\n       The ``callback_time`` argument now accepts `datetime.timedelta` objects,\n       in addition to the previous numeric milliseconds.\n    \"\"\"\n\n    def __init__(\n        self,\n        callback: Callable[[], Awaitable | None],\n        callback_time: datetime.timedelta | float,\n        jitter: float = 0,\n    ) -> None:\n        self.callback = callback\n        if isinstance(callback_time, datetime.timedelta):\n            self.callback_time = callback_time / datetime.timedelta(milliseconds=1)\n        else:\n            if callback_time <= 0:\n                raise ValueError(\"Periodic callback must have a positive callback_time\")\n            self.callback_time = callback_time\n        self.jitter = jitter\n        self._running = False\n        self._timeout: object = None\n\n    def start(self) -> None:\n        \"\"\"Starts the timer.\"\"\"\n        # Looking up the IOLoop here allows to first instantiate the\n        # PeriodicCallback in another thread, then start it using\n        # IOLoop.add_callback().\n        self.io_loop = IOLoop.current()\n        self._running = True\n        self._next_timeout = self.io_loop.time()\n        self._schedule_next()\n\n    def stop(self) -> None:\n        \"\"\"Stops the timer.\"\"\"\n        self._running = False\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n            self._timeout = None\n\n    def is_running(self) -> bool:\n        \"\"\"Returns ``True`` if this `.PeriodicCallback` has been started.\n\n        .. versionadded:: 4.1\n        \"\"\"\n        return self._running\n\n    async def _run(self) -> None:\n        if not self._running:\n            return\n        try:\n            val = self.callback()\n            if val is not None and isawaitable(val):\n                await val\n        except Exception:\n            app_log.error(\"Exception in callback %r\", self.callback, exc_info=True)\n        finally:\n            self._schedule_next()\n\n    def _schedule_next(self) -> None:\n        if self._running:\n            self._update_next(self.io_loop.time())\n            self._timeout = self.io_loop.add_timeout(self._next_timeout, self._run)\n\n    def _update_next(self, current_time: float) -> None:\n        callback_time_sec = self.callback_time / 1000.0\n        if self.jitter:\n            # apply jitter fraction\n            callback_time_sec *= 1 + (self.jitter * (random.random() - 0.5))\n        if self._next_timeout <= current_time:\n            # The period should be measured from the start of one call\n            # to the start of the next. If one call takes too long,\n            # skip cycles to get back to a multiple of the original\n            # schedule.\n            self._next_timeout += (\n                math.floor((current_time - self._next_timeout) / callback_time_sec) + 1\n            ) * callback_time_sec\n        else:\n            # If the clock moved backwards, ensure we advance the next\n            # timeout instead of recomputing the same value again.\n            # This may result in long gaps between callbacks if the\n            # clock jumps backwards by a lot, but the far more common\n            # scenario is a small NTP adjustment that should just be\n            # ignored.\n            #\n            # Note that on some systems if time.time() runs slower\n            # than time.monotonic() (most common on windows), we\n            # effectively experience a small backwards time jump on\n            # every iteration because PeriodicCallback uses\n            # time.time() while asyncio schedules callbacks using\n            # time.monotonic().\n            # https://github.com/tornadoweb/tornado/issues/2333\n            self._next_timeout += callback_time_sec\n"
  },
  {
    "path": "tornado/iostream.py",
    "content": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Utility classes to write to and read from non-blocking files and sockets.\n\nContents:\n\n* `BaseIOStream`: Generic interface for reading and writing.\n* `IOStream`: Implementation of BaseIOStream using non-blocking sockets.\n* `SSLIOStream`: SSL-aware version of IOStream.\n* `PipeIOStream`: Pipe-based IOStream implementation.\n\"\"\"\n\nimport asyncio\nimport collections\nimport errno\nimport io\nimport numbers\nimport os\nimport re\nimport socket\nimport ssl\nimport sys\nimport typing\nfrom collections.abc import Awaitable, Callable\nfrom re import Pattern\nfrom types import TracebackType\nfrom typing import (\n    Any,\n    TypeVar,\n)\n\nfrom tornado import ioloop\nfrom tornado.concurrent import Future, future_set_result_unless_cancelled\nfrom tornado.log import gen_log\nfrom tornado.netutil import _client_ssl_defaults, _server_ssl_defaults, ssl_wrap_socket\nfrom tornado.util import errno_from_exception\n\n_IOStreamType = TypeVar(\"_IOStreamType\", bound=\"IOStream\")\n\n# These errnos indicate that a connection has been abruptly terminated.\n# They should be caught and handled less noisily than other errors.\n_ERRNO_CONNRESET = (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE, errno.ETIMEDOUT)\n\nif hasattr(errno, \"WSAECONNRESET\"):\n    _ERRNO_CONNRESET += (  # type: ignore\n        errno.WSAECONNRESET,  # type: ignore\n        errno.WSAECONNABORTED,  # type: ignore\n        errno.WSAETIMEDOUT,  # type: ignore\n    )\n\nif sys.platform == \"darwin\":\n    # OSX appears to have a race condition that causes send(2) to return\n    # EPROTOTYPE if called while a socket is being torn down:\n    # http://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/\n    # Since the socket is being closed anyway, treat this as an ECONNRESET\n    # instead of an unexpected error.\n    _ERRNO_CONNRESET += (errno.EPROTOTYPE,)  # type: ignore\n\n_WINDOWS = sys.platform.startswith(\"win\")\n\n\nclass StreamClosedError(IOError):\n    \"\"\"Exception raised by `IOStream` methods when the stream is closed.\n\n    Note that the close callback is scheduled to run *after* other\n    callbacks on the stream (to allow for buffered data to be processed),\n    so you may see this error before you see the close callback.\n\n    The ``real_error`` attribute contains the underlying error that caused\n    the stream to close (if any).\n\n    .. versionchanged:: 4.3\n       Added the ``real_error`` attribute.\n    \"\"\"\n\n    def __init__(self, real_error: BaseException | None = None) -> None:\n        super().__init__(\"Stream is closed\")\n        self.real_error = real_error\n\n\nclass UnsatisfiableReadError(Exception):\n    \"\"\"Exception raised when a read cannot be satisfied.\n\n    Raised by ``read_until`` and ``read_until_regex`` with a ``max_bytes``\n    argument.\n    \"\"\"\n\n    pass\n\n\nclass StreamBufferFullError(Exception):\n    \"\"\"Exception raised by `IOStream` methods when the buffer is full.\"\"\"\n\n\nclass _StreamBuffer:\n    \"\"\"\n    A specialized buffer that tries to avoid copies when large pieces\n    of data are encountered.\n    \"\"\"\n\n    def __init__(self) -> None:\n        # A sequence of (False, bytearray) and (True, memoryview) objects\n        self._buffers: collections.deque[tuple[bool, bytearray | memoryview]] = (\n            collections.deque()\n        )\n        # Position in the first buffer\n        self._first_pos = 0\n        self._size = 0\n\n    def __len__(self) -> int:\n        return self._size\n\n    # Data above this size will be appended separately instead\n    # of extending an existing bytearray\n    _large_buf_threshold = 2048\n\n    def append(self, data: bytes | bytearray | memoryview) -> None:\n        \"\"\"\n        Append the given piece of data (should be a buffer-compatible object).\n        \"\"\"\n        size = len(data)\n        if size > self._large_buf_threshold:\n            if not isinstance(data, memoryview):\n                data = memoryview(data)\n            self._buffers.append((True, data))\n        elif size > 0:\n            if self._buffers:\n                is_memview, b = self._buffers[-1]\n                new_buf = is_memview or len(b) >= self._large_buf_threshold\n            else:\n                new_buf = True\n            if new_buf:\n                self._buffers.append((False, bytearray(data)))\n            else:\n                b += data  # type: ignore\n\n        self._size += size\n\n    def peek(self, size: int) -> memoryview:\n        \"\"\"\n        Get a view over at most ``size`` bytes (possibly fewer) at the\n        current buffer position.\n        \"\"\"\n        assert size > 0\n        try:\n            is_memview, b = self._buffers[0]\n        except IndexError:\n            return memoryview(b\"\")\n\n        pos = self._first_pos\n        if is_memview:\n            return typing.cast(memoryview, b[pos : pos + size])\n        else:\n            return memoryview(b)[pos : pos + size]\n\n    def advance(self, size: int) -> None:\n        \"\"\"\n        Advance the current buffer position by ``size`` bytes.\n        \"\"\"\n        assert 0 < size <= self._size\n        self._size -= size\n        pos = self._first_pos\n\n        buffers = self._buffers\n        while buffers and size > 0:\n            is_large, b = buffers[0]\n            b_remain = len(b) - size - pos\n            if b_remain <= 0:\n                buffers.popleft()\n                size -= len(b) - pos\n                pos = 0\n            elif is_large:\n                pos += size\n                size = 0\n            else:\n                pos += size\n                del typing.cast(bytearray, b)[:pos]\n                pos = 0\n                size = 0\n\n        assert size == 0\n        self._first_pos = pos\n\n\nclass BaseIOStream:\n    \"\"\"A utility class to write to and read from a non-blocking file or socket.\n\n    We support a non-blocking ``write()`` and a family of ``read_*()``\n    methods. When the operation completes, the ``Awaitable`` will resolve\n    with the data read (or ``None`` for ``write()``). All outstanding\n    ``Awaitables`` will resolve with a `StreamClosedError` when the\n    stream is closed; `.BaseIOStream.set_close_callback` can also be used\n    to be notified of a closed stream.\n\n    When a stream is closed due to an error, the IOStream's ``error``\n    attribute contains the exception object.\n\n    Subclasses must implement `fileno`, `close_fd`, `write_to_fd`,\n    `read_from_fd`, and optionally `get_fd_error`.\n\n    \"\"\"\n\n    def __init__(\n        self,\n        max_buffer_size: int | None = None,\n        read_chunk_size: int | None = None,\n        max_write_buffer_size: int | None = None,\n    ) -> None:\n        \"\"\"`BaseIOStream` constructor.\n\n        :arg max_buffer_size: Maximum amount of incoming data to buffer;\n            defaults to 100MB.\n        :arg read_chunk_size: Amount of data to read at one time from the\n            underlying transport; defaults to 64KB.\n        :arg max_write_buffer_size: Amount of outgoing data to buffer;\n            defaults to unlimited.\n\n        .. versionchanged:: 4.0\n           Add the ``max_write_buffer_size`` parameter.  Changed default\n           ``read_chunk_size`` to 64KB.\n        .. versionchanged:: 5.0\n           The ``io_loop`` argument (deprecated since version 4.1) has been\n           removed.\n        \"\"\"\n        self.io_loop = ioloop.IOLoop.current()\n        self.max_buffer_size = max_buffer_size or 104857600\n        # A chunk size that is too close to max_buffer_size can cause\n        # spurious failures.\n        self.read_chunk_size = min(read_chunk_size or 65536, self.max_buffer_size // 2)\n        self.max_write_buffer_size = max_write_buffer_size\n        self.error: BaseException | None = None\n        self._read_buffer = bytearray()\n        self._read_buffer_size = 0\n        self._user_read_buffer = False\n        self._after_user_read_buffer: bytearray | None = None\n        self._write_buffer = _StreamBuffer()\n        self._total_write_index = 0\n        self._total_write_done_index = 0\n        self._read_delimiter: bytes | None = None\n        self._read_regex: Pattern | None = None\n        self._read_max_bytes: int | None = None\n        self._read_bytes: int | None = None\n        self._read_partial = False\n        self._read_until_close = False\n        self._read_future: Future | None = None\n        self._write_futures: collections.deque[tuple[int, Future[None]]] = (\n            collections.deque()\n        )\n        self._close_callback: Callable[[], None] | None = None\n        self._connect_future: Future[IOStream] | None = None\n        # _ssl_connect_future should be defined in SSLIOStream\n        # but it's here so we can clean it up in _signal_closed\n        # TODO: refactor that so subclasses can add additional futures\n        # to be cancelled.\n        self._ssl_connect_future: Future[SSLIOStream] | None = None\n        self._connecting = False\n        self._state: int | None = None\n        self._closed = False\n\n    def fileno(self) -> int | ioloop._Selectable:\n        \"\"\"Returns the file descriptor for this stream.\"\"\"\n        raise NotImplementedError()\n\n    def close_fd(self) -> None:\n        \"\"\"Closes the file underlying this stream.\n\n        ``close_fd`` is called by `BaseIOStream` and should not be called\n        elsewhere; other users should call `close` instead.\n        \"\"\"\n        raise NotImplementedError()\n\n    def write_to_fd(self, data: memoryview) -> int:\n        \"\"\"Attempts to write ``data`` to the underlying file.\n\n        Returns the number of bytes written.\n        \"\"\"\n        raise NotImplementedError()\n\n    def read_from_fd(self, buf: bytearray | memoryview) -> int | None:\n        \"\"\"Attempts to read from the underlying file.\n\n        Reads up to ``len(buf)`` bytes, storing them in the buffer.\n        Returns the number of bytes read. Returns None if there was\n        nothing to read (the socket returned `~errno.EWOULDBLOCK` or\n        equivalent), and zero on EOF.\n\n        .. versionchanged:: 5.0\n\n           Interface redesigned to take a buffer and return a number\n           of bytes instead of a freshly-allocated object.\n        \"\"\"\n        raise NotImplementedError()\n\n    def get_fd_error(self) -> Exception | None:\n        \"\"\"Returns information about any error on the underlying file.\n\n        This method is called after the `.IOLoop` has signaled an error on the\n        file descriptor, and should return an Exception (such as `socket.error`\n        with additional information, or None if no such information is\n        available.\n        \"\"\"\n        return None\n\n    def read_until_regex(\n        self, regex: bytes, max_bytes: int | None = None\n    ) -> Awaitable[bytes]:\n        \"\"\"Asynchronously read until we have matched the given regex.\n\n        The result includes the data that matches the regex and anything\n        that came before it.\n\n        If ``max_bytes`` is not None, the connection will be closed\n        if more than ``max_bytes`` bytes have been read and the regex is\n        not satisfied.\n\n        .. versionchanged:: 4.0\n            Added the ``max_bytes`` argument.  The ``callback`` argument is\n            now optional and a `.Future` will be returned if it is omitted.\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed. Use the returned\n           `.Future` instead.\n\n        \"\"\"\n        future = self._start_read()\n        self._read_regex = re.compile(regex)\n        self._read_max_bytes = max_bytes\n        try:\n            self._try_inline_read()\n        except UnsatisfiableReadError as e:\n            # Handle this the same way as in _handle_events.\n            gen_log.info(\"Unsatisfiable read, closing connection: %s\" % e)\n            self.close(exc_info=e)\n            return future\n        except:\n            # Ensure that the future doesn't log an error because its\n            # failure was never examined.\n            future.add_done_callback(lambda f: f.exception())\n            raise\n        return future\n\n    def read_until(\n        self, delimiter: bytes, max_bytes: int | None = None\n    ) -> Awaitable[bytes]:\n        \"\"\"Asynchronously read until we have found the given delimiter.\n\n        The result includes all the data read including the delimiter.\n\n        If ``max_bytes`` is not None, the connection will be closed\n        if more than ``max_bytes`` bytes have been read and the delimiter\n        is not found.\n\n        .. versionchanged:: 4.0\n            Added the ``max_bytes`` argument.  The ``callback`` argument is\n            now optional and a `.Future` will be returned if it is omitted.\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed. Use the returned\n           `.Future` instead.\n        \"\"\"\n        future = self._start_read()\n        self._read_delimiter = delimiter\n        self._read_max_bytes = max_bytes\n        try:\n            self._try_inline_read()\n        except UnsatisfiableReadError as e:\n            # Handle this the same way as in _handle_events.\n            gen_log.info(\"Unsatisfiable read, closing connection: %s\" % e)\n            self.close(exc_info=e)\n            return future\n        except:\n            future.add_done_callback(lambda f: f.exception())\n            raise\n        return future\n\n    def read_bytes(self, num_bytes: int, partial: bool = False) -> Awaitable[bytes]:\n        \"\"\"Asynchronously read a number of bytes.\n\n        If ``partial`` is true, data is returned as soon as we have\n        any bytes to return (but never more than ``num_bytes``)\n\n        .. versionchanged:: 4.0\n            Added the ``partial`` argument.  The callback argument is now\n            optional and a `.Future` will be returned if it is omitted.\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` and ``streaming_callback`` arguments have\n           been removed. Use the returned `.Future` (and\n           ``partial=True`` for ``streaming_callback``) instead.\n\n        \"\"\"\n        future = self._start_read()\n        assert isinstance(num_bytes, numbers.Integral)\n        self._read_bytes = num_bytes\n        self._read_partial = partial\n        try:\n            self._try_inline_read()\n        except:\n            future.add_done_callback(lambda f: f.exception())\n            raise\n        return future\n\n    def read_into(self, buf: bytearray, partial: bool = False) -> Awaitable[int]:\n        \"\"\"Asynchronously read a number of bytes.\n\n        ``buf`` must be a writable buffer into which data will be read.\n\n        If ``partial`` is true, the callback is run as soon as any bytes\n        have been read.  Otherwise, it is run when the ``buf`` has been\n        entirely filled with read data.\n\n        .. versionadded:: 5.0\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed. Use the returned\n           `.Future` instead.\n\n        \"\"\"\n        future = self._start_read()\n\n        # First copy data already in read buffer\n        available_bytes = self._read_buffer_size\n        n = len(buf)\n        if available_bytes >= n:\n            buf[:] = memoryview(self._read_buffer)[:n]\n            del self._read_buffer[:n]\n            self._after_user_read_buffer = self._read_buffer\n        elif available_bytes > 0:\n            buf[:available_bytes] = memoryview(self._read_buffer)[:]\n\n        # Set up the supplied buffer as our temporary read buffer.\n        # The original (if it had any data remaining) has been\n        # saved for later.\n        self._user_read_buffer = True\n        self._read_buffer = buf\n        self._read_buffer_size = available_bytes\n        self._read_bytes = n\n        self._read_partial = partial\n\n        try:\n            self._try_inline_read()\n        except:\n            future.add_done_callback(lambda f: f.exception())\n            raise\n        return future\n\n    def read_until_close(self) -> Awaitable[bytes]:\n        \"\"\"Asynchronously reads all data from the socket until it is closed.\n\n        This will buffer all available data until ``max_buffer_size``\n        is reached. If flow control or cancellation are desired, use a\n        loop with `read_bytes(partial=True) <.read_bytes>` instead.\n\n        .. versionchanged:: 4.0\n            The callback argument is now optional and a `.Future` will\n            be returned if it is omitted.\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` and ``streaming_callback`` arguments have\n           been removed. Use the returned `.Future` (and `read_bytes`\n           with ``partial=True`` for ``streaming_callback``) instead.\n\n        \"\"\"\n        future = self._start_read()\n        if self.closed():\n            self._finish_read(self._read_buffer_size)\n            return future\n        self._read_until_close = True\n        try:\n            self._try_inline_read()\n        except:\n            future.add_done_callback(lambda f: f.exception())\n            raise\n        return future\n\n    def write(self, data: bytes | memoryview) -> \"Future[None]\":\n        \"\"\"Asynchronously write the given data to this stream.\n\n        This method returns a `.Future` that resolves (with a result\n        of ``None``) when the write has been completed.\n\n        The ``data`` argument may be of type `bytes` or `memoryview`.\n\n        .. versionchanged:: 4.0\n            Now returns a `.Future` if no callback is given.\n\n        .. versionchanged:: 4.5\n            Added support for `memoryview` arguments.\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed. Use the returned\n           `.Future` instead.\n\n        \"\"\"\n        self._check_closed()\n        if data:\n            if isinstance(data, memoryview):\n                # Make sure that ``len(data) == data.nbytes``\n                data = memoryview(data).cast(\"B\")\n            if (\n                self.max_write_buffer_size is not None\n                and len(self._write_buffer) + len(data) > self.max_write_buffer_size\n            ):\n                raise StreamBufferFullError(\"Reached maximum write buffer size\")\n            self._write_buffer.append(data)\n            self._total_write_index += len(data)\n        future: Future[None] = Future()\n        future.add_done_callback(lambda f: f.exception())\n        self._write_futures.append((self._total_write_index, future))\n        if not self._connecting:\n            self._handle_write()\n            if self._write_buffer:\n                self._add_io_state(self.io_loop.WRITE)\n            self._maybe_add_error_listener()\n        return future\n\n    def set_close_callback(self, callback: Callable[[], None] | None) -> None:\n        \"\"\"Call the given callback when the stream is closed.\n\n        This mostly is not necessary for applications that use the\n        `.Future` interface; all outstanding ``Futures`` will resolve\n        with a `StreamClosedError` when the stream is closed. However,\n        it is still useful as a way to signal that the stream has been\n        closed while no other read or write is in progress.\n\n        Unlike other callback-based interfaces, ``set_close_callback``\n        was not removed in Tornado 6.0.\n        \"\"\"\n        self._close_callback = callback\n        self._maybe_add_error_listener()\n\n    def close(\n        self,\n        exc_info: (\n            None\n            | bool\n            | BaseException\n            | tuple[\n                type[BaseException] | None,\n                BaseException | None,\n                TracebackType | None,\n            ]\n        ) = False,\n    ) -> None:\n        \"\"\"Close this stream.\n\n        If ``exc_info`` is true, set the ``error`` attribute to the current\n        exception from `sys.exc_info` (or if ``exc_info`` is a tuple,\n        use that instead of `sys.exc_info`).\n        \"\"\"\n        if not self.closed():\n            if exc_info:\n                if isinstance(exc_info, tuple):\n                    self.error = exc_info[1]\n                elif isinstance(exc_info, BaseException):\n                    self.error = exc_info\n                else:\n                    exc_info = sys.exc_info()\n                    if any(exc_info):\n                        self.error = exc_info[1]\n            if self._read_until_close:\n                self._read_until_close = False\n                self._finish_read(self._read_buffer_size)\n            elif self._read_future is not None:\n                # resolve reads that are pending and ready to complete\n                try:\n                    pos = self._find_read_pos()\n                except UnsatisfiableReadError:\n                    pass\n                else:\n                    if pos is not None:\n                        self._read_from_buffer(pos)\n            if self._state is not None:\n                self.io_loop.remove_handler(self.fileno())\n                self._state = None\n            self.close_fd()\n            self._closed = True\n        self._signal_closed()\n\n    def _signal_closed(self) -> None:\n        futures: list[Future] = []\n        if self._read_future is not None:\n            futures.append(self._read_future)\n            self._read_future = None\n        futures += [future for _, future in self._write_futures]\n        self._write_futures.clear()\n        if self._connect_future is not None:\n            futures.append(self._connect_future)\n            self._connect_future = None\n        for future in futures:\n            if not future.done():\n                future.set_exception(StreamClosedError(real_error=self.error))\n            # Reference the exception to silence warnings. Annoyingly,\n            # this raises if the future was cancelled, but just\n            # returns any other error.\n            try:\n                future.exception()\n            except asyncio.CancelledError:\n                pass\n        if self._ssl_connect_future is not None:\n            # _ssl_connect_future expects to see the real exception (typically\n            # an ssl.SSLError), not just StreamClosedError.\n            if not self._ssl_connect_future.done():\n                if self.error is not None:\n                    self._ssl_connect_future.set_exception(self.error)\n                else:\n                    self._ssl_connect_future.set_exception(StreamClosedError())\n            self._ssl_connect_future.exception()\n            self._ssl_connect_future = None\n        if self._close_callback is not None:\n            cb = self._close_callback\n            self._close_callback = None\n            self.io_loop.add_callback(cb)\n        # Clear the buffers so they can be cleared immediately even\n        # if the IOStream object is kept alive by a reference cycle.\n        # TODO: Clear the read buffer too; it currently breaks some tests.\n        self._write_buffer = None  # type: ignore\n\n    def reading(self) -> bool:\n        \"\"\"Returns ``True`` if we are currently reading from the stream.\"\"\"\n        return self._read_future is not None\n\n    def writing(self) -> bool:\n        \"\"\"Returns ``True`` if we are currently writing to the stream.\"\"\"\n        return bool(self._write_buffer)\n\n    def closed(self) -> bool:\n        \"\"\"Returns ``True`` if the stream has been closed.\"\"\"\n        return self._closed\n\n    def set_nodelay(self, value: bool) -> None:\n        \"\"\"Sets the no-delay flag for this stream.\n\n        By default, data written to TCP streams may be held for a time\n        to make the most efficient use of bandwidth (according to\n        Nagle's algorithm).  The no-delay flag requests that data be\n        written as soon as possible, even if doing so would consume\n        additional bandwidth.\n\n        This flag is currently defined only for TCP-based ``IOStreams``.\n\n        .. versionadded:: 3.1\n        \"\"\"\n        pass\n\n    def _handle_connect(self) -> None:\n        raise NotImplementedError()\n\n    def _handle_events(self, fd: int | ioloop._Selectable, events: int) -> None:\n        if self.closed():\n            gen_log.warning(\"Got events for closed stream %s\", fd)\n            return\n        try:\n            if self._connecting:\n                # Most IOLoops will report a write failed connect\n                # with the WRITE event, but SelectIOLoop reports a\n                # READ as well so we must check for connecting before\n                # either.\n                self._handle_connect()\n            if self.closed():\n                return\n            if events & self.io_loop.READ:\n                self._handle_read()\n            if self.closed():\n                return\n            if events & self.io_loop.WRITE:\n                self._handle_write()\n            if self.closed():\n                return\n            if events & self.io_loop.ERROR:\n                self.error = self.get_fd_error()\n                # We may have queued up a user callback in _handle_read or\n                # _handle_write, so don't close the IOStream until those\n                # callbacks have had a chance to run.\n                self.io_loop.add_callback(self.close)\n                return\n            state = self.io_loop.ERROR\n            if self.reading():\n                state |= self.io_loop.READ\n            if self.writing():\n                state |= self.io_loop.WRITE\n            if state == self.io_loop.ERROR and self._read_buffer_size == 0:\n                # If the connection is idle, listen for reads too so\n                # we can tell if the connection is closed.  If there is\n                # data in the read buffer we won't run the close callback\n                # yet anyway, so we don't need to listen in this case.\n                state |= self.io_loop.READ\n            if state != self._state:\n                assert (\n                    self._state is not None\n                ), \"shouldn't happen: _handle_events without self._state\"\n                self._state = state\n                self.io_loop.update_handler(self.fileno(), self._state)\n        except UnsatisfiableReadError as e:\n            gen_log.info(\"Unsatisfiable read, closing connection: %s\" % e)\n            self.close(exc_info=e)\n        except Exception as e:\n            gen_log.error(\"Uncaught exception, closing connection.\", exc_info=True)\n            self.close(exc_info=e)\n            raise\n\n    def _read_to_buffer_loop(self) -> int | None:\n        # This method is called from _handle_read and _try_inline_read.\n        if self._read_bytes is not None:\n            target_bytes: int | None = self._read_bytes\n        elif self._read_max_bytes is not None:\n            target_bytes = self._read_max_bytes\n        elif self.reading():\n            # For read_until without max_bytes, or\n            # read_until_close, read as much as we can before\n            # scanning for the delimiter.\n            target_bytes = None\n        else:\n            target_bytes = 0\n        next_find_pos = 0\n        while not self.closed():\n            # Read from the socket until we get EWOULDBLOCK or equivalent.\n            # SSL sockets do some internal buffering, and if the data is\n            # sitting in the SSL object's buffer select() and friends\n            # can't see it; the only way to find out if it's there is to\n            # try to read it.\n            if self._read_to_buffer() == 0:\n                break\n\n            # If we've read all the bytes we can use, break out of\n            # this loop.\n\n            # If we've reached target_bytes, we know we're done.\n            if target_bytes is not None and self._read_buffer_size >= target_bytes:\n                break\n\n            # Otherwise, we need to call the more expensive find_read_pos.\n            # It's inefficient to do this on every read, so instead\n            # do it on the first read and whenever the read buffer\n            # size has doubled.\n            if self._read_buffer_size >= next_find_pos:\n                pos = self._find_read_pos()\n                if pos is not None:\n                    return pos\n                next_find_pos = self._read_buffer_size * 2\n        return self._find_read_pos()\n\n    def _handle_read(self) -> None:\n        try:\n            pos = self._read_to_buffer_loop()\n        except UnsatisfiableReadError:\n            raise\n        except asyncio.CancelledError:\n            raise\n        except Exception as e:\n            gen_log.warning(\"error on read: %s\" % e)\n            self.close(exc_info=e)\n            return\n        if pos is not None:\n            self._read_from_buffer(pos)\n\n    def _start_read(self) -> Future:\n        if self._read_future is not None:\n            # It is an error to start a read while a prior read is unresolved.\n            # However, if the prior read is unresolved because the stream was\n            # closed without satisfying it, it's better to raise\n            # StreamClosedError instead of AssertionError. In particular, this\n            # situation occurs in harmless situations in http1connection.py and\n            # an AssertionError would be logged noisily.\n            #\n            # On the other hand, it is legal to start a new read while the\n            # stream is closed, in case the read can be satisfied from the\n            # read buffer. So we only want to check the closed status of the\n            # stream if we need to decide what kind of error to raise for\n            # \"already reading\".\n            #\n            # These conditions have proven difficult to test; we have no\n            # unittests that reliably verify this behavior so be careful\n            # when making changes here. See #2651 and #2719.\n            self._check_closed()\n            assert self._read_future is None, \"Already reading\"\n        self._read_future = Future()\n        return self._read_future\n\n    def _finish_read(self, size: int) -> None:\n        if self._user_read_buffer:\n            self._read_buffer = self._after_user_read_buffer or bytearray()\n            self._after_user_read_buffer = None\n            self._read_buffer_size = len(self._read_buffer)\n            self._user_read_buffer = False\n            result: int | bytes = size\n        else:\n            result = self._consume(size)\n        if self._read_future is not None:\n            future = self._read_future\n            self._read_future = None\n            future_set_result_unless_cancelled(future, result)\n        self._maybe_add_error_listener()\n\n    def _try_inline_read(self) -> None:\n        \"\"\"Attempt to complete the current read operation from buffered data.\n\n        If the read can be completed without blocking, schedules the\n        read callback on the next IOLoop iteration; otherwise starts\n        listening for reads on the socket.\n        \"\"\"\n        # See if we've already got the data from a previous read\n        pos = self._find_read_pos()\n        if pos is not None:\n            self._read_from_buffer(pos)\n            return\n        self._check_closed()\n        pos = self._read_to_buffer_loop()\n        if pos is not None:\n            self._read_from_buffer(pos)\n            return\n        # We couldn't satisfy the read inline, so make sure we're\n        # listening for new data unless the stream is closed.\n        if not self.closed():\n            self._add_io_state(ioloop.IOLoop.READ)\n\n    def _read_to_buffer(self) -> int | None:\n        \"\"\"Reads from the socket and appends the result to the read buffer.\n\n        Returns the number of bytes read.  Returns 0 if there is nothing\n        to read (i.e. the read returns EWOULDBLOCK or equivalent).  On\n        error closes the socket and raises an exception.\n        \"\"\"\n        try:\n            while True:\n                try:\n                    if self._user_read_buffer:\n                        buf: memoryview | bytearray = memoryview(self._read_buffer)[\n                            self._read_buffer_size :\n                        ]\n                    else:\n                        buf = bytearray(self.read_chunk_size)\n                    bytes_read = self.read_from_fd(buf)\n                except OSError as e:\n                    # ssl.SSLError is a subclass of socket.error\n                    if self._is_connreset(e):\n                        # Treat ECONNRESET as a connection close rather than\n                        # an error to minimize log spam  (the exception will\n                        # be available on self.error for apps that care).\n                        self.close(exc_info=e)\n                        return None\n                    self.close(exc_info=e)\n                    raise\n                break\n            if bytes_read is None:\n                return 0\n            elif bytes_read == 0:\n                self.close()\n                return 0\n            if not self._user_read_buffer:\n                self._read_buffer += memoryview(buf)[:bytes_read]\n            self._read_buffer_size += bytes_read\n        finally:\n            # Break the reference to buf so we don't waste a chunk's worth of\n            # memory in case an exception hangs on to our stack frame.\n            del buf\n        if self._read_buffer_size > self.max_buffer_size:\n            gen_log.error(\"Reached maximum read buffer size\")\n            self.close()\n            raise StreamBufferFullError(\"Reached maximum read buffer size\")\n        return bytes_read\n\n    def _read_from_buffer(self, pos: int) -> None:\n        \"\"\"Attempts to complete the currently-pending read from the buffer.\n\n        The argument is either a position in the read buffer or None,\n        as returned by _find_read_pos.\n        \"\"\"\n        self._read_bytes = self._read_delimiter = self._read_regex = None\n        self._read_partial = False\n        self._finish_read(pos)\n\n    def _find_read_pos(self) -> int | None:\n        \"\"\"Attempts to find a position in the read buffer that satisfies\n        the currently-pending read.\n\n        Returns a position in the buffer if the current read can be satisfied,\n        or None if it cannot.\n        \"\"\"\n        if self._read_bytes is not None and (\n            self._read_buffer_size >= self._read_bytes\n            or (self._read_partial and self._read_buffer_size > 0)\n        ):\n            num_bytes = min(self._read_bytes, self._read_buffer_size)\n            return num_bytes\n        elif self._read_delimiter is not None:\n            # Multi-byte delimiters (e.g. '\\r\\n') may straddle two\n            # chunks in the read buffer, so we can't easily find them\n            # without collapsing the buffer.  However, since protocols\n            # using delimited reads (as opposed to reads of a known\n            # length) tend to be \"line\" oriented, the delimiter is likely\n            # to be in the first few chunks.  Merge the buffer gradually\n            # since large merges are relatively expensive and get undone in\n            # _consume().\n            if self._read_buffer:\n                loc = self._read_buffer.find(self._read_delimiter)\n                if loc != -1:\n                    delimiter_len = len(self._read_delimiter)\n                    self._check_max_bytes(self._read_delimiter, loc + delimiter_len)\n                    return loc + delimiter_len\n                self._check_max_bytes(self._read_delimiter, self._read_buffer_size)\n        elif self._read_regex is not None:\n            if self._read_buffer:\n                m = self._read_regex.search(self._read_buffer)\n                if m is not None:\n                    loc = m.end()\n                    self._check_max_bytes(self._read_regex, loc)\n                    return loc\n                self._check_max_bytes(self._read_regex, self._read_buffer_size)\n        return None\n\n    def _check_max_bytes(self, delimiter: bytes | Pattern, size: int) -> None:\n        if self._read_max_bytes is not None and size > self._read_max_bytes:\n            raise UnsatisfiableReadError(\n                \"delimiter %r not found within %d bytes\"\n                % (delimiter, self._read_max_bytes)\n            )\n\n    def _handle_write(self) -> None:\n        while True:\n            size = len(self._write_buffer)\n            if not size:\n                break\n            assert size > 0\n            try:\n                if _WINDOWS:\n                    # On windows, socket.send blows up if given a\n                    # write buffer that's too large, instead of just\n                    # returning the number of bytes it was able to\n                    # process.  Therefore we must not call socket.send\n                    # with more than 128KB at a time.\n                    size = 128 * 1024\n\n                num_bytes = self.write_to_fd(self._write_buffer.peek(size))\n                if num_bytes == 0:\n                    break\n                self._write_buffer.advance(num_bytes)\n                self._total_write_done_index += num_bytes\n            except BlockingIOError:\n                break\n            except OSError as e:\n                if not self._is_connreset(e):\n                    # Broken pipe errors are usually caused by connection\n                    # reset, and its better to not log EPIPE errors to\n                    # minimize log spam\n                    gen_log.warning(\"Write error on %s: %s\", self.fileno(), e)\n                self.close(exc_info=e)\n                return\n\n        while self._write_futures:\n            index, future = self._write_futures[0]\n            if index > self._total_write_done_index:\n                break\n            self._write_futures.popleft()\n            future_set_result_unless_cancelled(future, None)\n\n    def _consume(self, loc: int) -> bytes:\n        # Consume loc bytes from the read buffer and return them\n        if loc == 0:\n            return b\"\"\n        assert loc <= self._read_buffer_size\n        # Slice the bytearray buffer into bytes, without intermediate copying\n        b = (memoryview(self._read_buffer)[:loc]).tobytes()\n        self._read_buffer_size -= loc\n        del self._read_buffer[:loc]\n        return b\n\n    def _check_closed(self) -> None:\n        if self.closed():\n            raise StreamClosedError(real_error=self.error)\n\n    def _maybe_add_error_listener(self) -> None:\n        # This method is part of an optimization: to detect a connection that\n        # is closed when we're not actively reading or writing, we must listen\n        # for read events.  However, it is inefficient to do this when the\n        # connection is first established because we are going to read or write\n        # immediately anyway.  Instead, we insert checks at various times to\n        # see if the connection is idle and add the read listener then.\n        if self._state is None or self._state == ioloop.IOLoop.ERROR:\n            if (\n                not self.closed()\n                and self._read_buffer_size == 0\n                and self._close_callback is not None\n            ):\n                self._add_io_state(ioloop.IOLoop.READ)\n\n    def _add_io_state(self, state: int) -> None:\n        \"\"\"Adds `state` (IOLoop.{READ,WRITE} flags) to our event handler.\n\n        Implementation notes: Reads and writes have a fast path and a\n        slow path.  The fast path reads synchronously from socket\n        buffers, while the slow path uses `_add_io_state` to schedule\n        an IOLoop callback.\n\n        To detect closed connections, we must have called\n        `_add_io_state` at some point, but we want to delay this as\n        much as possible so we don't have to set an `IOLoop.ERROR`\n        listener that will be overwritten by the next slow-path\n        operation. If a sequence of fast-path ops do not end in a\n        slow-path op, (e.g. for an @asynchronous long-poll request),\n        we must add the error handler.\n\n        TODO: reevaluate this now that callbacks are gone.\n\n        \"\"\"\n        if self.closed():\n            # connection has been closed, so there can be no future events\n            return\n        if self._state is None:\n            self._state = ioloop.IOLoop.ERROR | state\n            self.io_loop.add_handler(self.fileno(), self._handle_events, self._state)\n        elif not self._state & state:\n            self._state = self._state | state\n            self.io_loop.update_handler(self.fileno(), self._state)\n\n    def _is_connreset(self, exc: BaseException) -> bool:\n        \"\"\"Return ``True`` if exc is ECONNRESET or equivalent.\n\n        May be overridden in subclasses.\n        \"\"\"\n        return (\n            isinstance(exc, (socket.error, IOError))\n            and errno_from_exception(exc) in _ERRNO_CONNRESET\n        )\n\n\nclass IOStream(BaseIOStream):\n    r\"\"\"Socket-based `IOStream` implementation.\n\n    This class supports the read and write methods from `BaseIOStream`\n    plus a `connect` method.\n\n    The ``socket`` parameter may either be connected or unconnected.\n    For server operations the socket is the result of calling\n    `socket.accept <socket.socket.accept>`.  For client operations the\n    socket is created with `socket.socket`, and may either be\n    connected before passing it to the `IOStream` or connected with\n    `IOStream.connect`.\n\n    A very simple (and broken) HTTP client using this class:\n\n    .. testcode::\n\n        import socket\n        import tornado\n\n        async def main():\n            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)\n            stream = tornado.iostream.IOStream(s)\n            await stream.connect((\"friendfeed.com\", 80))\n            await stream.write(b\"GET / HTTP/1.0\\r\\nHost: friendfeed.com\\r\\n\\r\\n\")\n            header_data = await stream.read_until(b\"\\r\\n\\r\\n\")\n            headers = {}\n            for line in header_data.split(b\"\\r\\n\"):\n                parts = line.split(b\":\")\n                if len(parts) == 2:\n                    headers[parts[0].strip()] = parts[1].strip()\n            body_data = await stream.read_bytes(int(headers[b\"Content-Length\"]))\n            print(body_data)\n            stream.close()\n\n        if __name__ == '__main__':\n            asyncio.run(main())\n\n    \"\"\"\n\n    def __init__(self, socket: socket.socket, *args: Any, **kwargs: Any) -> None:\n        self.socket = socket\n        self.socket.setblocking(False)\n        super().__init__(*args, **kwargs)\n\n    def fileno(self) -> int | ioloop._Selectable:\n        return self.socket\n\n    def close_fd(self) -> None:\n        self.socket.close()\n        self.socket = None  # type: ignore\n\n    def get_fd_error(self) -> Exception | None:\n        errno = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)\n        return socket.error(errno, os.strerror(errno))\n\n    def read_from_fd(self, buf: bytearray | memoryview) -> int | None:\n        try:\n            return self.socket.recv_into(buf, len(buf))\n        except BlockingIOError:\n            return None\n        finally:\n            del buf\n\n    def write_to_fd(self, data: memoryview) -> int:\n        try:\n            return self.socket.send(data)  # type: ignore\n        finally:\n            # Avoid keeping to data, which can be a memoryview.\n            # See https://github.com/tornadoweb/tornado/pull/2008\n            del data\n\n    def connect(\n        self: _IOStreamType, address: Any, server_hostname: str | None = None\n    ) -> \"Future[_IOStreamType]\":\n        \"\"\"Connects the socket to a remote address without blocking.\n\n        May only be called if the socket passed to the constructor was\n        not previously connected.  The address parameter is in the\n        same format as for `socket.connect <socket.socket.connect>` for\n        the type of socket passed to the IOStream constructor,\n        e.g. an ``(ip, port)`` tuple.  Hostnames are accepted here,\n        but will be resolved synchronously and block the IOLoop.\n        If you have a hostname instead of an IP address, the `.TCPClient`\n        class is recommended instead of calling this method directly.\n        `.TCPClient` will do asynchronous DNS resolution and handle\n        both IPv4 and IPv6.\n\n        If ``callback`` is specified, it will be called with no\n        arguments when the connection is completed; if not this method\n        returns a `.Future` (whose result after a successful\n        connection will be the stream itself).\n\n        In SSL mode, the ``server_hostname`` parameter will be used\n        for certificate validation (unless disabled in the\n        ``ssl_options``) and SNI.\n\n        Note that it is safe to call `IOStream.write\n        <BaseIOStream.write>` while the connection is pending, in\n        which case the data will be written as soon as the connection\n        is ready.  Calling `IOStream` read methods before the socket is\n        connected works on some platforms but is non-portable.\n\n        .. versionchanged:: 4.0\n            If no callback is given, returns a `.Future`.\n\n        .. versionchanged:: 4.2\n           SSL certificates are validated by default; pass\n           ``ssl_options=dict(cert_reqs=ssl.CERT_NONE)`` or a\n           suitably-configured `ssl.SSLContext` to the\n           `SSLIOStream` constructor to disable.\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed. Use the returned\n           `.Future` instead.\n\n        \"\"\"\n        self._connecting = True\n        future: Future[_IOStreamType] = Future()\n        self._connect_future = typing.cast(\"Future[IOStream]\", future)\n        try:\n            self.socket.connect(address)\n        except BlockingIOError:\n            # In non-blocking mode we expect connect() to raise an\n            # exception with EINPROGRESS or EWOULDBLOCK.\n            pass\n        except OSError as e:\n            # On freebsd, other errors such as ECONNREFUSED may be\n            # returned immediately when attempting to connect to\n            # localhost, so handle them the same way as an error\n            # reported later in _handle_connect.\n            if future is None:\n                gen_log.warning(\"Connect error on fd %s: %s\", self.socket.fileno(), e)\n            self.close(exc_info=e)\n            return future\n        self._add_io_state(self.io_loop.WRITE)\n        return future\n\n    def start_tls(\n        self,\n        server_side: bool,\n        ssl_options: dict[str, Any] | ssl.SSLContext | None = None,\n        server_hostname: str | None = None,\n    ) -> Awaitable[\"SSLIOStream\"]:\n        \"\"\"Convert this `IOStream` to an `SSLIOStream`.\n\n        This enables protocols that begin in clear-text mode and\n        switch to SSL after some initial negotiation (such as the\n        ``STARTTLS`` extension to SMTP and IMAP).\n\n        This method cannot be used if there are outstanding reads\n        or writes on the stream, or if there is any data in the\n        IOStream's buffer (data in the operating system's socket\n        buffer is allowed).  This means it must generally be used\n        immediately after reading or writing the last clear-text\n        data.  It can also be used immediately after connecting,\n        before any reads or writes.\n\n        The ``ssl_options`` argument may be either an `ssl.SSLContext`\n        object or a dictionary of keyword arguments for the\n        `ssl.SSLContext.wrap_socket` function.  The ``server_hostname`` argument\n        will be used for certificate validation unless disabled\n        in the ``ssl_options``.\n\n        This method returns a `.Future` whose result is the new\n        `SSLIOStream`.  After this method has been called,\n        any other operation on the original stream is undefined.\n\n        If a close callback is defined on this stream, it will be\n        transferred to the new stream.\n\n        .. versionadded:: 4.0\n\n        .. versionchanged:: 4.2\n           SSL certificates are validated by default; pass\n           ``ssl_options=dict(cert_reqs=ssl.CERT_NONE)`` or a\n           suitably-configured `ssl.SSLContext` to disable.\n        \"\"\"\n        if (\n            self._read_future\n            or self._write_futures\n            or self._connect_future\n            or self._closed\n            or self._read_buffer\n            or self._write_buffer\n        ):\n            raise ValueError(\"IOStream is not idle; cannot convert to SSL\")\n        if ssl_options is None:\n            if server_side:\n                ssl_options = _server_ssl_defaults\n            else:\n                ssl_options = _client_ssl_defaults\n\n        socket = self.socket\n        self.io_loop.remove_handler(socket)\n        self.socket = None  # type: ignore\n        socket = ssl_wrap_socket(\n            socket,\n            ssl_options,\n            server_hostname=server_hostname,\n            server_side=server_side,\n            do_handshake_on_connect=False,\n        )\n        orig_close_callback = self._close_callback\n        self._close_callback = None\n\n        future: Future[SSLIOStream] = Future()\n        ssl_stream = SSLIOStream(socket, ssl_options=ssl_options)\n        ssl_stream.set_close_callback(orig_close_callback)\n        ssl_stream._ssl_connect_future = future\n        ssl_stream.max_buffer_size = self.max_buffer_size\n        ssl_stream.read_chunk_size = self.read_chunk_size\n        return future\n\n    def _handle_connect(self) -> None:\n        try:\n            err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)\n        except OSError as e:\n            # Hurd doesn't allow SO_ERROR for loopback sockets because all\n            # errors for such sockets are reported synchronously.\n            if errno_from_exception(e) == errno.ENOPROTOOPT:\n                err = 0\n        if err != 0:\n            self.error = socket.error(err, os.strerror(err))\n            # IOLoop implementations may vary: some of them return\n            # an error state before the socket becomes writable, so\n            # in that case a connection failure would be handled by the\n            # error path in _handle_events instead of here.\n            if self._connect_future is None:\n                gen_log.warning(\n                    \"Connect error on fd %s: %s\",\n                    self.socket.fileno(),\n                    errno.errorcode[err],\n                )\n            self.close()\n            return\n        if self._connect_future is not None:\n            future = self._connect_future\n            self._connect_future = None\n            future_set_result_unless_cancelled(future, self)\n        self._connecting = False\n\n    def set_nodelay(self, value: bool) -> None:\n        if self.socket is not None and self.socket.family in (\n            socket.AF_INET,\n            socket.AF_INET6,\n        ):\n            try:\n                self.socket.setsockopt(\n                    socket.IPPROTO_TCP, socket.TCP_NODELAY, 1 if value else 0\n                )\n            except OSError as e:\n                # Sometimes setsockopt will fail if the socket is closed\n                # at the wrong time.  This can happen with HTTPServer\n                # resetting the value to ``False`` between requests.\n                if e.errno != errno.EINVAL and not self._is_connreset(e):\n                    raise\n\n\nclass SSLIOStream(IOStream):\n    \"\"\"A utility class to write to and read from a non-blocking SSL socket.\n\n    If the socket passed to the constructor is already connected,\n    it should be wrapped with::\n\n        ssl.SSLContext(...).wrap_socket(sock, do_handshake_on_connect=False, **kwargs)\n\n    before constructing the `SSLIOStream`.  Unconnected sockets will be\n    wrapped when `IOStream.connect` is finished.\n    \"\"\"\n\n    socket: ssl.SSLSocket\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        \"\"\"The ``ssl_options`` keyword argument may either be an\n        `ssl.SSLContext` object or a dictionary of keywords arguments\n        for `ssl.SSLContext.wrap_socket`\n        \"\"\"\n        self._ssl_options = kwargs.pop(\"ssl_options\", _client_ssl_defaults)\n        super().__init__(*args, **kwargs)\n        self._ssl_accepting = True\n        self._handshake_reading = False\n        self._handshake_writing = False\n        self._server_hostname: str | None = None\n\n        # If the socket is already connected, attempt to start the handshake.\n        try:\n            self.socket.getpeername()\n        except OSError:\n            pass\n        else:\n            # Indirectly start the handshake, which will run on the next\n            # IOLoop iteration and then the real IO state will be set in\n            # _handle_events.\n            self._add_io_state(self.io_loop.WRITE)\n\n    def reading(self) -> bool:\n        return self._handshake_reading or super().reading()\n\n    def writing(self) -> bool:\n        return self._handshake_writing or super().writing()\n\n    def _do_ssl_handshake(self) -> None:\n        # Based on code from test_ssl.py in the python stdlib\n        try:\n            self._handshake_reading = False\n            self._handshake_writing = False\n            self.socket.do_handshake()\n        except ssl.SSLError as err:\n            if err.args[0] == ssl.SSL_ERROR_WANT_READ:\n                self._handshake_reading = True\n                return\n            elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE:\n                self._handshake_writing = True\n                return\n            elif err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN):\n                return self.close(exc_info=err)\n            elif err.args[0] in (ssl.SSL_ERROR_SSL, ssl.SSL_ERROR_SYSCALL):\n                try:\n                    peer = self.socket.getpeername()\n                except Exception:\n                    peer = \"(not connected)\"\n                gen_log.warning(\n                    \"SSL Error on %s %s: %s\", self.socket.fileno(), peer, err\n                )\n                return self.close(exc_info=err)\n            raise\n        except OSError as err:\n            # Some port scans (e.g. nmap in -sT mode) have been known\n            # to cause do_handshake to raise EBADF and ENOTCONN, so make\n            # those errors quiet as well.\n            # https://groups.google.com/forum/?fromgroups#!topic/python-tornado/ApucKJat1_0\n            # Errno 0 is also possible in some cases (nc -z).\n            # https://github.com/tornadoweb/tornado/issues/2504\n            if self._is_connreset(err) or err.args[0] in (\n                0,\n                errno.EBADF,\n                errno.ENOTCONN,\n            ):\n                return self.close(exc_info=err)\n            raise\n        except AttributeError as err:\n            # On Linux, if the connection was reset before the call to\n            # wrap_socket, do_handshake will fail with an\n            # AttributeError.\n            return self.close(exc_info=err)\n        else:\n            self._ssl_accepting = False\n            # Prior to the introduction of SNI, this is where we would check\n            # the server's claimed hostname.\n            assert ssl.HAS_SNI\n            self._finish_ssl_connect()\n\n    def _finish_ssl_connect(self) -> None:\n        if self._ssl_connect_future is not None:\n            future = self._ssl_connect_future\n            self._ssl_connect_future = None\n            future_set_result_unless_cancelled(future, self)\n\n    def _handle_read(self) -> None:\n        if self._ssl_accepting:\n            self._do_ssl_handshake()\n            return\n        super()._handle_read()\n\n    def _handle_write(self) -> None:\n        if self._ssl_accepting:\n            self._do_ssl_handshake()\n            return\n        super()._handle_write()\n\n    def connect(\n        self, address: tuple, server_hostname: str | None = None\n    ) -> \"Future[SSLIOStream]\":\n        self._server_hostname = server_hostname\n        # Ignore the result of connect(). If it fails,\n        # wait_for_handshake will raise an error too. This is\n        # necessary for the old semantics of the connect callback\n        # (which takes no arguments). In 6.0 this can be refactored to\n        # be a regular coroutine.\n        # TODO: This is trickier than it looks, since if write()\n        # is called with a connect() pending, we want the connect\n        # to resolve before the write. Or do we care about this?\n        # (There's a test for it, but I think in practice users\n        # either wait for the connect before performing a write or\n        # they don't care about the connect Future at all)\n        fut = super().connect(address)\n        fut.add_done_callback(lambda f: f.exception())\n        return self.wait_for_handshake()\n\n    def _handle_connect(self) -> None:\n        # Call the superclass method to check for errors.\n        super()._handle_connect()\n        if self.closed():\n            return\n        # When the connection is complete, wrap the socket for SSL\n        # traffic.  Note that we do this by overriding _handle_connect\n        # instead of by passing a callback to super().connect because\n        # user callbacks are enqueued asynchronously on the IOLoop,\n        # but since _handle_events calls _handle_connect immediately\n        # followed by _handle_write we need this to be synchronous.\n        #\n        # The IOLoop will get confused if we swap out self.socket while the\n        # fd is registered, so remove it now and re-register after\n        # wrap_socket().\n        self.io_loop.remove_handler(self.socket)\n        old_state = self._state\n        assert old_state is not None\n        self._state = None\n        self.socket = ssl_wrap_socket(\n            self.socket,\n            self._ssl_options,\n            server_hostname=self._server_hostname,\n            do_handshake_on_connect=False,\n            server_side=False,\n        )\n        self._add_io_state(old_state)\n\n    def wait_for_handshake(self) -> \"Future[SSLIOStream]\":\n        \"\"\"Wait for the initial SSL handshake to complete.\n\n        If a ``callback`` is given, it will be called with no\n        arguments once the handshake is complete; otherwise this\n        method returns a `.Future` which will resolve to the\n        stream itself after the handshake is complete.\n\n        Once the handshake is complete, information such as\n        the peer's certificate and NPN/ALPN selections may be\n        accessed on ``self.socket``.\n\n        This method is intended for use on server-side streams\n        or after using `IOStream.start_tls`; it should not be used\n        with `IOStream.connect` (which already waits for the\n        handshake to complete). It may only be called once per stream.\n\n        .. versionadded:: 4.2\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed. Use the returned\n           `.Future` instead.\n\n        \"\"\"\n        if self._ssl_connect_future is not None:\n            raise RuntimeError(\"Already waiting\")\n        future = self._ssl_connect_future = Future()\n        if not self._ssl_accepting:\n            self._finish_ssl_connect()\n        return future\n\n    def write_to_fd(self, data: memoryview) -> int:\n        # clip buffer size at 1GB since SSL sockets only support upto 2GB\n        # this change in behaviour is transparent, since the function is\n        # already expected to (possibly) write less than the provided buffer\n        if len(data) >> 30:\n            data = memoryview(data)[: 1 << 30]\n        try:\n            return self.socket.send(data)  # type: ignore\n        except ssl.SSLError as e:\n            if e.args[0] == ssl.SSL_ERROR_WANT_WRITE:\n                # In Python 3.5+, SSLSocket.send raises a WANT_WRITE error if\n                # the socket is not writeable; we need to transform this into\n                # an EWOULDBLOCK socket.error or a zero return value,\n                # either of which will be recognized by the caller of this\n                # method. Prior to Python 3.5, an unwriteable socket would\n                # simply return 0 bytes written.\n                return 0\n            raise\n        finally:\n            # Avoid keeping to data, which can be a memoryview.\n            # See https://github.com/tornadoweb/tornado/pull/2008\n            del data\n\n    def read_from_fd(self, buf: bytearray | memoryview) -> int | None:\n        try:\n            if self._ssl_accepting:\n                # If the handshake hasn't finished yet, there can't be anything\n                # to read (attempting to read may or may not raise an exception\n                # depending on the SSL version)\n                return None\n            # clip buffer size at 1GB since SSL sockets only support upto 2GB\n            # this change in behaviour is transparent, since the function is\n            # already expected to (possibly) read less than the provided buffer\n            if len(buf) >> 30:\n                buf = memoryview(buf)[: 1 << 30]\n            try:\n                return self.socket.recv_into(buf, len(buf))\n            except ssl.SSLError as e:\n                # SSLError is a subclass of socket.error, so this except\n                # block must come first.\n                if e.args[0] == ssl.SSL_ERROR_WANT_READ:\n                    return None\n                else:\n                    raise\n            except BlockingIOError:\n                return None\n        finally:\n            del buf\n\n    def _is_connreset(self, e: BaseException) -> bool:\n        if isinstance(e, ssl.SSLError) and e.args[0] == ssl.SSL_ERROR_EOF:\n            return True\n        return super()._is_connreset(e)\n\n\nclass PipeIOStream(BaseIOStream):\n    \"\"\"Pipe-based `IOStream` implementation.\n\n    The constructor takes an integer file descriptor (such as one returned\n    by `os.pipe`) rather than an open file object.  Pipes are generally\n    one-way, so a `PipeIOStream` can be used for reading or writing but not\n    both.\n\n    ``PipeIOStream`` is only available on Unix-based platforms.\n    \"\"\"\n\n    def __init__(self, fd: int, *args: Any, **kwargs: Any) -> None:\n        self.fd = fd\n        self._fio = io.FileIO(self.fd, \"r+\")\n        if sys.platform == \"win32\":\n            # The form and placement of this assertion is important to mypy.\n            # A plain assert statement isn't recognized here. If the assertion\n            # were earlier it would worry that the attributes of self aren't\n            # set on windows. If it were missing it would complain about\n            # the absence of the set_blocking function.\n            raise AssertionError(\"PipeIOStream is not supported on Windows\")\n        os.set_blocking(fd, False)\n        super().__init__(*args, **kwargs)\n\n    def fileno(self) -> int:\n        return self.fd\n\n    def close_fd(self) -> None:\n        self._fio.close()\n\n    def write_to_fd(self, data: memoryview) -> int:\n        try:\n            return os.write(self.fd, data)  # type: ignore\n        finally:\n            # Avoid keeping to data, which can be a memoryview.\n            # See https://github.com/tornadoweb/tornado/pull/2008\n            del data\n\n    def read_from_fd(self, buf: bytearray | memoryview) -> int | None:\n        try:\n            return self._fio.readinto(buf)  # type: ignore\n        except OSError as e:\n            if errno_from_exception(e) == errno.EBADF:\n                # If the writing half of a pipe is closed, select will\n                # report it as readable but reads will fail with EBADF.\n                self.close(exc_info=e)\n                return None\n            else:\n                raise\n        finally:\n            del buf\n\n\ndef doctests() -> Any:\n    import doctest\n\n    return doctest.DocTestSuite()\n"
  },
  {
    "path": "tornado/locale.py",
    "content": "# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Translation methods for generating localized strings.\n\nTo load a locale and generate a translated string::\n\n    user_locale = tornado.locale.get(\"es_LA\")\n    print(user_locale.translate(\"Sign out\"))\n\n`tornado.locale.get()` returns the closest matching locale, not necessarily the\nspecific locale you requested. You can support pluralization with\nadditional arguments to `~Locale.translate()`, e.g.::\n\n    people = [...]\n    message = user_locale.translate(\n        \"%(list)s is online\", \"%(list)s are online\", len(people))\n    print(message % {\"list\": user_locale.list(people)})\n\nThe first string is chosen if ``len(people) == 1``, otherwise the second\nstring is chosen.\n\nApplications should call one of `load_translations` (which uses a simple\nCSV format) or `load_gettext_translations` (which uses the ``.mo`` format\nsupported by `gettext` and related tools).  If neither method is called,\nthe `Locale.translate` method will simply return the original string.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport codecs\nimport csv\nimport datetime\nimport gettext\nimport glob\nimport os\nimport re\nfrom collections.abc import Iterable\nfrom typing import Any\n\nfrom tornado import escape\nfrom tornado._locale_data import LOCALE_NAMES\nfrom tornado.log import gen_log\n\n_default_locale = \"en_US\"\n_translations: dict[str, Any] = {}\n_supported_locales = frozenset([_default_locale])\n_use_gettext = False\nCONTEXT_SEPARATOR = \"\\x04\"\n\n\ndef get(*locale_codes: str) -> Locale:\n    \"\"\"Returns the closest match for the given locale codes.\n\n    We iterate over all given locale codes in order. If we have a tight\n    or a loose match for the code (e.g., \"en\" for \"en_US\"), we return\n    the locale. Otherwise we move to the next code in the list.\n\n    By default we return ``en_US`` if no translations are found for any of\n    the specified locales. You can change the default locale with\n    `set_default_locale()`.\n    \"\"\"\n    return Locale.get_closest(*locale_codes)\n\n\ndef set_default_locale(code: str) -> None:\n    \"\"\"Sets the default locale.\n\n    The default locale is assumed to be the language used for all strings\n    in the system. The translations loaded from disk are mappings from\n    the default locale to the destination locale. Consequently, you don't\n    need to create a translation file for the default locale.\n    \"\"\"\n    global _default_locale\n    global _supported_locales\n    _default_locale = code\n    _supported_locales = frozenset(list(_translations.keys()) + [_default_locale])\n\n\ndef load_translations(directory: str, encoding: str | None = None) -> None:\n    \"\"\"Loads translations from CSV files in a directory.\n\n    Translations are strings with optional Python-style named placeholders\n    (e.g., ``My name is %(name)s``) and their associated translations.\n\n    The directory should have translation files of the form ``LOCALE.csv``,\n    e.g. ``es_GT.csv``. The CSV files should have two or three columns: string,\n    translation, and an optional plural indicator. Plural indicators should\n    be one of \"plural\" or \"singular\". A given string can have both singular\n    and plural forms. For example ``%(name)s liked this`` may have a\n    different verb conjugation depending on whether %(name)s is one\n    name or a list of names. There should be two rows in the CSV file for\n    that string, one with plural indicator \"singular\", and one \"plural\".\n    For strings with no verbs that would change on translation, simply\n    use \"unknown\" or the empty string (or don't include the column at all).\n\n    The file is read using the `csv` module in the default \"excel\" dialect.\n    In this format there should not be spaces after the commas.\n\n    If no ``encoding`` parameter is given, the encoding will be\n    detected automatically (among UTF-8 and UTF-16) if the file\n    contains a byte-order marker (BOM), defaulting to UTF-8 if no BOM\n    is present.\n\n    Example translation ``es_LA.csv``::\n\n        \"I love you\",\"Te amo\"\n        \"%(name)s liked this\",\"A %(name)s les gustó esto\",\"plural\"\n        \"%(name)s liked this\",\"A %(name)s le gustó esto\",\"singular\"\n\n    .. versionchanged:: 4.3\n       Added ``encoding`` parameter. Added support for BOM-based encoding\n       detection, UTF-16, and UTF-8-with-BOM.\n    \"\"\"\n    global _translations\n    global _supported_locales\n    _translations = {}\n    for path in os.listdir(directory):\n        if not path.endswith(\".csv\"):\n            continue\n        locale, extension = path.split(\".\")\n        if not re.match(\"[a-z]+(_[A-Z]+)?$\", locale):\n            gen_log.error(\n                \"Unrecognized locale %r (path: %s)\",\n                locale,\n                os.path.join(directory, path),\n            )\n            continue\n        full_path = os.path.join(directory, path)\n        if encoding is None:\n            # Try to autodetect encoding based on the BOM.\n            with open(full_path, \"rb\") as bf:\n                data = bf.read(len(codecs.BOM_UTF16_LE))\n            if data in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE):\n                encoding = \"utf-16\"\n            else:\n                # utf-8-sig is \"utf-8 with optional BOM\". It's discouraged\n                # in most cases but is common with CSV files because Excel\n                # cannot read utf-8 files without a BOM.\n                encoding = \"utf-8-sig\"\n        # python 3: csv.reader requires a file open in text mode.\n        # Specify an encoding to avoid dependence on $LANG environment variable.\n        with open(full_path, encoding=encoding) as f:\n            _translations[locale] = {}\n            for i, row in enumerate(csv.reader(f)):\n                if not row or len(row) < 2:\n                    continue\n                row = [escape.to_unicode(c).strip() for c in row]\n                english, translation = row[:2]\n                if len(row) > 2:\n                    plural = row[2] or \"unknown\"\n                else:\n                    plural = \"unknown\"\n                if plural not in (\"plural\", \"singular\", \"unknown\"):\n                    gen_log.error(\n                        \"Unrecognized plural indicator %r in %s line %d\",\n                        plural,\n                        path,\n                        i + 1,\n                    )\n                    continue\n                _translations[locale].setdefault(plural, {})[english] = translation\n    _supported_locales = frozenset(list(_translations.keys()) + [_default_locale])\n    gen_log.debug(\"Supported locales: %s\", sorted(_supported_locales))\n\n\ndef load_gettext_translations(directory: str, domain: str) -> None:\n    \"\"\"Loads translations from `gettext`'s locale tree\n\n    Locale tree is similar to system's ``/usr/share/locale``, like::\n\n        {directory}/{lang}/LC_MESSAGES/{domain}.mo\n\n    Three steps are required to have your app translated:\n\n    1. Generate POT translation file::\n\n        xgettext --language=Python --keyword=_:1,2 -d mydomain file1.py file2.html etc\n\n    2. Merge against existing POT file::\n\n        msgmerge old.po mydomain.po > new.po\n\n    3. Compile::\n\n        msgfmt mydomain.po -o {directory}/pt_BR/LC_MESSAGES/mydomain.mo\n    \"\"\"\n    global _translations\n    global _supported_locales\n    global _use_gettext\n    _translations = {}\n\n    for filename in glob.glob(\n        os.path.join(directory, \"*\", \"LC_MESSAGES\", domain + \".mo\")\n    ):\n        lang = os.path.basename(os.path.dirname(os.path.dirname(filename)))\n        try:\n            _translations[lang] = gettext.translation(\n                domain, directory, languages=[lang]\n            )\n        except Exception as e:\n            gen_log.error(\"Cannot load translation for '%s': %s\", lang, str(e))\n            continue\n    _supported_locales = frozenset(list(_translations.keys()) + [_default_locale])\n    _use_gettext = True\n    gen_log.debug(\"Supported locales: %s\", sorted(_supported_locales))\n\n\ndef get_supported_locales() -> Iterable[str]:\n    \"\"\"Returns a list of all the supported locale codes.\"\"\"\n    return _supported_locales\n\n\nclass Locale:\n    \"\"\"Object representing a locale.\n\n    After calling one of `load_translations` or `load_gettext_translations`,\n    call `get` or `get_closest` to get a Locale object.\n    \"\"\"\n\n    _cache: dict[str, Locale] = {}\n\n    @classmethod\n    def get_closest(cls, *locale_codes: str) -> Locale:\n        \"\"\"Returns the closest match for the given locale code.\"\"\"\n        for code in locale_codes:\n            if not code:\n                continue\n            code = code.replace(\"-\", \"_\")\n            parts = code.split(\"_\")\n            if len(parts) > 2:\n                continue\n            elif len(parts) == 2:\n                code = parts[0].lower() + \"_\" + parts[1].upper()\n            if code in _supported_locales:\n                return cls.get(code)\n            if parts[0].lower() in _supported_locales:\n                return cls.get(parts[0].lower())\n        return cls.get(_default_locale)\n\n    @classmethod\n    def get(cls, code: str) -> Locale:\n        \"\"\"Returns the Locale for the given locale code.\n\n        If it is not supported, we raise an exception.\n        \"\"\"\n        if code not in cls._cache:\n            assert code in _supported_locales\n            translations = _translations.get(code, None)\n            if translations is None:\n                locale: Locale = CSVLocale(code, {})\n            elif _use_gettext:\n                locale = GettextLocale(code, translations)\n            else:\n                locale = CSVLocale(code, translations)\n            cls._cache[code] = locale\n        return cls._cache[code]\n\n    def __init__(self, code: str) -> None:\n        self.code = code\n        self.name = LOCALE_NAMES.get(code, {}).get(\"name\", \"Unknown\")\n        self.rtl = False\n        for prefix in [\"fa\", \"ar\", \"he\"]:\n            if self.code.startswith(prefix):\n                self.rtl = True\n                break\n\n        # Initialize strings for date formatting\n        _ = self.translate\n        self._months = [\n            _(\"January\"),\n            _(\"February\"),\n            _(\"March\"),\n            _(\"April\"),\n            _(\"May\"),\n            _(\"June\"),\n            _(\"July\"),\n            _(\"August\"),\n            _(\"September\"),\n            _(\"October\"),\n            _(\"November\"),\n            _(\"December\"),\n        ]\n        self._weekdays = [\n            _(\"Monday\"),\n            _(\"Tuesday\"),\n            _(\"Wednesday\"),\n            _(\"Thursday\"),\n            _(\"Friday\"),\n            _(\"Saturday\"),\n            _(\"Sunday\"),\n        ]\n\n    def translate(\n        self,\n        message: str,\n        plural_message: str | None = None,\n        count: int | None = None,\n    ) -> str:\n        \"\"\"Returns the translation for the given message for this locale.\n\n        If ``plural_message`` is given, you must also provide\n        ``count``. We return ``plural_message`` when ``count != 1``,\n        and we return the singular form for the given message when\n        ``count == 1``.\n        \"\"\"\n        raise NotImplementedError()\n\n    def pgettext(\n        self,\n        context: str,\n        message: str,\n        plural_message: str | None = None,\n        count: int | None = None,\n    ) -> str:\n        raise NotImplementedError()\n\n    def format_date(\n        self,\n        date: int | float | datetime.datetime,\n        gmt_offset: int = 0,\n        relative: bool = True,\n        shorter: bool = False,\n        full_format: bool = False,\n    ) -> str:\n        \"\"\"Formats the given date.\n\n        By default, we return a relative time (e.g., \"2 minutes ago\"). You\n        can return an absolute date string with ``relative=False``.\n\n        You can force a full format date (\"July 10, 1980\") with\n        ``full_format=True``.\n\n        This method is primarily intended for dates in the past.\n        For dates in the future, we fall back to full format.\n\n        .. versionchanged:: 6.4\n           Aware `datetime.datetime` objects are now supported (naive\n           datetimes are still assumed to be UTC).\n        \"\"\"\n        if isinstance(date, (int, float)):\n            date = datetime.datetime.fromtimestamp(date, datetime.timezone.utc)\n        if date.tzinfo is None:\n            date = date.replace(tzinfo=datetime.timezone.utc)\n        now = datetime.datetime.now(datetime.timezone.utc)\n        if date > now:\n            if relative and (date - now).seconds < 60:\n                # Due to click skew, things are some things slightly\n                # in the future. Round timestamps in the immediate\n                # future down to now in relative mode.\n                date = now\n            else:\n                # Otherwise, future dates always use the full format.\n                full_format = True\n        local_date = date - datetime.timedelta(minutes=gmt_offset)\n        local_now = now - datetime.timedelta(minutes=gmt_offset)\n        local_yesterday = local_now - datetime.timedelta(hours=24)\n        difference = now - date\n        seconds = difference.seconds\n        days = difference.days\n\n        _ = self.translate\n        format = None\n        if not full_format:\n            if relative and days == 0:\n                if seconds < 50:\n                    return _(\"1 second ago\", \"%(seconds)d seconds ago\", seconds) % {\n                        \"seconds\": seconds\n                    }\n\n                if seconds < 50 * 60:\n                    minutes = round(seconds / 60.0)\n                    return _(\"1 minute ago\", \"%(minutes)d minutes ago\", minutes) % {\n                        \"minutes\": minutes\n                    }\n\n                hours = round(seconds / (60.0 * 60))\n                return _(\"1 hour ago\", \"%(hours)d hours ago\", hours) % {\"hours\": hours}\n\n            if days == 0:\n                format = _(\"%(time)s\")\n            elif days == 1 and local_date.day == local_yesterday.day and relative:\n                format = _(\"yesterday\") if shorter else _(\"yesterday at %(time)s\")\n            elif days < 5:\n                format = _(\"%(weekday)s\") if shorter else _(\"%(weekday)s at %(time)s\")\n            elif days < 334:  # 11mo, since confusing for same month last year\n                format = (\n                    _(\"%(month_name)s %(day)s\")\n                    if shorter\n                    else _(\"%(month_name)s %(day)s at %(time)s\")\n                )\n\n        if format is None:\n            format = (\n                _(\"%(month_name)s %(day)s, %(year)s\")\n                if shorter\n                else _(\"%(month_name)s %(day)s, %(year)s at %(time)s\")\n            )\n\n        tfhour_clock = self.code not in (\"en\", \"en_US\", \"zh_CN\")\n        if tfhour_clock:\n            str_time = \"%d:%02d\" % (local_date.hour, local_date.minute)\n        elif self.code == \"zh_CN\":\n            str_time = \"%s%d:%02d\" % (\n                (\"\\u4e0a\\u5348\", \"\\u4e0b\\u5348\")[local_date.hour >= 12],\n                local_date.hour % 12 or 12,\n                local_date.minute,\n            )\n        else:\n            str_time = \"%d:%02d %s\" % (\n                local_date.hour % 12 or 12,\n                local_date.minute,\n                (\"am\", \"pm\")[local_date.hour >= 12],\n            )\n\n        return format % {\n            \"month_name\": self._months[local_date.month - 1],\n            \"weekday\": self._weekdays[local_date.weekday()],\n            \"day\": str(local_date.day),\n            \"year\": str(local_date.year),\n            \"time\": str_time,\n        }\n\n    def format_day(\n        self, date: datetime.datetime, gmt_offset: int = 0, dow: bool = True\n    ) -> bool:\n        \"\"\"Formats the given date as a day of week.\n\n        Example: \"Monday, January 22\". You can remove the day of week with\n        ``dow=False``.\n        \"\"\"\n        local_date = date - datetime.timedelta(minutes=gmt_offset)\n        _ = self.translate\n        if dow:\n            return _(\"%(weekday)s, %(month_name)s %(day)s\") % {\n                \"month_name\": self._months[local_date.month - 1],\n                \"weekday\": self._weekdays[local_date.weekday()],\n                \"day\": str(local_date.day),\n            }\n        else:\n            return _(\"%(month_name)s %(day)s\") % {\n                \"month_name\": self._months[local_date.month - 1],\n                \"day\": str(local_date.day),\n            }\n\n    def list(self, parts: Any) -> str:\n        \"\"\"Returns a comma-separated list for the given list of parts.\n\n        The format is, e.g., \"A, B and C\", \"A and B\" or just \"A\" for lists\n        of size 1.\n        \"\"\"\n        _ = self.translate\n        if len(parts) == 0:\n            return \"\"\n        if len(parts) == 1:\n            return parts[0]\n        comma = \" \\u0648 \" if self.code.startswith(\"fa\") else \", \"\n        return _(\"%(commas)s and %(last)s\") % {\n            \"commas\": comma.join(parts[:-1]),\n            \"last\": parts[len(parts) - 1],\n        }\n\n    def friendly_number(self, value: int) -> str:\n        \"\"\"Returns a comma-separated number for the given integer.\"\"\"\n        if self.code not in (\"en\", \"en_US\"):\n            return str(value)\n        s = str(value)\n        parts = []\n        while s:\n            parts.append(s[-3:])\n            s = s[:-3]\n        return \",\".join(reversed(parts))\n\n\nclass CSVLocale(Locale):\n    \"\"\"Locale implementation using tornado's CSV translation format.\"\"\"\n\n    def __init__(self, code: str, translations: dict[str, dict[str, str]]) -> None:\n        self.translations = translations\n        super().__init__(code)\n\n    def translate(\n        self,\n        message: str,\n        plural_message: str | None = None,\n        count: int | None = None,\n    ) -> str:\n        if plural_message is not None:\n            assert count is not None\n            if count != 1:\n                message = plural_message\n                message_dict = self.translations.get(\"plural\", {})\n            else:\n                message_dict = self.translations.get(\"singular\", {})\n        else:\n            message_dict = self.translations.get(\"unknown\", {})\n        return message_dict.get(message, message)\n\n    def pgettext(\n        self,\n        context: str,\n        message: str,\n        plural_message: str | None = None,\n        count: int | None = None,\n    ) -> str:\n        if self.translations:\n            gen_log.warning(\"pgettext is not supported by CSVLocale\")\n        return self.translate(message, plural_message, count)\n\n\nclass GettextLocale(Locale):\n    \"\"\"Locale implementation using the `gettext` module.\"\"\"\n\n    def __init__(self, code: str, translations: gettext.NullTranslations) -> None:\n        self.ngettext = translations.ngettext\n        self.gettext = translations.gettext\n        # self.gettext must exist before __init__ is called, since it\n        # calls into self.translate\n        super().__init__(code)\n\n    def translate(\n        self,\n        message: str,\n        plural_message: str | None = None,\n        count: int | None = None,\n    ) -> str:\n        if plural_message is not None:\n            assert count is not None\n            return self.ngettext(message, plural_message, count)\n        else:\n            return self.gettext(message)\n\n    def pgettext(\n        self,\n        context: str,\n        message: str,\n        plural_message: str | None = None,\n        count: int | None = None,\n    ) -> str:\n        \"\"\"Allows to set context for translation, accepts plural forms.\n\n        Usage example::\n\n            pgettext(\"law\", \"right\")\n            pgettext(\"good\", \"right\")\n\n        Plural message example::\n\n            pgettext(\"organization\", \"club\", \"clubs\", len(clubs))\n            pgettext(\"stick\", \"club\", \"clubs\", len(clubs))\n\n        To generate POT file with context, add following options to step 1\n        of `load_gettext_translations` sequence::\n\n            xgettext [basic options] --keyword=pgettext:1c,2 --keyword=pgettext:1c,2,3\n\n        .. versionadded:: 4.2\n        \"\"\"\n        if plural_message is not None:\n            assert count is not None\n            msgs_with_ctxt = (\n                f\"{context}{CONTEXT_SEPARATOR}{message}\",\n                f\"{context}{CONTEXT_SEPARATOR}{plural_message}\",\n                count,\n            )\n            result = self.ngettext(*msgs_with_ctxt)\n            if CONTEXT_SEPARATOR in result:\n                # Translation not found\n                result = self.ngettext(message, plural_message, count)\n            return result\n        else:\n            msg_with_ctxt = f\"{context}{CONTEXT_SEPARATOR}{message}\"\n            result = self.gettext(msg_with_ctxt)\n            if CONTEXT_SEPARATOR in result:\n                # Translation not found\n                result = message\n            return result\n"
  },
  {
    "path": "tornado/locks.py",
    "content": "# Copyright 2015 The Tornado Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\nimport collections\nimport datetime\nimport types\nfrom collections.abc import Awaitable\nfrom typing import Any, Optional, Type\n\nfrom tornado import gen, ioloop\nfrom tornado.concurrent import Future, future_set_result_unless_cancelled\n\n__all__ = [\"Condition\", \"Event\", \"Semaphore\", \"BoundedSemaphore\", \"Lock\"]\n\n\nclass _TimeoutGarbageCollector:\n    \"\"\"Base class for objects that periodically clean up timed-out waiters.\n\n    Avoids memory leak in a common pattern like:\n\n        while True:\n            yield condition.wait(short_timeout)\n            print('looping....')\n    \"\"\"\n\n    def __init__(self) -> None:\n        self._waiters: collections.deque[Future] = collections.deque()\n        self._timeouts = 0\n\n    def _garbage_collect(self) -> None:\n        # Occasionally clear timed-out waiters.\n        self._timeouts += 1\n        if self._timeouts > 100:\n            self._timeouts = 0\n            self._waiters = collections.deque(w for w in self._waiters if not w.done())\n\n\nclass Condition(_TimeoutGarbageCollector):\n    \"\"\"A condition allows one or more coroutines to wait until notified.\n\n    Like a standard `threading.Condition`, but does not need an underlying lock\n    that is acquired and released.\n\n    With a `Condition`, coroutines can wait to be notified by other coroutines:\n\n    .. testcode::\n\n        import asyncio\n        from tornado import gen\n        from tornado.locks import Condition\n\n        condition = Condition()\n\n        async def waiter():\n            print(\"I'll wait right here\")\n            await condition.wait()\n            print(\"I'm done waiting\")\n\n        async def notifier():\n            print(\"About to notify\")\n            condition.notify()\n            print(\"Done notifying\")\n\n        async def runner():\n            # Wait for waiter() and notifier() in parallel\n            await gen.multi([waiter(), notifier()])\n\n        asyncio.run(runner())\n\n    .. testoutput::\n\n        I'll wait right here\n        About to notify\n        Done notifying\n        I'm done waiting\n\n    `wait` takes an optional ``timeout`` argument, which is either an absolute\n    timestamp::\n\n        io_loop = IOLoop.current()\n\n        # Wait up to 1 second for a notification.\n        await condition.wait(timeout=io_loop.time() + 1)\n\n    ...or a `datetime.timedelta` for a timeout relative to the current time::\n\n        # Wait up to 1 second.\n        await condition.wait(timeout=datetime.timedelta(seconds=1))\n\n    The method returns False if there's no notification before the deadline.\n\n    .. versionchanged:: 5.0\n       Previously, waiters could be notified synchronously from within\n       `notify`. Now, the notification will always be received on the\n       next iteration of the `.IOLoop`.\n    \"\"\"\n\n    def __repr__(self) -> str:\n        result = f\"<{self.__class__.__name__}\"\n        if self._waiters:\n            result += \" waiters[%s]\" % len(self._waiters)\n        return result + \">\"\n\n    def wait(\n        self, timeout: float | datetime.timedelta | None = None\n    ) -> Awaitable[bool]:\n        \"\"\"Wait for `.notify`.\n\n        Returns a `.Future` that resolves ``True`` if the condition is notified,\n        or ``False`` after a timeout.\n        \"\"\"\n        waiter: Future[bool] = Future()\n        self._waiters.append(waiter)\n        if timeout:\n\n            def on_timeout() -> None:\n                if not waiter.done():\n                    future_set_result_unless_cancelled(waiter, False)\n                self._garbage_collect()\n\n            io_loop = ioloop.IOLoop.current()\n            timeout_handle = io_loop.add_timeout(timeout, on_timeout)\n            waiter.add_done_callback(lambda _: io_loop.remove_timeout(timeout_handle))\n        return waiter\n\n    def notify(self, n: int = 1) -> None:\n        \"\"\"Wake ``n`` waiters.\"\"\"\n        waiters = []  # Waiters we plan to run right now.\n        while n and self._waiters:\n            waiter = self._waiters.popleft()\n            if not waiter.done():  # Might have timed out.\n                n -= 1\n                waiters.append(waiter)\n\n        for waiter in waiters:\n            future_set_result_unless_cancelled(waiter, True)\n\n    def notify_all(self) -> None:\n        \"\"\"Wake all waiters.\"\"\"\n        self.notify(len(self._waiters))\n\n\nclass Event:\n    \"\"\"An event blocks coroutines until its internal flag is set to True.\n\n    Similar to `threading.Event`.\n\n    A coroutine can wait for an event to be set. Once it is set, calls to\n    ``yield event.wait()`` will not block unless the event has been cleared:\n\n    .. testcode::\n\n        import asyncio\n        from tornado import gen\n        from tornado.locks import Event\n\n        event = Event()\n\n        async def waiter():\n            print(\"Waiting for event\")\n            await event.wait()\n            print(\"Not waiting this time\")\n            await event.wait()\n            print(\"Done\")\n\n        async def setter():\n            print(\"About to set the event\")\n            event.set()\n\n        async def runner():\n            await gen.multi([waiter(), setter()])\n\n        asyncio.run(runner())\n\n    .. testoutput::\n\n        Waiting for event\n        About to set the event\n        Not waiting this time\n        Done\n    \"\"\"\n\n    def __init__(self) -> None:\n        self._value = False\n        self._waiters: set[Future[None]] = set()\n\n    def __repr__(self) -> str:\n        return \"<{} {}>\".format(\n            self.__class__.__name__,\n            \"set\" if self.is_set() else \"clear\",\n        )\n\n    def is_set(self) -> bool:\n        \"\"\"Return ``True`` if the internal flag is true.\"\"\"\n        return self._value\n\n    def set(self) -> None:\n        \"\"\"Set the internal flag to ``True``. All waiters are awakened.\n\n        Calling `.wait` once the flag is set will not block.\n        \"\"\"\n        if not self._value:\n            self._value = True\n\n            for fut in self._waiters:\n                if not fut.done():\n                    fut.set_result(None)\n\n    def clear(self) -> None:\n        \"\"\"Reset the internal flag to ``False``.\n\n        Calls to `.wait` will block until `.set` is called.\n        \"\"\"\n        self._value = False\n\n    def wait(\n        self, timeout: float | datetime.timedelta | None = None\n    ) -> Awaitable[None]:\n        \"\"\"Block until the internal flag is true.\n\n        Returns an awaitable, which raises `tornado.util.TimeoutError` after a\n        timeout.\n        \"\"\"\n        fut: Future[None] = Future()\n        if self._value:\n            fut.set_result(None)\n            return fut\n        self._waiters.add(fut)\n        fut.add_done_callback(lambda fut: self._waiters.remove(fut))\n        if timeout is None:\n            return fut\n        else:\n            timeout_fut = gen.with_timeout(timeout, fut)\n            # This is a slightly clumsy workaround for the fact that\n            # gen.with_timeout doesn't cancel its futures. Cancelling\n            # fut will remove it from the waiters list.\n            timeout_fut.add_done_callback(\n                lambda tf: fut.cancel() if not fut.done() else None\n            )\n            return timeout_fut\n\n\nclass _ReleasingContextManager:\n    \"\"\"Releases a Lock or Semaphore at the end of a \"with\" statement.\n\n    with (yield semaphore.acquire()):\n        pass\n\n    # Now semaphore.release() has been called.\n    \"\"\"\n\n    def __init__(self, obj: Any) -> None:\n        self._obj = obj\n\n    def __enter__(self) -> None:\n        pass\n\n    def __exit__(\n        self,\n        exc_type: \"Optional[Type[BaseException]]\",\n        exc_val: BaseException | None,\n        exc_tb: types.TracebackType | None,\n    ) -> None:\n        self._obj.release()\n\n\nclass Semaphore(_TimeoutGarbageCollector):\n    \"\"\"A lock that can be acquired a fixed number of times before blocking.\n\n    A Semaphore manages a counter representing the number of `.release` calls\n    minus the number of `.acquire` calls, plus an initial value. The `.acquire`\n    method blocks if necessary until it can return without making the counter\n    negative.\n\n    Semaphores limit access to a shared resource. To allow access for two\n    workers at a time:\n\n    .. testsetup:: semaphore\n\n       from collections import deque\n\n       from tornado import gen\n       from tornado.ioloop import IOLoop\n       from tornado.concurrent import Future\n\n       inited = False\n\n       async def simulator(futures):\n           for f in futures:\n               # simulate the asynchronous passage of time\n               await gen.sleep(0)\n               await gen.sleep(0)\n               f.set_result(None)\n\n       def use_some_resource():\n           global inited\n           global futures_q\n           if not inited:\n               inited = True\n               # Ensure reliable doctest output: resolve Futures one at a time.\n               futures_q = deque([Future() for _ in range(3)])\n               IOLoop.current().add_callback(simulator, list(futures_q))\n\n           return futures_q.popleft()\n\n    .. testcode:: semaphore\n\n        import asyncio\n        from tornado import gen\n        from tornado.locks import Semaphore\n\n        sem = Semaphore(2)\n\n        async def worker(worker_id):\n            await sem.acquire()\n            try:\n                print(\"Worker %d is working\" % worker_id)\n                await use_some_resource()\n            finally:\n                print(\"Worker %d is done\" % worker_id)\n                sem.release()\n\n        async def runner():\n            # Join all workers.\n            await gen.multi([worker(i) for i in range(3)])\n\n        asyncio.run(runner())\n\n    .. testoutput:: semaphore\n\n        Worker 0 is working\n        Worker 1 is working\n        Worker 0 is done\n        Worker 2 is working\n        Worker 1 is done\n        Worker 2 is done\n\n    Workers 0 and 1 are allowed to run concurrently, but worker 2 waits until\n    the semaphore has been released once, by worker 0.\n\n    The semaphore can be used as an async context manager::\n\n        async def worker(worker_id):\n            async with sem:\n                print(\"Worker %d is working\" % worker_id)\n                await use_some_resource()\n\n            # Now the semaphore has been released.\n            print(\"Worker %d is done\" % worker_id)\n\n    For compatibility with older versions of Python, `.acquire` is a\n    context manager, so ``worker`` could also be written as::\n\n        @gen.coroutine\n        def worker(worker_id):\n            with (yield sem.acquire()):\n                print(\"Worker %d is working\" % worker_id)\n                yield use_some_resource()\n\n            # Now the semaphore has been released.\n            print(\"Worker %d is done\" % worker_id)\n\n    .. versionchanged:: 4.3\n       Added ``async with`` support in Python 3.5.\n\n    \"\"\"\n\n    def __init__(self, value: int = 1) -> None:\n        super().__init__()\n        if value < 0:\n            raise ValueError(\"semaphore initial value must be >= 0\")\n\n        self._value = value\n\n    def __repr__(self) -> str:\n        res = super().__repr__()\n        extra = \"locked\" if self._value == 0 else f\"unlocked,value:{self._value}\"\n        if self._waiters:\n            extra = f\"{extra},waiters:{len(self._waiters)}\"\n        return f\"<{res[1:-1]} [{extra}]>\"\n\n    def release(self) -> None:\n        \"\"\"Increment the counter and wake one waiter.\"\"\"\n        self._value += 1\n        while self._waiters:\n            waiter = self._waiters.popleft()\n            if not waiter.done():\n                self._value -= 1\n\n                # If the waiter is a coroutine paused at\n                #\n                #     with (yield semaphore.acquire()):\n                #\n                # then the context manager's __exit__ calls release() at the end\n                # of the \"with\" block.\n                waiter.set_result(_ReleasingContextManager(self))\n                break\n\n    def acquire(\n        self, timeout: float | datetime.timedelta | None = None\n    ) -> Awaitable[_ReleasingContextManager]:\n        \"\"\"Decrement the counter. Returns an awaitable.\n\n        Block if the counter is zero and wait for a `.release`. The awaitable\n        raises `.TimeoutError` after the deadline.\n        \"\"\"\n        waiter: Future[_ReleasingContextManager] = Future()\n        if self._value > 0:\n            self._value -= 1\n            waiter.set_result(_ReleasingContextManager(self))\n        else:\n            self._waiters.append(waiter)\n            if timeout:\n\n                def on_timeout() -> None:\n                    if not waiter.done():\n                        waiter.set_exception(gen.TimeoutError())\n                    self._garbage_collect()\n\n                io_loop = ioloop.IOLoop.current()\n                timeout_handle = io_loop.add_timeout(timeout, on_timeout)\n                waiter.add_done_callback(\n                    lambda _: io_loop.remove_timeout(timeout_handle)\n                )\n        return waiter\n\n    def __enter__(self) -> None:\n        raise RuntimeError(\"Use 'async with' instead of 'with' for Semaphore\")\n\n    def __exit__(\n        self,\n        typ: \"Optional[Type[BaseException]]\",\n        value: BaseException | None,\n        traceback: types.TracebackType | None,\n    ) -> None:\n        self.__enter__()\n\n    async def __aenter__(self) -> None:\n        await self.acquire()\n\n    async def __aexit__(\n        self,\n        typ: \"Optional[Type[BaseException]]\",\n        value: BaseException | None,\n        tb: types.TracebackType | None,\n    ) -> None:\n        self.release()\n\n\nclass BoundedSemaphore(Semaphore):\n    \"\"\"A semaphore that prevents release() being called too many times.\n\n    If `.release` would increment the semaphore's value past the initial\n    value, it raises `ValueError`. Semaphores are mostly used to guard\n    resources with limited capacity, so a semaphore released too many times\n    is a sign of a bug.\n    \"\"\"\n\n    def __init__(self, value: int = 1) -> None:\n        super().__init__(value=value)\n        self._initial_value = value\n\n    def release(self) -> None:\n        \"\"\"Increment the counter and wake one waiter.\"\"\"\n        if self._value >= self._initial_value:\n            raise ValueError(\"Semaphore released too many times\")\n        super().release()\n\n\nclass Lock:\n    \"\"\"A lock for coroutines.\n\n    A Lock begins unlocked, and `acquire` locks it immediately. While it is\n    locked, a coroutine that yields `acquire` waits until another coroutine\n    calls `release`.\n\n    Releasing an unlocked lock raises `RuntimeError`.\n\n    A Lock can be used as an async context manager with the ``async\n    with`` statement:\n\n    >>> from tornado import locks\n    >>> lock = locks.Lock()\n    >>>\n    >>> async def f():\n    ...    async with lock:\n    ...        # Do something holding the lock.\n    ...        pass\n    ...\n    ...    # Now the lock is released.\n\n    For compatibility with older versions of Python, the `.acquire`\n    method asynchronously returns a regular context manager:\n\n    >>> async def f2():\n    ...    with (yield lock.acquire()):\n    ...        # Do something holding the lock.\n    ...        pass\n    ...\n    ...    # Now the lock is released.\n\n    .. versionchanged:: 4.3\n       Added ``async with`` support in Python 3.5.\n\n    \"\"\"\n\n    def __init__(self) -> None:\n        self._block = BoundedSemaphore(value=1)\n\n    def __repr__(self) -> str:\n        return f\"<{self.__class__.__name__} _block={self._block}>\"\n\n    def acquire(\n        self, timeout: float | datetime.timedelta | None = None\n    ) -> Awaitable[_ReleasingContextManager]:\n        \"\"\"Attempt to lock. Returns an awaitable.\n\n        Returns an awaitable, which raises `tornado.util.TimeoutError` after a\n        timeout.\n        \"\"\"\n        return self._block.acquire(timeout)\n\n    def release(self) -> None:\n        \"\"\"Unlock.\n\n        The first coroutine in line waiting for `acquire` gets the lock.\n\n        If not locked, raise a `RuntimeError`.\n        \"\"\"\n        try:\n            self._block.release()\n        except ValueError:\n            raise RuntimeError(\"release unlocked lock\")\n\n    def __enter__(self) -> None:\n        raise RuntimeError(\"Use `async with` instead of `with` for Lock\")\n\n    def __exit__(\n        self,\n        typ: \"Optional[Type[BaseException]]\",\n        value: BaseException | None,\n        tb: types.TracebackType | None,\n    ) -> None:\n        self.__enter__()\n\n    async def __aenter__(self) -> None:\n        await self.acquire()\n\n    async def __aexit__(\n        self,\n        typ: \"Optional[Type[BaseException]]\",\n        value: BaseException | None,\n        tb: types.TracebackType | None,\n    ) -> None:\n        self.release()\n"
  },
  {
    "path": "tornado/log.py",
    "content": "#\n# Copyright 2012 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\"\"\"Logging support for Tornado.\n\nTornado uses three logger streams:\n\n* ``tornado.access``: Per-request logging for Tornado's HTTP servers (and\n  potentially other servers in the future)\n* ``tornado.application``: Logging of errors from application code (i.e.\n  uncaught exceptions from callbacks)\n* ``tornado.general``: General-purpose logging, including any errors\n  or warnings from Tornado itself.\n\nThese streams may be configured independently using the standard library's\n`logging` module.  For example, you may wish to send ``tornado.access`` logs\nto a separate file for analysis.\n\"\"\"\n\nimport logging\nimport logging.handlers\nimport sys\n\nfrom tornado.escape import _unicode\nfrom tornado.util import basestring_type, unicode_type\n\ntry:\n    import colorama  # type: ignore\nexcept ImportError:\n    colorama = None\n\ntry:\n    import curses\nexcept ImportError:\n    curses = None  # type: ignore\n\nfrom typing import Any, cast\n\n# Logger objects for internal tornado use\naccess_log = logging.getLogger(\"tornado.access\")\napp_log = logging.getLogger(\"tornado.application\")\ngen_log = logging.getLogger(\"tornado.general\")\n\n\ndef _stderr_supports_color() -> bool:\n    try:\n        if hasattr(sys.stderr, \"isatty\") and sys.stderr.isatty():\n            if curses:\n                curses.setupterm()\n                if curses.tigetnum(\"colors\") > 0:\n                    return True\n            elif colorama:\n                if sys.stderr is getattr(\n                    colorama.initialise, \"wrapped_stderr\", object()\n                ):\n                    return True\n    except Exception:\n        # Very broad exception handling because it's always better to\n        # fall back to non-colored logs than to break at startup.\n        pass\n    return False\n\n\ndef _safe_unicode(s: Any) -> str:\n    try:\n        return _unicode(s)\n    except UnicodeDecodeError:\n        return repr(s)\n\n\nclass LogFormatter(logging.Formatter):\n    \"\"\"Log formatter used in Tornado.\n\n    Key features of this formatter are:\n\n    * Color support when logging to a terminal that supports it.\n    * Timestamps on every log line.\n    * Robust against str/bytes encoding problems.\n\n    This formatter is enabled automatically by\n    `tornado.options.parse_command_line` or `tornado.options.parse_config_file`\n    (unless ``--logging=none`` is used).\n\n    Color support on Windows versions that do not support ANSI color codes is\n    enabled by use of the colorama__ library. Applications that wish to use\n    this must first initialize colorama with a call to ``colorama.init``.\n    See the colorama documentation for details.\n\n    __ https://pypi.python.org/pypi/colorama\n\n    .. versionchanged:: 4.5\n       Added support for ``colorama``. Changed the constructor\n       signature to be compatible with `logging.config.dictConfig`.\n    \"\"\"\n\n    DEFAULT_FORMAT = \"%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s\"  # noqa: E501\n    DEFAULT_DATE_FORMAT = \"%y%m%d %H:%M:%S\"\n    DEFAULT_COLORS = {\n        logging.DEBUG: 4,  # Blue\n        logging.INFO: 2,  # Green\n        logging.WARNING: 3,  # Yellow\n        logging.ERROR: 1,  # Red\n        logging.CRITICAL: 5,  # Magenta\n    }\n\n    def __init__(\n        self,\n        fmt: str = DEFAULT_FORMAT,\n        datefmt: str = DEFAULT_DATE_FORMAT,\n        style: str = \"%\",\n        color: bool = True,\n        colors: dict[int, int] = DEFAULT_COLORS,\n    ) -> None:\n        r\"\"\"\n        :arg bool color: Enables color support.\n        :arg str fmt: Log message format.\n          It will be applied to the attributes dict of log records. The\n          text between ``%(color)s`` and ``%(end_color)s`` will be colored\n          depending on the level if color support is on.\n        :arg dict colors: color mappings from logging level to terminal color\n          code\n        :arg str datefmt: Datetime format.\n          Used for formatting ``(asctime)`` placeholder in ``prefix_fmt``.\n\n        .. versionchanged:: 3.2\n\n           Added ``fmt`` and ``datefmt`` arguments.\n        \"\"\"\n        logging.Formatter.__init__(self, datefmt=datefmt)\n        self._fmt = fmt\n\n        self._colors: dict[int, str] = {}\n        if color and _stderr_supports_color():\n            if curses is not None:\n                fg_color = curses.tigetstr(\"setaf\") or curses.tigetstr(\"setf\") or b\"\"\n\n                for levelno, code in colors.items():\n                    # Convert the terminal control characters from\n                    # bytes to unicode strings for easier use with the\n                    # logging module.\n                    self._colors[levelno] = unicode_type(\n                        curses.tparm(fg_color, code), \"ascii\"\n                    )\n                normal = curses.tigetstr(\"sgr0\")\n                if normal is not None:\n                    self._normal = unicode_type(normal, \"ascii\")\n                else:\n                    self._normal = \"\"\n            else:\n                # If curses is not present (currently we'll only get here for\n                # colorama on windows), assume hard-coded ANSI color codes.\n                for levelno, code in colors.items():\n                    self._colors[levelno] = \"\\033[2;3%dm\" % code\n                self._normal = \"\\033[0m\"\n        else:\n            self._normal = \"\"\n\n    def format(self, record: Any) -> str:\n        try:\n            message = record.getMessage()\n            assert isinstance(message, basestring_type)  # guaranteed by logging\n            # Encoding notes:  The logging module prefers to work with character\n            # strings, but only enforces that log messages are instances of\n            # basestring.  In python 2, non-ascii bytestrings will make\n            # their way through the logging framework until they blow up with\n            # an unhelpful decoding error (with this formatter it happens\n            # when we attach the prefix, but there are other opportunities for\n            # exceptions further along in the framework).\n            #\n            # If a byte string makes it this far, convert it to unicode to\n            # ensure it will make it out to the logs.  Use repr() as a fallback\n            # to ensure that all byte strings can be converted successfully,\n            # but don't do it by default so we don't add extra quotes to ascii\n            # bytestrings.  This is a bit of a hacky place to do this, but\n            # it's worth it since the encoding errors that would otherwise\n            # result are so useless (and tornado is fond of using utf8-encoded\n            # byte strings wherever possible).\n            record.message = _safe_unicode(message)\n        except Exception as e:\n            record.message = f\"Bad message ({e!r}): {record.__dict__!r}\"\n\n        record.asctime = self.formatTime(record, cast(str, self.datefmt))\n\n        if record.levelno in self._colors:\n            record.color = self._colors[record.levelno]\n            record.end_color = self._normal\n        else:\n            record.color = record.end_color = \"\"\n\n        formatted = self._fmt % record.__dict__\n\n        if record.exc_info:\n            if not record.exc_text:\n                record.exc_text = self.formatException(record.exc_info)\n        if record.exc_text:\n            # exc_text contains multiple lines.  We need to _safe_unicode\n            # each line separately so that non-utf8 bytes don't cause\n            # all the newlines to turn into '\\n'.\n            lines = [formatted.rstrip()]\n            lines.extend(_safe_unicode(ln) for ln in record.exc_text.split(\"\\n\"))\n            formatted = \"\\n\".join(lines)\n        return formatted.replace(\"\\n\", \"\\n    \")\n\n\ndef enable_pretty_logging(\n    options: Any = None, logger: logging.Logger | None = None\n) -> None:\n    \"\"\"Turns on formatted logging output as configured.\n\n    This is called automatically by `tornado.options.parse_command_line`\n    and `tornado.options.parse_config_file`.\n    \"\"\"\n    if options is None:\n        import tornado.options\n\n        options = tornado.options.options\n    if options.logging is None or options.logging.lower() == \"none\":\n        return\n    if logger is None:\n        logger = logging.getLogger()\n    logger.setLevel(getattr(logging, options.logging.upper()))\n    if options.log_file_prefix:\n        rotate_mode = options.log_rotate_mode\n        if rotate_mode == \"size\":\n            channel: logging.Handler = logging.handlers.RotatingFileHandler(\n                filename=options.log_file_prefix,\n                maxBytes=options.log_file_max_size,\n                backupCount=options.log_file_num_backups,\n                encoding=\"utf-8\",\n            )\n        elif rotate_mode == \"time\":\n            channel = logging.handlers.TimedRotatingFileHandler(\n                filename=options.log_file_prefix,\n                when=options.log_rotate_when,\n                interval=options.log_rotate_interval,\n                backupCount=options.log_file_num_backups,\n                encoding=\"utf-8\",\n            )\n        else:\n            error_message = (\n                \"The value of log_rotate_mode option should be \"\n                + '\"size\" or \"time\", not \"%s\".' % rotate_mode\n            )\n            raise ValueError(error_message)\n        channel.setFormatter(LogFormatter(color=False))\n        logger.addHandler(channel)\n\n    if options.log_to_stderr or (options.log_to_stderr is None and not logger.handlers):\n        # Set up color if we are in a tty and curses is installed\n        channel = logging.StreamHandler()\n        channel.setFormatter(LogFormatter())\n        logger.addHandler(channel)\n\n\ndef define_logging_options(options: Any = None) -> None:\n    \"\"\"Add logging-related flags to ``options``.\n\n    These options are present automatically on the default options instance;\n    this method is only necessary if you have created your own `.OptionParser`.\n\n    .. versionadded:: 4.2\n        This function existed in prior versions but was broken and undocumented until 4.2.\n    \"\"\"\n    if options is None:\n        # late import to prevent cycle\n        import tornado.options\n\n        options = tornado.options.options\n    options.define(\n        \"logging\",\n        default=\"info\",\n        help=(\n            \"Set the Python log level. If 'none', tornado won't touch the \"\n            \"logging configuration.\"\n        ),\n        metavar=\"debug|info|warning|error|none\",\n    )\n    options.define(\n        \"log_to_stderr\",\n        type=bool,\n        default=None,\n        help=(\n            \"Send log output to stderr (colorized if possible). \"\n            \"By default use stderr if --log_file_prefix is not set and \"\n            \"no other logging is configured.\"\n        ),\n    )\n    options.define(\n        \"log_file_prefix\",\n        type=str,\n        default=None,\n        metavar=\"PATH\",\n        help=(\n            \"Path prefix for log files. \"\n            \"Note that if you are running multiple tornado processes, \"\n            \"log_file_prefix must be different for each of them (e.g. \"\n            \"include the port number)\"\n        ),\n    )\n    options.define(\n        \"log_file_max_size\",\n        type=int,\n        default=100 * 1000 * 1000,\n        help=\"max size of log files before rollover\",\n    )\n    options.define(\n        \"log_file_num_backups\", type=int, default=10, help=\"number of log files to keep\"\n    )\n\n    options.define(\n        \"log_rotate_when\",\n        type=str,\n        default=\"midnight\",\n        help=(\n            \"specify the type of TimedRotatingFileHandler interval \"\n            \"other options:('S', 'M', 'H', 'D', 'W0'-'W6')\"\n        ),\n    )\n    options.define(\n        \"log_rotate_interval\",\n        type=int,\n        default=1,\n        help=\"The interval value of timed rotating\",\n    )\n\n    options.define(\n        \"log_rotate_mode\",\n        type=str,\n        default=\"size\",\n        help=\"The mode of rotating files(time or size)\",\n    )\n\n    options.add_parse_callback(lambda: enable_pretty_logging(options))\n"
  },
  {
    "path": "tornado/netutil.py",
    "content": "#\n# Copyright 2011 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Miscellaneous network utility code.\"\"\"\n\nimport asyncio\nimport concurrent.futures\nimport errno\nimport os\nimport socket\nimport ssl\nimport stat\nimport sys\nfrom collections.abc import Awaitable, Callable\nfrom typing import Any\n\nfrom tornado.concurrent import dummy_executor, run_on_executor\nfrom tornado.ioloop import IOLoop\nfrom tornado.util import Configurable, errno_from_exception\n\n# Note that the naming of ssl.Purpose is confusing; the purpose\n# of a context is to authenticate the opposite side of the connection.\n_client_ssl_defaults = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)\n_server_ssl_defaults = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)\nif hasattr(ssl, \"OP_NO_COMPRESSION\"):\n    # See netutil.ssl_options_to_context\n    _client_ssl_defaults.options |= ssl.OP_NO_COMPRESSION\n    _server_ssl_defaults.options |= ssl.OP_NO_COMPRESSION\n\n# ThreadedResolver runs getaddrinfo on a thread. If the hostname is unicode,\n# getaddrinfo attempts to import encodings.idna. If this is done at\n# module-import time, the import lock is already held by the main thread,\n# leading to deadlock. Avoid it by caching the idna encoder on the main\n# thread now.\n\"foo\".encode(\"idna\")\n\n# For undiagnosed reasons, 'latin1' codec may also need to be preloaded.\n\"foo\".encode(\"latin1\")\n\n# Default backlog used when calling sock.listen()\n_DEFAULT_BACKLOG = 128\n\n\ndef bind_sockets(\n    port: int,\n    address: str | None = None,\n    family: socket.AddressFamily = socket.AF_UNSPEC,\n    backlog: int = _DEFAULT_BACKLOG,\n    flags: int | None = None,\n    reuse_port: bool = False,\n) -> list[socket.socket]:\n    \"\"\"Creates listening sockets bound to the given port and address.\n\n    Returns a list of socket objects (multiple sockets are returned if\n    the given address maps to multiple IP addresses, which is most common\n    for mixed IPv4 and IPv6 use).\n\n    Address may be either an IP address or hostname.  If it's a hostname,\n    the server will listen on all IP addresses associated with the\n    name.  Address may be an empty string or None to listen on all\n    available interfaces.  Family may be set to either `socket.AF_INET`\n    or `socket.AF_INET6` to restrict to IPv4 or IPv6 addresses, otherwise\n    both will be used if available.\n\n    The ``backlog`` argument has the same meaning as for\n    `socket.listen() <socket.socket.listen>`.\n\n    ``flags`` is a bitmask of AI_* flags to `~socket.getaddrinfo`, like\n    ``socket.AI_PASSIVE | socket.AI_NUMERICHOST``.\n\n    ``reuse_port`` option sets ``SO_REUSEPORT`` option for every socket\n    in the list. If your platform doesn't support this option ValueError will\n    be raised.\n    \"\"\"\n    if reuse_port and not hasattr(socket, \"SO_REUSEPORT\"):\n        raise ValueError(\"the platform doesn't support SO_REUSEPORT\")\n\n    sockets = []\n    if address == \"\":\n        address = None\n    if not socket.has_ipv6 and family == socket.AF_UNSPEC:\n        # Python can be compiled with --disable-ipv6, which causes\n        # operations on AF_INET6 sockets to fail, but does not\n        # automatically exclude those results from getaddrinfo\n        # results.\n        # http://bugs.python.org/issue16208\n        family = socket.AF_INET\n    if flags is None:\n        flags = socket.AI_PASSIVE\n    bound_port = None\n    unique_addresses: set = set()\n    for res in sorted(\n        socket.getaddrinfo(address, port, family, socket.SOCK_STREAM, 0, flags),\n        key=lambda x: x[0],\n    ):\n        if res in unique_addresses:\n            continue\n\n        unique_addresses.add(res)\n\n        af, socktype, proto, canonname, sockaddr = res\n        if (\n            sys.platform == \"darwin\"\n            and address == \"localhost\"\n            and af == socket.AF_INET6\n            and sockaddr[3] != 0  # type: ignore\n        ):\n            # Mac OS X includes a link-local address fe80::1%lo0 in the\n            # getaddrinfo results for 'localhost'.  However, the firewall\n            # doesn't understand that this is a local address and will\n            # prompt for access (often repeatedly, due to an apparent\n            # bug in its ability to remember granting access to an\n            # application). Skip these addresses.\n            continue\n        try:\n            sock = socket.socket(af, socktype, proto)\n        except OSError as e:\n            if errno_from_exception(e) == errno.EAFNOSUPPORT:\n                continue\n            raise\n        if os.name != \"nt\":\n            try:\n                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n            except OSError as e:\n                if errno_from_exception(e) != errno.ENOPROTOOPT:\n                    # Hurd doesn't support SO_REUSEADDR.\n                    raise\n        if reuse_port:\n            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)\n        if af == socket.AF_INET6:\n            # On linux, ipv6 sockets accept ipv4 too by default,\n            # but this makes it impossible to bind to both\n            # 0.0.0.0 in ipv4 and :: in ipv6.  On other systems,\n            # separate sockets *must* be used to listen for both ipv4\n            # and ipv6.  For consistency, always disable ipv4 on our\n            # ipv6 sockets and use a separate ipv4 socket when needed.\n            #\n            # Python 2.x on windows doesn't have IPPROTO_IPV6.\n            if hasattr(socket, \"IPPROTO_IPV6\"):\n                sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)\n\n        # automatic port allocation with port=None\n        # should bind on the same port on IPv4 and IPv6\n        host, requested_port = sockaddr[:2]\n        if requested_port == 0 and bound_port is not None:\n            sockaddr = tuple([host, bound_port] + list(sockaddr[2:]))\n\n        sock.setblocking(False)\n        try:\n            sock.bind(sockaddr)\n        except OSError as e:\n            if (\n                errno_from_exception(e) == errno.EADDRNOTAVAIL\n                and address == \"localhost\"\n                and sockaddr[0] == \"::1\"\n            ):\n                # On some systems (most notably docker with default\n                # configurations), ipv6 is partially disabled:\n                # socket.has_ipv6 is true, we can create AF_INET6\n                # sockets, and getaddrinfo(\"localhost\", ...,\n                # AF_PASSIVE) resolves to ::1, but we get an error\n                # when binding.\n                #\n                # Swallow the error, but only for this specific case.\n                # If EADDRNOTAVAIL occurs in other situations, it\n                # might be a real problem like a typo in a\n                # configuration.\n                sock.close()\n                continue\n            else:\n                raise\n        bound_port = sock.getsockname()[1]\n        sock.listen(backlog)\n        sockets.append(sock)\n    return sockets\n\n\nif hasattr(socket, \"AF_UNIX\"):\n\n    def bind_unix_socket(\n        file: str, mode: int = 0o600, backlog: int = _DEFAULT_BACKLOG\n    ) -> socket.socket:\n        \"\"\"Creates a listening unix socket.\n\n        If a socket with the given name already exists, it will be deleted.\n        If any other file with that name exists, an exception will be\n        raised.\n\n        Returns a socket object (not a list of socket objects like\n        `bind_sockets`)\n        \"\"\"\n        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n        try:\n            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        except OSError as e:\n            if errno_from_exception(e) != errno.ENOPROTOOPT:\n                # Hurd doesn't support SO_REUSEADDR\n                raise\n        sock.setblocking(False)\n        # File names comprising of an initial null-byte denote an abstract\n        # namespace, on Linux, and therefore are not subject to file system\n        # orientated processing.\n        if not file.startswith(\"\\0\"):\n            try:\n                st = os.stat(file)\n            except FileNotFoundError:\n                pass\n            else:\n                if stat.S_ISSOCK(st.st_mode):\n                    os.remove(file)\n                else:\n                    raise ValueError(\"File %s exists and is not a socket\", file)\n            sock.bind(file)\n            os.chmod(file, mode)\n        else:\n            sock.bind(file)\n        sock.listen(backlog)\n        return sock\n\n\ndef add_accept_handler(\n    sock: socket.socket, callback: Callable[[socket.socket, Any], None]\n) -> Callable[[], None]:\n    \"\"\"Adds an `.IOLoop` event handler to accept new connections on ``sock``.\n\n    When a connection is accepted, ``callback(connection, address)`` will\n    be run (``connection`` is a socket object, and ``address`` is the\n    address of the other end of the connection).  Note that this signature\n    is different from the ``callback(fd, events)`` signature used for\n    `.IOLoop` handlers.\n\n    A callable is returned which, when called, will remove the `.IOLoop`\n    event handler and stop processing further incoming connections.\n\n    .. versionchanged:: 5.0\n       The ``io_loop`` argument (deprecated since version 4.1) has been removed.\n\n    .. versionchanged:: 5.0\n       A callable is returned (``None`` was returned before).\n    \"\"\"\n    io_loop = IOLoop.current()\n    removed = [False]\n\n    def accept_handler(fd: socket.socket, events: int) -> None:\n        # More connections may come in while we're handling callbacks;\n        # to prevent starvation of other tasks we must limit the number\n        # of connections we accept at a time.  Ideally we would accept\n        # up to the number of connections that were waiting when we\n        # entered this method, but this information is not available\n        # (and rearranging this method to call accept() as many times\n        # as possible before running any callbacks would have adverse\n        # effects on load balancing in multiprocess configurations).\n        # Instead, we use the (default) listen backlog as a rough\n        # heuristic for the number of connections we can reasonably\n        # accept at once.\n        for i in range(_DEFAULT_BACKLOG):\n            if removed[0]:\n                # The socket was probably closed\n                return\n            try:\n                connection, address = sock.accept()\n            except BlockingIOError:\n                # EWOULDBLOCK indicates we have accepted every\n                # connection that is available.\n                return\n            except ConnectionAbortedError:\n                # ECONNABORTED indicates that there was a connection\n                # but it was closed while still in the accept queue.\n                # (observed on FreeBSD).\n                continue\n            callback(connection, address)\n\n    def remove_handler() -> None:\n        io_loop.remove_handler(sock)\n        removed[0] = True\n\n    io_loop.add_handler(sock, accept_handler, IOLoop.READ)\n    return remove_handler\n\n\ndef is_valid_ip(ip: str) -> bool:\n    \"\"\"Returns ``True`` if the given string is a well-formed IP address.\n\n    Supports IPv4 and IPv6.\n    \"\"\"\n    if not ip or \"\\x00\" in ip:\n        # getaddrinfo resolves empty strings to localhost, and truncates\n        # on zero bytes.\n        return False\n    try:\n        res = socket.getaddrinfo(\n            ip, 0, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_NUMERICHOST\n        )\n        return bool(res)\n    except socket.gaierror as e:\n        if e.args[0] == socket.EAI_NONAME:\n            return False\n        raise\n    except UnicodeError:\n        # `socket.getaddrinfo` will raise a UnicodeError from the\n        # `idna` decoder if the input is longer than 63 characters,\n        # even for socket.AI_NUMERICHOST.  See\n        # https://bugs.python.org/issue32958 for discussion\n        return False\n    return True\n\n\nclass Resolver(Configurable):\n    \"\"\"Configurable asynchronous DNS resolver interface.\n\n    By default, a blocking implementation is used (which simply calls\n    `socket.getaddrinfo`).  An alternative implementation can be\n    chosen with the `Resolver.configure <.Configurable.configure>`\n    class method::\n\n        Resolver.configure('tornado.netutil.ThreadedResolver')\n\n    The implementations of this interface included with Tornado are\n\n    * `tornado.netutil.DefaultLoopResolver`\n    * `tornado.netutil.DefaultExecutorResolver` (deprecated)\n    * `tornado.netutil.BlockingResolver` (deprecated)\n    * `tornado.netutil.ThreadedResolver` (deprecated)\n    * `tornado.netutil.OverrideResolver`\n    * `tornado.platform.caresresolver.CaresResolver` (deprecated)\n\n    .. versionchanged:: 5.0\n       The default implementation has changed from `BlockingResolver` to\n       `DefaultExecutorResolver`.\n\n    .. versionchanged:: 6.2\n       The default implementation has changed from `DefaultExecutorResolver` to\n       `DefaultLoopResolver`.\n    \"\"\"\n\n    @classmethod\n    def configurable_base(cls) -> type[\"Resolver\"]:\n        return Resolver\n\n    @classmethod\n    def configurable_default(cls) -> type[\"Resolver\"]:\n        return DefaultLoopResolver\n\n    def resolve(\n        self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC\n    ) -> Awaitable[list[tuple[int, Any]]]:\n        \"\"\"Resolves an address.\n\n        The ``host`` argument is a string which may be a hostname or a\n        literal IP address.\n\n        Returns a `.Future` whose result is a list of (family,\n        address) pairs, where address is a tuple suitable to pass to\n        `socket.connect <socket.socket.connect>` (i.e. a ``(host,\n        port)`` pair for IPv4; additional fields may be present for\n        IPv6). If a ``callback`` is passed, it will be run with the\n        result as an argument when it is complete.\n\n        :raises IOError: if the address cannot be resolved.\n\n        .. versionchanged:: 4.4\n           Standardized all implementations to raise `IOError`.\n\n        .. versionchanged:: 6.0 The ``callback`` argument was removed.\n           Use the returned awaitable object instead.\n\n        \"\"\"\n        raise NotImplementedError()\n\n    def close(self) -> None:\n        \"\"\"Closes the `Resolver`, freeing any resources used.\n\n        .. versionadded:: 3.1\n\n        \"\"\"\n        pass\n\n\ndef _resolve_addr(\n    host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC\n) -> list[tuple[int, Any]]:\n    # On Solaris, getaddrinfo fails if the given port is not found\n    # in /etc/services and no socket type is given, so we must pass\n    # one here.  The socket type used here doesn't seem to actually\n    # matter (we discard the one we get back in the results),\n    # so the addresses we return should still be usable with SOCK_DGRAM.\n    addrinfo = socket.getaddrinfo(host, port, family, socket.SOCK_STREAM)\n    results = []\n    for fam, socktype, proto, canonname, address in addrinfo:\n        results.append((fam, address))\n    return results  # type: ignore\n\n\nclass DefaultExecutorResolver(Resolver):\n    \"\"\"Resolver implementation using `.IOLoop.run_in_executor`.\n\n    .. versionadded:: 5.0\n\n    .. deprecated:: 6.2\n\n       Use `DefaultLoopResolver` instead.\n    \"\"\"\n\n    async def resolve(\n        self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC\n    ) -> list[tuple[int, Any]]:\n        result = await IOLoop.current().run_in_executor(\n            None, _resolve_addr, host, port, family\n        )\n        return result\n\n\nclass DefaultLoopResolver(Resolver):\n    \"\"\"Resolver implementation using `asyncio.loop.getaddrinfo`.\"\"\"\n\n    async def resolve(\n        self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC\n    ) -> list[tuple[int, Any]]:\n        # On Solaris, getaddrinfo fails if the given port is not found\n        # in /etc/services and no socket type is given, so we must pass\n        # one here.  The socket type used here doesn't seem to actually\n        # matter (we discard the one we get back in the results),\n        # so the addresses we return should still be usable with SOCK_DGRAM.\n        return [\n            (fam, address)\n            for fam, _, _, _, address in await asyncio.get_running_loop().getaddrinfo(\n                host, port, family=family, type=socket.SOCK_STREAM\n            )\n        ]\n\n\nclass ExecutorResolver(Resolver):\n    \"\"\"Resolver implementation using a `concurrent.futures.Executor`.\n\n    Use this instead of `ThreadedResolver` when you require additional\n    control over the executor being used.\n\n    The executor will be shut down when the resolver is closed unless\n    ``close_resolver=False``; use this if you want to reuse the same\n    executor elsewhere.\n\n    .. versionchanged:: 5.0\n       The ``io_loop`` argument (deprecated since version 4.1) has been removed.\n\n    .. deprecated:: 5.0\n       The default `Resolver` now uses `asyncio.loop.getaddrinfo`;\n       use that instead of this class.\n    \"\"\"\n\n    def initialize(\n        self,\n        executor: concurrent.futures.Executor | None = None,\n        close_executor: bool = True,\n    ) -> None:\n        if executor is not None:\n            self.executor = executor\n            self.close_executor = close_executor\n        else:\n            self.executor = dummy_executor\n            self.close_executor = False\n\n    def close(self) -> None:\n        if self.close_executor:\n            self.executor.shutdown()\n        self.executor = None  # type: ignore\n\n    @run_on_executor\n    def resolve(\n        self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC\n    ) -> list[tuple[int, Any]]:\n        return _resolve_addr(host, port, family)\n\n\nclass BlockingResolver(ExecutorResolver):\n    \"\"\"Default `Resolver` implementation, using `socket.getaddrinfo`.\n\n    The `.IOLoop` will be blocked during the resolution, although the\n    callback will not be run until the next `.IOLoop` iteration.\n\n    .. deprecated:: 5.0\n       The default `Resolver` now uses `.IOLoop.run_in_executor`; use that instead\n       of this class.\n    \"\"\"\n\n    def initialize(self) -> None:  # type: ignore\n        super().initialize()\n\n\nclass ThreadedResolver(ExecutorResolver):\n    \"\"\"Multithreaded non-blocking `Resolver` implementation.\n\n    The thread pool size can be configured with::\n\n        Resolver.configure('tornado.netutil.ThreadedResolver',\n                           num_threads=10)\n\n    .. versionchanged:: 3.1\n       All ``ThreadedResolvers`` share a single thread pool, whose\n       size is set by the first one to be created.\n\n    .. deprecated:: 5.0\n       The default `Resolver` now uses `.IOLoop.run_in_executor`; use that instead\n       of this class.\n    \"\"\"\n\n    _threadpool: concurrent.futures.ThreadPoolExecutor | None = None\n    _threadpool_pid: int | None = None\n\n    def initialize(self, num_threads: int = 10) -> None:  # type: ignore\n        threadpool = ThreadedResolver._create_threadpool(num_threads)\n        super().initialize(executor=threadpool, close_executor=False)\n\n    @classmethod\n    def _create_threadpool(\n        cls, num_threads: int\n    ) -> concurrent.futures.ThreadPoolExecutor:\n        pid = os.getpid()\n        if cls._threadpool_pid != pid:\n            # Threads cannot survive after a fork, so if our pid isn't what it\n            # was when we created the pool then delete it.\n            cls._threadpool = None\n        if cls._threadpool is None:\n            cls._threadpool = concurrent.futures.ThreadPoolExecutor(num_threads)\n            cls._threadpool_pid = pid\n        return cls._threadpool\n\n\nclass OverrideResolver(Resolver):\n    \"\"\"Wraps a resolver with a mapping of overrides.\n\n    This can be used to make local DNS changes (e.g. for testing)\n    without modifying system-wide settings.\n\n    The mapping can be in three formats::\n\n        {\n            # Hostname to host or ip\n            \"example.com\": \"127.0.1.1\",\n\n            # Host+port to host+port\n            (\"login.example.com\", 443): (\"localhost\", 1443),\n\n            # Host+port+address family to host+port\n            (\"login.example.com\", 443, socket.AF_INET6): (\"::1\", 1443),\n        }\n\n    .. versionchanged:: 5.0\n       Added support for host-port-family triplets.\n    \"\"\"\n\n    def initialize(self, resolver: Resolver, mapping: dict) -> None:\n        self.resolver = resolver\n        self.mapping = mapping\n\n    def close(self) -> None:\n        self.resolver.close()\n\n    def resolve(\n        self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC\n    ) -> Awaitable[list[tuple[int, Any]]]:\n        if (host, port, family) in self.mapping:\n            host, port = self.mapping[(host, port, family)]\n        elif (host, port) in self.mapping:\n            host, port = self.mapping[(host, port)]\n        elif host in self.mapping:\n            host = self.mapping[host]\n        return self.resolver.resolve(host, port, family)\n\n\n# These are the keyword arguments to ssl.wrap_socket that must be translated\n# to their SSLContext equivalents (the other arguments are still passed\n# to SSLContext.wrap_socket).\n_SSL_CONTEXT_KEYWORDS = frozenset(\n    [\"ssl_version\", \"certfile\", \"keyfile\", \"cert_reqs\", \"ca_certs\", \"ciphers\"]\n)\n\n\ndef ssl_options_to_context(\n    ssl_options: dict[str, Any] | ssl.SSLContext,\n    server_side: bool | None = None,\n) -> ssl.SSLContext:\n    \"\"\"Try to convert an ``ssl_options`` dictionary to an\n    `~ssl.SSLContext` object.\n\n    The ``ssl_options`` argument may be either an `ssl.SSLContext` object or a dictionary containing\n    keywords to be passed to ``ssl.SSLContext.wrap_socket``.  This function converts the dict form\n    to its `~ssl.SSLContext` equivalent, and may be used when a component which accepts both forms\n    needs to upgrade to the `~ssl.SSLContext` version to use features like SNI or ALPN.\n\n    .. versionchanged:: 6.2\n\n       Added server_side argument. Omitting this argument will result in a DeprecationWarning on\n       Python 3.10.\n\n    \"\"\"\n    if isinstance(ssl_options, ssl.SSLContext):\n        return ssl_options\n    assert isinstance(ssl_options, dict)\n    assert all(k in _SSL_CONTEXT_KEYWORDS for k in ssl_options), ssl_options\n    # TODO: Now that we have the server_side argument, can we switch to\n    # create_default_context or would that change behavior?\n    default_version = ssl.PROTOCOL_TLS\n    if server_side:\n        default_version = ssl.PROTOCOL_TLS_SERVER\n    elif server_side is not None:\n        default_version = ssl.PROTOCOL_TLS_CLIENT\n    context = ssl.SSLContext(ssl_options.get(\"ssl_version\", default_version))\n    if \"certfile\" in ssl_options:\n        context.load_cert_chain(\n            ssl_options[\"certfile\"], ssl_options.get(\"keyfile\", None)\n        )\n    if \"cert_reqs\" in ssl_options:\n        if ssl_options[\"cert_reqs\"] == ssl.CERT_NONE:\n            # This may have been set automatically by PROTOCOL_TLS_CLIENT but is\n            # incompatible with CERT_NONE so we must manually clear it.\n            context.check_hostname = False\n        context.verify_mode = ssl_options[\"cert_reqs\"]\n    if \"ca_certs\" in ssl_options:\n        context.load_verify_locations(ssl_options[\"ca_certs\"])\n    if \"ciphers\" in ssl_options:\n        context.set_ciphers(ssl_options[\"ciphers\"])\n    if hasattr(ssl, \"OP_NO_COMPRESSION\"):\n        # Disable TLS compression to avoid CRIME and related attacks.\n        # This constant depends on openssl version 1.0.\n        # TODO: Do we need to do this ourselves or can we trust\n        # the defaults?\n        context.options |= ssl.OP_NO_COMPRESSION\n    return context\n\n\ndef ssl_wrap_socket(\n    socket: socket.socket,\n    ssl_options: dict[str, Any] | ssl.SSLContext,\n    server_hostname: str | None = None,\n    server_side: bool | None = None,\n    **kwargs: Any,\n) -> ssl.SSLSocket:\n    \"\"\"Returns an ``ssl.SSLSocket`` wrapping the given socket.\n\n    ``ssl_options`` may be either an `ssl.SSLContext` object or a\n    dictionary (as accepted by `ssl_options_to_context`).  Additional\n    keyword arguments are passed to `ssl.SSLContext.wrap_socket`.\n\n    .. versionchanged:: 6.2\n\n       Added server_side argument. Omitting this argument will\n       result in a DeprecationWarning on Python 3.10.\n    \"\"\"\n    context = ssl_options_to_context(ssl_options, server_side=server_side)\n    if server_side is None:\n        server_side = False\n    assert ssl.HAS_SNI\n    # TODO: add a unittest for hostname validation (python added server-side SNI support in 3.4)\n    # In the meantime it can be manually tested with\n    # python3 -m tornado.httpclient https://sni.velox.ch\n    return context.wrap_socket(\n        socket, server_hostname=server_hostname, server_side=server_side, **kwargs\n    )\n"
  },
  {
    "path": "tornado/options.py",
    "content": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"A command line parsing module that lets modules define their own options.\n\nThis module is inspired by Google's `gflags\n<https://github.com/google/python-gflags>`_. The primary difference\nwith libraries such as `argparse` is that a global registry is used so\nthat options may be defined in any module (it also enables\n`tornado.log` by default). The rest of Tornado does not depend on this\nmodule, so feel free to use `argparse` or other configuration\nlibraries if you prefer them.\n\nOptions must be defined with `tornado.options.define` before use,\ngenerally at the top level of a module. The options are then\naccessible as attributes of `tornado.options.options`::\n\n    # myapp/db.py\n    from tornado.options import define, options\n\n    define(\"mysql_host\", default=\"127.0.0.1:3306\", help=\"Main user DB\")\n    define(\"memcache_hosts\", default=\"127.0.0.1:11011\", multiple=True,\n           help=\"Main user memcache servers\")\n\n    def connect():\n        db = database.Connection(options.mysql_host)\n        ...\n\n    # myapp/server.py\n    from tornado.options import define, options\n\n    define(\"port\", default=8080, help=\"port to listen on\")\n\n    def start_server():\n        app = make_app()\n        app.listen(options.port)\n\nThe ``main()`` method of your application does not need to be aware of all of\nthe options used throughout your program; they are all automatically loaded\nwhen the modules are loaded.  However, all modules that define options\nmust have been imported before the command line is parsed.\n\nYour ``main()`` method can parse the command line or parse a config file with\neither `parse_command_line` or `parse_config_file`::\n\n    import myapp.db, myapp.server\n    import tornado\n\n    if __name__ == '__main__':\n        tornado.options.parse_command_line()\n        # or\n        tornado.options.parse_config_file(\"/etc/server.conf\")\n\n.. note::\n\n   When using multiple ``parse_*`` functions, pass ``final=False`` to all\n   but the last one, or side effects may occur twice (in particular,\n   this can result in log messages being doubled).\n\n`tornado.options.options` is a singleton instance of `OptionParser`, and\nthe top-level functions in this module (`define`, `parse_command_line`, etc)\nsimply call methods on it.  You may create additional `OptionParser`\ninstances to define isolated sets of options, such as for subcommands.\n\n.. note::\n\n   By default, several options are defined that will configure the\n   standard `logging` module when `parse_command_line` or `parse_config_file`\n   are called.  If you want Tornado to leave the logging configuration\n   alone so you can manage it yourself, either pass ``--logging=none``\n   on the command line or do the following to disable it in code::\n\n       from tornado.options import options, parse_command_line\n       options.logging = None\n       parse_command_line()\n\n.. note::\n\n   `parse_command_line` or `parse_config_file` function should called after\n   logging configuration and user-defined command line flags using the\n   ``callback`` option definition, or these configurations will not take effect.\n\n.. versionchanged:: 4.3\n   Dashes and underscores are fully interchangeable in option names;\n   options can be defined, set, and read with any mix of the two.\n   Dashes are typical for command-line usage while config files require\n   underscores.\n\"\"\"\n\nimport datetime\nimport numbers\nimport os\nimport re\nimport sys\nimport textwrap\nfrom collections.abc import Callable, Iterable, Iterator\nfrom typing import (\n    Any,\n    TextIO,\n)\n\nfrom tornado.escape import _unicode, native_str\nfrom tornado.log import define_logging_options\nfrom tornado.util import basestring_type, exec_in\n\n\nclass Error(Exception):\n    \"\"\"Exception raised by errors in the options module.\"\"\"\n\n    pass\n\n\nclass OptionParser:\n    \"\"\"A collection of options, a dictionary with object-like access.\n\n    Normally accessed via static functions in the `tornado.options` module,\n    which reference a global instance.\n    \"\"\"\n\n    def __init__(self) -> None:\n        # we have to use self.__dict__ because we override setattr.\n        self.__dict__[\"_options\"] = {}\n        self.__dict__[\"_parse_callbacks\"] = []\n        self.define(\n            \"help\",\n            type=bool,\n            help=\"show this help information\",\n            callback=self._help_callback,\n        )\n\n    def _normalize_name(self, name: str) -> str:\n        return name.replace(\"_\", \"-\")\n\n    def __getattr__(self, name: str) -> Any:\n        name = self._normalize_name(name)\n        if isinstance(self._options.get(name), _Option):\n            return self._options[name].value()\n        raise AttributeError(\"Unrecognized option %r\" % name)\n\n    def __setattr__(self, name: str, value: Any) -> None:\n        name = self._normalize_name(name)\n        if isinstance(self._options.get(name), _Option):\n            return self._options[name].set(value)\n        raise AttributeError(\"Unrecognized option %r\" % name)\n\n    def __iter__(self) -> Iterator:\n        return (opt.name for opt in self._options.values())\n\n    def __contains__(self, name: str) -> bool:\n        name = self._normalize_name(name)\n        return name in self._options\n\n    def __getitem__(self, name: str) -> Any:\n        return self.__getattr__(name)\n\n    def __setitem__(self, name: str, value: Any) -> None:\n        return self.__setattr__(name, value)\n\n    def items(self) -> Iterable[tuple[str, Any]]:\n        \"\"\"An iterable of (name, value) pairs.\n\n        .. versionadded:: 3.1\n        \"\"\"\n        return [(opt.name, opt.value()) for name, opt in self._options.items()]\n\n    def groups(self) -> set[str]:\n        \"\"\"The set of option-groups created by ``define``.\n\n        .. versionadded:: 3.1\n        \"\"\"\n        return {opt.group_name for opt in self._options.values()}\n\n    def group_dict(self, group: str) -> dict[str, Any]:\n        \"\"\"The names and values of options in a group.\n\n        Useful for copying options into Application settings::\n\n            from tornado.options import define, parse_command_line, options\n\n            define('template_path', group='application')\n            define('static_path', group='application')\n\n            parse_command_line()\n\n            application = Application(\n                handlers, **options.group_dict('application'))\n\n        .. versionadded:: 3.1\n        \"\"\"\n        return {\n            opt.name: opt.value()\n            for name, opt in self._options.items()\n            if not group or group == opt.group_name\n        }\n\n    def as_dict(self) -> dict[str, Any]:\n        \"\"\"The names and values of all options.\n\n        .. versionadded:: 3.1\n        \"\"\"\n        return {opt.name: opt.value() for name, opt in self._options.items()}\n\n    def define(\n        self,\n        name: str,\n        default: Any = None,\n        type: type | None = None,\n        help: str | None = None,\n        metavar: str | None = None,\n        multiple: bool = False,\n        group: str | None = None,\n        callback: Callable[[Any], None] | None = None,\n    ) -> None:\n        \"\"\"Defines a new command line option.\n\n        ``type`` can be any of `str`, `int`, `float`, `bool`,\n        `~datetime.datetime`, or `~datetime.timedelta`. If no ``type``\n        is given but a ``default`` is, ``type`` is the type of\n        ``default``. Otherwise, ``type`` defaults to `str`.\n\n        If ``multiple`` is True, the option value is a list of ``type``\n        instead of an instance of ``type``.\n\n        ``help`` and ``metavar`` are used to construct the\n        automatically generated command line help string. The help\n        message is formatted like::\n\n           --name=METAVAR      help string\n\n        ``group`` is used to group the defined options in logical\n        groups. By default, command line options are grouped by the\n        file in which they are defined.\n\n        Command line option names must be unique globally.\n\n        If a ``callback`` is given, it will be run with the new value whenever\n        the option is changed.  This can be used to combine command-line\n        and file-based options::\n\n            define(\"config\", type=str, help=\"path to config file\",\n                   callback=lambda path: parse_config_file(path, final=False))\n\n        With this definition, options in the file specified by ``--config`` will\n        override options set earlier on the command line, but can be overridden\n        by later flags.\n\n        \"\"\"\n        normalized = self._normalize_name(name)\n        if normalized in self._options:\n            raise Error(\n                \"Option %r already defined in %s\"\n                % (normalized, self._options[normalized].file_name)\n            )\n        frame = sys._getframe(0)\n        if frame is not None:\n            options_file = frame.f_code.co_filename\n\n            # Can be called directly, or through top level define() fn, in which\n            # case, step up above that frame to look for real caller.\n            if (\n                frame.f_back is not None\n                and frame.f_back.f_code.co_filename == options_file\n                and frame.f_back.f_code.co_name == \"define\"\n            ):\n                frame = frame.f_back\n\n            assert frame.f_back is not None\n            file_name = frame.f_back.f_code.co_filename\n        else:\n            file_name = \"<unknown>\"\n        if file_name == options_file:\n            file_name = \"\"\n        if type is None:\n            if not multiple and default is not None:\n                type = default.__class__\n            else:\n                type = str\n        if group:\n            group_name: str | None = group\n        else:\n            group_name = file_name\n        option = _Option(\n            name,\n            file_name=file_name,\n            default=default,\n            type=type,\n            help=help,\n            metavar=metavar,\n            multiple=multiple,\n            group_name=group_name,\n            callback=callback,\n        )\n        self._options[normalized] = option\n\n    def parse_command_line(\n        self, args: list[str] | None = None, final: bool = True\n    ) -> list[str]:\n        \"\"\"Parses all options given on the command line (defaults to\n        `sys.argv`).\n\n        Options look like ``--option=value`` and are parsed according\n        to their ``type``. For boolean options, ``--option`` is\n        equivalent to ``--option=true``\n\n        If the option has ``multiple=True``, comma-separated values\n        are accepted. For multi-value integer options, the syntax\n        ``x:y`` is also accepted and equivalent to ``range(x, y)``.\n\n        Note that ``args[0]`` is ignored since it is the program name\n        in `sys.argv`.\n\n        We return a list of all arguments that are not parsed as options.\n\n        If ``final`` is ``False``, parse callbacks will not be run.\n        This is useful for applications that wish to combine configurations\n        from multiple sources.\n\n        \"\"\"\n        if args is None:\n            args = sys.argv\n        remaining: list[str] = []\n        for i in range(1, len(args)):\n            # All things after the last option are command line arguments\n            if not args[i].startswith(\"-\"):\n                remaining = args[i:]\n                break\n            if args[i] == \"--\":\n                remaining = args[i + 1 :]\n                break\n            arg = args[i].lstrip(\"-\")\n            name, equals, value = arg.partition(\"=\")\n            name = self._normalize_name(name)\n            if name not in self._options:\n                self.print_help()\n                raise Error(\"Unrecognized command line option: %r\" % name)\n            option = self._options[name]\n            if not equals:\n                if option.type == bool:\n                    value = \"true\"\n                else:\n                    raise Error(\"Option %r requires a value\" % name)\n            option.parse(value)\n\n        if final:\n            self.run_parse_callbacks()\n\n        return remaining\n\n    def parse_config_file(self, path: str, final: bool = True) -> None:\n        \"\"\"Parses and loads the config file at the given path.\n\n        The config file contains Python code that will be executed (so\n        it is **not safe** to use untrusted config files). Anything in\n        the global namespace that matches a defined option will be\n        used to set that option's value.\n\n        Options may either be the specified type for the option or\n        strings (in which case they will be parsed the same way as in\n        `.parse_command_line`)\n\n        Example (using the options defined in the top-level docs of\n        this module)::\n\n            port = 80\n            mysql_host = 'mydb.example.com:3306'\n            # Both lists and comma-separated strings are allowed for\n            # multiple=True.\n            memcache_hosts = ['cache1.example.com:11011',\n                              'cache2.example.com:11011']\n            memcache_hosts = 'cache1.example.com:11011,cache2.example.com:11011'\n\n        If ``final`` is ``False``, parse callbacks will not be run.\n        This is useful for applications that wish to combine configurations\n        from multiple sources.\n\n        .. note::\n\n            `tornado.options` is primarily a command-line library.\n            Config file support is provided for applications that wish\n            to use it, but applications that prefer config files may\n            wish to look at other libraries instead.\n\n        .. versionchanged:: 4.1\n           Config files are now always interpreted as utf-8 instead of\n           the system default encoding.\n\n        .. versionchanged:: 4.4\n           The special variable ``__file__`` is available inside config\n           files, specifying the absolute path to the config file itself.\n\n        .. versionchanged:: 5.1\n           Added the ability to set options via strings in config files.\n\n        \"\"\"\n        config = {\"__file__\": os.path.abspath(path)}\n        with open(path, \"rb\") as f:\n            exec_in(native_str(f.read()), config, config)\n        for name in config:\n            normalized = self._normalize_name(name)\n            if normalized in self._options:\n                option = self._options[normalized]\n                if option.multiple:\n                    if not isinstance(config[name], (list, str)):\n                        raise Error(\n                            \"Option %r is required to be a list of %s \"\n                            \"or a comma-separated string\"\n                            % (option.name, option.type.__name__)\n                        )\n\n                if type(config[name]) is str and (\n                    option.type is not str or option.multiple\n                ):\n                    option.parse(config[name])\n                else:\n                    option.set(config[name])\n\n        if final:\n            self.run_parse_callbacks()\n\n    def print_help(self, file: TextIO | None = None) -> None:\n        \"\"\"Prints all the command line options to stderr (or another file).\"\"\"\n        if file is None:\n            file = sys.stderr\n        print(\"Usage: %s [OPTIONS]\" % sys.argv[0], file=file)\n        print(\"\\nOptions:\\n\", file=file)\n        by_group: dict[str, list[_Option]] = {}\n        for option in self._options.values():\n            by_group.setdefault(option.group_name, []).append(option)\n\n        for filename, o in sorted(by_group.items()):\n            if filename:\n                print(\"\\n%s options:\\n\" % os.path.normpath(filename), file=file)\n            o.sort(key=lambda option: option.name)\n            for option in o:\n                # Always print names with dashes in a CLI context.\n                prefix = self._normalize_name(option.name)\n                if option.metavar:\n                    prefix += \"=\" + option.metavar\n                description = option.help or \"\"\n                if option.default is not None and option.default != \"\":\n                    description += \" (default %s)\" % option.default\n                lines = textwrap.wrap(description, 79 - 35)\n                if len(prefix) > 30 or len(lines) == 0:\n                    lines.insert(0, \"\")\n                print(\"  --%-30s %s\" % (prefix, lines[0]), file=file)\n                for line in lines[1:]:\n                    print(\"%-34s %s\" % (\" \", line), file=file)\n        print(file=file)\n\n    def _help_callback(self, value: bool) -> None:\n        if value:\n            self.print_help()\n            sys.exit(0)\n\n    def add_parse_callback(self, callback: Callable[[], None]) -> None:\n        \"\"\"Adds a parse callback, to be invoked when option parsing is done.\"\"\"\n        self._parse_callbacks.append(callback)\n\n    def run_parse_callbacks(self) -> None:\n        for callback in self._parse_callbacks:\n            callback()\n\n    def mockable(self) -> \"_Mockable\":\n        \"\"\"Returns a wrapper around self that is compatible with\n        `unittest.mock.patch`.\n\n        The `unittest.mock.patch` function is incompatible with objects like ``options`` that\n        override ``__getattr__`` and ``__setattr__``.  This function returns an object that can be\n        used with `mock.patch.object <unittest.mock.patch.object>` to modify option values::\n\n            with mock.patch.object(options.mockable(), 'name', value):\n                assert options.name == value\n        \"\"\"\n        return _Mockable(self)\n\n\nclass _Mockable:\n    \"\"\"`mock.patch` compatible wrapper for `OptionParser`.\n\n    As of ``mock`` version 1.0.1, when an object uses ``__getattr__``\n    hooks instead of ``__dict__``, ``patch.__exit__`` tries to delete\n    the attribute it set instead of setting a new one (assuming that\n    the object does not capture ``__setattr__``, so the patch\n    created a new attribute in ``__dict__``).\n\n    _Mockable's getattr and setattr pass through to the underlying\n    OptionParser, and delattr undoes the effect of a previous setattr.\n    \"\"\"\n\n    def __init__(self, options: OptionParser) -> None:\n        # Modify __dict__ directly to bypass __setattr__\n        self.__dict__[\"_options\"] = options\n        self.__dict__[\"_originals\"] = {}\n\n    def __getattr__(self, name: str) -> Any:\n        return getattr(self._options, name)\n\n    def __setattr__(self, name: str, value: Any) -> None:\n        assert name not in self._originals, \"don't reuse mockable objects\"\n        self._originals[name] = getattr(self._options, name)\n        setattr(self._options, name, value)\n\n    def __delattr__(self, name: str) -> None:\n        setattr(self._options, name, self._originals.pop(name))\n\n\nclass _Option:\n    # This class could almost be made generic, but the way the types\n    # interact with the multiple argument makes this tricky. (default\n    # and the callback use List[T], but type is still Type[T]).\n    UNSET = object()\n\n    def __init__(\n        self,\n        name: str,\n        default: Any = None,\n        type: type | None = None,\n        help: str | None = None,\n        metavar: str | None = None,\n        multiple: bool = False,\n        file_name: str | None = None,\n        group_name: str | None = None,\n        callback: Callable[[Any], None] | None = None,\n    ) -> None:\n        if default is None and multiple:\n            default = []\n        self.name = name\n        if type is None:\n            raise ValueError(\"type must not be None\")\n        self.type = type\n        self.help = help\n        self.metavar = metavar\n        self.multiple = multiple\n        self.file_name = file_name\n        self.group_name = group_name\n        self.callback = callback\n        self.default = default\n        self._value: Any = _Option.UNSET\n\n    def value(self) -> Any:\n        return self.default if self._value is _Option.UNSET else self._value\n\n    def parse(self, value: str) -> Any:\n        _parse: Callable[[str], Any] = {\n            datetime.datetime: self._parse_datetime,\n            datetime.timedelta: self._parse_timedelta,\n            bool: self._parse_bool,\n            basestring_type: self._parse_string,\n        }.get(self.type, self.type)\n        if self.multiple:\n            self._value = []\n            for part in value.split(\",\"):\n                if issubclass(self.type, numbers.Integral):\n                    # allow ranges of the form X:Y (inclusive at both ends)\n                    lo_str, _, hi_str = part.partition(\":\")\n                    lo = _parse(lo_str)\n                    hi = _parse(hi_str) if hi_str else lo\n                    self._value.extend(range(lo, hi + 1))\n                else:\n                    self._value.append(_parse(part))\n        else:\n            self._value = _parse(value)\n        if self.callback is not None:\n            self.callback(self._value)\n        return self.value()\n\n    def set(self, value: Any) -> None:\n        if self.multiple:\n            if not isinstance(value, list):\n                raise Error(\n                    \"Option %r is required to be a list of %s\"\n                    % (self.name, self.type.__name__)\n                )\n            for item in value:\n                if item is not None and not isinstance(item, self.type):\n                    raise Error(\n                        \"Option %r is required to be a list of %s\"\n                        % (self.name, self.type.__name__)\n                    )\n        else:\n            if value is not None and not isinstance(value, self.type):\n                raise Error(\n                    \"Option %r is required to be a %s (%s given)\"\n                    % (self.name, self.type.__name__, type(value))\n                )\n        self._value = value\n        if self.callback is not None:\n            self.callback(self._value)\n\n    # Supported date/time formats in our options\n    _DATETIME_FORMATS = [\n        \"%a %b %d %H:%M:%S %Y\",\n        \"%Y-%m-%d %H:%M:%S\",\n        \"%Y-%m-%d %H:%M\",\n        \"%Y-%m-%dT%H:%M\",\n        \"%Y%m%d %H:%M:%S\",\n        \"%Y%m%d %H:%M\",\n        \"%Y-%m-%d\",\n        \"%Y%m%d\",\n        \"%H:%M:%S\",\n        \"%H:%M\",\n    ]\n\n    def _parse_datetime(self, value: str) -> datetime.datetime:\n        for format in self._DATETIME_FORMATS:\n            try:\n                return datetime.datetime.strptime(value, format)\n            except ValueError:\n                pass\n        raise Error(\"Unrecognized date/time format: %r\" % value)\n\n    _TIMEDELTA_ABBREV_DICT = {\n        \"h\": \"hours\",\n        \"m\": \"minutes\",\n        \"min\": \"minutes\",\n        \"s\": \"seconds\",\n        \"sec\": \"seconds\",\n        \"ms\": \"milliseconds\",\n        \"us\": \"microseconds\",\n        \"d\": \"days\",\n        \"w\": \"weeks\",\n    }\n\n    _FLOAT_PATTERN = r\"[-+]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][-+]?\\d+)?\"\n\n    _TIMEDELTA_PATTERN = re.compile(\n        r\"\\s*(%s)\\s*(\\w*)\\s*\" % _FLOAT_PATTERN, re.IGNORECASE\n    )\n\n    def _parse_timedelta(self, value: str) -> datetime.timedelta:\n        try:\n            sum = datetime.timedelta()\n            start = 0\n            while start < len(value):\n                m = self._TIMEDELTA_PATTERN.match(value, start)\n                if not m:\n                    raise Exception()\n                num = float(m.group(1))\n                units = m.group(2) or \"seconds\"\n                units = self._TIMEDELTA_ABBREV_DICT.get(units, units)\n\n                sum += datetime.timedelta(**{units: num})\n                start = m.end()\n            return sum\n        except Exception:\n            raise\n\n    def _parse_bool(self, value: str) -> bool:\n        return value.lower() not in (\"false\", \"0\", \"f\")\n\n    def _parse_string(self, value: str) -> str:\n        return _unicode(value)\n\n\noptions = OptionParser()\n\"\"\"Global options object.\n\nAll defined options are available as attributes on this object.\n\"\"\"\n\n\ndef define(\n    name: str,\n    default: Any = None,\n    type: type | None = None,\n    help: str | None = None,\n    metavar: str | None = None,\n    multiple: bool = False,\n    group: str | None = None,\n    callback: Callable[[Any], None] | None = None,\n) -> None:\n    \"\"\"Defines an option in the global namespace.\n\n    See `OptionParser.define`.\n    \"\"\"\n    return options.define(\n        name,\n        default=default,\n        type=type,\n        help=help,\n        metavar=metavar,\n        multiple=multiple,\n        group=group,\n        callback=callback,\n    )\n\n\ndef parse_command_line(args: list[str] | None = None, final: bool = True) -> list[str]:\n    \"\"\"Parses global options from the command line.\n\n    See `OptionParser.parse_command_line`.\n    \"\"\"\n    return options.parse_command_line(args, final=final)\n\n\ndef parse_config_file(path: str, final: bool = True) -> None:\n    \"\"\"Parses global options from a config file.\n\n    See `OptionParser.parse_config_file`.\n    \"\"\"\n    return options.parse_config_file(path, final=final)\n\n\ndef print_help(file: TextIO | None = None) -> None:\n    \"\"\"Prints all the command line options to stderr (or another file).\n\n    See `OptionParser.print_help`.\n    \"\"\"\n    return options.print_help(file)\n\n\ndef add_parse_callback(callback: Callable[[], None]) -> None:\n    \"\"\"Adds a parse callback, to be invoked when option parsing is done.\n\n    See `OptionParser.add_parse_callback`\n    \"\"\"\n    options.add_parse_callback(callback)\n\n\n# Default options\ndefine_logging_options(options)\n"
  },
  {
    "path": "tornado/platform/__init__.py",
    "content": ""
  },
  {
    "path": "tornado/platform/asyncio.py",
    "content": "\"\"\"Bridges between the `asyncio` module and Tornado IOLoop.\n\n.. versionadded:: 3.2\n\nThis module integrates Tornado with the ``asyncio`` module introduced\nin Python 3.4. This makes it possible to combine the two libraries on\nthe same event loop.\n\n.. deprecated:: 5.0\n\n   While the code in this module is still used, it is now enabled\n   automatically when `asyncio` is available, so applications should\n   no longer need to refer to this module directly.\n\n.. note::\n\n   Tornado is designed to use a selector-based event loop. On Windows,\n   where a proactor-based event loop has been the default since Python 3.8,\n   a selector event loop is emulated by running ``select`` on a separate thread.\n   Configuring ``asyncio`` to use a selector event loop may improve performance\n   of Tornado (but may reduce performance of other ``asyncio``-based libraries\n   in the same process).\n\"\"\"\n\nimport asyncio\nimport atexit\nimport concurrent.futures\nimport contextvars\nimport errno\nimport functools\nimport select\nimport socket\nimport sys\nimport threading\nimport typing\nimport warnings\nfrom collections.abc import Callable\nfrom typing import (\n    Any,\n    Protocol,\n    TypeVar,\n    Union,\n)\n\nfrom tornado.gen import convert_yielded\nfrom tornado.ioloop import IOLoop, _Selectable\n\nif typing.TYPE_CHECKING:\n    from typing_extensions import TypeVarTuple, Unpack\n\n\nclass _HasFileno(Protocol):\n    def fileno(self) -> int:\n        pass\n\n\n_FileDescriptorLike = Union[int, _HasFileno]\n\n_T = TypeVar(\"_T\")\n\nif typing.TYPE_CHECKING:\n    _Ts = TypeVarTuple(\"_Ts\")\n\n# Collection of selector thread event loops to shut down on exit.\n_selector_loops: set[\"SelectorThread\"] = set()\n\n\ndef _atexit_callback() -> None:\n    for loop in _selector_loops:\n        with loop._select_cond:\n            loop._closing_selector = True\n            loop._select_cond.notify()\n        try:\n            loop._waker_w.send(b\"a\")\n        except BlockingIOError:\n            pass\n        if loop._thread is not None:\n            # If we don't join our (daemon) thread here, we may get a deadlock\n            # during interpreter shutdown. I don't really understand why. This\n            # deadlock happens every time in CI (both travis and appveyor) but\n            # I've never been able to reproduce locally.\n            loop._thread.join()\n    _selector_loops.clear()\n\n\natexit.register(_atexit_callback)\n\n\nclass BaseAsyncIOLoop(IOLoop):\n    def initialize(  # type: ignore\n        self, asyncio_loop: asyncio.AbstractEventLoop, **kwargs: Any\n    ) -> None:\n        # asyncio_loop is always the real underlying IOLoop. This is used in\n        # ioloop.py to maintain the asyncio-to-ioloop mappings.\n        self.asyncio_loop = asyncio_loop\n        # selector_loop is an event loop that implements the add_reader family of\n        # methods. Usually the same as asyncio_loop but differs on platforms such\n        # as windows where the default event loop does not implement these methods.\n        self.selector_loop = asyncio_loop\n        if hasattr(asyncio, \"ProactorEventLoop\") and isinstance(\n            asyncio_loop, asyncio.ProactorEventLoop\n        ):\n            # Ignore this line for mypy because the abstract method checker\n            # doesn't understand dynamic proxies.\n            self.selector_loop = AddThreadSelectorEventLoop(asyncio_loop)  # type: ignore\n        # Maps fd to (fileobj, handler function) pair (as in IOLoop.add_handler)\n        self.handlers: dict[int, tuple[int | _Selectable, Callable]] = {}\n        # Set of fds listening for reads/writes\n        self.readers: set[int] = set()\n        self.writers: set[int] = set()\n        self.closing = False\n        # If an asyncio loop was closed through an asyncio interface\n        # instead of IOLoop.close(), we'd never hear about it and may\n        # have left a dangling reference in our map. In case an\n        # application (or, more likely, a test suite) creates and\n        # destroys a lot of event loops in this way, check here to\n        # ensure that we don't have a lot of dead loops building up in\n        # the map.\n        #\n        # TODO(bdarnell): consider making self.asyncio_loop a weakref\n        # for AsyncIOMainLoop and make _ioloop_for_asyncio a\n        # WeakKeyDictionary.\n        for loop in IOLoop._ioloop_for_asyncio.copy():\n            if loop.is_closed():\n                try:\n                    del IOLoop._ioloop_for_asyncio[loop]\n                except KeyError:\n                    pass\n\n        # Make sure we don't already have an IOLoop for this asyncio loop\n        existing_loop = IOLoop._ioloop_for_asyncio.setdefault(asyncio_loop, self)\n        if existing_loop is not self:\n            raise RuntimeError(\n                f\"IOLoop {existing_loop} already associated with asyncio loop {asyncio_loop}\"\n            )\n\n        super().initialize(**kwargs)\n\n    def close(self, all_fds: bool = False) -> None:\n        self.closing = True\n        for fd in list(self.handlers):\n            fileobj, handler_func = self.handlers[fd]\n            self.remove_handler(fd)\n            if all_fds:\n                self.close_fd(fileobj)\n        # Remove the mapping before closing the asyncio loop. If this\n        # happened in the other order, we could race against another\n        # initialize() call which would see the closed asyncio loop,\n        # assume it was closed from the asyncio side, and do this\n        # cleanup for us, leading to a KeyError.\n        del IOLoop._ioloop_for_asyncio[self.asyncio_loop]\n        if self.selector_loop is not self.asyncio_loop:\n            self.selector_loop.close()\n        self.asyncio_loop.close()\n\n    def add_handler(\n        self, fd: int | _Selectable, handler: Callable[..., None], events: int\n    ) -> None:\n        fd, fileobj = self.split_fd(fd)\n        if fd in self.handlers:\n            raise ValueError(\"fd %s added twice\" % fd)\n        self.handlers[fd] = (fileobj, handler)\n        if events & IOLoop.READ:\n            self.selector_loop.add_reader(fd, self._handle_events, fd, IOLoop.READ)\n            self.readers.add(fd)\n        if events & IOLoop.WRITE:\n            self.selector_loop.add_writer(fd, self._handle_events, fd, IOLoop.WRITE)\n            self.writers.add(fd)\n\n    def update_handler(self, fd: int | _Selectable, events: int) -> None:\n        fd, fileobj = self.split_fd(fd)\n        if events & IOLoop.READ:\n            if fd not in self.readers:\n                self.selector_loop.add_reader(fd, self._handle_events, fd, IOLoop.READ)\n                self.readers.add(fd)\n        else:\n            if fd in self.readers:\n                self.selector_loop.remove_reader(fd)\n                self.readers.remove(fd)\n        if events & IOLoop.WRITE:\n            if fd not in self.writers:\n                self.selector_loop.add_writer(fd, self._handle_events, fd, IOLoop.WRITE)\n                self.writers.add(fd)\n        else:\n            if fd in self.writers:\n                self.selector_loop.remove_writer(fd)\n                self.writers.remove(fd)\n\n    def remove_handler(self, fd: int | _Selectable) -> None:\n        fd, fileobj = self.split_fd(fd)\n        if fd not in self.handlers:\n            return\n        if fd in self.readers:\n            self.selector_loop.remove_reader(fd)\n            self.readers.remove(fd)\n        if fd in self.writers:\n            self.selector_loop.remove_writer(fd)\n            self.writers.remove(fd)\n        del self.handlers[fd]\n\n    def _handle_events(self, fd: int, events: int) -> None:\n        fileobj, handler_func = self.handlers[fd]\n        handler_func(fileobj, events)\n\n    def start(self) -> None:\n        self.asyncio_loop.run_forever()\n\n    def stop(self) -> None:\n        self.asyncio_loop.stop()\n\n    def call_at(\n        self, when: float, callback: Callable, *args: Any, **kwargs: Any\n    ) -> object:\n        # asyncio.call_at supports *args but not **kwargs, so bind them here.\n        # We do not synchronize self.time and asyncio_loop.time, so\n        # convert from absolute to relative.\n        return self.asyncio_loop.call_later(\n            max(0, when - self.time()),\n            self._run_callback,\n            functools.partial(callback, *args, **kwargs),\n        )\n\n    def remove_timeout(self, timeout: object) -> None:\n        timeout.cancel()  # type: ignore\n\n    def add_callback(self, callback: Callable, *args: Any, **kwargs: Any) -> None:\n        try:\n            if asyncio.get_running_loop() is self.asyncio_loop:\n                call_soon = self.asyncio_loop.call_soon\n            else:\n                call_soon = self.asyncio_loop.call_soon_threadsafe\n        except RuntimeError:\n            call_soon = self.asyncio_loop.call_soon_threadsafe\n\n        try:\n            call_soon(self._run_callback, functools.partial(callback, *args, **kwargs))\n        except RuntimeError:\n            # \"Event loop is closed\". Swallow the exception for\n            # consistency with PollIOLoop (and logical consistency\n            # with the fact that we can't guarantee that an\n            # add_callback that completes without error will\n            # eventually execute).\n            pass\n        except AttributeError:\n            # ProactorEventLoop may raise this instead of RuntimeError\n            # if call_soon_threadsafe races with a call to close().\n            # Swallow it too for consistency.\n            pass\n\n    def add_callback_from_signal(\n        self, callback: Callable, *args: Any, **kwargs: Any\n    ) -> None:\n        warnings.warn(\"add_callback_from_signal is deprecated\", DeprecationWarning)\n        try:\n            self.asyncio_loop.call_soon_threadsafe(\n                self._run_callback, functools.partial(callback, *args, **kwargs)\n            )\n        except RuntimeError:\n            pass\n\n    def run_in_executor(\n        self,\n        executor: concurrent.futures.Executor | None,\n        func: Callable[..., _T],\n        *args: Any,\n    ) -> \"asyncio.Future[_T]\":\n        return self.asyncio_loop.run_in_executor(executor, func, *args)\n\n    def set_default_executor(self, executor: concurrent.futures.Executor) -> None:\n        return self.asyncio_loop.set_default_executor(executor)\n\n\nclass AsyncIOMainLoop(BaseAsyncIOLoop):\n    \"\"\"``AsyncIOMainLoop`` creates an `.IOLoop` that corresponds to the\n    current ``asyncio`` event loop (i.e. the one returned by\n    ``asyncio.get_event_loop()``).\n\n    .. deprecated:: 5.0\n\n       Now used automatically when appropriate; it is no longer necessary\n       to refer to this class directly.\n\n    .. versionchanged:: 5.0\n\n       Closing an `AsyncIOMainLoop` now closes the underlying asyncio loop.\n    \"\"\"\n\n    def initialize(self, **kwargs: Any) -> None:  # type: ignore\n        super().initialize(asyncio.get_event_loop(), **kwargs)\n\n    def _make_current(self) -> None:\n        # AsyncIOMainLoop already refers to the current asyncio loop so\n        # nothing to do here.\n        pass\n\n\nclass AsyncIOLoop(BaseAsyncIOLoop):\n    \"\"\"``AsyncIOLoop`` is an `.IOLoop` that runs on an ``asyncio`` event loop.\n    This class follows the usual Tornado semantics for creating new\n    ``IOLoops``; these loops are not necessarily related to the\n    ``asyncio`` default event loop.\n\n    Each ``AsyncIOLoop`` creates a new ``asyncio.EventLoop``; this object\n    can be accessed with the ``asyncio_loop`` attribute.\n\n    .. versionchanged:: 6.2\n\n       Support explicit ``asyncio_loop`` argument\n       for specifying the asyncio loop to attach to,\n       rather than always creating a new one with the default policy.\n\n    .. versionchanged:: 5.0\n\n       When an ``AsyncIOLoop`` becomes the current `.IOLoop`, it also sets\n       the current `asyncio` event loop.\n\n    .. deprecated:: 5.0\n\n       Now used automatically when appropriate; it is no longer necessary\n       to refer to this class directly.\n    \"\"\"\n\n    def initialize(self, **kwargs: Any) -> None:  # type: ignore\n        self.is_current = False\n        loop = None\n        if \"asyncio_loop\" not in kwargs:\n            kwargs[\"asyncio_loop\"] = loop = asyncio.new_event_loop()\n        try:\n            super().initialize(**kwargs)\n        except Exception:\n            # If initialize() does not succeed (taking ownership of the loop),\n            # we have to close it.\n            if loop is not None:\n                loop.close()\n            raise\n\n    def close(self, all_fds: bool = False) -> None:\n        if self.is_current:\n            self._clear_current()\n        super().close(all_fds=all_fds)\n\n    def _make_current(self) -> None:\n        if not self.is_current:\n            try:\n                self.old_asyncio = asyncio.get_event_loop()\n            except (RuntimeError, AssertionError):\n                self.old_asyncio = None  # type: ignore\n            self.is_current = True\n        asyncio.set_event_loop(self.asyncio_loop)\n\n    def _clear_current_hook(self) -> None:\n        if self.is_current:\n            asyncio.set_event_loop(self.old_asyncio)\n            self.is_current = False\n\n\ndef to_tornado_future(asyncio_future: asyncio.Future) -> asyncio.Future:\n    \"\"\"Convert an `asyncio.Future` to a `tornado.concurrent.Future`.\n\n    .. versionadded:: 4.1\n\n    .. deprecated:: 5.0\n       Tornado ``Futures`` have been merged with `asyncio.Future`,\n       so this method is now a no-op.\n    \"\"\"\n    return asyncio_future\n\n\ndef to_asyncio_future(tornado_future: asyncio.Future) -> asyncio.Future:\n    \"\"\"Convert a Tornado yieldable object to an `asyncio.Future`.\n\n    .. versionadded:: 4.1\n\n    .. versionchanged:: 4.3\n       Now accepts any yieldable object, not just\n       `tornado.concurrent.Future`.\n\n    .. deprecated:: 5.0\n       Tornado ``Futures`` have been merged with `asyncio.Future`,\n       so this method is now equivalent to `tornado.gen.convert_yielded`.\n    \"\"\"\n    return convert_yielded(tornado_future)\n\n\n_AnyThreadEventLoopPolicy = None\n\n\ndef __getattr__(name: str) -> typing.Any:\n    # The event loop policy system is deprecated in Python 3.14; simply accessing\n    # the name asyncio.DefaultEventLoopPolicy will raise a warning. Lazily create\n    # the AnyThreadEventLoopPolicy class so that the warning is only raised if\n    # the policy is used.\n    if name != \"AnyThreadEventLoopPolicy\":\n        raise AttributeError(f\"module {__name__!r} has no attribute {name!r}\")\n\n    global _AnyThreadEventLoopPolicy\n    if _AnyThreadEventLoopPolicy is None:\n        if sys.platform == \"win32\" and hasattr(\n            asyncio, \"WindowsSelectorEventLoopPolicy\"\n        ):\n            # \"Any thread\" and \"selector\" should be orthogonal, but there's not a clean\n            # interface for composing policies so pick the right base.\n            _BasePolicy = asyncio.WindowsSelectorEventLoopPolicy  # type: ignore\n        else:\n            _BasePolicy = asyncio.DefaultEventLoopPolicy\n\n        class AnyThreadEventLoopPolicy(_BasePolicy):  # type: ignore\n            \"\"\"Event loop policy that allows loop creation on any thread.\n\n            The default `asyncio` event loop policy only automatically creates\n            event loops in the main threads. Other threads must create event\n            loops explicitly or `asyncio.get_event_loop` (and therefore\n            `.IOLoop.current`) will fail. Installing this policy allows event\n            loops to be created automatically on any thread, matching the\n            behavior of Tornado versions prior to 5.0 (or 5.0 on Python 2).\n\n            Usage::\n\n                asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy())\n\n            .. versionadded:: 5.0\n\n            .. deprecated:: 6.2\n\n                ``AnyThreadEventLoopPolicy`` affects the implicit creation\n                of an event loop, which is deprecated in Python 3.10 and\n                will be removed in a future version of Python. At that time\n                ``AnyThreadEventLoopPolicy`` will no longer be useful.\n                If you are relying on it, use `asyncio.new_event_loop`\n                or `asyncio.run` explicitly in any non-main threads that\n                need event loops.\n            \"\"\"\n\n            def __init__(self) -> None:\n                super().__init__()\n                warnings.warn(\n                    \"AnyThreadEventLoopPolicy is deprecated, use asyncio.run \"\n                    \"or asyncio.new_event_loop instead\",\n                    DeprecationWarning,\n                    stacklevel=2,\n                )\n\n            def get_event_loop(self) -> asyncio.AbstractEventLoop:\n                try:\n                    return super().get_event_loop()\n                except RuntimeError:\n                    # \"There is no current event loop in thread %r\"\n                    loop = self.new_event_loop()\n                    self.set_event_loop(loop)\n                    return loop\n\n        _AnyThreadEventLoopPolicy = AnyThreadEventLoopPolicy\n\n    return _AnyThreadEventLoopPolicy\n\n\nclass SelectorThread:\n    \"\"\"Define ``add_reader`` methods to be called in a background select thread.\n\n    Instances of this class start a second thread to run a selector.\n    This thread is completely hidden from the user;\n    all callbacks are run on the wrapped event loop's thread.\n\n    Typically used via ``AddThreadSelectorEventLoop``,\n    but can be attached to a running asyncio loop.\n    \"\"\"\n\n    _closed = False\n\n    def __init__(self, real_loop: asyncio.AbstractEventLoop) -> None:\n        self._main_thread_ctx = contextvars.copy_context()\n\n        self._real_loop = real_loop\n\n        self._select_cond = threading.Condition()\n        self._select_args: None | (\n            tuple[list[_FileDescriptorLike], list[_FileDescriptorLike]]\n        ) = None\n        self._closing_selector = False\n        self._thread: threading.Thread | None = None\n        self._thread_manager_handle = self._thread_manager()\n\n        async def thread_manager_anext() -> None:\n            # the anext builtin wasn't added until 3.10. We just need to iterate\n            # this generator one step.\n            await self._thread_manager_handle.__anext__()\n\n        # When the loop starts, start the thread. Not too soon because we can't\n        # clean up if we get to this point but the event loop is closed without\n        # starting.\n        self._real_loop.call_soon(\n            lambda: self._real_loop.create_task(thread_manager_anext()),\n            context=self._main_thread_ctx,\n        )\n\n        self._readers: dict[_FileDescriptorLike, Callable] = {}\n        self._writers: dict[_FileDescriptorLike, Callable] = {}\n\n        # Writing to _waker_w will wake up the selector thread, which\n        # watches for _waker_r to be readable.\n        self._waker_r, self._waker_w = socket.socketpair()\n        self._waker_r.setblocking(False)\n        self._waker_w.setblocking(False)\n        _selector_loops.add(self)\n        self.add_reader(self._waker_r, self._consume_waker)\n\n    def close(self) -> None:\n        if self._closed:\n            return\n        with self._select_cond:\n            self._closing_selector = True\n            self._select_cond.notify()\n        self._wake_selector()\n        if self._thread is not None:\n            self._thread.join()\n        _selector_loops.discard(self)\n        self.remove_reader(self._waker_r)\n        self._waker_r.close()\n        self._waker_w.close()\n        self._closed = True\n\n    async def _thread_manager(self) -> typing.AsyncGenerator[None, None]:\n        # Create a thread to run the select system call. We manage this thread\n        # manually so we can trigger a clean shutdown from an atexit hook. Note\n        # that due to the order of operations at shutdown, only daemon threads\n        # can be shut down in this way (non-daemon threads would require the\n        # introduction of a new hook: https://bugs.python.org/issue41962)\n        self._thread = threading.Thread(\n            name=\"Tornado selector\",\n            daemon=True,\n            target=self._run_select,\n        )\n        self._thread.start()\n        self._start_select()\n        try:\n            # The presense of this yield statement means that this coroutine\n            # is actually an asynchronous generator, which has a special\n            # shutdown protocol. We wait at this yield point until the\n            # event loop's shutdown_asyncgens method is called, at which point\n            # we will get a GeneratorExit exception and can shut down the\n            # selector thread.\n            yield\n        except GeneratorExit:\n            self.close()\n            raise\n\n    def _wake_selector(self) -> None:\n        if self._closed:\n            return\n        try:\n            self._waker_w.send(b\"a\")\n        except BlockingIOError:\n            pass\n\n    def _consume_waker(self) -> None:\n        try:\n            self._waker_r.recv(1024)\n        except BlockingIOError:\n            pass\n\n    def _start_select(self) -> None:\n        # Capture reader and writer sets here in the event loop\n        # thread to avoid any problems with concurrent\n        # modification while the select loop uses them.\n        with self._select_cond:\n            assert self._select_args is None\n            self._select_args = (list(self._readers.keys()), list(self._writers.keys()))\n            self._select_cond.notify()\n\n    def _run_select(self) -> None:\n        while True:\n            with self._select_cond:\n                while self._select_args is None and not self._closing_selector:\n                    self._select_cond.wait()\n                if self._closing_selector:\n                    return\n                assert self._select_args is not None\n                to_read, to_write = self._select_args\n                self._select_args = None\n\n            # We use the simpler interface of the select module instead of\n            # the more stateful interface in the selectors module because\n            # this class is only intended for use on windows, where\n            # select.select is the only option. The selector interface\n            # does not have well-documented thread-safety semantics that\n            # we can rely on so ensuring proper synchronization would be\n            # tricky.\n            try:\n                # On windows, selecting on a socket for write will not\n                # return the socket when there is an error (but selecting\n                # for reads works). Also select for errors when selecting\n                # for writes, and merge the results.\n                #\n                # This pattern is also used in\n                # https://github.com/python/cpython/blob/v3.8.0/Lib/selectors.py#L312-L317\n                rs, ws, xs = select.select(to_read, to_write, to_write)\n                ws = ws + xs\n            except OSError as e:\n                # After remove_reader or remove_writer is called, the file\n                # descriptor may subsequently be closed on the event loop\n                # thread. It's possible that this select thread hasn't\n                # gotten into the select system call by the time that\n                # happens in which case (at least on macOS), select may\n                # raise a \"bad file descriptor\" error. If we get that\n                # error, check and see if we're also being woken up by\n                # polling the waker alone. If we are, just return to the\n                # event loop and we'll get the updated set of file\n                # descriptors on the next iteration. Otherwise, raise the\n                # original error.\n                if e.errno == getattr(errno, \"WSAENOTSOCK\", errno.EBADF):\n                    rs, _, _ = select.select([self._waker_r.fileno()], [], [], 0)\n                    if rs:\n                        ws = []\n                    else:\n                        raise\n                else:\n                    raise\n\n            try:\n                self._real_loop.call_soon_threadsafe(\n                    self._handle_select, rs, ws, context=self._main_thread_ctx\n                )\n            except RuntimeError:\n                # \"Event loop is closed\". Swallow the exception for\n                # consistency with PollIOLoop (and logical consistency\n                # with the fact that we can't guarantee that an\n                # add_callback that completes without error will\n                # eventually execute).\n                pass\n            except AttributeError:\n                # ProactorEventLoop may raise this instead of RuntimeError\n                # if call_soon_threadsafe races with a call to close().\n                # Swallow it too for consistency.\n                pass\n\n    def _handle_select(\n        self, rs: list[_FileDescriptorLike], ws: list[_FileDescriptorLike]\n    ) -> None:\n        for r in rs:\n            self._handle_event(r, self._readers)\n        for w in ws:\n            self._handle_event(w, self._writers)\n        self._start_select()\n\n    def _handle_event(\n        self,\n        fd: _FileDescriptorLike,\n        cb_map: dict[_FileDescriptorLike, Callable],\n    ) -> None:\n        try:\n            callback = cb_map[fd]\n        except KeyError:\n            return\n        callback()\n\n    def add_reader(\n        self, fd: _FileDescriptorLike, callback: Callable[..., None], *args: Any\n    ) -> None:\n        self._readers[fd] = functools.partial(callback, *args)\n        self._wake_selector()\n\n    def add_writer(\n        self, fd: _FileDescriptorLike, callback: Callable[..., None], *args: Any\n    ) -> None:\n        self._writers[fd] = functools.partial(callback, *args)\n        self._wake_selector()\n\n    def remove_reader(self, fd: _FileDescriptorLike) -> bool:\n        try:\n            del self._readers[fd]\n        except KeyError:\n            return False\n        self._wake_selector()\n        return True\n\n    def remove_writer(self, fd: _FileDescriptorLike) -> bool:\n        try:\n            del self._writers[fd]\n        except KeyError:\n            return False\n        self._wake_selector()\n        return True\n\n\nclass AddThreadSelectorEventLoop(asyncio.AbstractEventLoop):\n    \"\"\"Wrap an event loop to add implementations of the ``add_reader`` method family.\n\n    Instances of this class start a second thread to run a selector.\n    This thread is completely hidden from the user; all callbacks are\n    run on the wrapped event loop's thread.\n\n    This class is used automatically by Tornado; applications should not need\n    to refer to it directly.\n\n    It is safe to wrap any event loop with this class, although it only makes sense\n    for event loops that do not implement the ``add_reader`` family of methods\n    themselves (i.e. ``WindowsProactorEventLoop``)\n\n    Closing the ``AddThreadSelectorEventLoop`` also closes the wrapped event loop.\n\n    \"\"\"\n\n    # This class is a __getattribute__-based proxy. All attributes other than those\n    # in this set are proxied through to the underlying loop.\n    MY_ATTRIBUTES = {\n        \"_real_loop\",\n        \"_selector\",\n        \"add_reader\",\n        \"add_writer\",\n        \"close\",\n        \"remove_reader\",\n        \"remove_writer\",\n    }\n\n    def __getattribute__(self, name: str) -> Any:\n        if name in AddThreadSelectorEventLoop.MY_ATTRIBUTES:\n            return super().__getattribute__(name)\n        return getattr(self._real_loop, name)\n\n    def __init__(self, real_loop: asyncio.AbstractEventLoop) -> None:\n        self._real_loop = real_loop\n        self._selector = SelectorThread(real_loop)\n\n    def close(self) -> None:\n        self._selector.close()\n        self._real_loop.close()\n\n    def add_reader(\n        self,\n        fd: \"_FileDescriptorLike\",\n        callback: Callable[..., None],\n        *args: \"Unpack[_Ts]\",\n    ) -> None:\n        return self._selector.add_reader(fd, callback, *args)\n\n    def add_writer(\n        self,\n        fd: \"_FileDescriptorLike\",\n        callback: Callable[..., None],\n        *args: \"Unpack[_Ts]\",\n    ) -> None:\n        return self._selector.add_writer(fd, callback, *args)\n\n    def remove_reader(self, fd: \"_FileDescriptorLike\") -> bool:\n        return self._selector.remove_reader(fd)\n\n    def remove_writer(self, fd: \"_FileDescriptorLike\") -> bool:\n        return self._selector.remove_writer(fd)\n"
  },
  {
    "path": "tornado/platform/caresresolver.py",
    "content": "import socket\nimport typing\nfrom collections.abc import Generator\nfrom typing import Any\n\nimport pycares  # type: ignore\n\nfrom tornado import gen\nfrom tornado.concurrent import Future\nfrom tornado.ioloop import IOLoop\nfrom tornado.netutil import Resolver, is_valid_ip\n\n\nclass CaresResolver(Resolver):\n    \"\"\"Name resolver based on the c-ares library.\n\n    This is a non-blocking and non-threaded resolver.  It may not produce the\n    same results as the system resolver, but can be used for non-blocking\n    resolution when threads cannot be used.\n\n    ``pycares`` will not return a mix of ``AF_INET`` and ``AF_INET6`` when\n    ``family`` is ``AF_UNSPEC``, so it is only recommended for use in\n    ``AF_INET`` (i.e. IPv4).  This is the default for\n    ``tornado.simple_httpclient``, but other libraries may default to\n    ``AF_UNSPEC``.\n\n    .. versionchanged:: 5.0\n       The ``io_loop`` argument (deprecated since version 4.1) has been removed.\n\n    .. deprecated:: 6.2\n       This class is deprecated and will be removed in Tornado 7.0. Use the default\n       thread-based resolver instead.\n    \"\"\"\n\n    def initialize(self) -> None:\n        self.io_loop = IOLoop.current()\n        self.channel = pycares.Channel(sock_state_cb=self._sock_state_cb)\n        self.fds: dict[int, int] = {}\n\n    def _sock_state_cb(self, fd: int, readable: bool, writable: bool) -> None:\n        state = (IOLoop.READ if readable else 0) | (IOLoop.WRITE if writable else 0)\n        if not state:\n            self.io_loop.remove_handler(fd)\n            del self.fds[fd]\n        elif fd in self.fds:\n            self.io_loop.update_handler(fd, state)\n            self.fds[fd] = state\n        else:\n            self.io_loop.add_handler(fd, self._handle_events, state)\n            self.fds[fd] = state\n\n    def _handle_events(self, fd: int, events: int) -> None:\n        read_fd = pycares.ARES_SOCKET_BAD\n        write_fd = pycares.ARES_SOCKET_BAD\n        if events & IOLoop.READ:\n            read_fd = fd\n        if events & IOLoop.WRITE:\n            write_fd = fd\n        self.channel.process_fd(read_fd, write_fd)\n\n    @gen.coroutine\n    def resolve(\n        self, host: str, port: int, family: int = 0\n    ) -> \"Generator[Any, Any, list[tuple[int, Any]]]\":\n        if is_valid_ip(host):\n            addresses = [host]\n        else:\n            # gethostbyname doesn't take callback as a kwarg\n            fut: Future[tuple[Any, Any]] = Future()\n            self.channel.gethostbyname(\n                host, family, lambda result, error: fut.set_result((result, error))\n            )\n            result, error = yield fut\n            if error:\n                raise OSError(\n                    \"C-Ares returned error %s: %s while resolving %s\"\n                    % (error, pycares.errno.strerror(error), host)\n                )\n            addresses = result.addresses\n        addrinfo = []\n        for address in addresses:\n            if \".\" in address:\n                address_family = socket.AF_INET\n            elif \":\" in address:\n                address_family = socket.AF_INET6\n            else:\n                address_family = socket.AF_UNSPEC\n            if family != socket.AF_UNSPEC and family != address_family:\n                raise OSError(\n                    \"Requested socket family %d but got %d\" % (family, address_family)\n                )\n            addrinfo.append((typing.cast(int, address_family), (address, port)))\n        return addrinfo\n"
  },
  {
    "path": "tornado/platform/twisted.py",
    "content": "# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\"\"\"Bridges between the Twisted package and Tornado.\"\"\"\n\nimport sys\nimport typing\n\nfrom twisted.internet.defer import Deferred  # type: ignore\nfrom twisted.python import failure  # type: ignore\n\nfrom tornado import gen\nfrom tornado.concurrent import Future, future_set_exc_info\n\n\ndef install() -> None:\n    \"\"\"Install ``AsyncioSelectorReactor`` as the default Twisted reactor.\n\n    .. deprecated:: 5.1\n\n       This function is provided for backwards compatibility; code\n       that does not require compatibility with older versions of\n       Tornado should use\n       ``twisted.internet.asyncioreactor.install()`` directly.\n\n    .. versionchanged:: 6.0.3\n\n       In Tornado 5.x and before, this function installed a reactor\n       based on the Tornado ``IOLoop``. When that reactor\n       implementation was removed in Tornado 6.0.0, this function was\n       removed as well. It was restored in Tornado 6.0.3 using the\n       ``asyncio`` reactor instead.\n\n    \"\"\"\n    from twisted.internet.asyncioreactor import install  # type: ignore\n\n    install()\n\n\nif hasattr(gen.convert_yielded, \"register\"):\n\n    @gen.convert_yielded.register(Deferred)\n    def _(d: Deferred) -> Future:\n        f: Future[typing.Any] = Future()\n\n        def errback(failure: failure.Failure) -> None:\n            try:\n                failure.raiseException()\n                # Should never happen, but just in case\n                raise Exception(\"errback called without error\")\n            except:\n                future_set_exc_info(f, sys.exc_info())\n\n        d.addCallbacks(f.set_result, errback)\n        return f\n"
  },
  {
    "path": "tornado/process.py",
    "content": "#\n# Copyright 2011 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Utilities for working with multiple processes, including both forking\nthe server into multiple processes and managing subprocesses.\n\"\"\"\n\nimport asyncio\nimport multiprocessing\nimport os\nimport signal\nimport subprocess\nimport sys\nimport time\nfrom binascii import hexlify\nfrom collections.abc import Callable\nfrom typing import Any\n\nfrom tornado import ioloop\nfrom tornado.concurrent import (\n    Future,\n    future_set_exception_unless_cancelled,\n    future_set_result_unless_cancelled,\n)\nfrom tornado.iostream import PipeIOStream\nfrom tornado.log import gen_log\n\n# Re-export this exception for convenience.\nCalledProcessError = subprocess.CalledProcessError\n\n\ndef cpu_count() -> int:\n    \"\"\"Returns the number of processors on this machine.\"\"\"\n    if multiprocessing is None:\n        return 1\n    try:\n        return multiprocessing.cpu_count()\n    except NotImplementedError:\n        pass\n    try:\n        return os.sysconf(\"SC_NPROCESSORS_CONF\")  # type: ignore\n    except (AttributeError, ValueError):\n        pass\n    gen_log.error(\"Could not detect number of processors; assuming 1\")\n    return 1\n\n\ndef _reseed_random() -> None:\n    if \"random\" not in sys.modules:\n        return\n    import random\n\n    # If os.urandom is available, this method does the same thing as\n    # random.seed (at least as of python 2.6).  If os.urandom is not\n    # available, we mix in the pid in addition to a timestamp.\n    try:\n        seed = int(hexlify(os.urandom(16)), 16)\n    except NotImplementedError:\n        seed = int(time.time() * 1000) ^ os.getpid()\n    random.seed(seed)\n\n\n_task_id = None\n\n\ndef fork_processes(num_processes: int | None, max_restarts: int | None = None) -> int:\n    \"\"\"Starts multiple worker processes.\n\n    If ``num_processes`` is None or <= 0, we detect the number of cores\n    available on this machine and fork that number of child\n    processes. If ``num_processes`` is given and > 0, we fork that\n    specific number of sub-processes.\n\n    Since we use processes and not threads, there is no shared memory\n    between any server code.\n\n    Note that multiple processes are not compatible with the autoreload\n    module (or the ``autoreload=True`` option to `tornado.web.Application`\n    which defaults to True when ``debug=True``).\n    When using multiple processes, no IOLoops can be created or\n    referenced until after the call to ``fork_processes``.\n\n    In each child process, ``fork_processes`` returns its *task id*, a\n    number between 0 and ``num_processes``.  Processes that exit\n    abnormally (due to a signal or non-zero exit status) are restarted\n    with the same id (up to ``max_restarts`` times).  In the parent\n    process, ``fork_processes`` calls ``sys.exit(0)`` after all child\n    processes have exited normally.\n\n    max_restarts defaults to 100.\n\n    Availability: Unix\n    \"\"\"\n    if sys.platform == \"win32\":\n        # The exact form of this condition matters to mypy; it understands\n        # if but not assert in this context.\n        raise Exception(\"fork not available on windows\")\n    if max_restarts is None:\n        max_restarts = 100\n\n    assert _task_id is None\n    if num_processes is None or num_processes <= 0:\n        num_processes = cpu_count()\n    gen_log.info(\"Starting %d processes\", num_processes)\n    children = {}\n\n    def start_child(i: int) -> int | None:\n        pid = os.fork()\n        if pid == 0:\n            # child process\n            _reseed_random()\n            global _task_id\n            _task_id = i\n            return i\n        else:\n            children[pid] = i\n            return None\n\n    for i in range(num_processes):\n        id = start_child(i)\n        if id is not None:\n            return id\n    num_restarts = 0\n    while children:\n        pid, status = os.wait()\n        if pid not in children:\n            continue\n        id = children.pop(pid)\n        if os.WIFSIGNALED(status):\n            gen_log.warning(\n                \"child %d (pid %d) killed by signal %d, restarting\",\n                id,\n                pid,\n                os.WTERMSIG(status),\n            )\n        elif os.WEXITSTATUS(status) != 0:\n            gen_log.warning(\n                \"child %d (pid %d) exited with status %d, restarting\",\n                id,\n                pid,\n                os.WEXITSTATUS(status),\n            )\n        else:\n            gen_log.info(\"child %d (pid %d) exited normally\", id, pid)\n            continue\n        num_restarts += 1\n        if num_restarts > max_restarts:\n            raise RuntimeError(\"Too many child restarts, giving up\")\n        new_id = start_child(id)\n        if new_id is not None:\n            return new_id\n    # All child processes exited cleanly, so exit the master process\n    # instead of just returning to right after the call to\n    # fork_processes (which will probably just start up another IOLoop\n    # unless the caller checks the return value).\n    sys.exit(0)\n\n\ndef task_id() -> int | None:\n    \"\"\"Returns the current task id, if any.\n\n    Returns None if this process was not created by `fork_processes`.\n    \"\"\"\n    return _task_id\n\n\nclass Subprocess:\n    \"\"\"Wraps ``subprocess.Popen`` with IOStream support.\n\n    The constructor is the same as ``subprocess.Popen`` with the following\n    additions:\n\n    * ``stdin``, ``stdout``, and ``stderr`` may have the value\n      ``tornado.process.Subprocess.STREAM``, which will make the corresponding\n      attribute of the resulting Subprocess a `.PipeIOStream`. If this option\n      is used, the caller is responsible for closing the streams when done\n      with them.\n\n    The ``Subprocess.STREAM`` option and the ``set_exit_callback`` and\n    ``wait_for_exit`` methods do not work on Windows. There is\n    therefore no reason to use this class instead of\n    ``subprocess.Popen`` on that platform.\n\n    .. versionchanged:: 5.0\n       The ``io_loop`` argument (deprecated since version 4.1) has been removed.\n\n    \"\"\"\n\n    STREAM = object()\n\n    _initialized = False\n    _waiting = {}  # type: ignore\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        self.io_loop = ioloop.IOLoop.current()\n        # All FDs we create should be closed on error; those in to_close\n        # should be closed in the parent process on success.\n        pipe_fds: list[int] = []\n        to_close: list[int] = []\n        if kwargs.get(\"stdin\") is Subprocess.STREAM:\n            in_r, in_w = os.pipe()\n            kwargs[\"stdin\"] = in_r\n            pipe_fds.extend((in_r, in_w))\n            to_close.append(in_r)\n            self.stdin = PipeIOStream(in_w)\n        if kwargs.get(\"stdout\") is Subprocess.STREAM:\n            out_r, out_w = os.pipe()\n            kwargs[\"stdout\"] = out_w\n            pipe_fds.extend((out_r, out_w))\n            to_close.append(out_w)\n            self.stdout = PipeIOStream(out_r)\n        if kwargs.get(\"stderr\") is Subprocess.STREAM:\n            err_r, err_w = os.pipe()\n            kwargs[\"stderr\"] = err_w\n            pipe_fds.extend((err_r, err_w))\n            to_close.append(err_w)\n            self.stderr = PipeIOStream(err_r)\n        try:\n            self.proc = subprocess.Popen(*args, **kwargs)\n        except:\n            for fd in pipe_fds:\n                os.close(fd)\n            raise\n        for fd in to_close:\n            os.close(fd)\n        self.pid = self.proc.pid\n        for attr in [\"stdin\", \"stdout\", \"stderr\"]:\n            if not hasattr(self, attr):  # don't clobber streams set above\n                setattr(self, attr, getattr(self.proc, attr))\n        self._exit_callback: Callable[[int], None] | None = None\n        self.returncode: int | None = None\n\n    def set_exit_callback(self, callback: Callable[[int], None]) -> None:\n        \"\"\"Runs ``callback`` when this process exits.\n\n        The callback takes one argument, the return code of the process.\n\n        This method uses a ``SIGCHLD`` handler, which is a global setting\n        and may conflict if you have other libraries trying to handle the\n        same signal.  If you are using more than one ``IOLoop`` it may\n        be necessary to call `Subprocess.initialize` first to designate\n        one ``IOLoop`` to run the signal handlers.\n\n        In many cases a close callback on the stdout or stderr streams\n        can be used as an alternative to an exit callback if the\n        signal handler is causing a problem.\n\n        Availability: Unix\n        \"\"\"\n        self._exit_callback = callback\n        Subprocess.initialize()\n        Subprocess._waiting[self.pid] = self\n        Subprocess._try_cleanup_process(self.pid)\n\n    def wait_for_exit(self, raise_error: bool = True) -> \"Future[int]\":\n        \"\"\"Returns a `.Future` which resolves when the process exits.\n\n        Usage::\n\n            ret = yield proc.wait_for_exit()\n\n        This is a coroutine-friendly alternative to `set_exit_callback`\n        (and a replacement for the blocking `subprocess.Popen.wait`).\n\n        By default, raises `subprocess.CalledProcessError` if the process\n        has a non-zero exit status. Use ``wait_for_exit(raise_error=False)``\n        to suppress this behavior and return the exit status without raising.\n\n        .. versionadded:: 4.2\n\n        Availability: Unix\n        \"\"\"\n        future: Future[int] = Future()\n\n        def callback(ret: int) -> None:\n            if ret != 0 and raise_error:\n                # Unfortunately we don't have the original args any more.\n                future_set_exception_unless_cancelled(\n                    future, CalledProcessError(ret, \"unknown\")\n                )\n            else:\n                future_set_result_unless_cancelled(future, ret)\n\n        self.set_exit_callback(callback)\n        return future\n\n    @classmethod\n    def initialize(cls) -> None:\n        \"\"\"Initializes the ``SIGCHLD`` handler.\n\n        The signal handler is run on an `.IOLoop` to avoid locking issues.\n        Note that the `.IOLoop` used for signal handling need not be the\n        same one used by individual Subprocess objects (as long as the\n        ``IOLoops`` are each running in separate threads).\n\n        .. versionchanged:: 5.0\n           The ``io_loop`` argument (deprecated since version 4.1) has been\n           removed.\n\n        Availability: Unix\n        \"\"\"\n        if cls._initialized:\n            return\n        loop = asyncio.get_event_loop()\n        loop.add_signal_handler(signal.SIGCHLD, cls._cleanup)\n        cls._initialized = True\n\n    @classmethod\n    def uninitialize(cls) -> None:\n        \"\"\"Removes the ``SIGCHLD`` handler.\"\"\"\n        if not cls._initialized:\n            return\n        loop = asyncio.get_event_loop()\n        loop.remove_signal_handler(signal.SIGCHLD)\n        cls._initialized = False\n\n    @classmethod\n    def _cleanup(cls) -> None:\n        for pid in list(cls._waiting.keys()):  # make a copy\n            cls._try_cleanup_process(pid)\n\n    @classmethod\n    def _try_cleanup_process(cls, pid: int) -> None:\n        try:\n            ret_pid, status = os.waitpid(pid, os.WNOHANG)  # type: ignore\n        except ChildProcessError:\n            return\n        if ret_pid == 0:\n            return\n        assert ret_pid == pid\n        subproc = cls._waiting.pop(pid)\n        subproc.io_loop.add_callback(subproc._set_returncode, status)\n\n    def _set_returncode(self, status: int) -> None:\n        if sys.platform == \"win32\":\n            self.returncode = -1\n        else:\n            if os.WIFSIGNALED(status):\n                self.returncode = -os.WTERMSIG(status)\n            else:\n                assert os.WIFEXITED(status)\n                self.returncode = os.WEXITSTATUS(status)\n        # We've taken over wait() duty from the subprocess.Popen\n        # object. If we don't inform it of the process's return code,\n        # it will log a warning at destruction in python 3.6+.\n        self.proc.returncode = self.returncode\n        if self._exit_callback:\n            callback = self._exit_callback\n            self._exit_callback = None\n            callback(self.returncode)\n"
  },
  {
    "path": "tornado/py.typed",
    "content": ""
  },
  {
    "path": "tornado/queues.py",
    "content": "# Copyright 2015 The Tornado Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Asynchronous queues for coroutines. These classes are very similar\nto those provided in the standard library's `asyncio package\n<https://docs.python.org/3/library/asyncio-queue.html>`_.\n\n.. warning::\n\n   Unlike the standard library's `queue` module, the classes defined here\n   are *not* thread-safe. To use these queues from another thread,\n   use `.IOLoop.add_callback` to transfer control to the `.IOLoop` thread\n   before calling any queue methods.\n\n\"\"\"\n\nfrom __future__ import annotations\n\nimport collections\nimport datetime\nimport heapq\nfrom collections.abc import Awaitable\nfrom typing import Any, Generic, TypeVar\n\nfrom tornado import gen, ioloop\nfrom tornado.concurrent import Future, future_set_result_unless_cancelled\nfrom tornado.locks import Event\n\n_T = TypeVar(\"_T\")\n\n__all__ = [\"Queue\", \"PriorityQueue\", \"LifoQueue\", \"QueueFull\", \"QueueEmpty\"]\n\n\nclass QueueEmpty(Exception):\n    \"\"\"Raised by `.Queue.get_nowait` when the queue has no items.\"\"\"\n\n    pass\n\n\nclass QueueFull(Exception):\n    \"\"\"Raised by `.Queue.put_nowait` when a queue is at its maximum size.\"\"\"\n\n    pass\n\n\ndef _set_timeout(future: Future, timeout: None | float | datetime.timedelta) -> None:\n    if timeout:\n\n        def on_timeout() -> None:\n            if not future.done():\n                future.set_exception(gen.TimeoutError())\n\n        io_loop = ioloop.IOLoop.current()\n        timeout_handle = io_loop.add_timeout(timeout, on_timeout)\n        future.add_done_callback(lambda _: io_loop.remove_timeout(timeout_handle))\n\n\nclass _QueueIterator(Generic[_T]):\n    def __init__(self, q: Queue[_T]) -> None:\n        self.q = q\n\n    def __anext__(self) -> Awaitable[_T]:\n        return self.q.get()\n\n\nclass Queue(Generic[_T]):\n    \"\"\"Coordinate producer and consumer coroutines.\n\n    If maxsize is 0 (the default) the queue size is unbounded.\n\n    .. testcode::\n\n        import asyncio\n        from tornado.ioloop import IOLoop\n        from tornado.queues import Queue\n\n        q = Queue(maxsize=2)\n\n        async def consumer():\n            async for item in q:\n                try:\n                    print('Doing work on %s' % item)\n                    await asyncio.sleep(0.01)\n                finally:\n                    q.task_done()\n\n        async def producer():\n            for item in range(5):\n                await q.put(item)\n                print('Put %s' % item)\n\n        async def main():\n            # Start consumer without waiting (since it never finishes).\n            IOLoop.current().spawn_callback(consumer)\n            await producer()     # Wait for producer to put all tasks.\n            await q.join()       # Wait for consumer to finish all tasks.\n            print('Done')\n\n        asyncio.run(main())\n\n    .. testoutput::\n\n        Put 0\n        Put 1\n        Doing work on 0\n        Put 2\n        Doing work on 1\n        Put 3\n        Doing work on 2\n        Put 4\n        Doing work on 3\n        Doing work on 4\n        Done\n\n\n    In versions of Python without native coroutines (before 3.5),\n    ``consumer()`` could be written as::\n\n        @gen.coroutine\n        def consumer():\n            while True:\n                item = yield q.get()\n                try:\n                    print('Doing work on %s' % item)\n                    yield gen.sleep(0.01)\n                finally:\n                    q.task_done()\n\n    .. versionchanged:: 4.3\n       Added ``async for`` support in Python 3.5.\n\n    \"\"\"\n\n    # Exact type depends on subclass. Could be another generic\n    # parameter and use protocols to be more precise here.\n    _queue: Any = None\n\n    def __init__(self, maxsize: int = 0) -> None:\n        if maxsize is None:\n            raise TypeError(\"maxsize can't be None\")\n\n        if maxsize < 0:\n            raise ValueError(\"maxsize can't be negative\")\n\n        self._maxsize = maxsize\n        self._init()\n        self._getters: collections.deque[Future[_T]] = collections.deque([])\n        self._putters: collections.deque[tuple[_T, Future[None]]] = collections.deque(\n            []\n        )\n        self._unfinished_tasks = 0\n        self._finished = Event()\n        self._finished.set()\n\n    @property\n    def maxsize(self) -> int:\n        \"\"\"Number of items allowed in the queue.\"\"\"\n        return self._maxsize\n\n    def qsize(self) -> int:\n        \"\"\"Number of items in the queue.\"\"\"\n        return len(self._queue)\n\n    def empty(self) -> bool:\n        return not self._queue\n\n    def full(self) -> bool:\n        if self.maxsize == 0:\n            return False\n        else:\n            return self.qsize() >= self.maxsize\n\n    def put(\n        self, item: _T, timeout: float | datetime.timedelta | None = None\n    ) -> Future[None]:\n        \"\"\"Put an item into the queue, perhaps waiting until there is room.\n\n        Returns a Future, which raises `tornado.util.TimeoutError` after a\n        timeout.\n\n        ``timeout`` may be a number denoting a time (on the same\n        scale as `tornado.ioloop.IOLoop.time`, normally `time.time`), or a\n        `datetime.timedelta` object for a deadline relative to the\n        current time.\n        \"\"\"\n        future: Future[None] = Future()\n        try:\n            self.put_nowait(item)\n        except QueueFull:\n            self._putters.append((item, future))\n            _set_timeout(future, timeout)\n        else:\n            future.set_result(None)\n        return future\n\n    def put_nowait(self, item: _T) -> None:\n        \"\"\"Put an item into the queue without blocking.\n\n        If no free slot is immediately available, raise `QueueFull`.\n        \"\"\"\n        self._consume_expired()\n        if self._getters:\n            assert self.empty(), \"queue non-empty, why are getters waiting?\"\n            getter = self._getters.popleft()\n            self.__put_internal(item)\n            future_set_result_unless_cancelled(getter, self._get())\n        elif self.full():\n            raise QueueFull\n        else:\n            self.__put_internal(item)\n\n    def get(self, timeout: float | datetime.timedelta | None = None) -> Awaitable[_T]:\n        \"\"\"Remove and return an item from the queue.\n\n        Returns an awaitable which resolves once an item is available, or raises\n        `tornado.util.TimeoutError` after a timeout.\n\n        ``timeout`` may be a number denoting a time (on the same\n        scale as `tornado.ioloop.IOLoop.time`, normally `time.time`), or a\n        `datetime.timedelta` object for a deadline relative to the\n        current time.\n\n        .. note::\n\n           The ``timeout`` argument of this method differs from that\n           of the standard library's `queue.Queue.get`. That method\n           interprets numeric values as relative timeouts; this one\n           interprets them as absolute deadlines and requires\n           ``timedelta`` objects for relative timeouts (consistent\n           with other timeouts in Tornado).\n\n        \"\"\"\n        future: Future[_T] = Future()\n        try:\n            future.set_result(self.get_nowait())\n        except QueueEmpty:\n            self._getters.append(future)\n            _set_timeout(future, timeout)\n        return future\n\n    def get_nowait(self) -> _T:\n        \"\"\"Remove and return an item from the queue without blocking.\n\n        Return an item if one is immediately available, else raise\n        `QueueEmpty`.\n        \"\"\"\n        self._consume_expired()\n        if self._putters:\n            assert self.full(), \"queue not full, why are putters waiting?\"\n            item, putter = self._putters.popleft()\n            self.__put_internal(item)\n            future_set_result_unless_cancelled(putter, None)\n            return self._get()\n        elif self.qsize():\n            return self._get()\n        else:\n            raise QueueEmpty\n\n    def task_done(self) -> None:\n        \"\"\"Indicate that a formerly enqueued task is complete.\n\n        Used by queue consumers. For each `.get` used to fetch a task, a\n        subsequent call to `.task_done` tells the queue that the processing\n        on the task is complete.\n\n        If a `.join` is blocking, it resumes when all items have been\n        processed; that is, when every `.put` is matched by a `.task_done`.\n\n        Raises `ValueError` if called more times than `.put`.\n        \"\"\"\n        if self._unfinished_tasks <= 0:\n            raise ValueError(\"task_done() called too many times\")\n        self._unfinished_tasks -= 1\n        if self._unfinished_tasks == 0:\n            self._finished.set()\n\n    def join(\n        self, timeout: float | datetime.timedelta | None = None\n    ) -> Awaitable[None]:\n        \"\"\"Block until all items in the queue are processed.\n\n        Returns an awaitable, which raises `tornado.util.TimeoutError` after a\n        timeout.\n        \"\"\"\n        return self._finished.wait(timeout)\n\n    def __aiter__(self) -> _QueueIterator[_T]:\n        return _QueueIterator(self)\n\n    # These three are overridable in subclasses.\n    def _init(self) -> None:\n        self._queue = collections.deque()\n\n    def _get(self) -> _T:\n        return self._queue.popleft()\n\n    def _put(self, item: _T) -> None:\n        self._queue.append(item)\n\n    # End of the overridable methods.\n\n    def __put_internal(self, item: _T) -> None:\n        self._unfinished_tasks += 1\n        self._finished.clear()\n        self._put(item)\n\n    def _consume_expired(self) -> None:\n        # Remove timed-out waiters.\n        while self._putters and self._putters[0][1].done():\n            self._putters.popleft()\n\n        while self._getters and self._getters[0].done():\n            self._getters.popleft()\n\n    def __repr__(self) -> str:\n        return f\"<{type(self).__name__} at {hex(id(self))} {self._format()}>\"\n\n    def __str__(self) -> str:\n        return f\"<{type(self).__name__} {self._format()}>\"\n\n    def _format(self) -> str:\n        result = f\"maxsize={self.maxsize!r}\"\n        if getattr(self, \"_queue\", None):\n            result += \" queue=%r\" % self._queue\n        if self._getters:\n            result += \" getters[%s]\" % len(self._getters)\n        if self._putters:\n            result += \" putters[%s]\" % len(self._putters)\n        if self._unfinished_tasks:\n            result += \" tasks=%s\" % self._unfinished_tasks\n        return result\n\n\nclass PriorityQueue(Queue):\n    \"\"\"A `.Queue` that retrieves entries in priority order, lowest first.\n\n    Entries are typically tuples like ``(priority number, data)``.\n\n    .. testcode::\n\n        import asyncio\n        from tornado.queues import PriorityQueue\n\n        async def main():\n            q = PriorityQueue()\n            q.put((1, 'medium-priority item'))\n            q.put((0, 'high-priority item'))\n            q.put((10, 'low-priority item'))\n\n            print(await q.get())\n            print(await q.get())\n            print(await q.get())\n\n        asyncio.run(main())\n\n    .. testoutput::\n\n        (0, 'high-priority item')\n        (1, 'medium-priority item')\n        (10, 'low-priority item')\n    \"\"\"\n\n    def _init(self) -> None:\n        self._queue = []\n\n    def _put(self, item: _T) -> None:\n        heapq.heappush(self._queue, item)\n\n    def _get(self) -> _T:  # type: ignore[type-var]\n        return heapq.heappop(self._queue)\n\n\nclass LifoQueue(Queue):\n    \"\"\"A `.Queue` that retrieves the most recently put items first.\n\n    .. testcode::\n\n        import asyncio\n        from tornado.queues import LifoQueue\n\n        async def main():\n            q = LifoQueue()\n            q.put(3)\n            q.put(2)\n            q.put(1)\n\n            print(await q.get())\n            print(await q.get())\n            print(await q.get())\n\n        asyncio.run(main())\n\n    .. testoutput::\n\n        1\n        2\n        3\n    \"\"\"\n\n    def _init(self) -> None:\n        self._queue = []\n\n    def _put(self, item: _T) -> None:\n        self._queue.append(item)\n\n    def _get(self) -> _T:  # type: ignore[type-var]\n        return self._queue.pop()\n"
  },
  {
    "path": "tornado/routing.py",
    "content": "# Copyright 2015 The Tornado Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Flexible routing implementation.\n\nTornado routes HTTP requests to appropriate handlers using `Router`\nclass implementations. The `tornado.web.Application` class is a\n`Router` implementation and may be used directly, or the classes in\nthis module may be used for additional flexibility. The `RuleRouter`\nclass can match on more criteria than `.Application`, or the `Router`\ninterface can be subclassed for maximum customization.\n\n`Router` interface extends `~.httputil.HTTPServerConnectionDelegate`\nto provide additional routing capabilities. This also means that any\n`Router` implementation can be used directly as a ``request_callback``\nfor `~.httpserver.HTTPServer` constructor.\n\n`Router` subclass must implement a ``find_handler`` method to provide\na suitable `~.httputil.HTTPMessageDelegate` instance to handle the\nrequest:\n\n.. code-block:: python\n\n    class CustomRouter(Router):\n        def find_handler(self, request, **kwargs):\n            # some routing logic providing a suitable HTTPMessageDelegate instance\n            return MessageDelegate(request.connection)\n\n    class MessageDelegate(HTTPMessageDelegate):\n        def __init__(self, connection):\n            self.connection = connection\n\n        def finish(self):\n            self.connection.write_headers(\n                ResponseStartLine(\"HTTP/1.1\", 200, \"OK\"),\n                HTTPHeaders({\"Content-Length\": \"2\"}),\n                b\"OK\")\n            self.connection.finish()\n\n    router = CustomRouter()\n    server = HTTPServer(router)\n\nThe main responsibility of `Router` implementation is to provide a\nmapping from a request to `~.httputil.HTTPMessageDelegate` instance\nthat will handle this request. In the example above we can see that\nrouting is possible even without instantiating an `~.web.Application`.\n\nFor routing to `~.web.RequestHandler` implementations we need an\n`~.web.Application` instance. `~.web.Application.get_handler_delegate`\nprovides a convenient way to create `~.httputil.HTTPMessageDelegate`\nfor a given request and `~.web.RequestHandler`.\n\nHere is a simple example of how we can we route to\n`~.web.RequestHandler` subclasses by HTTP method:\n\n.. code-block:: python\n\n    resources = {}\n\n    class GetResource(RequestHandler):\n        def get(self, path):\n            if path not in resources:\n                raise HTTPError(404)\n\n            self.finish(resources[path])\n\n    class PostResource(RequestHandler):\n        def post(self, path):\n            resources[path] = self.request.body\n\n    class HTTPMethodRouter(Router):\n        def __init__(self, app):\n            self.app = app\n\n        def find_handler(self, request, **kwargs):\n            handler = GetResource if request.method == \"GET\" else PostResource\n            return self.app.get_handler_delegate(request, handler, path_args=[request.path])\n\n    router = HTTPMethodRouter(Application())\n    server = HTTPServer(router)\n\n`ReversibleRouter` interface adds the ability to distinguish between\nthe routes and reverse them to the original urls using route's name\nand additional arguments. `~.web.Application` is itself an\nimplementation of `ReversibleRouter` class.\n\n`RuleRouter` and `ReversibleRuleRouter` are implementations of\n`Router` and `ReversibleRouter` interfaces and can be used for\ncreating rule-based routing configurations.\n\nRules are instances of `Rule` class. They contain a `Matcher`, which\nprovides the logic for determining whether the rule is a match for a\nparticular request and a target, which can be one of the following.\n\n1) An instance of `~.httputil.HTTPServerConnectionDelegate`:\n\n.. code-block:: python\n\n    router = RuleRouter([\n        Rule(PathMatches(\"/handler\"), ConnectionDelegate()),\n        # ... more rules\n    ])\n\n    class ConnectionDelegate(HTTPServerConnectionDelegate):\n        def start_request(self, server_conn, request_conn):\n            return MessageDelegate(request_conn)\n\n2) A callable accepting a single argument of `~.httputil.HTTPServerRequest` type:\n\n.. code-block:: python\n\n    router = RuleRouter([\n        Rule(PathMatches(\"/callable\"), request_callable)\n    ])\n\n    def request_callable(request):\n        request.write(b\"HTTP/1.1 200 OK\\\\r\\\\nContent-Length: 2\\\\r\\\\n\\\\r\\\\nOK\")\n        request.finish()\n\n3) Another `Router` instance:\n\n.. code-block:: python\n\n    router = RuleRouter([\n        Rule(PathMatches(\"/router.*\"), CustomRouter())\n    ])\n\nOf course a nested `RuleRouter` or a `~.web.Application` is allowed:\n\n.. code-block:: python\n\n    router = RuleRouter([\n        Rule(HostMatches(\"example.com\"), RuleRouter([\n            Rule(PathMatches(\"/app1/.*\"), Application([(r\"/app1/handler\", Handler)])),\n        ]))\n    ])\n\n    server = HTTPServer(router)\n\nIn the example below `RuleRouter` is used to route between applications:\n\n.. code-block:: python\n\n    app1 = Application([\n        (r\"/app1/handler\", Handler1),\n        # other handlers ...\n    ])\n\n    app2 = Application([\n        (r\"/app2/handler\", Handler2),\n        # other handlers ...\n    ])\n\n    router = RuleRouter([\n        Rule(PathMatches(\"/app1.*\"), app1),\n        Rule(PathMatches(\"/app2.*\"), app2)\n    ])\n\n    server = HTTPServer(router)\n\nFor more information on application-level routing see docs for `~.web.Application`.\n\n.. versionadded:: 4.5\n\n\"\"\"\n\nimport re\nfrom collections.abc import Awaitable, Sequence\nfrom functools import partial\nfrom re import Pattern\nfrom typing import (\n    Any,\n    Union,\n    overload,\n)\n\nfrom tornado import httputil\nfrom tornado.escape import url_escape, url_unescape, utf8\nfrom tornado.httpserver import _CallableAdapter\nfrom tornado.log import app_log\nfrom tornado.util import basestring_type, import_object, re_unescape, unicode_type\n\n\nclass Router(httputil.HTTPServerConnectionDelegate):\n    \"\"\"Abstract router interface.\"\"\"\n\n    def find_handler(\n        self, request: httputil.HTTPServerRequest, **kwargs: Any\n    ) -> httputil.HTTPMessageDelegate | None:\n        \"\"\"Must be implemented to return an appropriate instance of `~.httputil.HTTPMessageDelegate`\n        that can serve the request.\n        Routing implementations may pass additional kwargs to extend the routing logic.\n\n        :arg httputil.HTTPServerRequest request: current HTTP request.\n        :arg kwargs: additional keyword arguments passed by routing implementation.\n        :returns: an instance of `~.httputil.HTTPMessageDelegate` that will be used to\n            process the request.\n        \"\"\"\n        raise NotImplementedError()\n\n    def start_request(\n        self, server_conn: object, request_conn: httputil.HTTPConnection\n    ) -> httputil.HTTPMessageDelegate:\n        return _RoutingDelegate(self, server_conn, request_conn)\n\n\nclass ReversibleRouter(Router):\n    \"\"\"Abstract router interface for routers that can handle named routes\n    and support reversing them to original urls.\n    \"\"\"\n\n    def reverse_url(self, name: str, *args: Any) -> str | None:\n        \"\"\"Returns url string for a given route name and arguments\n        or ``None`` if no match is found.\n\n        :arg str name: route name.\n        :arg args: url parameters.\n        :returns: parametrized url string for a given route name (or ``None``).\n        \"\"\"\n        raise NotImplementedError()\n\n\nclass _RoutingDelegate(httputil.HTTPMessageDelegate):\n    def __init__(\n        self, router: Router, server_conn: object, request_conn: httputil.HTTPConnection\n    ) -> None:\n        self.server_conn = server_conn\n        self.request_conn = request_conn\n        self.delegate: httputil.HTTPMessageDelegate | None = None\n        self.router: Router = router\n\n    def headers_received(\n        self,\n        start_line: httputil.RequestStartLine | httputil.ResponseStartLine,\n        headers: httputil.HTTPHeaders,\n    ) -> Awaitable[None] | None:\n        assert isinstance(start_line, httputil.RequestStartLine)\n        request = httputil.HTTPServerRequest(\n            connection=self.request_conn,\n            server_connection=self.server_conn,\n            start_line=start_line,\n            headers=headers,\n        )\n\n        self.delegate = self.router.find_handler(request)\n        if self.delegate is None:\n            app_log.debug(\n                \"Delegate for %s %s request not found\",\n                start_line.method,\n                start_line.path,\n            )\n            self.delegate = _DefaultMessageDelegate(self.request_conn)\n\n        return self.delegate.headers_received(start_line, headers)\n\n    def data_received(self, chunk: bytes) -> Awaitable[None] | None:\n        assert self.delegate is not None\n        return self.delegate.data_received(chunk)\n\n    def finish(self) -> None:\n        assert self.delegate is not None\n        self.delegate.finish()\n\n    def on_connection_close(self) -> None:\n        if self.delegate is not None:\n            self.delegate.on_connection_close()\n\n\nclass _DefaultMessageDelegate(httputil.HTTPMessageDelegate):\n    def __init__(self, connection: httputil.HTTPConnection) -> None:\n        self.connection = connection\n\n    def finish(self) -> None:\n        self.connection.write_headers(\n            httputil.ResponseStartLine(\"HTTP/1.1\", 404, \"Not Found\"),\n            httputil.HTTPHeaders(),\n        )\n        self.connection.finish()\n\n\n# _RuleList can either contain pre-constructed Rules or a sequence of\n# arguments to be passed to the Rule constructor.\n_RuleList = Sequence[\n    Union[\n        \"Rule\",\n        list[Any],  # Can't do detailed typechecking of lists.\n        tuple[Union[str, \"Matcher\"], Any],\n        tuple[Union[str, \"Matcher\"], Any, dict[str, Any]],\n        tuple[Union[str, \"Matcher\"], Any, dict[str, Any], str],\n    ]\n]\n\n\nclass RuleRouter(Router):\n    \"\"\"Rule-based router implementation.\"\"\"\n\n    def __init__(self, rules: _RuleList | None = None) -> None:\n        \"\"\"Constructs a router from an ordered list of rules::\n\n            RuleRouter([\n                Rule(PathMatches(\"/handler\"), Target),\n                # ... more rules\n            ])\n\n        You can also omit explicit `Rule` constructor and use tuples of arguments::\n\n            RuleRouter([\n                (PathMatches(\"/handler\"), Target),\n            ])\n\n        `PathMatches` is a default matcher, so the example above can be simplified::\n\n            RuleRouter([\n                (\"/handler\", Target),\n            ])\n\n        In the examples above, ``Target`` can be a nested `Router` instance, an instance of\n        `~.httputil.HTTPServerConnectionDelegate` or an old-style callable,\n        accepting a request argument.\n\n        :arg rules: a list of `Rule` instances or tuples of `Rule`\n            constructor arguments.\n        \"\"\"\n        self.rules: list[Rule] = []\n        if rules:\n            self.add_rules(rules)\n\n    def add_rules(self, rules: _RuleList) -> None:\n        \"\"\"Appends new rules to the router.\n\n        :arg rules: a list of Rule instances (or tuples of arguments, which are\n            passed to Rule constructor).\n        \"\"\"\n        for rule in rules:\n            if isinstance(rule, (tuple, list)):\n                assert len(rule) in (2, 3, 4)\n                if isinstance(rule[0], basestring_type):\n                    rule = Rule(PathMatches(rule[0]), *rule[1:])\n                else:\n                    rule = Rule(*rule)\n\n            self.rules.append(self.process_rule(rule))\n\n    def process_rule(self, rule: \"Rule\") -> \"Rule\":\n        \"\"\"Override this method for additional preprocessing of each rule.\n\n        :arg Rule rule: a rule to be processed.\n        :returns: the same or modified Rule instance.\n        \"\"\"\n        return rule\n\n    def find_handler(\n        self, request: httputil.HTTPServerRequest, **kwargs: Any\n    ) -> httputil.HTTPMessageDelegate | None:\n        for rule in self.rules:\n            target_params = rule.matcher.match(request)\n            if target_params is not None:\n                if rule.target_kwargs:\n                    target_params[\"target_kwargs\"] = rule.target_kwargs\n\n                delegate = self.get_target_delegate(\n                    rule.target, request, **target_params\n                )\n\n                if delegate is not None:\n                    return delegate\n\n        return None\n\n    def get_target_delegate(\n        self, target: Any, request: httputil.HTTPServerRequest, **target_params: Any\n    ) -> httputil.HTTPMessageDelegate | None:\n        \"\"\"Returns an instance of `~.httputil.HTTPMessageDelegate` for a\n        Rule's target. This method is called by `~.find_handler` and can be\n        extended to provide additional target types.\n\n        :arg target: a Rule's target.\n        :arg httputil.HTTPServerRequest request: current request.\n        :arg target_params: additional parameters that can be useful\n            for `~.httputil.HTTPMessageDelegate` creation.\n        \"\"\"\n        if isinstance(target, Router):\n            return target.find_handler(request, **target_params)\n\n        elif isinstance(target, httputil.HTTPServerConnectionDelegate):\n            assert request.connection is not None\n            return target.start_request(request.server_connection, request.connection)\n\n        elif callable(target):\n            assert request.connection is not None\n            return _CallableAdapter(\n                partial(target, **target_params), request.connection\n            )\n\n        return None\n\n\nclass ReversibleRuleRouter(ReversibleRouter, RuleRouter):\n    \"\"\"A rule-based router that implements ``reverse_url`` method.\n\n    Each rule added to this router may have a ``name`` attribute that can be\n    used to reconstruct an original uri. The actual reconstruction takes place\n    in a rule's matcher (see `Matcher.reverse`).\n    \"\"\"\n\n    def __init__(self, rules: _RuleList | None = None) -> None:\n        self.named_rules: dict[str, Any] = {}\n        super().__init__(rules)\n\n    def process_rule(self, rule: \"Rule\") -> \"Rule\":\n        rule = super().process_rule(rule)\n\n        if rule.name:\n            if rule.name in self.named_rules:\n                app_log.warning(\n                    \"Multiple handlers named %s; replacing previous value\", rule.name\n                )\n            self.named_rules[rule.name] = rule\n\n        return rule\n\n    def reverse_url(self, name: str, *args: Any) -> str | None:\n        if name in self.named_rules:\n            return self.named_rules[name].matcher.reverse(*args)\n\n        for rule in self.rules:\n            if isinstance(rule.target, ReversibleRouter):\n                reversed_url = rule.target.reverse_url(name, *args)\n                if reversed_url is not None:\n                    return reversed_url\n\n        return None\n\n\nclass Rule:\n    \"\"\"A routing rule.\"\"\"\n\n    def __init__(\n        self,\n        matcher: \"Matcher\",\n        target: Any,\n        target_kwargs: dict[str, Any] | None = None,\n        name: str | None = None,\n    ) -> None:\n        \"\"\"Constructs a Rule instance.\n\n        :arg Matcher matcher: a `Matcher` instance used for determining\n            whether the rule should be considered a match for a specific\n            request.\n        :arg target: a Rule's target (typically a ``RequestHandler`` or\n            `~.httputil.HTTPServerConnectionDelegate` subclass or even a nested `Router`,\n            depending on routing implementation).\n        :arg dict target_kwargs: a dict of parameters that can be useful\n            at the moment of target instantiation (for example, ``status_code``\n            for a ``RequestHandler`` subclass). They end up in\n            ``target_params['target_kwargs']`` of `RuleRouter.get_target_delegate`\n            method.\n        :arg str name: the name of the rule that can be used to find it\n            in `ReversibleRouter.reverse_url` implementation.\n        \"\"\"\n        if isinstance(target, str):\n            # import the Module and instantiate the class\n            # Must be a fully qualified name (module.ClassName)\n            target = import_object(target)\n\n        self.matcher: Matcher = matcher\n        self.target = target\n        self.target_kwargs = target_kwargs if target_kwargs else {}\n        self.name = name\n\n    def reverse(self, *args: Any) -> str | None:\n        return self.matcher.reverse(*args)\n\n    def __repr__(self) -> str:\n        return \"{}({!r}, {}, kwargs={!r}, name={!r})\".format(\n            self.__class__.__name__,\n            self.matcher,\n            self.target,\n            self.target_kwargs,\n            self.name,\n        )\n\n\nclass Matcher:\n    \"\"\"Represents a matcher for request features.\"\"\"\n\n    def match(self, request: httputil.HTTPServerRequest) -> dict[str, Any] | None:\n        \"\"\"Matches current instance against the request.\n\n        :arg httputil.HTTPServerRequest request: current HTTP request\n        :returns: a dict of parameters to be passed to the target handler\n            (for example, ``handler_kwargs``, ``path_args``, ``path_kwargs``\n            can be passed for proper `~.web.RequestHandler` instantiation).\n            An empty dict is a valid (and common) return value to indicate a match\n            when the argument-passing features are not used.\n            ``None`` must be returned to indicate that there is no match.\"\"\"\n        raise NotImplementedError()\n\n    def reverse(self, *args: Any) -> str | None:\n        \"\"\"Reconstructs full url from matcher instance and additional arguments.\"\"\"\n        return None\n\n\nclass AnyMatches(Matcher):\n    \"\"\"Matches any request.\"\"\"\n\n    def match(self, request: httputil.HTTPServerRequest) -> dict[str, Any] | None:\n        return {}\n\n\nclass HostMatches(Matcher):\n    \"\"\"Matches requests from hosts specified by ``host_pattern`` regex.\"\"\"\n\n    def __init__(self, host_pattern: str | Pattern) -> None:\n        if isinstance(host_pattern, basestring_type):\n            if not host_pattern.endswith(\"$\"):\n                host_pattern += \"$\"\n            self.host_pattern = re.compile(host_pattern)\n        else:\n            self.host_pattern = host_pattern\n\n    def match(self, request: httputil.HTTPServerRequest) -> dict[str, Any] | None:\n        if self.host_pattern.match(request.host_name):\n            return {}\n\n        return None\n\n\nclass DefaultHostMatches(Matcher):\n    \"\"\"Matches requests from host that is equal to application's default_host.\n    Always returns no match if ``X-Real-Ip`` header is present.\n    \"\"\"\n\n    def __init__(self, application: Any, host_pattern: Pattern) -> None:\n        self.application = application\n        self.host_pattern = host_pattern\n\n    def match(self, request: httputil.HTTPServerRequest) -> dict[str, Any] | None:\n        # Look for default host if not behind load balancer (for debugging)\n        if \"X-Real-Ip\" not in request.headers:\n            if self.host_pattern.match(self.application.default_host):\n                return {}\n        return None\n\n\nclass PathMatches(Matcher):\n    \"\"\"Matches requests with paths specified by ``path_pattern`` regex.\"\"\"\n\n    def __init__(self, path_pattern: str | Pattern) -> None:\n        if isinstance(path_pattern, basestring_type):\n            if not path_pattern.endswith(\"$\"):\n                path_pattern += \"$\"\n            self.regex = re.compile(path_pattern)\n        else:\n            self.regex = path_pattern\n\n        assert len(self.regex.groupindex) in (0, self.regex.groups), (\n            \"groups in url regexes must either be all named or all \"\n            \"positional: %r\" % self.regex.pattern\n        )\n\n        self._path, self._group_count = self._find_groups()\n\n    def match(self, request: httputil.HTTPServerRequest) -> dict[str, Any] | None:\n        match = self.regex.match(request.path)\n        if match is None:\n            return None\n        if not self.regex.groups:\n            return {}\n\n        path_args: list[bytes] = []\n        path_kwargs: dict[str, bytes] = {}\n\n        # Pass matched groups to the handler.  Since\n        # match.groups() includes both named and\n        # unnamed groups, we want to use either groups\n        # or groupdict but not both.\n        if self.regex.groupindex:\n            path_kwargs = {\n                str(k): _unquote_or_none(v) for (k, v) in match.groupdict().items()\n            }\n        else:\n            path_args = [_unquote_or_none(s) for s in match.groups()]\n\n        return dict(path_args=path_args, path_kwargs=path_kwargs)\n\n    def reverse(self, *args: Any) -> str | None:\n        if self._path is None:\n            raise ValueError(\"Cannot reverse url regex \" + self.regex.pattern)\n        assert len(args) == self._group_count, (\n            \"required number of arguments \" \"not found\"\n        )\n        if not len(args):\n            return self._path\n        converted_args = []\n        for a in args:\n            if not isinstance(a, (unicode_type, bytes)):\n                a = str(a)\n            converted_args.append(url_escape(utf8(a), plus=False))\n        return self._path % tuple(converted_args)\n\n    def _find_groups(self) -> tuple[str | None, int | None]:\n        \"\"\"Returns a tuple (reverse string, group count) for a url.\n\n        For example: Given the url pattern /([0-9]{4})/([a-z-]+)/, this method\n        would return ('/%s/%s/', 2).\n        \"\"\"\n        pattern = self.regex.pattern\n        if pattern.startswith(\"^\"):\n            pattern = pattern[1:]\n        if pattern.endswith(\"$\"):\n            pattern = pattern[:-1]\n\n        if self.regex.groups != pattern.count(\"(\"):\n            # The pattern is too complicated for our simplistic matching,\n            # so we can't support reversing it.\n            return None, None\n\n        pieces = []\n        for fragment in pattern.split(\"(\"):\n            if \")\" in fragment:\n                paren_loc = fragment.index(\")\")\n                if paren_loc >= 0:\n                    try:\n                        unescaped_fragment = re_unescape(fragment[paren_loc + 1 :])\n                    except ValueError:\n                        # If we can't unescape part of it, we can't\n                        # reverse this url.\n                        return (None, None)\n                    pieces.append(\"%s\" + unescaped_fragment)\n            else:\n                try:\n                    unescaped_fragment = re_unescape(fragment)\n                except ValueError:\n                    # If we can't unescape part of it, we can't\n                    # reverse this url.\n                    return (None, None)\n                pieces.append(unescaped_fragment)\n\n        return \"\".join(pieces), self.regex.groups\n\n\nclass URLSpec(Rule):\n    \"\"\"Specifies mappings between URLs and handlers.\n\n    .. versionchanged: 4.5\n       `URLSpec` is now a subclass of a `Rule` with `PathMatches` matcher and is preserved for\n       backwards compatibility.\n    \"\"\"\n\n    def __init__(\n        self,\n        pattern: str | Pattern,\n        handler: Any,\n        kwargs: dict[str, Any] | None = None,\n        name: str | None = None,\n    ) -> None:\n        \"\"\"Parameters:\n\n        * ``pattern``: Regular expression to be matched. Any capturing\n          groups in the regex will be passed in to the handler's\n          get/post/etc methods as arguments (by keyword if named, by\n          position if unnamed. Named and unnamed capturing groups\n          may not be mixed in the same rule).\n\n        * ``handler``: `~.web.RequestHandler` subclass to be invoked.\n\n        * ``kwargs`` (optional): A dictionary of additional arguments\n          to be passed to the handler's constructor.\n\n        * ``name`` (optional): A name for this handler.  Used by\n          `~.web.Application.reverse_url`.\n\n        \"\"\"\n        matcher = PathMatches(pattern)\n        super().__init__(matcher, handler, kwargs, name)\n\n        self.regex = matcher.regex\n        self.handler_class = self.target\n        self.kwargs = kwargs\n\n    def __repr__(self) -> str:\n        return \"{}({!r}, {}, kwargs={!r}, name={!r})\".format(\n            self.__class__.__name__,\n            self.regex.pattern,\n            self.handler_class,\n            self.kwargs,\n            self.name,\n        )\n\n\n@overload\ndef _unquote_or_none(s: str) -> bytes:\n    pass\n\n\n@overload\ndef _unquote_or_none(s: None) -> None:\n    pass\n\n\ndef _unquote_or_none(s: str | None) -> bytes | None:\n    \"\"\"None-safe wrapper around url_unescape to handle unmatched optional\n    groups correctly.\n\n    Note that args are passed as bytes so the handler can decide what\n    encoding to use.\n    \"\"\"\n    if s is None:\n        return s\n    return url_unescape(s, encoding=None, plus=False)\n"
  },
  {
    "path": "tornado/simple_httpclient.py",
    "content": "import base64\nimport collections\nimport copy\nimport functools\nimport re\nimport socket\nimport ssl\nimport sys\nimport time\nimport urllib.parse\nfrom collections.abc import Awaitable, Callable\nfrom io import BytesIO\nfrom types import TracebackType\nfrom typing import Any, Optional, Type\n\nfrom tornado import gen, httputil, version\nfrom tornado.escape import _unicode\nfrom tornado.http1connection import HTTP1Connection, HTTP1ConnectionParameters\nfrom tornado.httpclient import (\n    AsyncHTTPClient,\n    HTTPError,\n    HTTPRequest,\n    HTTPResponse,\n    _RequestProxy,\n    main,\n)\nfrom tornado.ioloop import IOLoop\nfrom tornado.iostream import IOStream, StreamClosedError\nfrom tornado.log import gen_log\nfrom tornado.netutil import (\n    OverrideResolver,\n    Resolver,\n    _client_ssl_defaults,\n    is_valid_ip,\n)\nfrom tornado.tcpclient import TCPClient\n\n\nclass HTTPTimeoutError(HTTPError):\n    \"\"\"Error raised by SimpleAsyncHTTPClient on timeout.\n\n    For historical reasons, this is a subclass of `.HTTPClientError`\n    which simulates a response code of 599.\n\n    .. versionadded:: 5.1\n    \"\"\"\n\n    def __init__(self, message: str) -> None:\n        super().__init__(599, message=message)\n\n    def __str__(self) -> str:\n        return self.message or \"Timeout\"\n\n\nclass HTTPStreamClosedError(HTTPError):\n    \"\"\"Error raised by SimpleAsyncHTTPClient when the underlying stream is closed.\n\n    When a more specific exception is available (such as `ConnectionResetError`),\n    it may be raised instead of this one.\n\n    For historical reasons, this is a subclass of `.HTTPClientError`\n    which simulates a response code of 599.\n\n    .. versionadded:: 5.1\n    \"\"\"\n\n    def __init__(self, message: str) -> None:\n        super().__init__(599, message=message)\n\n    def __str__(self) -> str:\n        return self.message or \"Stream closed\"\n\n\nclass SimpleAsyncHTTPClient(AsyncHTTPClient):\n    \"\"\"Non-blocking HTTP client with no external dependencies.\n\n    This class implements an HTTP 1.1 client on top of Tornado's IOStreams.\n    Some features found in the curl-based AsyncHTTPClient are not yet\n    supported.  In particular, proxies are not supported, connections\n    are not reused, and callers cannot select the network interface to be\n    used.\n\n    This implementation supports the following arguments, which can be passed\n    to ``configure()`` to control the global singleton, or to the constructor\n    when ``force_instance=True``.\n\n    ``max_clients`` is the number of concurrent requests that can be\n    in progress; when this limit is reached additional requests will be\n    queued. Note that time spent waiting in this queue still counts\n    against the ``request_timeout``.\n\n    ``defaults`` is a dict of parameters that will be used as defaults on all\n    `.HTTPRequest` objects submitted to this client.\n\n    ``hostname_mapping`` is a dictionary mapping hostnames to IP addresses.\n    It can be used to make local DNS changes when modifying system-wide\n    settings like ``/etc/hosts`` is not possible or desirable (e.g. in\n    unittests). ``resolver`` is similar, but using the `.Resolver` interface\n    instead of a simple mapping.\n\n    ``max_buffer_size`` (default 100MB) is the number of bytes\n    that can be read into memory at once. ``max_body_size``\n    (defaults to ``max_buffer_size``) is the largest response body\n    that the client will accept.  Without a\n    ``streaming_callback``, the smaller of these two limits\n    applies; with a ``streaming_callback`` only ``max_body_size``\n    does.\n\n    .. versionchanged:: 4.2\n        Added the ``max_body_size`` argument.\n    \"\"\"\n\n    def initialize(  # type: ignore\n        self,\n        max_clients: int = 10,\n        hostname_mapping: dict[str, str] | None = None,\n        max_buffer_size: int = 104857600,\n        resolver: Resolver | None = None,\n        defaults: dict[str, Any] | None = None,\n        max_header_size: int | None = None,\n        max_body_size: int | None = None,\n    ) -> None:\n        super().initialize(defaults=defaults)\n        self.max_clients = max_clients\n        self.queue: collections.deque[\n            tuple[object, HTTPRequest, Callable[[HTTPResponse], None]]\n        ] = collections.deque()\n        self.active: dict[\n            object, tuple[HTTPRequest, Callable[[HTTPResponse], None]]\n        ] = {}\n        self.waiting: dict[\n            object, tuple[HTTPRequest, Callable[[HTTPResponse], None], object]\n        ] = {}\n        self.max_buffer_size = max_buffer_size\n        self.max_header_size = max_header_size\n        self.max_body_size = max_body_size\n        # TCPClient could create a Resolver for us, but we have to do it\n        # ourselves to support hostname_mapping.\n        if resolver:\n            self.resolver = resolver\n            self.own_resolver = False\n        else:\n            self.resolver = Resolver()\n            self.own_resolver = True\n        if hostname_mapping is not None:\n            self.resolver = OverrideResolver(\n                resolver=self.resolver, mapping=hostname_mapping\n            )\n        self.tcp_client = TCPClient(resolver=self.resolver)\n\n    def close(self) -> None:\n        super().close()\n        if self.own_resolver:\n            self.resolver.close()\n        self.tcp_client.close()\n\n    def fetch_impl(\n        self, request: HTTPRequest, callback: Callable[[HTTPResponse], None]\n    ) -> None:\n        key = object()\n        self.queue.append((key, request, callback))\n        assert request.connect_timeout is not None\n        assert request.request_timeout is not None\n        timeout_handle = None\n        if len(self.active) >= self.max_clients:\n            timeout = (\n                min(request.connect_timeout, request.request_timeout)\n                or request.connect_timeout\n                or request.request_timeout\n            )  # min but skip zero\n            if timeout:\n                timeout_handle = self.io_loop.add_timeout(\n                    self.io_loop.time() + timeout,\n                    functools.partial(self._on_timeout, key, \"in request queue\"),\n                )\n        self.waiting[key] = (request, callback, timeout_handle)\n        self._process_queue()\n        if self.queue:\n            gen_log.debug(\n                \"max_clients limit reached, request queued. \"\n                \"%d active, %d queued requests.\" % (len(self.active), len(self.queue))\n            )\n\n    def _process_queue(self) -> None:\n        while self.queue and len(self.active) < self.max_clients:\n            key, request, callback = self.queue.popleft()\n            if key not in self.waiting:\n                continue\n            self._remove_timeout(key)\n            self.active[key] = (request, callback)\n            release_callback = functools.partial(self._release_fetch, key)\n            self._handle_request(request, release_callback, callback)\n\n    def _connection_class(self) -> type:\n        return _HTTPConnection\n\n    def _handle_request(\n        self,\n        request: HTTPRequest,\n        release_callback: Callable[[], None],\n        final_callback: Callable[[HTTPResponse], None],\n    ) -> None:\n        self._connection_class()(\n            self,\n            request,\n            release_callback,\n            final_callback,\n            self.max_buffer_size,\n            self.tcp_client,\n            self.max_header_size,\n            self.max_body_size,\n        )\n\n    def _release_fetch(self, key: object) -> None:\n        del self.active[key]\n        self._process_queue()\n\n    def _remove_timeout(self, key: object) -> None:\n        if key in self.waiting:\n            request, callback, timeout_handle = self.waiting[key]\n            if timeout_handle is not None:\n                self.io_loop.remove_timeout(timeout_handle)\n            del self.waiting[key]\n\n    def _on_timeout(self, key: object, info: str | None = None) -> None:\n        \"\"\"Timeout callback of request.\n\n        Construct a timeout HTTPResponse when a timeout occurs.\n\n        :arg object key: A simple object to mark the request.\n        :info string key: More detailed timeout information.\n        \"\"\"\n        request, callback, timeout_handle = self.waiting[key]\n        self.queue.remove((key, request, callback))\n\n        error_message = f\"Timeout {info}\" if info else \"Timeout\"\n        timeout_response = HTTPResponse(\n            request,\n            599,\n            error=HTTPTimeoutError(error_message),\n            request_time=self.io_loop.time() - request.start_time,\n        )\n        self.io_loop.add_callback(callback, timeout_response)\n        del self.waiting[key]\n\n\nclass _HTTPConnection(httputil.HTTPMessageDelegate):\n    _SUPPORTED_METHODS = {\"GET\", \"HEAD\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\", \"OPTIONS\"}\n\n    def __init__(\n        self,\n        client: SimpleAsyncHTTPClient | None,\n        request: HTTPRequest,\n        release_callback: Callable[[], None],\n        final_callback: Callable[[HTTPResponse], None],\n        max_buffer_size: int,\n        tcp_client: TCPClient,\n        max_header_size: int,\n        max_body_size: int,\n    ) -> None:\n        self.io_loop = IOLoop.current()\n        self.start_time = self.io_loop.time()\n        self.start_wall_time = time.time()\n        self.client = client\n        self.request = request\n        self.release_callback = release_callback\n        self.final_callback = final_callback\n        self.max_buffer_size = max_buffer_size\n        self.tcp_client = tcp_client\n        self.max_header_size = max_header_size\n        self.max_body_size = max_body_size\n        self.code: int | None = None\n        self.headers: httputil.HTTPHeaders | None = None\n        self.chunks: list[bytes] = []\n        self._decompressor = None\n        # Timeout handle returned by IOLoop.add_timeout\n        self._timeout: object = None\n        self._sockaddr = None\n        IOLoop.current().add_future(\n            gen.convert_yielded(self.run()), lambda f: f.result()\n        )\n\n    async def run(self) -> None:\n        try:\n            self.parsed = urllib.parse.urlsplit(_unicode(self.request.url))\n            if self.parsed.scheme not in (\"http\", \"https\"):\n                raise ValueError(\"Unsupported url scheme: %s\" % self.request.url)\n            # urlsplit results have hostname and port results, but they\n            # didn't support ipv6 literals until python 2.7.\n            netloc = self.parsed.netloc\n            if \"@\" in netloc:\n                userpass, _, netloc = netloc.rpartition(\"@\")\n            host, port = httputil.split_host_and_port(netloc)\n            if port is None:\n                port = 443 if self.parsed.scheme == \"https\" else 80\n            if re.match(r\"^\\[.*\\]$\", host):\n                # raw ipv6 addresses in urls are enclosed in brackets\n                host = host[1:-1]\n            self.parsed_hostname = host  # save final host for _on_connect\n\n            if self.request.allow_ipv6 is False:\n                af = socket.AF_INET\n            else:\n                af = socket.AF_UNSPEC\n\n            ssl_options = self._get_ssl_options(self.parsed.scheme)\n\n            source_ip = None\n            if self.request.network_interface:\n                if is_valid_ip(self.request.network_interface):\n                    source_ip = self.request.network_interface\n                else:\n                    raise ValueError(\n                        \"Unrecognized IPv4 or IPv6 address for network_interface, got %r\"\n                        % (self.request.network_interface,)\n                    )\n\n            if self.request.connect_timeout and self.request.request_timeout:\n                timeout = min(\n                    self.request.connect_timeout, self.request.request_timeout\n                )\n            elif self.request.connect_timeout:\n                timeout = self.request.connect_timeout\n            elif self.request.request_timeout:\n                timeout = self.request.request_timeout\n            else:\n                timeout = 0\n            if timeout:\n                self._timeout = self.io_loop.add_timeout(\n                    self.start_time + timeout,\n                    functools.partial(self._on_timeout, \"while connecting\"),\n                )\n            stream = await self.tcp_client.connect(\n                host,\n                port,\n                af=af,\n                ssl_options=ssl_options,\n                max_buffer_size=self.max_buffer_size,\n                source_ip=source_ip,\n            )\n\n            if self.final_callback is None:\n                # final_callback is cleared if we've hit our timeout.\n                stream.close()\n                return\n            self.stream = stream\n            self.stream.set_close_callback(self.on_connection_close)\n            self._remove_timeout()\n            if self.final_callback is None:\n                return\n            if self.request.request_timeout:\n                self._timeout = self.io_loop.add_timeout(\n                    self.start_time + self.request.request_timeout,\n                    functools.partial(self._on_timeout, \"during request\"),\n                )\n            if (\n                self.request.method not in self._SUPPORTED_METHODS\n                and not self.request.allow_nonstandard_methods\n            ):\n                raise KeyError(\"unknown method %s\" % self.request.method)\n            for key in (\n                \"proxy_host\",\n                \"proxy_port\",\n                \"proxy_username\",\n                \"proxy_password\",\n                \"proxy_auth_mode\",\n            ):\n                if getattr(self.request, key, None):\n                    raise NotImplementedError(\"%s not supported\" % key)\n            if \"Connection\" not in self.request.headers:\n                self.request.headers[\"Connection\"] = \"close\"\n            if \"Host\" not in self.request.headers:\n                if \"@\" in self.parsed.netloc:\n                    self.request.headers[\"Host\"] = self.parsed.netloc.rpartition(\"@\")[\n                        -1\n                    ]\n                else:\n                    self.request.headers[\"Host\"] = self.parsed.netloc\n            username, password = None, None\n            if self.parsed.username is not None:\n                username, password = self.parsed.username, self.parsed.password\n            elif self.request.auth_username is not None:\n                username = self.request.auth_username\n                password = self.request.auth_password or \"\"\n            if username is not None:\n                assert password is not None\n                if self.request.auth_mode not in (None, \"basic\"):\n                    raise ValueError(\"unsupported auth_mode %s\", self.request.auth_mode)\n                self.request.headers[\"Authorization\"] = \"Basic \" + _unicode(\n                    base64.b64encode(\n                        httputil.encode_username_password(username, password)\n                    )\n                )\n            if self.request.user_agent:\n                self.request.headers[\"User-Agent\"] = self.request.user_agent\n            elif self.request.headers.get(\"User-Agent\") is None:\n                self.request.headers[\"User-Agent\"] = f\"Tornado/{version}\"\n            if not self.request.allow_nonstandard_methods:\n                # Some HTTP methods nearly always have bodies while others\n                # almost never do. Fail in this case unless the user has\n                # opted out of sanity checks with allow_nonstandard_methods.\n                body_expected = self.request.method in (\"POST\", \"PATCH\", \"PUT\")\n                body_present = (\n                    self.request.body is not None\n                    or self.request.body_producer is not None\n                )\n                if (body_expected and not body_present) or (\n                    body_present and not body_expected\n                ):\n                    raise ValueError(\n                        \"Body must %sbe None for method %s (unless \"\n                        \"allow_nonstandard_methods is true)\"\n                        % (\"not \" if body_expected else \"\", self.request.method)\n                    )\n            if self.request.expect_100_continue:\n                self.request.headers[\"Expect\"] = \"100-continue\"\n            if self.request.body is not None:\n                # When body_producer is used the caller is responsible for\n                # setting Content-Length (or else chunked encoding will be used).\n                self.request.headers[\"Content-Length\"] = str(len(self.request.body))\n            if (\n                self.request.method == \"POST\"\n                and \"Content-Type\" not in self.request.headers\n            ):\n                self.request.headers[\"Content-Type\"] = (\n                    \"application/x-www-form-urlencoded\"\n                )\n            if self.request.decompress_response:\n                self.request.headers[\"Accept-Encoding\"] = \"gzip\"\n            req_path = (self.parsed.path or \"/\") + (\n                (\"?\" + self.parsed.query) if self.parsed.query else \"\"\n            )\n            self.connection = self._create_connection(stream)\n            start_line = httputil.RequestStartLine(self.request.method, req_path, \"\")\n            self.connection.write_headers(start_line, self.request.headers)\n            if self.request.expect_100_continue:\n                await self.connection.read_response(self)\n            else:\n                await self._write_body(True)\n        except Exception:\n            if not self._handle_exception(*sys.exc_info()):\n                raise\n\n    def _get_ssl_options(self, scheme: str) -> None | dict[str, Any] | ssl.SSLContext:\n        if scheme == \"https\":\n            if self.request.ssl_options is not None:\n                return self.request.ssl_options\n            # If we are using the defaults, don't construct a\n            # new SSLContext.\n            if (\n                self.request.validate_cert\n                and self.request.ca_certs is None\n                and self.request.client_cert is None\n                and self.request.client_key is None\n            ):\n                return _client_ssl_defaults\n            ssl_ctx = ssl.create_default_context(\n                ssl.Purpose.SERVER_AUTH, cafile=self.request.ca_certs\n            )\n            if not self.request.validate_cert:\n                ssl_ctx.check_hostname = False\n                ssl_ctx.verify_mode = ssl.CERT_NONE\n            if self.request.client_cert is not None:\n                ssl_ctx.load_cert_chain(\n                    self.request.client_cert, self.request.client_key\n                )\n            if hasattr(ssl, \"OP_NO_COMPRESSION\"):\n                # See netutil.ssl_options_to_context\n                ssl_ctx.options |= ssl.OP_NO_COMPRESSION\n            return ssl_ctx\n        return None\n\n    def _on_timeout(self, info: str | None = None) -> None:\n        \"\"\"Timeout callback of _HTTPConnection instance.\n\n        Raise a `HTTPTimeoutError` when a timeout occurs.\n\n        :info string key: More detailed timeout information.\n        \"\"\"\n        self._timeout = None\n        error_message = f\"Timeout {info}\" if info else \"Timeout\"\n        if self.final_callback is not None:\n            self._handle_exception(\n                HTTPTimeoutError, HTTPTimeoutError(error_message), None\n            )\n\n    def _remove_timeout(self) -> None:\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n            self._timeout = None\n\n    def _create_connection(self, stream: IOStream) -> HTTP1Connection:\n        stream.set_nodelay(True)\n        connection = HTTP1Connection(\n            stream,\n            True,\n            HTTP1ConnectionParameters(\n                no_keep_alive=True,\n                max_header_size=self.max_header_size,\n                max_body_size=self.max_body_size,\n                decompress=bool(self.request.decompress_response),\n            ),\n            self._sockaddr,\n        )\n        return connection\n\n    async def _write_body(self, start_read: bool) -> None:\n        if self.request.body is not None:\n            self.connection.write(self.request.body)\n        elif self.request.body_producer is not None:\n            fut = self.request.body_producer(self.connection.write)\n            if fut is not None:\n                await fut\n        self.connection.finish()\n        if start_read:\n            try:\n                await self.connection.read_response(self)\n            except StreamClosedError:\n                if not self._handle_exception(*sys.exc_info()):\n                    raise\n\n    def _release(self) -> None:\n        if self.release_callback is not None:\n            release_callback = self.release_callback\n            self.release_callback = None  # type: ignore\n            release_callback()\n\n    def _run_callback(self, response: HTTPResponse) -> None:\n        self._release()\n        if self.final_callback is not None:\n            final_callback = self.final_callback\n            self.final_callback = None  # type: ignore\n            self.io_loop.add_callback(final_callback, response)\n\n    def _handle_exception(\n        self,\n        typ: \"Optional[Type[BaseException]]\",\n        value: BaseException | None,\n        tb: TracebackType | None,\n    ) -> bool:\n        if self.final_callback is not None:\n            self._remove_timeout()\n            if isinstance(value, StreamClosedError):\n                if value.real_error is None:\n                    value = HTTPStreamClosedError(\"Stream closed\")\n                else:\n                    value = value.real_error\n            self._run_callback(\n                HTTPResponse(\n                    self.request,\n                    599,\n                    error=value,\n                    request_time=self.io_loop.time() - self.start_time,\n                    start_time=self.start_wall_time,\n                )\n            )\n\n            if hasattr(self, \"stream\"):\n                # TODO: this may cause a StreamClosedError to be raised\n                # by the connection's Future.  Should we cancel the\n                # connection more gracefully?\n                self.stream.close()\n            return True\n        else:\n            # If our callback has already been called, we are probably\n            # catching an exception that is not caused by us but rather\n            # some child of our callback. Rather than drop it on the floor,\n            # pass it along, unless it's just the stream being closed.\n            return isinstance(value, StreamClosedError)\n\n    def on_connection_close(self) -> None:\n        if self.final_callback is not None:\n            message = \"Connection closed\"\n            if self.stream.error:\n                raise self.stream.error\n            try:\n                raise HTTPStreamClosedError(message)\n            except HTTPStreamClosedError:\n                self._handle_exception(*sys.exc_info())\n\n    async def headers_received(\n        self,\n        first_line: httputil.ResponseStartLine | httputil.RequestStartLine,\n        headers: httputil.HTTPHeaders,\n    ) -> None:\n        assert isinstance(first_line, httputil.ResponseStartLine)\n        if self.request.expect_100_continue and first_line.code == 100:\n            await self._write_body(False)\n            return\n        self.code = first_line.code\n        self.reason = first_line.reason\n        self.headers = headers\n\n        if self._should_follow_redirect():\n            return\n\n        if self.request.header_callback is not None:\n            # Reassemble the start line.\n            self.request.header_callback(\"%s %s %s\\r\\n\" % first_line)\n            for k, v in self.headers.get_all():\n                self.request.header_callback(f\"{k}: {v}\\r\\n\")\n            self.request.header_callback(\"\\r\\n\")\n\n    def _should_follow_redirect(self) -> bool:\n        if self.request.follow_redirects:\n            assert self.request.max_redirects is not None\n            return (\n                self.code in (301, 302, 303, 307, 308)\n                and self.request.max_redirects > 0\n                and self.headers is not None\n                and self.headers.get(\"Location\") is not None\n            )\n        return False\n\n    def finish(self) -> None:\n        assert self.code is not None\n        data = b\"\".join(self.chunks)\n        self._remove_timeout()\n        original_request = getattr(self.request, \"original_request\", self.request)\n        if self._should_follow_redirect():\n            assert isinstance(self.request, _RequestProxy)\n            assert self.headers is not None\n            new_request = copy.copy(self.request.request)\n            new_request.url = urllib.parse.urljoin(\n                self.request.url, self.headers[\"Location\"]\n            )\n            assert self.request.max_redirects is not None\n            new_request.max_redirects = self.request.max_redirects - 1\n            del new_request.headers[\"Host\"]\n            # https://tools.ietf.org/html/rfc7231#section-6.4\n            #\n            # The original HTTP spec said that after a 301 or 302\n            # redirect, the request method should be preserved.\n            # However, browsers implemented this by changing the\n            # method to GET, and the behavior stuck. 303 redirects\n            # always specified this POST-to-GET behavior, arguably\n            # for *all* methods, but libcurl < 7.70 only does this\n            # for POST, while libcurl >= 7.70 does it for other methods.\n            if (self.code == 303 and self.request.method != \"HEAD\") or (\n                self.code in (301, 302) and self.request.method == \"POST\"\n            ):\n                new_request.method = \"GET\"\n                new_request.body = None  # type: ignore\n                for h in [\n                    \"Content-Length\",\n                    \"Content-Type\",\n                    \"Content-Encoding\",\n                    \"Transfer-Encoding\",\n                ]:\n                    try:\n                        del self.request.headers[h]\n                    except KeyError:\n                        pass\n            new_request.original_request = original_request  # type: ignore\n            final_callback = self.final_callback\n            self.final_callback = None  # type: ignore\n            self._release()\n            assert self.client is not None\n            fut = self.client.fetch(new_request, raise_error=False)\n            fut.add_done_callback(lambda f: final_callback(f.result()))\n            self._on_end_request()\n            return\n        if self.request.streaming_callback:\n            buffer = BytesIO()\n        else:\n            buffer = BytesIO(data)  # TODO: don't require one big string?\n        response = HTTPResponse(\n            original_request,\n            self.code,\n            reason=getattr(self, \"reason\", None),\n            headers=self.headers,\n            request_time=self.io_loop.time() - self.start_time,\n            start_time=self.start_wall_time,\n            buffer=buffer,\n            effective_url=self.request.url,\n        )\n        self._run_callback(response)\n        self._on_end_request()\n\n    def _on_end_request(self) -> None:\n        self.stream.close()\n\n    def data_received(self, chunk: bytes) -> Awaitable[None] | None:\n        if self._should_follow_redirect():\n            # We're going to follow a redirect so just discard the body.\n            return None\n        if self.request.streaming_callback is not None:\n            return self.request.streaming_callback(chunk)\n        else:\n            self.chunks.append(chunk)\n            return None\n\n\nif __name__ == \"__main__\":\n    AsyncHTTPClient.configure(SimpleAsyncHTTPClient)\n    main()\n"
  },
  {
    "path": "tornado/speedups.c",
    "content": "#define PY_SSIZE_T_CLEAN\n#include <Python.h>\n#include <stdint.h>\n\nstatic PyObject* websocket_mask(PyObject* self, PyObject* args) {\n    const char* mask;\n    Py_ssize_t mask_len;\n    uint32_t uint32_mask;\n    uint64_t uint64_mask;\n    const char* data;\n    Py_ssize_t data_len;\n    Py_ssize_t i;\n    PyObject* result;\n    char* buf;\n\n    if (!PyArg_ParseTuple(args, \"s#s#\", &mask, &mask_len, &data, &data_len)) {\n        return NULL;\n    }\n\n    uint32_mask = ((uint32_t*)mask)[0];\n\n    result = PyBytes_FromStringAndSize(NULL, data_len);\n    if (!result) {\n        return NULL;\n    }\n    buf = PyBytes_AsString(result);\n\n    if (sizeof(size_t) >= 8) {\n        uint64_mask = uint32_mask;\n        uint64_mask = (uint64_mask << 32) | uint32_mask;\n\n        while (data_len >= 8) {\n            ((uint64_t*)buf)[0] = ((uint64_t*)data)[0] ^ uint64_mask;\n            data += 8;\n            buf += 8;\n            data_len -= 8;\n        }\n    }\n\n    while (data_len >= 4) {\n        ((uint32_t*)buf)[0] = ((uint32_t*)data)[0] ^ uint32_mask;\n        data += 4;\n        buf += 4;\n        data_len -= 4;\n    }\n\n    for (i = 0; i < data_len; i++) {\n        buf[i] = data[i] ^ mask[i];\n    }\n\n    return result;\n}\n\nstatic int speedups_exec(PyObject *module) {\n    return 0;\n}\n\nstatic PyMethodDef methods[] = {\n    {\"websocket_mask\",  websocket_mask, METH_VARARGS, \"\"},\n    {NULL, NULL, 0, NULL}\n};\n\nstatic PyModuleDef_Slot slots[] = {\n    {Py_mod_exec, speedups_exec},\n#if (!defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030c0000) || Py_LIMITED_API >= 0x030c0000\n    {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},\n#endif\n#if (!defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030d0000) || Py_LIMITED_API >= 0x030d0000\n    {Py_mod_gil, Py_MOD_GIL_NOT_USED},\n#endif\n    {0, NULL}\n};\n\nstatic struct PyModuleDef speedupsmodule = {\n   PyModuleDef_HEAD_INIT,\n   \"speedups\",\n   NULL,\n   0,\n   methods,\n   slots,\n};\n\nPyMODINIT_FUNC\nPyInit_speedups(void) {\n    return PyModuleDef_Init(&speedupsmodule);\n}\n"
  },
  {
    "path": "tornado/speedups.pyi",
    "content": "def websocket_mask(mask: bytes, data: bytes) -> bytes: ...\n"
  },
  {
    "path": "tornado/tcpclient.py",
    "content": "#\n# Copyright 2014 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"A non-blocking TCP connection factory.\"\"\"\n\nimport datetime\nimport functools\nimport numbers\nimport socket\nimport ssl\nfrom collections.abc import Callable, Iterator\nfrom typing import Any, Tuple\n\nfrom tornado import gen\nfrom tornado.concurrent import Future, future_add_done_callback\nfrom tornado.gen import TimeoutError\nfrom tornado.ioloop import IOLoop\nfrom tornado.iostream import IOStream\nfrom tornado.netutil import Resolver\n\n_INITIAL_CONNECT_TIMEOUT = 0.3\n\n\nclass _Connector:\n    \"\"\"A stateless implementation of the \"Happy Eyeballs\" algorithm.\n\n    \"Happy Eyeballs\" is documented in RFC6555 as the recommended practice\n    for when both IPv4 and IPv6 addresses are available.\n\n    In this implementation, we partition the addresses by family, and\n    make the first connection attempt to whichever address was\n    returned first by ``getaddrinfo``.  If that connection fails or\n    times out, we begin a connection in parallel to the first address\n    of the other family.  If there are additional failures we retry\n    with other addresses, keeping one connection attempt per family\n    in flight at a time.\n\n    http://tools.ietf.org/html/rfc6555\n\n    \"\"\"\n\n    def __init__(\n        self,\n        addrinfo: list[tuple],\n        connect: Callable[\n            [socket.AddressFamily, tuple], tuple[IOStream, \"Future[IOStream]\"]\n        ],\n    ) -> None:\n        self.io_loop = IOLoop.current()\n        self.connect = connect\n\n        self.future: Future[tuple[socket.AddressFamily, Any, IOStream]] = Future()\n        self.timeout: object | None = None\n        self.connect_timeout: object | None = None\n        self.last_error: Exception | None = None\n        self.remaining = len(addrinfo)\n        self.primary_addrs, self.secondary_addrs = self.split(addrinfo)\n        self.streams: set[IOStream] = set()\n\n    @staticmethod\n    def split(\n        addrinfo: list[tuple],\n    ) -> tuple[\n        list[tuple[socket.AddressFamily, tuple]],\n        list[tuple[socket.AddressFamily, tuple]],\n    ]:\n        \"\"\"Partition the ``addrinfo`` list by address family.\n\n        Returns two lists.  The first list contains the first entry from\n        ``addrinfo`` and all others with the same family, and the\n        second list contains all other addresses (normally one list will\n        be AF_INET and the other AF_INET6, although non-standard resolvers\n        may return additional families).\n        \"\"\"\n        primary = []\n        secondary = []\n        primary_af = addrinfo[0][0]\n        for af, addr in addrinfo:\n            if af == primary_af:\n                primary.append((af, addr))\n            else:\n                secondary.append((af, addr))\n        return primary, secondary\n\n    def start(\n        self,\n        timeout: float = _INITIAL_CONNECT_TIMEOUT,\n        connect_timeout: float | datetime.timedelta | None = None,\n    ) -> \"Future[Tuple[socket.AddressFamily, Any, IOStream]]\":\n        self.try_connect(iter(self.primary_addrs))\n        self.set_timeout(timeout)\n        if connect_timeout is not None:\n            self.set_connect_timeout(connect_timeout)\n        return self.future\n\n    def try_connect(self, addrs: Iterator[tuple[socket.AddressFamily, tuple]]) -> None:\n        try:\n            af, addr = next(addrs)\n        except StopIteration:\n            # We've reached the end of our queue, but the other queue\n            # might still be working.  Send a final error on the future\n            # only when both queues are finished.\n            if self.remaining == 0 and not self.future.done():\n                self.future.set_exception(\n                    self.last_error or IOError(\"connection failed\")\n                )\n            return\n        stream, future = self.connect(af, addr)\n        self.streams.add(stream)\n        future_add_done_callback(\n            future, functools.partial(self.on_connect_done, addrs, af, addr)\n        )\n\n    def on_connect_done(\n        self,\n        addrs: Iterator[tuple[socket.AddressFamily, tuple]],\n        af: socket.AddressFamily,\n        addr: tuple,\n        future: \"Future[IOStream]\",\n    ) -> None:\n        self.remaining -= 1\n        try:\n            stream = future.result()\n        except Exception as e:\n            if self.future.done():\n                return\n            # Error: try again (but remember what happened so we have an\n            # error to raise in the end)\n            self.last_error = e\n            self.try_connect(addrs)\n            if self.timeout is not None:\n                # If the first attempt failed, don't wait for the\n                # timeout to try an address from the secondary queue.\n                self.io_loop.remove_timeout(self.timeout)\n                self.on_timeout()\n            return\n        self.clear_timeouts()\n        if self.future.done():\n            # This is a late arrival; just drop it.\n            stream.close()\n        else:\n            self.streams.discard(stream)\n            self.future.set_result((af, addr, stream))\n            self.close_streams()\n\n    def set_timeout(self, timeout: float) -> None:\n        self.timeout = self.io_loop.add_timeout(\n            self.io_loop.time() + timeout, self.on_timeout\n        )\n\n    def on_timeout(self) -> None:\n        self.timeout = None\n        if not self.future.done():\n            self.try_connect(iter(self.secondary_addrs))\n\n    def clear_timeout(self) -> None:\n        if self.timeout is not None:\n            self.io_loop.remove_timeout(self.timeout)\n\n    def set_connect_timeout(self, connect_timeout: float | datetime.timedelta) -> None:\n        self.connect_timeout = self.io_loop.add_timeout(\n            connect_timeout, self.on_connect_timeout\n        )\n\n    def on_connect_timeout(self) -> None:\n        if not self.future.done():\n            self.future.set_exception(TimeoutError())\n        self.close_streams()\n\n    def clear_timeouts(self) -> None:\n        if self.timeout is not None:\n            self.io_loop.remove_timeout(self.timeout)\n        if self.connect_timeout is not None:\n            self.io_loop.remove_timeout(self.connect_timeout)\n\n    def close_streams(self) -> None:\n        for stream in self.streams:\n            stream.close()\n\n\nclass TCPClient:\n    \"\"\"A non-blocking TCP connection factory.\n\n    .. versionchanged:: 5.0\n       The ``io_loop`` argument (deprecated since version 4.1) has been removed.\n    \"\"\"\n\n    def __init__(self, resolver: Resolver | None = None) -> None:\n        if resolver is not None:\n            self.resolver = resolver\n            self._own_resolver = False\n        else:\n            self.resolver = Resolver()\n            self._own_resolver = True\n\n    def close(self) -> None:\n        if self._own_resolver:\n            self.resolver.close()\n\n    async def connect(\n        self,\n        host: str,\n        port: int,\n        af: socket.AddressFamily = socket.AF_UNSPEC,\n        ssl_options: dict[str, Any] | ssl.SSLContext | None = None,\n        max_buffer_size: int | None = None,\n        source_ip: str | None = None,\n        source_port: int | None = None,\n        timeout: float | datetime.timedelta | None = None,\n    ) -> IOStream:\n        \"\"\"Connect to the given host and port.\n\n        Asynchronously returns an `.IOStream` (or `.SSLIOStream` if\n        ``ssl_options`` is not None).\n\n        Using the ``source_ip`` kwarg, one can specify the source\n        IP address to use when establishing the connection.\n        In case the user needs to resolve and\n        use a specific interface, it has to be handled outside\n        of Tornado as this depends very much on the platform.\n\n        Raises `TimeoutError` if the input future does not complete before\n        ``timeout``, which may be specified in any form allowed by\n        `.IOLoop.add_timeout` (i.e. a `datetime.timedelta` or an absolute time\n        relative to `.IOLoop.time`)\n\n        Similarly, when the user requires a certain source port, it can\n        be specified using the ``source_port`` arg.\n\n        .. versionchanged:: 4.5\n           Added the ``source_ip`` and ``source_port`` arguments.\n\n        .. versionchanged:: 5.0\n           Added the ``timeout`` argument.\n        \"\"\"\n        if timeout is not None:\n            if isinstance(timeout, numbers.Real):\n                timeout = IOLoop.current().time() + timeout\n            elif isinstance(timeout, datetime.timedelta):\n                timeout = IOLoop.current().time() + timeout.total_seconds()\n            else:\n                raise TypeError(\"Unsupported timeout %r\" % timeout)\n        if timeout is not None:\n            addrinfo = await gen.with_timeout(\n                timeout, self.resolver.resolve(host, port, af)\n            )\n        else:\n            addrinfo = await self.resolver.resolve(host, port, af)\n        connector = _Connector(\n            addrinfo,\n            functools.partial(\n                self._create_stream,\n                max_buffer_size,\n                source_ip=source_ip,\n                source_port=source_port,\n            ),\n        )\n        af, addr, stream = await connector.start(connect_timeout=timeout)\n        # TODO: For better performance we could cache the (af, addr)\n        # information here and re-use it on subsequent connections to\n        # the same host. (http://tools.ietf.org/html/rfc6555#section-4.2)\n        if ssl_options is not None:\n            if timeout is not None:\n                stream = await gen.with_timeout(\n                    timeout,\n                    stream.start_tls(\n                        False, ssl_options=ssl_options, server_hostname=host\n                    ),\n                )\n            else:\n                stream = await stream.start_tls(\n                    False, ssl_options=ssl_options, server_hostname=host\n                )\n        return stream\n\n    def _create_stream(\n        self,\n        max_buffer_size: int | None,\n        af: socket.AddressFamily,\n        addr: tuple,\n        source_ip: str | None = None,\n        source_port: int | None = None,\n    ) -> tuple[IOStream, \"Future[IOStream]\"]:\n        # Always connect in plaintext; we'll convert to ssl if necessary\n        # after one connection has completed.\n        source_port_bind = source_port if isinstance(source_port, int) else 0\n        source_ip_bind = source_ip\n        if source_port_bind and not source_ip:\n            # User required a specific port, but did not specify\n            # a certain source IP, will bind to the default loopback.\n            source_ip_bind = \"::1\" if af == socket.AF_INET6 else \"127.0.0.1\"\n            # Trying to use the same address family as the requested af socket:\n            # - 127.0.0.1 for IPv4\n            # - ::1 for IPv6\n        socket_obj = socket.socket(af)\n        if source_port_bind or source_ip_bind:\n            # If the user requires binding also to a specific IP/port.\n            try:\n                socket_obj.bind((source_ip_bind, source_port_bind))\n            except OSError:\n                socket_obj.close()\n                # Fail loudly if unable to use the IP/port.\n                raise\n        try:\n            stream = IOStream(socket_obj, max_buffer_size=max_buffer_size)\n        except OSError as e:\n            fu: Future[IOStream] = Future()\n            fu.set_exception(e)\n            return stream, fu\n        else:\n            return stream, stream.connect(addr)\n"
  },
  {
    "path": "tornado/tcpserver.py",
    "content": "#\n# Copyright 2011 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"A non-blocking, single-threaded TCP server.\"\"\"\n\nimport errno\nimport os\nimport socket\nimport ssl\nfrom collections.abc import Awaitable, Callable, Iterable\nfrom typing import Any\n\nfrom tornado import gen, process\nfrom tornado.ioloop import IOLoop\nfrom tornado.iostream import IOStream, SSLIOStream\nfrom tornado.log import app_log\nfrom tornado.netutil import (\n    _DEFAULT_BACKLOG,\n    add_accept_handler,\n    bind_sockets,\n    ssl_wrap_socket,\n)\nfrom tornado.util import errno_from_exception\n\n\nclass TCPServer:\n    r\"\"\"A non-blocking, single-threaded TCP server.\n\n    To use `TCPServer`, define a subclass which overrides the `handle_stream`\n    method. For example, a simple echo server could be defined like this::\n\n      from tornado.tcpserver import TCPServer\n      from tornado.iostream import StreamClosedError\n\n      class EchoServer(TCPServer):\n          async def handle_stream(self, stream, address):\n              while True:\n                  try:\n                      data = await stream.read_until(b\"\\n\") await\n                      stream.write(data)\n                  except StreamClosedError:\n                      break\n\n    To make this server serve SSL traffic, send the ``ssl_options`` keyword\n    argument with an `ssl.SSLContext` object. For compatibility with older\n    versions of Python ``ssl_options`` may also be a dictionary of keyword\n    arguments for the `ssl.SSLContext.wrap_socket` method.::\n\n       ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)\n       ssl_ctx.load_cert_chain(os.path.join(data_dir, \"mydomain.crt\"),\n                               os.path.join(data_dir, \"mydomain.key\"))\n       TCPServer(ssl_options=ssl_ctx)\n\n    `TCPServer` initialization follows one of three patterns:\n\n    1. `listen`: single-process::\n\n            async def main():\n                server = TCPServer()\n                server.listen(8888)\n                await asyncio.Event().wait()\n\n            asyncio.run(main())\n\n       While this example does not create multiple processes on its own, when\n       the ``reuse_port=True`` argument is passed to ``listen()`` you can run\n       the program multiple times to create a multi-process service.\n\n    2. `add_sockets`: multi-process::\n\n            sockets = bind_sockets(8888)\n            tornado.process.fork_processes(0)\n            async def post_fork_main():\n                server = TCPServer()\n                server.add_sockets(sockets)\n                await asyncio.Event().wait()\n            asyncio.run(post_fork_main())\n\n       The `add_sockets` interface is more complicated, but it can be used with\n       `tornado.process.fork_processes` to run a multi-process service with all\n       worker processes forked from a single parent.  `add_sockets` can also be\n       used in single-process servers if you want to create your listening\n       sockets in some way other than `~tornado.netutil.bind_sockets`.\n\n       Note that when using this pattern, nothing that touches the event loop\n       can be run before ``fork_processes``.\n\n    3. `bind`/`start`: simple **deprecated** multi-process::\n\n            server = TCPServer()\n            server.bind(8888)\n            server.start(0)  # Forks multiple sub-processes\n            IOLoop.current().start()\n\n       This pattern is deprecated because it requires interfaces in the\n       `asyncio` module that have been deprecated since Python 3.10. Support for\n       creating multiple processes in the ``start`` method will be removed in a\n       future version of Tornado.\n\n    .. versionadded:: 3.1\n       The ``max_buffer_size`` argument.\n\n    .. versionchanged:: 5.0\n       The ``io_loop`` argument has been removed.\n    \"\"\"\n\n    def __init__(\n        self,\n        ssl_options: dict[str, Any] | ssl.SSLContext | None = None,\n        max_buffer_size: int | None = None,\n        read_chunk_size: int | None = None,\n    ) -> None:\n        self.ssl_options = ssl_options\n        self._sockets: dict[int, socket.socket] = {}\n        self._handlers: dict[int, Callable[[], None]] = {}\n        self._pending_sockets: list[socket.socket] = []\n        self._started = False\n        self._stopped = False\n        self.max_buffer_size = max_buffer_size\n        self.read_chunk_size = read_chunk_size\n\n        # Verify the SSL options. Otherwise we don't get errors until clients\n        # connect. This doesn't verify that the keys are legitimate, but\n        # the SSL module doesn't do that until there is a connected socket\n        # which seems like too much work\n        if self.ssl_options is not None and isinstance(self.ssl_options, dict):\n            # Only certfile is required: it can contain both keys\n            if \"certfile\" not in self.ssl_options:\n                raise KeyError('missing key \"certfile\" in ssl_options')\n\n            if not os.path.exists(self.ssl_options[\"certfile\"]):\n                raise ValueError(\n                    'certfile \"%s\" does not exist' % self.ssl_options[\"certfile\"]\n                )\n            if \"keyfile\" in self.ssl_options and not os.path.exists(\n                self.ssl_options[\"keyfile\"]\n            ):\n                raise ValueError(\n                    'keyfile \"%s\" does not exist' % self.ssl_options[\"keyfile\"]\n                )\n\n    def listen(\n        self,\n        port: int,\n        address: str | None = None,\n        family: socket.AddressFamily = socket.AF_UNSPEC,\n        backlog: int = _DEFAULT_BACKLOG,\n        flags: int | None = None,\n        reuse_port: bool = False,\n    ) -> None:\n        \"\"\"Starts accepting connections on the given port.\n\n        This method may be called more than once to listen on multiple ports.\n        `listen` takes effect immediately; it is not necessary to call\n        `TCPServer.start` afterwards.  It is, however, necessary to start the\n        event loop if it is not already running.\n\n        All arguments have the same meaning as in\n        `tornado.netutil.bind_sockets`.\n\n        .. versionchanged:: 6.2\n\n           Added ``family``, ``backlog``, ``flags``, and ``reuse_port``\n           arguments to match `tornado.netutil.bind_sockets`.\n        \"\"\"\n        sockets = bind_sockets(\n            port,\n            address=address,\n            family=family,\n            backlog=backlog,\n            flags=flags,\n            reuse_port=reuse_port,\n        )\n        self.add_sockets(sockets)\n\n    def add_sockets(self, sockets: Iterable[socket.socket]) -> None:\n        \"\"\"Makes this server start accepting connections on the given sockets.\n\n        The ``sockets`` parameter is a list of socket objects such as\n        those returned by `~tornado.netutil.bind_sockets`.\n        `add_sockets` is typically used in combination with that\n        method and `tornado.process.fork_processes` to provide greater\n        control over the initialization of a multi-process server.\n        \"\"\"\n        for sock in sockets:\n            self._sockets[sock.fileno()] = sock\n            self._handlers[sock.fileno()] = add_accept_handler(\n                sock, self._handle_connection\n            )\n\n    def add_socket(self, socket: socket.socket) -> None:\n        \"\"\"Singular version of `add_sockets`.  Takes a single socket object.\"\"\"\n        self.add_sockets([socket])\n\n    def bind(\n        self,\n        port: int,\n        address: str | None = None,\n        family: socket.AddressFamily = socket.AF_UNSPEC,\n        backlog: int = _DEFAULT_BACKLOG,\n        flags: int | None = None,\n        reuse_port: bool = False,\n    ) -> None:\n        \"\"\"Binds this server to the given port on the given address.\n\n        To start the server, call `start`. If you want to run this server in a\n        single process, you can call `listen` as a shortcut to the sequence of\n        `bind` and `start` calls.\n\n        Address may be either an IP address or hostname.  If it's a hostname,\n        the server will listen on all IP addresses associated with the name.\n        Address may be an empty string or None to listen on all available\n        interfaces.  Family may be set to either `socket.AF_INET` or\n        `socket.AF_INET6` to restrict to IPv4 or IPv6 addresses, otherwise both\n        will be used if available.\n\n        The ``backlog`` argument has the same meaning as for `socket.listen\n        <socket.socket.listen>`. The ``reuse_port`` argument has the same\n        meaning as for `.bind_sockets`.\n\n        This method may be called multiple times prior to `start` to listen on\n        multiple ports or interfaces.\n\n        .. versionchanged:: 4.4\n           Added the ``reuse_port`` argument.\n\n        .. versionchanged:: 6.2\n           Added the ``flags`` argument to match `.bind_sockets`.\n\n        .. deprecated:: 6.2\n           Use either ``listen()`` or ``add_sockets()`` instead of ``bind()``\n           and ``start()``.\n        \"\"\"\n        sockets = bind_sockets(\n            port,\n            address=address,\n            family=family,\n            backlog=backlog,\n            flags=flags,\n            reuse_port=reuse_port,\n        )\n        if self._started:\n            self.add_sockets(sockets)\n        else:\n            self._pending_sockets.extend(sockets)\n\n    def start(\n        self, num_processes: int | None = 1, max_restarts: int | None = None\n    ) -> None:\n        \"\"\"Starts this server in the `.IOLoop`.\n\n        By default, we run the server in this process and do not fork any\n        additional child process.\n\n        If num_processes is ``None`` or <= 0, we detect the number of cores\n        available on this machine and fork that number of child\n        processes. If num_processes is given and > 1, we fork that\n        specific number of sub-processes.\n\n        Since we use processes and not threads, there is no shared memory\n        between any server code.\n\n        Note that multiple processes are not compatible with the autoreload\n        module (or the ``autoreload=True`` option to `tornado.web.Application`\n        which defaults to True when ``debug=True``).\n        When using multiple processes, no IOLoops can be created or\n        referenced until after the call to ``TCPServer.start(n)``.\n\n        Values of ``num_processes`` other than 1 are not supported on Windows.\n\n        The ``max_restarts`` argument is passed to `.fork_processes`.\n\n        .. versionchanged:: 6.0\n\n           Added ``max_restarts`` argument.\n\n        .. deprecated:: 6.2\n           Use either ``listen()`` or ``add_sockets()`` instead of ``bind()``\n           and ``start()``.\n        \"\"\"\n        assert not self._started\n        self._started = True\n        if num_processes != 1:\n            process.fork_processes(num_processes, max_restarts)\n        sockets = self._pending_sockets\n        self._pending_sockets = []\n        self.add_sockets(sockets)\n\n    def stop(self) -> None:\n        \"\"\"Stops listening for new connections.\n\n        Requests currently in progress may still continue after the\n        server is stopped.\n        \"\"\"\n        if self._stopped:\n            return\n        self._stopped = True\n        for fd, sock in self._sockets.items():\n            assert sock.fileno() == fd\n            # Unregister socket from IOLoop\n            self._handlers.pop(fd)()\n            sock.close()\n\n    def handle_stream(self, stream: IOStream, address: tuple) -> Awaitable[None] | None:\n        \"\"\"Override to handle a new `.IOStream` from an incoming connection.\n\n        This method may be a coroutine; if so any exceptions it raises\n        asynchronously will be logged. Accepting of incoming connections\n        will not be blocked by this coroutine.\n\n        If this `TCPServer` is configured for SSL, ``handle_stream``\n        may be called before the SSL handshake has completed. Use\n        `.SSLIOStream.wait_for_handshake` if you need to verify the client's\n        certificate or use NPN/ALPN.\n\n        .. versionchanged:: 4.2\n           Added the option for this method to be a coroutine.\n        \"\"\"\n        raise NotImplementedError()\n\n    def _handle_connection(self, connection: socket.socket, address: Any) -> None:\n        if self.ssl_options is not None:\n            assert ssl, \"OpenSSL required for SSL\"\n            try:\n                connection = ssl_wrap_socket(\n                    connection,\n                    self.ssl_options,\n                    server_side=True,\n                    do_handshake_on_connect=False,\n                )\n            except ssl.SSLError as err:\n                if err.args[0] == ssl.SSL_ERROR_EOF:\n                    return connection.close()\n                else:\n                    raise\n            except OSError as err:\n                # If the connection is closed immediately after it is created\n                # (as in a port scan), we can get one of several errors.\n                # wrap_socket makes an internal call to getpeername,\n                # which may return either EINVAL (Mac OS X) or ENOTCONN\n                # (Linux).  If it returns ENOTCONN, this error is\n                # silently swallowed by the ssl module, so we need to\n                # catch another error later on (AttributeError in\n                # SSLIOStream._do_ssl_handshake).\n                # To test this behavior, try nmap with the -sT flag.\n                # https://github.com/tornadoweb/tornado/pull/750\n                if errno_from_exception(err) in (errno.ECONNABORTED, errno.EINVAL):\n                    return connection.close()\n                else:\n                    raise\n        try:\n            if self.ssl_options is not None:\n                stream: IOStream = SSLIOStream(\n                    connection,\n                    max_buffer_size=self.max_buffer_size,\n                    read_chunk_size=self.read_chunk_size,\n                )\n            else:\n                stream = IOStream(\n                    connection,\n                    max_buffer_size=self.max_buffer_size,\n                    read_chunk_size=self.read_chunk_size,\n                )\n\n            future = self.handle_stream(stream, address)\n            if future is not None:\n                IOLoop.current().add_future(\n                    gen.convert_yielded(future), lambda f: f.result()\n                )\n        except Exception:\n            app_log.error(\"Error in connection callback\", exc_info=True)\n"
  },
  {
    "path": "tornado/template.py",
    "content": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"A simple template system that compiles templates to Python code.\n\nBasic usage looks like::\n\n    t = template.Template(\"<html>{{ myvalue }}</html>\")\n    print(t.generate(myvalue=\"XXX\"))\n\n`Loader` is a class that loads templates from a root directory and caches\nthe compiled templates::\n\n    loader = template.Loader(\"/home/btaylor\")\n    print(loader.load(\"test.html\").generate(myvalue=\"XXX\"))\n\nWe compile all templates to raw Python. Error-reporting is currently... uh,\ninteresting. Syntax for the templates::\n\n    ### base.html\n    <html>\n      <head>\n        <title>{% block title %}Default title{% end %}</title>\n      </head>\n      <body>\n        <ul>\n          {% for student in students %}\n            {% block student %}\n              <li>{{ escape(student.name) }}</li>\n            {% end %}\n          {% end %}\n        </ul>\n      </body>\n    </html>\n\n    ### bold.html\n    {% extends \"base.html\" %}\n\n    {% block title %}A bolder title{% end %}\n\n    {% block student %}\n      <li><span style=\"bold\">{{ escape(student.name) }}</span></li>\n    {% end %}\n\nUnlike most other template systems, we do not put any restrictions on the\nexpressions you can include in your statements. ``if`` and ``for`` blocks get\ntranslated exactly into Python, so you can do complex expressions like::\n\n   {% for student in [p for p in people if p.student and p.age > 23] %}\n     <li>{{ escape(student.name) }}</li>\n   {% end %}\n\nTranslating directly to Python means you can apply functions to expressions\neasily, like the ``escape()`` function in the examples above. You can pass\nfunctions in to your template just like any other variable\n(In a `.RequestHandler`, override `.RequestHandler.get_template_namespace`)::\n\n   ### Python code\n   def add(x, y):\n      return x + y\n   template.execute(add=add)\n\n   ### The template\n   {{ add(1, 2) }}\n\nWe provide the functions `escape() <.xhtml_escape>`, `.url_escape()`,\n`.json_encode()`, and `.squeeze()` to all templates by default.\n\nTypical applications do not create `Template` or `Loader` instances by\nhand, but instead use the `~.RequestHandler.render` and\n`~.RequestHandler.render_string` methods of\n`tornado.web.RequestHandler`, which load templates automatically based\non the ``template_path`` `.Application` setting.\n\nVariable names beginning with ``_tt_`` are reserved by the template\nsystem and should not be used by application code.\n\nSyntax Reference\n----------------\n\nTemplate expressions are surrounded by double curly braces: ``{{ ... }}``.\nThe contents may be any python expression, which will be escaped according\nto the current autoescape setting and inserted into the output.  Other\ntemplate directives use ``{% %}``.\n\nTo comment out a section so that it is omitted from the output, surround it\nwith ``{# ... #}``.\n\n\nTo include a literal ``{{``, ``{%``, or ``{#`` in the output, escape them as\n``{{!``, ``{%!``, and ``{#!``, respectively.\n\n\n``{% apply *function* %}...{% end %}``\n    Applies a function to the output of all template code between ``apply``\n    and ``end``::\n\n        {% apply linkify %}{{name}} said: {{message}}{% end %}\n\n    Note that as an implementation detail apply blocks are implemented\n    as nested functions and thus may interact strangely with variables\n    set via ``{% set %}``, or the use of ``{% break %}`` or ``{% continue %}``\n    within loops.\n\n``{% autoescape *function* %}``\n    Sets the autoescape mode for the current file.  This does not affect\n    other files, even those referenced by ``{% include %}``.  Note that\n    autoescaping can also be configured globally, at the `.Application`\n    or `Loader`.::\n\n        {% autoescape xhtml_escape %}\n        {% autoescape None %}\n\n``{% block *name* %}...{% end %}``\n    Indicates a named, replaceable block for use with ``{% extends %}``.\n    Blocks in the parent template will be replaced with the contents of\n    the same-named block in a child template.::\n\n        <!-- base.html -->\n        <title>{% block title %}Default title{% end %}</title>\n\n        <!-- mypage.html -->\n        {% extends \"base.html\" %}\n        {% block title %}My page title{% end %}\n\n``{% comment ... %}``\n    A comment which will be removed from the template output.  Note that\n    there is no ``{% end %}`` tag; the comment goes from the word ``comment``\n    to the closing ``%}`` tag.\n\n``{% extends *filename* %}``\n    Inherit from another template.  Templates that use ``extends`` should\n    contain one or more ``block`` tags to replace content from the parent\n    template.  Anything in the child template not contained in a ``block``\n    tag will be ignored.  For an example, see the ``{% block %}`` tag.\n\n``{% for *var* in *expr* %}...{% end %}``\n    Same as the python ``for`` statement.  ``{% break %}`` and\n    ``{% continue %}`` may be used inside the loop.\n\n``{% from *x* import *y* %}``\n    Same as the python ``import`` statement.\n\n``{% if *condition* %}...{% elif *condition* %}...{% else %}...{% end %}``\n    Conditional statement - outputs the first section whose condition is\n    true.  (The ``elif`` and ``else`` sections are optional)\n\n``{% import *module* %}``\n    Same as the python ``import`` statement.\n\n``{% include *filename* %}``\n    Includes another template file.  The included file can see all the local\n    variables as if it were copied directly to the point of the ``include``\n    directive (the ``{% autoescape %}`` directive is an exception).\n    Alternately, ``{% module Template(filename, **kwargs) %}`` may be used\n    to include another template with an isolated namespace.\n\n``{% module *expr* %}``\n    Renders a `~tornado.web.UIModule`.  The output of the ``UIModule`` is\n    not escaped::\n\n        {% module Template(\"foo.html\", arg=42) %}\n\n    ``UIModules`` are a feature of the `tornado.web.RequestHandler`\n    class (and specifically its ``render`` method) and will not work\n    when the template system is used on its own in other contexts.\n\n``{% raw *expr* %}``\n    Outputs the result of the given expression without autoescaping.\n\n``{% set *x* = *y* %}``\n    Sets a local variable.\n\n``{% try %}...{% except %}...{% else %}...{% finally %}...{% end %}``\n    Same as the python ``try`` statement.\n\n``{% while *condition* %}... {% end %}``\n    Same as the python ``while`` statement.  ``{% break %}`` and\n    ``{% continue %}`` may be used inside the loop.\n\n``{% whitespace *mode* %}``\n    Sets the whitespace mode for the remainder of the current file\n    (or until the next ``{% whitespace %}`` directive). See\n    `filter_whitespace` for available options. New in Tornado 4.3.\n\"\"\"\n\nimport datetime\nimport linecache\nimport os.path\nimport posixpath\nimport re\nimport threading\nimport typing\nfrom collections.abc import Callable, Iterable\nfrom io import StringIO\nfrom typing import Any, ContextManager, Optional, TextIO\n\nfrom tornado import escape\nfrom tornado.log import app_log\nfrom tornado.util import ObjectDict, exec_in, unicode_type\n\n_DEFAULT_AUTOESCAPE = \"xhtml_escape\"\n\n\nclass _UnsetMarker:\n    pass\n\n\n_UNSET = _UnsetMarker()\n\n\ndef filter_whitespace(mode: str, text: str) -> str:\n    \"\"\"Transform whitespace in ``text`` according to ``mode``.\n\n    Available modes are:\n\n    * ``all``: Return all whitespace unmodified.\n    * ``single``: Collapse consecutive whitespace with a single whitespace\n      character, preserving newlines.\n    * ``oneline``: Collapse all runs of whitespace into a single space\n      character, removing all newlines in the process.\n\n    .. versionadded:: 4.3\n    \"\"\"\n    if mode == \"all\":\n        return text\n    elif mode == \"single\":\n        text = re.sub(r\"([\\t ]+)\", \" \", text)\n        text = re.sub(r\"(\\s*\\n\\s*)\", \"\\n\", text)\n        return text\n    elif mode == \"oneline\":\n        return re.sub(r\"(\\s+)\", \" \", text)\n    else:\n        raise Exception(\"invalid whitespace mode %s\" % mode)\n\n\nclass Template:\n    \"\"\"A compiled template.\n\n    We compile into Python from the given template_string. You can generate\n    the template from variables with generate().\n    \"\"\"\n\n    # note that the constructor's signature is not extracted with\n    # autodoc because _UNSET looks like garbage.  When changing\n    # this signature update website/sphinx/template.rst too.\n    def __init__(\n        self,\n        template_string: str | bytes,\n        name: str = \"<string>\",\n        loader: Optional[\"BaseLoader\"] = None,\n        compress_whitespace: bool | _UnsetMarker = _UNSET,\n        autoescape: str | _UnsetMarker | None = _UNSET,\n        whitespace: str | None = None,\n    ) -> None:\n        \"\"\"Construct a Template.\n\n        :arg str template_string: the contents of the template file.\n        :arg str name: the filename from which the template was loaded\n            (used for error message).\n        :arg tornado.template.BaseLoader loader: the `~tornado.template.BaseLoader` responsible\n            for this template, used to resolve ``{% include %}`` and ``{% extend %}`` directives.\n        :arg bool compress_whitespace: Deprecated since Tornado 4.3.\n            Equivalent to ``whitespace=\"single\"`` if true and\n            ``whitespace=\"all\"`` if false.\n        :arg str autoescape: The name of a function in the template\n            namespace, or ``None`` to disable escaping by default.\n        :arg str whitespace: A string specifying treatment of whitespace;\n            see `filter_whitespace` for options.\n\n        .. versionchanged:: 4.3\n           Added ``whitespace`` parameter; deprecated ``compress_whitespace``.\n        \"\"\"\n        self.name = escape.native_str(name)\n\n        if compress_whitespace is not _UNSET:\n            # Convert deprecated compress_whitespace (bool) to whitespace (str).\n            if whitespace is not None:\n                raise Exception(\"cannot set both whitespace and compress_whitespace\")\n            whitespace = \"single\" if compress_whitespace else \"all\"\n        if whitespace is None:\n            if loader and loader.whitespace:\n                whitespace = loader.whitespace\n            else:\n                # Whitespace defaults by filename.\n                if name.endswith(\".html\") or name.endswith(\".js\"):\n                    whitespace = \"single\"\n                else:\n                    whitespace = \"all\"\n        # Validate the whitespace setting.\n        assert whitespace is not None\n        filter_whitespace(whitespace, \"\")\n\n        if not isinstance(autoescape, _UnsetMarker):\n            self.autoescape: str | None = autoescape\n        elif loader:\n            self.autoescape = loader.autoescape\n        else:\n            self.autoescape = _DEFAULT_AUTOESCAPE\n\n        self.namespace = loader.namespace if loader else {}\n        reader = _TemplateReader(name, escape.native_str(template_string), whitespace)\n        self.file = _File(self, _parse(reader, self))\n        self.code = self._generate_python(loader)\n        self.loader = loader\n        try:\n            # Under python2.5, the fake filename used here must match\n            # the module name used in __name__ below.\n            # The dont_inherit flag prevents template.py's future imports\n            # from being applied to the generated code.\n            self.compiled = compile(\n                escape.to_unicode(self.code),\n                \"%s.generated.py\" % self.name.replace(\".\", \"_\"),\n                \"exec\",\n                dont_inherit=True,\n            )\n        except Exception:\n            formatted_code = _format_code(self.code).rstrip()\n            app_log.error(\"%s code:\\n%s\", self.name, formatted_code)\n            raise\n\n    def generate(self, **kwargs: Any) -> bytes:\n        \"\"\"Generate this template with the given arguments.\"\"\"\n        namespace = {\n            \"escape\": escape.xhtml_escape,\n            \"xhtml_escape\": escape.xhtml_escape,\n            \"url_escape\": escape.url_escape,\n            \"json_encode\": escape.json_encode,\n            \"squeeze\": escape.squeeze,\n            \"linkify\": escape.linkify,\n            \"datetime\": datetime,\n            \"_tt_utf8\": escape.utf8,  # for internal use\n            \"_tt_string_types\": (unicode_type, bytes),\n            # __name__ and __loader__ allow the traceback mechanism to find\n            # the generated source code.\n            \"__name__\": self.name.replace(\".\", \"_\"),\n            \"__loader__\": ObjectDict(get_source=lambda name: self.code),\n        }\n        namespace.update(self.namespace)\n        namespace.update(kwargs)\n        exec_in(self.compiled, namespace)\n        execute = typing.cast(Callable[[], bytes], namespace[\"_tt_execute\"])\n        # Clear the traceback module's cache of source data now that\n        # we've generated a new template (mainly for this module's\n        # unittests, where different tests reuse the same name).\n        linecache.clearcache()\n        return execute()\n\n    def _generate_python(self, loader: Optional[\"BaseLoader\"]) -> str:\n        buffer = StringIO()\n        try:\n            # named_blocks maps from names to _NamedBlock objects\n            named_blocks: dict[str, _NamedBlock] = {}\n            ancestors = self._get_ancestors(loader)\n            ancestors.reverse()\n            for ancestor in ancestors:\n                ancestor.find_named_blocks(loader, named_blocks)\n            writer = _CodeWriter(buffer, named_blocks, loader, ancestors[0].template)\n            ancestors[0].generate(writer)\n            return buffer.getvalue()\n        finally:\n            buffer.close()\n\n    def _get_ancestors(self, loader: Optional[\"BaseLoader\"]) -> list[\"_File\"]:\n        ancestors = [self.file]\n        for chunk in self.file.body.chunks:\n            if isinstance(chunk, _ExtendsBlock):\n                if not loader:\n                    raise ParseError(\n                        \"{% extends %} block found, but no \" \"template loader\"\n                    )\n                template = loader.load(chunk.name, self.name)\n                ancestors.extend(template._get_ancestors(loader))\n        return ancestors\n\n\nclass BaseLoader:\n    \"\"\"Base class for template loaders.\n\n    You must use a template loader to use template constructs like\n    ``{% extends %}`` and ``{% include %}``. The loader caches all\n    templates after they are loaded the first time.\n    \"\"\"\n\n    def __init__(\n        self,\n        autoescape: str | None = _DEFAULT_AUTOESCAPE,\n        namespace: dict[str, Any] | None = None,\n        whitespace: str | None = None,\n    ) -> None:\n        \"\"\"Construct a template loader.\n\n        :arg str autoescape: The name of a function in the template\n            namespace, such as \"xhtml_escape\", or ``None`` to disable\n            autoescaping by default.\n        :arg dict namespace: A dictionary to be added to the default template\n            namespace, or ``None``.\n        :arg str whitespace: A string specifying default behavior for\n            whitespace in templates; see `filter_whitespace` for options.\n            Default is \"single\" for files ending in \".html\" and \".js\" and\n            \"all\" for other files.\n\n        .. versionchanged:: 4.3\n           Added ``whitespace`` parameter.\n        \"\"\"\n        self.autoescape = autoescape\n        self.namespace = namespace or {}\n        self.whitespace = whitespace\n        self.templates: dict[str, Template] = {}\n        # self.lock protects self.templates.  It's a reentrant lock\n        # because templates may load other templates via `include` or\n        # `extends`.  Note that thanks to the GIL this code would be safe\n        # even without the lock, but could lead to wasted work as multiple\n        # threads tried to compile the same template simultaneously.\n        self.lock = threading.RLock()\n\n    def reset(self) -> None:\n        \"\"\"Resets the cache of compiled templates.\"\"\"\n        with self.lock:\n            self.templates = {}\n\n    def resolve_path(self, name: str, parent_path: str | None = None) -> str:\n        \"\"\"Converts a possibly-relative path to absolute (used internally).\"\"\"\n        raise NotImplementedError()\n\n    def load(self, name: str, parent_path: str | None = None) -> Template:\n        \"\"\"Loads a template.\"\"\"\n        name = self.resolve_path(name, parent_path=parent_path)\n        with self.lock:\n            if name not in self.templates:\n                self.templates[name] = self._create_template(name)\n            return self.templates[name]\n\n    def _create_template(self, name: str) -> Template:\n        raise NotImplementedError()\n\n\nclass Loader(BaseLoader):\n    \"\"\"A template loader that loads from a single root directory.\"\"\"\n\n    def __init__(self, root_directory: str, **kwargs: Any) -> None:\n        super().__init__(**kwargs)\n        self.root = os.path.abspath(root_directory)\n\n    def resolve_path(self, name: str, parent_path: str | None = None) -> str:\n        if (\n            parent_path\n            and not parent_path.startswith(\"<\")\n            and not parent_path.startswith(\"/\")\n            and not name.startswith(\"/\")\n        ):\n            current_path = os.path.join(self.root, parent_path)\n            file_dir = os.path.dirname(os.path.abspath(current_path))\n            relative_path = os.path.abspath(os.path.join(file_dir, name))\n            if relative_path.startswith(self.root):\n                name = relative_path[len(self.root) + 1 :]\n        return name\n\n    def _create_template(self, name: str) -> Template:\n        path = os.path.join(self.root, name)\n        with open(path, \"rb\") as f:\n            template = Template(f.read(), name=name, loader=self)\n            return template\n\n\nclass DictLoader(BaseLoader):\n    \"\"\"A template loader that loads from a dictionary.\"\"\"\n\n    def __init__(self, dict: dict[str, str], **kwargs: Any) -> None:\n        super().__init__(**kwargs)\n        self.dict = dict\n\n    def resolve_path(self, name: str, parent_path: str | None = None) -> str:\n        if (\n            parent_path\n            and not parent_path.startswith(\"<\")\n            and not parent_path.startswith(\"/\")\n            and not name.startswith(\"/\")\n        ):\n            file_dir = posixpath.dirname(parent_path)\n            name = posixpath.normpath(posixpath.join(file_dir, name))\n        return name\n\n    def _create_template(self, name: str) -> Template:\n        return Template(self.dict[name], name=name, loader=self)\n\n\nclass _Node:\n    def each_child(self) -> Iterable[\"_Node\"]:\n        return ()\n\n    def generate(self, writer: \"_CodeWriter\") -> None:\n        raise NotImplementedError()\n\n    def find_named_blocks(\n        self, loader: BaseLoader | None, named_blocks: dict[str, \"_NamedBlock\"]\n    ) -> None:\n        for child in self.each_child():\n            child.find_named_blocks(loader, named_blocks)\n\n\nclass _File(_Node):\n    def __init__(self, template: Template, body: \"_ChunkList\") -> None:\n        self.template = template\n        self.body = body\n        self.line = 0\n\n    def generate(self, writer: \"_CodeWriter\") -> None:\n        writer.write_line(\"def _tt_execute():\", self.line)\n        with writer.indent():\n            writer.write_line(\"_tt_buffer = []\", self.line)\n            writer.write_line(\"_tt_append = _tt_buffer.append\", self.line)\n            self.body.generate(writer)\n            writer.write_line(\"return _tt_utf8('').join(_tt_buffer)\", self.line)\n\n    def each_child(self) -> Iterable[\"_Node\"]:\n        return (self.body,)\n\n\nclass _ChunkList(_Node):\n    def __init__(self, chunks: list[_Node]) -> None:\n        self.chunks = chunks\n\n    def generate(self, writer: \"_CodeWriter\") -> None:\n        for chunk in self.chunks:\n            chunk.generate(writer)\n\n    def each_child(self) -> Iterable[\"_Node\"]:\n        return self.chunks\n\n\nclass _NamedBlock(_Node):\n    def __init__(self, name: str, body: _Node, template: Template, line: int) -> None:\n        self.name = name\n        self.body = body\n        self.template = template\n        self.line = line\n\n    def each_child(self) -> Iterable[\"_Node\"]:\n        return (self.body,)\n\n    def generate(self, writer: \"_CodeWriter\") -> None:\n        block = writer.named_blocks[self.name]\n        with writer.include(block.template, self.line):\n            block.body.generate(writer)\n\n    def find_named_blocks(\n        self, loader: BaseLoader | None, named_blocks: dict[str, \"_NamedBlock\"]\n    ) -> None:\n        named_blocks[self.name] = self\n        _Node.find_named_blocks(self, loader, named_blocks)\n\n\nclass _ExtendsBlock(_Node):\n    def __init__(self, name: str) -> None:\n        self.name = name\n\n\nclass _IncludeBlock(_Node):\n    def __init__(self, name: str, reader: \"_TemplateReader\", line: int) -> None:\n        self.name = name\n        self.template_name = reader.name\n        self.line = line\n\n    def find_named_blocks(\n        self, loader: BaseLoader | None, named_blocks: dict[str, _NamedBlock]\n    ) -> None:\n        assert loader is not None\n        included = loader.load(self.name, self.template_name)\n        included.file.find_named_blocks(loader, named_blocks)\n\n    def generate(self, writer: \"_CodeWriter\") -> None:\n        assert writer.loader is not None\n        included = writer.loader.load(self.name, self.template_name)\n        with writer.include(included, self.line):\n            included.file.body.generate(writer)\n\n\nclass _ApplyBlock(_Node):\n    def __init__(self, method: str, line: int, body: _Node) -> None:\n        self.method = method\n        self.line = line\n        self.body = body\n\n    def each_child(self) -> Iterable[\"_Node\"]:\n        return (self.body,)\n\n    def generate(self, writer: \"_CodeWriter\") -> None:\n        method_name = \"_tt_apply%d\" % writer.apply_counter\n        writer.apply_counter += 1\n        writer.write_line(\"def %s():\" % method_name, self.line)\n        with writer.indent():\n            writer.write_line(\"_tt_buffer = []\", self.line)\n            writer.write_line(\"_tt_append = _tt_buffer.append\", self.line)\n            self.body.generate(writer)\n            writer.write_line(\"return _tt_utf8('').join(_tt_buffer)\", self.line)\n        writer.write_line(\n            f\"_tt_append(_tt_utf8({self.method}({method_name}())))\", self.line\n        )\n\n\nclass _ControlBlock(_Node):\n    def __init__(self, statement: str, line: int, body: _Node) -> None:\n        self.statement = statement\n        self.line = line\n        self.body = body\n\n    def each_child(self) -> Iterable[_Node]:\n        return (self.body,)\n\n    def generate(self, writer: \"_CodeWriter\") -> None:\n        writer.write_line(\"%s:\" % self.statement, self.line)\n        with writer.indent():\n            self.body.generate(writer)\n            # Just in case the body was empty\n            writer.write_line(\"pass\", self.line)\n\n\nclass _IntermediateControlBlock(_Node):\n    def __init__(self, statement: str, line: int) -> None:\n        self.statement = statement\n        self.line = line\n\n    def generate(self, writer: \"_CodeWriter\") -> None:\n        # In case the previous block was empty\n        writer.write_line(\"pass\", self.line)\n        writer.write_line(\"%s:\" % self.statement, self.line, writer.indent_size() - 1)\n\n\nclass _Statement(_Node):\n    def __init__(self, statement: str, line: int) -> None:\n        self.statement = statement\n        self.line = line\n\n    def generate(self, writer: \"_CodeWriter\") -> None:\n        writer.write_line(self.statement, self.line)\n\n\nclass _Expression(_Node):\n    def __init__(self, expression: str, line: int, raw: bool = False) -> None:\n        self.expression = expression\n        self.line = line\n        self.raw = raw\n\n    def generate(self, writer: \"_CodeWriter\") -> None:\n        writer.write_line(\"_tt_tmp = %s\" % self.expression, self.line)\n        writer.write_line(\n            \"if isinstance(_tt_tmp, _tt_string_types):\" \" _tt_tmp = _tt_utf8(_tt_tmp)\",\n            self.line,\n        )\n        writer.write_line(\"else: _tt_tmp = _tt_utf8(str(_tt_tmp))\", self.line)\n        if not self.raw and writer.current_template.autoescape is not None:\n            # In python3 functions like xhtml_escape return unicode,\n            # so we have to convert to utf8 again.\n            writer.write_line(\n                \"_tt_tmp = _tt_utf8(%s(_tt_tmp))\" % writer.current_template.autoescape,\n                self.line,\n            )\n        writer.write_line(\"_tt_append(_tt_tmp)\", self.line)\n\n\nclass _Module(_Expression):\n    def __init__(self, expression: str, line: int) -> None:\n        super().__init__(\"_tt_modules.\" + expression, line, raw=True)\n\n\nclass _Text(_Node):\n    def __init__(self, value: str, line: int, whitespace: str) -> None:\n        self.value = value\n        self.line = line\n        self.whitespace = whitespace\n\n    def generate(self, writer: \"_CodeWriter\") -> None:\n        value = self.value\n\n        # Compress whitespace if requested, with a crude heuristic to avoid\n        # altering preformatted whitespace.\n        if \"<pre>\" not in value:\n            value = filter_whitespace(self.whitespace, value)\n\n        if value:\n            writer.write_line(\"_tt_append(%r)\" % escape.utf8(value), self.line)\n\n\nclass ParseError(Exception):\n    \"\"\"Raised for template syntax errors.\n\n    ``ParseError`` instances have ``filename`` and ``lineno`` attributes\n    indicating the position of the error.\n\n    .. versionchanged:: 4.3\n       Added ``filename`` and ``lineno`` attributes.\n    \"\"\"\n\n    def __init__(\n        self, message: str, filename: str | None = None, lineno: int = 0\n    ) -> None:\n        self.message = message\n        # The names \"filename\" and \"lineno\" are chosen for consistency\n        # with python SyntaxError.\n        self.filename = filename\n        self.lineno = lineno\n\n    def __str__(self) -> str:\n        return \"%s at %s:%d\" % (self.message, self.filename, self.lineno)\n\n\nclass _CodeWriter:\n    def __init__(\n        self,\n        file: TextIO,\n        named_blocks: dict[str, _NamedBlock],\n        loader: BaseLoader | None,\n        current_template: Template,\n    ) -> None:\n        self.file = file\n        self.named_blocks = named_blocks\n        self.loader = loader\n        self.current_template = current_template\n        self.apply_counter = 0\n        self.include_stack: list[tuple[Template, int]] = []\n        self._indent = 0\n\n    def indent_size(self) -> int:\n        return self._indent\n\n    def indent(self) -> \"ContextManager\":\n        class Indenter:\n            def __enter__(_) -> \"_CodeWriter\":\n                self._indent += 1\n                return self\n\n            def __exit__(_, *args: Any) -> None:\n                assert self._indent > 0\n                self._indent -= 1\n\n        return Indenter()\n\n    def include(self, template: Template, line: int) -> \"ContextManager\":\n        self.include_stack.append((self.current_template, line))\n        self.current_template = template\n\n        class IncludeTemplate:\n            def __enter__(_) -> \"_CodeWriter\":\n                return self\n\n            def __exit__(_, *args: Any) -> None:\n                self.current_template = self.include_stack.pop()[0]\n\n        return IncludeTemplate()\n\n    def write_line(\n        self, line: str, line_number: int, indent: int | None = None\n    ) -> None:\n        if indent is None:\n            indent = self._indent\n        line_comment = \"  # %s:%d\" % (self.current_template.name, line_number)\n        if self.include_stack:\n            ancestors = [\n                \"%s:%d\" % (tmpl.name, lineno) for (tmpl, lineno) in self.include_stack\n            ]\n            line_comment += \" (via %s)\" % \", \".join(reversed(ancestors))\n        print(\"    \" * indent + line + line_comment, file=self.file)\n\n\nclass _TemplateReader:\n    def __init__(self, name: str, text: str, whitespace: str) -> None:\n        self.name = name\n        self.text = text\n        self.whitespace = whitespace\n        self.line = 1\n        self.pos = 0\n\n    def find(self, needle: str, start: int = 0, end: int | None = None) -> int:\n        assert start >= 0, start\n        pos = self.pos\n        start += pos\n        if end is None:\n            index = self.text.find(needle, start)\n        else:\n            end += pos\n            assert end >= start\n            index = self.text.find(needle, start, end)\n        if index != -1:\n            index -= pos\n        return index\n\n    def consume(self, count: int | None = None) -> str:\n        if count is None:\n            count = len(self.text) - self.pos\n        newpos = self.pos + count\n        self.line += self.text.count(\"\\n\", self.pos, newpos)\n        s = self.text[self.pos : newpos]\n        self.pos = newpos\n        return s\n\n    def remaining(self) -> int:\n        return len(self.text) - self.pos\n\n    def __len__(self) -> int:\n        return self.remaining()\n\n    def __getitem__(self, key: int | slice) -> str:\n        if isinstance(key, slice):\n            size = len(self)\n            start, stop, step = key.indices(size)\n            if start is None:\n                start = self.pos\n            else:\n                start += self.pos\n            if stop is not None:\n                stop += self.pos\n            return self.text[slice(start, stop, step)]\n        elif key < 0:\n            return self.text[key]\n        else:\n            return self.text[self.pos + key]\n\n    def __str__(self) -> str:\n        return self.text[self.pos :]\n\n    def raise_parse_error(self, msg: str) -> None:\n        raise ParseError(msg, self.name, self.line)\n\n\ndef _format_code(code: str) -> str:\n    lines = code.splitlines()\n    format = \"%%%dd  %%s\\n\" % len(repr(len(lines) + 1))\n    return \"\".join([format % (i + 1, line) for (i, line) in enumerate(lines)])\n\n\ndef _parse(\n    reader: _TemplateReader,\n    template: Template,\n    in_block: str | None = None,\n    in_loop: str | None = None,\n) -> _ChunkList:\n    body = _ChunkList([])\n    while True:\n        # Find next template directive\n        curly = 0\n        while True:\n            curly = reader.find(\"{\", curly)\n            if curly == -1 or curly + 1 == reader.remaining():\n                # EOF\n                if in_block:\n                    reader.raise_parse_error(\n                        \"Missing {%% end %%} block for %s\" % in_block\n                    )\n                body.chunks.append(\n                    _Text(reader.consume(), reader.line, reader.whitespace)\n                )\n                return body\n            # If the first curly brace is not the start of a special token,\n            # start searching from the character after it\n            if reader[curly + 1] not in (\"{\", \"%\", \"#\"):\n                curly += 1\n                continue\n            # When there are more than 2 curlies in a row, use the\n            # innermost ones.  This is useful when generating languages\n            # like latex where curlies are also meaningful\n            if (\n                curly + 2 < reader.remaining()\n                and reader[curly + 1] == \"{\"\n                and reader[curly + 2] == \"{\"\n            ):\n                curly += 1\n                continue\n            break\n\n        # Append any text before the special token\n        if curly > 0:\n            cons = reader.consume(curly)\n            body.chunks.append(_Text(cons, reader.line, reader.whitespace))\n\n        start_brace = reader.consume(2)\n        line = reader.line\n\n        # Template directives may be escaped as \"{{!\" or \"{%!\".\n        # In this case output the braces and consume the \"!\".\n        # This is especially useful in conjunction with jquery templates,\n        # which also use double braces.\n        if reader.remaining() and reader[0] == \"!\":\n            reader.consume(1)\n            body.chunks.append(_Text(start_brace, line, reader.whitespace))\n            continue\n\n        # Comment\n        if start_brace == \"{#\":\n            end = reader.find(\"#}\")\n            if end == -1:\n                reader.raise_parse_error(\"Missing end comment #}\")\n            contents = reader.consume(end).strip()\n            reader.consume(2)\n            continue\n\n        # Expression\n        if start_brace == \"{{\":\n            end = reader.find(\"}}\")\n            if end == -1:\n                reader.raise_parse_error(\"Missing end expression }}\")\n            contents = reader.consume(end).strip()\n            reader.consume(2)\n            if not contents:\n                reader.raise_parse_error(\"Empty expression\")\n            body.chunks.append(_Expression(contents, line))\n            continue\n\n        # Block\n        assert start_brace == \"{%\", start_brace\n        end = reader.find(\"%}\")\n        if end == -1:\n            reader.raise_parse_error(\"Missing end block %}\")\n        contents = reader.consume(end).strip()\n        reader.consume(2)\n        if not contents:\n            reader.raise_parse_error(\"Empty block tag ({% %})\")\n\n        operator, space, suffix = contents.partition(\" \")\n        suffix = suffix.strip()\n\n        # Intermediate (\"else\", \"elif\", etc) blocks\n        intermediate_blocks = {\n            \"else\": {\"if\", \"for\", \"while\", \"try\"},\n            \"elif\": {\"if\"},\n            \"except\": {\"try\"},\n            \"finally\": {\"try\"},\n        }\n        allowed_parents = intermediate_blocks.get(operator)\n        if allowed_parents is not None:\n            if not in_block:\n                reader.raise_parse_error(f\"{operator} outside {allowed_parents} block\")\n            if in_block not in allowed_parents:\n                reader.raise_parse_error(\n                    f\"{operator} block cannot be attached to {in_block} block\"\n                )\n            body.chunks.append(_IntermediateControlBlock(contents, line))\n            continue\n\n        # End tag\n        elif operator == \"end\":\n            if not in_block:\n                reader.raise_parse_error(\"Extra {% end %} block\")\n            return body\n\n        elif operator in (\n            \"extends\",\n            \"include\",\n            \"set\",\n            \"import\",\n            \"from\",\n            \"comment\",\n            \"autoescape\",\n            \"whitespace\",\n            \"raw\",\n            \"module\",\n        ):\n            if operator == \"comment\":\n                continue\n            if operator == \"extends\":\n                suffix = suffix.strip('\"').strip(\"'\")\n                if not suffix:\n                    reader.raise_parse_error(\"extends missing file path\")\n                block: _Node = _ExtendsBlock(suffix)\n            elif operator in (\"import\", \"from\"):\n                if not suffix:\n                    reader.raise_parse_error(\"import missing statement\")\n                block = _Statement(contents, line)\n            elif operator == \"include\":\n                suffix = suffix.strip('\"').strip(\"'\")\n                if not suffix:\n                    reader.raise_parse_error(\"include missing file path\")\n                block = _IncludeBlock(suffix, reader, line)\n            elif operator == \"set\":\n                if not suffix:\n                    reader.raise_parse_error(\"set missing statement\")\n                block = _Statement(suffix, line)\n            elif operator == \"autoescape\":\n                fn: str | None = suffix.strip()\n                if fn == \"None\":\n                    fn = None\n                template.autoescape = fn\n                continue\n            elif operator == \"whitespace\":\n                mode = suffix.strip()\n                # Validate the selected mode\n                filter_whitespace(mode, \"\")\n                reader.whitespace = mode\n                continue\n            elif operator == \"raw\":\n                block = _Expression(suffix, line, raw=True)\n            elif operator == \"module\":\n                block = _Module(suffix, line)\n            body.chunks.append(block)\n            continue\n\n        elif operator in (\"apply\", \"block\", \"try\", \"if\", \"for\", \"while\"):\n            # parse inner body recursively\n            if operator in (\"for\", \"while\"):\n                block_body = _parse(reader, template, operator, operator)\n            elif operator == \"apply\":\n                # apply creates a nested function so syntactically it's not\n                # in the loop.\n                block_body = _parse(reader, template, operator, None)\n            else:\n                block_body = _parse(reader, template, operator, in_loop)\n\n            if operator == \"apply\":\n                if not suffix:\n                    reader.raise_parse_error(\"apply missing method name\")\n                block = _ApplyBlock(suffix, line, block_body)\n            elif operator == \"block\":\n                if not suffix:\n                    reader.raise_parse_error(\"block missing name\")\n                block = _NamedBlock(suffix, block_body, template, line)\n            else:\n                block = _ControlBlock(contents, line, block_body)\n            body.chunks.append(block)\n            continue\n\n        elif operator in (\"break\", \"continue\"):\n            if not in_loop:\n                reader.raise_parse_error(\n                    \"{} outside {} block\".format(operator, {\"for\", \"while\"})\n                )\n            body.chunks.append(_Statement(contents, line))\n            continue\n\n        else:\n            reader.raise_parse_error(\"unknown operator: %r\" % operator)\n"
  },
  {
    "path": "tornado/test/__init__.py",
    "content": ""
  },
  {
    "path": "tornado/test/__main__.py",
    "content": "\"\"\"Shim to allow python -m tornado.test.\"\"\"\n\nfrom tornado.test.runtests import all, main\n\n# tornado.testing.main autodiscovery relies on 'all' being present in\n# the main module, so import it here even though it is not used directly.\n# The following line prevents a pyflakes warning.\nall = all\n\nmain()\n"
  },
  {
    "path": "tornado/test/asyncio_test.py",
    "content": "# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\nimport asyncio\nimport contextvars\nimport threading\nimport time\nimport unittest\nimport warnings\nfrom concurrent.futures import ThreadPoolExecutor\n\nimport tornado.platform.asyncio\nfrom tornado import gen\nfrom tornado.ioloop import IOLoop\nfrom tornado.platform.asyncio import (\n    AddThreadSelectorEventLoop,\n    AsyncIOLoop,\n    to_asyncio_future,\n)\nfrom tornado.test.util import ignore_deprecation\nfrom tornado.testing import (\n    AsyncHTTPTestCase,\n    AsyncTestCase,\n    gen_test,\n    setup_with_context_manager,\n)\nfrom tornado.web import Application, RequestHandler\n\n\nclass AsyncIOLoopTest(AsyncTestCase):\n    @property\n    def asyncio_loop(self):\n        return self.io_loop.asyncio_loop  # type: ignore\n\n    def test_asyncio_callback(self):\n        # Basic test that the asyncio loop is set up correctly.\n        async def add_callback():\n            asyncio.get_event_loop().call_soon(self.stop)\n\n        self.asyncio_loop.run_until_complete(add_callback())\n        self.wait()\n\n    @gen_test\n    def test_asyncio_future(self):\n        # Test that we can yield an asyncio future from a tornado coroutine.\n        # Without 'yield from', we must wrap coroutines in ensure_future.\n        x = yield asyncio.ensure_future(\n            asyncio.get_event_loop().run_in_executor(None, lambda: 42)\n        )\n        self.assertEqual(x, 42)\n\n    @gen_test\n    def test_asyncio_yield_from(self):\n        @gen.coroutine\n        def f():\n            event_loop = asyncio.get_event_loop()\n            x = yield from event_loop.run_in_executor(None, lambda: 42)\n            return x\n\n        result = yield f()\n        self.assertEqual(result, 42)\n\n    def test_asyncio_adapter(self):\n        # This test demonstrates that when using the asyncio coroutine\n        # runner (i.e. run_until_complete), the to_asyncio_future\n        # adapter is needed. No adapter is needed in the other direction,\n        # as demonstrated by other tests in the package.\n        @gen.coroutine\n        def tornado_coroutine():\n            yield gen.moment\n            raise gen.Return(42)\n\n        async def native_coroutine_without_adapter():\n            return await tornado_coroutine()\n\n        async def native_coroutine_with_adapter():\n            return await to_asyncio_future(tornado_coroutine())\n\n        # Use the adapter, but two degrees from the tornado coroutine.\n        async def native_coroutine_with_adapter2():\n            return await to_asyncio_future(native_coroutine_without_adapter())\n\n        # Tornado supports native coroutines both with and without adapters\n        self.assertEqual(self.io_loop.run_sync(native_coroutine_without_adapter), 42)\n        self.assertEqual(self.io_loop.run_sync(native_coroutine_with_adapter), 42)\n        self.assertEqual(self.io_loop.run_sync(native_coroutine_with_adapter2), 42)\n\n        # Asyncio only supports coroutines that yield asyncio-compatible\n        # Futures (which our Future is since 5.0).\n        self.assertEqual(\n            self.asyncio_loop.run_until_complete(native_coroutine_without_adapter()),\n            42,\n        )\n        self.assertEqual(\n            self.asyncio_loop.run_until_complete(native_coroutine_with_adapter()),\n            42,\n        )\n        self.assertEqual(\n            self.asyncio_loop.run_until_complete(native_coroutine_with_adapter2()),\n            42,\n        )\n\n    def test_add_thread_close_idempotent(self):\n        loop = AddThreadSelectorEventLoop(asyncio.get_event_loop())  # type: ignore\n        loop.close()\n        loop.close()\n\n\nclass LeakTest(unittest.TestCase):\n    def setUp(self):\n        # Trigger a cleanup of the mapping so we start with a clean slate.\n        AsyncIOLoop(make_current=False).close()\n\n    def tearDown(self):\n        try:\n            loop = asyncio.get_event_loop_policy().get_event_loop()\n        except Exception:\n            # We may not have a current event loop at this point.\n            pass\n        else:\n            loop.close()\n\n    def test_ioloop_close_leak(self):\n        orig_count = len(IOLoop._ioloop_for_asyncio)\n        for i in range(10):\n            # Create and close an AsyncIOLoop using Tornado interfaces.\n            with warnings.catch_warnings():\n                warnings.simplefilter(\"ignore\", DeprecationWarning)\n                loop = AsyncIOLoop()\n                loop.close()\n        new_count = len(IOLoop._ioloop_for_asyncio) - orig_count\n        self.assertEqual(new_count, 0)\n\n    def test_asyncio_close_leak(self):\n        orig_count = len(IOLoop._ioloop_for_asyncio)\n        for i in range(10):\n            # Create and close an AsyncIOMainLoop using asyncio interfaces.\n            loop = asyncio.new_event_loop()\n            loop.call_soon(IOLoop.current)\n            loop.call_soon(loop.stop)\n            loop.run_forever()\n            loop.close()\n        new_count = len(IOLoop._ioloop_for_asyncio) - orig_count\n        # Because the cleanup is run on new loop creation, we have one\n        # dangling entry in the map (but only one).\n        self.assertEqual(new_count, 1)\n\n\nclass SelectorThreadLeakTest(unittest.TestCase):\n    # These tests are only relevant on windows, but they should pass anywhere.\n    def setUp(self):\n        # As a precaution, ensure that we've run an event loop at least once\n        # so if it spins up any singleton threads they're already there.\n        asyncio.run(self.dummy_tornado_coroutine())\n        self.orig_thread_count = threading.active_count()\n\n    def assert_no_thread_leak(self):\n        # For some reason we see transient failures here, but I haven't been able\n        # to catch it to identify which thread is causing it. Whatever thread it\n        # is, it appears to quickly clean up on its own, so just retry a few times.\n        # At least some of the time the errant thread was running at the time we\n        # captured self.orig_thread_count, so use inequalities.\n        deadline = time.time() + 1\n        while time.time() < deadline:\n            threads = list(threading.enumerate())\n            if len(threads) <= self.orig_thread_count:\n                break\n            time.sleep(0.1)\n        self.assertLessEqual(len(threads), self.orig_thread_count, threads)\n\n    async def dummy_tornado_coroutine(self):\n        # Just access the IOLoop to initialize the selector thread.\n        IOLoop.current()\n\n    def test_asyncio_run(self):\n        for i in range(10):\n            # asyncio.run calls shutdown_asyncgens for us.\n            asyncio.run(self.dummy_tornado_coroutine())\n        self.assert_no_thread_leak()\n\n    def test_asyncio_manual(self):\n        for i in range(10):\n            loop = asyncio.new_event_loop()\n            loop.run_until_complete(self.dummy_tornado_coroutine())\n            # Without this step, we'd leak the thread.\n            loop.run_until_complete(loop.shutdown_asyncgens())\n            loop.close()\n        self.assert_no_thread_leak()\n\n    def test_tornado(self):\n        for i in range(10):\n            # The IOLoop interfaces are aware of the selector thread and\n            # (synchronously) shut it down.\n            loop = IOLoop(make_current=False)\n            loop.run_sync(self.dummy_tornado_coroutine)\n            loop.close()\n        self.assert_no_thread_leak()\n\n\nclass AnyThreadEventLoopPolicyTest(unittest.TestCase):\n    def setUp(self):\n        setup_with_context_manager(self, ignore_deprecation())\n        # Referencing the event loop policy attributes raises deprecation warnings,\n        # so instead of importing this at the top of the file we capture it here.\n        self.AnyThreadEventLoopPolicy = (\n            tornado.platform.asyncio.AnyThreadEventLoopPolicy\n        )\n        self.orig_policy = asyncio.get_event_loop_policy()\n        self.executor = ThreadPoolExecutor(1)\n\n    def tearDown(self):\n        asyncio.set_event_loop_policy(self.orig_policy)\n        self.executor.shutdown()\n\n    def get_event_loop_on_thread(self):\n        def get_and_close_event_loop():\n            \"\"\"Get the event loop. Close it if one is returned.\n\n            Returns the (closed) event loop. This is a silly thing\n            to do and leaves the thread in a broken state, but it's\n            enough for this test. Closing the loop avoids resource\n            leak warnings.\n            \"\"\"\n            loop = asyncio.get_event_loop()\n            loop.close()\n            return loop\n\n        future = self.executor.submit(get_and_close_event_loop)\n        return future.result()\n\n    def test_asyncio_accessor(self):\n        with warnings.catch_warnings():\n            warnings.simplefilter(\"ignore\", DeprecationWarning)\n            # With the default policy, non-main threads don't get an event\n            # loop.\n            self.assertRaises(\n                RuntimeError, self.executor.submit(asyncio.get_event_loop).result\n            )\n            # Set the policy and we can get a loop.\n            asyncio.set_event_loop_policy(self.AnyThreadEventLoopPolicy())\n            self.assertIsInstance(\n                self.executor.submit(asyncio.get_event_loop).result(),\n                asyncio.AbstractEventLoop,\n            )\n            # Clean up to silence leak warnings. Always use asyncio since\n            # IOLoop doesn't (currently) close the underlying loop.\n            self.executor.submit(lambda: asyncio.get_event_loop().close()).result()  # type: ignore\n\n    def test_tornado_accessor(self):\n        # Tornado's IOLoop.current() API can create a loop for any thread,\n        # regardless of this event loop policy.\n        with warnings.catch_warnings():\n            warnings.simplefilter(\"ignore\", DeprecationWarning)\n            self.assertIsInstance(self.executor.submit(IOLoop.current).result(), IOLoop)\n            # Clean up to silence leak warnings. Always use asyncio since\n            # IOLoop doesn't (currently) close the underlying loop.\n            self.executor.submit(lambda: asyncio.get_event_loop().close()).result()  # type: ignore\n\n            asyncio.set_event_loop_policy(self.AnyThreadEventLoopPolicy())\n            self.assertIsInstance(self.executor.submit(IOLoop.current).result(), IOLoop)\n            self.executor.submit(lambda: asyncio.get_event_loop().close()).result()  # type: ignore\n\n\nclass SelectorThreadContextvarsTest(AsyncHTTPTestCase):\n    ctx_value = \"foo\"\n    test_endpoint = \"/\"\n    tornado_test_ctx = contextvars.ContextVar(\"tornado_test_ctx\", default=\"default\")\n    tornado_test_ctx.set(ctx_value)\n\n    def get_app(self) -> Application:\n        tornado_test_ctx = self.tornado_test_ctx\n\n        class Handler(RequestHandler):\n            async def get(self):\n                # On the Windows platform,\n                # when a asyncio.events.Handle is created\n                # in the SelectorThread without providing a context,\n                # it will copy the current thread's context,\n                # which can lead to the loss of the main thread's context\n                # when executing the handle.\n                # Therefore, it is necessary to\n                # save a copy of the main thread's context in the SelectorThread\n                # for creating the handle.\n                self.write(tornado_test_ctx.get())\n\n        return Application([(self.test_endpoint, Handler)])\n\n    def test_context_vars(self):\n        self.assertEqual(self.ctx_value, self.fetch(self.test_endpoint).body.decode())\n"
  },
  {
    "path": "tornado/test/auth_test.py",
    "content": "# These tests do not currently do much to verify the correct implementation\n# of the openid/oauth protocols, they just exercise the major code paths\n# and ensure that it doesn't blow up (e.g. with unicode/bytes issues in\n# python 3)\n\nfrom unittest import mock\n\nfrom tornado import gen\nfrom tornado.auth import (\n    FacebookGraphMixin,\n    GoogleOAuth2Mixin,\n    OAuth2Mixin,\n    OAuthMixin,\n    OpenIdMixin,\n    TwitterMixin,\n)\nfrom tornado.escape import json_decode\nfrom tornado.httpclient import HTTPClientError\nfrom tornado.httputil import url_concat\nfrom tornado.log import app_log\nfrom tornado.testing import AsyncHTTPTestCase, ExpectLog\nfrom tornado.web import Application, HTTPError, RequestHandler\n\n\nclass OpenIdClientLoginHandler(RequestHandler, OpenIdMixin):\n    def initialize(self, test):\n        self._OPENID_ENDPOINT = test.get_url(\"/openid/server/authenticate\")\n\n    @gen.coroutine\n    def get(self):\n        if self.get_argument(\"openid.mode\", None):\n            user = yield self.get_authenticated_user(\n                http_client=self.settings[\"http_client\"]\n            )\n            if user is None:\n                raise Exception(\"user is None\")\n            self.finish(user)\n            return\n        res = self.authenticate_redirect()  # type: ignore\n        assert res is None\n\n\nclass OpenIdServerAuthenticateHandler(RequestHandler):\n    def post(self):\n        if self.get_argument(\"openid.mode\") != \"check_authentication\":\n            raise Exception(\"incorrect openid.mode %r\")\n        self.write(\"is_valid:true\")\n\n\nclass OAuth1ClientLoginHandler(RequestHandler, OAuthMixin):\n    def initialize(self, test, version):\n        self._OAUTH_VERSION = version\n        self._OAUTH_REQUEST_TOKEN_URL = test.get_url(\"/oauth1/server/request_token\")\n        self._OAUTH_AUTHORIZE_URL = test.get_url(\"/oauth1/server/authorize\")\n        self._OAUTH_ACCESS_TOKEN_URL = test.get_url(\"/oauth1/server/access_token\")\n\n    def _oauth_consumer_token(self):\n        return dict(key=\"asdf\", secret=\"qwer\")\n\n    @gen.coroutine\n    def get(self):\n        if self.get_argument(\"oauth_token\", None):\n            user = yield self.get_authenticated_user(\n                http_client=self.settings[\"http_client\"]\n            )\n            if user is None:\n                raise Exception(\"user is None\")\n            self.finish(user)\n            return\n        yield self.authorize_redirect(http_client=self.settings[\"http_client\"])\n\n    @gen.coroutine\n    def _oauth_get_user_future(self, access_token):\n        if self.get_argument(\"fail_in_get_user\", None):\n            raise Exception(\"failing in get_user\")\n        if access_token != dict(key=\"uiop\", secret=\"5678\"):\n            raise Exception(\"incorrect access token %r\" % access_token)\n        return dict(email=\"foo@example.com\")\n\n\nclass OAuth1ClientLoginCoroutineHandler(OAuth1ClientLoginHandler):\n    \"\"\"Replaces OAuth1ClientLoginCoroutineHandler's get() with a coroutine.\"\"\"\n\n    @gen.coroutine\n    def get(self):\n        if self.get_argument(\"oauth_token\", None):\n            # Ensure that any exceptions are set on the returned Future,\n            # not simply thrown into the surrounding StackContext.\n            try:\n                yield self.get_authenticated_user()\n            except Exception as e:\n                self.set_status(503)\n                self.write(\"got exception: %s\" % e)\n        else:\n            yield self.authorize_redirect()\n\n\nclass OAuth1ClientRequestParametersHandler(RequestHandler, OAuthMixin):\n    def initialize(self, version):\n        self._OAUTH_VERSION = version\n\n    def _oauth_consumer_token(self):\n        return dict(key=\"asdf\", secret=\"qwer\")\n\n    def get(self):\n        params = self._oauth_request_parameters(\n            \"http://www.example.com/api/asdf\",\n            dict(key=\"uiop\", secret=\"5678\"),\n            parameters=dict(foo=\"bar\"),\n        )\n        self.write(params)\n\n\nclass OAuth1ServerRequestTokenHandler(RequestHandler):\n    def get(self):\n        self.write(\"oauth_token=zxcv&oauth_token_secret=1234\")\n\n\nclass OAuth1ServerAccessTokenHandler(RequestHandler):\n    def get(self):\n        self.write(\"oauth_token=uiop&oauth_token_secret=5678\")\n\n\nclass OAuth2ClientLoginHandler(RequestHandler, OAuth2Mixin):\n    def initialize(self, test):\n        self._OAUTH_AUTHORIZE_URL = test.get_url(\"/oauth2/server/authorize\")\n\n    def get(self):\n        res = self.authorize_redirect()  # type: ignore\n        assert res is None\n\n\nclass FacebookClientLoginHandler(RequestHandler, FacebookGraphMixin):\n    def initialize(self, test):\n        self._OAUTH_AUTHORIZE_URL = test.get_url(\"/facebook/server/authorize\")\n        self._OAUTH_ACCESS_TOKEN_URL = test.get_url(\"/facebook/server/access_token\")\n        self._FACEBOOK_BASE_URL = test.get_url(\"/facebook/server\")\n\n    @gen.coroutine\n    def get(self):\n        if self.get_argument(\"code\", None):\n            user = yield self.get_authenticated_user(\n                redirect_uri=self.request.full_url(),\n                client_id=self.settings[\"facebook_api_key\"],\n                client_secret=self.settings[\"facebook_secret\"],\n                code=self.get_argument(\"code\"),\n            )\n            self.write(user)\n        else:\n            self.authorize_redirect(\n                redirect_uri=self.request.full_url(),\n                client_id=self.settings[\"facebook_api_key\"],\n                extra_params={\"scope\": \"read_stream,offline_access\"},\n            )\n\n\nclass FacebookServerAccessTokenHandler(RequestHandler):\n    def get(self):\n        self.write(dict(access_token=\"asdf\", expires_in=3600))\n\n\nclass FacebookServerMeHandler(RequestHandler):\n    def get(self):\n        self.write(\"{}\")\n\n\nclass TwitterClientHandler(RequestHandler, TwitterMixin):\n    def initialize(self, test):\n        self._OAUTH_REQUEST_TOKEN_URL = test.get_url(\"/oauth1/server/request_token\")\n        self._OAUTH_ACCESS_TOKEN_URL = test.get_url(\"/twitter/server/access_token\")\n        self._OAUTH_AUTHORIZE_URL = test.get_url(\"/oauth1/server/authorize\")\n        self._OAUTH_AUTHENTICATE_URL = test.get_url(\"/twitter/server/authenticate\")\n        self._TWITTER_BASE_URL = test.get_url(\"/twitter/api\")\n\n    def get_auth_http_client(self):\n        return self.settings[\"http_client\"]\n\n\nclass TwitterClientLoginHandler(TwitterClientHandler):\n    @gen.coroutine\n    def get(self):\n        if self.get_argument(\"oauth_token\", None):\n            user = yield self.get_authenticated_user()\n            if user is None:\n                raise Exception(\"user is None\")\n            self.finish(user)\n            return\n        yield self.authorize_redirect()\n\n\nclass TwitterClientAuthenticateHandler(TwitterClientHandler):\n    # Like TwitterClientLoginHandler, but uses authenticate_redirect\n    # instead of authorize_redirect.\n    @gen.coroutine\n    def get(self):\n        if self.get_argument(\"oauth_token\", None):\n            user = yield self.get_authenticated_user()\n            if user is None:\n                raise Exception(\"user is None\")\n            self.finish(user)\n            return\n        yield self.authenticate_redirect()\n\n\nclass TwitterClientLoginGenCoroutineHandler(TwitterClientHandler):\n    @gen.coroutine\n    def get(self):\n        if self.get_argument(\"oauth_token\", None):\n            user = yield self.get_authenticated_user()\n            self.finish(user)\n        else:\n            # New style: with @gen.coroutine the result must be yielded\n            # or else the request will be auto-finished too soon.\n            yield self.authorize_redirect()\n\n\nclass TwitterClientShowUserHandler(TwitterClientHandler):\n    @gen.coroutine\n    def get(self):\n        # TODO: would be nice to go through the login flow instead of\n        # cheating with a hard-coded access token.\n        try:\n            response = yield self.twitter_request(\n                \"/users/show/%s\" % self.get_argument(\"name\"),\n                access_token=dict(key=\"hjkl\", secret=\"vbnm\"),\n            )\n        except HTTPClientError:\n            # TODO(bdarnell): Should we catch HTTP errors and\n            # transform some of them (like 403s) into AuthError?\n            self.set_status(500)\n            self.finish(\"error from twitter request\")\n        else:\n            self.finish(response)\n\n\nclass TwitterServerAccessTokenHandler(RequestHandler):\n    def get(self):\n        self.write(\"oauth_token=hjkl&oauth_token_secret=vbnm&screen_name=foo\")\n\n\nclass TwitterServerShowUserHandler(RequestHandler):\n    def get(self, screen_name):\n        if screen_name == \"error\":\n            raise HTTPError(500)\n        assert \"oauth_nonce\" in self.request.arguments\n        assert \"oauth_timestamp\" in self.request.arguments\n        assert \"oauth_signature\" in self.request.arguments\n        assert self.get_argument(\"oauth_consumer_key\") == \"test_twitter_consumer_key\"\n        assert self.get_argument(\"oauth_signature_method\") == \"HMAC-SHA1\"\n        assert self.get_argument(\"oauth_version\") == \"1.0\"\n        assert self.get_argument(\"oauth_token\") == \"hjkl\"\n        self.write(dict(screen_name=screen_name, name=screen_name.capitalize()))\n\n\nclass TwitterServerVerifyCredentialsHandler(RequestHandler):\n    def get(self):\n        assert \"oauth_nonce\" in self.request.arguments\n        assert \"oauth_timestamp\" in self.request.arguments\n        assert \"oauth_signature\" in self.request.arguments\n        assert self.get_argument(\"oauth_consumer_key\") == \"test_twitter_consumer_key\"\n        assert self.get_argument(\"oauth_signature_method\") == \"HMAC-SHA1\"\n        assert self.get_argument(\"oauth_version\") == \"1.0\"\n        assert self.get_argument(\"oauth_token\") == \"hjkl\"\n        self.write(dict(screen_name=\"foo\", name=\"Foo\"))\n\n\nclass AuthTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application(\n            [\n                # test endpoints\n                (\"/openid/client/login\", OpenIdClientLoginHandler, dict(test=self)),\n                (\n                    \"/oauth10/client/login\",\n                    OAuth1ClientLoginHandler,\n                    dict(test=self, version=\"1.0\"),\n                ),\n                (\n                    \"/oauth10/client/request_params\",\n                    OAuth1ClientRequestParametersHandler,\n                    dict(version=\"1.0\"),\n                ),\n                (\n                    \"/oauth10a/client/login\",\n                    OAuth1ClientLoginHandler,\n                    dict(test=self, version=\"1.0a\"),\n                ),\n                (\n                    \"/oauth10a/client/login_coroutine\",\n                    OAuth1ClientLoginCoroutineHandler,\n                    dict(test=self, version=\"1.0a\"),\n                ),\n                (\n                    \"/oauth10a/client/request_params\",\n                    OAuth1ClientRequestParametersHandler,\n                    dict(version=\"1.0a\"),\n                ),\n                (\"/oauth2/client/login\", OAuth2ClientLoginHandler, dict(test=self)),\n                (\"/facebook/client/login\", FacebookClientLoginHandler, dict(test=self)),\n                (\"/twitter/client/login\", TwitterClientLoginHandler, dict(test=self)),\n                (\n                    \"/twitter/client/authenticate\",\n                    TwitterClientAuthenticateHandler,\n                    dict(test=self),\n                ),\n                (\n                    \"/twitter/client/login_gen_coroutine\",\n                    TwitterClientLoginGenCoroutineHandler,\n                    dict(test=self),\n                ),\n                (\n                    \"/twitter/client/show_user\",\n                    TwitterClientShowUserHandler,\n                    dict(test=self),\n                ),\n                # simulated servers\n                (\"/openid/server/authenticate\", OpenIdServerAuthenticateHandler),\n                (\"/oauth1/server/request_token\", OAuth1ServerRequestTokenHandler),\n                (\"/oauth1/server/access_token\", OAuth1ServerAccessTokenHandler),\n                (\"/facebook/server/access_token\", FacebookServerAccessTokenHandler),\n                (\"/facebook/server/me\", FacebookServerMeHandler),\n                (\"/twitter/server/access_token\", TwitterServerAccessTokenHandler),\n                (r\"/twitter/api/users/show/(.*)\\.json\", TwitterServerShowUserHandler),\n                (\n                    r\"/twitter/api/account/verify_credentials\\.json\",\n                    TwitterServerVerifyCredentialsHandler,\n                ),\n            ],\n            http_client=self.http_client,\n            twitter_consumer_key=\"test_twitter_consumer_key\",\n            twitter_consumer_secret=\"test_twitter_consumer_secret\",\n            facebook_api_key=\"test_facebook_api_key\",\n            facebook_secret=\"test_facebook_secret\",\n        )\n\n    def test_openid_redirect(self):\n        response = self.fetch(\"/openid/client/login\", follow_redirects=False)\n        self.assertEqual(response.code, 302)\n        self.assertIn(\"/openid/server/authenticate?\", response.headers[\"Location\"])\n\n    def test_openid_get_user(self):\n        response = self.fetch(\n            \"/openid/client/login?openid.mode=blah\"\n            \"&openid.ns.ax=http://openid.net/srv/ax/1.0\"\n            \"&openid.ax.type.email=http://axschema.org/contact/email\"\n            \"&openid.ax.value.email=foo@example.com\"\n        )\n        response.rethrow()\n        parsed = json_decode(response.body)\n        self.assertEqual(parsed[\"email\"], \"foo@example.com\")\n\n    def test_oauth10_redirect(self):\n        response = self.fetch(\"/oauth10/client/login\", follow_redirects=False)\n        self.assertEqual(response.code, 302)\n        self.assertTrue(\n            response.headers[\"Location\"].endswith(\n                \"/oauth1/server/authorize?oauth_token=zxcv\"\n            )\n        )\n        # the cookie is base64('zxcv')|base64('1234')\n        self.assertIn(\n            '_oauth_request_token=\"enhjdg==|MTIzNA==\"',\n            response.headers[\"Set-Cookie\"],\n            response.headers[\"Set-Cookie\"],\n        )\n\n    def test_oauth10_get_user(self):\n        response = self.fetch(\n            \"/oauth10/client/login?oauth_token=zxcv\",\n            headers={\"Cookie\": \"_oauth_request_token=enhjdg==|MTIzNA==\"},\n        )\n        response.rethrow()\n        parsed = json_decode(response.body)\n        self.assertEqual(parsed[\"email\"], \"foo@example.com\")\n        self.assertEqual(parsed[\"access_token\"], dict(key=\"uiop\", secret=\"5678\"))\n\n    def test_oauth10_request_parameters(self):\n        response = self.fetch(\"/oauth10/client/request_params\")\n        response.rethrow()\n        parsed = json_decode(response.body)\n        self.assertEqual(parsed[\"oauth_consumer_key\"], \"asdf\")\n        self.assertEqual(parsed[\"oauth_token\"], \"uiop\")\n        self.assertIn(\"oauth_nonce\", parsed)\n        self.assertIn(\"oauth_signature\", parsed)\n\n    def test_oauth10a_redirect(self):\n        response = self.fetch(\"/oauth10a/client/login\", follow_redirects=False)\n        self.assertEqual(response.code, 302)\n        self.assertTrue(\n            response.headers[\"Location\"].endswith(\n                \"/oauth1/server/authorize?oauth_token=zxcv\"\n            )\n        )\n        # the cookie is base64('zxcv')|base64('1234')\n        self.assertTrue(\n            '_oauth_request_token=\"enhjdg==|MTIzNA==\"'\n            in response.headers[\"Set-Cookie\"],\n            response.headers[\"Set-Cookie\"],\n        )\n\n    def test_oauth10a_redirect_error(self):\n        with mock.patch.object(OAuth1ServerRequestTokenHandler, \"get\") as get:\n            get.side_effect = Exception(\"boom\")\n            with ExpectLog(app_log, \"Uncaught exception\"):\n                response = self.fetch(\"/oauth10a/client/login\", follow_redirects=False)\n            self.assertEqual(response.code, 500)\n\n    def test_oauth10a_get_user(self):\n        response = self.fetch(\n            \"/oauth10a/client/login?oauth_token=zxcv\",\n            headers={\"Cookie\": \"_oauth_request_token=enhjdg==|MTIzNA==\"},\n        )\n        response.rethrow()\n        parsed = json_decode(response.body)\n        self.assertEqual(parsed[\"email\"], \"foo@example.com\")\n        self.assertEqual(parsed[\"access_token\"], dict(key=\"uiop\", secret=\"5678\"))\n\n    def test_oauth10a_request_parameters(self):\n        response = self.fetch(\"/oauth10a/client/request_params\")\n        response.rethrow()\n        parsed = json_decode(response.body)\n        self.assertEqual(parsed[\"oauth_consumer_key\"], \"asdf\")\n        self.assertEqual(parsed[\"oauth_token\"], \"uiop\")\n        self.assertIn(\"oauth_nonce\", parsed)\n        self.assertIn(\"oauth_signature\", parsed)\n\n    def test_oauth10a_get_user_coroutine_exception(self):\n        response = self.fetch(\n            \"/oauth10a/client/login_coroutine?oauth_token=zxcv&fail_in_get_user=true\",\n            headers={\"Cookie\": \"_oauth_request_token=enhjdg==|MTIzNA==\"},\n        )\n        self.assertEqual(response.code, 503)\n\n    def test_oauth2_redirect(self):\n        response = self.fetch(\"/oauth2/client/login\", follow_redirects=False)\n        self.assertEqual(response.code, 302)\n        self.assertIn(\"/oauth2/server/authorize?\", response.headers[\"Location\"])\n\n    def test_facebook_login(self):\n        response = self.fetch(\"/facebook/client/login\", follow_redirects=False)\n        self.assertEqual(response.code, 302)\n        self.assertTrue(\"/facebook/server/authorize?\" in response.headers[\"Location\"])\n        response = self.fetch(\n            \"/facebook/client/login?code=1234\", follow_redirects=False\n        )\n        self.assertEqual(response.code, 200)\n        user = json_decode(response.body)\n        self.assertEqual(user[\"access_token\"], \"asdf\")\n        self.assertEqual(user[\"session_expires\"], \"3600\")\n\n    def base_twitter_redirect(self, url):\n        # Same as test_oauth10a_redirect\n        response = self.fetch(url, follow_redirects=False)\n        self.assertEqual(response.code, 302)\n        self.assertTrue(\n            response.headers[\"Location\"].endswith(\n                \"/oauth1/server/authorize?oauth_token=zxcv\"\n            )\n        )\n        # the cookie is base64('zxcv')|base64('1234')\n        self.assertIn(\n            '_oauth_request_token=\"enhjdg==|MTIzNA==\"',\n            response.headers[\"Set-Cookie\"],\n            response.headers[\"Set-Cookie\"],\n        )\n\n    def test_twitter_redirect(self):\n        self.base_twitter_redirect(\"/twitter/client/login\")\n\n    def test_twitter_redirect_gen_coroutine(self):\n        self.base_twitter_redirect(\"/twitter/client/login_gen_coroutine\")\n\n    def test_twitter_authenticate_redirect(self):\n        response = self.fetch(\"/twitter/client/authenticate\", follow_redirects=False)\n        self.assertEqual(response.code, 302)\n        self.assertTrue(\n            response.headers[\"Location\"].endswith(\n                \"/twitter/server/authenticate?oauth_token=zxcv\"\n            ),\n            response.headers[\"Location\"],\n        )\n        # the cookie is base64('zxcv')|base64('1234')\n        self.assertIn(\n            '_oauth_request_token=\"enhjdg==|MTIzNA==\"',\n            response.headers[\"Set-Cookie\"],\n            response.headers[\"Set-Cookie\"],\n        )\n\n    def test_twitter_get_user(self):\n        response = self.fetch(\n            \"/twitter/client/login?oauth_token=zxcv\",\n            headers={\"Cookie\": \"_oauth_request_token=enhjdg==|MTIzNA==\"},\n        )\n        response.rethrow()\n        parsed = json_decode(response.body)\n        self.assertEqual(\n            parsed,\n            {\n                \"access_token\": {\n                    \"key\": \"hjkl\",\n                    \"screen_name\": \"foo\",\n                    \"secret\": \"vbnm\",\n                },\n                \"name\": \"Foo\",\n                \"screen_name\": \"foo\",\n                \"username\": \"foo\",\n            },\n        )\n\n    def test_twitter_show_user(self):\n        response = self.fetch(\"/twitter/client/show_user?name=somebody\")\n        response.rethrow()\n        self.assertEqual(\n            json_decode(response.body), {\"name\": \"Somebody\", \"screen_name\": \"somebody\"}\n        )\n\n    def test_twitter_show_user_error(self):\n        response = self.fetch(\"/twitter/client/show_user?name=error\")\n        self.assertEqual(response.code, 500)\n        self.assertEqual(response.body, b\"error from twitter request\")\n\n\nclass GoogleLoginHandler(RequestHandler, GoogleOAuth2Mixin):\n    def initialize(self, test):\n        self.test = test\n        self._OAUTH_REDIRECT_URI = test.get_url(\"/client/login\")\n        self._OAUTH_AUTHORIZE_URL = test.get_url(\"/google/oauth2/authorize\")\n        self._OAUTH_ACCESS_TOKEN_URL = test.get_url(\"/google/oauth2/token\")\n\n    @gen.coroutine\n    def get(self):\n        code = self.get_argument(\"code\", None)\n        if code is not None:\n            # retrieve authenticate google user\n            access = yield self.get_authenticated_user(self._OAUTH_REDIRECT_URI, code)\n            user = yield self.oauth2_request(\n                self.test.get_url(\"/google/oauth2/userinfo\"),\n                access_token=access[\"access_token\"],\n            )\n            # return the user and access token as json\n            user[\"access_token\"] = access[\"access_token\"]\n            self.write(user)\n        else:\n            self.authorize_redirect(\n                redirect_uri=self._OAUTH_REDIRECT_URI,\n                client_id=self.settings[\"google_oauth\"][\"key\"],\n                scope=[\"profile\", \"email\"],\n                response_type=\"code\",\n                extra_params={\"prompt\": \"select_account\"},\n            )\n\n\nclass GoogleOAuth2AuthorizeHandler(RequestHandler):\n    def get(self):\n        # issue a fake auth code and redirect to redirect_uri\n        code = \"fake-authorization-code\"\n        self.redirect(url_concat(self.get_argument(\"redirect_uri\"), dict(code=code)))\n\n\nclass GoogleOAuth2TokenHandler(RequestHandler):\n    def post(self):\n        assert self.get_argument(\"code\") == \"fake-authorization-code\"\n        # issue a fake token\n        self.finish(\n            {\"access_token\": \"fake-access-token\", \"expires_in\": \"never-expires\"}\n        )\n\n\nclass GoogleOAuth2UserinfoHandler(RequestHandler):\n    def get(self):\n        assert self.get_argument(\"access_token\") == \"fake-access-token\"\n        # return a fake user\n        self.finish({\"name\": \"Foo\", \"email\": \"foo@example.com\"})\n\n\nclass GoogleOAuth2Test(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application(\n            [\n                # test endpoints\n                (\"/client/login\", GoogleLoginHandler, dict(test=self)),\n                # simulated google authorization server endpoints\n                (\"/google/oauth2/authorize\", GoogleOAuth2AuthorizeHandler),\n                (\"/google/oauth2/token\", GoogleOAuth2TokenHandler),\n                (\"/google/oauth2/userinfo\", GoogleOAuth2UserinfoHandler),\n            ],\n            google_oauth={\n                \"key\": \"fake_google_client_id\",\n                \"secret\": \"fake_google_client_secret\",\n            },\n        )\n\n    def test_google_login(self):\n        response = self.fetch(\"/client/login\")\n        self.assertDictEqual(\n            {\n                \"name\": \"Foo\",\n                \"email\": \"foo@example.com\",\n                \"access_token\": \"fake-access-token\",\n            },\n            json_decode(response.body),\n        )\n"
  },
  {
    "path": "tornado/test/autoreload_test.py",
    "content": "import os\nimport shutil\nimport subprocess\nimport sys\nimport textwrap\nimport time\nimport unittest\nfrom subprocess import Popen\nfrom tempfile import mkdtemp\n\n\nclass AutoreloadTest(unittest.TestCase):\n    def setUp(self):\n        # When these tests fail the output sometimes exceeds the default maxDiff.\n        self.maxDiff = 1024\n\n        self.path = mkdtemp()\n\n        # Most test apps run themselves twice via autoreload. The first time it manually triggers\n        # a reload (could also do this by touching a file but this is faster since filesystem\n        # timestamps are not necessarily high resolution). The second time it exits directly\n        # so that the autoreload wrapper (if it is used) doesn't catch it.\n        #\n        # The last line of each such test's \"main\" program should be\n        #     exec(open(\"run_twice_magic.py\").read())\n        self.write_files({\"run_twice_magic.py\": \"\"\"\n                    import os\n                    import sys\n\n                    import tornado.autoreload\n\n                    sys.stdout.flush()\n\n                    if \"TESTAPP_STARTED\" not in os.environ:\n                        os.environ[\"TESTAPP_STARTED\"] = \"1\"\n                        tornado.autoreload._reload()\n                    else:\n                        os._exit(0)\n                \"\"\"})\n\n    def tearDown(self):\n        try:\n            shutil.rmtree(self.path)\n        except OSError:\n            # Windows disallows deleting files that are in use by\n            # another process, and even though we've waited for our\n            # child process below, it appears that its lock on these\n            # files is not guaranteed to be released by this point.\n            # Sleep and try again (once).\n            time.sleep(1)\n            shutil.rmtree(self.path)\n\n    def write_files(self, tree, base_path=None):\n        \"\"\"Write a directory tree to self.path.\n\n        tree is a dictionary mapping file names to contents, or\n        sub-dictionaries representing subdirectories.\n        \"\"\"\n        if base_path is None:\n            base_path = self.path\n        for name, contents in tree.items():\n            if isinstance(contents, dict):\n                os.mkdir(os.path.join(base_path, name))\n                self.write_files(contents, os.path.join(base_path, name))\n            else:\n                with open(os.path.join(base_path, name), \"w\", encoding=\"utf-8\") as f:\n                    f.write(textwrap.dedent(contents))\n\n    def run_subprocess(self, args):\n        # Make sure the tornado module under test is available to the test\n        # application\n        parts = [os.getcwd()]\n        if \"PYTHONPATH\" in os.environ:\n            parts += [\n                os.path.join(os.getcwd(), part)\n                for part in os.environ[\"PYTHONPATH\"].split(os.pathsep)\n            ]\n        pythonpath = os.pathsep.join(parts)\n\n        p = Popen(\n            args,\n            stdout=subprocess.PIPE,\n            env=dict(os.environ, PYTHONPATH=pythonpath),\n            cwd=self.path,\n            universal_newlines=True,\n            encoding=\"utf-8\",\n        )\n\n        # This timeout needs to be fairly generous for pypy due to jit\n        # warmup costs.\n        for i in range(40):\n            if p.poll() is not None:\n                break\n            time.sleep(0.1)\n        else:\n            p.kill()\n            raise Exception(\"subprocess failed to terminate\")\n\n        out = p.communicate()[0]\n        self.assertEqual(p.returncode, 0)\n        return out\n\n    def test_reload(self):\n        main = \"\"\"\\\nimport sys\n\n# In module mode, the path is set to the parent directory and we can import testapp.\ntry:\n    import testapp\nexcept ImportError:\n    print(\"import testapp failed\")\nelse:\n    print(\"import testapp succeeded\")\n\nspec = getattr(sys.modules[__name__], '__spec__', None)\nprint(f\"Starting {__name__=}, __spec__.name={getattr(spec, 'name', None)}\")\nexec(open(\"run_twice_magic.py\", encoding=\"utf-8\").read())\n\"\"\"\n\n        # Create temporary test application\n        self.write_files(\n            {\n                \"testapp\": {\n                    \"__init__.py\": \"\",\n                    \"__main__.py\": main,\n                },\n            }\n        )\n\n        # The autoreload wrapper should support all the same modes as the python interpreter.\n        # The wrapper itself should have no effect on this test so we try all modes with and\n        # without it.\n        for wrapper in [False, True]:\n            with self.subTest(wrapper=wrapper):\n                with self.subTest(mode=\"module\"):\n                    if wrapper:\n                        base_args = [sys.executable, \"-m\", \"tornado.autoreload\"]\n                    else:\n                        base_args = [sys.executable]\n                    # In module mode, the path is set to the parent directory and we can import\n                    # testapp. Also, the __spec__.name is set to the fully qualified module name.\n                    out = self.run_subprocess(base_args + [\"-m\", \"testapp\"])\n                    self.assertEqual(\n                        out,\n                        (\n                            \"import testapp succeeded\\n\"\n                            + \"Starting __name__='__main__', __spec__.name=testapp.__main__\\n\"\n                        )\n                        * 2,\n                    )\n\n                with self.subTest(mode=\"file\"):\n                    out = self.run_subprocess(base_args + [\"testapp/__main__.py\"])\n                    # In file mode, we do not expect the path to be set so we can import testapp,\n                    # but when the wrapper is used the -m argument to the python interpreter\n                    # does this for us.\n                    expect_import = (\n                        \"import testapp succeeded\"\n                        if wrapper\n                        else \"import testapp failed\"\n                    )\n                    # In file mode there is no qualified module spec.\n                    self.assertEqual(\n                        out,\n                        f\"{expect_import}\\nStarting __name__='__main__', __spec__.name=None\\n\"\n                        * 2,\n                    )\n\n                with self.subTest(mode=\"directory\"):\n                    # Running as a directory finds __main__.py like a module. It does not manipulate\n                    # sys.path but it does set a spec with a name of exactly __main__.\n                    out = self.run_subprocess(base_args + [\"testapp\"])\n                    expect_import = (\n                        \"import testapp succeeded\"\n                        if wrapper\n                        else \"import testapp failed\"\n                    )\n                    self.assertEqual(\n                        out,\n                        f\"{expect_import}\\nStarting __name__='__main__', __spec__.name=__main__\\n\"\n                        * 2,\n                    )\n\n    def test_reload_wrapper_preservation(self):\n        # This test verifies that when `python -m tornado.autoreload`\n        # is used on an application that also has an internal\n        # autoreload, the reload wrapper is preserved on restart.\n        main = \"\"\"\\\nimport sys\n\n# This import will fail if path is not set up correctly\nimport testapp\n\nif 'tornado.autoreload' not in sys.modules:\n    raise Exception('started without autoreload wrapper')\n\nprint('Starting')\nexec(open(\"run_twice_magic.py\", encoding=\"utf-8\").read())\n\"\"\"\n\n        self.write_files(\n            {\n                \"testapp\": {\n                    \"__init__.py\": \"\",\n                    \"__main__.py\": main,\n                },\n            }\n        )\n\n        out = self.run_subprocess(\n            [sys.executable, \"-m\", \"tornado.autoreload\", \"-m\", \"testapp\"]\n        )\n        self.assertEqual(out, \"Starting\\n\" * 2)\n\n    def test_reload_wrapper_args(self):\n        main = \"\"\"\\\nimport os\nimport sys\n\nprint(os.path.basename(sys.argv[0]))\nprint(f'argv={sys.argv[1:]}')\nexec(open(\"run_twice_magic.py\", encoding=\"utf-8\").read())\n\"\"\"\n        # Create temporary test application\n        self.write_files({\"main.py\": main})\n\n        # Make sure the tornado module under test is available to the test\n        # application\n        out = self.run_subprocess(\n            [\n                sys.executable,\n                \"-m\",\n                \"tornado.autoreload\",\n                \"main.py\",\n                \"arg1\",\n                \"--arg2\",\n                \"-m\",\n                \"arg3\",\n            ],\n        )\n\n        self.assertEqual(out, \"main.py\\nargv=['arg1', '--arg2', '-m', 'arg3']\\n\" * 2)\n\n    def test_reload_wrapper_until_success(self):\n        main = \"\"\"\\\nimport os\nimport sys\n\nif \"TESTAPP_STARTED\" in os.environ:\n    print(\"exiting cleanly\")\n    sys.exit(0)\nelse:\n    print(\"reloading\")\n    exec(open(\"run_twice_magic.py\", encoding=\"utf-8\").read())\n\"\"\"\n\n        # Create temporary test application\n        self.write_files({\"main.py\": main})\n\n        out = self.run_subprocess(\n            [sys.executable, \"-m\", \"tornado.autoreload\", \"--until-success\", \"main.py\"]\n        )\n\n        self.assertEqual(out, \"reloading\\nexiting cleanly\\n\")\n"
  },
  {
    "path": "tornado/test/circlerefs_test.py",
    "content": "\"\"\"Test script to find circular references.\n\nCircular references are not leaks per se, because they will eventually\nbe GC'd. However, on CPython, they prevent the reference-counting fast\npath from being used and instead rely on the slower full GC. This\nincreases memory footprint and CPU overhead, so we try to eliminate\ncircular references created by normal operation.\n\"\"\"\n\nimport asyncio\nimport contextlib\nimport gc\nimport io\nimport sys\nimport traceback\nimport types\nimport unittest\n\nimport tornado\nfrom tornado import gen, httpclient, web\nfrom tornado.test.util import skipNotCPython\n\n\ndef find_circular_references(garbage):\n    \"\"\"Find circular references in a list of objects.\n\n    The garbage list contains objects that participate in a cycle,\n    but also the larger set of objects kept alive by that cycle.\n    This function finds subsets of those objects that make up\n    the cycle(s).\n    \"\"\"\n\n    def inner(level):\n        for item in level:\n            item_id = id(item)\n            if item_id not in garbage_ids:\n                continue\n            if item_id in visited_ids:\n                continue\n            if item_id in stack_ids:\n                candidate = stack[stack.index(item) :]\n                candidate.append(item)\n                found.append(candidate)\n                continue\n\n            stack.append(item)\n            stack_ids.add(item_id)\n            inner(gc.get_referents(item))\n            stack.pop()\n            stack_ids.remove(item_id)\n            visited_ids.add(item_id)\n\n    found: list[object] = []\n    stack = []\n    stack_ids = set()\n    garbage_ids = set(map(id, garbage))\n    visited_ids = set()\n\n    inner(garbage)\n    return found\n\n\n@contextlib.contextmanager\ndef assert_no_cycle_garbage():\n    \"\"\"Raise AssertionError if the wrapped code creates garbage with cycles.\"\"\"\n    gc.disable()\n    gc.collect()\n    gc.set_debug(gc.DEBUG_STATS | gc.DEBUG_SAVEALL)\n    yield\n    try:\n        # We have DEBUG_STATS on which causes gc.collect to write to stderr.\n        # Capture the output instead of spamming the logs on passing runs.\n        f = io.StringIO()\n        old_stderr = sys.stderr\n        sys.stderr = f\n        try:\n            gc.collect()\n        finally:\n            sys.stderr = old_stderr\n        garbage = gc.garbage[:]\n        # Must clear gc.garbage (the same object, not just replacing it with a\n        # new list) to avoid warnings at shutdown.\n        gc.garbage[:] = []\n        if len(garbage) == 0:\n            return\n        for circular in find_circular_references(garbage):\n            f.write(\"\\n==========\\n Circular \\n==========\")\n            for item in circular:\n                f.write(f\"\\n    {repr(item)}\")\n            for item in circular:\n                if isinstance(item, types.FrameType):\n                    f.write(f\"\\nLocals: {item.f_locals}\")\n                    f.write(f\"\\nTraceback: {repr(item)}\")\n                    traceback.print_stack(item)\n        del garbage\n        raise AssertionError(f.getvalue())\n    finally:\n        gc.set_debug(0)\n        gc.enable()\n\n\n# GC behavior is cpython-specific\n@skipNotCPython\nclass CircleRefsTest(unittest.TestCase):\n    def test_known_leak(self):\n        # Construct a known leak scenario to make sure the test harness works.\n        class C:\n            def __init__(self, name):\n                self.name = name\n                self.a: C | None = None\n                self.b: C | None = None\n                self.c: C | None = None\n\n            def __repr__(self):\n                return f\"name={self.name}\"\n\n        with self.assertRaises(AssertionError) as cm:\n            with assert_no_cycle_garbage():\n                # a and b form a reference cycle. c is not part of the cycle,\n                # but it cannot be GC'd while a and b are alive.\n                a = C(\"a\")\n                b = C(\"b\")\n                c = C(\"c\")\n                a.b = b\n                a.c = c\n                b.a = a\n                b.c = c\n                del a, b\n        self.assertIn(\"Circular\", str(cm.exception))\n        # Leading spaces ensure we only catch these at the beginning of a line, meaning they are a\n        # cycle participant and not simply the contents of a locals dict or similar container. (This\n        # depends on the formatting above which isn't ideal but this test evolved from a\n        # command-line script) Note that the behavior here changed in python 3.11; in newer pythons\n        # locals are handled a bit differently and the test passes without the spaces.\n        self.assertIn(\"    name=a\", str(cm.exception))\n        self.assertIn(\"    name=b\", str(cm.exception))\n        self.assertNotIn(\"    name=c\", str(cm.exception))\n\n    async def run_handler(self, handler_class):\n        app = web.Application(\n            [\n                (r\"/\", handler_class),\n            ]\n        )\n        socket, port = tornado.testing.bind_unused_port()\n        server = tornado.httpserver.HTTPServer(app)\n        server.add_socket(socket)\n\n        client = httpclient.AsyncHTTPClient()\n        with assert_no_cycle_garbage():\n            # Only the fetch (and the corresponding server-side handler)\n            # are being tested for cycles. In particular, the Application\n            # object has internal cycles (as of this writing) which we don't\n            # care to fix since in real world usage the Application object\n            # is effectively a global singleton.\n            await client.fetch(f\"http://127.0.0.1:{port}/\")\n        client.close()\n        server.stop()\n        socket.close()\n\n    def test_sync_handler(self):\n        class Handler(web.RequestHandler):\n            def get(self):\n                self.write(\"ok\\n\")\n\n        asyncio.run(self.run_handler(Handler))\n\n    def test_finish_exception_handler(self):\n        class Handler(web.RequestHandler):\n            def get(self):\n                raise web.Finish(\"ok\\n\")\n\n        asyncio.run(self.run_handler(Handler))\n\n    def test_coro_handler(self):\n        class Handler(web.RequestHandler):\n            @gen.coroutine\n            def get(self):\n                yield asyncio.sleep(0.01)\n                self.write(\"ok\\n\")\n\n        asyncio.run(self.run_handler(Handler))\n\n    def test_async_handler(self):\n        class Handler(web.RequestHandler):\n            async def get(self):\n                await asyncio.sleep(0.01)\n                self.write(\"ok\\n\")\n\n        asyncio.run(self.run_handler(Handler))\n\n    def test_run_on_executor(self):\n        # From https://github.com/tornadoweb/tornado/issues/2620\n        #\n        # When this test was introduced it found cycles in IOLoop.add_future\n        # and tornado.concurrent.chain_future.\n        import concurrent.futures\n\n        with concurrent.futures.ThreadPoolExecutor(1) as thread_pool:\n\n            class Factory:\n                executor = thread_pool\n\n                @tornado.concurrent.run_on_executor\n                def run(self):\n                    return None\n\n            factory = Factory()\n\n            async def main():\n                # The cycle is not reported on the first call. It's not clear why.\n                for i in range(2):\n                    await factory.run()\n\n            with assert_no_cycle_garbage():\n                asyncio.run(main())\n"
  },
  {
    "path": "tornado/test/concurrent_test.py",
    "content": "#\n# Copyright 2012 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\nimport logging\nimport re\nimport socket\nimport unittest\nfrom concurrent import futures\n\nfrom tornado import gen\nfrom tornado.concurrent import (\n    Future,\n    chain_future,\n    future_set_result_unless_cancelled,\n    run_on_executor,\n)\nfrom tornado.escape import to_unicode, utf8\nfrom tornado.iostream import IOStream\nfrom tornado.tcpserver import TCPServer\nfrom tornado.testing import AsyncTestCase, bind_unused_port, gen_test\n\n\nclass MiscFutureTest(AsyncTestCase):\n    def test_future_set_result_unless_cancelled(self):\n        fut: Future[int] = Future()\n        future_set_result_unless_cancelled(fut, 42)\n        self.assertEqual(fut.result(), 42)\n        self.assertFalse(fut.cancelled())\n\n        fut = Future()\n        fut.cancel()\n        is_cancelled = fut.cancelled()\n        future_set_result_unless_cancelled(fut, 42)\n        self.assertEqual(fut.cancelled(), is_cancelled)\n        if not is_cancelled:\n            self.assertEqual(fut.result(), 42)\n\n\nclass ChainFutureTest(AsyncTestCase):\n    @gen_test\n    async def test_asyncio_futures(self):\n        fut: Future[int] = Future()\n        fut2: Future[int] = Future()\n        chain_future(fut, fut2)\n        fut.set_result(42)\n        result = await fut2\n        self.assertEqual(result, 42)\n\n    @gen_test\n    async def test_concurrent_futures(self):\n        # A three-step chain: two concurrent futures (showing that both arguments to chain_future\n        # can be concurrent futures), and then one from a concurrent future to an asyncio future so\n        # we can use it in await.\n        fut: futures.Future[int] = futures.Future()\n        fut2: futures.Future[int] = futures.Future()\n        fut3: Future[int] = Future()\n        chain_future(fut, fut2)\n        chain_future(fut2, fut3)\n        fut.set_result(42)\n        result = await fut3\n        self.assertEqual(result, 42)\n\n\n# The following series of classes demonstrate and test various styles\n# of use, with and without generators and futures.\n\n\nclass CapServer(TCPServer):\n    @gen.coroutine\n    def handle_stream(self, stream, address):\n        data = yield stream.read_until(b\"\\n\")\n        data = to_unicode(data)\n        if data == data.upper():\n            stream.write(b\"error\\talready capitalized\\n\")\n        else:\n            # data already has \\n\n            stream.write(utf8(\"ok\\t%s\" % data.upper()))\n        stream.close()\n\n\nclass CapError(Exception):\n    pass\n\n\nclass BaseCapClient:\n    def __init__(self, port):\n        self.port = port\n\n    def process_response(self, data):\n        m = re.match(\"(.*)\\t(.*)\\n\", to_unicode(data))\n        if m is None:\n            raise Exception(\"did not match\")\n        status, message = m.groups()\n        if status == \"ok\":\n            return message\n        else:\n            raise CapError(message)\n\n\nclass GeneratorCapClient(BaseCapClient):\n    @gen.coroutine\n    def capitalize(self, request_data):\n        logging.debug(\"capitalize\")\n        stream = IOStream(socket.socket())\n        logging.debug(\"connecting\")\n        yield stream.connect((\"127.0.0.1\", self.port))\n        stream.write(utf8(request_data + \"\\n\"))\n        logging.debug(\"reading\")\n        data = yield stream.read_until(b\"\\n\")\n        logging.debug(\"returning\")\n        stream.close()\n        raise gen.Return(self.process_response(data))\n\n\nclass GeneratorCapClientTest(AsyncTestCase):\n    def setUp(self):\n        super().setUp()\n        self.server = CapServer()\n        sock, port = bind_unused_port()\n        self.server.add_sockets([sock])\n        self.client = GeneratorCapClient(port=port)\n\n    def tearDown(self):\n        self.server.stop()\n        super().tearDown()\n\n    def test_future(self):\n        future = self.client.capitalize(\"hello\")\n        self.io_loop.add_future(future, self.stop)\n        self.wait()\n        self.assertEqual(future.result(), \"HELLO\")\n\n    def test_future_error(self):\n        future = self.client.capitalize(\"HELLO\")\n        self.io_loop.add_future(future, self.stop)\n        self.wait()\n        self.assertRaisesRegex(CapError, \"already capitalized\", future.result)\n\n    def test_generator(self):\n        @gen.coroutine\n        def f():\n            result = yield self.client.capitalize(\"hello\")\n            self.assertEqual(result, \"HELLO\")\n\n        self.io_loop.run_sync(f)\n\n    def test_generator_error(self):\n        @gen.coroutine\n        def f():\n            with self.assertRaisesRegex(CapError, \"already capitalized\"):\n                yield self.client.capitalize(\"HELLO\")\n\n        self.io_loop.run_sync(f)\n\n\nclass RunOnExecutorTest(AsyncTestCase):\n    @gen_test\n    def test_no_calling(self):\n        class Object:\n            def __init__(self):\n                self.executor = futures.thread.ThreadPoolExecutor(1)\n\n            @run_on_executor\n            def f(self):\n                return 42\n\n        o = Object()\n        answer = yield o.f()\n        self.assertEqual(answer, 42)\n\n    @gen_test\n    def test_call_with_no_args(self):\n        class Object:\n            def __init__(self):\n                self.executor = futures.ThreadPoolExecutor(1)\n\n            @run_on_executor()\n            def f(self):\n                return 42\n\n        o = Object()\n        answer = yield o.f()\n        self.assertEqual(answer, 42)\n\n    @gen_test\n    def test_call_with_executor(self):\n        class Object:\n            def __init__(self):\n                self.__executor = futures.ThreadPoolExecutor(1)\n\n            @run_on_executor(executor=\"_Object__executor\")\n            def f(self):\n                return 42\n\n        o = Object()\n        answer = yield o.f()\n        self.assertEqual(answer, 42)\n\n    @gen_test\n    def test_async_await(self):\n        class Object:\n            def __init__(self):\n                self.executor = futures.ThreadPoolExecutor(1)\n\n            @run_on_executor()\n            def f(self):\n                return 42\n\n        o = Object()\n\n        async def f():\n            answer = await o.f()\n            return answer\n\n        result = yield f()\n        self.assertEqual(result, 42)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tornado/test/csv_translations/fr_FR.csv",
    "content": "\"school\",\"école\"\n"
  },
  {
    "path": "tornado/test/curl_httpclient_test.py",
    "content": "import unittest\nfrom hashlib import md5\n\nfrom tornado import gen\nfrom tornado.escape import utf8\nfrom tornado.test import httpclient_test\nfrom tornado.testing import AsyncHTTPTestCase\nfrom tornado.web import Application, RequestHandler\n\ntry:\n    import pycurl\nexcept ImportError:\n    pycurl = None  # type: ignore\n\nif pycurl is not None:\n    from tornado.curl_httpclient import CurlAsyncHTTPClient\n\n\n@unittest.skipIf(pycurl is None, \"pycurl module not present\")\nclass CurlHTTPClientCommonTestCase(httpclient_test.HTTPClientCommonTestCase):\n    def get_http_client(self):\n        client = CurlAsyncHTTPClient(defaults=dict(allow_ipv6=False))\n        # make sure AsyncHTTPClient magic doesn't give us the wrong class\n        self.assertTrue(isinstance(client, CurlAsyncHTTPClient))\n        return client\n\n\nclass DigestAuthHandler(RequestHandler):\n    def initialize(self, username, password):\n        self.username = username\n        self.password = password\n\n    def get(self):\n        realm = \"test\"\n        opaque = \"asdf\"\n        # Real implementations would use a random nonce.\n        nonce = \"1234\"\n\n        auth_header = self.request.headers.get(\"Authorization\", None)\n        if auth_header is not None:\n            auth_mode, params = auth_header.split(\" \", 1)\n            assert auth_mode == \"Digest\"\n            param_dict = {}\n            for pair in params.split(\",\"):\n                k, v = pair.strip().split(\"=\", 1)\n                if v[0] == '\"' and v[-1] == '\"':\n                    v = v[1:-1]\n                param_dict[k] = v\n            assert param_dict[\"realm\"] == realm\n            assert param_dict[\"opaque\"] == opaque\n            assert param_dict[\"nonce\"] == nonce\n            assert param_dict[\"username\"] == self.username\n            assert param_dict[\"uri\"] == self.request.path\n            h1 = md5(utf8(f\"{self.username}:{realm}:{self.password}\")).hexdigest()\n            h2 = md5(utf8(f\"{self.request.method}:{self.request.path}\")).hexdigest()\n            digest = md5(utf8(f\"{h1}:{nonce}:{h2}\")).hexdigest()\n            if digest == param_dict[\"response\"]:\n                self.write(\"ok\")\n            else:\n                self.write(\"fail\")\n        else:\n            self.set_status(401)\n            self.set_header(\n                \"WWW-Authenticate\",\n                f'Digest realm=\"{realm}\", nonce=\"{nonce}\", opaque=\"{opaque}\"',\n            )\n\n\nclass CustomReasonHandler(RequestHandler):\n    def get(self):\n        self.set_status(200, \"Custom reason\")\n\n\nclass CustomFailReasonHandler(RequestHandler):\n    def get(self):\n        self.set_status(400, \"Custom reason\")\n\n\n@unittest.skipIf(pycurl is None, \"pycurl module not present\")\nclass CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super().setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application(\n            [\n                (\"/digest\", DigestAuthHandler, {\"username\": \"foo\", \"password\": \"bar\"}),\n                (\n                    \"/digest_non_ascii\",\n                    DigestAuthHandler,\n                    {\"username\": \"foo\", \"password\": \"barユ£\"},\n                ),\n                (\"/custom_reason\", CustomReasonHandler),\n                (\"/custom_fail_reason\", CustomFailReasonHandler),\n            ]\n        )\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(\n            force_instance=True, defaults=dict(allow_ipv6=False), **kwargs\n        )\n\n    def test_digest_auth(self):\n        response = self.fetch(\n            \"/digest\", auth_mode=\"digest\", auth_username=\"foo\", auth_password=\"bar\"\n        )\n        self.assertEqual(response.body, b\"ok\")\n\n    def test_custom_reason(self):\n        response = self.fetch(\"/custom_reason\")\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch(\"/custom_fail_reason\")\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_digest_auth_non_ascii(self):\n        response = self.fetch(\n            \"/digest_non_ascii\",\n            auth_mode=\"digest\",\n            auth_username=\"foo\",\n            auth_password=\"barユ£\",\n        )\n        self.assertEqual(response.body, b\"ok\")\n\n    def test_streaming_callback_not_permitted(self):\n        @gen.coroutine\n        def _recv_chunk(chunk):\n            yield gen.moment\n\n        with self.assertRaises(TypeError):\n            self.fetch(\"/digest\", streaming_callback=_recv_chunk)\n\n        import asyncio\n\n        async def _async_recv_chunk(chunk):\n            await asyncio.sleep(0)\n\n        with self.assertRaises(TypeError):\n            self.fetch(\"/digest\", streaming_callback=_async_recv_chunk)\n"
  },
  {
    "path": "tornado/test/escape_test.py",
    "content": "import unittest\nfrom typing import Any\n\nimport tornado\nfrom tornado.escape import (\n    json_decode,\n    json_encode,\n    recursive_unicode,\n    squeeze,\n    to_unicode,\n    url_escape,\n    url_unescape,\n    utf8,\n    xhtml_escape,\n    xhtml_unescape,\n)\nfrom tornado.util import unicode_type\n\nlinkify_tests: list[tuple[str | bytes, dict[str, Any], str]] = [\n    # (input, linkify_kwargs, expected_output)\n    (\n        \"hello http://world.com/!\",\n        {},\n        'hello <a href=\"http://world.com/\">http://world.com/</a>!',\n    ),\n    (\n        \"hello http://world.com/with?param=true&stuff=yes\",\n        {},\n        'hello <a href=\"http://world.com/with?param=true&amp;stuff=yes\">http://world.com/with?param=true&amp;stuff=yes</a>',  # noqa: E501\n    ),\n    # an opened paren followed by many chars killed Gruber's regex\n    (\n        \"http://url.com/w(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n        {},\n        '<a href=\"http://url.com/w\">http://url.com/w</a>(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',  # noqa: E501\n    ),\n    # as did too many dots at the end\n    (\n        \"http://url.com/withmany.......................................\",\n        {},\n        '<a href=\"http://url.com/withmany\">http://url.com/withmany</a>.......................................',  # noqa: E501\n    ),\n    (\n        \"http://url.com/withmany((((((((((((((((((((((((((((((((((a)\",\n        {},\n        '<a href=\"http://url.com/withmany\">http://url.com/withmany</a>((((((((((((((((((((((((((((((((((a)',  # noqa: E501\n    ),\n    # some examples from http://daringfireball.net/2009/11/liberal_regex_for_matching_urls\n    # plus a fex extras (such as multiple parentheses).\n    (\n        \"http://foo.com/blah_blah\",\n        {},\n        '<a href=\"http://foo.com/blah_blah\">http://foo.com/blah_blah</a>',\n    ),\n    (\n        \"http://foo.com/blah_blah/\",\n        {},\n        '<a href=\"http://foo.com/blah_blah/\">http://foo.com/blah_blah/</a>',\n    ),\n    (\n        \"(Something like http://foo.com/blah_blah)\",\n        {},\n        '(Something like <a href=\"http://foo.com/blah_blah\">http://foo.com/blah_blah</a>)',\n    ),\n    (\n        \"http://foo.com/blah_blah_(wikipedia)\",\n        {},\n        '<a href=\"http://foo.com/blah_blah_(wikipedia)\">http://foo.com/blah_blah_(wikipedia)</a>',\n    ),\n    (\n        \"http://foo.com/blah_(blah)_(wikipedia)_blah\",\n        {},\n        '<a href=\"http://foo.com/blah_(blah)_(wikipedia)_blah\">http://foo.com/blah_(blah)_(wikipedia)_blah</a>',  # noqa: E501\n    ),\n    (\n        \"(Something like http://foo.com/blah_blah_(wikipedia))\",\n        {},\n        '(Something like <a href=\"http://foo.com/blah_blah_(wikipedia)\">http://foo.com/blah_blah_(wikipedia)</a>)',  # noqa: E501\n    ),\n    (\n        \"http://foo.com/blah_blah.\",\n        {},\n        '<a href=\"http://foo.com/blah_blah\">http://foo.com/blah_blah</a>.',\n    ),\n    (\n        \"http://foo.com/blah_blah/.\",\n        {},\n        '<a href=\"http://foo.com/blah_blah/\">http://foo.com/blah_blah/</a>.',\n    ),\n    (\n        \"<http://foo.com/blah_blah>\",\n        {},\n        '&lt;<a href=\"http://foo.com/blah_blah\">http://foo.com/blah_blah</a>&gt;',\n    ),\n    (\n        \"<http://foo.com/blah_blah/>\",\n        {},\n        '&lt;<a href=\"http://foo.com/blah_blah/\">http://foo.com/blah_blah/</a>&gt;',\n    ),\n    (\n        \"http://foo.com/blah_blah,\",\n        {},\n        '<a href=\"http://foo.com/blah_blah\">http://foo.com/blah_blah</a>,',\n    ),\n    (\n        \"http://www.example.com/wpstyle/?p=364.\",\n        {},\n        '<a href=\"http://www.example.com/wpstyle/?p=364\">http://www.example.com/wpstyle/?p=364</a>.',  # noqa: E501\n    ),\n    (\n        \"rdar://1234\",\n        {\"permitted_protocols\": [\"http\", \"rdar\"]},\n        '<a href=\"rdar://1234\">rdar://1234</a>',\n    ),\n    (\n        \"rdar:/1234\",\n        {\"permitted_protocols\": [\"rdar\"]},\n        '<a href=\"rdar:/1234\">rdar:/1234</a>',\n    ),\n    (\n        \"http://userid:password@example.com:8080\",\n        {},\n        '<a href=\"http://userid:password@example.com:8080\">http://userid:password@example.com:8080</a>',  # noqa: E501\n    ),\n    (\n        \"http://userid@example.com\",\n        {},\n        '<a href=\"http://userid@example.com\">http://userid@example.com</a>',\n    ),\n    (\n        \"http://userid@example.com:8080\",\n        {},\n        '<a href=\"http://userid@example.com:8080\">http://userid@example.com:8080</a>',\n    ),\n    (\n        \"http://userid:password@example.com\",\n        {},\n        '<a href=\"http://userid:password@example.com\">http://userid:password@example.com</a>',\n    ),\n    (\n        \"message://%3c330e7f8409726r6a4ba78dkf1fd71420c1bf6ff@mail.gmail.com%3e\",\n        {\"permitted_protocols\": [\"http\", \"message\"]},\n        '<a href=\"message://%3c330e7f8409726r6a4ba78dkf1fd71420c1bf6ff@mail.gmail.com%3e\">'\n        \"message://%3c330e7f8409726r6a4ba78dkf1fd71420c1bf6ff@mail.gmail.com%3e</a>\",\n    ),\n    (\n        \"http://\\u27a1.ws/\\u4a39\",\n        {},\n        '<a href=\"http://\\u27a1.ws/\\u4a39\">http://\\u27a1.ws/\\u4a39</a>',\n    ),\n    (\n        \"<tag>http://example.com</tag>\",\n        {},\n        '&lt;tag&gt;<a href=\"http://example.com\">http://example.com</a>&lt;/tag&gt;',\n    ),\n    (\n        \"Just a www.example.com link.\",\n        {},\n        'Just a <a href=\"http://www.example.com\">www.example.com</a> link.',\n    ),\n    (\n        \"Just a www.example.com link.\",\n        {\"require_protocol\": True},\n        \"Just a www.example.com link.\",\n    ),\n    (\n        \"A http://reallylong.com/link/that/exceedsthelenglimit.html\",\n        {\"require_protocol\": True, \"shorten\": True},\n        'A <a href=\"http://reallylong.com/link/that/exceedsthelenglimit.html\"'\n        ' title=\"http://reallylong.com/link/that/exceedsthelenglimit.html\">http://reallylong.com/link...</a>',  # noqa: E501\n    ),\n    (\n        \"A http://reallylongdomainnamethatwillbetoolong.com/hi!\",\n        {\"shorten\": True},\n        'A <a href=\"http://reallylongdomainnamethatwillbetoolong.com/hi\"'\n        ' title=\"http://reallylongdomainnamethatwillbetoolong.com/hi\">http://reallylongdomainnametha...</a>!',  # noqa: E501\n    ),\n    (\n        \"A file:///passwords.txt and http://web.com link\",\n        {},\n        'A file:///passwords.txt and <a href=\"http://web.com\">http://web.com</a> link',\n    ),\n    (\n        \"A file:///passwords.txt and http://web.com link\",\n        {\"permitted_protocols\": [\"file\"]},\n        'A <a href=\"file:///passwords.txt\">file:///passwords.txt</a> and http://web.com link',\n    ),\n    (\n        \"www.external-link.com\",\n        {\"extra_params\": 'rel=\"nofollow\" class=\"external\"'},\n        '<a href=\"http://www.external-link.com\" rel=\"nofollow\" class=\"external\">www.external-link.com</a>',  # noqa: E501\n    ),\n    (\n        \"www.external-link.com and www.internal-link.com/blogs extra\",\n        {\n            \"extra_params\": lambda href: (\n                'class=\"internal\"'\n                if href.startswith(\"http://www.internal-link.com\")\n                else 'rel=\"nofollow\" class=\"external\"'\n            )\n        },\n        '<a href=\"http://www.external-link.com\" rel=\"nofollow\" class=\"external\">www.external-link.com</a>'  # noqa: E501\n        ' and <a href=\"http://www.internal-link.com/blogs\" class=\"internal\">www.internal-link.com/blogs</a> extra',  # noqa: E501\n    ),\n    (\n        \"www.external-link.com\",\n        {\"extra_params\": lambda href: '    rel=\"nofollow\" class=\"external\"  '},\n        '<a href=\"http://www.external-link.com\" rel=\"nofollow\" class=\"external\">www.external-link.com</a>',  # noqa: E501\n    ),\n]\n\n\nclass EscapeTestCase(unittest.TestCase):\n    def test_linkify(self):\n        for text, kwargs, html in linkify_tests:\n            linked = tornado.escape.linkify(text, **kwargs)\n            self.assertEqual(linked, html)\n\n    def test_xhtml_escape(self):\n        tests: list[tuple[str | bytes, str | bytes]] = [\n            (\"<foo>\", \"&lt;foo&gt;\"),\n            (\"<foo>\", \"&lt;foo&gt;\"),\n            (b\"<foo>\", b\"&lt;foo&gt;\"),\n            (\"<>&\\\"'\", \"&lt;&gt;&amp;&quot;&#x27;\"),\n            (\"&amp;\", \"&amp;amp;\"),\n            (\"<\\u00e9>\", \"&lt;\\u00e9&gt;\"),\n            (b\"<\\xc3\\xa9>\", b\"&lt;\\xc3\\xa9&gt;\"),\n        ]\n        for unescaped, escaped in tests:\n            self.assertEqual(utf8(xhtml_escape(unescaped)), utf8(escaped))\n            self.assertEqual(utf8(unescaped), utf8(xhtml_unescape(escaped)))\n\n    def test_xhtml_unescape_numeric(self):\n        tests = [\n            (\"foo&#32;bar\", \"foo bar\"),\n            (\"foo&#x20;bar\", \"foo bar\"),\n            (\"foo&#X20;bar\", \"foo bar\"),\n            (\"foo&#xabc;bar\", \"foo\\u0abcbar\"),\n            (\"foo&#xyz;bar\", \"foo&#xyz;bar\"),  # invalid encoding\n            (\"foo&#;bar\", \"foo&#;bar\"),  # invalid encoding\n            (\"foo&#x;bar\", \"foo&#x;bar\"),  # invalid encoding\n        ]\n        for escaped, unescaped in tests:\n            self.assertEqual(unescaped, xhtml_unescape(escaped))\n\n    def test_url_escape_unicode(self):\n        tests: list[tuple[str | bytes, str]] = [\n            # byte strings are passed through as-is\n            (\"\\u00e9\".encode(), \"%C3%A9\"),\n            (\"\\u00e9\".encode(\"latin1\"), \"%E9\"),\n            # unicode strings become utf8\n            (\"\\u00e9\", \"%C3%A9\"),\n        ]\n        for unescaped, escaped in tests:\n            self.assertEqual(url_escape(unescaped), escaped)\n\n    def test_url_unescape_unicode(self):\n        tests = [\n            (\"%C3%A9\", \"\\u00e9\", \"utf8\"),\n            (\"%C3%A9\", \"\\u00c3\\u00a9\", \"latin1\"),\n            (\"%C3%A9\", utf8(\"\\u00e9\"), None),\n        ]\n        for escaped, unescaped, encoding in tests:\n            # input strings to url_unescape should only contain ascii\n            # characters, but make sure the function accepts both byte\n            # and unicode strings.\n            self.assertEqual(url_unescape(to_unicode(escaped), encoding), unescaped)\n            self.assertEqual(url_unescape(utf8(escaped), encoding), unescaped)\n\n    def test_url_escape_quote_plus(self):\n        unescaped = \"+ #%\"\n        plus_escaped = \"%2B+%23%25\"\n        escaped = \"%2B%20%23%25\"\n        self.assertEqual(url_escape(unescaped), plus_escaped)\n        self.assertEqual(url_escape(unescaped, plus=False), escaped)\n        self.assertEqual(url_unescape(plus_escaped), unescaped)\n        self.assertEqual(url_unescape(escaped, plus=False), unescaped)\n        self.assertEqual(url_unescape(plus_escaped, encoding=None), utf8(unescaped))\n        self.assertEqual(\n            url_unescape(escaped, encoding=None, plus=False), utf8(unescaped)\n        )\n\n    def test_escape_return_types(self):\n        # On python2 the escape methods should generally return the same\n        # type as their argument\n        self.assertEqual(type(xhtml_escape(\"foo\")), str)\n        self.assertEqual(type(xhtml_escape(\"foo\")), unicode_type)\n\n    def test_json_decode(self):\n        # json_decode accepts both bytes and unicode, but strings it returns\n        # are always unicode.\n        self.assertEqual(json_decode(b'\"foo\"'), \"foo\")\n        self.assertEqual(json_decode('\"foo\"'), \"foo\")\n\n        # Non-ascii bytes are interpreted as utf8\n        self.assertEqual(json_decode(utf8('\"\\u00e9\"')), \"\\u00e9\")\n\n    def test_json_encode(self):\n        # json deals with strings, not bytes.  On python 2 byte strings will\n        # convert automatically if they are utf8; on python 3 byte strings\n        # are not allowed.\n        self.assertEqual(json_decode(json_encode(\"\\u00e9\")), \"\\u00e9\")\n        if bytes is str:\n            self.assertEqual(json_decode(json_encode(utf8(\"\\u00e9\"))), \"\\u00e9\")\n            self.assertRaises(UnicodeDecodeError, json_encode, b\"\\xe9\")\n\n    def test_squeeze(self):\n        self.assertEqual(\n            squeeze(\"sequences     of    whitespace   chars\"),\n            \"sequences of whitespace chars\",\n        )\n\n    def test_recursive_unicode(self):\n        tests = {\n            \"dict\": {b\"foo\": b\"bar\"},\n            \"list\": [b\"foo\", b\"bar\"],\n            \"tuple\": (b\"foo\", b\"bar\"),\n            \"bytes\": b\"foo\",\n        }\n        self.assertEqual(recursive_unicode(tests[\"dict\"]), {\"foo\": \"bar\"})\n        self.assertEqual(recursive_unicode(tests[\"list\"]), [\"foo\", \"bar\"])\n        self.assertEqual(recursive_unicode(tests[\"tuple\"]), (\"foo\", \"bar\"))\n        self.assertEqual(recursive_unicode(tests[\"bytes\"]), \"foo\")\n"
  },
  {
    "path": "tornado/test/gen_test.py",
    "content": "import asyncio\nimport contextvars\nimport datetime\nimport gc\nimport platform\nimport sys\nimport time\nimport unittest\nimport weakref\nfrom concurrent import futures\n\nfrom tornado import gen\nfrom tornado.concurrent import Future\nfrom tornado.log import app_log\nfrom tornado.test.util import skipNotCPython\nfrom tornado.testing import AsyncHTTPTestCase, AsyncTestCase, ExpectLog, gen_test\nfrom tornado.web import Application, HTTPError, RequestHandler\n\n\nclass GenBasicTest(AsyncTestCase):\n    @gen.coroutine\n    def delay(self, iterations, arg):\n        \"\"\"Returns arg after a number of IOLoop iterations.\"\"\"\n        for i in range(iterations):\n            yield gen.moment\n        raise gen.Return(arg)\n\n    @gen.coroutine\n    def async_future(self, result):\n        yield gen.moment\n        return result\n\n    @gen.coroutine\n    def async_exception(self, e):\n        yield gen.moment\n        raise e\n\n    @gen.coroutine\n    def add_one_async(self, x):\n        yield gen.moment\n        raise gen.Return(x + 1)\n\n    def test_no_yield(self):\n        @gen.coroutine\n        def f():\n            pass\n\n        self.io_loop.run_sync(f)\n\n    def test_exception_phase1(self):\n        @gen.coroutine\n        def f():\n            1 / 0\n\n        self.assertRaises(ZeroDivisionError, self.io_loop.run_sync, f)\n\n    def test_exception_phase2(self):\n        @gen.coroutine\n        def f():\n            yield gen.moment\n            1 / 0\n\n        self.assertRaises(ZeroDivisionError, self.io_loop.run_sync, f)\n\n    def test_bogus_yield(self):\n        @gen.coroutine\n        def f():\n            yield 42\n\n        self.assertRaises(gen.BadYieldError, self.io_loop.run_sync, f)\n\n    def test_bogus_yield_tuple(self):\n        @gen.coroutine\n        def f():\n            yield (1, 2)\n\n        self.assertRaises(gen.BadYieldError, self.io_loop.run_sync, f)\n\n    def test_reuse(self):\n        @gen.coroutine\n        def f():\n            yield gen.moment\n\n        self.io_loop.run_sync(f)\n        self.io_loop.run_sync(f)\n\n    def test_none(self):\n        @gen.coroutine\n        def f():\n            yield None\n\n        self.io_loop.run_sync(f)\n\n    def test_multi(self):\n        @gen.coroutine\n        def f():\n            results = yield [self.add_one_async(1), self.add_one_async(2)]\n            self.assertEqual(results, [2, 3])\n\n        self.io_loop.run_sync(f)\n\n    def test_multi_dict(self):\n        @gen.coroutine\n        def f():\n            results = yield dict(foo=self.add_one_async(1), bar=self.add_one_async(2))\n            self.assertEqual(results, dict(foo=2, bar=3))\n\n        self.io_loop.run_sync(f)\n\n    def test_multi_delayed(self):\n        @gen.coroutine\n        def f():\n            # callbacks run at different times\n            responses = yield gen.multi_future(\n                [self.delay(3, \"v1\"), self.delay(1, \"v2\")]\n            )\n            self.assertEqual(responses, [\"v1\", \"v2\"])\n\n        self.io_loop.run_sync(f)\n\n    def test_multi_dict_delayed(self):\n        @gen.coroutine\n        def f():\n            # callbacks run at different times\n            responses = yield gen.multi_future(\n                dict(foo=self.delay(3, \"v1\"), bar=self.delay(1, \"v2\"))\n            )\n            self.assertEqual(responses, dict(foo=\"v1\", bar=\"v2\"))\n\n        self.io_loop.run_sync(f)\n\n    @gen_test\n    def test_multi_performance(self):\n        # Yielding a list used to have quadratic performance; make\n        # sure a large list stays reasonable.  On my laptop a list of\n        # 2000 used to take 1.8s, now it takes 0.12.\n        start = time.time()\n        yield [gen.moment for i in range(2000)]\n        end = time.time()\n        self.assertLess(end - start, 1.0)\n\n    @gen_test\n    def test_multi_empty(self):\n        # Empty lists or dicts should return the same type.\n        x = yield []\n        self.assertTrue(isinstance(x, list))\n        y = yield {}\n        self.assertTrue(isinstance(y, dict))\n\n    @gen_test\n    def test_future(self):\n        result = yield self.async_future(1)\n        self.assertEqual(result, 1)\n\n    @gen_test\n    def test_multi_future(self):\n        results = yield [self.async_future(1), self.async_future(2)]\n        self.assertEqual(results, [1, 2])\n\n    @gen_test\n    def test_multi_future_duplicate(self):\n        # Note that this doesn't work with native corotines, only with\n        # decorated coroutines.\n        f = self.async_future(2)\n        results = yield [self.async_future(1), f, self.async_future(3), f]\n        self.assertEqual(results, [1, 2, 3, 2])\n\n    @gen_test\n    def test_multi_dict_future(self):\n        results = yield dict(foo=self.async_future(1), bar=self.async_future(2))\n        self.assertEqual(results, dict(foo=1, bar=2))\n\n    @gen_test\n    def test_multi_exceptions(self):\n        with ExpectLog(app_log, \"Multiple exceptions in yield list\"):\n            with self.assertRaises(RuntimeError) as cm:\n                yield gen.Multi(\n                    [\n                        self.async_exception(RuntimeError(\"error 1\")),\n                        self.async_exception(RuntimeError(\"error 2\")),\n                    ]\n                )\n        self.assertEqual(str(cm.exception), \"error 1\")\n\n        # With only one exception, no error is logged.\n        with self.assertRaises(RuntimeError):\n            yield gen.Multi(\n                [self.async_exception(RuntimeError(\"error 1\")), self.async_future(2)]\n            )\n\n        # Exception logging may be explicitly quieted.\n        with self.assertRaises(RuntimeError):\n            yield gen.Multi(\n                [\n                    self.async_exception(RuntimeError(\"error 1\")),\n                    self.async_exception(RuntimeError(\"error 2\")),\n                ],\n                quiet_exceptions=RuntimeError,\n            )\n\n    @gen_test\n    def test_multi_future_exceptions(self):\n        with ExpectLog(app_log, \"Multiple exceptions in yield list\"):\n            with self.assertRaises(RuntimeError) as cm:\n                yield [\n                    self.async_exception(RuntimeError(\"error 1\")),\n                    self.async_exception(RuntimeError(\"error 2\")),\n                ]\n        self.assertEqual(str(cm.exception), \"error 1\")\n\n        # With only one exception, no error is logged.\n        with self.assertRaises(RuntimeError):\n            yield [self.async_exception(RuntimeError(\"error 1\")), self.async_future(2)]\n\n        # Exception logging may be explicitly quieted.\n        with self.assertRaises(RuntimeError):\n            yield gen.multi_future(\n                [\n                    self.async_exception(RuntimeError(\"error 1\")),\n                    self.async_exception(RuntimeError(\"error 2\")),\n                ],\n                quiet_exceptions=RuntimeError,\n            )\n\n    def test_sync_raise_return(self):\n        @gen.coroutine\n        def f():\n            raise gen.Return()\n\n        self.io_loop.run_sync(f)\n\n    def test_async_raise_return(self):\n        @gen.coroutine\n        def f():\n            yield gen.moment\n            raise gen.Return()\n\n        self.io_loop.run_sync(f)\n\n    def test_sync_raise_return_value(self):\n        @gen.coroutine\n        def f():\n            raise gen.Return(42)\n\n        self.assertEqual(42, self.io_loop.run_sync(f))\n\n    def test_sync_raise_return_value_tuple(self):\n        @gen.coroutine\n        def f():\n            raise gen.Return((1, 2))\n\n        self.assertEqual((1, 2), self.io_loop.run_sync(f))\n\n    def test_async_raise_return_value(self):\n        @gen.coroutine\n        def f():\n            yield gen.moment\n            raise gen.Return(42)\n\n        self.assertEqual(42, self.io_loop.run_sync(f))\n\n    def test_async_raise_return_value_tuple(self):\n        @gen.coroutine\n        def f():\n            yield gen.moment\n            raise gen.Return((1, 2))\n\n        self.assertEqual((1, 2), self.io_loop.run_sync(f))\n\n\nclass GenCoroutineTest(AsyncTestCase):\n    def setUp(self):\n        # Stray StopIteration exceptions can lead to tests exiting prematurely,\n        # so we need explicit checks here to make sure the tests run all\n        # the way through.\n        self.finished = False\n        super().setUp()\n\n    def tearDown(self):\n        super().tearDown()\n        assert self.finished\n\n    def test_attributes(self):\n        self.finished = True\n\n        def f():\n            yield gen.moment\n\n        coro = gen.coroutine(f)\n        self.assertEqual(coro.__name__, f.__name__)\n        self.assertEqual(coro.__module__, f.__module__)\n        self.assertIs(coro.__wrapped__, f)  # type: ignore\n\n    def test_is_coroutine_function(self):\n        self.finished = True\n\n        def f():\n            yield gen.moment\n\n        coro = gen.coroutine(f)\n        self.assertFalse(gen.is_coroutine_function(f))\n        self.assertTrue(gen.is_coroutine_function(coro))\n        self.assertFalse(gen.is_coroutine_function(coro()))\n\n    @gen_test\n    def test_sync_gen_return(self):\n        @gen.coroutine\n        def f():\n            raise gen.Return(42)\n\n        result = yield f()\n        self.assertEqual(result, 42)\n        self.finished = True\n\n    @gen_test\n    def test_async_gen_return(self):\n        @gen.coroutine\n        def f():\n            yield gen.moment\n            raise gen.Return(42)\n\n        result = yield f()\n        self.assertEqual(result, 42)\n        self.finished = True\n\n    @gen_test\n    def test_sync_return(self):\n        @gen.coroutine\n        def f():\n            return 42\n\n        result = yield f()\n        self.assertEqual(result, 42)\n        self.finished = True\n\n    @gen_test\n    def test_async_return(self):\n        @gen.coroutine\n        def f():\n            yield gen.moment\n            return 42\n\n        result = yield f()\n        self.assertEqual(result, 42)\n        self.finished = True\n\n    @gen_test\n    def test_async_early_return(self):\n        # A yield statement exists but is not executed, which means\n        # this function \"returns\" via an exception.  This exception\n        # doesn't happen before the exception handling is set up.\n        @gen.coroutine\n        def f():\n            if True:\n                return 42\n            yield gen.Task(self.io_loop.add_callback)\n\n        result = yield f()\n        self.assertEqual(result, 42)\n        self.finished = True\n\n    @gen_test\n    def test_async_await(self):\n        @gen.coroutine\n        def f1():\n            yield gen.moment\n            raise gen.Return(42)\n\n        # This test verifies that an async function can await a\n        # yield-based gen.coroutine, and that a gen.coroutine\n        # (the test method itself) can yield an async function.\n        async def f2():\n            result = await f1()\n            return result\n\n        result = yield f2()\n        self.assertEqual(result, 42)\n        self.finished = True\n\n    @gen_test\n    def test_asyncio_sleep_zero(self):\n        # asyncio.sleep(0) turns into a special case (equivalent to\n        # `yield None`)\n        async def f():\n            import asyncio\n\n            await asyncio.sleep(0)\n            return 42\n\n        result = yield f()\n        self.assertEqual(result, 42)\n        self.finished = True\n\n    @gen_test\n    def test_async_await_mixed_multi_native_future(self):\n        @gen.coroutine\n        def f1():\n            yield gen.moment\n\n        async def f2():\n            await f1()\n            return 42\n\n        @gen.coroutine\n        def f3():\n            yield gen.moment\n            raise gen.Return(43)\n\n        results = yield [f2(), f3()]\n        self.assertEqual(results, [42, 43])\n        self.finished = True\n\n    @gen_test\n    def test_async_with_timeout(self):\n        async def f1():\n            return 42\n\n        result = yield gen.with_timeout(datetime.timedelta(hours=1), f1())\n        self.assertEqual(result, 42)\n        self.finished = True\n\n    @gen_test\n    def test_sync_return_no_value(self):\n        @gen.coroutine\n        def f():\n            return\n\n        result = yield f()\n        self.assertIsNone(result)\n        self.finished = True\n\n    @gen_test\n    def test_async_return_no_value(self):\n        @gen.coroutine\n        def f():\n            yield gen.moment\n            return\n\n        result = yield f()\n        self.assertIsNone(result)\n        self.finished = True\n\n    @gen_test\n    def test_sync_raise(self):\n        @gen.coroutine\n        def f():\n            1 / 0\n\n        # The exception is raised when the future is yielded\n        # (or equivalently when its result method is called),\n        # not when the function itself is called).\n        future = f()\n        with self.assertRaises(ZeroDivisionError):\n            yield future\n        self.finished = True\n\n    @gen_test\n    def test_async_raise(self):\n        @gen.coroutine\n        def f():\n            yield gen.moment\n            1 / 0\n\n        future = f()\n        with self.assertRaises(ZeroDivisionError):\n            yield future\n        self.finished = True\n\n    @gen_test\n    def test_replace_yieldpoint_exception(self):\n        # Test exception handling: a coroutine can catch one exception\n        # raised by a yield point and raise a different one.\n        @gen.coroutine\n        def f1():\n            1 / 0\n\n        @gen.coroutine\n        def f2():\n            try:\n                yield f1()\n            except ZeroDivisionError:\n                raise KeyError()\n\n        future = f2()\n        with self.assertRaises(KeyError):\n            yield future\n        self.finished = True\n\n    @gen_test\n    def test_swallow_yieldpoint_exception(self):\n        # Test exception handling: a coroutine can catch an exception\n        # raised by a yield point and not raise a different one.\n        @gen.coroutine\n        def f1():\n            1 / 0\n\n        @gen.coroutine\n        def f2():\n            try:\n                yield f1()\n            except ZeroDivisionError:\n                raise gen.Return(42)\n\n        result = yield f2()\n        self.assertEqual(result, 42)\n        self.finished = True\n\n    @gen_test\n    def test_moment(self):\n        calls = []\n\n        @gen.coroutine\n        def f(name, yieldable):\n            for i in range(5):\n                calls.append(name)\n                yield yieldable\n\n        # First, confirm the behavior without moment: each coroutine\n        # monopolizes the event loop until it finishes.\n        immediate: Future[None] = Future()\n        immediate.set_result(None)\n        yield [f(\"a\", immediate), f(\"b\", immediate)]\n        self.assertEqual(\"\".join(calls), \"aaaaabbbbb\")\n\n        # With moment, they take turns.\n        calls = []\n        yield [f(\"a\", gen.moment), f(\"b\", gen.moment)]\n        self.assertEqual(\"\".join(calls), \"ababababab\")\n        self.finished = True\n\n        calls = []\n        yield [f(\"a\", gen.moment), f(\"b\", immediate)]\n        self.assertEqual(\"\".join(calls), \"abbbbbaaaa\")\n\n    @gen_test\n    def test_sleep(self):\n        yield gen.sleep(0.01)\n        self.finished = True\n\n    @gen_test\n    def test_py3_leak_exception_context(self):\n        class LeakedException(Exception):\n            pass\n\n        @gen.coroutine\n        def inner(iteration):\n            raise LeakedException(iteration)\n\n        try:\n            yield inner(1)\n        except LeakedException as e:\n            self.assertEqual(str(e), \"1\")\n            self.assertIsNone(e.__context__)\n\n        try:\n            yield inner(2)\n        except LeakedException as e:\n            self.assertEqual(str(e), \"2\")\n            self.assertIsNone(e.__context__)\n\n        self.finished = True\n\n    @skipNotCPython\n    def test_coroutine_refcounting(self):\n        # On CPython, tasks and their arguments should be released immediately\n        # without waiting for garbage collection.\n        @gen.coroutine\n        def inner():\n            class Foo:\n                pass\n\n            local_var = Foo()\n            self.local_ref = weakref.ref(local_var)\n\n            def dummy():\n                pass\n\n            yield gen.coroutine(dummy)()\n            raise ValueError(\"Some error\")\n\n        @gen.coroutine\n        def inner2():\n            try:\n                yield inner()\n            except ValueError:\n                pass\n\n        self.io_loop.run_sync(inner2, timeout=3)\n\n        self.assertIsNone(self.local_ref())\n        self.finished = True\n\n    def test_asyncio_future_debug_info(self):\n        self.finished = True\n        # Enable debug mode\n        asyncio_loop = asyncio.get_event_loop()\n        self.addCleanup(asyncio_loop.set_debug, asyncio_loop.get_debug())\n        asyncio_loop.set_debug(True)\n\n        def f():\n            yield gen.moment\n\n        coro = gen.coroutine(f)()\n        self.assertIsInstance(coro, asyncio.Future)\n        # We expect the coroutine repr() to show the place where\n        # it was instantiated\n        expected = \"created at %s:%d\" % (__file__, f.__code__.co_firstlineno + 3)\n        actual = repr(coro)\n        self.assertIn(expected, actual)\n\n    @gen_test\n    def test_asyncio_gather(self):\n        # This demonstrates that tornado coroutines can be understood\n        # by asyncio (This failed prior to Tornado 5.0).\n        @gen.coroutine\n        def f():\n            yield gen.moment\n            raise gen.Return(1)\n\n        ret = yield asyncio.gather(f(), f())\n        self.assertEqual(ret, [1, 1])\n        self.finished = True\n\n\nclass GenCoroutineSequenceHandler(RequestHandler):\n    @gen.coroutine\n    def get(self):\n        yield gen.moment\n        self.write(\"1\")\n        yield gen.moment\n        self.write(\"2\")\n        yield gen.moment\n        self.finish(\"3\")\n\n\nclass GenCoroutineUnfinishedSequenceHandler(RequestHandler):\n    @gen.coroutine\n    def get(self):\n        yield gen.moment\n        self.write(\"1\")\n        yield gen.moment\n        self.write(\"2\")\n        yield gen.moment\n        # just write, don't finish\n        self.write(\"3\")\n\n\n# \"Undecorated\" here refers to the absence of @asynchronous.\nclass UndecoratedCoroutinesHandler(RequestHandler):\n    @gen.coroutine\n    def prepare(self):\n        self.chunks: list[str] = []\n        yield gen.moment\n        self.chunks.append(\"1\")\n\n    @gen.coroutine\n    def get(self):\n        self.chunks.append(\"2\")\n        yield gen.moment\n        self.chunks.append(\"3\")\n        yield gen.moment\n        self.write(\"\".join(self.chunks))\n\n\nclass AsyncPrepareErrorHandler(RequestHandler):\n    @gen.coroutine\n    def prepare(self):\n        yield gen.moment\n        raise HTTPError(403)\n\n    def get(self):\n        self.finish(\"ok\")\n\n\nclass NativeCoroutineHandler(RequestHandler):\n    async def get(self):\n        await asyncio.sleep(0)\n        self.write(\"ok\")\n\n\nclass GenWebTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application(\n            [\n                (\"/coroutine_sequence\", GenCoroutineSequenceHandler),\n                (\n                    \"/coroutine_unfinished_sequence\",\n                    GenCoroutineUnfinishedSequenceHandler,\n                ),\n                (\"/undecorated_coroutine\", UndecoratedCoroutinesHandler),\n                (\"/async_prepare_error\", AsyncPrepareErrorHandler),\n                (\"/native_coroutine\", NativeCoroutineHandler),\n            ]\n        )\n\n    def test_coroutine_sequence_handler(self):\n        response = self.fetch(\"/coroutine_sequence\")\n        self.assertEqual(response.body, b\"123\")\n\n    def test_coroutine_unfinished_sequence_handler(self):\n        response = self.fetch(\"/coroutine_unfinished_sequence\")\n        self.assertEqual(response.body, b\"123\")\n\n    def test_undecorated_coroutines(self):\n        response = self.fetch(\"/undecorated_coroutine\")\n        self.assertEqual(response.body, b\"123\")\n\n    def test_async_prepare_error_handler(self):\n        response = self.fetch(\"/async_prepare_error\")\n        self.assertEqual(response.code, 403)\n\n    def test_native_coroutine_handler(self):\n        response = self.fetch(\"/native_coroutine\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.body, b\"ok\")\n\n\nclass WithTimeoutTest(AsyncTestCase):\n    @gen_test\n    def test_timeout(self):\n        with self.assertRaises(gen.TimeoutError):\n            yield gen.with_timeout(datetime.timedelta(seconds=0.1), Future())\n\n    @gen_test\n    def test_completes_before_timeout(self):\n        future: Future[str] = Future()\n        self.io_loop.add_timeout(\n            datetime.timedelta(seconds=0.1), lambda: future.set_result(\"asdf\")\n        )\n        result = yield gen.with_timeout(datetime.timedelta(seconds=3600), future)\n        self.assertEqual(result, \"asdf\")\n\n    @gen_test\n    def test_fails_before_timeout(self):\n        future: Future[str] = Future()\n        self.io_loop.add_timeout(\n            datetime.timedelta(seconds=0.1),\n            lambda: future.set_exception(ZeroDivisionError()),\n        )\n        with self.assertRaises(ZeroDivisionError):\n            yield gen.with_timeout(datetime.timedelta(seconds=3600), future)\n\n    @gen_test\n    def test_already_resolved(self):\n        future: Future[str] = Future()\n        future.set_result(\"asdf\")\n        result = yield gen.with_timeout(datetime.timedelta(seconds=3600), future)\n        self.assertEqual(result, \"asdf\")\n\n    @gen_test\n    def test_timeout_concurrent_future(self):\n        # A concurrent future that does not resolve before the timeout.\n        with futures.ThreadPoolExecutor(1) as executor:\n            with self.assertRaises(gen.TimeoutError):\n                yield gen.with_timeout(\n                    self.io_loop.time(), executor.submit(time.sleep, 0.1)\n                )\n\n    @gen_test\n    def test_completed_concurrent_future(self):\n        # A concurrent future that is resolved before we even submit it\n        # to with_timeout.\n        with futures.ThreadPoolExecutor(1) as executor:\n\n            def dummy():\n                pass\n\n            f = executor.submit(dummy)\n            f.result()  # wait for completion\n            yield gen.with_timeout(datetime.timedelta(seconds=3600), f)\n\n    @gen_test\n    def test_normal_concurrent_future(self):\n        # A conccurrent future that resolves while waiting for the timeout.\n        with futures.ThreadPoolExecutor(1) as executor:\n            yield gen.with_timeout(\n                datetime.timedelta(seconds=3600),\n                executor.submit(lambda: time.sleep(0.01)),\n            )\n\n\nclass WaitIteratorTest(AsyncTestCase):\n    @gen_test\n    def test_empty_iterator(self):\n        g = gen.WaitIterator()\n        self.assertTrue(g.done(), \"empty generator iterated\")\n\n        with self.assertRaises(ValueError):\n            g = gen.WaitIterator(Future(), bar=Future())\n\n        self.assertIsNone(g.current_index, \"bad nil current index\")\n        self.assertIsNone(g.current_future, \"bad nil current future\")\n\n    @gen_test\n    def test_already_done(self):\n        f1: Future[int] = Future()\n        f2: Future[int] = Future()\n        f3: Future[int] = Future()\n        f1.set_result(24)\n        f2.set_result(42)\n        f3.set_result(84)\n\n        g = gen.WaitIterator(f1, f2, f3)\n        i = 0\n        while not g.done():\n            r = yield g.next()\n            # Order is not guaranteed, but the current implementation\n            # preserves ordering of already-done Futures.\n            if i == 0:\n                self.assertEqual(g.current_index, 0)\n                self.assertIs(g.current_future, f1)\n                self.assertEqual(r, 24)\n            elif i == 1:\n                self.assertEqual(g.current_index, 1)\n                self.assertIs(g.current_future, f2)\n                self.assertEqual(r, 42)\n            elif i == 2:\n                self.assertEqual(g.current_index, 2)\n                self.assertIs(g.current_future, f3)\n                self.assertEqual(r, 84)\n            i += 1\n\n        self.assertIsNone(g.current_index, \"bad nil current index\")\n        self.assertIsNone(g.current_future, \"bad nil current future\")\n\n        dg = gen.WaitIterator(f1=f1, f2=f2)\n\n        while not dg.done():\n            dr = yield dg.next()\n            if dg.current_index == \"f1\":\n                self.assertTrue(\n                    dg.current_future == f1 and dr == 24,\n                    \"WaitIterator dict status incorrect\",\n                )\n            elif dg.current_index == \"f2\":\n                self.assertTrue(\n                    dg.current_future == f2 and dr == 42,\n                    \"WaitIterator dict status incorrect\",\n                )\n            else:\n                self.fail(f\"got bad WaitIterator index {dg.current_index}\")\n\n            i += 1\n        self.assertIsNone(g.current_index, \"bad nil current index\")\n        self.assertIsNone(g.current_future, \"bad nil current future\")\n\n    def finish_coroutines(self, iteration, futures):\n        if iteration == 3:\n            futures[2].set_result(24)\n        elif iteration == 5:\n            futures[0].set_exception(ZeroDivisionError())\n        elif iteration == 8:\n            futures[1].set_result(42)\n            futures[3].set_result(84)\n\n        if iteration < 8:\n            self.io_loop.add_callback(self.finish_coroutines, iteration + 1, futures)\n\n    @gen_test\n    def test_iterator(self):\n        futures: list[Future[int]] = [Future(), Future(), Future(), Future()]\n\n        self.finish_coroutines(0, futures)\n\n        g = gen.WaitIterator(*futures)\n\n        i = 0\n        while not g.done():\n            try:\n                r = yield g.next()\n            except ZeroDivisionError:\n                self.assertIs(g.current_future, futures[0], \"exception future invalid\")\n            else:\n                if i == 0:\n                    self.assertEqual(r, 24, \"iterator value incorrect\")\n                    self.assertEqual(g.current_index, 2, \"wrong index\")\n                elif i == 2:\n                    self.assertEqual(r, 42, \"iterator value incorrect\")\n                    self.assertEqual(g.current_index, 1, \"wrong index\")\n                elif i == 3:\n                    self.assertEqual(r, 84, \"iterator value incorrect\")\n                    self.assertEqual(g.current_index, 3, \"wrong index\")\n            i += 1\n\n    @gen_test\n    def test_iterator_async_await(self):\n        # Recreate the previous test with py35 syntax. It's a little clunky\n        # because of the way the previous test handles an exception on\n        # a single iteration.\n        futures: list[Future[int]] = [Future(), Future(), Future(), Future()]\n        self.finish_coroutines(0, futures)\n        self.finished = False\n\n        async def f():\n            i = 0\n            g = gen.WaitIterator(*futures)\n            try:\n                async for r in g:\n                    if i == 0:\n                        self.assertEqual(r, 24, \"iterator value incorrect\")\n                        self.assertEqual(g.current_index, 2, \"wrong index\")\n                    else:\n                        raise Exception(\"expected exception on iteration 1\")\n                    i += 1\n            except ZeroDivisionError:\n                i += 1\n            async for r in g:\n                if i == 2:\n                    self.assertEqual(r, 42, \"iterator value incorrect\")\n                    self.assertEqual(g.current_index, 1, \"wrong index\")\n                elif i == 3:\n                    self.assertEqual(r, 84, \"iterator value incorrect\")\n                    self.assertEqual(g.current_index, 3, \"wrong index\")\n                else:\n                    raise Exception(\"didn't expect iteration %d\" % i)\n                i += 1\n            self.finished = True\n\n        yield f()\n        self.assertTrue(self.finished)\n\n    @gen_test\n    def test_no_ref(self):\n        # In this usage, there is no direct hard reference to the\n        # WaitIterator itself, only the Future it returns. Since\n        # WaitIterator uses weak references internally to improve GC\n        # performance, this used to cause problems.\n        yield gen.with_timeout(\n            datetime.timedelta(seconds=0.1), gen.WaitIterator(gen.sleep(0)).next()\n        )\n\n\nclass RunnerGCTest(AsyncTestCase):\n    def is_pypy3(self):\n        return platform.python_implementation() == \"PyPy\" and sys.version_info > (3,)\n\n    @gen_test\n    def test_gc(self):\n        # GitHub issue 1769: Runner objects can get GCed unexpectedly\n        # while their future is alive.\n        weakref_scope: list[weakref.ReferenceType | None] = [None]\n\n        def callback():\n            gc.collect(2)\n            weakref_scope[0]().set_result(123)  # type: ignore\n\n        @gen.coroutine\n        def tester():\n            fut: Future[int] = Future()\n            weakref_scope[0] = weakref.ref(fut)\n            self.io_loop.add_callback(callback)\n            yield fut\n\n        yield gen.with_timeout(datetime.timedelta(seconds=0.2), tester())\n\n    def test_gc_infinite_coro(self):\n        # GitHub issue 2229: suspended coroutines should be GCed when\n        # their loop is closed, even if they're involved in a reference\n        # cycle.\n        loop = self.get_new_ioloop()\n        result: list[bool | None] = []\n        wfut = []\n\n        @gen.coroutine\n        def infinite_coro():\n            try:\n                while True:\n                    yield gen.sleep(1e-3)\n                    result.append(True)\n            finally:\n                # coroutine finalizer\n                result.append(None)\n\n        @gen.coroutine\n        def do_something():\n            fut = infinite_coro()\n            fut._refcycle = fut  # type: ignore\n            wfut.append(weakref.ref(fut))\n            yield gen.sleep(0.2)\n\n        loop.run_sync(do_something)\n        loop.close()\n        gc.collect()\n        # Future was collected\n        self.assertIsNone(wfut[0]())\n        # At least one wakeup\n        self.assertGreaterEqual(len(result), 2)\n        if not self.is_pypy3():\n            # coroutine finalizer was called (not on PyPy3 apparently)\n            self.assertIsNone(result[-1])\n\n    def test_gc_infinite_async_await(self):\n        # Same as test_gc_infinite_coro, but with a `async def` function\n        import asyncio\n\n        async def infinite_coro(result):\n            try:\n                while True:\n                    await gen.sleep(1e-3)\n                    result.append(True)\n            finally:\n                # coroutine finalizer\n                result.append(None)\n\n        loop = self.get_new_ioloop()\n        result: list[bool | None] = []\n        wfut = []\n\n        @gen.coroutine\n        def do_something():\n            fut = asyncio.get_event_loop().create_task(infinite_coro(result))\n            fut._refcycle = fut  # type: ignore\n            wfut.append(weakref.ref(fut))\n            yield gen.sleep(0.2)\n\n        loop.run_sync(do_something)\n        with ExpectLog(\"asyncio\", \"Task was destroyed but it is pending\"):\n            loop.close()\n            gc.collect()\n        # Future was collected\n        self.assertIsNone(wfut[0]())\n        # At least one wakeup and one finally\n        self.assertGreaterEqual(len(result), 2)\n        if not self.is_pypy3():\n            # coroutine finalizer was called (not on PyPy3 apparently)\n            self.assertIsNone(result[-1])\n\n    def test_multi_moment(self):\n        # Test gen.multi with moment\n        # now that it's not a real Future\n        @gen.coroutine\n        def wait_a_moment():\n            result = yield gen.multi([gen.moment, gen.moment])\n            raise gen.Return(result)\n\n        loop = self.get_new_ioloop()\n        result = loop.run_sync(wait_a_moment)\n        self.assertEqual(result, [None, None])\n\n\nctx_var: contextvars.ContextVar[int] = contextvars.ContextVar(\"ctx_var\")\n\n\nclass ContextVarsTest(AsyncTestCase):\n    async def native_root(self, x):\n        ctx_var.set(x)\n        await self.inner(x)\n\n    @gen.coroutine\n    def gen_root(self, x):\n        ctx_var.set(x)\n        yield\n        yield self.inner(x)\n\n    async def inner(self, x):\n        self.assertEqual(ctx_var.get(), x)\n        await self.gen_inner(x)\n        self.assertEqual(ctx_var.get(), x)\n\n        # IOLoop.run_in_executor doesn't automatically copy context\n        ctx = contextvars.copy_context()\n        await self.io_loop.run_in_executor(None, lambda: ctx.run(self.thread_inner, x))\n        self.assertEqual(ctx_var.get(), x)\n\n        # Neither does asyncio's run_in_executor.\n        await asyncio.get_event_loop().run_in_executor(\n            None, lambda: ctx.run(self.thread_inner, x)\n        )\n        self.assertEqual(ctx_var.get(), x)\n\n    @gen.coroutine\n    def gen_inner(self, x):\n        self.assertEqual(ctx_var.get(), x)\n        yield\n        self.assertEqual(ctx_var.get(), x)\n\n    def thread_inner(self, x):\n        self.assertEqual(ctx_var.get(), x)\n\n    @gen_test\n    def test_propagate(self):\n        # Verify that context vars get propagated across various\n        # combinations of native and decorated coroutines.\n        yield [\n            self.native_root(1),\n            self.native_root(2),\n            self.gen_root(3),\n            self.gen_root(4),\n        ]\n\n    @gen_test\n    def test_reset(self):\n        token = ctx_var.set(1)\n        yield\n        # reset asserts that we are still at the same level of the context tree,\n        # so we must make sure that we maintain that property across yield.\n        ctx_var.reset(token)\n\n    @gen_test\n    def test_propagate_to_first_yield_with_native_async_function(self):\n        x = 10\n\n        async def native_async_function():\n            self.assertEqual(ctx_var.get(), x)\n\n        ctx_var.set(x)\n        yield native_async_function()\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tornado/test/gettext_translations/extract_me.py",
    "content": "# flake8: noqa\n# Dummy source file to allow creation of the initial .po file in the\n# same way as a real project.  I'm not entirely sure about the real\n# workflow here, but this seems to work.\n#\n# 1) xgettext --language=Python --keyword=_:1,2 --keyword=pgettext:1c,2 --keyword=pgettext:1c,2,3 extract_me.py -o tornado_test.po\n# 2) Edit tornado_test.po, setting CHARSET, Plural-Forms and setting msgstr\n# 3) msgfmt tornado_test.po -o tornado_test.mo\n# 4) Put the file in the proper location: $LANG/LC_MESSAGES\n\n_(\"school\")  # type: ignore[name-defined]\npgettext(\"law\", \"right\")  # type: ignore[name-defined]\npgettext(\"good\", \"right\")  # type: ignore[name-defined]\npgettext(\"organization\", \"club\", \"clubs\", 1)  # type: ignore[name-defined]\npgettext(\"stick\", \"club\", \"clubs\", 1)  # type: ignore[name-defined]\n"
  },
  {
    "path": "tornado/test/gettext_translations/fr_FR/LC_MESSAGES/tornado_test.po",
    "content": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same license as the PACKAGE package.\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2015-01-27 11:05+0300\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"Language: \\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=utf-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n > 1);\\n\"\n\n#: extract_me.py:11\nmsgid \"school\"\nmsgstr \"école\"\n\n#: extract_me.py:12\nmsgctxt \"law\"\nmsgid \"right\"\nmsgstr \"le droit\"\n\n#: extract_me.py:13\nmsgctxt \"good\"\nmsgid \"right\"\nmsgstr \"le bien\"\n\n#: extract_me.py:14\nmsgctxt \"organization\"\nmsgid \"club\"\nmsgid_plural \"clubs\"\nmsgstr[0] \"le club\"\nmsgstr[1] \"les clubs\"\n\n#: extract_me.py:15\nmsgctxt \"stick\"\nmsgid \"club\"\nmsgid_plural \"clubs\"\nmsgstr[0] \"le bâton\"\nmsgstr[1] \"les bâtons\"\n"
  },
  {
    "path": "tornado/test/http1connection_test.py",
    "content": "import socket\n\nfrom tornado.http1connection import HTTP1Connection\nfrom tornado.httputil import HTTPMessageDelegate\nfrom tornado.iostream import IOStream\nfrom tornado.locks import Event\nfrom tornado.netutil import add_accept_handler\nfrom tornado.testing import AsyncTestCase, bind_unused_port, gen_test\n\n\nclass HTTP1ConnectionTest(AsyncTestCase):\n    code: int | None = None\n\n    def setUp(self):\n        super().setUp()\n        self.asyncSetUp()\n\n    @gen_test\n    def asyncSetUp(self):\n        listener, port = bind_unused_port()\n        event = Event()\n\n        def accept_callback(conn, addr):\n            self.server_stream = IOStream(conn)\n            self.addCleanup(self.server_stream.close)\n            event.set()\n\n        add_accept_handler(listener, accept_callback)\n        self.client_stream = IOStream(socket.socket())\n        self.addCleanup(self.client_stream.close)\n        yield [self.client_stream.connect((\"127.0.0.1\", port)), event.wait()]\n        self.io_loop.remove_handler(listener)\n        listener.close()\n\n    @gen_test\n    def test_http10_no_content_length(self):\n        # Regression test for a bug in which can_keep_alive would crash\n        # for an HTTP/1.0 (not 1.1) response with no content-length.\n        conn = HTTP1Connection(self.client_stream, True)\n        self.server_stream.write(b\"HTTP/1.0 200 Not Modified\\r\\n\\r\\nhello\")\n        self.server_stream.close()\n\n        event = Event()\n        test = self\n        body = []\n\n        class Delegate(HTTPMessageDelegate):\n            def headers_received(self, start_line, headers):\n                test.code = start_line.code\n\n            def data_received(self, data):\n                body.append(data)\n\n            def finish(self):\n                event.set()\n\n        yield conn.read_response(Delegate())\n        yield event.wait()\n        self.assertEqual(self.code, 200)\n        self.assertEqual(b\"\".join(body), b\"hello\")\n"
  },
  {
    "path": "tornado/test/httpclient_test.py",
    "content": "import base64\nimport binascii\nimport copy\nimport datetime\nimport gzip\nimport subprocess\nimport sys\nimport threading\nimport time\nimport unicodedata\nimport unittest\nfrom contextlib import closing\nfrom io import BytesIO\n\nfrom tornado import gen, netutil\nfrom tornado.escape import native_str, to_unicode, utf8\nfrom tornado.httpclient import (\n    HTTPClient,\n    HTTPError,\n    HTTPRequest,\n    HTTPResponse,\n    _RequestProxy,\n)\nfrom tornado.httpserver import HTTPServer\nfrom tornado.httputil import HTTPHeaders, format_timestamp\nfrom tornado.ioloop import IOLoop\nfrom tornado.iostream import IOStream\nfrom tornado.log import app_log, gen_log\nfrom tornado.test.util import ignore_deprecation\nfrom tornado.testing import AsyncHTTPTestCase, ExpectLog, bind_unused_port, gen_test\nfrom tornado.web import Application, RequestHandler, url\n\n\nclass HelloWorldHandler(RequestHandler):\n    def get(self):\n        name = self.get_argument(\"name\", \"world\")\n        self.set_header(\"Content-Type\", \"text/plain\")\n        self.finish(\"Hello %s!\" % name)\n\n\nclass PostHandler(RequestHandler):\n    def post(self):\n        self.finish(\n            \"Post arg1: %s, arg2: %s\"\n            % (self.get_argument(\"arg1\"), self.get_argument(\"arg2\"))\n        )\n\n\nclass PutHandler(RequestHandler):\n    def put(self):\n        self.write(\"Put body: \")\n        self.write(self.request.body)\n\n\nclass RedirectHandler(RequestHandler):\n    def prepare(self):\n        self.write(\"redirects can have bodies too\")\n        self.redirect(\n            self.get_argument(\"url\"), status=int(self.get_argument(\"status\", \"302\"))\n        )\n\n\nclass RedirectWithoutLocationHandler(RequestHandler):\n    def prepare(self):\n        # For testing error handling of a redirect with no location header.\n        self.set_status(301)\n        self.finish()\n\n\nclass ChunkHandler(RequestHandler):\n    @gen.coroutine\n    def get(self):\n        self.write(\"asdf\")\n        self.flush()\n        # Wait a bit to ensure the chunks are sent and received separately.\n        yield gen.sleep(0.01)\n        self.write(\"qwer\")\n\n\nclass AuthHandler(RequestHandler):\n    def get(self):\n        self.finish(self.request.headers[\"Authorization\"])\n\n\nclass CountdownHandler(RequestHandler):\n    def get(self, count):\n        count = int(count)\n        if count > 0:\n            self.redirect(self.reverse_url(\"countdown\", count - 1))\n        else:\n            self.write(\"Zero\")\n\n\nclass EchoPostHandler(RequestHandler):\n    def post(self):\n        self.write(self.request.body)\n\n\nclass UserAgentHandler(RequestHandler):\n    def get(self):\n        self.write(self.request.headers.get(\"User-Agent\", \"User agent not set\"))\n\n\nclass ContentLength304Handler(RequestHandler):\n    def get(self):\n        self.set_status(304)\n        self.set_header(\"Content-Length\", 42)\n\n    def _clear_representation_headers(self):\n        # Tornado strips content-length from 304 responses, but here we\n        # want to simulate servers that include the headers anyway.\n        pass\n\n\nclass PatchHandler(RequestHandler):\n    def patch(self):\n        \"Return the request payload - so we can check it is being kept\"\n        self.write(self.request.body)\n\n\nclass AllMethodsHandler(RequestHandler):\n    SUPPORTED_METHODS = RequestHandler.SUPPORTED_METHODS + (\"OTHER\",)  # type: ignore\n\n    def method(self):\n        assert self.request.method is not None\n        self.write(self.request.method)\n\n    get = head = post = put = delete = options = patch = other = method  # type: ignore\n\n\nclass SetHeaderHandler(RequestHandler):\n    def get(self):\n        # Use get_arguments for keys to get strings, but\n        # request.arguments for values to get bytes.\n        for k, v in zip(self.get_arguments(\"k\"), self.request.arguments[\"v\"]):\n            self.set_header(k, v)\n\n\nclass InvalidGzipHandler(RequestHandler):\n    def get(self) -> None:\n        # set Content-Encoding manually to avoid automatic gzip encoding\n        self.set_header(\"Content-Type\", \"text/plain\")\n        self.set_header(\"Content-Encoding\", \"gzip\")\n        # Triggering the potential bug seems to depend on input length.\n        # This length is taken from the bad-response example reported in\n        # https://github.com/tornadoweb/tornado/pull/2875 (uncompressed).\n        text = \"\".join(f\"Hello World {i}\\n\" for i in range(9000))[:149051]\n        body = gzip.compress(text.encode(), compresslevel=6) + b\"\\00\"\n        self.write(body)\n\n\nclass HeaderEncodingHandler(RequestHandler):\n    def get(self):\n        self.finish(self.request.headers[\"Foo\"].encode(\"ISO8859-1\"))\n\n\n# These tests end up getting run redundantly: once here with the default\n# HTTPClient implementation, and then again in each implementation's own\n# test suite.\n\n\nclass HTTPClientCommonTestCase(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application(\n            [\n                url(\"/hello\", HelloWorldHandler),\n                url(\"/post\", PostHandler),\n                url(\"/put\", PutHandler),\n                url(\"/redirect\", RedirectHandler),\n                url(\"/redirect_without_location\", RedirectWithoutLocationHandler),\n                url(\"/chunk\", ChunkHandler),\n                url(\"/auth\", AuthHandler),\n                url(\"/countdown/([0-9]+)\", CountdownHandler, name=\"countdown\"),\n                url(\"/echopost\", EchoPostHandler),\n                url(\"/user_agent\", UserAgentHandler),\n                url(\"/304_with_content_length\", ContentLength304Handler),\n                url(\"/all_methods\", AllMethodsHandler),\n                url(\"/patch\", PatchHandler),\n                url(\"/set_header\", SetHeaderHandler),\n                url(\"/invalid_gzip\", InvalidGzipHandler),\n                url(\"/header-encoding\", HeaderEncodingHandler),\n            ],\n            gzip=True,\n        )\n\n    def test_patch_receives_payload(self):\n        body = b\"some patch data\"\n        response = self.fetch(\"/patch\", method=\"PATCH\", body=body)\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.body, body)\n\n    def test_hello_world(self):\n        response = self.fetch(\"/hello\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.headers[\"Content-Type\"], \"text/plain\")\n        self.assertEqual(response.body, b\"Hello world!\")\n        assert response.request_time is not None\n        self.assertEqual(int(response.request_time), 0)\n\n        response = self.fetch(\"/hello?name=Ben\")\n        self.assertEqual(response.body, b\"Hello Ben!\")\n\n    def test_streaming_callback(self):\n        # streaming_callback is also tested in test_chunked\n        chunks: list[bytes] = []\n        response = self.fetch(\"/hello\", streaming_callback=chunks.append)\n        # with streaming_callback, data goes to the callback and not response.body\n        self.assertEqual(chunks, [b\"Hello world!\"])\n        self.assertFalse(response.body)\n\n    def test_post(self):\n        response = self.fetch(\"/post\", method=\"POST\", body=\"arg1=foo&arg2=bar\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.body, b\"Post arg1: foo, arg2: bar\")\n\n    def test_chunked(self):\n        response = self.fetch(\"/chunk\")\n        self.assertEqual(response.body, b\"asdfqwer\")\n\n        chunks: list[bytes] = []\n        response = self.fetch(\"/chunk\", streaming_callback=chunks.append)\n        self.assertEqual(chunks, [b\"asdf\", b\"qwer\"])\n        self.assertFalse(response.body)\n\n    def test_chunked_close(self):\n        # test case in which chunks spread read-callback processing\n        # over several ioloop iterations, but the connection is already closed.\n        sock, port = bind_unused_port()\n        with closing(sock):\n\n            @gen.coroutine\n            def accept_callback(conn, address):\n                # fake an HTTP server using chunked encoding where the final chunks\n                # and connection close all happen at once\n                stream = IOStream(conn)\n                request_data = yield stream.read_until(b\"\\r\\n\\r\\n\")\n                if b\"HTTP/1.\" not in request_data:\n                    self.skipTest(\"requires HTTP/1.x\")\n                yield stream.write(b\"\"\"\\\nHTTP/1.1 200 OK\nTransfer-Encoding: chunked\n\n1\n1\n1\n2\n0\n\n\"\"\".replace(b\"\\n\", b\"\\r\\n\"))\n                stream.close()\n\n            netutil.add_accept_handler(sock, accept_callback)  # type: ignore\n            resp = self.fetch(\"http://127.0.0.1:%d/\" % port)\n            resp.rethrow()\n            self.assertEqual(resp.body, b\"12\")\n            self.io_loop.remove_handler(sock.fileno())\n\n    def test_basic_auth(self):\n        # This test data appears in section 2 of RFC 7617.\n        self.assertEqual(\n            self.fetch(\n                \"/auth\", auth_username=\"Aladdin\", auth_password=\"open sesame\"\n            ).body,\n            b\"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\",\n        )\n\n    def test_basic_auth_explicit_mode(self):\n        self.assertEqual(\n            self.fetch(\n                \"/auth\",\n                auth_username=\"Aladdin\",\n                auth_password=\"open sesame\",\n                auth_mode=\"basic\",\n            ).body,\n            b\"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\",\n        )\n\n    def test_basic_auth_unicode(self):\n        # This test data appears in section 2.1 of RFC 7617.\n        self.assertEqual(\n            self.fetch(\"/auth\", auth_username=\"test\", auth_password=\"123£\").body,\n            b\"Basic dGVzdDoxMjPCow==\",\n        )\n\n        # The standard mandates NFC. Give it a decomposed username\n        # and ensure it is normalized to composed form.\n        username = unicodedata.normalize(\"NFD\", \"josé\")\n        self.assertEqual(\n            self.fetch(\"/auth\", auth_username=username, auth_password=\"səcrət\").body,\n            b\"Basic am9zw6k6c8mZY3LJmXQ=\",\n        )\n\n    def test_unsupported_auth_mode(self):\n        # curl and simple clients handle errors a bit differently; the\n        # important thing is that they don't fall back to basic auth\n        # on an unknown mode.\n        with ExpectLog(gen_log, \"uncaught exception\", required=False):\n            with self.assertRaises((ValueError, HTTPError)):  # type: ignore\n                self.fetch(\n                    \"/auth\",\n                    auth_username=\"Aladdin\",\n                    auth_password=\"open sesame\",\n                    auth_mode=\"asdf\",\n                    raise_error=True,\n                )\n\n    def test_follow_redirect(self):\n        response = self.fetch(\"/countdown/2\", follow_redirects=False)\n        self.assertEqual(302, response.code)\n        self.assertTrue(response.headers[\"Location\"].endswith(\"/countdown/1\"))\n\n        response = self.fetch(\"/countdown/2\")\n        self.assertEqual(200, response.code)\n        self.assertTrue(response.effective_url.endswith(\"/countdown/0\"))\n        self.assertEqual(b\"Zero\", response.body)\n\n    def test_redirect_without_location(self):\n        response = self.fetch(\"/redirect_without_location\", follow_redirects=True)\n        # If there is no location header, the redirect response should\n        # just be returned as-is. (This should arguably raise an\n        # error, but libcurl doesn't treat this as an error, so we\n        # don't either).\n        self.assertEqual(301, response.code)\n\n    def test_redirect_put_with_body(self):\n        response = self.fetch(\n            \"/redirect?url=/put&status=307\", method=\"PUT\", body=\"hello\"\n        )\n        self.assertEqual(response.body, b\"Put body: hello\")\n\n    def test_redirect_put_without_body(self):\n        # This \"without body\" edge case is similar to what happens with body_producer.\n        response = self.fetch(\n            \"/redirect?url=/put&status=307\",\n            method=\"PUT\",\n            allow_nonstandard_methods=True,\n        )\n        self.assertEqual(response.body, b\"Put body: \")\n\n    def test_method_after_redirect(self):\n        # Legacy redirect codes (301, 302) convert POST requests to GET.\n        for status in [301, 302, 303]:\n            url = \"/redirect?url=/all_methods&status=%d\" % status\n            resp = self.fetch(url, method=\"POST\", body=b\"\")\n            self.assertEqual(b\"GET\", resp.body)\n\n            # Other methods are left alone, except for 303 redirect, depending on client\n            for method in [\"GET\", \"OPTIONS\", \"PUT\", \"DELETE\"]:\n                resp = self.fetch(url, method=method, allow_nonstandard_methods=True)\n                if status in [301, 302]:\n                    self.assertEqual(utf8(method), resp.body)\n                else:\n                    self.assertIn(resp.body, [utf8(method), b\"GET\"])\n\n            # HEAD is different so check it separately.\n            resp = self.fetch(url, method=\"HEAD\")\n            self.assertEqual(200, resp.code)\n            self.assertEqual(b\"\", resp.body)\n\n        # Newer redirects always preserve the original method.\n        for status in [307, 308]:\n            url = \"/redirect?url=/all_methods&status=307\"\n            for method in [\"GET\", \"OPTIONS\", \"POST\", \"PUT\", \"DELETE\"]:\n                resp = self.fetch(url, method=method, allow_nonstandard_methods=True)\n                self.assertEqual(method, to_unicode(resp.body))\n            resp = self.fetch(url, method=\"HEAD\")\n            self.assertEqual(200, resp.code)\n            self.assertEqual(b\"\", resp.body)\n\n    def test_credentials_in_url(self):\n        url = self.get_url(\"/auth\").replace(\"http://\", \"http://me:secret@\")\n        response = self.fetch(url)\n        self.assertEqual(b\"Basic \" + base64.b64encode(b\"me:secret\"), response.body)\n\n    def test_body_encoding(self):\n        unicode_body = \"\\xe9\"\n        byte_body = binascii.a2b_hex(b\"e9\")\n\n        # unicode string in body gets converted to utf8\n        response = self.fetch(\n            \"/echopost\",\n            method=\"POST\",\n            body=unicode_body,\n            headers={\"Content-Type\": \"application/blah\"},\n        )\n        self.assertEqual(response.headers[\"Content-Length\"], \"2\")\n        self.assertEqual(response.body, utf8(unicode_body))\n\n        # byte strings pass through directly\n        response = self.fetch(\n            \"/echopost\",\n            method=\"POST\",\n            body=byte_body,\n            headers={\"Content-Type\": \"application/blah\"},\n        )\n        self.assertEqual(response.headers[\"Content-Length\"], \"1\")\n        self.assertEqual(response.body, byte_body)\n\n        # Mixing unicode in headers and byte string bodies shouldn't\n        # break anything\n        response = self.fetch(\n            \"/echopost\",\n            method=\"POST\",\n            body=byte_body,\n            headers={\"Content-Type\": \"application/blah\"},\n            user_agent=\"foo\",\n        )\n        self.assertEqual(response.headers[\"Content-Length\"], \"1\")\n        self.assertEqual(response.body, byte_body)\n\n    def test_types(self):\n        response = self.fetch(\"/hello\")\n        self.assertEqual(type(response.body), bytes)\n        self.assertEqual(type(response.headers[\"Content-Type\"]), str)\n        self.assertEqual(type(response.code), int)\n        self.assertEqual(type(response.effective_url), str)\n\n    def test_gzip(self):\n        # All the tests in this file should be using gzip, but this test\n        # ensures that it is in fact getting compressed, and also tests\n        # the httpclient's decompress=False option.\n        # Setting Accept-Encoding manually bypasses the client's\n        # decompression so we can see the raw data.\n        response = self.fetch(\n            \"/chunk\", decompress_response=False, headers={\"Accept-Encoding\": \"gzip\"}\n        )\n        self.assertEqual(response.headers[\"Content-Encoding\"], \"gzip\")\n        self.assertNotEqual(response.body, b\"asdfqwer\")\n        # Our test data gets bigger when gzipped.  Oops.  :)\n        # Chunked encoding bypasses the MIN_LENGTH check.\n        self.assertEqual(len(response.body), 34)\n        f = gzip.GzipFile(mode=\"r\", fileobj=response.buffer)\n        self.assertEqual(f.read(), b\"asdfqwer\")\n\n    def test_invalid_gzip(self):\n        # test if client hangs on tricky invalid gzip\n        # curl/simple httpclient have different behavior (exception, logging)\n        with ExpectLog(\n            gen_log, \".*Malformed HTTP message.*unconsumed gzip data\", required=False\n        ):\n            try:\n                response = self.fetch(\"/invalid_gzip\")\n                self.assertEqual(response.code, 200)\n                self.assertEqual(response.body[:14], b\"Hello World 0\\n\")\n            except HTTPError:\n                pass  # acceptable\n\n    def test_header_callback(self):\n        first_line = []\n        headers = {}\n        chunks = []\n\n        def header_callback(header_line):\n            if header_line.startswith(\"HTTP/1.1 101\"):\n                # Upgrading to HTTP/2\n                pass\n            elif header_line.startswith(\"HTTP/\"):\n                first_line.append(header_line)\n            elif header_line != \"\\r\\n\":\n                k, v = header_line.split(\":\", 1)\n                headers[k.lower()] = v.strip()\n\n        def streaming_callback(chunk):\n            # All header callbacks are run before any streaming callbacks,\n            # so the header data is available to process the data as it\n            # comes in.\n            self.assertEqual(headers[\"content-type\"], \"text/html; charset=UTF-8\")\n            chunks.append(chunk)\n\n        self.fetch(\n            \"/chunk\",\n            header_callback=header_callback,\n            streaming_callback=streaming_callback,\n        )\n        self.assertEqual(len(first_line), 1, first_line)\n        self.assertRegex(first_line[0], \"HTTP/[0-9]\\\\.[0-9] 200.*\\r\\n\")\n        self.assertEqual(chunks, [b\"asdf\", b\"qwer\"])\n\n    def test_header_callback_to_parse_line(self):\n        # Make a request with header_callback and feed the headers to HTTPHeaders.parse_line.\n        # (Instead of HTTPHeaders.parse which is used in normal cases). Ensure that the resulting\n        # headers are as expected, and in particular do not have trailing whitespace added\n        # due to the final CRLF line.\n        headers = HTTPHeaders()\n\n        def header_callback(line):\n            if line.startswith(\"HTTP/\"):\n                # Ignore the first status line\n                return\n            headers.parse_line(line)\n\n        self.fetch(\"/hello\", header_callback=header_callback)\n        for k, v in headers.get_all():\n            self.assertTrue(v == v.strip(), (k, v))\n\n    @gen_test\n    def test_configure_defaults(self):\n        defaults = dict(user_agent=\"TestDefaultUserAgent\", allow_ipv6=False)\n        # Construct a new instance of the configured client class\n        client = self.http_client.__class__(force_instance=True, defaults=defaults)\n        try:\n            response = yield client.fetch(self.get_url(\"/user_agent\"))\n            self.assertEqual(response.body, b\"TestDefaultUserAgent\")\n        finally:\n            client.close()\n\n    def test_header_types(self):\n        # Header values may be passed as character or utf8 byte strings,\n        # in a plain dictionary or an HTTPHeaders object.\n        # Keys must always be the native str type.\n        # All combinations should have the same results on the wire.\n        for value in [\"MyUserAgent\", b\"MyUserAgent\"]:\n            for container in [dict, HTTPHeaders]:\n                headers = container()\n                headers[\"User-Agent\"] = value\n                resp = self.fetch(\"/user_agent\", headers=headers)\n                self.assertEqual(\n                    resp.body,\n                    b\"MyUserAgent\",\n                    \"response=%r, value=%r, container=%r\"\n                    % (resp.body, value, container),\n                )\n\n    def test_multi_line_headers(self):\n        # Multi-line http headers are rare but rfc-allowed\n        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2\n        sock, port = bind_unused_port()\n        with closing(sock):\n\n            @gen.coroutine\n            def accept_callback(conn, address):\n                stream = IOStream(conn)\n                request_data = yield stream.read_until(b\"\\r\\n\\r\\n\")\n                if b\"HTTP/1.\" not in request_data:\n                    self.skipTest(\"requires HTTP/1.x\")\n                yield stream.write(b\"\"\"\\\nHTTP/1.1 200 OK\nX-XSS-Protection: 1;\n\\tmode=block\n\n\"\"\".replace(b\"\\n\", b\"\\r\\n\"))\n                stream.close()\n\n            netutil.add_accept_handler(sock, accept_callback)  # type: ignore\n            try:\n                resp = self.fetch(\"http://127.0.0.1:%d/\" % port)\n                resp.rethrow()\n                self.assertEqual(resp.headers[\"X-XSS-Protection\"], \"1; mode=block\")\n            finally:\n                self.io_loop.remove_handler(sock.fileno())\n\n    @gen_test\n    def test_header_encoding(self):\n        response = yield self.http_client.fetch(\n            self.get_url(\"/header-encoding\"),\n            headers={\n                \"Foo\": \"b\\xe4r\",\n            },\n        )\n        self.assertEqual(response.body, \"b\\xe4r\".encode(\"ISO8859-1\"))\n\n    def test_304_with_content_length(self):\n        # According to the spec 304 responses SHOULD NOT include\n        # Content-Length or other entity headers, but some servers do it\n        # anyway.\n        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5\n        response = self.fetch(\"/304_with_content_length\")\n        self.assertEqual(response.code, 304)\n        self.assertEqual(response.headers[\"Content-Length\"], \"42\")\n\n    @gen_test\n    def test_future_interface(self):\n        response = yield self.http_client.fetch(self.get_url(\"/hello\"))\n        self.assertEqual(response.body, b\"Hello world!\")\n\n    @gen_test\n    def test_future_http_error(self):\n        with self.assertRaises(HTTPError) as context:\n            yield self.http_client.fetch(self.get_url(\"/notfound\"))\n        assert context.exception is not None\n        assert context.exception.response is not None\n        self.assertEqual(context.exception.code, 404)\n        self.assertEqual(context.exception.response.code, 404)\n\n    @gen_test\n    def test_future_http_error_no_raise(self):\n        response = yield self.http_client.fetch(\n            self.get_url(\"/notfound\"), raise_error=False\n        )\n        self.assertEqual(response.code, 404)\n\n    @gen_test\n    def test_reuse_request_from_response(self):\n        # The response.request attribute should be an HTTPRequest, not\n        # a _RequestProxy.\n        # This test uses self.http_client.fetch because self.fetch calls\n        # self.get_url on the input unconditionally.\n        url = self.get_url(\"/hello\")\n        response = yield self.http_client.fetch(url)\n        self.assertEqual(response.request.url, url)\n        self.assertTrue(isinstance(response.request, HTTPRequest))\n        response2 = yield self.http_client.fetch(response.request)\n        self.assertEqual(response2.body, b\"Hello world!\")\n\n    @gen_test\n    def test_bind_source_ip(self):\n        url = self.get_url(\"/hello\")\n        request = HTTPRequest(url, network_interface=\"127.0.0.1\")\n        response = yield self.http_client.fetch(request)\n        self.assertEqual(response.code, 200)\n\n        with self.assertRaises((ValueError, HTTPError)) as context:  # type: ignore\n            request = HTTPRequest(url, network_interface=\"not-interface-or-ip\")\n            yield self.http_client.fetch(request)\n        self.assertTrue(\n            \"Failed binding local connection end\" in str(context.exception)\n            or \"not-interface-or-ip\" in str(context.exception)\n        )\n\n    def test_all_methods(self):\n        for method in [\"GET\", \"DELETE\", \"OPTIONS\"]:\n            response = self.fetch(\"/all_methods\", method=method)\n            self.assertEqual(response.body, utf8(method))\n        for method in [\"POST\", \"PUT\", \"PATCH\"]:\n            response = self.fetch(\"/all_methods\", method=method, body=b\"\")\n            self.assertEqual(response.body, utf8(method))\n        response = self.fetch(\"/all_methods\", method=\"HEAD\")\n        self.assertEqual(response.body, b\"\")\n        response = self.fetch(\n            \"/all_methods\", method=\"OTHER\", allow_nonstandard_methods=True\n        )\n        self.assertEqual(response.body, b\"OTHER\")\n\n    def test_body_sanity_checks(self):\n        # These methods require a body.\n        for method in (\"POST\", \"PUT\", \"PATCH\"):\n            with self.assertRaises(ValueError) as context:\n                self.fetch(\"/all_methods\", method=method, raise_error=True)\n            self.assertIn(\"must not be None\", str(context.exception))\n\n            resp = self.fetch(\n                \"/all_methods\", method=method, allow_nonstandard_methods=True\n            )\n            self.assertEqual(resp.code, 200)\n\n        # These methods don't allow a body.\n        for method in (\"GET\", \"DELETE\", \"OPTIONS\"):\n            with self.assertRaises(ValueError) as context:\n                self.fetch(\n                    \"/all_methods\", method=method, body=b\"asdf\", raise_error=True\n                )\n            self.assertIn(\"must be None\", str(context.exception))\n\n            # In most cases this can be overridden, but curl_httpclient\n            # does not allow body with a GET at all.\n            if method != \"GET\":\n                self.fetch(\n                    \"/all_methods\",\n                    method=method,\n                    body=b\"asdf\",\n                    allow_nonstandard_methods=True,\n                    raise_error=True,\n                )\n                self.assertEqual(resp.code, 200)\n\n    # This test causes odd failures with the combination of\n    # curl_httpclient (at least with the version of libcurl available\n    # on ubuntu 12.04), TwistedIOLoop, and epoll.  For POST (but not PUT),\n    # curl decides the response came back too soon and closes the connection\n    # to start again.  It does this *before* telling the socket callback to\n    # unregister the FD.  Some IOLoop implementations have special kernel\n    # integration to discover this immediately.  Tornado's IOLoops\n    # ignore errors on remove_handler to accommodate this behavior, but\n    # Twisted's reactor does not.  The removeReader call fails and so\n    # do all future removeAll calls (which our tests do at cleanup).\n    #\n    # def test_post_307(self):\n    #    response = self.fetch(\"/redirect?status=307&url=/post\",\n    #                          method=\"POST\", body=b\"arg1=foo&arg2=bar\")\n    #    self.assertEqual(response.body, b\"Post arg1: foo, arg2: bar\")\n\n    def test_put_307(self):\n        response = self.fetch(\n            \"/redirect?status=307&url=/put\", method=\"PUT\", body=b\"hello\"\n        )\n        response.rethrow()\n        self.assertEqual(response.body, b\"Put body: hello\")\n\n    def test_non_ascii_header(self):\n        # Non-ascii headers are sent as latin1.\n        response = self.fetch(\"/set_header?k=foo&v=%E9\")\n        response.rethrow()\n        self.assertEqual(response.headers[\"Foo\"], native_str(\"\\u00e9\"))\n\n    def test_response_times(self):\n        # A few simple sanity checks of the response time fields to\n        # make sure they're using the right basis (between the\n        # wall-time and monotonic clocks).\n        start_time = time.time()\n        response = self.fetch(\"/hello\")\n        response.rethrow()\n        self.assertIsNotNone(response.request_time)\n        assert response.request_time is not None  # for mypy\n        self.assertGreaterEqual(response.request_time, 0)\n        self.assertLess(response.request_time, 1.0)\n        # A very crude check to make sure that start_time is based on\n        # wall time and not the monotonic clock.\n        self.assertIsNotNone(response.start_time)\n        assert response.start_time is not None  # for mypy\n        self.assertLess(abs(response.start_time - start_time), 1.0)\n\n        for k, v in response.time_info.items():\n            self.assertTrue(0 <= v < 1.0, f\"time_info[{k}] out of bounds: {v}\")\n\n    def test_zero_timeout(self):\n        response = self.fetch(\"/hello\", connect_timeout=0)\n        self.assertEqual(response.code, 200)\n\n        response = self.fetch(\"/hello\", request_timeout=0)\n        self.assertEqual(response.code, 200)\n\n        response = self.fetch(\"/hello\", connect_timeout=0, request_timeout=0)\n        self.assertEqual(response.code, 200)\n\n    @gen_test\n    def test_error_after_cancel(self):\n        fut = self.http_client.fetch(self.get_url(\"/404\"))\n        self.assertTrue(fut.cancel())\n        with ExpectLog(app_log, \"Exception after Future was cancelled\") as el:\n            # We can't wait on the cancelled Future any more, so just\n            # let the IOLoop run until the exception gets logged (or\n            # not, in which case we exit the loop and ExpectLog will\n            # raise).\n            for i in range(100):\n                yield gen.sleep(0.01)\n                if el.logged_stack:\n                    break\n\n    def test_header_crlf(self):\n        # Ensure that the client doesn't allow CRLF injection in headers. RFC 9112 section 2.2\n        # prohibits a bare CR specifically and \"a recipient MAY recognize a single LF as a line\n        # terminator\" so we check each character separately as well as the (redundant) CRLF pair.\n        for header, name in [\n            (\"foo\\rbar:\", \"cr\"),\n            (\"foo\\nbar:\", \"lf\"),\n            (\"foo\\r\\nbar:\", \"crlf\"),\n        ]:\n            with self.subTest(name=name, position=\"value\"):\n                with self.assertRaises(ValueError):\n                    self.fetch(\"/hello\", headers={\"foo\": header})\n            with self.subTest(name=name, position=\"key\"):\n                with self.assertRaises(ValueError):\n                    self.fetch(\"/hello\", headers={header: \"foo\"})\n\n\nclass RequestProxyTest(unittest.TestCase):\n    def test_request_set(self):\n        proxy = _RequestProxy(\n            HTTPRequest(\"http://example.com/\", user_agent=\"foo\"), dict()\n        )\n        self.assertEqual(proxy.user_agent, \"foo\")\n\n    def test_default_set(self):\n        proxy = _RequestProxy(\n            HTTPRequest(\"http://example.com/\"), dict(network_interface=\"foo\")\n        )\n        self.assertEqual(proxy.network_interface, \"foo\")\n\n    def test_both_set(self):\n        proxy = _RequestProxy(\n            HTTPRequest(\"http://example.com/\", proxy_host=\"foo\"), dict(proxy_host=\"bar\")\n        )\n        self.assertEqual(proxy.proxy_host, \"foo\")\n\n    def test_neither_set(self):\n        proxy = _RequestProxy(HTTPRequest(\"http://example.com/\"), dict())\n        self.assertIsNone(proxy.auth_username)\n\n    def test_bad_attribute(self):\n        proxy = _RequestProxy(HTTPRequest(\"http://example.com/\"), dict())\n        with self.assertRaises(AttributeError):\n            proxy.foo\n\n    def test_defaults_none(self):\n        proxy = _RequestProxy(HTTPRequest(\"http://example.com/\"), None)\n        self.assertIsNone(proxy.auth_username)\n\n\nclass HTTPResponseTestCase(unittest.TestCase):\n    def test_str(self):\n        response = HTTPResponse(  # type: ignore\n            HTTPRequest(\"http://example.com\"), 200, buffer=BytesIO()\n        )\n        s = str(response)\n        self.assertTrue(s.startswith(\"HTTPResponse(\"))\n        self.assertIn(\"code=200\", s)\n\n\nclass SyncHTTPClientTest(unittest.TestCase):\n    def setUp(self):\n        self.server_ioloop = IOLoop(make_current=False)\n        event = threading.Event()\n\n        @gen.coroutine\n        def init_server():\n            sock, self.port = bind_unused_port()\n            app = Application([(\"/\", HelloWorldHandler)])\n            self.server = HTTPServer(app)\n            self.server.add_socket(sock)\n            event.set()\n\n        def start():\n            self.server_ioloop.run_sync(init_server)\n            self.server_ioloop.start()\n\n        self.server_thread = threading.Thread(target=start)\n        self.server_thread.start()\n        event.wait()\n\n        self.http_client = HTTPClient()\n\n    def tearDown(self):\n        def stop_server():\n            self.server.stop()\n            # Delay the shutdown of the IOLoop by several iterations because\n            # the server may still have some cleanup work left when\n            # the client finishes with the response (this is noticeable\n            # with http/2, which leaves a Future with an unexamined\n            # StreamClosedError on the loop).\n\n            @gen.coroutine\n            def slow_stop():\n                yield self.server.close_all_connections()\n                # The number of iterations is difficult to predict. Typically,\n                # one is sufficient, although sometimes it needs more.\n                for i in range(5):\n                    yield\n                self.server_ioloop.stop()\n\n            self.server_ioloop.add_callback(slow_stop)\n\n        self.server_ioloop.add_callback(stop_server)\n        self.server_thread.join()\n        self.http_client.close()\n        self.server_ioloop.close(all_fds=True)\n\n    def get_url(self, path):\n        return \"http://127.0.0.1:%d%s\" % (self.port, path)\n\n    def test_sync_client(self):\n        response = self.http_client.fetch(self.get_url(\"/\"))\n        self.assertEqual(b\"Hello world!\", response.body)\n\n    def test_sync_client_error(self):\n        # Synchronous HTTPClient raises errors directly; no need for\n        # response.rethrow()\n        with self.assertRaises(HTTPError) as assertion:\n            self.http_client.fetch(self.get_url(\"/notfound\"))\n        self.assertEqual(assertion.exception.code, 404)\n\n\nclass SyncHTTPClientSubprocessTest(unittest.TestCase):\n    def test_destructor_log(self):\n        # Regression test for\n        # https://github.com/tornadoweb/tornado/issues/2539\n        #\n        # In the past, the following program would log an\n        # \"inconsistent AsyncHTTPClient cache\" error from a destructor\n        # when the process is shutting down. The shutdown process is\n        # subtle and I don't fully understand it; the failure does not\n        # manifest if that lambda isn't there or is a simpler object\n        # like an int (nor does it manifest in the tornado test suite\n        # as a whole, which is why we use this subprocess).\n        proc = subprocess.run(\n            [\n                sys.executable,\n                \"-c\",\n                \"from tornado.httpclient import HTTPClient; f = lambda: None; c = HTTPClient()\",\n            ],\n            stdout=subprocess.PIPE,\n            stderr=subprocess.STDOUT,\n            check=True,\n            timeout=15,\n        )\n        if proc.stdout:\n            print(\"STDOUT:\")\n            print(to_unicode(proc.stdout))\n        if proc.stdout:\n            self.fail(\"subprocess produced unexpected output\")\n\n\nclass HTTPRequestTestCase(unittest.TestCase):\n    def test_headers(self):\n        request = HTTPRequest(\"http://example.com\", headers={\"foo\": \"bar\"})\n        self.assertEqual(request.headers, {\"foo\": \"bar\"})\n\n    def test_headers_setter(self):\n        request = HTTPRequest(\"http://example.com\")\n        request.headers = {\"bar\": \"baz\"}  # type: ignore\n        self.assertEqual(request.headers, {\"bar\": \"baz\"})\n\n    def test_null_headers_setter(self):\n        request = HTTPRequest(\"http://example.com\")\n        request.headers = None  # type: ignore\n        self.assertEqual(request.headers, {})\n\n    def test_body(self):\n        request = HTTPRequest(\"http://example.com\", body=\"foo\")\n        self.assertEqual(request.body, utf8(\"foo\"))\n\n    def test_body_setter(self):\n        request = HTTPRequest(\"http://example.com\")\n        request.body = \"foo\"  # type: ignore\n        self.assertEqual(request.body, utf8(\"foo\"))\n\n    def test_if_modified_since(self):\n        http_date = datetime.datetime.now(datetime.timezone.utc)\n        request = HTTPRequest(\"http://example.com\", if_modified_since=http_date)\n        self.assertEqual(\n            request.headers, {\"If-Modified-Since\": format_timestamp(http_date)}\n        )\n\n    def test_if_modified_since_naive_deprecated(self):\n        with ignore_deprecation():\n            http_date = datetime.datetime.utcnow()\n        request = HTTPRequest(\"http://example.com\", if_modified_since=http_date)\n        self.assertEqual(\n            request.headers, {\"If-Modified-Since\": format_timestamp(http_date)}\n        )\n\n\nclass HTTPErrorTestCase(unittest.TestCase):\n    def test_copy(self):\n        e = HTTPError(403)\n        e2 = copy.copy(e)\n        self.assertIsNot(e, e2)\n        self.assertEqual(e.code, e2.code)\n\n    def test_plain_error(self):\n        e = HTTPError(403)\n        self.assertEqual(str(e), \"HTTP 403: Forbidden\")\n        self.assertEqual(repr(e), \"HTTP 403: Forbidden\")\n\n    def test_error_with_response(self):\n        resp = HTTPResponse(HTTPRequest(\"http://example.com/\"), 403)\n        with self.assertRaises(HTTPError) as cm:\n            resp.rethrow()\n        e = cm.exception\n        self.assertEqual(str(e), \"HTTP 403: Forbidden\")\n        self.assertEqual(repr(e), \"HTTP 403: Forbidden\")\n"
  },
  {
    "path": "tornado/test/httpserver_test.py",
    "content": "import datetime\nimport gzip\nimport logging\nimport os\nimport shutil\nimport socket\nimport ssl\nimport sys\nimport tempfile\nimport textwrap\nimport typing\nimport unittest\nimport urllib.parse\nimport uuid\nfrom contextlib import closing, contextmanager\nfrom io import BytesIO\n\nfrom tornado import gen, netutil\nfrom tornado.escape import (\n    _unicode,\n    json_decode,\n    json_encode,\n    native_str,\n    recursive_unicode,\n    utf8,\n)\nfrom tornado.http1connection import HTTP1Connection\nfrom tornado.httpclient import HTTPError\nfrom tornado.httpserver import HTTPServer\nfrom tornado.httputil import (\n    HTTPHeaders,\n    HTTPMessageDelegate,\n    HTTPServerConnectionDelegate,\n    ResponseStartLine,\n)\nfrom tornado.iostream import IOStream\nfrom tornado.locks import Event\nfrom tornado.log import app_log, gen_log\nfrom tornado.simple_httpclient import SimpleAsyncHTTPClient\nfrom tornado.test.util import abstract_base_test\nfrom tornado.testing import (\n    AsyncHTTPSTestCase,\n    AsyncHTTPTestCase,\n    AsyncTestCase,\n    ExpectLog,\n    gen_test,\n)\nfrom tornado.web import Application, RequestHandler, stream_request_body\n\n\nasync def read_stream_body(stream):\n    \"\"\"Reads an HTTP response from `stream` and returns a tuple of its\n    start_line, headers and body.\"\"\"\n    chunks = []\n\n    class Delegate(HTTPMessageDelegate):\n        def headers_received(self, start_line, headers):\n            self.headers = headers\n            self.start_line = start_line\n\n        def data_received(self, chunk):\n            chunks.append(chunk)\n\n        def finish(self):\n            conn.detach()  # type: ignore\n\n    conn = HTTP1Connection(stream, True)\n    delegate = Delegate()\n    await conn.read_response(delegate)\n    return delegate.start_line, delegate.headers, b\"\".join(chunks)\n\n\nclass HandlerBaseTestCase(AsyncHTTPTestCase):\n    Handler = None\n\n    def get_app(self):\n        return Application([(\"/\", self.__class__.Handler)])\n\n    def fetch_json(self, *args, **kwargs):\n        response = self.fetch(*args, **kwargs)\n        response.rethrow()\n        return json_decode(response.body)\n\n\nclass HelloWorldRequestHandler(RequestHandler):\n    def initialize(self, protocol=\"http\"):\n        self.expected_protocol = protocol\n\n    def get(self):\n        if self.request.protocol != self.expected_protocol:\n            raise Exception(\"unexpected protocol\")\n        self.finish(\"Hello world\")\n\n    def post(self):\n        self.finish(\"Got %d bytes in POST\" % len(self.request.body))\n\n\nclass SSLTest(AsyncHTTPSTestCase):\n    def get_app(self):\n        return Application([(\"/\", HelloWorldRequestHandler, dict(protocol=\"https\"))])\n\n    def get_ssl_options(self):\n        return dict(\n            ssl_version=ssl.PROTOCOL_TLS_SERVER,\n            **AsyncHTTPSTestCase.default_ssl_options(),\n        )\n\n    def test_ssl(self):\n        response = self.fetch(\"/\")\n        self.assertEqual(response.body, b\"Hello world\")\n\n    def test_large_post(self):\n        response = self.fetch(\"/\", method=\"POST\", body=\"A\" * 5000)\n        self.assertEqual(response.body, b\"Got 5000 bytes in POST\")\n\n    def test_non_ssl_request(self):\n        # Make sure the server closes the connection when it gets a non-ssl\n        # connection, rather than waiting for a timeout or otherwise\n        # misbehaving.\n        with ExpectLog(gen_log, \"(SSL Error|uncaught exception)\"):\n            with ExpectLog(gen_log, \"Uncaught exception\", required=False):\n                with self.assertRaises((IOError, HTTPError)):  # type: ignore\n                    self.fetch(\n                        self.get_url(\"/\").replace(\"https:\", \"http:\"),\n                        request_timeout=3600,\n                        connect_timeout=3600,\n                        raise_error=True,\n                    )\n\n    def test_error_logging(self):\n        # No stack traces are logged for SSL errors.\n        with ExpectLog(gen_log, \"SSL Error\") as expect_log:\n            with self.assertRaises((IOError, HTTPError)):  # type: ignore\n                self.fetch(\n                    self.get_url(\"/\").replace(\"https:\", \"http:\"), raise_error=True\n                )\n        self.assertFalse(expect_log.logged_stack)\n\n\nclass BadSSLOptionsTest(unittest.TestCase):\n    def test_missing_arguments(self):\n        application = Application()\n        self.assertRaises(\n            KeyError,\n            HTTPServer,\n            application,\n            ssl_options={\"keyfile\": \"/__missing__.crt\"},\n        )\n\n    def test_missing_key(self):\n        \"\"\"A missing SSL key should cause an immediate exception.\"\"\"\n\n        application = Application()\n        module_dir = os.path.dirname(__file__)\n        existing_certificate = os.path.join(module_dir, \"test.crt\")\n        existing_key = os.path.join(module_dir, \"test.key\")\n\n        self.assertRaises(\n            (ValueError, IOError),\n            HTTPServer,\n            application,\n            ssl_options={\"certfile\": \"/__mising__.crt\"},\n        )\n        self.assertRaises(\n            (ValueError, IOError),\n            HTTPServer,\n            application,\n            ssl_options={\n                \"certfile\": existing_certificate,\n                \"keyfile\": \"/__missing__.key\",\n            },\n        )\n\n        # This actually works because both files exist\n        HTTPServer(\n            application,\n            ssl_options={\"certfile\": existing_certificate, \"keyfile\": existing_key},\n        )\n\n\nclass MultipartTestHandler(RequestHandler):\n    def post(self):\n        self.finish(\n            {\n                \"header\": self.request.headers[\"X-Header-Encoding-Test\"],\n                \"argument\": self.get_argument(\"argument\"),\n                \"filename\": self.request.files[\"files\"][0].filename,\n                \"filebody\": _unicode(self.request.files[\"files\"][0][\"body\"]),\n            }\n        )\n\n\n# This test is also called from wsgi_test\nclass HTTPConnectionTest(AsyncHTTPTestCase):\n    def get_handlers(self):\n        return [\n            (\"/multipart\", MultipartTestHandler),\n            (\"/hello\", HelloWorldRequestHandler),\n        ]\n\n    def get_app(self):\n        return Application(self.get_handlers())\n\n    def raw_fetch(self, headers, body, newline=b\"\\r\\n\"):\n        with closing(IOStream(socket.socket())) as stream:\n            self.io_loop.run_sync(\n                lambda: stream.connect((\"127.0.0.1\", self.get_http_port()))\n            )\n            stream.write(\n                newline.join(headers + [utf8(\"Content-Length: %d\" % len(body))])\n                + newline\n                + newline\n                + body\n            )\n            start_line, headers, body = self.io_loop.run_sync(\n                lambda: read_stream_body(stream)\n            )\n            return body\n\n    def test_multipart_form(self):\n        # Encodings here are tricky:  Headers are latin1, bodies can be\n        # anything (we use utf8 by default).\n        response = self.raw_fetch(\n            [\n                b\"POST /multipart HTTP/1.0\",\n                b\"Content-Type: multipart/form-data; boundary=1234567890\",\n                b\"X-Header-encoding-test: \\xe9\",\n            ],\n            b\"\\r\\n\".join(\n                [\n                    b\"Content-Disposition: form-data; name=argument\",\n                    b\"\",\n                    \"\\u00e1\".encode(),\n                    b\"--1234567890\",\n                    'Content-Disposition: form-data; name=\"files\"; filename=\"\\u00f3\"'.encode(),\n                    b\"\",\n                    \"\\u00fa\".encode(),\n                    b\"--1234567890--\",\n                    b\"\",\n                ]\n            ),\n        )\n        data = json_decode(response)\n        self.assertEqual(\"\\u00e9\", data[\"header\"])\n        self.assertEqual(\"\\u00e1\", data[\"argument\"])\n        self.assertEqual(\"\\u00f3\", data[\"filename\"])\n        self.assertEqual(\"\\u00fa\", data[\"filebody\"])\n\n    def test_newlines(self):\n        # We support both CRLF and bare LF as line separators.\n        for newline in (b\"\\r\\n\", b\"\\n\"):\n            response = self.raw_fetch([b\"GET /hello HTTP/1.0\"], b\"\", newline=newline)\n            self.assertEqual(response, b\"Hello world\")\n\n    @gen_test\n    def test_100_continue(self):\n        # Run through a 100-continue interaction by hand:\n        # When given Expect: 100-continue, we get a 100 response after the\n        # headers, and then the real response after the body.\n        stream = IOStream(socket.socket())\n        yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n        yield stream.write(\n            b\"\\r\\n\".join(\n                [\n                    b\"POST /hello HTTP/1.1\",\n                    b\"Host: 127.0.0.1\",\n                    b\"Content-Length: 1024\",\n                    b\"Expect: 100-continue\",\n                    b\"Connection: close\",\n                    b\"\\r\\n\",\n                ]\n            )\n        )\n        data = yield stream.read_until(b\"\\r\\n\\r\\n\")\n        self.assertTrue(data.startswith(b\"HTTP/1.1 100 \"), data)\n        stream.write(b\"a\" * 1024)\n        first_line = yield stream.read_until(b\"\\r\\n\")\n        self.assertTrue(first_line.startswith(b\"HTTP/1.1 200\"), first_line)\n        header_data = yield stream.read_until(b\"\\r\\n\\r\\n\")\n        headers = HTTPHeaders.parse(native_str(header_data.decode(\"latin1\")))\n        body = yield stream.read_bytes(int(headers[\"Content-Length\"]))\n        self.assertEqual(body, b\"Got 1024 bytes in POST\")\n        stream.close()\n\n\nclass EchoHandler(RequestHandler):\n    def get(self):\n        self.write(recursive_unicode(self.request.arguments))\n\n    def post(self):\n        self.write(recursive_unicode(self.request.arguments))\n\n\nclass TypeCheckHandler(RequestHandler):\n    def prepare(self):\n        self.errors: dict[str, str] = {}\n        fields = [\n            (\"method\", str),\n            (\"uri\", str),\n            (\"version\", str),\n            (\"remote_ip\", str),\n            (\"protocol\", str),\n            (\"host\", str),\n            (\"path\", str),\n            (\"query\", str),\n        ]\n        for field, expected_type in fields:\n            self.check_type(field, getattr(self.request, field), expected_type)\n\n        self.check_type(\"header_key\", list(self.request.headers.keys())[0], str)\n        self.check_type(\"header_value\", list(self.request.headers.values())[0], str)\n\n        self.check_type(\"cookie_key\", list(self.request.cookies.keys())[0], str)\n        self.check_type(\n            \"cookie_value\", list(self.request.cookies.values())[0].value, str\n        )\n        # secure cookies\n\n        self.check_type(\"arg_key\", list(self.request.arguments.keys())[0], str)\n        self.check_type(\"arg_value\", list(self.request.arguments.values())[0][0], bytes)\n\n    def post(self):\n        self.check_type(\"body\", self.request.body, bytes)\n        self.write(self.errors)\n\n    def get(self):\n        self.write(self.errors)\n\n    def check_type(self, name, obj, expected_type):\n        actual_type = type(obj)\n        if expected_type != actual_type:\n            self.errors[name] = f\"expected {expected_type}, got {actual_type}\"\n\n\nclass PostEchoHandler(RequestHandler):\n    def post(self, *path_args):\n        self.write(dict(echo=self.get_argument(\"data\")))\n\n\nclass PostEchoGBKHandler(PostEchoHandler):\n    def decode_argument(self, value, name=None):\n        try:\n            return value.decode(\"gbk\")\n        except Exception:\n            raise HTTPError(400, \"invalid gbk bytes: %r\" % value)\n\n\nclass HTTPServerTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application(\n            [\n                (\"/echo\", EchoHandler),\n                (\"/typecheck\", TypeCheckHandler),\n                (\"//doubleslash\", EchoHandler),\n                (\"/post_utf8\", PostEchoHandler),\n                (\"/post_gbk\", PostEchoGBKHandler),\n            ]\n        )\n\n    def test_query_string_encoding(self):\n        response = self.fetch(\"/echo?foo=%C3%A9\")\n        data = json_decode(response.body)\n        self.assertEqual(data, {\"foo\": [\"\\u00e9\"]})\n\n    def test_empty_query_string(self):\n        response = self.fetch(\"/echo?foo=&foo=\")\n        data = json_decode(response.body)\n        self.assertEqual(data, {\"foo\": [\"\", \"\"]})\n\n    def test_empty_post_parameters(self):\n        response = self.fetch(\"/echo\", method=\"POST\", body=\"foo=&bar=\")\n        data = json_decode(response.body)\n        self.assertEqual(data, {\"foo\": [\"\"], \"bar\": [\"\"]})\n\n    def test_types(self):\n        headers = {\"Cookie\": \"foo=bar\"}\n        response = self.fetch(\"/typecheck?foo=bar\", headers=headers)\n        data = json_decode(response.body)\n        self.assertEqual(data, {})\n\n        response = self.fetch(\n            \"/typecheck\", method=\"POST\", body=\"foo=bar\", headers=headers\n        )\n        data = json_decode(response.body)\n        self.assertEqual(data, {})\n\n    def test_double_slash(self):\n        # urlparse.urlsplit (which tornado.httpserver used to use\n        # incorrectly) would parse paths beginning with \"//\" as\n        # protocol-relative urls.\n        response = self.fetch(\"//doubleslash\")\n        self.assertEqual(200, response.code)\n        self.assertEqual(json_decode(response.body), {})\n\n    def test_post_encodings(self):\n        headers = {\"Content-Type\": \"application/x-www-form-urlencoded\"}\n        uni_text = \"chinese: \\u5f20\\u4e09\"\n        for enc in (\"utf8\", \"gbk\"):\n            for quote in (True, False):\n                with self.subTest(enc=enc, quote=quote):\n                    bin_text = uni_text.encode(enc)\n                    if quote:\n                        bin_text = urllib.parse.quote(bin_text).encode(\"ascii\")\n                    response = self.fetch(\n                        \"/post_\" + enc,\n                        method=\"POST\",\n                        headers=headers,\n                        body=(b\"data=\" + bin_text),\n                    )\n                    self.assertEqual(json_decode(response.body), {\"echo\": uni_text})\n\n\nclass HTTPServerRawTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([(\"/echo\", EchoHandler)])\n\n    def setUp(self):\n        super().setUp()\n        self.stream = IOStream(socket.socket())\n        self.io_loop.run_sync(\n            lambda: self.stream.connect((\"127.0.0.1\", self.get_http_port()))\n        )\n\n    def tearDown(self):\n        self.stream.close()\n        super().tearDown()\n\n    def test_empty_request(self):\n        self.stream.close()\n        self.io_loop.add_timeout(datetime.timedelta(seconds=0.001), self.stop)\n        self.wait()\n\n    def test_malformed_first_line_response(self):\n        with ExpectLog(gen_log, \".*Malformed HTTP request line\", level=logging.INFO):\n            self.stream.write(b\"asdf\\r\\n\\r\\n\")\n            start_line, headers, response = self.io_loop.run_sync(\n                lambda: read_stream_body(self.stream)\n            )\n            self.assertEqual(\"HTTP/1.1\", start_line.version)\n            self.assertEqual(400, start_line.code)\n            self.assertEqual(\"Bad Request\", start_line.reason)\n\n    def test_malformed_first_line_log(self):\n        with ExpectLog(gen_log, \".*Malformed HTTP request line\", level=logging.INFO):\n            self.stream.write(b\"asdf\\r\\n\\r\\n\")\n            # TODO: need an async version of ExpectLog so we don't need\n            # hard-coded timeouts here.\n            self.io_loop.add_timeout(datetime.timedelta(seconds=0.05), self.stop)\n            self.wait()\n\n    def test_malformed_headers(self):\n        with ExpectLog(\n            gen_log,\n            \".*Malformed HTTP message.*no colon in header line\",\n            level=logging.INFO,\n        ):\n            self.stream.write(b\"GET / HTTP/1.0\\r\\nasdf\\r\\n\\r\\n\")\n            self.io_loop.add_timeout(datetime.timedelta(seconds=0.05), self.stop)\n            self.wait()\n\n    def test_invalid_host_header_with_whitespace(self):\n        with ExpectLog(\n            gen_log, \".*Malformed HTTP message.*Invalid Host header\", level=logging.INFO\n        ):\n            self.stream.write(b\"GET / HTTP/1.0\\r\\nHost: foo bar\\r\\n\\r\\n\")\n            start_line, headers, response = self.io_loop.run_sync(\n                lambda: read_stream_body(self.stream)\n            )\n            self.assertEqual(\"HTTP/1.1\", start_line.version)\n            self.assertEqual(400, start_line.code)\n            self.assertEqual(\"Bad Request\", start_line.reason)\n\n    def test_chunked_request_body(self):\n        # Chunked requests are not widely supported and we don't have a way\n        # to generate them in AsyncHTTPClient, but HTTPServer will read them.\n        self.stream.write(b\"\"\"\\\nPOST /echo HTTP/1.1\nHost: 127.0.0.1\nTransfer-Encoding: chunked\nContent-Type: application/x-www-form-urlencoded\n\n4\nfoo=\n3\nbar\n0\n\n\"\"\".replace(b\"\\n\", b\"\\r\\n\"))\n        start_line, headers, response = self.io_loop.run_sync(\n            lambda: read_stream_body(self.stream)\n        )\n        self.assertEqual(json_decode(response), {\"foo\": [\"bar\"]})\n\n    def test_chunked_request_uppercase(self):\n        # As per RFC 2616 section 3.6, \"Transfer-Encoding\" header's value is\n        # case-insensitive.\n        self.stream.write(b\"\"\"\\\nPOST /echo HTTP/1.1\nHost: 127.0.0.1\nTransfer-Encoding: Chunked\nContent-Type: application/x-www-form-urlencoded\n\n4\nfoo=\n3\nbar\n0\n\n\"\"\".replace(b\"\\n\", b\"\\r\\n\"))\n        start_line, headers, response = self.io_loop.run_sync(\n            lambda: read_stream_body(self.stream)\n        )\n        self.assertEqual(json_decode(response), {\"foo\": [\"bar\"]})\n\n    def test_chunked_request_body_invalid_size(self):\n        # Only hex digits are allowed in chunk sizes. Python's int() function\n        # also accepts underscores, so make sure we reject them here.\n        self.stream.write(b\"\"\"\\\nPOST /echo HTTP/1.1\nHost: 127.0.0.1\nTransfer-Encoding: chunked\n\n1_a\n1234567890abcdef1234567890\n0\n\n\"\"\".replace(b\"\\n\", b\"\\r\\n\"))\n        with ExpectLog(gen_log, \".*invalid chunk size\", level=logging.INFO):\n            start_line, headers, response = self.io_loop.run_sync(\n                lambda: read_stream_body(self.stream)\n            )\n        self.assertEqual(400, start_line.code)\n\n    def test_chunked_request_body_duplicate_header(self):\n        # Repeated Transfer-Encoding headers should be an error (and not confuse\n        # the chunked-encoding detection to mess up framing).\n        self.stream.write(b\"\"\"\\\nPOST /echo HTTP/1.1\nHost: 127.0.0.1\nTransfer-Encoding: chunked\nTransfer-encoding: chunked\n\n2\nok\n0\n\n\"\"\")\n        with ExpectLog(\n            gen_log,\n            \".*Unsupported Transfer-Encoding chunked,chunked\",\n            level=logging.INFO,\n        ):\n            start_line, headers, response = self.io_loop.run_sync(\n                lambda: read_stream_body(self.stream)\n            )\n        self.assertEqual(400, start_line.code)\n\n    def test_chunked_request_body_unsupported_transfer_encoding(self):\n        # We don't support transfer-encodings other than chunked.\n        self.stream.write(b\"\"\"\\\nPOST /echo HTTP/1.1\nHost: 127.0.0.1\nTransfer-Encoding: gzip, chunked\n\n2\nok\n0\n\n\"\"\")\n        with ExpectLog(\n            gen_log, \".*Unsupported Transfer-Encoding gzip, chunked\", level=logging.INFO\n        ):\n            start_line, headers, response = self.io_loop.run_sync(\n                lambda: read_stream_body(self.stream)\n            )\n        self.assertEqual(400, start_line.code)\n\n    def test_chunked_request_body_transfer_encoding_and_content_length(self):\n        # Transfer-encoding and content-length are mutually exclusive\n        self.stream.write(b\"\"\"\\\nPOST /echo HTTP/1.1\nHost: 127.0.0.1\nTransfer-Encoding: chunked\nContent-Length: 2\n\n2\nok\n0\n\n\"\"\")\n        with ExpectLog(\n            gen_log,\n            \".*Message with both Transfer-Encoding and Content-Length\",\n            level=logging.INFO,\n        ):\n            start_line, headers, response = self.io_loop.run_sync(\n                lambda: read_stream_body(self.stream)\n            )\n        self.assertEqual(400, start_line.code)\n\n    @gen_test\n    def test_invalid_content_length(self):\n        # HTTP only allows decimal digits in content-length. Make sure we don't\n        # accept anything else, with special attention to things accepted by the\n        # python int() function (leading plus signs and internal underscores).\n        test_cases = [\n            (\"alphabetic\", \"foo\"),\n            (\"leading plus\", \"+10\"),\n            (\"internal underscore\", \"1_0\"),\n        ]\n        for name, value in test_cases:\n            with self.subTest(name=name), closing(IOStream(socket.socket())) as stream:\n                with ExpectLog(\n                    gen_log,\n                    \".*Only integer Content-Length is allowed\",\n                    level=logging.INFO,\n                ):\n                    yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n                    stream.write(utf8(textwrap.dedent(f\"\"\"\\\n                            POST /echo HTTP/1.1\n                            Host: 127.0.0.1\n                            Content-Length: {value}\n                            Connection: close\n\n                            1234567890\n                            \"\"\").replace(\"\\n\", \"\\r\\n\")))\n                    yield stream.read_until_close()\n\n    @gen_test\n    def test_invalid_methods(self):\n        # RFC 9110 distinguishes between syntactically invalid methods and those that are\n        # valid but unknown. The former must give a 400 status code, while the latter should\n        # give a 405.\n        test_cases = [\n            (\"FOO\", 405, None),\n            (\"FOO,BAR\", 400, \".*Malformed HTTP request line\"),\n        ]\n        for method, code, log_msg in test_cases:\n            if log_msg is not None:\n                expect_log = ExpectLog(gen_log, log_msg, level=logging.INFO)\n            else:\n\n                @contextmanager\n                def noop_context():\n                    yield\n\n                expect_log = noop_context()  # type: ignore\n            with (\n                self.subTest(method=method),\n                closing(IOStream(socket.socket())) as stream,\n                expect_log,\n            ):\n                yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n                stream.write(utf8(f\"{method} /echo HTTP/1.1\\r\\nHost:127.0.0.1\\r\\n\\r\\n\"))\n                resp = yield stream.read_until(b\"\\r\\n\\r\\n\")\n                self.assertTrue(\n                    resp.startswith(b\"HTTP/1.1 %d\" % code),\n                    f\"expected status code {code} in {resp!r}\",\n                )\n\n\nclass XHeaderTest(HandlerBaseTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            self.set_header(\"request-version\", self.request.version)\n            self.write(\n                dict(\n                    remote_ip=self.request.remote_ip,\n                    remote_protocol=self.request.protocol,\n                )\n            )\n\n    def get_httpserver_options(self):\n        return dict(xheaders=True, trusted_downstream=[\"5.5.5.5\"])\n\n    def test_ip_headers(self):\n        self.assertEqual(self.fetch_json(\"/\")[\"remote_ip\"], \"127.0.0.1\")\n\n        valid_ipv4 = {\"X-Real-IP\": \"4.4.4.4\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=valid_ipv4)[\"remote_ip\"], \"4.4.4.4\"\n        )\n\n        valid_ipv4_list = {\"X-Forwarded-For\": \"127.0.0.1, 4.4.4.4\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=valid_ipv4_list)[\"remote_ip\"], \"4.4.4.4\"\n        )\n\n        valid_ipv6 = {\"X-Real-IP\": \"2620:0:1cfe:face:b00c::3\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=valid_ipv6)[\"remote_ip\"],\n            \"2620:0:1cfe:face:b00c::3\",\n        )\n\n        valid_ipv6_list = {\"X-Forwarded-For\": \"::1, 2620:0:1cfe:face:b00c::3\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=valid_ipv6_list)[\"remote_ip\"],\n            \"2620:0:1cfe:face:b00c::3\",\n        )\n\n        invalid_chars = {\"X-Real-IP\": \"4.4.4.4<script>\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=invalid_chars)[\"remote_ip\"], \"127.0.0.1\"\n        )\n\n        invalid_chars_list = {\"X-Forwarded-For\": \"4.4.4.4, 5.5.5.5<script>\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=invalid_chars_list)[\"remote_ip\"], \"127.0.0.1\"\n        )\n\n        invalid_host = {\"X-Real-IP\": \"www.google.com\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=invalid_host)[\"remote_ip\"], \"127.0.0.1\"\n        )\n\n    def test_trusted_downstream(self):\n        valid_ipv4_list = {\"X-Forwarded-For\": \"127.0.0.1, 4.4.4.4, 5.5.5.5\"}\n        resp = self.fetch(\"/\", headers=valid_ipv4_list)\n        if resp.headers[\"request-version\"].startswith(\"HTTP/2\"):\n            # This is a hack - there's nothing that fundamentally requires http/1\n            # here but tornado_http2 doesn't support it yet.\n            self.skipTest(\"requires HTTP/1.x\")\n        result = json_decode(resp.body)\n        self.assertEqual(result[\"remote_ip\"], \"4.4.4.4\")\n\n    def test_scheme_headers(self):\n        self.assertEqual(self.fetch_json(\"/\")[\"remote_protocol\"], \"http\")\n\n        https_scheme = {\"X-Scheme\": \"https\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=https_scheme)[\"remote_protocol\"], \"https\"\n        )\n\n        https_forwarded = {\"X-Forwarded-Proto\": \"https\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=https_forwarded)[\"remote_protocol\"], \"https\"\n        )\n\n        https_multi_forwarded = {\"X-Forwarded-Proto\": \"https , http\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=https_multi_forwarded)[\"remote_protocol\"],\n            \"http\",\n        )\n\n        http_multi_forwarded = {\"X-Forwarded-Proto\": \"http,https\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=http_multi_forwarded)[\"remote_protocol\"],\n            \"https\",\n        )\n\n        bad_forwarded = {\"X-Forwarded-Proto\": \"unknown\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=bad_forwarded)[\"remote_protocol\"], \"http\"\n        )\n\n\nclass SSLXHeaderTest(AsyncHTTPSTestCase, HandlerBaseTestCase):\n    def get_app(self):\n        return Application([(\"/\", XHeaderTest.Handler)])\n\n    def get_httpserver_options(self):\n        output = super().get_httpserver_options()\n        output[\"xheaders\"] = True\n        return output\n\n    def test_request_without_xprotocol(self):\n        self.assertEqual(self.fetch_json(\"/\")[\"remote_protocol\"], \"https\")\n\n        http_scheme = {\"X-Scheme\": \"http\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=http_scheme)[\"remote_protocol\"], \"http\"\n        )\n\n        bad_scheme = {\"X-Scheme\": \"unknown\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=bad_scheme)[\"remote_protocol\"], \"https\"\n        )\n\n\nclass ManualProtocolTest(HandlerBaseTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            self.write(dict(protocol=self.request.protocol))\n\n    def get_httpserver_options(self):\n        return dict(protocol=\"https\")\n\n    def test_manual_protocol(self):\n        self.assertEqual(self.fetch_json(\"/\")[\"protocol\"], \"https\")\n\n\n@abstract_base_test\nclass UnixSocketTest(AsyncTestCase):\n    \"\"\"HTTPServers can listen on Unix sockets too.\n\n    Why would you want to do this?  Nginx can proxy to backends listening\n    on unix sockets, for one thing (and managing a namespace for unix\n    sockets can be easier than managing a bunch of TCP port numbers).\n\n    Unfortunately, there's no way to specify a unix socket in a url for\n    an HTTP client, so we have to test this by hand.\n    \"\"\"\n\n    address = \"\"\n\n    def setUp(self):\n        super().setUp()\n        app = Application([(\"/hello\", HelloWorldRequestHandler)])\n        self.server = HTTPServer(app)\n        self.server.add_socket(netutil.bind_unix_socket(self.address))\n\n    def tearDown(self):\n        self.io_loop.run_sync(self.server.close_all_connections)\n        self.server.stop()\n        super().tearDown()\n\n    @gen_test\n    def test_unix_socket(self):\n        with closing(IOStream(socket.socket(socket.AF_UNIX))) as stream:\n            stream.connect(self.address)\n            stream.write(b\"GET /hello HTTP/1.0\\r\\n\\r\\n\")\n            response = yield stream.read_until(b\"\\r\\n\")\n            self.assertEqual(response, b\"HTTP/1.1 200 OK\\r\\n\")\n            header_data = yield stream.read_until(b\"\\r\\n\\r\\n\")\n            headers = HTTPHeaders.parse(header_data.decode(\"latin1\"))\n            body = yield stream.read_bytes(int(headers[\"Content-Length\"]))\n            self.assertEqual(body, b\"Hello world\")\n\n    @gen_test\n    def test_unix_socket_bad_request(self):\n        # Unix sockets don't have remote addresses so they just return an\n        # empty string.\n        with ExpectLog(gen_log, \"Malformed HTTP message from\", level=logging.INFO):\n            with closing(IOStream(socket.socket(socket.AF_UNIX))) as stream:\n                stream.connect(self.address)\n                stream.write(b\"garbage\\r\\n\\r\\n\")\n                response = yield stream.read_until_close()\n        self.assertEqual(response, b\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\")\n\n\n@unittest.skipIf(\n    not hasattr(socket, \"AF_UNIX\") or sys.platform == \"cygwin\",\n    \"unix sockets not supported on this platform\",\n)\nclass UnixSocketTestFile(UnixSocketTest):\n    def setUp(self):\n        self.tmpdir = tempfile.mkdtemp()\n        self.address = os.path.join(self.tmpdir, \"test.sock\")\n        super().setUp()\n\n    def tearDown(self):\n        super().tearDown()\n        shutil.rmtree(self.tmpdir)\n\n\n@unittest.skipIf(\n    not (hasattr(socket, \"AF_UNIX\") and sys.platform.startswith(\"linux\")),\n    \"abstract namespace unix sockets not supported on this platform\",\n)\nclass UnixSocketTestAbstract(UnixSocketTest):\n    def setUp(self):\n        self.address = \"\\0\" + uuid.uuid4().hex\n        super().setUp()\n\n\nclass KeepAliveTest(AsyncHTTPTestCase):\n    \"\"\"Tests various scenarios for HTTP 1.1 keep-alive support.\n\n    These tests don't use AsyncHTTPClient because we want to control\n    connection reuse and closing.\n    \"\"\"\n\n    def get_app(self):\n        class HelloHandler(RequestHandler):\n            def get(self):\n                self.finish(\"Hello world\")\n\n            def post(self):\n                self.finish(\"Hello world\")\n\n        class LargeHandler(RequestHandler):\n            def get(self):\n                # 512KB should be bigger than the socket buffers so it will\n                # be written out in chunks.\n                self.write(\"\".join(chr(i % 256) * 1024 for i in range(512)))\n\n        class TransferEncodingChunkedHandler(RequestHandler):\n            @gen.coroutine\n            def head(self):\n                self.write(\"Hello world\")\n                yield self.flush()\n\n        class FinishOnCloseHandler(RequestHandler):\n            def initialize(self, cleanup_event):\n                self.cleanup_event = cleanup_event\n\n            @gen.coroutine\n            def get(self):\n                self.flush()\n                yield self.cleanup_event.wait()\n\n            def on_connection_close(self):\n                # This is not very realistic, but finishing the request\n                # from the close callback has the right timing to mimic\n                # some errors seen in the wild.\n                self.finish(\"closed\")\n\n        self.cleanup_event = Event()\n        return Application(\n            [\n                (\"/\", HelloHandler),\n                (\"/large\", LargeHandler),\n                (\"/chunked\", TransferEncodingChunkedHandler),\n                (\n                    \"/finish_on_close\",\n                    FinishOnCloseHandler,\n                    dict(cleanup_event=self.cleanup_event),\n                ),\n            ]\n        )\n\n    def setUp(self):\n        super().setUp()\n        self.http_version = b\"HTTP/1.1\"\n\n    def tearDown(self):\n        # We just closed the client side of the socket; let the IOLoop run\n        # once to make sure the server side got the message.\n        self.io_loop.add_timeout(datetime.timedelta(seconds=0.001), self.stop)\n        self.wait()\n\n        if hasattr(self, \"stream\"):\n            self.stream.close()\n        super().tearDown()\n\n    # The next few methods are a crude manual http client\n    @gen.coroutine\n    def connect(self):\n        self.stream = IOStream(socket.socket())\n        yield self.stream.connect((\"127.0.0.1\", self.get_http_port()))\n\n    @gen.coroutine\n    def read_headers(self):\n        first_line = yield self.stream.read_until(b\"\\r\\n\")\n        self.assertTrue(first_line.startswith(b\"HTTP/1.1 200\"), first_line)\n        header_bytes = yield self.stream.read_until(b\"\\r\\n\\r\\n\")\n        headers = HTTPHeaders.parse(header_bytes.decode(\"latin1\"))\n        raise gen.Return(headers)\n\n    @gen.coroutine\n    def read_response(self):\n        self.headers = yield self.read_headers()\n        body = yield self.stream.read_bytes(int(self.headers[\"Content-Length\"]))\n        self.assertEqual(b\"Hello world\", body)\n\n    def close(self):\n        self.stream.close()\n        del self.stream\n\n    @gen_test\n    def test_two_requests(self):\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\nHost:127.0.0.1\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\nHost:127.0.0.1\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.close()\n\n    @gen_test\n    def test_request_close(self):\n        yield self.connect()\n        self.stream.write(\n            b\"GET / HTTP/1.1\\r\\nHost:127.0.0.1\\r\\nConnection: close\\r\\n\\r\\n\"\n        )\n        yield self.read_response()\n        data = yield self.stream.read_until_close()\n        self.assertTrue(not data)\n        self.assertEqual(self.headers[\"Connection\"], \"close\")\n        self.close()\n\n    # keepalive is supported for http 1.0 too, but it's opt-in\n    @gen_test\n    def test_http10(self):\n        self.http_version = b\"HTTP/1.0\"\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.0\\r\\n\\r\\n\")\n        yield self.read_response()\n        data = yield self.stream.read_until_close()\n        self.assertFalse(data)\n        self.assertNotIn(\"Connection\", self.headers)\n        self.close()\n\n    @gen_test\n    def test_http10_keepalive(self):\n        self.http_version = b\"HTTP/1.0\"\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.close()\n\n    @gen_test\n    def test_http10_keepalive_extra_crlf(self):\n        self.http_version = b\"HTTP/1.0\"\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.close()\n\n    @gen_test\n    def test_pipelined_requests(self):\n        yield self.connect()\n        self.stream.write(\n            b\"GET / HTTP/1.1\\r\\nHost:127.0.0.1\\r\\n\\r\\nGET / HTTP/1.1\\r\\nHost:127.0.0.1\\r\\n\\r\\n\"\n        )\n        yield self.read_response()\n        yield self.read_response()\n        self.close()\n\n    @gen_test\n    def test_pipelined_cancel(self):\n        yield self.connect()\n        self.stream.write(\n            b\"GET / HTTP/1.1\\r\\nHost:127.0.0.1\\r\\n\\r\\nGET / HTTP/1.1\\r\\nHost:127.0.0.1\\r\\n\\r\\n\"\n        )\n        # only read once\n        yield self.read_response()\n        self.close()\n\n    @gen_test\n    def test_cancel_during_download(self):\n        yield self.connect()\n        self.stream.write(b\"GET /large HTTP/1.1\\r\\nHost:127.0.0.1\\r\\n\\r\\n\")\n        yield self.read_headers()\n        yield self.stream.read_bytes(1024)\n        self.close()\n\n    @gen_test\n    def test_finish_while_closed(self):\n        yield self.connect()\n        self.stream.write(b\"GET /finish_on_close HTTP/1.1\\r\\nHost:127.0.0.1\\r\\n\\r\\n\")\n        yield self.read_headers()\n        self.close()\n        # Let the hanging coroutine clean up after itself\n        self.cleanup_event.set()\n\n    @gen_test\n    def test_keepalive_chunked(self):\n        self.http_version = b\"HTTP/1.0\"\n        yield self.connect()\n        self.stream.write(\n            b\"POST / HTTP/1.0\\r\\n\"\n            b\"Connection: keep-alive\\r\\n\"\n            b\"Transfer-Encoding: chunked\\r\\n\"\n            b\"\\r\\n\"\n            b\"0\\r\\n\"\n            b\"\\r\\n\"\n        )\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.close()\n\n    @gen_test\n    def test_keepalive_chunked_head_no_body(self):\n        yield self.connect()\n        self.stream.write(b\"HEAD /chunked HTTP/1.1\\r\\nHost:127.0.0.1\\r\\n\\r\\n\")\n        yield self.read_headers()\n\n        self.stream.write(b\"HEAD /chunked HTTP/1.1\\r\\nHost:127.0.0.1\\r\\n\\r\\n\")\n        yield self.read_headers()\n        self.close()\n\n\nclass GzipBaseTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([(\"/\", EchoHandler)])\n\n    def post_gzip(self, body):\n        bytesio = BytesIO()\n        gzip_file = gzip.GzipFile(mode=\"w\", fileobj=bytesio)\n        gzip_file.write(utf8(body))\n        gzip_file.close()\n        compressed_body = bytesio.getvalue()\n        return self.fetch(\n            \"/\",\n            method=\"POST\",\n            body=compressed_body,\n            headers={\"Content-Encoding\": \"gzip\"},\n        )\n\n    def test_uncompressed(self):\n        response = self.fetch(\"/\", method=\"POST\", body=\"foo=bar\")\n        self.assertEqual(json_decode(response.body), {\"foo\": [\"bar\"]})\n\n\nclass GzipTest(GzipBaseTest, AsyncHTTPTestCase):\n    def get_httpserver_options(self):\n        return dict(decompress_request=True)\n\n    def test_gzip(self):\n        response = self.post_gzip(\"foo=bar\")\n        self.assertEqual(json_decode(response.body), {\"foo\": [\"bar\"]})\n\n    def test_gzip_case_insensitive(self):\n        # https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.2.1\n        bytesio = BytesIO()\n        gzip_file = gzip.GzipFile(mode=\"w\", fileobj=bytesio)\n        gzip_file.write(utf8(\"foo=bar\"))\n        gzip_file.close()\n        compressed_body = bytesio.getvalue()\n        response = self.fetch(\n            \"/\",\n            method=\"POST\",\n            body=compressed_body,\n            headers={\"Content-Encoding\": \"GZIP\"},\n        )\n        self.assertEqual(json_decode(response.body), {\"foo\": [\"bar\"]})\n\n\nclass GzipUnsupportedTest(GzipBaseTest, AsyncHTTPTestCase):\n    def test_gzip_unsupported(self):\n        # Gzip support is opt-in; without it the server fails to parse\n        # the body (but parsing form bodies is currently just a log message,\n        # not a fatal error).\n        with ExpectLog(gen_log, \".*Unsupported Content-Encoding\"):\n            response = self.post_gzip(\"foo=bar\")\n        self.assertEqual(response.code, 400)\n\n\nclass StreamingChunkSizeTest(AsyncHTTPTestCase):\n    # 50 characters long, and repetitive so it can be compressed.\n    BODY = b\"01234567890123456789012345678901234567890123456789\"\n    CHUNK_SIZE = 16\n\n    def get_http_client(self):\n        # body_producer doesn't work on curl_httpclient, so override the\n        # configured AsyncHTTPClient implementation.\n        return SimpleAsyncHTTPClient()\n\n    def get_httpserver_options(self):\n        return dict(chunk_size=self.CHUNK_SIZE, decompress_request=True)\n\n    class MessageDelegate(HTTPMessageDelegate):\n        def __init__(self, connection):\n            self.connection = connection\n\n        def headers_received(self, start_line, headers):\n            self.chunk_lengths: list[int] = []\n\n        def data_received(self, chunk):\n            self.chunk_lengths.append(len(chunk))\n\n        def finish(self):\n            response_body = utf8(json_encode(self.chunk_lengths))\n            self.connection.write_headers(\n                ResponseStartLine(\"HTTP/1.1\", 200, \"OK\"),\n                HTTPHeaders({\"Content-Length\": str(len(response_body))}),\n            )\n            self.connection.write(response_body)\n            self.connection.finish()\n\n    def get_app(self):\n        class App(HTTPServerConnectionDelegate):\n            def start_request(self, server_conn, request_conn):\n                return StreamingChunkSizeTest.MessageDelegate(request_conn)\n\n        return App()\n\n    def fetch_chunk_sizes(self, **kwargs):\n        response = self.fetch(\"/\", method=\"POST\", **kwargs)\n        response.rethrow()\n        chunks = json_decode(response.body)\n        self.assertEqual(len(self.BODY), sum(chunks))\n        for chunk_size in chunks:\n            self.assertLessEqual(\n                chunk_size, self.CHUNK_SIZE, \"oversized chunk: \" + str(chunks)\n            )\n            self.assertGreater(chunk_size, 0, \"empty chunk: \" + str(chunks))\n        return chunks\n\n    def compress(self, body):\n        bytesio = BytesIO()\n        gzfile = gzip.GzipFile(mode=\"w\", fileobj=bytesio)\n        gzfile.write(body)\n        gzfile.close()\n        compressed = bytesio.getvalue()\n        if len(compressed) >= len(body):\n            raise Exception(\"body did not shrink when compressed\")\n        return compressed\n\n    def test_regular_body(self):\n        chunks = self.fetch_chunk_sizes(body=self.BODY)\n        # Without compression we know exactly what to expect.\n        self.assertEqual([16, 16, 16, 2], chunks)\n\n    def test_compressed_body(self):\n        self.fetch_chunk_sizes(\n            body=self.compress(self.BODY), headers={\"Content-Encoding\": \"gzip\"}\n        )\n        # Compression creates irregular boundaries so the assertions\n        # in fetch_chunk_sizes are as specific as we can get.\n\n    def test_chunked_body(self):\n        def body_producer(write):\n            write(self.BODY[:20])\n            write(self.BODY[20:])\n\n        chunks = self.fetch_chunk_sizes(body_producer=body_producer)\n        # HTTP chunk boundaries translate to application-visible breaks\n        self.assertEqual([16, 4, 16, 14], chunks)\n\n    def test_chunked_compressed(self):\n        compressed = self.compress(self.BODY)\n        self.assertGreater(len(compressed), 20)\n\n        def body_producer(write):\n            write(compressed[:20])\n            write(compressed[20:])\n\n        self.fetch_chunk_sizes(\n            body_producer=body_producer, headers={\"Content-Encoding\": \"gzip\"}\n        )\n\n\nclass InvalidOutputContentLengthTest(AsyncHTTPTestCase):\n    class MessageDelegate(HTTPMessageDelegate):\n        def __init__(self, connection):\n            self.connection = connection\n\n        def headers_received(self, start_line, headers):\n            content_lengths = {\n                \"normal\": \"10\",\n                \"alphabetic\": \"foo\",\n                \"leading plus\": \"+10\",\n                \"underscore\": \"1_0\",\n            }\n            self.connection.write_headers(\n                ResponseStartLine(\"HTTP/1.1\", 200, \"OK\"),\n                HTTPHeaders({\"Content-Length\": content_lengths[headers[\"x-test\"]]}),\n            )\n            self.connection.write(b\"1234567890\")\n            self.connection.finish()\n\n    def get_app(self):\n        class App(HTTPServerConnectionDelegate):\n            def start_request(self, server_conn, request_conn):\n                return InvalidOutputContentLengthTest.MessageDelegate(request_conn)\n\n        return App()\n\n    def test_invalid_output_content_length(self):\n        with self.subTest(\"normal\"):\n            response = self.fetch(\"/\", method=\"GET\", headers={\"x-test\": \"normal\"})\n            response.rethrow()\n            self.assertEqual(response.body, b\"1234567890\")\n        for test in [\"alphabetic\", \"leading plus\", \"underscore\"]:\n            with self.subTest(test):\n                # This log matching could be tighter but I think I'm already\n                # over-testing here.\n                with ExpectLog(app_log, \"Uncaught exception\"):\n                    with self.assertRaises(HTTPError):\n                        self.fetch(\"/\", method=\"GET\", headers={\"x-test\": test})\n\n\nclass MaxHeaderSizeTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([(\"/\", HelloWorldRequestHandler)])\n\n    def get_httpserver_options(self):\n        return dict(max_header_size=1024)\n\n    def test_small_headers(self):\n        response = self.fetch(\"/\", headers={\"X-Filler\": \"a\" * 100})\n        response.rethrow()\n        self.assertEqual(response.body, b\"Hello world\")\n\n    def test_large_headers(self):\n        with ExpectLog(gen_log, \"Unsatisfiable read\", required=False):\n            try:\n                self.fetch(\"/\", headers={\"X-Filler\": \"a\" * 1000}, raise_error=True)\n                self.fail(\"did not raise expected exception\")\n            except HTTPError as e:\n                # 431 is \"Request Header Fields Too Large\", defined in RFC\n                # 6585. However, many implementations just close the\n                # connection in this case, resulting in a missing response.\n                if e.response is not None:\n                    self.assertIn(e.response.code, (431, 599))\n\n\nclass IdleTimeoutTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([(\"/\", HelloWorldRequestHandler)])\n\n    def get_httpserver_options(self):\n        return dict(idle_connection_timeout=0.1)\n\n    def setUp(self):\n        super().setUp()\n        self.streams: list[IOStream] = []\n\n    def tearDown(self):\n        super().tearDown()\n        for stream in self.streams:\n            stream.close()\n\n    @gen.coroutine\n    def connect(self):\n        stream = IOStream(socket.socket())\n        yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n        self.streams.append(stream)\n        raise gen.Return(stream)\n\n    @gen_test\n    def test_unused_connection(self):\n        stream = yield self.connect()\n        event = Event()\n        stream.set_close_callback(event.set)\n        yield event.wait()\n\n    @gen_test\n    def test_idle_after_use(self):\n        stream = yield self.connect()\n        event = Event()\n        stream.set_close_callback(event.set)\n\n        # Use the connection twice to make sure keep-alives are working\n        for i in range(2):\n            stream.write(b\"GET / HTTP/1.1\\r\\nHost: 127.0.0.1\\r\\n\\r\\n\")\n            yield stream.read_until(b\"\\r\\n\\r\\n\")\n            data = yield stream.read_bytes(11)\n            self.assertEqual(data, b\"Hello world\")\n\n        # Now let the timeout trigger and close the connection.\n        yield event.wait()\n\n\nclass BodyLimitsTest(AsyncHTTPTestCase):\n    def get_app(self):\n        class BufferedHandler(RequestHandler):\n            def put(self):\n                self.write(str(len(self.request.body)))\n\n        @stream_request_body\n        class StreamingHandler(RequestHandler):\n            def initialize(self):\n                self.bytes_read = 0\n\n            def prepare(self):\n                conn = typing.cast(HTTP1Connection, self.request.connection)\n                if \"expected_size\" in self.request.arguments:\n                    conn.set_max_body_size(int(self.get_argument(\"expected_size\")))\n                if \"body_timeout\" in self.request.arguments:\n                    conn.set_body_timeout(float(self.get_argument(\"body_timeout\")))\n\n            def data_received(self, data):\n                self.bytes_read += len(data)\n\n            def put(self):\n                self.write(str(self.bytes_read))\n\n        return Application(\n            [(\"/buffered\", BufferedHandler), (\"/streaming\", StreamingHandler)]\n        )\n\n    def get_httpserver_options(self):\n        return dict(body_timeout=3600, max_body_size=4096)\n\n    def get_http_client(self):\n        # body_producer doesn't work on curl_httpclient, so override the\n        # configured AsyncHTTPClient implementation.\n        return SimpleAsyncHTTPClient()\n\n    def test_small_body(self):\n        response = self.fetch(\"/buffered\", method=\"PUT\", body=b\"a\" * 4096)\n        self.assertEqual(response.body, b\"4096\")\n        response = self.fetch(\"/streaming\", method=\"PUT\", body=b\"a\" * 4096)\n        self.assertEqual(response.body, b\"4096\")\n\n    def test_large_body_buffered(self):\n        with ExpectLog(gen_log, \".*Content-Length too long\", level=logging.INFO):\n            response = self.fetch(\"/buffered\", method=\"PUT\", body=b\"a\" * 10240)\n        self.assertEqual(response.code, 400)\n\n    @unittest.skipIf(os.name == \"nt\", \"flaky on windows\")\n    def test_large_body_buffered_chunked(self):\n        # This test is flaky on windows for unknown reasons.\n        with ExpectLog(gen_log, \".*chunked body too large\", level=logging.INFO):\n            response = self.fetch(\n                \"/buffered\",\n                method=\"PUT\",\n                body_producer=lambda write: write(b\"a\" * 10240),\n            )\n        self.assertEqual(response.code, 400)\n\n    def test_large_body_streaming(self):\n        with ExpectLog(gen_log, \".*Content-Length too long\", level=logging.INFO):\n            response = self.fetch(\"/streaming\", method=\"PUT\", body=b\"a\" * 10240)\n        self.assertEqual(response.code, 400)\n\n    @unittest.skipIf(os.name == \"nt\", \"flaky on windows\")\n    def test_large_body_streaming_chunked(self):\n        with ExpectLog(gen_log, \".*chunked body too large\", level=logging.INFO):\n            response = self.fetch(\n                \"/streaming\",\n                method=\"PUT\",\n                body_producer=lambda write: write(b\"a\" * 10240),\n            )\n        self.assertEqual(response.code, 400)\n\n    def test_large_body_streaming_override(self):\n        response = self.fetch(\n            \"/streaming?expected_size=10240\", method=\"PUT\", body=b\"a\" * 10240\n        )\n        self.assertEqual(response.body, b\"10240\")\n\n    def test_large_body_streaming_chunked_override(self):\n        response = self.fetch(\n            \"/streaming?expected_size=10240\",\n            method=\"PUT\",\n            body_producer=lambda write: write(b\"a\" * 10240),\n        )\n        self.assertEqual(response.body, b\"10240\")\n\n    @gen_test\n    def test_timeout(self):\n        stream = IOStream(socket.socket())\n        try:\n            yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n            # Use a raw stream because AsyncHTTPClient won't let us read a\n            # response without finishing a body.\n            stream.write(\n                b\"PUT /streaming?body_timeout=0.1 HTTP/1.0\\r\\n\"\n                b\"Content-Length: 42\\r\\n\\r\\n\"\n            )\n            with ExpectLog(gen_log, \"Timeout reading body\", level=logging.INFO):\n                response = yield stream.read_until_close()\n            self.assertEqual(response, b\"\")\n        finally:\n            stream.close()\n\n    @gen_test\n    def test_body_size_override_reset(self):\n        # The max_body_size override is reset between requests.\n        stream = IOStream(socket.socket())\n        try:\n            yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n            # Use a raw stream so we can make sure it's all on one connection.\n            stream.write(\n                b\"PUT /streaming?expected_size=10240 HTTP/1.1\\r\\n\"\n                b\"Host: 127.0.0.1\\r\\n\"\n                b\"Content-Length: 10240\\r\\n\\r\\n\"\n            )\n            stream.write(b\"a\" * 10240)\n            start_line, headers, response = yield read_stream_body(stream)\n            self.assertEqual(response, b\"10240\")\n            # Without the ?expected_size parameter, we get the old default value\n            stream.write(\n                b\"PUT /streaming HTTP/1.1\\r\\n\"\n                b\"Host: 127.0.0.1\\r\\n\"\n                b\"Content-Length: 10240\\r\\n\\r\\n\"\n            )\n            with ExpectLog(gen_log, \".*Content-Length too long\", level=logging.INFO):\n                data = yield stream.read_until_close()\n            self.assertEqual(data, b\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\")\n        finally:\n            stream.close()\n\n\nclass LegacyInterfaceTest(AsyncHTTPTestCase):\n    def get_app(self):\n        # The old request_callback interface does not implement the\n        # delegate interface, and writes its response via request.write\n        # instead of request.connection.write_headers.\n        def handle_request(request):\n            self.http1 = request.version.startswith(\"HTTP/1.\")\n            if not self.http1:\n                # This test will be skipped if we're using HTTP/2,\n                # so just close it out cleanly using the modern interface.\n                request.connection.write_headers(\n                    ResponseStartLine(\"\", 200, \"OK\"), HTTPHeaders()\n                )\n                request.connection.finish()\n                return\n            message = b\"Hello world\"\n            request.connection.write(\n                utf8(\"HTTP/1.1 200 OK\\r\\n\" \"Content-Length: %d\\r\\n\\r\\n\" % len(message))\n            )\n            request.connection.write(message)\n            request.connection.finish()\n\n        return handle_request\n\n    def test_legacy_interface(self):\n        response = self.fetch(\"/\")\n        if not self.http1:\n            self.skipTest(\"requires HTTP/1.x\")\n        self.assertEqual(response.body, b\"Hello world\")\n"
  },
  {
    "path": "tornado/test/httputil_test.py",
    "content": "import copy\nimport datetime\nimport logging\nimport pickle\nimport time\nimport unittest\nimport urllib.parse\n\nfrom tornado.escape import native_str, utf8\nfrom tornado.httputil import (\n    HTTPFile,\n    HTTPHeaders,\n    HTTPInputError,\n    HTTPServerRequest,\n    ParseMultipartConfig,\n    format_timestamp,\n    parse_cookie,\n    parse_multipart_form_data,\n    parse_request_start_line,\n    qs_to_qsl,\n    url_concat,\n)\nfrom tornado.log import gen_log\nfrom tornado.test.util import ignore_deprecation, skipIfEmulated\n\n\ndef form_data_args() -> tuple[dict[str, list[bytes]], dict[str, list[HTTPFile]]]:\n    \"\"\"Return two empty dicts suitable for use with parse_multipart_form_data.\n\n    mypy insists on type annotations for dict literals, so this lets us avoid\n    the verbose types throughout this test.\n    \"\"\"\n    return {}, {}\n\n\nclass TestUrlConcat(unittest.TestCase):\n    def test_url_concat_no_query_params(self):\n        url = url_concat(\"https://localhost/path\", [(\"y\", \"y\"), (\"z\", \"z\")])\n        self.assertEqual(url, \"https://localhost/path?y=y&z=z\")\n\n    def test_url_concat_encode_args(self):\n        url = url_concat(\"https://localhost/path\", [(\"y\", \"/y\"), (\"z\", \"z\")])\n        self.assertEqual(url, \"https://localhost/path?y=%2Fy&z=z\")\n\n    def test_url_concat_trailing_q(self):\n        url = url_concat(\"https://localhost/path?\", [(\"y\", \"y\"), (\"z\", \"z\")])\n        self.assertEqual(url, \"https://localhost/path?y=y&z=z\")\n\n    def test_url_concat_q_with_no_trailing_amp(self):\n        url = url_concat(\"https://localhost/path?x\", [(\"y\", \"y\"), (\"z\", \"z\")])\n        self.assertEqual(url, \"https://localhost/path?x=&y=y&z=z\")\n\n    def test_url_concat_trailing_amp(self):\n        url = url_concat(\"https://localhost/path?x&\", [(\"y\", \"y\"), (\"z\", \"z\")])\n        self.assertEqual(url, \"https://localhost/path?x=&y=y&z=z\")\n\n    def test_url_concat_mult_params(self):\n        url = url_concat(\"https://localhost/path?a=1&b=2\", [(\"y\", \"y\"), (\"z\", \"z\")])\n        self.assertEqual(url, \"https://localhost/path?a=1&b=2&y=y&z=z\")\n\n    def test_url_concat_no_params(self):\n        url = url_concat(\"https://localhost/path?r=1&t=2\", [])\n        self.assertEqual(url, \"https://localhost/path?r=1&t=2\")\n\n    def test_url_concat_none_params(self):\n        url = url_concat(\"https://localhost/path?r=1&t=2\", None)\n        self.assertEqual(url, \"https://localhost/path?r=1&t=2\")\n\n    def test_url_concat_with_frag(self):\n        url = url_concat(\"https://localhost/path#tab\", [(\"y\", \"y\")])\n        self.assertEqual(url, \"https://localhost/path?y=y#tab\")\n\n    def test_url_concat_multi_same_params(self):\n        url = url_concat(\"https://localhost/path\", [(\"y\", \"y1\"), (\"y\", \"y2\")])\n        self.assertEqual(url, \"https://localhost/path?y=y1&y=y2\")\n\n    def test_url_concat_multi_same_query_params(self):\n        url = url_concat(\"https://localhost/path?r=1&r=2\", [(\"y\", \"y\")])\n        self.assertEqual(url, \"https://localhost/path?r=1&r=2&y=y\")\n\n    def test_url_concat_dict_params(self):\n        url = url_concat(\"https://localhost/path\", dict(y=\"y\"))\n        self.assertEqual(url, \"https://localhost/path?y=y\")\n\n\nclass QsParseTest(unittest.TestCase):\n    def test_parsing(self):\n        qsstring = \"a=1&b=2&a=3\"\n        qs = urllib.parse.parse_qs(qsstring)\n        qsl = list(qs_to_qsl(qs))\n        self.assertIn((\"a\", \"1\"), qsl)\n        self.assertIn((\"a\", \"3\"), qsl)\n        self.assertIn((\"b\", \"2\"), qsl)\n\n\nclass MultipartFormDataTest(unittest.TestCase):\n    def test_file_upload(self):\n        data = b\"\"\"\\\n--1234\nContent-Disposition: form-data; name=\"files\"; filename=\"ab.txt\"\n\nFoo\n--1234--\"\"\".replace(b\"\\n\", b\"\\r\\n\")\n        args, files = form_data_args()\n        parse_multipart_form_data(b\"1234\", data, args, files)\n        file = files[\"files\"][0]\n        self.assertEqual(file[\"filename\"], \"ab.txt\")\n        self.assertEqual(file[\"body\"], b\"Foo\")\n\n    def test_unquoted_names(self):\n        # quotes are optional unless special characters are present\n        data = b\"\"\"\\\n--1234\nContent-Disposition: form-data; name=files; filename=ab.txt\n\nFoo\n--1234--\"\"\".replace(b\"\\n\", b\"\\r\\n\")\n        args, files = form_data_args()\n        parse_multipart_form_data(b\"1234\", data, args, files)\n        file = files[\"files\"][0]\n        self.assertEqual(file[\"filename\"], \"ab.txt\")\n        self.assertEqual(file[\"body\"], b\"Foo\")\n\n    def test_special_filenames(self):\n        filenames = [\n            \"a;b.txt\",\n            'a\"b.txt',\n            'a\";b.txt',\n            'a;\"b.txt',\n            'a\";\";.txt',\n            'a\\\\\"b.txt',\n            \"a\\\\b.txt\",\n            \"a b.txt\",\n            \"a\\tb.txt\",\n        ]\n        for filename in filenames:\n            logging.debug(\"trying filename %r\", filename)\n            str_data = \"\"\"\\\n--1234\nContent-Disposition: form-data; name=\"files\"; filename=\"%s\"\n\nFoo\n--1234--\"\"\" % filename.replace(\"\\\\\", \"\\\\\\\\\").replace('\"', '\\\\\"')\n            data = utf8(str_data.replace(\"\\n\", \"\\r\\n\"))\n            args, files = form_data_args()\n            parse_multipart_form_data(b\"1234\", data, args, files)\n            file = files[\"files\"][0]\n            self.assertEqual(file[\"filename\"], filename)\n            self.assertEqual(file[\"body\"], b\"Foo\")\n\n    def test_invalid_chars(self):\n        filenames = [\n            \"a\\rb.txt\",\n            \"a\\0b.txt\",\n            \"a\\x08b.txt\",\n        ]\n        for filename in filenames:\n            str_data = \"\"\"\\\n--1234\nContent-Disposition: form-data; name=\"files\"; filename=\"%s\"\n\nFoo\n--1234--\"\"\" % filename.replace(\"\\\\\", \"\\\\\\\\\").replace('\"', '\\\\\"')\n            data = utf8(str_data.replace(\"\\n\", \"\\r\\n\"))\n            args, files = form_data_args()\n            with self.assertRaises(HTTPInputError) as cm:\n                parse_multipart_form_data(b\"1234\", data, args, files)\n            self.assertIn(\"Invalid header value\", str(cm.exception))\n\n    def test_non_ascii_filename_rfc5987(self):\n        data = b\"\"\"\\\n--1234\nContent-Disposition: form-data; name=\"files\"; filename=\"ab.txt\"; filename*=UTF-8''%C3%A1b.txt\n\nFoo\n--1234--\"\"\".replace(b\"\\n\", b\"\\r\\n\")\n        args, files = form_data_args()\n        parse_multipart_form_data(b\"1234\", data, args, files)\n        file = files[\"files\"][0]\n        self.assertEqual(file[\"filename\"], \"áb.txt\")\n        self.assertEqual(file[\"body\"], b\"Foo\")\n\n    def test_non_ascii_filename_raw(self):\n        data = \"\"\"\\\n--1234\nContent-Disposition: form-data; name=\"files\"; filename=\"测试.txt\"\n\nFoo\n--1234--\"\"\".encode().replace(b\"\\n\", b\"\\r\\n\")\n        args, files = form_data_args()\n        parse_multipart_form_data(b\"1234\", data, args, files)\n        file = files[\"files\"][0]\n        self.assertEqual(file[\"filename\"], \"测试.txt\")\n        self.assertEqual(file[\"body\"], b\"Foo\")\n\n    def test_boundary_starts_and_ends_with_quotes(self):\n        data = b\"\"\"\\\n--1234\nContent-Disposition: form-data; name=\"files\"; filename=\"ab.txt\"\n\nFoo\n--1234--\"\"\".replace(b\"\\n\", b\"\\r\\n\")\n        args, files = form_data_args()\n        parse_multipart_form_data(b'\"1234\"', data, args, files)\n        file = files[\"files\"][0]\n        self.assertEqual(file[\"filename\"], \"ab.txt\")\n        self.assertEqual(file[\"body\"], b\"Foo\")\n\n    def test_missing_headers(self):\n        data = b\"\"\"\\\n--1234\n\nFoo\n--1234--\"\"\".replace(b\"\\n\", b\"\\r\\n\")\n        args, files = form_data_args()\n        with self.assertRaises(\n            HTTPInputError, msg=\"multipart/form-data missing headers\"\n        ):\n            parse_multipart_form_data(b\"1234\", data, args, files)\n        self.assertEqual(files, {})\n\n    def test_invalid_content_disposition(self):\n        data = b\"\"\"\\\n--1234\nContent-Disposition: invalid; name=\"files\"; filename=\"ab.txt\"\n\nFoo\n--1234--\"\"\".replace(b\"\\n\", b\"\\r\\n\")\n        args, files = form_data_args()\n        with self.assertRaises(HTTPInputError, msg=\"Invalid multipart/form-data\"):\n            parse_multipart_form_data(b\"1234\", data, args, files)\n        self.assertEqual(files, {})\n\n    def test_line_does_not_end_with_correct_line_break(self):\n        data = b\"\"\"\\\n--1234\nContent-Disposition: form-data; name=\"files\"; filename=\"ab.txt\"\n\nFoo--1234--\"\"\".replace(b\"\\n\", b\"\\r\\n\")\n        args, files = form_data_args()\n        with self.assertRaises(HTTPInputError, msg=\"Invalid multipart/form-data\"):\n            parse_multipart_form_data(b\"1234\", data, args, files)\n        self.assertEqual(files, {})\n\n    def test_content_disposition_header_without_name_parameter(self):\n        data = b\"\"\"\\\n--1234\nContent-Disposition: form-data; filename=\"ab.txt\"\n\nFoo\n--1234--\"\"\".replace(b\"\\n\", b\"\\r\\n\")\n        args, files = form_data_args()\n        with self.assertRaises(\n            HTTPInputError, msg=\"multipart/form-data value missing name\"\n        ):\n            parse_multipart_form_data(b\"1234\", data, args, files)\n        self.assertEqual(files, {})\n\n    def test_data_after_final_boundary(self):\n        # The spec requires that data after the final boundary be ignored.\n        # http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html\n        # In practice, some libraries include an extra CRLF after the boundary.\n        data = b\"\"\"\\\n--1234\nContent-Disposition: form-data; name=\"files\"; filename=\"ab.txt\"\n\nFoo\n--1234--\n\"\"\".replace(b\"\\n\", b\"\\r\\n\")\n        args, files = form_data_args()\n        parse_multipart_form_data(b\"1234\", data, args, files)\n        file = files[\"files\"][0]\n        self.assertEqual(file[\"filename\"], \"ab.txt\")\n        self.assertEqual(file[\"body\"], b\"Foo\")\n\n    def test_disposition_param_linear_performance(self):\n        # This is a regression test for performance of parsing parameters\n        # to the content-disposition header, specifically for semicolons within\n        # quoted strings.\n        def f(n):\n            start = time.perf_counter()\n            message = (\n                b\"--1234\\r\\nContent-Disposition: form-data; \"\n                + b'x=\"'\n                + b\";\" * n\n                + b'\"; '\n                + b'name=\"files\"; filename=\"a.txt\"\\r\\n\\r\\nFoo\\r\\n--1234--\\r\\n'\n            )\n            args: dict[str, list[bytes]] = {}\n            files: dict[str, list[HTTPFile]] = {}\n            parse_multipart_form_data(b\"1234\", message, args, files)\n            return time.perf_counter() - start\n\n        d1 = f(1_000)\n        # Note that headers larger than this are blocked by the default configuration.\n        d2 = f(10_000)\n        if d2 / d1 > 20:\n            self.fail(f\"Disposition param parsing is not linear: {d1=} vs {d2=}\")\n\n    def test_multipart_config(self):\n        boundary = b\"1234\"\n        body = b\"\"\"--1234\nContent-Disposition: form-data; name=\"files\"; filename=\"ab.txt\"\n\n--1234--\"\"\".replace(b\"\\n\", b\"\\r\\n\")\n        config = ParseMultipartConfig()\n        args, files = form_data_args()\n        parse_multipart_form_data(boundary, body, args, files, config=config)\n        self.assertEqual(files[\"files\"][0][\"filename\"], \"ab.txt\")\n\n        config_no_parts = ParseMultipartConfig(max_parts=0)\n        with self.assertRaises(HTTPInputError) as cm:\n            parse_multipart_form_data(\n                boundary, body, args, files, config=config_no_parts\n            )\n        self.assertIn(\"too many parts\", str(cm.exception))\n\n        config_small_headers = ParseMultipartConfig(max_part_header_size=10)\n        with self.assertRaises(HTTPInputError) as cm:\n            parse_multipart_form_data(\n                boundary, body, args, files, config=config_small_headers\n            )\n        self.assertIn(\"header too large\", str(cm.exception))\n\n        config_disabled = ParseMultipartConfig(enabled=False)\n        with self.assertRaises(HTTPInputError) as cm:\n            parse_multipart_form_data(\n                boundary, body, args, files, config=config_disabled\n            )\n        self.assertIn(\"multipart/form-data parsing is disabled\", str(cm.exception))\n\n\nclass HTTPHeadersTest(unittest.TestCase):\n    def test_multi_line(self):\n        # Lines beginning with whitespace are appended to the previous line\n        # with any leading whitespace replaced by a single space.\n        # Note that while multi-line headers are a part of the HTTP spec,\n        # their use is strongly discouraged.\n        data = \"\"\"\\\nFoo: bar\n baz\nAsdf: qwer\n\\tzxcv\nFoo: even\n     more\n     lines\n\"\"\".replace(\"\\n\", \"\\r\\n\")\n        headers = HTTPHeaders.parse(data)\n        self.assertEqual(headers[\"asdf\"], \"qwer zxcv\")\n        self.assertEqual(headers.get_list(\"asdf\"), [\"qwer zxcv\"])\n        self.assertEqual(headers[\"Foo\"], \"bar baz,even more lines\")\n        self.assertEqual(headers.get_list(\"foo\"), [\"bar baz\", \"even more lines\"])\n        self.assertEqual(\n            sorted(list(headers.get_all())),\n            [(\"Asdf\", \"qwer zxcv\"), (\"Foo\", \"bar baz\"), (\"Foo\", \"even more lines\")],\n        )\n        # Verify case insensitivity in-operator\n        self.assertTrue(\"asdf\" in headers)\n        self.assertTrue(\"Asdf\" in headers)\n\n    def test_continuation(self):\n        data = \"Foo: bar\\r\\n\\tasdf\"\n        headers = HTTPHeaders.parse(data)\n        self.assertEqual(headers[\"Foo\"], \"bar asdf\")\n\n        # If the first line starts with whitespace, it's a\n        # continuation line with nothing to continue, so reject it\n        # (with a proper error).\n        data = \" Foo: bar\"\n        self.assertRaises(HTTPInputError, HTTPHeaders.parse, data)\n\n        # \\f (formfeed) is whitespace according to str.isspace, but\n        # not according to the HTTP spec.\n        data = \"Foo: bar\\r\\n\\fasdf\"\n        self.assertRaises(HTTPInputError, HTTPHeaders.parse, data)\n\n    def test_forbidden_ascii_characters(self):\n        # Control characters and ASCII whitespace other than space, tab, and CRLF are not allowed in\n        # headers.\n        for c in range(0xFF):\n            data = f\"Foo: bar{chr(c)}baz\\r\\n\"\n            if c == 0x09 or (c >= 0x20 and c != 0x7F):\n                headers = HTTPHeaders.parse(data)\n                self.assertEqual(headers[\"Foo\"], f\"bar{chr(c)}baz\")\n            else:\n                self.assertRaises(HTTPInputError, HTTPHeaders.parse, data)\n\n    def test_unicode_newlines(self):\n        # Ensure that only \\r\\n is recognized as a header separator, and not\n        # the other newline-like unicode characters.\n        # Characters that are likely to be problematic can be found in\n        # http://unicode.org/standard/reports/tr13/tr13-5.html\n        # and cpython's unicodeobject.c (which defines the implementation\n        # of unicode_type.splitlines(), and uses a different list than TR13).\n        newlines = [\n            # The following ascii characters are sometimes treated as newline-like,\n            # but they're disallowed in HTTP headers. This test covers unicode\n            # characters that are permitted in headers (under the obs-text rule).\n            # \"\\u001b\",  # VERTICAL TAB\n            # \"\\u001c\",  # FILE SEPARATOR\n            # \"\\u001d\",  # GROUP SEPARATOR\n            # \"\\u001e\",  # RECORD SEPARATOR\n            \"\\u0085\",  # NEXT LINE\n            \"\\u2028\",  # LINE SEPARATOR\n            \"\\u2029\",  # PARAGRAPH SEPARATOR\n        ]\n        for newline in newlines:\n            # Try the utf8 and latin1 representations of each newline\n            for encoding in [\"utf8\", \"latin1\"]:\n                try:\n                    try:\n                        encoded = newline.encode(encoding)\n                    except UnicodeEncodeError:\n                        # Some chars cannot be represented in latin1\n                        continue\n                    data = b\"Cookie: foo=\" + encoded + b\"bar\"\n                    # parse() wants a native_str, so decode through latin1\n                    # in the same way the real parser does.\n                    headers = HTTPHeaders.parse(native_str(data.decode(\"latin1\")))\n                    expected = [\n                        (\n                            \"Cookie\",\n                            \"foo=\" + native_str(encoded.decode(\"latin1\")) + \"bar\",\n                        )\n                    ]\n                    self.assertEqual(expected, list(headers.get_all()))\n                except Exception:\n                    gen_log.warning(\"failed while trying %r in %s\", newline, encoding)\n                    raise\n\n    def test_unicode_whitespace(self):\n        # Only tabs and spaces are to be stripped according to the HTTP standard.\n        # Other unicode whitespace is to be left as-is. In the context of headers,\n        # this specifically means the whitespace characters falling within the\n        # latin1 charset.\n        whitespace = [\n            (\" \", True),  # SPACE\n            (\"\\t\", True),  # TAB\n            (\"\\u00a0\", False),  # NON-BREAKING SPACE\n            (\"\\u0085\", False),  # NEXT LINE\n        ]\n        for c, stripped in whitespace:\n            headers = HTTPHeaders.parse(\"Transfer-Encoding: %schunked\" % c)\n            if stripped:\n                expected = [(\"Transfer-Encoding\", \"chunked\")]\n            else:\n                expected = [(\"Transfer-Encoding\", \"%schunked\" % c)]\n            self.assertEqual(expected, list(headers.get_all()))\n\n    def test_optional_cr(self):\n        # Bare CR is  not a valid line separator\n        with self.assertRaises(HTTPInputError):\n            HTTPHeaders.parse(\"CRLF: crlf\\r\\nLF: lf\\nCR: cr\\rMore: more\\r\\n\")\n\n        # Both CRLF and LF should be accepted as separators. CR should not be\n        # part of the data when followed by LF.\n        headers = HTTPHeaders.parse(\"CRLF: crlf\\r\\nLF: lf\\nMore: more\\r\\n\")\n        self.assertEqual(\n            sorted(headers.get_all()),\n            [(\"Crlf\", \"crlf\"), (\"Lf\", \"lf\"), (\"More\", \"more\")],\n        )\n\n    def test_copy(self):\n        all_pairs = [(\"A\", \"1\"), (\"A\", \"2\"), (\"B\", \"c\")]\n        h1 = HTTPHeaders()\n        for k, v in all_pairs:\n            h1.add(k, v)\n        h2 = h1.copy()\n        h3 = copy.copy(h1)\n        h4 = copy.deepcopy(h1)\n        for headers in [h1, h2, h3, h4]:\n            # All the copies are identical, no matter how they were\n            # constructed.\n            self.assertEqual(list(sorted(headers.get_all())), all_pairs)\n        for headers in [h2, h3, h4]:\n            # Neither the dict or its member lists are reused.\n            self.assertIsNot(headers, h1)\n            self.assertIsNot(headers.get_list(\"A\"), h1.get_list(\"A\"))\n\n    def test_pickle_roundtrip(self):\n        headers = HTTPHeaders()\n        headers.add(\"Set-Cookie\", \"a=b\")\n        headers.add(\"Set-Cookie\", \"c=d\")\n        headers.add(\"Content-Type\", \"text/html\")\n        pickled = pickle.dumps(headers)\n        unpickled = pickle.loads(pickled)\n        self.assertEqual(sorted(headers.get_all()), sorted(unpickled.get_all()))\n        self.assertEqual(sorted(headers.items()), sorted(unpickled.items()))\n\n    def test_setdefault(self):\n        headers = HTTPHeaders()\n        headers[\"foo\"] = \"bar\"\n        # If a value is present, setdefault returns it without changes.\n        self.assertEqual(headers.setdefault(\"foo\", \"baz\"), \"bar\")\n        self.assertEqual(headers[\"foo\"], \"bar\")\n        # If a value is not present, setdefault sets it for future use.\n        self.assertEqual(headers.setdefault(\"quux\", \"xyzzy\"), \"xyzzy\")\n        self.assertEqual(headers[\"quux\"], \"xyzzy\")\n        self.assertEqual(sorted(headers.get_all()), [(\"Foo\", \"bar\"), (\"Quux\", \"xyzzy\")])\n\n    def test_string(self):\n        headers = HTTPHeaders()\n        headers.add(\"Foo\", \"1\")\n        headers.add(\"Foo\", \"2\")\n        headers.add(\"Foo\", \"3\")\n        headers2 = HTTPHeaders.parse(str(headers))\n        self.assertEqual(headers, headers2)\n\n    def test_invalid_header_names(self):\n        invalid_names = [\n            \"\",\n            \"foo bar\",\n            \"foo\\tbar\",\n            \"foo\\nbar\",\n            \"foo\\x00bar\",\n            \"foo \",\n            \" foo\",\n            \"é\",\n        ]\n        for name in invalid_names:\n            headers = HTTPHeaders()\n            with self.assertRaises(HTTPInputError):\n                headers.add(name, \"bar\")\n\n    def test_linear_performance(self):\n        def f(n):\n            start = time.perf_counter()\n            headers = HTTPHeaders()\n            for i in range(n):\n                headers.add(\"X-Foo\", \"bar\")\n            return time.perf_counter() - start\n\n        # This runs under 50ms on my laptop as of 2025-12-09.\n        d1 = f(10_000)\n        d2 = f(100_000)\n        if d2 / d1 > 20:\n            # d2 should be about 10x d1 but allow a wide margin for variability.\n            self.fail(f\"HTTPHeaders.add() does not scale linearly: {d1=} vs {d2=}\")\n\n\nclass FormatTimestampTest(unittest.TestCase):\n    # Make sure that all the input types are supported.\n    TIMESTAMP = 1359312200.503611\n    EXPECTED = \"Sun, 27 Jan 2013 18:43:20 GMT\"\n\n    def check(self, value):\n        self.assertEqual(format_timestamp(value), self.EXPECTED)\n\n    def test_unix_time_float(self):\n        self.check(self.TIMESTAMP)\n\n    def test_unix_time_int(self):\n        self.check(int(self.TIMESTAMP))\n\n    def test_struct_time(self):\n        self.check(time.gmtime(self.TIMESTAMP))\n\n    def test_time_tuple(self):\n        tup = tuple(time.gmtime(self.TIMESTAMP))\n        self.assertEqual(9, len(tup))\n        self.check(tup)\n\n    def test_utc_naive_datetime(self):\n        self.check(\n            datetime.datetime.fromtimestamp(\n                self.TIMESTAMP, datetime.timezone.utc\n            ).replace(tzinfo=None)\n        )\n\n    def test_utc_naive_datetime_deprecated(self):\n        with ignore_deprecation():\n            self.check(datetime.datetime.utcfromtimestamp(self.TIMESTAMP))\n\n    def test_utc_aware_datetime(self):\n        self.check(\n            datetime.datetime.fromtimestamp(self.TIMESTAMP, datetime.timezone.utc)\n        )\n\n    def test_other_aware_datetime(self):\n        # Other timezones are ignored; the timezone is always printed as GMT\n        self.check(\n            datetime.datetime.fromtimestamp(\n                self.TIMESTAMP, datetime.timezone(datetime.timedelta(hours=-4))\n            )\n        )\n\n\n# HTTPServerRequest is mainly tested incidentally to the server itself,\n# but this tests the parts of the class that can be tested in isolation.\nclass HTTPServerRequestTest(unittest.TestCase):\n    def test_default_constructor(self):\n        # All parameters are formally optional, but uri is required\n        # (and has been for some time).  This test ensures that no\n        # more required parameters slip in.\n        HTTPServerRequest(method=\"GET\", uri=\"/\")\n\n    def test_body_is_a_byte_string(self):\n        request = HTTPServerRequest(method=\"GET\", uri=\"/\")\n        self.assertIsInstance(request.body, bytes)\n\n    def test_repr_does_not_contain_headers(self):\n        request = HTTPServerRequest(\n            method=\"GET\", uri=\"/\", headers=HTTPHeaders({\"Canary\": [\"Coal Mine\"]})\n        )\n        self.assertNotIn(\"Canary\", repr(request))\n\n\nclass ParseRequestStartLineTest(unittest.TestCase):\n    METHOD = \"GET\"\n    PATH = \"/foo\"\n    VERSION = \"HTTP/1.1\"\n\n    def test_parse_request_start_line(self):\n        start_line = \" \".join([self.METHOD, self.PATH, self.VERSION])\n        parsed_start_line = parse_request_start_line(start_line)\n        self.assertEqual(parsed_start_line.method, self.METHOD)\n        self.assertEqual(parsed_start_line.path, self.PATH)\n        self.assertEqual(parsed_start_line.version, self.VERSION)\n\n\nclass ParseCookieTest(unittest.TestCase):\n    # These tests copied from Django:\n    # https://github.com/django/django/pull/6277/commits/da810901ada1cae9fc1f018f879f11a7fb467b28\n    def test_python_cookies(self):\n        \"\"\"\n        Test cases copied from Python's Lib/test/test_http_cookies.py\n        \"\"\"\n        self.assertEqual(\n            parse_cookie(\"chips=ahoy; vienna=finger\"),\n            {\"chips\": \"ahoy\", \"vienna\": \"finger\"},\n        )\n        # Here parse_cookie() differs from Python's cookie parsing in that it\n        # treats all semicolons as delimiters, even within quotes.\n        self.assertEqual(\n            parse_cookie('keebler=\"E=mc2; L=\\\\\"Loves\\\\\"; fudge=\\\\012;\"'),\n            {\"keebler\": '\"E=mc2', \"L\": '\\\\\"Loves\\\\\"', \"fudge\": \"\\\\012\", \"\": '\"'},\n        )\n        # Illegal cookies that have an '=' char in an unquoted value.\n        self.assertEqual(parse_cookie(\"keebler=E=mc2\"), {\"keebler\": \"E=mc2\"})\n        # Cookies with ':' character in their name.\n        self.assertEqual(\n            parse_cookie(\"key:term=value:term\"), {\"key:term\": \"value:term\"}\n        )\n        # Cookies with '[' and ']'.\n        self.assertEqual(\n            parse_cookie(\"a=b; c=[; d=r; f=h\"), {\"a\": \"b\", \"c\": \"[\", \"d\": \"r\", \"f\": \"h\"}\n        )\n\n    def test_cookie_edgecases(self):\n        # Cookies that RFC6265 allows.\n        self.assertEqual(\n            parse_cookie(\"a=b; Domain=example.com\"), {\"a\": \"b\", \"Domain\": \"example.com\"}\n        )\n        # parse_cookie() has historically kept only the last cookie with the\n        # same name.\n        self.assertEqual(parse_cookie(\"a=b; h=i; a=c\"), {\"a\": \"c\", \"h\": \"i\"})\n\n    def test_invalid_cookies(self):\n        \"\"\"\n        Cookie strings that go against RFC6265 but browsers will send if set\n        via document.cookie.\n        \"\"\"\n        # Chunks without an equals sign appear as unnamed values per\n        # https://bugzilla.mozilla.org/show_bug.cgi?id=169091\n        self.assertIn(\n            \"django_language\",\n            parse_cookie(\"abc=def; unnamed; django_language=en\").keys(),\n        )\n        # Even a double quote may be an unamed value.\n        self.assertEqual(parse_cookie('a=b; \"; c=d'), {\"a\": \"b\", \"\": '\"', \"c\": \"d\"})\n        # Spaces in names and values, and an equals sign in values.\n        self.assertEqual(\n            parse_cookie(\"a b c=d e = f; gh=i\"), {\"a b c\": \"d e = f\", \"gh\": \"i\"}\n        )\n        # More characters the spec forbids.\n        self.assertEqual(\n            parse_cookie('a   b,c<>@:/[]?{}=d  \"  =e,f g'),\n            {\"a   b,c<>@:/[]?{}\": 'd  \"  =e,f g'},\n        )\n        # Unicode characters. The spec only allows ASCII.\n        self.assertEqual(\n            parse_cookie(\"saint=André Bessette\"),\n            {\"saint\": native_str(\"André Bessette\")},\n        )\n        # Browsers don't send extra whitespace or semicolons in Cookie headers,\n        # but parse_cookie() should parse whitespace the same way\n        # document.cookie parses whitespace.\n        self.assertEqual(\n            parse_cookie(\"  =  b  ;  ;  =  ;   c  =  ;  \"), {\"\": \"b\", \"c\": \"\"}\n        )\n\n    def test_unquote(self):\n        # Copied from\n        # https://github.com/python/cpython/blob/dc7a2b6522ec7af41282bc34f405bee9b306d611/Lib/test/test_http_cookies.py#L62\n        cases = [\n            (r'a=\"b=\\\"\"', 'b=\"'),\n            (r'a=\"b=\\\\\"', \"b=\\\\\"),\n            (r'a=\"b=\\=\"', \"b==\"),\n            (r'a=\"b=\\n\"', \"b=n\"),\n            (r'a=\"b=\\042\"', 'b=\"'),\n            (r'a=\"b=\\134\"', \"b=\\\\\"),\n            (r'a=\"b=\\377\"', \"b=\\xff\"),\n            (r'a=\"b=\\400\"', \"b=400\"),\n            (r'a=\"b=\\42\"', \"b=42\"),\n            (r'a=\"b=\\\\042\"', \"b=\\\\042\"),\n            (r'a=\"b=\\\\134\"', \"b=\\\\134\"),\n            (r'a=\"b=\\\\\\\"\"', 'b=\\\\\"'),\n            (r'a=\"b=\\\\\\042\"', 'b=\\\\\"'),\n            (r'a=\"b=\\134\\\"\"', 'b=\\\\\"'),\n            (r'a=\"b=\\134\\042\"', 'b=\\\\\"'),\n        ]\n        for encoded, decoded in cases:\n            with self.subTest(encoded):\n                c = parse_cookie(encoded)\n                self.assertEqual(c[\"a\"], decoded)\n\n    @skipIfEmulated\n    def test_unquote_large(self):\n        # Adapted from\n        # https://github.com/python/cpython/blob/dc7a2b6522ec7af41282bc34f405bee9b306d611/Lib/test/test_http_cookies.py#L87\n        # Modified from that test because we handle semicolons differently from the stdlib.\n        #\n        # This is a performance regression test: prior to improvements in Tornado 6.4.2, this test\n        # would take over a minute with n= 100k. Now it runs in tens of milliseconds.\n        n = 100000\n        for encoded in r\"\\\\\", r\"\\134\":\n            with self.subTest(encoded):\n                start = time.time()\n                data = 'a=\"b=' + encoded * n + '\"'\n                value = parse_cookie(data)[\"a\"]\n                end = time.time()\n                self.assertEqual(value[:3], \"b=\\\\\")\n                self.assertEqual(value[-3:], \"\\\\\\\\\\\\\")\n                self.assertEqual(len(value), n + 2)\n\n                # Very loose performance check to avoid false positives\n                self.assertLess(end - start, 1, \"Test took too long\")\n"
  },
  {
    "path": "tornado/test/import_test.py",
    "content": "import subprocess\nimport sys\nimport unittest\n\n_import_everything = b\"\"\"\n# The event loop is not fork-safe, and it's easy to initialize an asyncio.Future\n# at startup, which in turn creates the default event loop and prevents forking.\n# Explicitly disallow the default event loop so that an error will be raised\n# if something tries to touch it.\nimport asyncio\nimport warnings\nwith warnings.catch_warnings():\n    warnings.simplefilter(\"ignore\", DeprecationWarning)\n    asyncio.set_event_loop(None)\n\nimport importlib\nimport tornado\n\nfor mod in tornado.__all__:\n    if mod == \"curl_httpclient\":\n        # This module has extra dependencies; skip it if they're not installed.\n        try:\n            import pycurl\n        except ImportError:\n            continue\n    importlib.import_module(f\"tornado.{mod}\")\n\"\"\"\n\n_import_lazy = b\"\"\"\nimport sys\nimport tornado\n\nif \"tornado.web\" in sys.modules:\n    raise Exception(\"unexpected eager import\")\n\n# Trigger a lazy import by referring to something in a submodule.\ntornado.web.RequestHandler\n\nif \"tornado.web\" not in sys.modules:\n    raise Exception(\"lazy import did not update sys.modules\")\n\"\"\"\n\n\nclass ImportTest(unittest.TestCase):\n    def test_import_everything(self):\n        # Test that all Tornado modules can be imported without side effects,\n        # specifically without initializing the default asyncio event loop.\n        # Since we can't tell which modules may have already beein imported\n        # in our process, do it in a subprocess for a clean slate.\n        proc = subprocess.Popen([sys.executable], stdin=subprocess.PIPE)\n        proc.communicate(_import_everything)\n        self.assertEqual(proc.returncode, 0)\n\n    def test_lazy_import(self):\n        # Test that submodules can be referenced lazily after \"import tornado\"\n        proc = subprocess.Popen([sys.executable], stdin=subprocess.PIPE)\n        proc.communicate(_import_lazy)\n        self.assertEqual(proc.returncode, 0)\n\n    def test_import_aliases(self):\n        # Ensure we don't delete formerly-documented aliases accidentally.\n        import asyncio\n\n        import tornado\n\n        self.assertIs(tornado.ioloop.TimeoutError, tornado.util.TimeoutError)\n        self.assertIs(tornado.gen.TimeoutError, tornado.util.TimeoutError)\n        self.assertIs(tornado.util.TimeoutError, asyncio.TimeoutError)\n"
  },
  {
    "path": "tornado/test/ioloop_test.py",
    "content": "import asyncio\nimport contextlib\nimport datetime\nimport functools\nimport socket\nimport subprocess\nimport sys\nimport threading\nimport time\nimport types\nimport unittest\nfrom collections.abc import Generator\nfrom concurrent import futures\nfrom concurrent.futures import ThreadPoolExecutor\nfrom unittest import mock\n\nfrom tornado import gen\nfrom tornado.concurrent import Future\nfrom tornado.escape import native_str\nfrom tornado.ioloop import IOLoop, PeriodicCallback, TimeoutError\nfrom tornado.log import app_log\nfrom tornado.test.util import (\n    ignore_deprecation,\n    skipIfNonUnix,\n)\nfrom tornado.testing import (\n    AsyncTestCase,\n    ExpectLog,\n    bind_unused_port,\n    gen_test,\n    setup_with_context_manager,\n)\n\n\nclass TestIOLoop(AsyncTestCase):\n    def test_add_callback_return_sequence(self):\n        # A callback returning {} or [] shouldn't spin the CPU, see Issue #1803.\n        self.calls = 0\n\n        loop = self.io_loop\n        test = self\n        old_add_callback = loop.add_callback\n\n        def add_callback(self, callback, *args, **kwargs):\n            test.calls += 1\n            old_add_callback(callback, *args, **kwargs)\n\n        loop.add_callback = types.MethodType(add_callback, loop)  # type: ignore\n        loop.add_callback(lambda: {})  # type: ignore\n        loop.add_callback(lambda: [])  # type: ignore\n        loop.add_timeout(datetime.timedelta(milliseconds=50), loop.stop)\n        loop.start()\n        self.assertLess(self.calls, 10)\n\n    def test_add_callback_wakeup(self):\n        # Make sure that add_callback from inside a running IOLoop\n        # wakes up the IOLoop immediately instead of waiting for a timeout.\n        def callback():\n            self.called = True\n            self.stop()\n\n        def schedule_callback():\n            self.called = False\n            self.io_loop.add_callback(callback)\n            # Store away the time so we can check if we woke up immediately\n            self.start_time = time.time()\n\n        self.io_loop.add_timeout(self.io_loop.time(), schedule_callback)\n        self.wait()\n        self.assertAlmostEqual(time.time(), self.start_time, places=2)\n        self.assertTrue(self.called)\n\n    def test_add_callback_wakeup_other_thread(self):\n        def target():\n            # sleep a bit to let the ioloop go into its poll loop\n            time.sleep(0.01)\n            self.stop_time = time.time()\n            self.io_loop.add_callback(self.stop)\n\n        thread = threading.Thread(target=target)\n        self.io_loop.add_callback(thread.start)\n        self.wait()\n        delta = time.time() - self.stop_time\n        self.assertLess(delta, 0.1)\n        thread.join()\n\n    def test_add_timeout_timedelta(self):\n        self.io_loop.add_timeout(datetime.timedelta(microseconds=1), self.stop)\n        self.wait()\n\n    def test_multiple_add(self):\n        sock, port = bind_unused_port()\n        try:\n            self.io_loop.add_handler(\n                sock.fileno(), lambda fd, events: None, IOLoop.READ\n            )\n            # Attempting to add the same handler twice fails\n            # (with a platform-dependent exception)\n            self.assertRaises(\n                Exception,\n                self.io_loop.add_handler,\n                sock.fileno(),\n                lambda fd, events: None,\n                IOLoop.READ,\n            )\n        finally:\n            self.io_loop.remove_handler(sock.fileno())\n            sock.close()\n\n    def test_remove_without_add(self):\n        # remove_handler should not throw an exception if called on an fd\n        # was never added.\n        sock, port = bind_unused_port()\n        try:\n            self.io_loop.remove_handler(sock.fileno())\n        finally:\n            sock.close()\n\n    def test_add_callback_from_signal(self):\n        # cheat a little bit and just run this normally, since we can't\n        # easily simulate the races that happen with real signal handlers\n        with ignore_deprecation():\n            self.io_loop.add_callback_from_signal(self.stop)\n        self.wait()\n\n    def test_add_callback_from_signal_other_thread(self):\n        # Very crude test, just to make sure that we cover this case.\n        # This also happens to be the first test where we run an IOLoop in\n        # a non-main thread.\n        other_ioloop = IOLoop(make_current=False)\n        thread = threading.Thread(target=other_ioloop.start)\n        thread.start()\n        with ignore_deprecation():\n            other_ioloop.add_callback_from_signal(other_ioloop.stop)\n        thread.join()\n        other_ioloop.close()\n\n    def test_add_callback_while_closing(self):\n        # add_callback should not fail if it races with another thread\n        # closing the IOLoop. The callbacks are dropped silently\n        # without executing.\n        closing = threading.Event()\n\n        def target():\n            other_ioloop.add_callback(other_ioloop.stop)\n            other_ioloop.start()\n            closing.set()\n            other_ioloop.close(all_fds=True)\n\n        other_ioloop = IOLoop(make_current=False)\n        thread = threading.Thread(target=target)\n        thread.start()\n        closing.wait()\n        for i in range(1000):\n            other_ioloop.add_callback(lambda: None)\n\n    @skipIfNonUnix  # just because socketpair is so convenient\n    def test_read_while_writeable(self):\n        # Ensure that write events don't come in while we're waiting for\n        # a read and haven't asked for writeability. (the reverse is\n        # difficult to test for)\n        client, server = socket.socketpair()\n        try:\n\n            def handler(fd, events):\n                self.assertEqual(events, IOLoop.READ)\n                self.stop()\n\n            self.io_loop.add_handler(client.fileno(), handler, IOLoop.READ)\n            self.io_loop.add_timeout(\n                self.io_loop.time() + 0.01, functools.partial(server.send, b\"asdf\")\n            )\n            self.wait()\n            self.io_loop.remove_handler(client.fileno())\n        finally:\n            client.close()\n            server.close()\n\n    def test_remove_timeout_after_fire(self):\n        # It is not an error to call remove_timeout after it has run.\n        handle = self.io_loop.add_timeout(self.io_loop.time(), self.stop)\n        self.wait()\n        self.io_loop.remove_timeout(handle)\n\n    def test_remove_timeout_cleanup(self):\n        # Add and remove enough callbacks to trigger cleanup.\n        # Not a very thorough test, but it ensures that the cleanup code\n        # gets executed and doesn't blow up.  This test is only really useful\n        # on PollIOLoop subclasses, but it should run silently on any\n        # implementation.\n        for i in range(2000):\n            timeout = self.io_loop.add_timeout(self.io_loop.time() + 3600, lambda: None)\n            self.io_loop.remove_timeout(timeout)\n        # HACK: wait two IOLoop iterations for the GC to happen.\n        self.io_loop.add_callback(lambda: self.io_loop.add_callback(self.stop))\n        self.wait()\n\n    def test_remove_timeout_from_timeout(self):\n        calls = [False, False]\n\n        # Schedule several callbacks and wait for them all to come due at once.\n        # t2 should be cancelled by t1, even though it is already scheduled to\n        # be run before the ioloop even looks at it.\n        now = self.io_loop.time()\n\n        def t1():\n            calls[0] = True\n            self.io_loop.remove_timeout(t2_handle)\n\n        self.io_loop.add_timeout(now + 0.01, t1)\n\n        def t2():\n            calls[1] = True\n\n        t2_handle = self.io_loop.add_timeout(now + 0.02, t2)\n        self.io_loop.add_timeout(now + 0.03, self.stop)\n        time.sleep(0.03)\n        self.wait()\n        self.assertEqual(calls, [True, False])\n\n    def test_timeout_with_arguments(self):\n        # This tests that all the timeout methods pass through *args correctly.\n        results: list[int] = []\n        self.io_loop.add_timeout(self.io_loop.time(), results.append, 1)\n        self.io_loop.add_timeout(datetime.timedelta(seconds=0), results.append, 2)\n        self.io_loop.call_at(self.io_loop.time(), results.append, 3)\n        self.io_loop.call_later(0, results.append, 4)\n        self.io_loop.call_later(0, self.stop)\n        self.wait()\n        # The asyncio event loop does not guarantee the order of these\n        # callbacks.\n        self.assertEqual(sorted(results), [1, 2, 3, 4])\n\n    def test_add_timeout_return(self):\n        # All the timeout methods return non-None handles that can be\n        # passed to remove_timeout.\n        handle = self.io_loop.add_timeout(self.io_loop.time(), lambda: None)\n        self.assertIsNotNone(handle)\n        self.io_loop.remove_timeout(handle)\n\n    def test_call_at_return(self):\n        handle = self.io_loop.call_at(self.io_loop.time(), lambda: None)\n        self.assertIsNotNone(handle)\n        self.io_loop.remove_timeout(handle)\n\n    def test_call_later_return(self):\n        handle = self.io_loop.call_later(0, lambda: None)\n        self.assertIsNotNone(handle)\n        self.io_loop.remove_timeout(handle)\n\n    def test_close_file_object(self):\n        \"\"\"When a file object is used instead of a numeric file descriptor,\n        the object should be closed (by IOLoop.close(all_fds=True),\n        not just the fd.\n        \"\"\"\n\n        # Use a socket since they are supported by IOLoop on all platforms.\n        # Unfortunately, sockets don't support the .closed attribute for\n        # inspecting their close status, so we must use a wrapper.\n        class SocketWrapper:\n            def __init__(self, sockobj):\n                self.sockobj = sockobj\n                self.closed = False\n\n            def fileno(self):\n                return self.sockobj.fileno()\n\n            def close(self):\n                self.closed = True\n                self.sockobj.close()\n\n        sockobj, port = bind_unused_port()\n        socket_wrapper = SocketWrapper(sockobj)\n        io_loop = IOLoop(make_current=False)\n        io_loop.run_sync(\n            lambda: io_loop.add_handler(\n                socket_wrapper, lambda fd, events: None, IOLoop.READ\n            )\n        )\n        io_loop.close(all_fds=True)\n        self.assertTrue(socket_wrapper.closed)\n\n    def test_handler_callback_file_object(self):\n        \"\"\"The handler callback receives the same fd object it passed in.\"\"\"\n        server_sock, port = bind_unused_port()\n        fds = []\n\n        def handle_connection(fd, events):\n            fds.append(fd)\n            conn, addr = server_sock.accept()\n            conn.close()\n            self.stop()\n\n        self.io_loop.add_handler(server_sock, handle_connection, IOLoop.READ)\n        with contextlib.closing(socket.socket()) as client_sock:\n            client_sock.connect((\"127.0.0.1\", port))\n            self.wait()\n        self.io_loop.remove_handler(server_sock)\n        self.io_loop.add_handler(server_sock.fileno(), handle_connection, IOLoop.READ)\n        with contextlib.closing(socket.socket()) as client_sock:\n            client_sock.connect((\"127.0.0.1\", port))\n            self.wait()\n        self.assertIs(fds[0], server_sock)\n        self.assertEqual(fds[1], server_sock.fileno())\n        self.io_loop.remove_handler(server_sock.fileno())\n        server_sock.close()\n\n    def test_mixed_fd_fileobj(self):\n        server_sock, port = bind_unused_port()\n\n        def f(fd, events):\n            pass\n\n        self.io_loop.add_handler(server_sock, f, IOLoop.READ)\n        with self.assertRaises(Exception):\n            # The exact error is unspecified - some implementations use\n            # IOError, others use ValueError.\n            self.io_loop.add_handler(server_sock.fileno(), f, IOLoop.READ)\n        self.io_loop.remove_handler(server_sock.fileno())\n        server_sock.close()\n\n    def test_reentrant(self):\n        \"\"\"Calling start() twice should raise an error, not deadlock.\"\"\"\n        returned_from_start = [False]\n        got_exception = [False]\n\n        def callback():\n            try:\n                self.io_loop.start()\n                returned_from_start[0] = True\n            except Exception:\n                got_exception[0] = True\n            self.stop()\n\n        self.io_loop.add_callback(callback)\n        self.wait()\n        self.assertTrue(got_exception[0])\n        self.assertFalse(returned_from_start[0])\n\n    def test_exception_logging(self):\n        \"\"\"Uncaught exceptions get logged by the IOLoop.\"\"\"\n        self.io_loop.add_callback(lambda: 1 / 0)\n        self.io_loop.add_callback(self.stop)\n        with ExpectLog(app_log, \"Exception in callback\"):\n            self.wait()\n\n    def test_exception_logging_future(self):\n        \"\"\"The IOLoop examines exceptions from Futures and logs them.\"\"\"\n\n        @gen.coroutine\n        def callback():\n            self.io_loop.add_callback(self.stop)\n            1 / 0\n\n        self.io_loop.add_callback(callback)\n        with ExpectLog(app_log, \"Exception in callback\"):\n            self.wait()\n\n    def test_exception_logging_native_coro(self):\n        \"\"\"The IOLoop examines exceptions from awaitables and logs them.\"\"\"\n\n        async def callback():\n            # Stop the IOLoop two iterations after raising an exception\n            # to give the exception time to be logged.\n            self.io_loop.add_callback(self.io_loop.add_callback, self.stop)\n            1 / 0\n\n        self.io_loop.add_callback(callback)\n        with ExpectLog(app_log, \"Exception in callback\"):\n            self.wait()\n\n    def test_spawn_callback(self):\n        # Both add_callback and spawn_callback run directly on the IOLoop,\n        # so their errors are logged without stopping the test.\n        self.io_loop.add_callback(lambda: 1 / 0)\n        self.io_loop.add_callback(self.stop)\n        with ExpectLog(app_log, \"Exception in callback\"):\n            self.wait()\n        # A spawned callback is run directly on the IOLoop, so it will be\n        # logged without stopping the test.\n        self.io_loop.spawn_callback(lambda: 1 / 0)\n        self.io_loop.add_callback(self.stop)\n        with ExpectLog(app_log, \"Exception in callback\"):\n            self.wait()\n\n    @skipIfNonUnix\n    def test_remove_handler_from_handler(self):\n        # Create two sockets with simultaneous read events.\n        client, server = socket.socketpair()\n        try:\n            client.send(b\"abc\")\n            server.send(b\"abc\")\n\n            # After reading from one fd, remove the other from the IOLoop.\n            chunks = []\n\n            def handle_read(fd, events):\n                chunks.append(fd.recv(1024))\n                if fd is client:\n                    self.io_loop.remove_handler(server)\n                else:\n                    self.io_loop.remove_handler(client)\n\n            self.io_loop.add_handler(client, handle_read, self.io_loop.READ)\n            self.io_loop.add_handler(server, handle_read, self.io_loop.READ)\n            self.io_loop.call_later(0.1, self.stop)\n            self.wait()\n\n            # Only one fd was read; the other was cleanly removed.\n            self.assertEqual(chunks, [b\"abc\"])\n        finally:\n            client.close()\n            server.close()\n\n    @skipIfNonUnix\n    @gen_test\n    def test_init_close_race(self):\n        # Regression test for #2367\n        #\n        # Skipped on windows because of what looks like a bug in the\n        # proactor event loop when started and stopped on non-main\n        # threads.\n        def f():\n            for i in range(10):\n                loop = IOLoop(make_current=False)\n                loop.close()\n\n        yield gen.multi([self.io_loop.run_in_executor(None, f) for i in range(2)])\n\n    def test_explicit_asyncio_loop(self):\n        asyncio_loop = asyncio.new_event_loop()\n        loop = IOLoop(asyncio_loop=asyncio_loop, make_current=False)\n        assert loop.asyncio_loop is asyncio_loop  # type: ignore\n        with self.assertRaises(RuntimeError):\n            # Can't register two IOLoops with the same asyncio_loop\n            IOLoop(asyncio_loop=asyncio_loop, make_current=False)\n        loop.close()\n\n\n# Deliberately not a subclass of AsyncTestCase so the IOLoop isn't\n# automatically set as current.\nclass TestIOLoopCurrent(unittest.TestCase):\n    def setUp(self):\n        setup_with_context_manager(self, ignore_deprecation())\n        self.io_loop: IOLoop | None = None\n        IOLoop.clear_current()\n\n    def tearDown(self):\n        if self.io_loop is not None:\n            self.io_loop.close()\n\n    def test_non_current(self):\n        self.io_loop = IOLoop(make_current=False)\n        # The new IOLoop is not initially made current.\n        self.assertIsNone(IOLoop.current(instance=False))\n        # Starting the IOLoop makes it current, and stopping the loop\n        # makes it non-current. This process is repeatable.\n        for i in range(3):\n\n            def f():\n                self.current_io_loop = IOLoop.current()\n                assert self.io_loop is not None\n                self.io_loop.stop()\n\n            self.io_loop.add_callback(f)\n            self.io_loop.start()\n            self.assertIs(self.current_io_loop, self.io_loop)\n            # Now that the loop is stopped, it is no longer current.\n            self.assertIsNone(IOLoop.current(instance=False))\n\n    def test_force_current(self):\n        self.io_loop = IOLoop(make_current=True)\n        self.assertIs(self.io_loop, IOLoop.current())\n\n\nclass TestIOLoopCurrentAsync(AsyncTestCase):\n    def setUp(self):\n        super().setUp()\n        setup_with_context_manager(self, ignore_deprecation())\n\n    @gen_test\n    def test_clear_without_current(self):\n        # If there is no current IOLoop, clear_current is a no-op (but\n        # should not fail). Use a thread so we see the threading.Local\n        # in a pristine state.\n        with ThreadPoolExecutor(1) as e:\n            yield e.submit(IOLoop.clear_current)\n\n\nclass TestIOLoopFutures(AsyncTestCase):\n    def test_add_future_threads(self):\n        with futures.ThreadPoolExecutor(1) as pool:\n\n            def dummy():\n                pass\n\n            self.io_loop.add_future(\n                pool.submit(dummy), lambda future: self.stop(future)\n            )\n            future = self.wait()\n            self.assertTrue(future.done())\n            self.assertIsNone(future.result())\n\n    @gen_test\n    def test_run_in_executor_gen(self):\n        event1 = threading.Event()\n        event2 = threading.Event()\n\n        def sync_func(self_event, other_event):\n            self_event.set()\n            other_event.wait()\n            # Note that return value doesn't actually do anything,\n            # it is just passed through to our final assertion to\n            # make sure it is passed through properly.\n            return self_event\n\n        # Run two synchronous functions, which would deadlock if not\n        # run in parallel.\n        res = yield [\n            IOLoop.current().run_in_executor(None, sync_func, event1, event2),\n            IOLoop.current().run_in_executor(None, sync_func, event2, event1),\n        ]\n\n        self.assertEqual([event1, event2], res)\n\n    @gen_test\n    def test_run_in_executor_native(self):\n        event1 = threading.Event()\n        event2 = threading.Event()\n\n        def sync_func(self_event, other_event):\n            self_event.set()\n            other_event.wait()\n            return self_event\n\n        # Go through an async wrapper to ensure that the result of\n        # run_in_executor works with await and not just gen.coroutine\n        # (simply passing the underlying concurrent future would do that).\n        async def async_wrapper(self_event, other_event):\n            return await IOLoop.current().run_in_executor(\n                None, sync_func, self_event, other_event\n            )\n\n        res = yield [async_wrapper(event1, event2), async_wrapper(event2, event1)]\n\n        self.assertEqual([event1, event2], res)\n\n    @gen_test\n    def test_set_default_executor(self):\n        count = [0]\n\n        class MyExecutor(futures.ThreadPoolExecutor):\n            def submit(self, func, *args):  # type: ignore[override]\n                count[0] += 1\n                return super().submit(func, *args)\n\n        event = threading.Event()\n\n        def sync_func():\n            event.set()\n\n        executor = MyExecutor(1)\n        loop = IOLoop.current()\n        loop.set_default_executor(executor)\n        yield loop.run_in_executor(None, sync_func)\n        self.assertEqual(1, count[0])\n        self.assertTrue(event.is_set())\n\n\nclass TestIOLoopRunSync(unittest.TestCase):\n    def setUp(self):\n        self.io_loop = IOLoop(make_current=False)\n\n    def tearDown(self):\n        self.io_loop.close()\n\n    def test_sync_result(self):\n        with self.assertRaises(gen.BadYieldError):\n            self.io_loop.run_sync(lambda: 42)\n\n    def test_sync_exception(self):\n        with self.assertRaises(ZeroDivisionError):\n            self.io_loop.run_sync(lambda: 1 / 0)\n\n    def test_async_result(self):\n        @gen.coroutine\n        def f():\n            yield gen.moment\n            raise gen.Return(42)\n\n        self.assertEqual(self.io_loop.run_sync(f), 42)\n\n    def test_async_exception(self):\n        @gen.coroutine\n        def f():\n            yield gen.moment\n            1 / 0\n\n        with self.assertRaises(ZeroDivisionError):\n            self.io_loop.run_sync(f)\n\n    def test_current(self):\n        def f():\n            self.assertIs(IOLoop.current(), self.io_loop)\n\n        self.io_loop.run_sync(f)\n\n    def test_timeout(self):\n        @gen.coroutine\n        def f():\n            yield gen.sleep(1)\n\n        self.assertRaises(TimeoutError, self.io_loop.run_sync, f, timeout=0.01)\n\n    def test_native_coroutine(self):\n        @gen.coroutine\n        def f1():\n            yield gen.moment\n\n        async def f2():\n            await f1()\n\n        self.io_loop.run_sync(f2)\n\n    def test_stop_no_timeout(self):\n        async def f():\n            await asyncio.sleep(0.1)\n            IOLoop.current().stop()\n            await asyncio.sleep(10)\n\n        with self.assertRaises(RuntimeError) as cm:\n            self.io_loop.run_sync(f)\n        assert \"Event loop stopped\" in str(cm.exception)\n\n\nclass TestPeriodicCallbackMath(unittest.TestCase):\n    def simulate_calls(self, pc, durations):\n        \"\"\"Simulate a series of calls to the PeriodicCallback.\n\n        Pass a list of call durations in seconds (negative values\n        work to simulate clock adjustments during the call, or more or\n        less equivalently, between calls). This method returns the\n        times at which each call would be made.\n        \"\"\"\n        calls = []\n        now = 1000\n        pc._next_timeout = now\n        for d in durations:\n            pc._update_next(now)\n            calls.append(pc._next_timeout)\n            now = pc._next_timeout + d\n        return calls\n\n    def dummy(self):\n        pass\n\n    def test_basic(self):\n        pc = PeriodicCallback(self.dummy, 10000)\n        self.assertEqual(\n            self.simulate_calls(pc, [0] * 5), [1010, 1020, 1030, 1040, 1050]\n        )\n\n    def test_overrun(self):\n        # If a call runs for too long, we skip entire cycles to get\n        # back on schedule.\n        call_durations = [9, 9, 10, 11, 20, 20, 35, 35, 0, 0, 0]\n        expected = [\n            1010,\n            1020,\n            1030,  # first 3 calls on schedule\n            1050,\n            1070,  # next 2 delayed one cycle\n            1100,\n            1130,  # next 2 delayed 2 cycles\n            1170,\n            1210,  # next 2 delayed 3 cycles\n            1220,\n            1230,  # then back on schedule.\n        ]\n\n        pc = PeriodicCallback(self.dummy, 10000)\n        self.assertEqual(self.simulate_calls(pc, call_durations), expected)\n\n    def test_clock_backwards(self):\n        pc = PeriodicCallback(self.dummy, 10000)\n        # Backwards jumps are ignored, potentially resulting in a\n        # slightly slow schedule (although we assume that when\n        # time.time() and time.monotonic() are different, time.time()\n        # is getting adjusted by NTP and is therefore more accurate)\n        self.assertEqual(\n            self.simulate_calls(pc, [-2, -1, -3, -2, 0]), [1010, 1020, 1030, 1040, 1050]\n        )\n\n        # For big jumps, we should perhaps alter the schedule, but we\n        # don't currently. This trace shows that we run callbacks\n        # every 10s of time.time(), but the first and second calls are\n        # 110s of real time apart because the backwards jump is\n        # ignored.\n        self.assertEqual(self.simulate_calls(pc, [-100, 0, 0]), [1010, 1020, 1030])\n\n    def test_jitter(self):\n        random_times = [0.5, 1, 0, 0.75]\n        expected = [1010, 1022.5, 1030, 1041.25]\n        call_durations = [0] * len(random_times)\n        pc = PeriodicCallback(self.dummy, 10000, jitter=0.5)\n\n        def mock_random():\n            return random_times.pop(0)\n\n        with mock.patch(\"random.random\", mock_random):\n            self.assertEqual(self.simulate_calls(pc, call_durations), expected)\n\n    def test_timedelta(self):\n        pc = PeriodicCallback(lambda: None, datetime.timedelta(minutes=1, seconds=23))\n        expected_callback_time = 83000\n        self.assertEqual(pc.callback_time, expected_callback_time)\n\n\nclass TestPeriodicCallbackAsync(AsyncTestCase):\n    def test_periodic_plain(self):\n        count = 0\n\n        def callback() -> None:\n            nonlocal count\n            count += 1\n            if count == 3:\n                self.stop()\n\n        pc = PeriodicCallback(callback, 10)\n        pc.start()\n        self.wait()\n        pc.stop()\n        self.assertEqual(count, 3)\n\n    def test_periodic_coro(self) -> None:\n        counts = [0, 0]\n\n        @gen.coroutine\n        def callback() -> \"Generator[Future[None], object, None]\":\n            counts[0] += 1\n            yield gen.sleep(0.025)\n            counts[1] += 1\n            if counts[1] == 3:\n                pc.stop()\n                self.io_loop.add_callback(self.stop)\n\n        pc = PeriodicCallback(callback, 10)\n        pc.start()\n        self.wait()\n        self.assertEqual(counts[0], 3)\n        self.assertEqual(counts[1], 3)\n\n    def test_periodic_async(self) -> None:\n        counts = [0, 0]\n\n        async def callback() -> None:\n            counts[0] += 1\n            await gen.sleep(0.025)\n            counts[1] += 1\n            if counts[1] == 3:\n                pc.stop()\n                self.io_loop.add_callback(self.stop)\n\n        pc = PeriodicCallback(callback, 10)\n        pc.start()\n        self.wait()\n        self.assertEqual(counts[0], 3)\n        self.assertEqual(counts[1], 3)\n\n\nclass TestIOLoopConfiguration(unittest.TestCase):\n    def run_python(self, *statements):\n        stmt_list = [\n            \"from tornado.ioloop import IOLoop\",\n            \"classname = lambda x: x.__class__.__name__\",\n        ] + list(statements)\n        args = [sys.executable, \"-c\", \"; \".join(stmt_list)]\n        return native_str(subprocess.check_output(args)).strip()\n\n    def test_default(self):\n        # When asyncio is available, it is used by default.\n        cls = self.run_python(\"print(classname(IOLoop.current()))\")\n        self.assertEqual(cls, \"AsyncIOMainLoop\")\n        cls = self.run_python(\"print(classname(IOLoop()))\")\n        self.assertEqual(cls, \"AsyncIOLoop\")\n\n    def test_asyncio(self):\n        cls = self.run_python(\n            'IOLoop.configure(\"tornado.platform.asyncio.AsyncIOLoop\")',\n            \"print(classname(IOLoop.current()))\",\n        )\n        self.assertEqual(cls, \"AsyncIOMainLoop\")\n\n    @unittest.skipIf(\n        sys.version_info >= (3, 14), \"implicit event loop creation not available\"\n    )\n    def test_asyncio_main(self):\n        cls = self.run_python(\n            \"from tornado.platform.asyncio import AsyncIOMainLoop\",\n            \"AsyncIOMainLoop().install()\",\n            \"print(classname(IOLoop.current()))\",\n        )\n        self.assertEqual(cls, \"AsyncIOMainLoop\")\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tornado/test/iostream_test.py",
    "content": "import asyncio\nimport errno\nimport hashlib\nimport logging\nimport os\nimport platform\nimport random\nimport socket\nimport ssl\nimport typing\nimport unittest\nfrom unittest import mock\n\nfrom tornado import gen, netutil\nfrom tornado.concurrent import Future\nfrom tornado.httpclient import AsyncHTTPClient, HTTPResponse\nfrom tornado.httputil import HTTPHeaders\nfrom tornado.ioloop import IOLoop\nfrom tornado.iostream import (\n    IOStream,\n    PipeIOStream,\n    SSLIOStream,\n    StreamClosedError,\n    _StreamBuffer,\n)\nfrom tornado.locks import Condition, Event\nfrom tornado.log import gen_log\nfrom tornado.netutil import ssl_options_to_context, ssl_wrap_socket\nfrom tornado.platform.asyncio import AddThreadSelectorEventLoop\nfrom tornado.tcpserver import TCPServer\nfrom tornado.test.util import (\n    abstract_base_test,\n    ignore_deprecation,\n    refusing_port,\n    skipIfNonUnix,\n)\nfrom tornado.testing import (\n    AsyncHTTPSTestCase,\n    AsyncHTTPTestCase,\n    AsyncTestCase,\n    ExpectLog,\n    bind_unused_port,\n    gen_test,\n)\nfrom tornado.web import Application, RequestHandler\n\n\ndef _server_ssl_options():\n    return dict(\n        certfile=os.path.join(os.path.dirname(__file__), \"test.crt\"),\n        keyfile=os.path.join(os.path.dirname(__file__), \"test.key\"),\n    )\n\n\nclass HelloHandler(RequestHandler):\n    def get(self):\n        self.write(\"Hello\")\n\n\n@abstract_base_test\nclass TestIOStreamWebMixin(AsyncTestCase):\n    # We want to run these tests with both AsyncHTTPTestCase and AsyncHTTPSTestCase,\n    # but this leads to some tricky inheritance situations. We want this class's\n    # get_app, but the test classes's get_http_port and fetch. There's no way to make\n    # the method resolution order to do what we want in all cases, so the current\n    # state is that that AsyncHTTP(S)TestCase must be the first base class of the\n    # final class, and that class must define a get_app method that calls mixin_get_app.\n    #\n    # Alternatives include defining this class in a factory that can change the base class\n    # or refactoring to use composition instead of inheritance for the http components.\n    def _make_client_iostream(self):\n        raise NotImplementedError()\n\n    def mixin_get_app(self):\n        return Application([(\"/\", HelloHandler)])\n\n    def get_http_port(self) -> int:\n        raise NotImplementedError()\n\n    def fetch(\n        self, path: str, raise_error: bool = False, **kwargs: typing.Any\n    ) -> HTTPResponse:\n        # To be filled in by mixing in AsyncHTTPTestCase or AsyncHTTPSTestCase\n        raise NotImplementedError()\n\n    def test_connection_closed(self):\n        # When a server sends a response and then closes the connection,\n        # the client must be allowed to read the data before the IOStream\n        # closes itself.  Epoll reports closed connections with a separate\n        # EPOLLRDHUP event delivered at the same time as the read event,\n        # while kqueue reports them as a second read/write event with an EOF\n        # flag.\n        if (\n            AsyncHTTPClient.configured_class().__name__.endswith(\"CurlAsyncHTTPClient\")\n            and platform.system() == \"Darwin\"\n        ):\n            # It's possible that this is Tornado's fault, either in AsyncIOLoop or in\n            # CurlAsyncHTTPClient, but we've also seen this kind of issue in libcurl itself\n            # (especially a long time ago). The error is tied to the use of Apple's\n            # SecureTransport instead of OpenSSL.\n            self.skipTest(\"libcurl doesn't handle closed connections cleanly on macOS\")\n        response = self.fetch(\"/\", headers={\"Connection\": \"close\"})\n        response.rethrow()\n\n    @gen_test\n    def test_read_until_close(self):\n        stream = self._make_client_iostream()\n        yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n        stream.write(b\"GET / HTTP/1.0\\r\\n\\r\\n\")\n\n        data = yield stream.read_until_close()\n        self.assertTrue(data.startswith(b\"HTTP/1.1 200\"))\n        self.assertTrue(data.endswith(b\"Hello\"))\n\n    @gen_test\n    def test_read_zero_bytes(self):\n        self.stream = self._make_client_iostream()\n        yield self.stream.connect((\"127.0.0.1\", self.get_http_port()))\n        self.stream.write(b\"GET / HTTP/1.0\\r\\n\\r\\n\")\n\n        # normal read\n        data = yield self.stream.read_bytes(9)\n        self.assertEqual(data, b\"HTTP/1.1 \")\n\n        # zero bytes\n        data = yield self.stream.read_bytes(0)\n        self.assertEqual(data, b\"\")\n\n        # another normal read\n        data = yield self.stream.read_bytes(3)\n        self.assertEqual(data, b\"200\")\n\n        self.stream.close()\n\n    @gen_test\n    def test_write_while_connecting(self):\n        stream = self._make_client_iostream()\n        connect_fut = stream.connect((\"127.0.0.1\", self.get_http_port()))\n        # unlike the previous tests, try to write before the connection\n        # is complete.\n        write_fut = stream.write(b\"GET / HTTP/1.0\\r\\nConnection: close\\r\\n\\r\\n\")\n        self.assertFalse(connect_fut.done())\n\n        # connect will always complete before write.\n        it = gen.WaitIterator(connect_fut, write_fut)\n        resolved_order = []\n        while not it.done():\n            yield it.next()\n            resolved_order.append(it.current_future)\n        self.assertEqual(resolved_order, [connect_fut, write_fut])\n\n        data = yield stream.read_until_close()\n        self.assertTrue(data.endswith(b\"Hello\"))\n\n        stream.close()\n\n    @gen_test\n    def test_future_interface(self):\n        \"\"\"Basic test of IOStream's ability to return Futures.\"\"\"\n        stream = self._make_client_iostream()\n        connect_result = yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n        self.assertIs(connect_result, stream)\n        yield stream.write(b\"GET / HTTP/1.0\\r\\n\\r\\n\")\n        first_line = yield stream.read_until(b\"\\r\\n\")\n        self.assertEqual(first_line, b\"HTTP/1.1 200 OK\\r\\n\")\n        # callback=None is equivalent to no callback.\n        header_data = yield stream.read_until(b\"\\r\\n\\r\\n\")\n        headers = HTTPHeaders.parse(header_data.decode(\"latin1\"))\n        content_length = int(headers[\"Content-Length\"])\n        body = yield stream.read_bytes(content_length)\n        self.assertEqual(body, b\"Hello\")\n        stream.close()\n\n    @gen_test\n    def test_future_close_while_reading(self):\n        stream = self._make_client_iostream()\n        yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n        yield stream.write(b\"GET / HTTP/1.0\\r\\n\\r\\n\")\n        with self.assertRaises(StreamClosedError):\n            yield stream.read_bytes(1024 * 1024)\n        stream.close()\n\n    @gen_test\n    def test_future_read_until_close(self):\n        # Ensure that the data comes through before the StreamClosedError.\n        stream = self._make_client_iostream()\n        yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n        yield stream.write(b\"GET / HTTP/1.0\\r\\nConnection: close\\r\\n\\r\\n\")\n        yield stream.read_until(b\"\\r\\n\\r\\n\")\n        body = yield stream.read_until_close()\n        self.assertEqual(body, b\"Hello\")\n\n        # Nothing else to read; the error comes immediately without waiting\n        # for yield.\n        with self.assertRaises(StreamClosedError):\n            stream.read_bytes(1)\n\n\n@abstract_base_test\nclass TestReadWriteMixin(AsyncTestCase):\n    # Tests where one stream reads and the other writes.\n    # These should work for BaseIOStream implementations.\n\n    def make_iostream_pair(self, **kwargs):\n        raise NotImplementedError\n\n    def iostream_pair(self, **kwargs):\n        \"\"\"Like make_iostream_pair, but called by ``async with``.\n\n        In py37 this becomes simpler with contextlib.asynccontextmanager.\n        \"\"\"\n\n        class IOStreamPairContext:\n            def __init__(self, test, kwargs):\n                self.test = test\n                self.kwargs = kwargs\n\n            async def __aenter__(self):\n                self.pair = await self.test.make_iostream_pair(**self.kwargs)\n                return self.pair\n\n            async def __aexit__(self, typ, value, tb):\n                for s in self.pair:\n                    s.close()\n\n        return IOStreamPairContext(self, kwargs)\n\n    @gen_test\n    def test_write_zero_bytes(self):\n        # Attempting to write zero bytes should run the callback without\n        # going into an infinite loop.\n        rs, ws = yield self.make_iostream_pair()\n        yield ws.write(b\"\")\n        ws.close()\n        rs.close()\n\n    @gen_test\n    def test_future_delayed_close_callback(self):\n        # Same as test_delayed_close_callback, but with the future interface.\n        rs, ws = yield self.make_iostream_pair()\n\n        try:\n            ws.write(b\"12\")\n            chunks = []\n            chunks.append((yield rs.read_bytes(1)))\n            ws.close()\n            chunks.append((yield rs.read_bytes(1)))\n            self.assertEqual(chunks, [b\"1\", b\"2\"])\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_close_buffered_data(self):\n        # Similar to the previous test, but with data stored in the OS's\n        # socket buffers instead of the IOStream's read buffer.  Out-of-band\n        # close notifications must be delayed until all data has been\n        # drained into the IOStream buffer. (epoll used to use out-of-band\n        # close events with EPOLLRDHUP, but no longer)\n        #\n        # This depends on the read_chunk_size being smaller than the\n        # OS socket buffer, so make it small.\n        rs, ws = yield self.make_iostream_pair(read_chunk_size=256)\n        try:\n            ws.write(b\"A\" * 512)\n            data = yield rs.read_bytes(256)\n            self.assertEqual(b\"A\" * 256, data)\n            ws.close()\n            # Allow the close to propagate to the `rs` side of the\n            # connection.  Using add_callback instead of add_timeout\n            # doesn't seem to work, even with multiple iterations\n            yield gen.sleep(0.01)\n            data = yield rs.read_bytes(256)\n            self.assertEqual(b\"A\" * 256, data)\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_read_until_close_after_close(self):\n        # Similar to test_delayed_close_callback, but read_until_close takes\n        # a separate code path so test it separately.\n        rs, ws = yield self.make_iostream_pair()\n        try:\n            ws.write(b\"1234\")\n            # Read one byte to make sure the client has received the data.\n            # It won't run the close callback as long as there is more buffered\n            # data that could satisfy a later read.\n            data = yield rs.read_bytes(1)\n            ws.close()\n            self.assertEqual(data, b\"1\")\n            data = yield rs.read_until_close()\n            self.assertEqual(data, b\"234\")\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_large_read_until(self):\n        # Performance test: read_until used to have a quadratic component\n        # so a read_until of 4MB would take 8 seconds; now it takes 0.25\n        # seconds.\n        rs, ws = yield self.make_iostream_pair()\n        try:\n            # This test fails on pypy with ssl.  I think it's because\n            # pypy's gc defeats moves objects, breaking the\n            # \"frozen write buffer\" assumption.\n            if (\n                isinstance(rs, SSLIOStream)\n                and platform.python_implementation() == \"PyPy\"\n            ):\n                raise unittest.SkipTest(\"pypy gc causes problems with openssl\")\n            NUM_KB = 4096\n            for i in range(NUM_KB):\n                ws.write(b\"A\" * 1024)\n            ws.write(b\"\\r\\n\")\n            data = yield rs.read_until(b\"\\r\\n\")\n            self.assertEqual(len(data), NUM_KB * 1024 + 2)\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    async def test_read_until_with_close_after_second_packet(self):\n        # This is a regression test for a regression in Tornado 6.0\n        # (maybe 6.0.3?) reported in\n        # https://github.com/tornadoweb/tornado/issues/2717\n        #\n        # The data arrives in two chunks; the stream is closed at the\n        # same time that the second chunk is received. If the second\n        # chunk is larger than the first, it works, but when this bug\n        # existed it would fail if the second chunk were smaller than\n        # the first. This is due to the optimization that the\n        # read_until condition is only checked when the buffer doubles\n        # in size\n        async with self.iostream_pair() as (rs, ws):\n            rf = asyncio.ensure_future(rs.read_until(b\"done\"))\n            # We need to wait for the read_until to actually start. On\n            # windows that's tricky because the selector runs in\n            # another thread; sleeping is the simplest way.\n            await asyncio.sleep(0.1)\n            await ws.write(b\"x\" * 2048)\n            ws.write(b\"done\")\n            ws.close()\n            await rf\n\n    @gen_test\n    async def test_read_until_unsatisfied_after_close(self):\n        # If a stream is closed while reading, it raises\n        # StreamClosedError instead of UnsatisfiableReadError (the\n        # latter should only be raised when byte limits are reached).\n        # The particular scenario tested here comes from #2717.\n        async with self.iostream_pair() as (rs, ws):\n            rf = asyncio.ensure_future(rs.read_until(b\"done\"))\n            await ws.write(b\"x\" * 2048)\n            ws.write(b\"foo\")\n            ws.close()\n            with self.assertRaises(StreamClosedError):\n                await rf\n\n    @gen_test\n    def test_close_callback_with_pending_read(self):\n        # Regression test for a bug that was introduced in 2.3\n        # where the IOStream._close_callback would never be called\n        # if there were pending reads.\n        OK = b\"OK\\r\\n\"\n        rs, ws = yield self.make_iostream_pair()\n        event = Event()\n        rs.set_close_callback(event.set)\n        try:\n            ws.write(OK)\n            res = yield rs.read_until(b\"\\r\\n\")\n            self.assertEqual(res, OK)\n\n            ws.close()\n            rs.read_until(b\"\\r\\n\")\n            # If _close_callback (self.stop) is not called,\n            # an AssertionError: Async operation timed out after 5 seconds\n            # will be raised.\n            yield event.wait()\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_future_close_callback(self):\n        # Regression test for interaction between the Future read interfaces\n        # and IOStream._maybe_add_error_listener.\n        rs, ws = yield self.make_iostream_pair()\n        closed = [False]\n        cond = Condition()\n\n        def close_callback():\n            closed[0] = True\n            cond.notify()\n\n        rs.set_close_callback(close_callback)\n        try:\n            ws.write(b\"a\")\n            res = yield rs.read_bytes(1)\n            self.assertEqual(res, b\"a\")\n            self.assertFalse(closed[0])\n            ws.close()\n            yield cond.wait()\n            self.assertTrue(closed[0])\n        finally:\n            rs.close()\n            ws.close()\n\n    @gen_test\n    def test_write_memoryview(self):\n        rs, ws = yield self.make_iostream_pair()\n        try:\n            fut = rs.read_bytes(4)\n            ws.write(memoryview(b\"hello\"))\n            data = yield fut\n            self.assertEqual(data, b\"hell\")\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_read_bytes_partial(self):\n        rs, ws = yield self.make_iostream_pair()\n        try:\n            # Ask for more than is available with partial=True\n            fut = rs.read_bytes(50, partial=True)\n            ws.write(b\"hello\")\n            data = yield fut\n            self.assertEqual(data, b\"hello\")\n\n            # Ask for less than what is available; num_bytes is still\n            # respected.\n            fut = rs.read_bytes(3, partial=True)\n            ws.write(b\"world\")\n            data = yield fut\n            self.assertEqual(data, b\"wor\")\n\n            # Partial reads won't return an empty string, but read_bytes(0)\n            # will.\n            data = yield rs.read_bytes(0, partial=True)\n            self.assertEqual(data, b\"\")\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_read_until_max_bytes(self):\n        rs, ws = yield self.make_iostream_pair()\n        closed = Event()\n        rs.set_close_callback(closed.set)\n        try:\n            # Extra room under the limit\n            fut = rs.read_until(b\"def\", max_bytes=50)\n            ws.write(b\"abcdef\")\n            data = yield fut\n            self.assertEqual(data, b\"abcdef\")\n\n            # Just enough space\n            fut = rs.read_until(b\"def\", max_bytes=6)\n            ws.write(b\"abcdef\")\n            data = yield fut\n            self.assertEqual(data, b\"abcdef\")\n\n            # Not enough space, but we don't know it until all we can do is\n            # log a warning and close the connection.\n            with ExpectLog(gen_log, \"Unsatisfiable read\", level=logging.INFO):\n                fut = rs.read_until(b\"def\", max_bytes=5)\n                ws.write(b\"123456\")\n                yield closed.wait()\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_read_until_max_bytes_inline(self):\n        rs, ws = yield self.make_iostream_pair()\n        closed = Event()\n        rs.set_close_callback(closed.set)\n        try:\n            # Similar to the error case in the previous test, but the\n            # ws writes first so rs reads are satisfied\n            # inline.  For consistency with the out-of-line case, we\n            # do not raise the error synchronously.\n            ws.write(b\"123456\")\n            with ExpectLog(gen_log, \"Unsatisfiable read\", level=logging.INFO):\n                with self.assertRaises(StreamClosedError):\n                    yield rs.read_until(b\"def\", max_bytes=5)\n            yield closed.wait()\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_read_until_max_bytes_ignores_extra(self):\n        rs, ws = yield self.make_iostream_pair()\n        closed = Event()\n        rs.set_close_callback(closed.set)\n        try:\n            # Even though data that matches arrives the same packet that\n            # puts us over the limit, we fail the request because it was not\n            # found within the limit.\n            ws.write(b\"abcdef\")\n            with ExpectLog(gen_log, \"Unsatisfiable read\", level=logging.INFO):\n                rs.read_until(b\"def\", max_bytes=5)\n                yield closed.wait()\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_read_until_regex_max_bytes(self):\n        rs, ws = yield self.make_iostream_pair()\n        closed = Event()\n        rs.set_close_callback(closed.set)\n        try:\n            # Extra room under the limit\n            fut = rs.read_until_regex(b\"def\", max_bytes=50)\n            ws.write(b\"abcdef\")\n            data = yield fut\n            self.assertEqual(data, b\"abcdef\")\n\n            # Just enough space\n            fut = rs.read_until_regex(b\"def\", max_bytes=6)\n            ws.write(b\"abcdef\")\n            data = yield fut\n            self.assertEqual(data, b\"abcdef\")\n\n            # Not enough space, but we don't know it until all we can do is\n            # log a warning and close the connection.\n            with ExpectLog(gen_log, \"Unsatisfiable read\", level=logging.INFO):\n                rs.read_until_regex(b\"def\", max_bytes=5)\n                ws.write(b\"123456\")\n                yield closed.wait()\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_read_until_regex_max_bytes_inline(self):\n        rs, ws = yield self.make_iostream_pair()\n        closed = Event()\n        rs.set_close_callback(closed.set)\n        try:\n            # Similar to the error case in the previous test, but the\n            # ws writes first so rs reads are satisfied\n            # inline.  For consistency with the out-of-line case, we\n            # do not raise the error synchronously.\n            ws.write(b\"123456\")\n            with ExpectLog(gen_log, \"Unsatisfiable read\", level=logging.INFO):\n                rs.read_until_regex(b\"def\", max_bytes=5)\n                yield closed.wait()\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_read_until_regex_max_bytes_ignores_extra(self):\n        rs, ws = yield self.make_iostream_pair()\n        closed = Event()\n        rs.set_close_callback(closed.set)\n        try:\n            # Even though data that matches arrives the same packet that\n            # puts us over the limit, we fail the request because it was not\n            # found within the limit.\n            ws.write(b\"abcdef\")\n            with ExpectLog(gen_log, \"Unsatisfiable read\", level=logging.INFO):\n                rs.read_until_regex(b\"def\", max_bytes=5)\n                yield closed.wait()\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_small_reads_from_large_buffer(self):\n        # 10KB buffer size, 100KB available to read.\n        # Read 1KB at a time and make sure that the buffer is not eagerly\n        # filled.\n        rs, ws = yield self.make_iostream_pair(max_buffer_size=10 * 1024)\n        try:\n            ws.write(b\"a\" * 1024 * 100)\n            for i in range(100):\n                data = yield rs.read_bytes(1024)\n                self.assertEqual(data, b\"a\" * 1024)\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_small_read_untils_from_large_buffer(self):\n        # 10KB buffer size, 100KB available to read.\n        # Read 1KB at a time and make sure that the buffer is not eagerly\n        # filled.\n        rs, ws = yield self.make_iostream_pair(max_buffer_size=10 * 1024)\n        try:\n            ws.write((b\"a\" * 1023 + b\"\\n\") * 100)\n            for i in range(100):\n                data = yield rs.read_until(b\"\\n\", max_bytes=4096)\n                self.assertEqual(data, b\"a\" * 1023 + b\"\\n\")\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_flow_control(self):\n        MB = 1024 * 1024\n        rs, ws = yield self.make_iostream_pair(max_buffer_size=5 * MB)\n        try:\n            # Client writes more than the rs will accept.\n            ws.write(b\"a\" * 10 * MB)\n            # The rs pauses while reading.\n            yield rs.read_bytes(MB)\n            yield gen.sleep(0.1)\n            # The ws's writes have been blocked; the rs can\n            # continue to read gradually.\n            for i in range(9):\n                yield rs.read_bytes(MB)\n        finally:\n            rs.close()\n            ws.close()\n\n    @gen_test\n    def test_read_into(self):\n        rs, ws = yield self.make_iostream_pair()\n\n        def sleep_some():\n            self.io_loop.run_sync(lambda: gen.sleep(0.05))\n\n        try:\n            buf = bytearray(10)\n            fut = rs.read_into(buf)\n            ws.write(b\"hello\")\n            yield gen.sleep(0.05)\n            self.assertTrue(rs.reading())\n            ws.write(b\"world!!\")\n            data = yield fut\n            self.assertFalse(rs.reading())\n            self.assertEqual(data, 10)\n            self.assertEqual(bytes(buf), b\"helloworld\")\n\n            # Existing buffer is fed into user buffer\n            fut = rs.read_into(buf)\n            yield gen.sleep(0.05)\n            self.assertTrue(rs.reading())\n            ws.write(b\"1234567890\")\n            data = yield fut\n            self.assertFalse(rs.reading())\n            self.assertEqual(data, 10)\n            self.assertEqual(bytes(buf), b\"!!12345678\")\n\n            # Existing buffer can satisfy read immediately\n            buf = bytearray(4)\n            ws.write(b\"abcdefghi\")\n            data = yield rs.read_into(buf)\n            self.assertEqual(data, 4)\n            self.assertEqual(bytes(buf), b\"90ab\")\n\n            data = yield rs.read_bytes(7)\n            self.assertEqual(data, b\"cdefghi\")\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_read_into_partial(self):\n        rs, ws = yield self.make_iostream_pair()\n\n        try:\n            # Partial read\n            buf = bytearray(10)\n            fut = rs.read_into(buf, partial=True)\n            ws.write(b\"hello\")\n            data = yield fut\n            self.assertFalse(rs.reading())\n            self.assertEqual(data, 5)\n            self.assertEqual(bytes(buf), b\"hello\\0\\0\\0\\0\\0\")\n\n            # Full read despite partial=True\n            ws.write(b\"world!1234567890\")\n            data = yield rs.read_into(buf, partial=True)\n            self.assertEqual(data, 10)\n            self.assertEqual(bytes(buf), b\"world!1234\")\n\n            # Existing buffer can satisfy read immediately\n            data = yield rs.read_into(buf, partial=True)\n            self.assertEqual(data, 6)\n            self.assertEqual(bytes(buf), b\"5678901234\")\n\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_read_into_zero_bytes(self):\n        rs, ws = yield self.make_iostream_pair()\n        try:\n            buf = bytearray()\n            fut = rs.read_into(buf)\n            self.assertEqual(fut.result(), 0)\n        finally:\n            ws.close()\n            rs.close()\n\n    @gen_test\n    def test_many_mixed_reads(self):\n        # Stress buffer handling when going back and forth between\n        # read_bytes() (using an internal buffer) and read_into()\n        # (using a user-allocated buffer).\n        r = random.Random(42)\n        nbytes = 1000000\n        rs, ws = yield self.make_iostream_pair()\n\n        produce_hash = hashlib.sha1()\n        consume_hash = hashlib.sha1()\n\n        @gen.coroutine\n        def produce():\n            remaining = nbytes\n            while remaining > 0:\n                size = r.randint(1, min(1000, remaining))\n                data = os.urandom(size)\n                produce_hash.update(data)\n                yield ws.write(data)\n                remaining -= size\n            assert remaining == 0\n\n        @gen.coroutine\n        def consume():\n            remaining = nbytes\n            while remaining > 0:\n                if r.random() > 0.5:\n                    # read_bytes()\n                    size = r.randint(1, min(1000, remaining))\n                    data = yield rs.read_bytes(size)\n                    consume_hash.update(data)\n                    remaining -= size\n                else:\n                    # read_into()\n                    size = r.randint(1, min(1000, remaining))\n                    buf = bytearray(size)\n                    n = yield rs.read_into(buf)\n                    assert n == size\n                    consume_hash.update(buf)\n                    remaining -= size\n            assert remaining == 0\n\n        try:\n            yield [produce(), consume()]\n            assert produce_hash.hexdigest() == consume_hash.hexdigest()\n        finally:\n            ws.close()\n            rs.close()\n\n\n@abstract_base_test\nclass TestIOStreamMixin(TestReadWriteMixin):\n    def _make_server_iostream(self, connection, **kwargs):\n        raise NotImplementedError()\n\n    def _make_client_iostream(self, connection, **kwargs):\n        raise NotImplementedError()\n\n    @gen.coroutine\n    def make_iostream_pair(self, **kwargs):\n        listener, port = bind_unused_port()\n        server_stream_fut: Future[IOStream] = Future()\n\n        def accept_callback(connection, address):\n            server_stream_fut.set_result(\n                self._make_server_iostream(connection, **kwargs)\n            )\n\n        netutil.add_accept_handler(listener, accept_callback)\n        client_stream = self._make_client_iostream(socket.socket(), **kwargs)\n        connect_fut = client_stream.connect((\"127.0.0.1\", port))\n        server_stream, client_stream = yield [server_stream_fut, connect_fut]\n        self.io_loop.remove_handler(listener.fileno())\n        listener.close()\n        raise gen.Return((server_stream, client_stream))\n\n    @gen_test\n    def test_connection_refused(self):\n        # When a connection is refused, the connect callback should not\n        # be run.  (The kqueue IOLoop used to behave differently from the\n        # epoll IOLoop in this respect)\n        cleanup_func, port = refusing_port()\n        self.addCleanup(cleanup_func)\n        stream = IOStream(socket.socket())\n\n        stream.set_close_callback(self.stop)\n        # log messages vary by platform and ioloop implementation\n        with ExpectLog(gen_log, \".*\", required=False):\n            with self.assertRaises(StreamClosedError):\n                yield stream.connect((\"127.0.0.1\", port))\n\n        self.assertTrue(isinstance(stream.error, ConnectionRefusedError), stream.error)\n\n    @gen_test\n    def test_gaierror(self):\n        # Test that IOStream sets its exc_info on getaddrinfo error.\n        # It's difficult to reliably trigger a getaddrinfo error;\n        # some resolvers own't even return errors for malformed names,\n        # so we mock it instead. If IOStream changes to call a Resolver\n        # before sock.connect, the mock target will need to change too.\n        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)\n        stream = IOStream(s)\n        stream.set_close_callback(self.stop)\n        with mock.patch(\n            \"socket.socket.connect\", side_effect=socket.gaierror(errno.EIO, \"boom\")\n        ):\n            with self.assertRaises(StreamClosedError):\n                yield stream.connect((\"localhost\", 80))\n            self.assertTrue(isinstance(stream.error, socket.gaierror))\n\n    @gen_test\n    def test_read_until_close_with_error(self):\n        server, client = yield self.make_iostream_pair()\n        try:\n            with mock.patch(\n                \"tornado.iostream.BaseIOStream._try_inline_read\",\n                side_effect=IOError(\"boom\"),\n            ):\n                with self.assertRaisesRegex(IOError, \"boom\"):\n                    client.read_until_close()\n        finally:\n            server.close()\n            client.close()\n\n    @skipIfNonUnix\n    @gen_test\n    def test_inline_read_error(self):\n        # An error on an inline read is raised without logging (on the\n        # assumption that it will eventually be noticed or logged further\n        # up the stack).\n        #\n        # This test is posix-only because windows os.close() doesn't work\n        # on socket FDs, but we can't close the socket object normally\n        # because we won't get the error we want if the socket knows\n        # it's closed.\n        #\n        # This test is also disabled when the\n        # AddThreadSelectorEventLoop is used, because a race between\n        # this thread closing the socket and the selector thread\n        # calling the select system call can make this test flaky.\n        # This event loop implementation is normally only used on\n        # windows, making this check redundant with skipIfNonUnix, but\n        # we sometimes enable it on other platforms for testing.\n        io_loop = IOLoop.current()\n        if isinstance(\n            io_loop.selector_loop,  # type: ignore[attr-defined]\n            AddThreadSelectorEventLoop,\n        ):\n            self.skipTest(\"AddThreadSelectorEventLoop not supported\")\n        server, client = yield self.make_iostream_pair()\n        try:\n            os.close(server.socket.fileno())\n            with self.assertRaises(socket.error):\n                server.read_bytes(1)\n        finally:\n            server.close()\n            client.close()\n\n    @gen_test\n    def test_async_read_error_logging(self):\n        # Socket errors on asynchronous reads should be logged (but only\n        # once).\n        server, client = yield self.make_iostream_pair()\n        closed = Event()\n        server.set_close_callback(closed.set)\n        try:\n            # Start a read that will be fulfilled asynchronously.\n            server.read_bytes(1)\n            client.write(b\"a\")\n            # Stub out read_from_fd to make it fail.\n\n            def fake_read_from_fd():\n                os.close(server.socket.fileno())\n                server.__class__.read_from_fd(server)\n\n            server.read_from_fd = fake_read_from_fd\n            # This log message is from _handle_read (not read_from_fd).\n            with ExpectLog(gen_log, \"error on read\"):\n                yield closed.wait()\n        finally:\n            server.close()\n            client.close()\n\n    @gen_test\n    def test_future_write(self):\n        \"\"\"\n        Test that write() Futures are never orphaned.\n        \"\"\"\n        # Run concurrent writers that will write enough bytes so as to\n        # clog the socket buffer and accumulate bytes in our write buffer.\n        m, n = 5000, 1000\n        nproducers = 10\n        total_bytes = m * n * nproducers\n        server, client = yield self.make_iostream_pair(max_buffer_size=total_bytes)\n\n        @gen.coroutine\n        def produce():\n            data = b\"x\" * m\n            for i in range(n):\n                yield server.write(data)\n\n        @gen.coroutine\n        def consume():\n            nread = 0\n            while nread < total_bytes:\n                res = yield client.read_bytes(m)\n                nread += len(res)\n\n        try:\n            yield [produce() for i in range(nproducers)] + [consume()]\n        finally:\n            server.close()\n            client.close()\n\n\nclass TestIOStreamWebHTTP(AsyncHTTPTestCase, TestIOStreamWebMixin):\n    def _make_client_iostream(self):\n        return IOStream(socket.socket())\n\n    def get_app(self):\n        return self.mixin_get_app()\n\n\nclass TestIOStreamWebHTTPS(AsyncHTTPSTestCase, TestIOStreamWebMixin):\n    def _make_client_iostream(self):\n        return SSLIOStream(socket.socket(), ssl_options=dict(cert_reqs=ssl.CERT_NONE))\n\n    def get_app(self):\n        return self.mixin_get_app()\n\n\nclass TestIOStream(TestIOStreamMixin):\n    def _make_server_iostream(self, connection, **kwargs):\n        return IOStream(connection, **kwargs)\n\n    def _make_client_iostream(self, connection, **kwargs):\n        return IOStream(connection, **kwargs)\n\n\nclass TestIOStreamSSL(TestIOStreamMixin):\n    def _make_server_iostream(self, connection, **kwargs):\n        ssl_ctx = ssl_options_to_context(_server_ssl_options(), server_side=True)\n        connection = ssl_ctx.wrap_socket(\n            connection,\n            server_side=True,\n            do_handshake_on_connect=False,\n        )\n        return SSLIOStream(connection, **kwargs)\n\n    def _make_client_iostream(self, connection, **kwargs):\n        return SSLIOStream(\n            connection, ssl_options=dict(cert_reqs=ssl.CERT_NONE), **kwargs\n        )\n\n\n# This will run some tests that are basically redundant but it's the\n# simplest way to make sure that it works to pass an SSLContext\n# instead of an ssl_options dict to the SSLIOStream constructor.\nclass TestIOStreamSSLContext(TestIOStreamMixin):\n    def _make_server_iostream(self, connection, **kwargs):\n        context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)\n        context.load_cert_chain(\n            os.path.join(os.path.dirname(__file__), \"test.crt\"),\n            os.path.join(os.path.dirname(__file__), \"test.key\"),\n        )\n        connection = ssl_wrap_socket(\n            connection, context, server_side=True, do_handshake_on_connect=False\n        )\n        return SSLIOStream(connection, **kwargs)\n\n    def _make_client_iostream(self, connection, **kwargs):\n        context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)\n        context.check_hostname = False\n        context.verify_mode = ssl.CERT_NONE\n        return SSLIOStream(connection, ssl_options=context, **kwargs)\n\n\nclass TestIOStreamStartTLS(AsyncTestCase):\n    def setUp(self):\n        try:\n            super().setUp()\n            self.listener, self.port = bind_unused_port()\n            self.server_stream = None\n            self.server_accepted: Future[None] = Future()\n            netutil.add_accept_handler(self.listener, self.accept)\n            self.client_stream: IOStream | None = IOStream(socket.socket())\n            self.io_loop.add_future(\n                self.client_stream.connect((\"127.0.0.1\", self.port)), self.stop\n            )\n            self.wait()\n            self.io_loop.add_future(self.server_accepted, self.stop)\n            self.wait()\n        except Exception as e:\n            print(e)\n            raise\n\n    def tearDown(self):\n        if self.server_stream is not None:\n            self.server_stream.close()\n        if self.client_stream is not None:\n            self.client_stream.close()\n        self.io_loop.remove_handler(self.listener.fileno())\n        self.listener.close()\n        super().tearDown()\n\n    def accept(self, connection, address):\n        if self.server_stream is not None:\n            self.fail(\"should only get one connection\")\n        self.server_stream = IOStream(connection)\n        self.server_accepted.set_result(None)\n\n    @gen.coroutine\n    def client_send_line(self, line):\n        assert self.client_stream is not None\n        self.client_stream.write(line)\n        assert self.server_stream is not None\n        recv_line = yield self.server_stream.read_until(b\"\\r\\n\")\n        self.assertEqual(line, recv_line)\n\n    @gen.coroutine\n    def server_send_line(self, line):\n        assert self.server_stream is not None\n        self.server_stream.write(line)\n        assert self.client_stream is not None\n        recv_line = yield self.client_stream.read_until(b\"\\r\\n\")\n        self.assertEqual(line, recv_line)\n\n    def client_start_tls(self, ssl_options=None, server_hostname=None):\n        assert self.client_stream is not None\n        client_stream = self.client_stream\n        self.client_stream = None\n        return client_stream.start_tls(False, ssl_options, server_hostname)\n\n    def server_start_tls(self, ssl_options=None):\n        assert self.server_stream is not None\n        server_stream = self.server_stream\n        self.server_stream = None\n        return server_stream.start_tls(True, ssl_options)\n\n    @gen_test\n    def test_start_tls_smtp(self):\n        # This flow is simplified from RFC 3207 section 5.\n        # We don't really need all of this, but it helps to make sure\n        # that after realistic back-and-forth traffic the buffers end up\n        # in a sane state.\n        yield self.server_send_line(b\"220 mail.example.com ready\\r\\n\")\n        yield self.client_send_line(b\"EHLO mail.example.com\\r\\n\")\n        yield self.server_send_line(b\"250-mail.example.com welcome\\r\\n\")\n        yield self.server_send_line(b\"250 STARTTLS\\r\\n\")\n        yield self.client_send_line(b\"STARTTLS\\r\\n\")\n        yield self.server_send_line(b\"220 Go ahead\\r\\n\")\n        client_future = self.client_start_tls(dict(cert_reqs=ssl.CERT_NONE))\n        server_future = self.server_start_tls(_server_ssl_options())\n        self.client_stream = yield client_future\n        self.server_stream = yield server_future\n        self.assertTrue(isinstance(self.client_stream, SSLIOStream))\n        self.assertTrue(isinstance(self.server_stream, SSLIOStream))\n        yield self.client_send_line(b\"EHLO mail.example.com\\r\\n\")\n        yield self.server_send_line(b\"250 mail.example.com welcome\\r\\n\")\n\n    @gen_test\n    def test_handshake_fail(self):\n        server_future = self.server_start_tls(_server_ssl_options())\n        # Certificates are verified with the default configuration.\n        with ExpectLog(gen_log, \"SSL Error\"):\n            client_future = self.client_start_tls(server_hostname=\"localhost\")\n            with self.assertRaises(ssl.SSLError):\n                yield client_future\n            with self.assertRaises((ssl.SSLError, socket.error)):\n                yield server_future\n\n    @gen_test\n    def test_check_hostname(self):\n        # Test that server_hostname parameter to start_tls is being used.\n        server_future = self.server_start_tls(_server_ssl_options())\n        with ExpectLog(gen_log, \"SSL Error\"):\n            client_future = self.client_start_tls(\n                ssl.create_default_context(), server_hostname=\"127.0.0.1\"\n            )\n            with self.assertRaises(ssl.SSLError):\n                # The client fails to connect with an SSL error.\n                yield client_future\n            with self.assertRaises(Exception):\n                # The server fails to connect, but the exact error is unspecified.\n                yield server_future\n\n    @gen_test\n    def test_typed_memoryview(self):\n        # Test support of memoryviews with an item size greater than 1 byte.\n        buf = memoryview(bytes(80)).cast(\"L\")\n        assert self.server_stream is not None\n        yield self.server_stream.write(buf)\n        assert self.client_stream is not None\n        # This will timeout if the calculation of the buffer size is incorrect\n        recv = yield self.client_stream.read_bytes(buf.nbytes)\n        self.assertEqual(bytes(recv), bytes(buf))\n\n\nclass WaitForHandshakeTest(AsyncTestCase):\n    @gen.coroutine\n    def connect_to_server(self, server_cls):\n        server = client = None\n        try:\n            sock, port = bind_unused_port()\n            server = server_cls(ssl_options=_server_ssl_options())\n            server.add_socket(sock)\n\n            ssl_ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)\n            ssl_ctx.check_hostname = False\n            ssl_ctx.verify_mode = ssl.CERT_NONE\n            # These tests fail with ConnectionAbortedErrors with TLS\n            # 1.3 on windows python 3.7.4 (which includes an upgrade\n            # to openssl 1.1.c. Other platforms might be affected with\n            # newer openssl too). Disable it until we figure out\n            # what's up.\n            # Update 2021-12-28: Still happening with Python 3.10 on\n            # Windows. OP_NO_TLSv1_3 now raises a DeprecationWarning.\n            with ignore_deprecation():\n                ssl_ctx.options |= getattr(ssl, \"OP_NO_TLSv1_3\", 0)\n                client = SSLIOStream(socket.socket(), ssl_options=ssl_ctx)\n            yield client.connect((\"127.0.0.1\", port))\n            self.assertIsNotNone(client.socket.cipher())\n        finally:\n            if server is not None:\n                server.stop()\n            if client is not None:\n                client.close()\n\n    @gen_test\n    def test_wait_for_handshake_future(self):\n        test = self\n        handshake_future: Future[None] = Future()\n\n        class TestServer(TCPServer):\n            def handle_stream(self, stream, address):\n                test.assertIsNone(stream.socket.cipher())\n                test.io_loop.spawn_callback(self.handle_connection, stream)\n\n            @gen.coroutine\n            def handle_connection(self, stream):\n                yield stream.wait_for_handshake()\n                handshake_future.set_result(None)\n\n        yield self.connect_to_server(TestServer)\n        yield handshake_future\n\n    @gen_test\n    def test_wait_for_handshake_already_waiting_error(self):\n        test = self\n        handshake_future: Future[None] = Future()\n\n        class TestServer(TCPServer):\n            @gen.coroutine\n            def handle_stream(self, stream, address):\n                fut = stream.wait_for_handshake()\n                test.assertRaises(RuntimeError, stream.wait_for_handshake)\n                yield fut\n\n                handshake_future.set_result(None)\n\n        yield self.connect_to_server(TestServer)\n        yield handshake_future\n\n    @gen_test\n    def test_wait_for_handshake_already_connected(self):\n        handshake_future: Future[None] = Future()\n\n        class TestServer(TCPServer):\n            @gen.coroutine\n            def handle_stream(self, stream, address):\n                yield stream.wait_for_handshake()\n                yield stream.wait_for_handshake()\n                handshake_future.set_result(None)\n\n        yield self.connect_to_server(TestServer)\n        yield handshake_future\n\n\nclass TestIOStreamCheckHostname(AsyncTestCase):\n    # This test ensures that hostname checks are working correctly after\n    # #3337 revealed that we have no test coverage in this area, and we\n    # removed a manual hostname check that was needed only for very old\n    # versions of python.\n    def setUp(self):\n        super().setUp()\n        self.listener, self.port = bind_unused_port()\n\n        def accept_callback(connection, address):\n            ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)\n            ssl_ctx.load_cert_chain(\n                os.path.join(os.path.dirname(__file__), \"test.crt\"),\n                os.path.join(os.path.dirname(__file__), \"test.key\"),\n            )\n            connection = ssl_ctx.wrap_socket(\n                connection,\n                server_side=True,\n                do_handshake_on_connect=False,\n            )\n            SSLIOStream(connection)\n\n        netutil.add_accept_handler(self.listener, accept_callback)\n\n        # Our self-signed cert is its own CA.  We have to pass the CA check before\n        # the hostname check will be performed.\n        self.client_ssl_ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)\n        self.client_ssl_ctx.load_verify_locations(\n            os.path.join(os.path.dirname(__file__), \"test.crt\")\n        )\n\n    def tearDown(self):\n        self.io_loop.remove_handler(self.listener.fileno())\n        self.listener.close()\n        super().tearDown()\n\n    @gen_test\n    async def test_match(self):\n        stream = SSLIOStream(socket.socket(), ssl_options=self.client_ssl_ctx)\n        await stream.connect(\n            (\"127.0.0.1\", self.port),\n            server_hostname=\"foo.example.com\",\n        )\n        stream.close()\n\n    @gen_test\n    async def test_no_match(self):\n        stream = SSLIOStream(socket.socket(), ssl_options=self.client_ssl_ctx)\n        with ExpectLog(\n            gen_log,\n            \".*alert bad certificate\",\n            level=logging.WARNING,\n            required=platform.system() != \"Windows\",\n        ):\n            with self.assertRaises(ssl.SSLCertVerificationError):\n                with ExpectLog(\n                    gen_log,\n                    \".*(certificate verify failed: Hostname mismatch)\",\n                    level=logging.WARNING,\n                ):\n                    await stream.connect(\n                        (\"127.0.0.1\", self.port),\n                        server_hostname=\"bar.example.com\",\n                    )\n            # The server logs a warning while cleaning up the failed connection.\n            # Unfortunately there's no good hook to wait for this logging.\n            # It doesn't seem to happen on windows; I'm not sure why.\n            if platform.system() != \"Windows\":\n                await asyncio.sleep(0.1)\n\n    @gen_test\n    async def test_check_disabled(self):\n        # check_hostname can be set to false and the connection will succeed even though it doesn't\n        # have the right hostname.\n        self.client_ssl_ctx.check_hostname = False\n        stream = SSLIOStream(socket.socket(), ssl_options=self.client_ssl_ctx)\n        await stream.connect(\n            (\"127.0.0.1\", self.port),\n            server_hostname=\"bar.example.com\",\n        )\n\n\n@skipIfNonUnix\nclass TestPipeIOStream(TestReadWriteMixin, AsyncTestCase):\n    @gen.coroutine\n    def make_iostream_pair(self, **kwargs):\n        r, w = os.pipe()\n\n        return PipeIOStream(r, **kwargs), PipeIOStream(w, **kwargs)\n\n    @gen_test\n    def test_pipe_iostream(self):\n        rs, ws = yield self.make_iostream_pair()\n\n        ws.write(b\"hel\")\n        ws.write(b\"lo world\")\n\n        data = yield rs.read_until(b\" \")\n        self.assertEqual(data, b\"hello \")\n\n        data = yield rs.read_bytes(3)\n        self.assertEqual(data, b\"wor\")\n\n        ws.close()\n\n        data = yield rs.read_until_close()\n        self.assertEqual(data, b\"ld\")\n\n        rs.close()\n\n    @gen_test\n    def test_pipe_iostream_big_write(self):\n        rs, ws = yield self.make_iostream_pair()\n\n        NUM_BYTES = 1048576\n\n        # Write 1MB of data, which should fill the buffer\n        ws.write(b\"1\" * NUM_BYTES)\n\n        data = yield rs.read_bytes(NUM_BYTES)\n        self.assertEqual(data, b\"1\" * NUM_BYTES)\n\n        ws.close()\n        rs.close()\n\n\nclass TestStreamBuffer(unittest.TestCase):\n    \"\"\"\n    Unit tests for the private _StreamBuffer class.\n    \"\"\"\n\n    def setUp(self):\n        self.random = random.Random(42)\n\n    def to_bytes(self, b):\n        if isinstance(b, (bytes, bytearray)):\n            return bytes(b)\n        elif isinstance(b, memoryview):\n            return b.tobytes()  # For py2\n        else:\n            raise TypeError(b)\n\n    def make_streambuffer(self, large_buf_threshold=10):\n        buf = _StreamBuffer()\n        assert buf._large_buf_threshold\n        buf._large_buf_threshold = large_buf_threshold\n        return buf\n\n    def check_peek(self, buf, expected):\n        size = 1\n        while size < 2 * len(expected):\n            got = self.to_bytes(buf.peek(size))\n            self.assertTrue(got)  # Not empty\n            self.assertLessEqual(len(got), size)\n            self.assertTrue(expected.startswith(got), (expected, got))\n            size = (size * 3 + 1) // 2\n\n    def check_append_all_then_skip_all(self, buf, objs, input_type):\n        self.assertEqual(len(buf), 0)\n\n        expected = b\"\"\n\n        for o in objs:\n            expected += o\n            buf.append(input_type(o))\n            self.assertEqual(len(buf), len(expected))\n            self.check_peek(buf, expected)\n\n        while expected:\n            n = self.random.randrange(1, len(expected) + 1)\n            expected = expected[n:]\n            buf.advance(n)\n            self.assertEqual(len(buf), len(expected))\n            self.check_peek(buf, expected)\n\n        self.assertEqual(len(buf), 0)\n\n    def test_small(self):\n        objs = [b\"12\", b\"345\", b\"67\", b\"89a\", b\"bcde\", b\"fgh\", b\"ijklmn\"]\n\n        buf = self.make_streambuffer()\n        self.check_append_all_then_skip_all(buf, objs, bytes)\n\n        buf = self.make_streambuffer()\n        self.check_append_all_then_skip_all(buf, objs, bytearray)\n\n        buf = self.make_streambuffer()\n        self.check_append_all_then_skip_all(buf, objs, memoryview)\n\n        # Test internal algorithm\n        buf = self.make_streambuffer(10)\n        for i in range(9):\n            buf.append(b\"x\")\n        self.assertEqual(len(buf._buffers), 1)\n        for i in range(9):\n            buf.append(b\"x\")\n        self.assertEqual(len(buf._buffers), 2)\n        buf.advance(10)\n        self.assertEqual(len(buf._buffers), 1)\n        buf.advance(8)\n        self.assertEqual(len(buf._buffers), 0)\n        self.assertEqual(len(buf), 0)\n\n    def test_large(self):\n        objs = [\n            b\"12\" * 5,\n            b\"345\" * 2,\n            b\"67\" * 20,\n            b\"89a\" * 12,\n            b\"bcde\" * 1,\n            b\"fgh\" * 7,\n            b\"ijklmn\" * 2,\n        ]\n\n        buf = self.make_streambuffer()\n        self.check_append_all_then_skip_all(buf, objs, bytes)\n\n        buf = self.make_streambuffer()\n        self.check_append_all_then_skip_all(buf, objs, bytearray)\n\n        buf = self.make_streambuffer()\n        self.check_append_all_then_skip_all(buf, objs, memoryview)\n\n        # Test internal algorithm\n        buf = self.make_streambuffer(10)\n        for i in range(3):\n            buf.append(b\"x\" * 11)\n        self.assertEqual(len(buf._buffers), 3)\n        buf.append(b\"y\")\n        self.assertEqual(len(buf._buffers), 4)\n        buf.append(b\"z\")\n        self.assertEqual(len(buf._buffers), 4)\n        buf.advance(33)\n        self.assertEqual(len(buf._buffers), 1)\n        buf.advance(2)\n        self.assertEqual(len(buf._buffers), 0)\n        self.assertEqual(len(buf), 0)\n"
  },
  {
    "path": "tornado/test/locale_test.py",
    "content": "import datetime\nimport os\nimport shutil\nimport tempfile\nimport unittest\n\nimport tornado.locale\nfrom tornado.escape import to_unicode, utf8\nfrom tornado.util import unicode_type\n\n\nclass TranslationLoaderTest(unittest.TestCase):\n    # TODO: less hacky way to get isolated tests\n    SAVE_VARS = [\"_translations\", \"_supported_locales\", \"_use_gettext\"]\n\n    def clear_locale_cache(self):\n        tornado.locale.Locale._cache = {}\n\n    def setUp(self):\n        self.saved: dict = {}\n        for var in TranslationLoaderTest.SAVE_VARS:\n            self.saved[var] = getattr(tornado.locale, var)\n        self.clear_locale_cache()\n\n    def tearDown(self):\n        for k, v in self.saved.items():\n            setattr(tornado.locale, k, v)\n        self.clear_locale_cache()\n\n    def test_csv(self):\n        tornado.locale.load_translations(\n            os.path.join(os.path.dirname(__file__), \"csv_translations\")\n        )\n        locale = tornado.locale.get(\"fr_FR\")\n        self.assertTrue(isinstance(locale, tornado.locale.CSVLocale))\n        self.assertEqual(locale.translate(\"school\"), \"\\u00e9cole\")\n\n    def test_csv_bom(self):\n        with open(\n            os.path.join(os.path.dirname(__file__), \"csv_translations\", \"fr_FR.csv\"),\n            \"rb\",\n        ) as f:\n            char_data = to_unicode(f.read())\n        # Re-encode our input data (which is utf-8 without BOM) in\n        # encodings that use the BOM and ensure that we can still load\n        # it. Note that utf-16-le and utf-16-be do not write a BOM,\n        # so we only test whichver variant is native to our platform.\n        for encoding in [\"utf-8-sig\", \"utf-16\"]:\n            tmpdir = tempfile.mkdtemp()\n            try:\n                with open(os.path.join(tmpdir, \"fr_FR.csv\"), \"wb\") as f:\n                    f.write(char_data.encode(encoding))\n                tornado.locale.load_translations(tmpdir)\n                locale = tornado.locale.get(\"fr_FR\")\n                self.assertIsInstance(locale, tornado.locale.CSVLocale)\n                self.assertEqual(locale.translate(\"school\"), \"\\u00e9cole\")\n            finally:\n                shutil.rmtree(tmpdir)\n\n    def test_gettext(self):\n        tornado.locale.load_gettext_translations(\n            os.path.join(os.path.dirname(__file__), \"gettext_translations\"),\n            \"tornado_test\",\n        )\n        locale = tornado.locale.get(\"fr_FR\")\n        self.assertTrue(isinstance(locale, tornado.locale.GettextLocale))\n        self.assertEqual(locale.translate(\"school\"), \"\\u00e9cole\")\n        self.assertEqual(locale.pgettext(\"law\", \"right\"), \"le droit\")\n        self.assertEqual(locale.pgettext(\"good\", \"right\"), \"le bien\")\n        self.assertEqual(locale.pgettext(\"organization\", \"club\", \"clubs\", 1), \"le club\")\n        self.assertEqual(\n            locale.pgettext(\"organization\", \"club\", \"clubs\", 2), \"les clubs\"\n        )\n        self.assertEqual(locale.pgettext(\"stick\", \"club\", \"clubs\", 1), \"le b\\xe2ton\")\n        self.assertEqual(locale.pgettext(\"stick\", \"club\", \"clubs\", 2), \"les b\\xe2tons\")\n\n\nclass LocaleDataTest(unittest.TestCase):\n    def test_non_ascii_name(self):\n        name = tornado.locale.LOCALE_NAMES[\"es_LA\"][\"name\"]\n        self.assertTrue(isinstance(name, unicode_type))\n        self.assertEqual(name, \"Espa\\u00f1ol\")\n        self.assertEqual(utf8(name), b\"Espa\\xc3\\xb1ol\")\n\n\nclass EnglishTest(unittest.TestCase):\n    def test_format_date(self):\n        locale = tornado.locale.get(\"en_US\")\n        date = datetime.datetime(2013, 4, 28, 18, 35)\n        self.assertEqual(\n            locale.format_date(date, full_format=True), \"April 28, 2013 at 6:35 pm\"\n        )\n\n        aware_dt = datetime.datetime.now(datetime.timezone.utc)\n        naive_dt = aware_dt.replace(tzinfo=None)\n        for name, now in {\"aware\": aware_dt, \"naive\": naive_dt}.items():\n            with self.subTest(dt=name):\n                self.assertEqual(\n                    locale.format_date(\n                        now - datetime.timedelta(seconds=2), full_format=False\n                    ),\n                    \"2 seconds ago\",\n                )\n                self.assertEqual(\n                    locale.format_date(\n                        now - datetime.timedelta(minutes=2), full_format=False\n                    ),\n                    \"2 minutes ago\",\n                )\n                self.assertEqual(\n                    locale.format_date(\n                        now - datetime.timedelta(hours=2), full_format=False\n                    ),\n                    \"2 hours ago\",\n                )\n\n                self.assertEqual(\n                    locale.format_date(\n                        now - datetime.timedelta(days=1),\n                        full_format=False,\n                        shorter=True,\n                    ),\n                    \"yesterday\",\n                )\n\n                date = now - datetime.timedelta(days=2)\n                self.assertEqual(\n                    locale.format_date(date, full_format=False, shorter=True),\n                    locale._weekdays[date.weekday()],\n                )\n\n                date = now - datetime.timedelta(days=300)\n                self.assertEqual(\n                    locale.format_date(date, full_format=False, shorter=True),\n                    \"%s %d\" % (locale._months[date.month - 1], date.day),\n                )\n\n                date = now - datetime.timedelta(days=500)\n                self.assertEqual(\n                    locale.format_date(date, full_format=False, shorter=True),\n                    \"%s %d, %d\" % (locale._months[date.month - 1], date.day, date.year),\n                )\n\n    def test_friendly_number(self):\n        locale = tornado.locale.get(\"en_US\")\n        self.assertEqual(locale.friendly_number(1000000), \"1,000,000\")\n\n    def test_list(self):\n        locale = tornado.locale.get(\"en_US\")\n        self.assertEqual(locale.list([]), \"\")\n        self.assertEqual(locale.list([\"A\"]), \"A\")\n        self.assertEqual(locale.list([\"A\", \"B\"]), \"A and B\")\n        self.assertEqual(locale.list([\"A\", \"B\", \"C\"]), \"A, B and C\")\n\n    def test_format_day(self):\n        locale = tornado.locale.get(\"en_US\")\n        date = datetime.datetime(2013, 4, 28, 18, 35)\n        self.assertEqual(locale.format_day(date=date, dow=True), \"Sunday, April 28\")\n        self.assertEqual(locale.format_day(date=date, dow=False), \"April 28\")\n"
  },
  {
    "path": "tornado/test/locks_test.py",
    "content": "# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\nimport asyncio\nimport unittest\nfrom datetime import timedelta\n\nfrom tornado import gen, locks\nfrom tornado.gen import TimeoutError\nfrom tornado.testing import AsyncTestCase, gen_test\n\n\nclass ConditionTest(AsyncTestCase):\n    def setUp(self):\n        super().setUp()\n        self.history: list[int | str] = []\n\n    def record_done(self, future, key):\n        \"\"\"Record the resolution of a Future returned by Condition.wait.\"\"\"\n\n        def callback(_):\n            if not future.result():\n                # wait() resolved to False, meaning it timed out.\n                self.history.append(\"timeout\")\n            else:\n                self.history.append(key)\n\n        future.add_done_callback(callback)\n\n    def loop_briefly(self):\n        \"\"\"Run all queued callbacks on the IOLoop.\n\n        In these tests, this method is used after calling notify() to\n        preserve the pre-5.0 behavior in which callbacks ran\n        synchronously.\n        \"\"\"\n        self.io_loop.add_callback(self.stop)\n        self.wait()\n\n    def test_repr(self):\n        c = locks.Condition()\n        self.assertIn(\"Condition\", repr(c))\n        self.assertNotIn(\"waiters\", repr(c))\n        c.wait()\n        self.assertIn(\"waiters\", repr(c))\n\n    @gen_test\n    def test_notify(self):\n        c = locks.Condition()\n        self.io_loop.call_later(0.01, c.notify)\n        yield c.wait()\n\n    def test_notify_1(self):\n        c = locks.Condition()\n        self.record_done(c.wait(), \"wait1\")\n        self.record_done(c.wait(), \"wait2\")\n        c.notify(1)\n        self.loop_briefly()\n        self.history.append(\"notify1\")\n        c.notify(1)\n        self.loop_briefly()\n        self.history.append(\"notify2\")\n        self.assertEqual([\"wait1\", \"notify1\", \"wait2\", \"notify2\"], self.history)\n\n    def test_notify_n(self):\n        c = locks.Condition()\n        for i in range(6):\n            self.record_done(c.wait(), i)\n\n        c.notify(3)\n        self.loop_briefly()\n\n        # Callbacks execute in the order they were registered.\n        self.assertEqual(list(range(3)), self.history)\n        c.notify(1)\n        self.loop_briefly()\n        self.assertEqual(list(range(4)), self.history)\n        c.notify(2)\n        self.loop_briefly()\n        self.assertEqual(list(range(6)), self.history)\n\n    def test_notify_all(self):\n        c = locks.Condition()\n        for i in range(4):\n            self.record_done(c.wait(), i)\n\n        c.notify_all()\n        self.loop_briefly()\n        self.history.append(\"notify_all\")\n\n        # Callbacks execute in the order they were registered.\n        self.assertEqual(list(range(4)) + [\"notify_all\"], self.history)  # type: ignore\n\n    @gen_test\n    def test_wait_timeout(self):\n        c = locks.Condition()\n        wait = c.wait(timedelta(seconds=0.01))\n        self.io_loop.call_later(0.02, c.notify)  # Too late.\n        yield gen.sleep(0.03)\n        self.assertFalse((yield wait))\n\n    @gen_test\n    def test_wait_timeout_preempted(self):\n        c = locks.Condition()\n\n        # This fires before the wait times out.\n        self.io_loop.call_later(0.01, c.notify)\n        wait = c.wait(timedelta(seconds=0.02))\n        yield gen.sleep(0.03)\n        yield wait  # No TimeoutError.\n\n    @gen_test\n    def test_notify_n_with_timeout(self):\n        # Register callbacks 0, 1, 2, and 3. Callback 1 has a timeout.\n        # Wait for that timeout to expire, then do notify(2) and make\n        # sure everyone runs. Verifies that a timed-out callback does\n        # not count against the 'n' argument to notify().\n        c = locks.Condition()\n        self.record_done(c.wait(), 0)\n        self.record_done(c.wait(timedelta(seconds=0.01)), 1)\n        self.record_done(c.wait(), 2)\n        self.record_done(c.wait(), 3)\n\n        # Wait for callback 1 to time out.\n        yield gen.sleep(0.02)\n        self.assertEqual([\"timeout\"], self.history)\n\n        c.notify(2)\n        yield gen.sleep(0.01)\n        self.assertEqual([\"timeout\", 0, 2], self.history)\n        self.assertEqual([\"timeout\", 0, 2], self.history)\n        c.notify()\n        yield\n        self.assertEqual([\"timeout\", 0, 2, 3], self.history)\n\n    @gen_test\n    def test_notify_all_with_timeout(self):\n        c = locks.Condition()\n        self.record_done(c.wait(), 0)\n        self.record_done(c.wait(timedelta(seconds=0.01)), 1)\n        self.record_done(c.wait(), 2)\n\n        # Wait for callback 1 to time out.\n        yield gen.sleep(0.02)\n        self.assertEqual([\"timeout\"], self.history)\n\n        c.notify_all()\n        yield\n        self.assertEqual([\"timeout\", 0, 2], self.history)\n\n    @gen_test\n    def test_nested_notify(self):\n        # Ensure no notifications lost, even if notify() is reentered by a\n        # waiter calling notify().\n        c = locks.Condition()\n\n        # Three waiters.\n        futures = [asyncio.ensure_future(c.wait()) for _ in range(3)]\n\n        # First and second futures resolved. Second future reenters notify(),\n        # resolving third future.\n        futures[1].add_done_callback(lambda _: c.notify())\n        c.notify(2)\n        yield\n        self.assertTrue(all(f.done() for f in futures))\n\n    @gen_test\n    def test_garbage_collection(self):\n        # Test that timed-out waiters are occasionally cleaned from the queue.\n        c = locks.Condition()\n        for _ in range(101):\n            c.wait(timedelta(seconds=0.01))\n\n        future = asyncio.ensure_future(c.wait())\n        self.assertEqual(102, len(c._waiters))\n\n        # Let first 101 waiters time out, triggering a collection.\n        yield gen.sleep(0.02)\n        self.assertEqual(1, len(c._waiters))\n\n        # Final waiter is still active.\n        self.assertFalse(future.done())\n        c.notify()\n        self.assertTrue(future.done())\n\n\nclass EventTest(AsyncTestCase):\n    def test_repr(self):\n        event = locks.Event()\n        self.assertTrue(\"clear\" in str(event))\n        self.assertFalse(\"set\" in str(event))\n        event.set()\n        self.assertFalse(\"clear\" in str(event))\n        self.assertTrue(\"set\" in str(event))\n\n    def test_event(self):\n        e = locks.Event()\n        future_0 = asyncio.ensure_future(e.wait())\n        e.set()\n        future_1 = asyncio.ensure_future(e.wait())\n        e.clear()\n        future_2 = asyncio.ensure_future(e.wait())\n\n        self.assertTrue(future_0.done())\n        self.assertTrue(future_1.done())\n        self.assertFalse(future_2.done())\n\n    @gen_test\n    def test_event_timeout(self):\n        e = locks.Event()\n        with self.assertRaises(TimeoutError):\n            yield e.wait(timedelta(seconds=0.01))\n\n        # After a timed-out waiter, normal operation works.\n        self.io_loop.add_timeout(timedelta(seconds=0.01), e.set)\n        yield e.wait(timedelta(seconds=1))\n\n    def test_event_set_multiple(self):\n        e = locks.Event()\n        e.set()\n        e.set()\n        self.assertTrue(e.is_set())\n\n    def test_event_wait_clear(self):\n        e = locks.Event()\n        f0 = asyncio.ensure_future(e.wait())\n        e.clear()\n        f1 = asyncio.ensure_future(e.wait())\n        e.set()\n        self.assertTrue(f0.done())\n        self.assertTrue(f1.done())\n\n\nclass SemaphoreTest(AsyncTestCase):\n    def test_negative_value(self):\n        self.assertRaises(ValueError, locks.Semaphore, value=-1)\n\n    def test_repr(self):\n        sem = locks.Semaphore()\n        self.assertIn(\"Semaphore\", repr(sem))\n        self.assertIn(\"unlocked,value:1\", repr(sem))\n        sem.acquire()\n        self.assertIn(\"locked\", repr(sem))\n        self.assertNotIn(\"waiters\", repr(sem))\n        sem.acquire()\n        self.assertIn(\"waiters\", repr(sem))\n\n    def test_acquire(self):\n        sem = locks.Semaphore()\n        f0 = asyncio.ensure_future(sem.acquire())\n        self.assertTrue(f0.done())\n\n        # Wait for release().\n        f1 = asyncio.ensure_future(sem.acquire())\n        self.assertFalse(f1.done())\n        f2 = asyncio.ensure_future(sem.acquire())\n        sem.release()\n        self.assertTrue(f1.done())\n        self.assertFalse(f2.done())\n        sem.release()\n        self.assertTrue(f2.done())\n\n        sem.release()\n        # Now acquire() is instant.\n        self.assertTrue(asyncio.ensure_future(sem.acquire()).done())\n        self.assertEqual(0, len(sem._waiters))\n\n    @gen_test\n    def test_acquire_timeout(self):\n        sem = locks.Semaphore(2)\n        yield sem.acquire()\n        yield sem.acquire()\n        acquire = sem.acquire(timedelta(seconds=0.01))\n        self.io_loop.call_later(0.02, sem.release)  # Too late.\n        yield gen.sleep(0.3)\n        with self.assertRaises(gen.TimeoutError):\n            yield acquire\n\n        sem.acquire()\n        f = asyncio.ensure_future(sem.acquire())\n        self.assertFalse(f.done())\n        sem.release()\n        self.assertTrue(f.done())\n\n    @gen_test\n    def test_acquire_timeout_preempted(self):\n        sem = locks.Semaphore(1)\n        yield sem.acquire()\n\n        # This fires before the wait times out.\n        self.io_loop.call_later(0.01, sem.release)\n        acquire = sem.acquire(timedelta(seconds=0.02))\n        yield gen.sleep(0.03)\n        yield acquire  # No TimeoutError.\n\n    def test_release_unacquired(self):\n        # Unbounded releases are allowed, and increment the semaphore's value.\n        sem = locks.Semaphore()\n        sem.release()\n        sem.release()\n\n        # Now the counter is 3. We can acquire three times before blocking.\n        self.assertTrue(asyncio.ensure_future(sem.acquire()).done())\n        self.assertTrue(asyncio.ensure_future(sem.acquire()).done())\n        self.assertTrue(asyncio.ensure_future(sem.acquire()).done())\n        self.assertFalse(asyncio.ensure_future(sem.acquire()).done())\n\n    @gen_test\n    def test_garbage_collection(self):\n        # Test that timed-out waiters are occasionally cleaned from the queue.\n        sem = locks.Semaphore(value=0)\n        futures = [\n            asyncio.ensure_future(sem.acquire(timedelta(seconds=0.01)))\n            for _ in range(101)\n        ]\n\n        future = asyncio.ensure_future(sem.acquire())\n        self.assertEqual(102, len(sem._waiters))\n\n        # Let first 101 waiters time out, triggering a collection.\n        yield gen.sleep(0.02)\n        self.assertEqual(1, len(sem._waiters))\n\n        # Final waiter is still active.\n        self.assertFalse(future.done())\n        sem.release()\n        self.assertTrue(future.done())\n\n        # Prevent \"Future exception was never retrieved\" messages.\n        for future in futures:\n            self.assertRaises(TimeoutError, future.result)\n\n\nclass SemaphoreContextManagerTest(AsyncTestCase):\n    @gen_test\n    def test_context_manager(self):\n        sem = locks.Semaphore()\n        with (yield sem.acquire()) as yielded:\n            self.assertIsNone(yielded)\n\n        # Semaphore was released and can be acquired again.\n        self.assertTrue(asyncio.ensure_future(sem.acquire()).done())\n\n    @gen_test\n    def test_context_manager_async_await(self):\n        # Repeat the above test using 'async with'.\n        sem = locks.Semaphore()\n\n        async def f():\n            async with sem as yielded:\n                self.assertIsNone(yielded)\n\n        yield f()\n\n        # Semaphore was released and can be acquired again.\n        self.assertTrue(asyncio.ensure_future(sem.acquire()).done())\n\n    @gen_test\n    def test_context_manager_exception(self):\n        sem = locks.Semaphore()\n        with self.assertRaises(ZeroDivisionError):\n            with (yield sem.acquire()):\n                1 / 0\n\n        # Semaphore was released and can be acquired again.\n        self.assertTrue(asyncio.ensure_future(sem.acquire()).done())\n\n    @gen_test\n    def test_context_manager_timeout(self):\n        sem = locks.Semaphore()\n        with (yield sem.acquire(timedelta(seconds=0.01))):\n            pass\n\n        # Semaphore was released and can be acquired again.\n        self.assertTrue(asyncio.ensure_future(sem.acquire()).done())\n\n    @gen_test\n    def test_context_manager_timeout_error(self):\n        sem = locks.Semaphore(value=0)\n        with self.assertRaises(gen.TimeoutError):\n            with (yield sem.acquire(timedelta(seconds=0.01))):\n                pass\n\n        # Counter is still 0.\n        self.assertFalse(asyncio.ensure_future(sem.acquire()).done())\n\n    @gen_test\n    def test_context_manager_contended(self):\n        sem = locks.Semaphore()\n        history = []\n\n        @gen.coroutine\n        def f(index):\n            with (yield sem.acquire()):\n                history.append(\"acquired %d\" % index)\n                yield gen.sleep(0.01)\n                history.append(\"release %d\" % index)\n\n        yield [f(i) for i in range(2)]\n\n        expected_history = []\n        for i in range(2):\n            expected_history.extend([\"acquired %d\" % i, \"release %d\" % i])\n\n        self.assertEqual(expected_history, history)\n\n    @gen_test\n    def test_yield_sem(self):\n        # Ensure we catch a \"with (yield sem)\", which should be\n        # \"with (yield sem.acquire())\".\n        with self.assertRaises(gen.BadYieldError):\n            with (yield locks.Semaphore()):\n                pass\n\n    def test_context_manager_misuse(self):\n        # Ensure we catch a \"with sem\", which should be\n        # \"with (yield sem.acquire())\".\n        with self.assertRaises(RuntimeError):\n            with locks.Semaphore():\n                pass\n\n\nclass BoundedSemaphoreTest(AsyncTestCase):\n    def test_release_unacquired(self):\n        sem = locks.BoundedSemaphore()\n        self.assertRaises(ValueError, sem.release)\n        # Value is 0.\n        sem.acquire()\n        # Block on acquire().\n        future = asyncio.ensure_future(sem.acquire())\n        self.assertFalse(future.done())\n        sem.release()\n        self.assertTrue(future.done())\n        # Value is 1.\n        sem.release()\n        self.assertRaises(ValueError, sem.release)\n\n\nclass LockTests(AsyncTestCase):\n    def test_repr(self):\n        lock = locks.Lock()\n        # No errors.\n        repr(lock)\n        lock.acquire()\n        repr(lock)\n\n    def test_acquire_release(self):\n        lock = locks.Lock()\n        self.assertTrue(asyncio.ensure_future(lock.acquire()).done())\n        future = asyncio.ensure_future(lock.acquire())\n        self.assertFalse(future.done())\n        lock.release()\n        self.assertTrue(future.done())\n\n    @gen_test\n    def test_acquire_fifo(self):\n        lock = locks.Lock()\n        self.assertTrue(asyncio.ensure_future(lock.acquire()).done())\n        N = 5\n        history = []\n\n        @gen.coroutine\n        def f(idx):\n            with (yield lock.acquire()):\n                history.append(idx)\n\n        futures = [f(i) for i in range(N)]\n        self.assertFalse(any(future.done() for future in futures))\n        lock.release()\n        yield futures\n        self.assertEqual(list(range(N)), history)\n\n    @gen_test\n    def test_acquire_fifo_async_with(self):\n        # Repeat the above test using `async with lock:`\n        # instead of `with (yield lock.acquire()):`.\n        lock = locks.Lock()\n        self.assertTrue(asyncio.ensure_future(lock.acquire()).done())\n        N = 5\n        history = []\n\n        async def f(idx):\n            async with lock:\n                history.append(idx)\n\n        futures = [f(i) for i in range(N)]\n        lock.release()\n        yield futures\n        self.assertEqual(list(range(N)), history)\n\n    @gen_test\n    def test_acquire_timeout(self):\n        lock = locks.Lock()\n        lock.acquire()\n        with self.assertRaises(gen.TimeoutError):\n            yield lock.acquire(timeout=timedelta(seconds=0.01))\n\n        # Still locked.\n        self.assertFalse(asyncio.ensure_future(lock.acquire()).done())\n\n    def test_multi_release(self):\n        lock = locks.Lock()\n        self.assertRaises(RuntimeError, lock.release)\n        lock.acquire()\n        lock.release()\n        self.assertRaises(RuntimeError, lock.release)\n\n    @gen_test\n    def test_yield_lock(self):\n        # Ensure we catch a \"with (yield lock)\", which should be\n        # \"with (yield lock.acquire())\".\n        with self.assertRaises(gen.BadYieldError):\n            with (yield locks.Lock()):\n                pass\n\n    def test_context_manager_misuse(self):\n        # Ensure we catch a \"with lock\", which should be\n        # \"with (yield lock.acquire())\".\n        with self.assertRaises(RuntimeError):\n            with locks.Lock():\n                pass\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tornado/test/log_test.py",
    "content": "#\n# Copyright 2012 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\nimport contextlib\nimport glob\nimport logging\nimport os\nimport re\nimport subprocess\nimport sys\nimport tempfile\nimport unittest\nimport warnings\n\nfrom tornado.escape import utf8\nfrom tornado.log import LogFormatter, define_logging_options, enable_pretty_logging\nfrom tornado.options import OptionParser\nfrom tornado.util import basestring_type\n\n\n@contextlib.contextmanager\ndef ignore_bytes_warning():\n    with warnings.catch_warnings():\n        warnings.simplefilter(\"ignore\", category=BytesWarning)\n        yield\n\n\nclass LogFormatterTest(unittest.TestCase):\n    # Matches the output of a single logging call (which may be multiple lines\n    # if a traceback was included, so we use the DOTALL option)\n    LINE_RE = re.compile(\n        b\"(?s)\\x01\\\\[E [0-9]{6} [0-9]{2}:[0-9]{2}:[0-9]{2} log_test:[0-9]+\\\\]\\x02 (.*)\"\n    )\n\n    def setUp(self):\n        self.formatter = LogFormatter(color=False)\n        # Fake color support.  We can't guarantee anything about the $TERM\n        # variable when the tests are run, so just patch in some values\n        # for testing.  (testing with color off fails to expose some potential\n        # encoding issues from the control characters)\n        self.formatter._colors = {logging.ERROR: \"\\u0001\"}\n        self.formatter._normal = \"\\u0002\"\n        # construct a Logger directly to bypass getLogger's caching\n        self.logger = logging.Logger(\"LogFormatterTest\")\n        self.logger.propagate = False\n        self.tempdir = tempfile.mkdtemp()\n        self.filename = os.path.join(self.tempdir, \"log.out\")\n        self.handler = self.make_handler(self.filename)\n        self.handler.setFormatter(self.formatter)\n        self.logger.addHandler(self.handler)\n\n    def tearDown(self):\n        self.handler.close()\n        os.unlink(self.filename)\n        os.rmdir(self.tempdir)\n\n    def make_handler(self, filename):\n        return logging.FileHandler(filename, encoding=\"utf-8\")\n\n    def get_output(self):\n        with open(self.filename, \"rb\") as f:\n            line = f.read().strip()\n            m = LogFormatterTest.LINE_RE.match(line)\n            if m:\n                return m.group(1)\n            else:\n                raise Exception(\"output didn't match regex: %r\" % line)\n\n    def test_basic_logging(self):\n        self.logger.error(\"foo\")\n        self.assertEqual(self.get_output(), b\"foo\")\n\n    def test_bytes_logging(self):\n        with ignore_bytes_warning():\n            # This will be \"\\xe9\" on python 2 or \"b'\\xe9'\" on python 3\n            self.logger.error(b\"\\xe9\")\n            self.assertEqual(self.get_output(), utf8(repr(b\"\\xe9\")))\n\n    def test_utf8_logging(self):\n        with ignore_bytes_warning():\n            self.logger.error(\"\\u00e9\".encode())\n        if issubclass(bytes, basestring_type):\n            # on python 2, utf8 byte strings (and by extension ascii byte\n            # strings) are passed through as-is.\n            self.assertEqual(self.get_output(), utf8(\"\\u00e9\"))\n        else:\n            # on python 3, byte strings always get repr'd even if\n            # they're ascii-only, so this degenerates into another\n            # copy of test_bytes_logging.\n            self.assertEqual(self.get_output(), utf8(repr(utf8(\"\\u00e9\"))))\n\n    def test_bytes_exception_logging(self):\n        try:\n            raise Exception(b\"\\xe9\")\n        except Exception:\n            self.logger.exception(\"caught exception\")\n        # This will be \"Exception: \\xe9\" on python 2 or\n        # \"Exception: b'\\xe9'\" on python 3.\n        output = self.get_output()\n        self.assertRegex(output, rb\"Exception.*\\\\xe9\")\n        # The traceback contains newlines, which should not have been escaped.\n        self.assertNotIn(rb\"\\n\", output)\n\n    def test_unicode_logging(self):\n        self.logger.error(\"\\u00e9\")\n        self.assertEqual(self.get_output(), utf8(\"\\u00e9\"))\n\n\nclass EnablePrettyLoggingTest(unittest.TestCase):\n    def setUp(self):\n        super().setUp()\n        self.options = OptionParser()\n        define_logging_options(self.options)\n        self.logger = logging.Logger(\"tornado.test.log_test.EnablePrettyLoggingTest\")\n        self.logger.propagate = False\n\n    def test_log_file(self):\n        tmpdir = tempfile.mkdtemp()\n        try:\n            self.options.log_file_prefix = tmpdir + \"/test_log\"\n            enable_pretty_logging(options=self.options, logger=self.logger)\n            self.assertEqual(1, len(self.logger.handlers))\n            self.logger.error(\"hello\")\n            self.logger.handlers[0].flush()\n            filenames = glob.glob(tmpdir + \"/test_log*\")\n            self.assertEqual(1, len(filenames))\n            with open(filenames[0], encoding=\"utf-8\") as f:\n                self.assertRegex(f.read(), r\"^\\[E [^]]*\\] hello$\")\n        finally:\n            for handler in self.logger.handlers:\n                handler.flush()\n                handler.close()\n            for filename in glob.glob(tmpdir + \"/test_log*\"):\n                os.unlink(filename)\n            os.rmdir(tmpdir)\n\n    def test_log_file_with_timed_rotating(self):\n        tmpdir = tempfile.mkdtemp()\n        try:\n            self.options.log_file_prefix = tmpdir + \"/test_log\"\n            self.options.log_rotate_mode = \"time\"\n            enable_pretty_logging(options=self.options, logger=self.logger)\n            self.logger.error(\"hello\")\n            self.logger.handlers[0].flush()\n            filenames = glob.glob(tmpdir + \"/test_log*\")\n            self.assertEqual(1, len(filenames))\n            with open(filenames[0], encoding=\"utf-8\") as f:\n                self.assertRegex(f.read(), r\"^\\[E [^]]*\\] hello$\")\n        finally:\n            for handler in self.logger.handlers:\n                handler.flush()\n                handler.close()\n            for filename in glob.glob(tmpdir + \"/test_log*\"):\n                os.unlink(filename)\n            os.rmdir(tmpdir)\n\n    def test_wrong_rotate_mode_value(self):\n        try:\n            self.options.log_file_prefix = \"some_path\"\n            self.options.log_rotate_mode = \"wrong_mode\"\n            self.assertRaises(\n                ValueError,\n                enable_pretty_logging,\n                options=self.options,\n                logger=self.logger,\n            )\n        finally:\n            for handler in self.logger.handlers:\n                handler.flush()\n                handler.close()\n\n\nclass LoggingOptionTest(unittest.TestCase):\n    \"\"\"Test the ability to enable and disable Tornado's logging hooks.\"\"\"\n\n    def logs_present(self, statement, args=None):\n        # Each test may manipulate and/or parse the options and then logs\n        # a line at the 'info' level.  This level is ignored in the\n        # logging module by default, but Tornado turns it on by default\n        # so it is the easiest way to tell whether tornado's logging hooks\n        # ran.\n        IMPORT = \"from tornado.options import options, parse_command_line\"\n        LOG_INFO = 'import logging; logging.info(\"hello\")'\n        program = \";\".join([IMPORT, statement, LOG_INFO])\n        proc = subprocess.Popen(\n            [sys.executable, \"-c\", program] + (args or []),\n            stdout=subprocess.PIPE,\n            stderr=subprocess.STDOUT,\n        )\n        stdout, stderr = proc.communicate()\n        self.assertEqual(proc.returncode, 0, \"process failed: %r\" % stdout)\n        return b\"hello\" in stdout\n\n    def test_default(self):\n        self.assertFalse(self.logs_present(\"pass\"))\n\n    def test_tornado_default(self):\n        self.assertTrue(self.logs_present(\"parse_command_line()\"))\n\n    def test_disable_command_line(self):\n        self.assertFalse(self.logs_present(\"parse_command_line()\", [\"--logging=none\"]))\n\n    def test_disable_command_line_case_insensitive(self):\n        self.assertFalse(self.logs_present(\"parse_command_line()\", [\"--logging=None\"]))\n\n    def test_disable_code_string(self):\n        self.assertFalse(\n            self.logs_present('options.logging = \"none\"; parse_command_line()')\n        )\n\n    def test_disable_code_none(self):\n        self.assertFalse(\n            self.logs_present(\"options.logging = None; parse_command_line()\")\n        )\n\n    def test_disable_override(self):\n        # command line trumps code defaults\n        self.assertTrue(\n            self.logs_present(\n                \"options.logging = None; parse_command_line()\", [\"--logging=info\"]\n            )\n        )\n"
  },
  {
    "path": "tornado/test/netutil_test.py",
    "content": "import errno\nimport signal\nimport socket\nimport sys\nimport time\nimport typing\nimport unittest\nfrom subprocess import Popen\n\nfrom tornado.netutil import (\n    BlockingResolver,\n    OverrideResolver,\n    ThreadedResolver,\n    bind_sockets,\n    is_valid_ip,\n)\nfrom tornado.test.util import abstract_base_test, skipIfNoNetwork\nfrom tornado.testing import AsyncTestCase, bind_unused_port, gen_test\n\ntry:\n    import pycares  # type: ignore\nexcept ImportError:\n    pycares = None\nelse:\n    from tornado.platform.caresresolver import CaresResolver\n\n\n@abstract_base_test\nclass _ResolverTestMixin(AsyncTestCase):\n    resolver: typing.Any = None\n\n    @gen_test\n    def test_localhost(self):\n        addrinfo = yield self.resolver.resolve(\"localhost\", 80, socket.AF_UNSPEC)\n        # Most of the time localhost resolves to either the ipv4 loopback\n        # address alone, or ipv4+ipv6. But some versions of pycares will only\n        # return the ipv6 version, so we have to check for either one alone.\n        self.assertTrue(\n            ((socket.AF_INET, (\"127.0.0.1\", 80)) in addrinfo)\n            or ((socket.AF_INET6, (\"::1\", 80)) in addrinfo),\n            f\"loopback address not found in {addrinfo}\",\n        )\n\n\n# It is impossible to quickly and consistently generate an error in name\n# resolution, so test this case separately, using mocks as needed.\n@abstract_base_test\nclass _ResolverErrorTestMixin(AsyncTestCase):\n    resolver: typing.Any = None\n\n    @gen_test\n    def test_bad_host(self):\n        with self.assertRaises(IOError):\n            yield self.resolver.resolve(\"an invalid domain\", 80, socket.AF_UNSPEC)\n\n\ndef _failing_getaddrinfo(*args):\n    \"\"\"Dummy implementation of getaddrinfo for use in mocks\"\"\"\n    raise socket.gaierror(errno.EIO, \"mock: lookup failed\")\n\n\n@skipIfNoNetwork\nclass BlockingResolverTest(_ResolverTestMixin):\n    def setUp(self):\n        super().setUp()\n        self.resolver = BlockingResolver()\n\n\n# getaddrinfo-based tests need mocking to reliably generate errors;\n# some configurations are slow to produce errors and take longer than\n# our default timeout.\nclass BlockingResolverErrorTest(_ResolverErrorTestMixin):\n    def setUp(self):\n        super().setUp()\n        self.resolver = BlockingResolver()\n        self.real_getaddrinfo = socket.getaddrinfo\n        socket.getaddrinfo = _failing_getaddrinfo\n\n    def tearDown(self):\n        socket.getaddrinfo = self.real_getaddrinfo\n        super().tearDown()\n\n\nclass OverrideResolverTest(_ResolverTestMixin):\n    def setUp(self):\n        super().setUp()\n        mapping = {\n            (\"google.com\", 80): (\"1.2.3.4\", 80),\n            (\"google.com\", 80, socket.AF_INET): (\"1.2.3.4\", 80),\n            (\"google.com\", 80, socket.AF_INET6): (\n                \"2a02:6b8:7c:40c:c51e:495f:e23a:3\",\n                80,\n            ),\n        }\n        self.resolver = OverrideResolver(BlockingResolver(), mapping)\n\n    @gen_test\n    def test_resolve_multiaddr(self):\n        result = yield self.resolver.resolve(\"google.com\", 80, socket.AF_INET)\n        self.assertIn((socket.AF_INET, (\"1.2.3.4\", 80)), result)\n\n        result = yield self.resolver.resolve(\"google.com\", 80, socket.AF_INET6)\n        self.assertIn(\n            (socket.AF_INET6, (\"2a02:6b8:7c:40c:c51e:495f:e23a:3\", 80, 0, 0)), result\n        )\n\n\n@skipIfNoNetwork\nclass ThreadedResolverTest(_ResolverTestMixin):\n    def setUp(self):\n        super().setUp()\n        self.resolver = ThreadedResolver()\n\n    def tearDown(self):\n        self.resolver.close()\n        # ThreadedResolver uses a global thread pool, so we have to shut it down\n        if ThreadedResolver._threadpool is not None:\n            ThreadedResolver._threadpool.shutdown(wait=True)\n            ThreadedResolver._threadpool = None\n        super().tearDown()\n\n\nclass ThreadedResolverErrorTest(_ResolverErrorTestMixin):\n    def setUp(self):\n        super().setUp()\n        self.resolver = BlockingResolver()\n        self.real_getaddrinfo = socket.getaddrinfo\n        socket.getaddrinfo = _failing_getaddrinfo\n\n    def tearDown(self):\n        socket.getaddrinfo = self.real_getaddrinfo\n        super().tearDown()\n\n\n@skipIfNoNetwork\n@unittest.skipIf(sys.platform == \"win32\", \"preexec_fn not available on win32\")\nclass ThreadedResolverImportTest(unittest.TestCase):\n    def test_import(self):\n        TIMEOUT = 5\n\n        # Test for a deadlock when importing a module that runs the\n        # ThreadedResolver at import-time. See resolve_test.py for\n        # full explanation.\n        command = [sys.executable, \"-c\", \"import tornado.test.resolve_test_helper\"]\n\n        start = time.time()\n        popen = Popen(command, preexec_fn=lambda: signal.alarm(TIMEOUT))\n        while time.time() - start < TIMEOUT:\n            return_code = popen.poll()\n            if return_code is not None:\n                self.assertEqual(0, return_code)\n                return  # Success.\n            time.sleep(0.05)\n\n        self.fail(\"import timed out\")\n\n\n# We do not test errors with CaresResolver:\n# Some DNS-hijacking ISPs (e.g. Time Warner) return non-empty results\n# with an NXDOMAIN status code.  Most resolvers treat this as an error;\n# C-ares returns the results, making the \"bad_host\" tests unreliable.\n# C-ares will try to resolve even malformed names, such as the\n# name with spaces used in this test.\n@skipIfNoNetwork\n@unittest.skipIf(pycares is None, \"pycares module not present\")\n@unittest.skipIf(sys.platform == \"win32\", \"pycares doesn't return loopback on windows\")\n@unittest.skipIf(sys.platform == \"darwin\", \"pycares doesn't return 127.0.0.1 on darwin\")\nclass CaresResolverTest(_ResolverTestMixin):\n    def setUp(self):\n        super().setUp()\n        self.resolver = CaresResolver()\n\n\nclass IsValidIPTest(unittest.TestCase):\n    def test_is_valid_ip(self):\n        self.assertTrue(is_valid_ip(\"127.0.0.1\"))\n        self.assertTrue(is_valid_ip(\"4.4.4.4\"))\n        self.assertTrue(is_valid_ip(\"::1\"))\n        self.assertTrue(is_valid_ip(\"2620:0:1cfe:face:b00c::3\"))\n        self.assertFalse(is_valid_ip(\"www.google.com\"))\n        self.assertFalse(is_valid_ip(\"localhost\"))\n        self.assertFalse(is_valid_ip(\"4.4.4.4<\"))\n        self.assertFalse(is_valid_ip(\" 127.0.0.1\"))\n        self.assertFalse(is_valid_ip(\"\"))\n        self.assertFalse(is_valid_ip(\" \"))\n        self.assertFalse(is_valid_ip(\"\\n\"))\n        self.assertFalse(is_valid_ip(\"\\x00\"))\n        self.assertFalse(is_valid_ip(\"a\" * 100))\n\n\nclass TestPortAllocation(unittest.TestCase):\n    def test_same_port_allocation(self):\n        sockets = bind_sockets(0, \"localhost\")\n        try:\n            port = sockets[0].getsockname()[1]\n            self.assertTrue(all(s.getsockname()[1] == port for s in sockets[1:]))\n        finally:\n            for sock in sockets:\n                sock.close()\n\n    @unittest.skipIf(\n        not hasattr(socket, \"SO_REUSEPORT\"), \"SO_REUSEPORT is not supported\"\n    )\n    def test_reuse_port(self):\n        sockets: list[socket.socket] = []\n        sock, port = bind_unused_port(reuse_port=True)\n        try:\n            sockets = bind_sockets(port, \"127.0.0.1\", reuse_port=True)\n            self.assertTrue(all(s.getsockname()[1] == port for s in sockets))\n        finally:\n            sock.close()\n            for sock in sockets:\n                sock.close()\n"
  },
  {
    "path": "tornado/test/options_test.cfg",
    "content": "port=443\nport=443\nusername='李康'\n\nfoo_bar='a'\n\nmy_path = __file__\n"
  },
  {
    "path": "tornado/test/options_test.py",
    "content": "import datetime\nimport os\nimport sys\nimport unittest\nfrom io import StringIO\nfrom unittest import mock\n\nfrom tornado.options import Error, OptionParser\nfrom tornado.util import basestring_type\n\n\nclass Email:\n    def __init__(self, value):\n        if isinstance(value, str) and \"@\" in value:\n            self._value = value\n        else:\n            raise ValueError()\n\n    @property\n    def value(self):\n        return self._value\n\n\nclass OptionsTest(unittest.TestCase):\n    def test_parse_command_line(self):\n        options = OptionParser()\n        options.define(\"port\", default=80)\n        options.parse_command_line([\"main.py\", \"--port=443\"])\n        self.assertEqual(options.port, 443)\n\n    def test_parse_config_file(self):\n        options = OptionParser()\n        options.define(\"port\", default=80)\n        options.define(\"username\", default=\"foo\")\n        options.define(\"my_path\")\n        config_path = os.path.join(\n            os.path.dirname(os.path.abspath(__file__)), \"options_test.cfg\"\n        )\n        options.parse_config_file(config_path)\n        self.assertEqual(options.port, 443)\n        self.assertEqual(options.username, \"李康\")\n        self.assertEqual(options.my_path, config_path)\n\n    def test_parse_callbacks(self):\n        options = OptionParser()\n        self.called = False\n\n        def callback():\n            self.called = True\n\n        options.add_parse_callback(callback)\n\n        # non-final parse doesn't run callbacks\n        options.parse_command_line([\"main.py\"], final=False)\n        self.assertFalse(self.called)\n\n        # final parse does\n        options.parse_command_line([\"main.py\"])\n        self.assertTrue(self.called)\n\n        # callbacks can be run more than once on the same options\n        # object if there are multiple final parses\n        self.called = False\n        options.parse_command_line([\"main.py\"])\n        self.assertTrue(self.called)\n\n    def test_help(self):\n        options = OptionParser()\n        try:\n            orig_stderr = sys.stderr\n            sys.stderr = StringIO()\n            with self.assertRaises(SystemExit):\n                options.parse_command_line([\"main.py\", \"--help\"])\n            usage = sys.stderr.getvalue()\n        finally:\n            sys.stderr = orig_stderr\n        self.assertIn(\"Usage:\", usage)\n\n    def test_subcommand(self):\n        base_options = OptionParser()\n        base_options.define(\"verbose\", default=False)\n        sub_options = OptionParser()\n        sub_options.define(\"foo\", type=str)\n        rest = base_options.parse_command_line(\n            [\"main.py\", \"--verbose\", \"subcommand\", \"--foo=bar\"]\n        )\n        self.assertEqual(rest, [\"subcommand\", \"--foo=bar\"])\n        self.assertTrue(base_options.verbose)\n        rest2 = sub_options.parse_command_line(rest)\n        self.assertEqual(rest2, [])\n        self.assertEqual(sub_options.foo, \"bar\")\n\n        # the two option sets are distinct\n        try:\n            orig_stderr = sys.stderr\n            sys.stderr = StringIO()\n            with self.assertRaises(Error):\n                sub_options.parse_command_line([\"subcommand\", \"--verbose\"])\n        finally:\n            sys.stderr = orig_stderr\n\n    def test_setattr(self):\n        options = OptionParser()\n        options.define(\"foo\", default=1, type=int)\n        options.foo = 2\n        self.assertEqual(options.foo, 2)\n\n    def test_setattr_type_check(self):\n        # setattr requires that options be the right type and doesn't\n        # parse from string formats.\n        options = OptionParser()\n        options.define(\"foo\", default=1, type=int)\n        with self.assertRaises(Error):\n            options.foo = \"2\"\n\n    def test_setattr_with_callback(self):\n        values: list[int] = []\n        options = OptionParser()\n        options.define(\"foo\", default=1, type=int, callback=values.append)\n        options.foo = 2\n        self.assertEqual(values, [2])\n\n    def _sample_options(self):\n        options = OptionParser()\n        options.define(\"a\", default=1)\n        options.define(\"b\", default=2)\n        return options\n\n    def test_iter(self):\n        options = self._sample_options()\n        # OptionParsers always define 'help'.\n        self.assertEqual({\"a\", \"b\", \"help\"}, set(iter(options)))\n\n    def test_getitem(self):\n        options = self._sample_options()\n        self.assertEqual(1, options[\"a\"])\n\n    def test_setitem(self):\n        options = OptionParser()\n        options.define(\"foo\", default=1, type=int)\n        options[\"foo\"] = 2\n        self.assertEqual(options[\"foo\"], 2)\n\n    def test_items(self):\n        options = self._sample_options()\n        # OptionParsers always define 'help'.\n        expected = [(\"a\", 1), (\"b\", 2), (\"help\", options.help)]\n        actual = sorted(options.items())\n        self.assertEqual(expected, actual)\n\n    def test_as_dict(self):\n        options = self._sample_options()\n        expected = {\"a\": 1, \"b\": 2, \"help\": options.help}\n        self.assertEqual(expected, options.as_dict())\n\n    def test_group_dict(self):\n        options = OptionParser()\n        options.define(\"a\", default=1)\n        options.define(\"b\", group=\"b_group\", default=2)\n\n        frame = sys._getframe(0)\n        this_file = frame.f_code.co_filename\n        self.assertEqual({\"b_group\", \"\", this_file}, options.groups())\n\n        b_group_dict = options.group_dict(\"b_group\")\n        self.assertEqual({\"b\": 2}, b_group_dict)\n\n        self.assertEqual({}, options.group_dict(\"nonexistent\"))\n\n    def test_mock_patch(self):\n        # ensure that our setattr hooks don't interfere with mock.patch\n        options = OptionParser()\n        options.define(\"foo\", default=1)\n        options.parse_command_line([\"main.py\", \"--foo=2\"])\n        self.assertEqual(options.foo, 2)\n\n        with mock.patch.object(options.mockable(), \"foo\", 3):\n            self.assertEqual(options.foo, 3)\n        self.assertEqual(options.foo, 2)\n\n        # Try nested patches mixed with explicit sets\n        with mock.patch.object(options.mockable(), \"foo\", 4):\n            self.assertEqual(options.foo, 4)\n            options.foo = 5\n            self.assertEqual(options.foo, 5)\n            with mock.patch.object(options.mockable(), \"foo\", 6):\n                self.assertEqual(options.foo, 6)\n            self.assertEqual(options.foo, 5)\n        self.assertEqual(options.foo, 2)\n\n    def _define_options(self):\n        options = OptionParser()\n        options.define(\"str\", type=str)\n        options.define(\"basestring\", type=basestring_type)\n        options.define(\"int\", type=int)\n        options.define(\"float\", type=float)\n        options.define(\"datetime\", type=datetime.datetime)\n        options.define(\"timedelta\", type=datetime.timedelta)\n        options.define(\"email\", type=Email)\n        options.define(\"list-of-int\", type=int, multiple=True)\n        options.define(\"list-of-str\", type=str, multiple=True)\n        return options\n\n    def _check_options_values(self, options):\n        self.assertEqual(options.str, \"asdf\")\n        self.assertEqual(options.basestring, \"qwer\")\n        self.assertEqual(options.int, 42)\n        self.assertEqual(options.float, 1.5)\n        self.assertEqual(options.datetime, datetime.datetime(2013, 4, 28, 5, 16))\n        self.assertEqual(options.timedelta, datetime.timedelta(seconds=45))\n        self.assertEqual(options.email.value, \"tornado@web.com\")\n        self.assertTrue(isinstance(options.email, Email))\n        self.assertEqual(options.list_of_int, [1, 2, 3])\n        self.assertEqual(options.list_of_str, [\"a\", \"b\", \"c\"])\n\n    def test_types(self):\n        options = self._define_options()\n        options.parse_command_line(\n            [\n                \"main.py\",\n                \"--str=asdf\",\n                \"--basestring=qwer\",\n                \"--int=42\",\n                \"--float=1.5\",\n                \"--datetime=2013-04-28 05:16\",\n                \"--timedelta=45s\",\n                \"--email=tornado@web.com\",\n                \"--list-of-int=1,2,3\",\n                \"--list-of-str=a,b,c\",\n            ]\n        )\n        self._check_options_values(options)\n\n    def test_types_with_conf_file(self):\n        for config_file_name in (\n            \"options_test_types.cfg\",\n            \"options_test_types_str.cfg\",\n        ):\n            options = self._define_options()\n            options.parse_config_file(\n                os.path.join(os.path.dirname(__file__), config_file_name)\n            )\n            self._check_options_values(options)\n\n    def test_multiple_string(self):\n        options = OptionParser()\n        options.define(\"foo\", type=str, multiple=True)\n        options.parse_command_line([\"main.py\", \"--foo=a,b,c\"])\n        self.assertEqual(options.foo, [\"a\", \"b\", \"c\"])\n\n    def test_multiple_int(self):\n        options = OptionParser()\n        options.define(\"foo\", type=int, multiple=True)\n        options.parse_command_line([\"main.py\", \"--foo=1,3,5:7\"])\n        self.assertEqual(options.foo, [1, 3, 5, 6, 7])\n\n    def test_error_redefine(self):\n        options = OptionParser()\n        options.define(\"foo\")\n        with self.assertRaises(Error) as cm:\n            options.define(\"foo\")\n        self.assertRegex(str(cm.exception), \"Option.*foo.*already defined\")\n\n    def test_error_redefine_underscore(self):\n        # Ensure that the dash/underscore normalization doesn't\n        # interfere with the redefinition error.\n        tests = [\n            (\"foo-bar\", \"foo-bar\"),\n            (\"foo_bar\", \"foo_bar\"),\n            (\"foo-bar\", \"foo_bar\"),\n            (\"foo_bar\", \"foo-bar\"),\n        ]\n        for a, b in tests:\n            with self.subTest(self, a=a, b=b):\n                options = OptionParser()\n                options.define(a)\n                with self.assertRaises(Error) as cm:\n                    options.define(b)\n                self.assertRegex(str(cm.exception), \"Option.*foo.bar.*already defined\")\n\n    def test_dash_underscore_cli(self):\n        # Dashes and underscores should be interchangeable.\n        for defined_name in [\"foo-bar\", \"foo_bar\"]:\n            for flag in [\"--foo-bar=a\", \"--foo_bar=a\"]:\n                options = OptionParser()\n                options.define(defined_name)\n                options.parse_command_line([\"main.py\", flag])\n                # Attr-style access always uses underscores.\n                self.assertEqual(options.foo_bar, \"a\")\n                # Dict-style access allows both.\n                self.assertEqual(options[\"foo-bar\"], \"a\")\n                self.assertEqual(options[\"foo_bar\"], \"a\")\n\n    def test_dash_underscore_file(self):\n        # No matter how an option was defined, it can be set with underscores\n        # in a config file.\n        for defined_name in [\"foo-bar\", \"foo_bar\"]:\n            options = OptionParser()\n            options.define(defined_name)\n            options.parse_config_file(\n                os.path.join(os.path.dirname(__file__), \"options_test.cfg\")\n            )\n            self.assertEqual(options.foo_bar, \"a\")\n\n    def test_dash_underscore_introspection(self):\n        # Original names are preserved in introspection APIs.\n        options = OptionParser()\n        options.define(\"with-dash\", group=\"g\")\n        options.define(\"with_underscore\", group=\"g\")\n        all_options = [\"help\", \"with-dash\", \"with_underscore\"]\n        self.assertEqual(sorted(options), all_options)\n        self.assertEqual(sorted(k for (k, v) in options.items()), all_options)\n        self.assertEqual(sorted(options.as_dict().keys()), all_options)\n\n        self.assertEqual(\n            sorted(options.group_dict(\"g\")), [\"with-dash\", \"with_underscore\"]\n        )\n\n        # --help shows CLI-style names with dashes.\n        buf = StringIO()\n        options.print_help(buf)\n        self.assertIn(\"--with-dash\", buf.getvalue())\n        self.assertIn(\"--with-underscore\", buf.getvalue())\n"
  },
  {
    "path": "tornado/test/options_test_types.cfg",
    "content": "from datetime import datetime, timedelta\nfrom tornado.test.options_test import Email\n\nstr = 'asdf'\nbasestring = 'qwer'\nint = 42\nfloat = 1.5\ndatetime = datetime(2013, 4, 28, 5, 16)\ntimedelta = timedelta(0, 45)\nemail = Email('tornado@web.com')\nlist_of_int = [1, 2, 3]\nlist_of_str = [\"a\", \"b\", \"c\"]\n"
  },
  {
    "path": "tornado/test/options_test_types_str.cfg",
    "content": "str = 'asdf'\nbasestring = 'qwer'\nint = 42\nfloat = 1.5\ndatetime = '2013-04-28 05:16'\ntimedelta = '45s'\nemail = 'tornado@web.com'\nlist_of_int = '1,2,3'\nlist_of_str = 'a,b,c'\n"
  },
  {
    "path": "tornado/test/process_test.py",
    "content": "import asyncio\nimport logging\nimport os\nimport signal\nimport subprocess\nimport sys\nimport time\nimport unittest\n\nfrom tornado.httpclient import HTTPClient, HTTPError\nfrom tornado.httpserver import HTTPServer\nfrom tornado.log import gen_log\nfrom tornado.process import Subprocess, fork_processes, task_id\nfrom tornado.simple_httpclient import SimpleAsyncHTTPClient\nfrom tornado.test.util import skipIfNonUnix\nfrom tornado.testing import AsyncTestCase, ExpectLog, bind_unused_port, gen_test\nfrom tornado.web import Application, RequestHandler\n\n\n# Not using AsyncHTTPTestCase because we need control over the IOLoop.\n@skipIfNonUnix\nclass ProcessTest(unittest.TestCase):\n    def get_app(self):\n        class ProcessHandler(RequestHandler):\n            def get(self):\n                if self.get_argument(\"exit\", None):\n                    # must use os._exit instead of sys.exit so unittest's\n                    # exception handler doesn't catch it\n                    os._exit(int(self.get_argument(\"exit\")))\n                if self.get_argument(\"signal\", None):\n                    os.kill(os.getpid(), int(self.get_argument(\"signal\")))\n                self.write(str(os.getpid()))\n\n        return Application([(\"/\", ProcessHandler)])\n\n    def tearDown(self):\n        if task_id() is not None:\n            # We're in a child process, and probably got to this point\n            # via an uncaught exception.  If we return now, both\n            # processes will continue with the rest of the test suite.\n            # Exit now so the parent process will restart the child\n            # (since we don't have a clean way to signal failure to\n            # the parent that won't restart)\n            logging.error(\"aborting child process from tearDown\")\n            logging.shutdown()\n            os._exit(1)\n        # In the surviving process, clear the alarm we set earlier\n        signal.alarm(0)\n        super().tearDown()\n\n    def test_multi_process(self):\n        # This test doesn't work on twisted because we use the global\n        # reactor and don't restore it to a sane state after the fork\n        # (asyncio has the same issue, but we have a special case in\n        # place for it).\n        with ExpectLog(\n            gen_log, \"(Starting .* processes|child .* exited|uncaught exception)\"\n        ):\n            sock, port = bind_unused_port()\n\n            def get_url(path):\n                return \"http://127.0.0.1:%d%s\" % (port, path)\n\n            # ensure that none of these processes live too long\n            signal.alarm(5)  # master process\n            try:\n                id = fork_processes(3, max_restarts=3)\n                self.assertIsNotNone(id)\n                signal.alarm(5)  # child processes\n            except SystemExit as e:\n                # if we exit cleanly from fork_processes, all the child processes\n                # finished with status 0\n                self.assertEqual(e.code, 0)\n                self.assertIsNone(task_id())\n                sock.close()\n                return\n            try:\n                if id in (0, 1):\n                    self.assertEqual(id, task_id())\n\n                    async def f():\n                        server = HTTPServer(self.get_app())\n                        server.add_sockets([sock])\n                        await asyncio.Event().wait()\n\n                    asyncio.run(f())\n                elif id == 2:\n                    self.assertEqual(id, task_id())\n                    sock.close()\n                    # Always use SimpleAsyncHTTPClient here; the curl\n                    # version appears to get confused sometimes if the\n                    # connection gets closed before it's had a chance to\n                    # switch from writing mode to reading mode.\n                    client = HTTPClient(SimpleAsyncHTTPClient)\n\n                    def fetch(url, fail_ok=False):\n                        try:\n                            return client.fetch(get_url(url))\n                        except HTTPError as e:\n                            if not (fail_ok and e.code == 599):\n                                raise\n\n                    # Make two processes exit abnormally\n                    fetch(\"/?exit=2\", fail_ok=True)\n                    fetch(\"/?exit=3\", fail_ok=True)\n\n                    # They've been restarted, so a new fetch will work\n                    int(fetch(\"/\").body)\n\n                    # Now the same with signals\n                    # Disabled because on the mac a process dying with a signal\n                    # can trigger an \"Application exited abnormally; send error\n                    # report to Apple?\" prompt.\n                    # fetch(\"/?signal=%d\" % signal.SIGTERM, fail_ok=True)\n                    # fetch(\"/?signal=%d\" % signal.SIGABRT, fail_ok=True)\n                    # int(fetch(\"/\").body)\n\n                    # Now kill them normally so they won't be restarted\n                    fetch(\"/?exit=0\", fail_ok=True)\n                    # One process left; watch it's pid change\n                    pid = int(fetch(\"/\").body)\n                    fetch(\"/?exit=4\", fail_ok=True)\n                    pid2 = int(fetch(\"/\").body)\n                    self.assertNotEqual(pid, pid2)\n\n                    # Kill the last one so we shut down cleanly\n                    fetch(\"/?exit=0\", fail_ok=True)\n\n                    os._exit(0)\n            except Exception:\n                logging.error(\"exception in child process %d\", id, exc_info=True)\n                raise\n\n\n@skipIfNonUnix\nclass SubprocessTest(AsyncTestCase):\n    def term_and_wait(self, subproc):\n        subproc.proc.terminate()\n        subproc.proc.wait()\n\n    @gen_test\n    def test_subprocess(self):\n        subproc = Subprocess(\n            [sys.executable, \"-u\", \"-i\", \"-I\"],\n            stdin=Subprocess.STREAM,\n            stdout=Subprocess.STREAM,\n            stderr=subprocess.STDOUT,\n        )\n        self.addCleanup(lambda: self.term_and_wait(subproc))\n        self.addCleanup(subproc.stdout.close)\n        self.addCleanup(subproc.stdin.close)\n        yield subproc.stdout.read_until(b\">>> \")\n        subproc.stdin.write(b\"print('hello')\\n\")\n        data = yield subproc.stdout.read_until(b\"\\n\")\n        self.assertEqual(data, b\"hello\\n\")\n\n        yield subproc.stdout.read_until(b\">>> \")\n        subproc.stdin.write(b\"raise SystemExit\\n\")\n        data = yield subproc.stdout.read_until_close()\n        self.assertEqual(data, b\"\")\n\n    @gen_test\n    def test_close_stdin(self):\n        # Close the parent's stdin handle and see that the child recognizes it.\n        subproc = Subprocess(\n            [sys.executable, \"-u\", \"-i\", \"-I\"],\n            stdin=Subprocess.STREAM,\n            stdout=Subprocess.STREAM,\n            stderr=subprocess.STDOUT,\n        )\n        self.addCleanup(lambda: self.term_and_wait(subproc))\n        yield subproc.stdout.read_until(b\">>> \")\n        subproc.stdin.close()\n        data = yield subproc.stdout.read_until_close()\n        self.assertEqual(data, b\"\\n\")\n\n    @gen_test\n    def test_stderr(self):\n        # This test is mysteriously flaky on twisted: it succeeds, but logs\n        # an error of EBADF on closing a file descriptor.\n        subproc = Subprocess(\n            [sys.executable, \"-u\", \"-c\", r\"import sys; sys.stderr.write('hello\\n')\"],\n            stderr=Subprocess.STREAM,\n        )\n        self.addCleanup(lambda: self.term_and_wait(subproc))\n        data = yield subproc.stderr.read_until(b\"\\n\")\n        self.assertEqual(data, b\"hello\\n\")\n        # More mysterious EBADF: This fails if done with self.addCleanup instead of here.\n        subproc.stderr.close()\n\n    def test_sigchild(self):\n        Subprocess.initialize()\n        self.addCleanup(Subprocess.uninitialize)\n        subproc = Subprocess([sys.executable, \"-c\", \"pass\"])\n        subproc.set_exit_callback(self.stop)\n        ret = self.wait()\n        self.assertEqual(ret, 0)\n        self.assertEqual(subproc.returncode, ret)\n\n    @gen_test\n    def test_sigchild_future(self):\n        Subprocess.initialize()\n        self.addCleanup(Subprocess.uninitialize)\n        subproc = Subprocess([sys.executable, \"-c\", \"pass\"])\n        ret = yield subproc.wait_for_exit()\n        self.assertEqual(ret, 0)\n        self.assertEqual(subproc.returncode, ret)\n\n    def test_sigchild_signal(self):\n        Subprocess.initialize()\n        self.addCleanup(Subprocess.uninitialize)\n        subproc = Subprocess(\n            [sys.executable, \"-c\", \"import time; time.sleep(30)\"],\n            stdout=Subprocess.STREAM,\n        )\n        self.addCleanup(subproc.stdout.close)\n        subproc.set_exit_callback(self.stop)\n\n        # For unclear reasons, killing a process too soon after\n        # creating it can result in an exit status corresponding to\n        # SIGKILL instead of the actual signal involved. This has been\n        # observed on macOS 10.15 with Python 3.8 installed via brew,\n        # but not with the system-installed Python 3.7.\n        time.sleep(0.1)\n\n        os.kill(subproc.pid, signal.SIGTERM)\n        try:\n            ret = self.wait()\n        except AssertionError:\n            # We failed to get the termination signal. This test is\n            # occasionally flaky on pypy, so try to get a little more\n            # information: did the process close its stdout\n            # (indicating that the problem is in the parent process's\n            # signal handling) or did the child process somehow fail\n            # to terminate?\n            fut = subproc.stdout.read_until_close()\n            fut.add_done_callback(lambda f: self.stop())  # type: ignore\n            try:\n                self.wait()\n            except AssertionError:\n                raise AssertionError(\"subprocess failed to terminate\")\n            else:\n                raise AssertionError(\n                    \"subprocess closed stdout but failed to \" \"get termination signal\"\n                )\n        self.assertEqual(subproc.returncode, ret)\n        self.assertEqual(ret, -signal.SIGTERM)\n\n    @gen_test\n    def test_wait_for_exit_raise(self):\n        Subprocess.initialize()\n        self.addCleanup(Subprocess.uninitialize)\n        subproc = Subprocess([sys.executable, \"-c\", \"import sys; sys.exit(1)\"])\n        with self.assertRaises(subprocess.CalledProcessError) as cm:\n            yield subproc.wait_for_exit()\n        self.assertEqual(cm.exception.returncode, 1)\n\n    @gen_test\n    def test_wait_for_exit_raise_disabled(self):\n        Subprocess.initialize()\n        self.addCleanup(Subprocess.uninitialize)\n        subproc = Subprocess([sys.executable, \"-c\", \"import sys; sys.exit(1)\"])\n        ret = yield subproc.wait_for_exit(raise_error=False)\n        self.assertEqual(ret, 1)\n"
  },
  {
    "path": "tornado/test/queues_test.py",
    "content": "# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\nimport asyncio\nimport unittest\nfrom datetime import timedelta\nfrom random import random\n\nfrom tornado import gen, queues\nfrom tornado.gen import TimeoutError\nfrom tornado.testing import AsyncTestCase, gen_test\n\n\nclass QueueBasicTest(AsyncTestCase):\n    def test_repr_and_str(self):\n        q: queues.Queue[None] = queues.Queue(maxsize=1)\n        self.assertIn(hex(id(q)), repr(q))\n        self.assertNotIn(hex(id(q)), str(q))\n        q.get()\n\n        for q_str in repr(q), str(q):\n            self.assertTrue(q_str.startswith(\"<Queue\"))\n            self.assertIn(\"maxsize=1\", q_str)\n            self.assertIn(\"getters[1]\", q_str)\n            self.assertNotIn(\"putters\", q_str)\n            self.assertNotIn(\"tasks\", q_str)\n\n        q.put(None)\n        q.put(None)\n        # Now the queue is full, this putter blocks.\n        q.put(None)\n\n        for q_str in repr(q), str(q):\n            self.assertNotIn(\"getters\", q_str)\n            self.assertIn(\"putters[1]\", q_str)\n            self.assertIn(\"tasks=2\", q_str)\n\n    def test_order(self):\n        q: queues.Queue[int] = queues.Queue()\n        for i in [1, 3, 2]:\n            q.put_nowait(i)\n\n        items = [q.get_nowait() for _ in range(3)]\n        self.assertEqual([1, 3, 2], items)\n\n    @gen_test\n    def test_maxsize(self):\n        self.assertRaises(TypeError, queues.Queue, maxsize=None)\n        self.assertRaises(ValueError, queues.Queue, maxsize=-1)\n\n        q: queues.Queue[int] = queues.Queue(maxsize=2)\n        self.assertTrue(q.empty())\n        self.assertFalse(q.full())\n        self.assertEqual(2, q.maxsize)\n        self.assertTrue(q.put(0).done())\n        self.assertTrue(q.put(1).done())\n        self.assertFalse(q.empty())\n        self.assertTrue(q.full())\n        put2 = q.put(2)\n        self.assertFalse(put2.done())\n        self.assertEqual(0, (yield q.get()))  # Make room.\n        self.assertTrue(put2.done())\n        self.assertFalse(q.empty())\n        self.assertTrue(q.full())\n\n\nclass QueueGetTest(AsyncTestCase):\n    @gen_test\n    def test_blocking_get(self):\n        q: queues.Queue[int] = queues.Queue()\n        q.put_nowait(0)\n        self.assertEqual(0, (yield q.get()))\n\n    def test_nonblocking_get(self):\n        q: queues.Queue[int] = queues.Queue()\n        q.put_nowait(0)\n        self.assertEqual(0, q.get_nowait())\n\n    def test_nonblocking_get_exception(self):\n        q: queues.Queue[int] = queues.Queue()\n        self.assertRaises(queues.QueueEmpty, q.get_nowait)\n\n    @gen_test\n    def test_get_with_putters(self):\n        q: queues.Queue[int] = queues.Queue(1)\n        q.put_nowait(0)\n        put = q.put(1)\n        self.assertEqual(0, (yield q.get()))\n        self.assertIsNone((yield put))\n\n    @gen_test\n    def test_blocking_get_wait(self):\n        q: queues.Queue[int] = queues.Queue()\n        q.put(0)\n        self.io_loop.call_later(0.01, q.put_nowait, 1)\n        self.io_loop.call_later(0.02, q.put_nowait, 2)\n        self.assertEqual(0, (yield q.get(timeout=timedelta(seconds=1))))\n        self.assertEqual(1, (yield q.get(timeout=timedelta(seconds=1))))\n\n    @gen_test\n    def test_get_timeout(self):\n        q: queues.Queue[int] = queues.Queue()\n        get_timeout = q.get(timeout=timedelta(seconds=0.01))\n        get = q.get()\n        with self.assertRaises(TimeoutError):\n            yield get_timeout\n\n        q.put_nowait(0)\n        self.assertEqual(0, (yield get))\n\n    @gen_test\n    def test_get_timeout_preempted(self):\n        q: queues.Queue[int] = queues.Queue()\n        get = q.get(timeout=timedelta(seconds=0.01))\n        q.put(0)\n        yield gen.sleep(0.02)\n        self.assertEqual(0, (yield get))\n\n    @gen_test\n    def test_get_clears_timed_out_putters(self):\n        q: queues.Queue[int] = queues.Queue(1)\n        # First putter succeeds, remainder block.\n        putters = [q.put(i, timedelta(seconds=0.01)) for i in range(10)]\n        put = q.put(10)\n        self.assertEqual(10, len(q._putters))\n        yield gen.sleep(0.02)\n        self.assertEqual(10, len(q._putters))\n        self.assertFalse(put.done())  # Final waiter is still active.\n        q.put(11)\n        self.assertEqual(0, (yield q.get()))  # get() clears the waiters.\n        self.assertEqual(1, len(q._putters))\n        for putter in putters[1:]:\n            self.assertRaises(TimeoutError, putter.result)\n\n    @gen_test\n    def test_get_clears_timed_out_getters(self):\n        q: queues.Queue[int] = queues.Queue()\n        getters = [\n            asyncio.ensure_future(q.get(timedelta(seconds=0.01))) for _ in range(10)\n        ]\n        get = asyncio.ensure_future(q.get())\n        self.assertEqual(11, len(q._getters))\n        yield gen.sleep(0.02)\n        self.assertEqual(11, len(q._getters))\n        self.assertFalse(get.done())  # Final waiter is still active.\n        q.get()  # get() clears the waiters.\n        self.assertEqual(2, len(q._getters))\n        for getter in getters:\n            self.assertRaises(TimeoutError, getter.result)\n\n    @gen_test\n    def test_async_for(self):\n        q: queues.Queue[int] = queues.Queue()\n        for i in range(5):\n            q.put(i)\n\n        async def f():\n            results = []\n            async for i in q:\n                results.append(i)\n                if i == 4:\n                    return results\n\n        results = yield f()\n        self.assertEqual(results, list(range(5)))\n\n\nclass QueuePutTest(AsyncTestCase):\n    @gen_test\n    def test_blocking_put(self):\n        q: queues.Queue[int] = queues.Queue()\n        q.put(0)\n        self.assertEqual(0, q.get_nowait())\n\n    def test_nonblocking_put_exception(self):\n        q: queues.Queue[int] = queues.Queue(1)\n        q.put(0)\n        self.assertRaises(queues.QueueFull, q.put_nowait, 1)\n\n    @gen_test\n    def test_put_with_getters(self):\n        q: queues.Queue[int] = queues.Queue()\n        get0 = q.get()\n        get1 = q.get()\n        yield q.put(0)\n        self.assertEqual(0, (yield get0))\n        yield q.put(1)\n        self.assertEqual(1, (yield get1))\n\n    @gen_test\n    def test_nonblocking_put_with_getters(self):\n        q: queues.Queue[int] = queues.Queue()\n        get0 = q.get()\n        get1 = q.get()\n        q.put_nowait(0)\n        # put_nowait does *not* immediately unblock getters.\n        yield gen.moment\n        self.assertEqual(0, (yield get0))\n        q.put_nowait(1)\n        yield gen.moment\n        self.assertEqual(1, (yield get1))\n\n    @gen_test\n    def test_blocking_put_wait(self):\n        q: queues.Queue[int] = queues.Queue(1)\n        q.put_nowait(0)\n\n        def get_and_discard():\n            q.get()\n\n        self.io_loop.call_later(0.01, get_and_discard)\n        self.io_loop.call_later(0.02, get_and_discard)\n        futures = [q.put(0), q.put(1)]\n        self.assertFalse(any(f.done() for f in futures))\n        yield futures\n\n    @gen_test\n    def test_put_timeout(self):\n        q: queues.Queue[int] = queues.Queue(1)\n        q.put_nowait(0)  # Now it's full.\n        put_timeout = q.put(1, timeout=timedelta(seconds=0.01))\n        put = q.put(2)\n        with self.assertRaises(TimeoutError):\n            yield put_timeout\n\n        self.assertEqual(0, q.get_nowait())\n        # 1 was never put in the queue.\n        self.assertEqual(2, (yield q.get()))\n\n        # Final get() unblocked this putter.\n        yield put\n\n    @gen_test\n    def test_put_timeout_preempted(self):\n        q: queues.Queue[int] = queues.Queue(1)\n        q.put_nowait(0)\n        put = q.put(1, timeout=timedelta(seconds=0.01))\n        q.get()\n        yield gen.sleep(0.02)\n        yield put  # No TimeoutError.\n\n    @gen_test\n    def test_put_clears_timed_out_putters(self):\n        q: queues.Queue[int] = queues.Queue(1)\n        # First putter succeeds, remainder block.\n        putters = [q.put(i, timedelta(seconds=0.01)) for i in range(10)]\n        put = q.put(10)\n        self.assertEqual(10, len(q._putters))\n        yield gen.sleep(0.02)\n        self.assertEqual(10, len(q._putters))\n        self.assertFalse(put.done())  # Final waiter is still active.\n        q.put(11)  # put() clears the waiters.\n        self.assertEqual(2, len(q._putters))\n        for putter in putters[1:]:\n            self.assertRaises(TimeoutError, putter.result)\n\n    @gen_test\n    def test_put_clears_timed_out_getters(self):\n        q: queues.Queue[int] = queues.Queue()\n        getters = [\n            asyncio.ensure_future(q.get(timedelta(seconds=0.01))) for _ in range(10)\n        ]\n        get = asyncio.ensure_future(q.get())\n        q.get()\n        self.assertEqual(12, len(q._getters))\n        yield gen.sleep(0.02)\n        self.assertEqual(12, len(q._getters))\n        self.assertFalse(get.done())  # Final waiters still active.\n        q.put(0)  # put() clears the waiters.\n        self.assertEqual(1, len(q._getters))\n        self.assertEqual(0, (yield get))\n        for getter in getters:\n            self.assertRaises(TimeoutError, getter.result)\n\n    @gen_test\n    def test_float_maxsize(self):\n        # If a float is passed for maxsize, a reasonable limit should\n        # be enforced, instead of being treated as unlimited.\n        # It happens to be rounded up.\n        # http://bugs.python.org/issue21723\n        q = queues.Queue(maxsize=1.3)  # type: ignore\n        self.assertTrue(q.empty())\n        self.assertFalse(q.full())\n        q.put_nowait(0)\n        q.put_nowait(1)\n        self.assertFalse(q.empty())\n        self.assertTrue(q.full())\n        self.assertRaises(queues.QueueFull, q.put_nowait, 2)\n        self.assertEqual(0, q.get_nowait())\n        self.assertFalse(q.empty())\n        self.assertFalse(q.full())\n\n        yield q.put(2)\n        put = q.put(3)\n        self.assertFalse(put.done())\n        self.assertEqual(1, (yield q.get()))\n        yield put\n        self.assertTrue(q.full())\n\n\nclass QueueJoinTest(AsyncTestCase):\n    queue_class = queues.Queue\n\n    def test_task_done_underflow(self):\n        q: queues.Queue = self.queue_class()\n        self.assertRaises(ValueError, q.task_done)\n\n    @gen_test\n    def test_task_done(self):\n        q: queues.Queue = self.queue_class()\n        for i in range(100):\n            q.put_nowait(i)\n\n        self.accumulator = 0\n\n        @gen.coroutine\n        def worker():\n            while True:\n                item = yield q.get()\n                self.accumulator += item\n                q.task_done()\n                yield gen.sleep(random() * 0.01)\n\n        # Two coroutines share work.\n        worker()\n        worker()\n        yield q.join()\n        self.assertEqual(sum(range(100)), self.accumulator)\n\n    @gen_test\n    def test_task_done_delay(self):\n        # Verify it is task_done(), not get(), that unblocks join().\n        q: queues.Queue = self.queue_class()\n        q.put_nowait(0)\n        join = asyncio.ensure_future(q.join())\n        self.assertFalse(join.done())\n        yield q.get()\n        self.assertFalse(join.done())\n        yield gen.moment\n        self.assertFalse(join.done())\n        q.task_done()\n        self.assertTrue(join.done())\n\n    @gen_test\n    def test_join_empty_queue(self):\n        q: queues.Queue = self.queue_class()\n        yield q.join()\n        yield q.join()\n\n    @gen_test\n    def test_join_timeout(self):\n        q: queues.Queue = self.queue_class()\n        q.put(0)\n        with self.assertRaises(TimeoutError):\n            yield q.join(timeout=timedelta(seconds=0.01))\n\n\nclass PriorityQueueJoinTest(QueueJoinTest):\n    queue_class = queues.PriorityQueue\n\n    @gen_test\n    def test_order(self):\n        q = self.queue_class(maxsize=2)\n        q.put_nowait((1, \"a\"))\n        q.put_nowait((0, \"b\"))\n        self.assertTrue(q.full())\n        q.put((3, \"c\"))\n        q.put((2, \"d\"))\n        self.assertEqual((0, \"b\"), q.get_nowait())\n        self.assertEqual((1, \"a\"), (yield q.get()))\n        self.assertEqual((2, \"d\"), q.get_nowait())\n        self.assertEqual((3, \"c\"), (yield q.get()))\n        self.assertTrue(q.empty())\n\n\nclass LifoQueueJoinTest(QueueJoinTest):\n    queue_class = queues.LifoQueue\n\n    @gen_test\n    def test_order(self):\n        q = self.queue_class(maxsize=2)\n        q.put_nowait(1)\n        q.put_nowait(0)\n        self.assertTrue(q.full())\n        q.put(3)\n        q.put(2)\n        self.assertEqual(3, q.get_nowait())\n        self.assertEqual(2, (yield q.get()))\n        self.assertEqual(0, q.get_nowait())\n        self.assertEqual(1, (yield q.get()))\n        self.assertTrue(q.empty())\n\n\nclass ProducerConsumerTest(AsyncTestCase):\n    @gen_test\n    def test_producer_consumer(self):\n        q: queues.Queue[int] = queues.Queue(maxsize=3)\n        history = []\n\n        # We don't yield between get() and task_done(), so get() must wait for\n        # the next tick. Otherwise we'd immediately call task_done and unblock\n        # join() before q.put() resumes, and we'd only process the first four\n        # items.\n        @gen.coroutine\n        def consumer():\n            while True:\n                history.append((yield q.get()))\n                q.task_done()\n\n        @gen.coroutine\n        def producer():\n            for item in range(10):\n                yield q.put(item)\n\n        consumer()\n        yield producer()\n        yield q.join()\n        self.assertEqual(list(range(10)), history)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tornado/test/resolve_test_helper.py",
    "content": "from tornado.ioloop import IOLoop\nfrom tornado.netutil import ThreadedResolver\n\n# When this module is imported, it runs getaddrinfo on a thread. Since\n# the hostname is unicode, getaddrinfo attempts to import encodings.idna\n# but blocks on the import lock. Verify that ThreadedResolver avoids\n# this deadlock.\n\nresolver = ThreadedResolver()\nIOLoop.current().run_sync(lambda: resolver.resolve(\"localhost\", 80))\n"
  },
  {
    "path": "tornado/test/routing_test.py",
    "content": "# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\nimport typing\n\nfrom tornado.httputil import (\n    HTTPHeaders,\n    HTTPMessageDelegate,\n    HTTPServerConnectionDelegate,\n    ResponseStartLine,\n)\nfrom tornado.routing import (\n    HostMatches,\n    PathMatches,\n    ReversibleRouter,\n    Router,\n    Rule,\n    RuleRouter,\n)\nfrom tornado.testing import AsyncHTTPTestCase\nfrom tornado.web import Application, HTTPError, RequestHandler\nfrom tornado.wsgi import WSGIContainer\n\n\nclass BasicRouter(Router):\n    def find_handler(self, request, **kwargs):\n        class MessageDelegate(HTTPMessageDelegate):\n            def __init__(self, connection):\n                self.connection = connection\n\n            def finish(self):\n                self.connection.write_headers(\n                    ResponseStartLine(\"HTTP/1.1\", 200, \"OK\"),\n                    HTTPHeaders({\"Content-Length\": \"2\"}),\n                    b\"OK\",\n                )\n                self.connection.finish()\n\n        return MessageDelegate(request.connection)\n\n\nclass BasicRouterTestCase(AsyncHTTPTestCase):\n    def get_app(self):\n        return BasicRouter()\n\n    def test_basic_router(self):\n        response = self.fetch(\"/any_request\")\n        self.assertEqual(response.body, b\"OK\")\n\n\nresources: dict[str, bytes] = {}\n\n\nclass GetResource(RequestHandler):\n    def get(self, path):\n        if path not in resources:\n            raise HTTPError(404)\n\n        self.finish(resources[path])\n\n\nclass PostResource(RequestHandler):\n    def post(self, path):\n        resources[path] = self.request.body\n\n\nclass HTTPMethodRouter(Router):\n    def __init__(self, app):\n        self.app = app\n\n    def find_handler(self, request, **kwargs):\n        handler = GetResource if request.method == \"GET\" else PostResource\n        return self.app.get_handler_delegate(request, handler, path_args=[request.path])\n\n\nclass HTTPMethodRouterTestCase(AsyncHTTPTestCase):\n    def get_app(self):\n        return HTTPMethodRouter(Application())\n\n    def test_http_method_router(self):\n        response = self.fetch(\"/post_resource\", method=\"POST\", body=\"data\")\n        self.assertEqual(response.code, 200)\n\n        response = self.fetch(\"/get_resource\")\n        self.assertEqual(response.code, 404)\n\n        response = self.fetch(\"/post_resource\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.body, b\"data\")\n\n\ndef _get_named_handler(handler_name):\n    class Handler(RequestHandler):\n        def get(self, *args, **kwargs):\n            if self.application.settings.get(\"app_name\") is not None:\n                self.write(self.application.settings[\"app_name\"] + \": \")\n\n            self.finish(handler_name + \": \" + self.reverse_url(handler_name))\n\n    return Handler\n\n\nFirstHandler = _get_named_handler(\"first_handler\")\nSecondHandler = _get_named_handler(\"second_handler\")\n\n\nclass CustomRouter(ReversibleRouter):\n    def __init__(self):\n        super().__init__()\n        self.routes: dict[str, typing.Any] = {}\n\n    def add_routes(self, routes):\n        self.routes.update(routes)\n\n    def find_handler(self, request, **kwargs):\n        if request.path in self.routes:\n            app, handler = self.routes[request.path]\n            return app.get_handler_delegate(request, handler)\n\n    def reverse_url(self, name, *args):\n        handler_path = \"/\" + name\n        return handler_path if handler_path in self.routes else None\n\n\nclass CustomRouterTestCase(AsyncHTTPTestCase):\n    def get_app(self):\n        router = CustomRouter()\n\n        class CustomApplication(Application):\n            def reverse_url(self, name, *args):\n                return router.reverse_url(name, *args)\n\n        app1 = CustomApplication(app_name=\"app1\")\n        app2 = CustomApplication(app_name=\"app2\")\n\n        router.add_routes(\n            {\n                \"/first_handler\": (app1, FirstHandler),\n                \"/second_handler\": (app2, SecondHandler),\n                \"/first_handler_second_app\": (app2, FirstHandler),\n            }\n        )\n\n        return router\n\n    def test_custom_router(self):\n        response = self.fetch(\"/first_handler\")\n        self.assertEqual(response.body, b\"app1: first_handler: /first_handler\")\n        response = self.fetch(\"/second_handler\")\n        self.assertEqual(response.body, b\"app2: second_handler: /second_handler\")\n        response = self.fetch(\"/first_handler_second_app\")\n        self.assertEqual(response.body, b\"app2: first_handler: /first_handler\")\n\n\nclass ConnectionDelegate(HTTPServerConnectionDelegate):\n    def start_request(self, server_conn, request_conn):\n        class MessageDelegate(HTTPMessageDelegate):\n            def __init__(self, connection):\n                self.connection = connection\n\n            def finish(self):\n                response_body = b\"OK\"\n                self.connection.write_headers(\n                    ResponseStartLine(\"HTTP/1.1\", 200, \"OK\"),\n                    HTTPHeaders({\"Content-Length\": str(len(response_body))}),\n                )\n                self.connection.write(response_body)\n                self.connection.finish()\n\n        return MessageDelegate(request_conn)\n\n\nclass RuleRouterTest(AsyncHTTPTestCase):\n    def get_app(self):\n        app = Application()\n\n        def request_callable(request):\n            request.connection.write_headers(\n                ResponseStartLine(\"HTTP/1.1\", 200, \"OK\"),\n                HTTPHeaders({\"Content-Length\": \"2\"}),\n            )\n            request.connection.write(b\"OK\")\n            request.connection.finish()\n\n        router = CustomRouter()\n        router.add_routes(\n            {\"/nested_handler\": (app, _get_named_handler(\"nested_handler\"))}\n        )\n\n        app.add_handlers(\n            \".*\",\n            [\n                (\n                    HostMatches(\"www.example.com\"),\n                    [\n                        (\n                            PathMatches(\"/first_handler\"),\n                            \"tornado.test.routing_test.SecondHandler\",\n                            {},\n                            \"second_handler\",\n                        )\n                    ],\n                ),\n                Rule(PathMatches(\"/.*handler\"), router),\n                Rule(PathMatches(\"/first_handler\"), FirstHandler, name=\"first_handler\"),\n                Rule(PathMatches(\"/request_callable\"), request_callable),\n                (\"/connection_delegate\", ConnectionDelegate()),\n            ],\n        )\n\n        return app\n\n    def test_rule_based_router(self):\n        response = self.fetch(\"/first_handler\")\n        self.assertEqual(response.body, b\"first_handler: /first_handler\")\n\n        response = self.fetch(\"/first_handler\", headers={\"Host\": \"www.example.com\"})\n        self.assertEqual(response.body, b\"second_handler: /first_handler\")\n\n        response = self.fetch(\"/nested_handler\")\n        self.assertEqual(response.body, b\"nested_handler: /nested_handler\")\n\n        response = self.fetch(\"/nested_not_found_handler\")\n        self.assertEqual(response.code, 404)\n\n        response = self.fetch(\"/connection_delegate\")\n        self.assertEqual(response.body, b\"OK\")\n\n        response = self.fetch(\"/request_callable\")\n        self.assertEqual(response.body, b\"OK\")\n\n        response = self.fetch(\"/404\")\n        self.assertEqual(response.code, 404)\n\n\nclass WSGIContainerTestCase(AsyncHTTPTestCase):\n    def get_app(self):\n        wsgi_app = WSGIContainer(self.wsgi_app)\n\n        class Handler(RequestHandler):\n            def get(self, *args, **kwargs):\n                self.finish(self.reverse_url(\"tornado\"))\n\n        return RuleRouter(\n            [\n                (\n                    PathMatches(\"/tornado.*\"),\n                    Application([(r\"/tornado/test\", Handler, {}, \"tornado\")]),\n                ),\n                (PathMatches(\"/wsgi\"), wsgi_app),\n            ]\n        )\n\n    def wsgi_app(self, environ, start_response):\n        start_response(\"200 OK\", [])\n        return [b\"WSGI\"]\n\n    def test_wsgi_container(self):\n        response = self.fetch(\"/tornado/test\")\n        self.assertEqual(response.body, b\"/tornado/test\")\n\n        response = self.fetch(\"/wsgi\")\n        self.assertEqual(response.body, b\"WSGI\")\n\n    def test_delegate_not_found(self):\n        response = self.fetch(\"/404\")\n        self.assertEqual(response.code, 404)\n"
  },
  {
    "path": "tornado/test/runtests.py",
    "content": "import gc\nimport io\nimport locale  # system locale module, not tornado.locale\nimport logging\nimport operator\nimport sys\nimport textwrap\nimport unittest\nimport warnings\nfrom functools import reduce\n\nfrom tornado.httpclient import AsyncHTTPClient\nfrom tornado.httpserver import HTTPServer\nfrom tornado.netutil import Resolver\nfrom tornado.options import add_parse_callback, define, options\nfrom tornado.test.util import ABT_SKIP_MESSAGE\n\nTEST_MODULES = [\n    \"tornado.httputil.doctests\",\n    \"tornado.iostream.doctests\",\n    \"tornado.util.doctests\",\n    \"tornado.test.asyncio_test\",\n    \"tornado.test.auth_test\",\n    \"tornado.test.autoreload_test\",\n    \"tornado.test.circlerefs_test\",\n    \"tornado.test.concurrent_test\",\n    \"tornado.test.curl_httpclient_test\",\n    \"tornado.test.escape_test\",\n    \"tornado.test.gen_test\",\n    \"tornado.test.http1connection_test\",\n    \"tornado.test.httpclient_test\",\n    \"tornado.test.httpserver_test\",\n    \"tornado.test.httputil_test\",\n    \"tornado.test.import_test\",\n    \"tornado.test.ioloop_test\",\n    \"tornado.test.iostream_test\",\n    \"tornado.test.locale_test\",\n    \"tornado.test.locks_test\",\n    \"tornado.test.netutil_test\",\n    \"tornado.test.log_test\",\n    \"tornado.test.options_test\",\n    \"tornado.test.process_test\",\n    \"tornado.test.queues_test\",\n    \"tornado.test.routing_test\",\n    \"tornado.test.simple_httpclient_test\",\n    \"tornado.test.tcpclient_test\",\n    \"tornado.test.tcpserver_test\",\n    \"tornado.test.template_test\",\n    \"tornado.test.testing_test\",\n    \"tornado.test.twisted_test\",\n    \"tornado.test.util_test\",\n    \"tornado.test.web_test\",\n    \"tornado.test.websocket_test\",\n    \"tornado.test.wsgi_test\",\n]\n\n\ndef all():\n    return unittest.defaultTestLoader.loadTestsFromNames(TEST_MODULES)\n\n\ndef test_runner_factory(stderr):\n\n    class TornadoTextTestResult(unittest.TextTestResult):\n        def addSkip(self, test, reason):\n            if reason == ABT_SKIP_MESSAGE:\n                # Don't report abstract base tests as skips in our own tooling.\n                #\n                # See tornado.test.util.abstract_base_test.\n                return\n            super().addSkip(test, reason)\n\n    class TornadoTextTestRunner(unittest.TextTestRunner):\n        def __init__(self, *args, **kwargs):\n            kwargs[\"stream\"] = stderr\n            kwargs[\"resultclass\"] = TornadoTextTestResult\n            super().__init__(*args, **kwargs)\n\n        def run(self, test):\n            result = super().run(test)\n            if result.skipped:\n                skip_reasons = {reason for (test, reason) in result.skipped}\n                self.stream.write(  # type: ignore\n                    textwrap.fill(\n                        \"Some tests were skipped because: %s\"\n                        % \", \".join(sorted(skip_reasons))\n                    )\n                )\n                self.stream.write(\"\\n\")  # type: ignore\n            return result\n\n    return TornadoTextTestRunner\n\n\nclass LogCounter(logging.Filter):\n    \"\"\"Counts the number of WARNING or higher log records.\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.info_count = self.warning_count = self.error_count = 0\n\n    def filter(self, record):\n        if record.levelno >= logging.ERROR:\n            self.error_count += 1\n        elif record.levelno >= logging.WARNING:\n            self.warning_count += 1\n        elif record.levelno >= logging.INFO:\n            self.info_count += 1\n        return True\n\n\nclass CountingStderr(io.IOBase):\n    def __init__(self, real):\n        self.real = real\n        self.byte_count = 0\n\n    def write(self, data):\n        self.byte_count += len(data)\n        return self.real.write(data)\n\n    def flush(self):\n        return self.real.flush()\n\n\ndef main():\n    # Be strict about most warnings (This is set in our test running\n    # scripts to catch import-time warnings, but set it again here to\n    # be sure). This also turns on warnings that are ignored by\n    # default, including DeprecationWarnings and python 3.2's\n    # ResourceWarnings.\n    warnings.filterwarnings(\"error\")\n    # setuptools sometimes gives ImportWarnings about things that are on\n    # sys.path even if they're not being used.\n    warnings.filterwarnings(\"ignore\", category=ImportWarning)\n    # Tornado generally shouldn't use anything deprecated, but some of\n    # our dependencies do (last match wins).\n    warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n    warnings.filterwarnings(\"error\", category=DeprecationWarning, module=r\"tornado\\..*\")\n    warnings.filterwarnings(\"ignore\", category=PendingDeprecationWarning)\n    warnings.filterwarnings(\n        \"error\", category=PendingDeprecationWarning, module=r\"tornado\\..*\"\n    )\n\n    logging.getLogger(\"tornado.access\").setLevel(logging.CRITICAL)\n\n    define(\n        \"httpclient\",\n        type=str,\n        default=None,\n        callback=lambda s: AsyncHTTPClient.configure(\n            s, defaults=dict(allow_ipv6=False)\n        ),\n    )\n    define(\"httpserver\", type=str, default=None, callback=HTTPServer.configure)\n    define(\"resolver\", type=str, default=None, callback=Resolver.configure)\n    define(\n        \"debug_gc\",\n        type=str,\n        multiple=True,\n        help=\"A comma-separated list of gc module debug constants, \"\n        \"e.g. DEBUG_STATS or DEBUG_COLLECTABLE,DEBUG_OBJECTS\",\n        callback=lambda values: gc.set_debug(\n            reduce(operator.or_, (getattr(gc, v) for v in values))\n        ),\n    )\n    define(\n        \"fail-if-logs\",\n        default=True,\n        help=\"If true, fail the tests if any log output is produced (unless captured by ExpectLog)\",\n    )\n\n    def set_locale(x):\n        locale.setlocale(locale.LC_ALL, x)\n\n    define(\"locale\", type=str, default=None, callback=set_locale)\n\n    log_counter = LogCounter()\n    add_parse_callback(lambda: logging.getLogger().handlers[0].addFilter(log_counter))\n\n    # Certain errors (especially \"unclosed resource\" errors raised in\n    # destructors) go directly to stderr instead of logging. Count\n    # anything written by anything but the test runner as an error.\n    orig_stderr = sys.stderr\n    counting_stderr = CountingStderr(orig_stderr)\n    sys.stderr = counting_stderr  # type: ignore\n\n    import tornado.testing\n\n    kwargs = {}\n\n    # HACK:  unittest.main will make its own changes to the warning\n    # configuration, which may conflict with the settings above\n    # or command-line flags like -bb.  Passing warnings=False\n    # suppresses this behavior, although this looks like an implementation\n    # detail.  http://bugs.python.org/issue15626\n    kwargs[\"warnings\"] = False\n\n    kwargs[\"testRunner\"] = test_runner_factory(orig_stderr)\n    try:\n        tornado.testing.main(**kwargs)\n    finally:\n        # The tests should run clean; consider it a failure if they\n        # logged anything at info level or above.\n        if (\n            log_counter.info_count > 0\n            or log_counter.warning_count > 0\n            or log_counter.error_count > 0\n            or counting_stderr.byte_count > 0\n        ):\n            logging.error(\n                \"logged %d infos, %d warnings, %d errors, and %d bytes to stderr\",\n                log_counter.info_count,\n                log_counter.warning_count,\n                log_counter.error_count,\n                counting_stderr.byte_count,\n            )\n            if options.fail_if_logs:\n                sys.exit(1)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "tornado/test/simple_httpclient_test.py",
    "content": "import collections\nimport errno\nimport logging\nimport os\nimport re\nimport socket\nimport ssl\nimport sys\nimport typing\nfrom contextlib import closing\n\nfrom tornado import gen, version\nfrom tornado.escape import to_unicode, utf8\nfrom tornado.httpclient import AsyncHTTPClient, HTTPResponse\nfrom tornado.httpserver import HTTPServer\nfrom tornado.httputil import HTTPHeaders, ResponseStartLine\nfrom tornado.ioloop import IOLoop\nfrom tornado.iostream import UnsatisfiableReadError\nfrom tornado.locks import Event\nfrom tornado.log import gen_log\nfrom tornado.netutil import Resolver, bind_sockets\nfrom tornado.simple_httpclient import (\n    HTTPStreamClosedError,\n    HTTPTimeoutError,\n    SimpleAsyncHTTPClient,\n)\nfrom tornado.test import httpclient_test\nfrom tornado.test.httpclient_test import (\n    ChunkHandler,\n    CountdownHandler,\n    HelloWorldHandler,\n    RedirectHandler,\n    UserAgentHandler,\n)\nfrom tornado.test.util import (\n    abstract_base_test,\n    refusing_port,\n    skipIfNoIPv6,\n)\nfrom tornado.testing import (\n    AsyncHTTPSTestCase,\n    AsyncHTTPTestCase,\n    AsyncTestCase,\n    ExpectLog,\n    gen_test,\n)\nfrom tornado.web import Application, RequestHandler, stream_request_body, url\n\n\nclass SimpleHTTPClientCommonTestCase(httpclient_test.HTTPClientCommonTestCase):\n    def get_http_client(self):\n        client = SimpleAsyncHTTPClient(force_instance=True)\n        self.assertTrue(isinstance(client, SimpleAsyncHTTPClient))\n        return client\n\n\nclass TriggerHandler(RequestHandler):\n    def initialize(self, queue, wake_callback):\n        self.queue = queue\n        self.wake_callback = wake_callback\n\n    @gen.coroutine\n    def get(self):\n        logging.debug(\"queuing trigger\")\n        event = Event()\n        self.queue.append(event.set)\n        if self.get_argument(\"wake\", \"true\") == \"true\":\n            self.wake_callback()\n        yield event.wait()\n\n\nclass ContentLengthHandler(RequestHandler):\n    def get(self):\n        self.stream = self.detach()\n        IOLoop.current().spawn_callback(self.write_response)\n\n    @gen.coroutine\n    def write_response(self):\n        yield self.stream.write(\n            utf8(\n                \"HTTP/1.0 200 OK\\r\\nContent-Length: %s\\r\\n\\r\\nok\"\n                % self.get_argument(\"value\")\n            )\n        )\n        self.stream.close()\n\n\nclass HeadHandler(RequestHandler):\n    def head(self):\n        self.set_header(\"Content-Length\", \"7\")\n\n\nclass OptionsHandler(RequestHandler):\n    def options(self):\n        self.set_header(\"Access-Control-Allow-Origin\", \"*\")\n        self.write(\"ok\")\n\n\nclass NoContentHandler(RequestHandler):\n    def get(self):\n        self.set_status(204)\n        self.finish()\n\n\nclass SeeOtherPostHandler(RequestHandler):\n    def post(self):\n        redirect_code = int(self.request.body)\n        assert redirect_code in (302, 303), \"unexpected body %r\" % self.request.body\n        self.set_header(\"Location\", \"/see_other_get\")\n        self.set_status(redirect_code)\n\n\nclass SeeOtherGetHandler(RequestHandler):\n    def get(self):\n        if self.request.body:\n            raise Exception(\"unexpected body %r\" % self.request.body)\n        self.write(\"ok\")\n\n\nclass HostEchoHandler(RequestHandler):\n    def get(self):\n        self.write(self.request.headers[\"Host\"])\n\n\nclass NoContentLengthHandler(RequestHandler):\n    def get(self):\n        if self.request.version.startswith(\"HTTP/1\"):\n            # Emulate the old HTTP/1.0 behavior of returning a body with no\n            # content-length.  Tornado handles content-length at the framework\n            # level so we have to go around it.\n            stream = self.detach()\n            stream.write(b\"HTTP/1.0 200 OK\\r\\n\\r\\n\" b\"hello\")\n            stream.close()\n        else:\n            self.finish(\"HTTP/1 required\")\n\n\nclass EchoPostHandler(RequestHandler):\n    def post(self):\n        self.write(self.request.body)\n\n\n@stream_request_body\nclass RespondInPrepareHandler(RequestHandler):\n    def prepare(self):\n        self.set_status(403)\n        self.finish(\"forbidden\")\n\n\n@abstract_base_test\nclass SimpleHTTPClientTestMixin(AsyncTestCase):\n    # See comments on TestIOStreamWebMixin\n    def get_http_port(self) -> int:\n        raise NotImplementedError()\n\n    def fetch(\n        self, path: str, raise_error: bool = False, **kwargs: typing.Any\n    ) -> HTTPResponse:\n        # To be filled in by mixing in AsyncHTTPTestCase or AsyncHTTPSTestCase\n        raise NotImplementedError()\n\n    def get_url(self, path: str) -> str:\n        raise NotImplementedError()\n\n    def get_protocol(self) -> str:\n        raise NotImplementedError()\n\n    def get_http_server(self) -> HTTPServer:\n        raise NotImplementedError()\n\n    def create_client(self, **kwargs):\n        raise NotImplementedError()\n\n    def mixin_get_app(self):\n        # callable objects to finish pending /trigger requests\n        self.triggers: typing.Deque[typing.Callable[[], None]] = collections.deque()\n        return Application(\n            [\n                url(\n                    \"/trigger\",\n                    TriggerHandler,\n                    dict(queue=self.triggers, wake_callback=self.stop),\n                ),\n                url(\"/chunk\", ChunkHandler),\n                url(\"/countdown/([0-9]+)\", CountdownHandler, name=\"countdown\"),\n                url(\"/hello\", HelloWorldHandler),\n                url(\"/content_length\", ContentLengthHandler),\n                url(\"/head\", HeadHandler),\n                url(\"/options\", OptionsHandler),\n                url(\"/no_content\", NoContentHandler),\n                url(\"/see_other_post\", SeeOtherPostHandler),\n                url(\"/see_other_get\", SeeOtherGetHandler),\n                url(\"/host_echo\", HostEchoHandler),\n                url(\"/no_content_length\", NoContentLengthHandler),\n                url(\"/echo_post\", EchoPostHandler),\n                url(\"/respond_in_prepare\", RespondInPrepareHandler),\n                url(\"/redirect\", RedirectHandler),\n                url(\"/user_agent\", UserAgentHandler),\n            ],\n            gzip=True,\n        )\n\n    def test_singleton(self):\n        # Class \"constructor\" reuses objects on the same IOLoop\n        self.assertIs(SimpleAsyncHTTPClient(), SimpleAsyncHTTPClient())\n        # unless force_instance is used\n        self.assertIsNot(\n            SimpleAsyncHTTPClient(), SimpleAsyncHTTPClient(force_instance=True)\n        )\n        # different IOLoops use different objects\n        with closing(IOLoop(make_current=False)) as io_loop2:\n\n            async def make_client():\n                await gen.sleep(0)\n                return SimpleAsyncHTTPClient()\n\n            client1 = self.io_loop.run_sync(make_client)\n            client2 = io_loop2.run_sync(make_client)\n            self.assertIsNot(client1, client2)\n\n    def test_connection_limit(self):\n        with closing(self.create_client(max_clients=2)) as client:\n            self.assertEqual(client.max_clients, 2)\n            seen = []\n            # Send 4 requests.  Two can be sent immediately, while the others\n            # will be queued\n            for i in range(4):\n\n                def cb(fut, i=i):\n                    seen.append(i)\n                    self.stop()\n\n                client.fetch(self.get_url(\"/trigger\")).add_done_callback(cb)\n            self.wait(condition=lambda: len(self.triggers) == 2)\n            self.assertEqual(len(client.queue), 2)\n\n            # Finish the first two requests and let the next two through\n            self.triggers.popleft()()\n            self.triggers.popleft()()\n            self.wait(condition=lambda: (len(self.triggers) == 2 and len(seen) == 2))\n            self.assertEqual(set(seen), {0, 1})\n            self.assertEqual(len(client.queue), 0)\n\n            # Finish all the pending requests\n            self.triggers.popleft()()\n            self.triggers.popleft()()\n            self.wait(condition=lambda: len(seen) == 4)\n            self.assertEqual(set(seen), {0, 1, 2, 3})\n            self.assertEqual(len(self.triggers), 0)\n\n    @gen_test\n    def test_redirect_connection_limit(self):\n        # following redirects should not consume additional connections\n        with closing(self.create_client(max_clients=1)) as client:\n            response = yield client.fetch(self.get_url(\"/countdown/3\"), max_redirects=3)\n            response.rethrow()\n\n    def test_max_redirects(self):\n        response = self.fetch(\"/countdown/5\", max_redirects=3)\n        self.assertEqual(302, response.code)\n        # We requested 5, followed three redirects for 4, 3, 2, then the last\n        # unfollowed redirect is to 1.\n        self.assertTrue(response.request.url.endswith(\"/countdown/5\"))\n        self.assertTrue(response.effective_url.endswith(\"/countdown/2\"))\n        self.assertTrue(response.headers[\"Location\"].endswith(\"/countdown/1\"))\n\n    def test_header_reuse(self):\n        # Apps may reuse a headers object if they are only passing in constant\n        # headers like user-agent.  The header object should not be modified.\n        headers = HTTPHeaders({\"User-Agent\": \"Foo\"})\n        self.fetch(\"/hello\", headers=headers)\n        self.assertEqual(list(headers.get_all()), [(\"User-Agent\", \"Foo\")])\n\n    def test_default_user_agent(self):\n        response = self.fetch(\"/user_agent\", method=\"GET\")\n        self.assertEqual(200, response.code)\n        self.assertEqual(response.body.decode(), f\"Tornado/{version}\")\n\n    def test_see_other_redirect(self):\n        for code in (302, 303):\n            response = self.fetch(\"/see_other_post\", method=\"POST\", body=\"%d\" % code)\n            self.assertEqual(200, response.code)\n            self.assertTrue(response.request.url.endswith(\"/see_other_post\"))\n            self.assertTrue(response.effective_url.endswith(\"/see_other_get\"))\n            # request is the original request, is a POST still\n            self.assertEqual(\"POST\", response.request.method)\n\n    @gen_test\n    def test_connect_timeout(self):\n        timeout = 0.1\n\n        cleanup_event = Event()\n        test = self\n\n        class TimeoutResolver(Resolver):\n            async def resolve(self, *args, **kwargs):\n                await cleanup_event.wait()\n                # Return something valid so the test doesn't raise during shutdown.\n                return [(socket.AF_INET, (\"127.0.0.1\", test.get_http_port()))]\n\n        with closing(self.create_client(resolver=TimeoutResolver())) as client:\n            with self.assertRaises(HTTPTimeoutError):\n                yield client.fetch(\n                    self.get_url(\"/hello\"),\n                    connect_timeout=timeout,\n                    request_timeout=3600,\n                    raise_error=True,\n                )\n\n        # Let the hanging coroutine clean up after itself. We need to\n        # wait more than a single IOLoop iteration for the SSL case,\n        # which logs errors on unexpected EOF.\n        cleanup_event.set()\n        yield gen.sleep(0.2)\n\n    def test_request_timeout(self):\n        timeout = 0.1\n        if os.name == \"nt\" or os.environ.get(\"EMULATION\") == \"1\":\n            timeout = 0.5\n\n        with self.assertRaises(HTTPTimeoutError):\n            self.fetch(\"/trigger?wake=false\", request_timeout=timeout, raise_error=True)\n        # trigger the hanging request to let it clean up after itself\n        self.triggers.popleft()()\n        self.io_loop.run_sync(lambda: gen.sleep(0))\n\n    @skipIfNoIPv6\n    def test_ipv6(self):\n        [sock] = bind_sockets(0, \"::1\", family=socket.AF_INET6)\n        port = sock.getsockname()[1]\n        self.get_http_server().add_socket(sock)\n        url = \"%s://[::1]:%d/hello\" % (self.get_protocol(), port)\n\n        # ipv6 is currently enabled by default but can be disabled\n        with self.assertRaises(Exception):\n            self.fetch(url, allow_ipv6=False, raise_error=True)\n\n        response = self.fetch(url)\n        self.assertEqual(response.body, b\"Hello world!\")\n\n    def test_multiple_content_length_accepted(self):\n        response = self.fetch(\"/content_length?value=2,2\")\n        self.assertEqual(response.body, b\"ok\")\n        response = self.fetch(\"/content_length?value=2,%202,2\")\n        self.assertEqual(response.body, b\"ok\")\n\n        with ExpectLog(\n            gen_log, \".*Multiple unequal Content-Lengths\", level=logging.INFO\n        ):\n            with self.assertRaises(HTTPStreamClosedError):\n                self.fetch(\"/content_length?value=2,4\", raise_error=True)\n            with self.assertRaises(HTTPStreamClosedError):\n                self.fetch(\"/content_length?value=2,%202,3\", raise_error=True)\n\n    def test_head_request(self):\n        response = self.fetch(\"/head\", method=\"HEAD\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.headers[\"content-length\"], \"7\")\n        self.assertFalse(response.body)\n\n    def test_options_request(self):\n        response = self.fetch(\"/options\", method=\"OPTIONS\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.headers[\"content-length\"], \"2\")\n        self.assertEqual(response.headers[\"access-control-allow-origin\"], \"*\")\n        self.assertEqual(response.body, b\"ok\")\n\n    def test_no_content(self):\n        response = self.fetch(\"/no_content\")\n        self.assertEqual(response.code, 204)\n        # 204 status shouldn't have a content-length\n        #\n        # Tests with a content-length header are included below\n        # in HTTP204NoContentTestCase.\n        self.assertNotIn(\"Content-Length\", response.headers)\n\n    def test_host_header(self):\n        host_re = re.compile(b\"^127.0.0.1:[0-9]+$\")\n        response = self.fetch(\"/host_echo\")\n        self.assertTrue(host_re.match(response.body))\n\n        url = self.get_url(\"/host_echo\").replace(\"http://\", \"http://me:secret@\")\n        response = self.fetch(url)\n        self.assertTrue(host_re.match(response.body), response.body)\n\n    def test_connection_refused(self):\n        cleanup_func, port = refusing_port()\n        self.addCleanup(cleanup_func)\n        with ExpectLog(gen_log, \".*\", required=False):\n            with self.assertRaises(socket.error) as cm:\n                self.fetch(\"http://127.0.0.1:%d/\" % port, raise_error=True)\n\n        if sys.platform != \"cygwin\":\n            # cygwin returns EPERM instead of ECONNREFUSED here\n            contains_errno = str(errno.ECONNREFUSED) in str(cm.exception)\n            if not contains_errno and hasattr(errno, \"WSAECONNREFUSED\"):\n                contains_errno = str(errno.WSAECONNREFUSED) in str(  # type: ignore\n                    cm.exception\n                )\n            self.assertTrue(contains_errno, cm.exception)\n            # This is usually \"Connection refused\".\n            # On windows, strerror is broken and returns \"Unknown error\".\n            expected_message = os.strerror(errno.ECONNREFUSED)\n            self.assertTrue(expected_message in str(cm.exception), cm.exception)\n\n    def test_queue_timeout(self):\n        with closing(self.create_client(max_clients=1)) as client:\n            # Wait for the trigger request to block, not complete.\n            fut1 = client.fetch(self.get_url(\"/trigger\"), request_timeout=10)\n            self.wait()\n            with self.assertRaises(HTTPTimeoutError) as cm:\n                self.io_loop.run_sync(\n                    lambda: client.fetch(\n                        self.get_url(\"/hello\"), connect_timeout=0.1, raise_error=True\n                    )\n                )\n\n            self.assertEqual(str(cm.exception), \"Timeout in request queue\")\n            self.triggers.popleft()()\n            self.io_loop.run_sync(lambda: fut1)\n\n    def test_no_content_length(self):\n        response = self.fetch(\"/no_content_length\")\n        if response.body == b\"HTTP/1 required\":\n            self.skipTest(\"requires HTTP/1.x\")\n        else:\n            self.assertEqual(b\"hello\", response.body)\n\n    def sync_body_producer(self, write):\n        write(b\"1234\")\n        write(b\"5678\")\n\n    @gen.coroutine\n    def async_body_producer(self, write):\n        yield write(b\"1234\")\n        yield gen.moment\n        yield write(b\"5678\")\n\n    def test_sync_body_producer_chunked(self):\n        response = self.fetch(\n            \"/echo_post\", method=\"POST\", body_producer=self.sync_body_producer\n        )\n        response.rethrow()\n        self.assertEqual(response.body, b\"12345678\")\n\n    def test_sync_body_producer_content_length(self):\n        response = self.fetch(\n            \"/echo_post\",\n            method=\"POST\",\n            body_producer=self.sync_body_producer,\n            headers={\"Content-Length\": \"8\"},\n        )\n        response.rethrow()\n        self.assertEqual(response.body, b\"12345678\")\n\n    def test_async_body_producer_chunked(self):\n        response = self.fetch(\n            \"/echo_post\", method=\"POST\", body_producer=self.async_body_producer\n        )\n        response.rethrow()\n        self.assertEqual(response.body, b\"12345678\")\n\n    def test_async_body_producer_content_length(self):\n        response = self.fetch(\n            \"/echo_post\",\n            method=\"POST\",\n            body_producer=self.async_body_producer,\n            headers={\"Content-Length\": \"8\"},\n        )\n        response.rethrow()\n        self.assertEqual(response.body, b\"12345678\")\n\n    def test_native_body_producer_chunked(self):\n        async def body_producer(write):\n            await write(b\"1234\")\n            import asyncio\n\n            await asyncio.sleep(0)\n            await write(b\"5678\")\n\n        response = self.fetch(\"/echo_post\", method=\"POST\", body_producer=body_producer)\n        response.rethrow()\n        self.assertEqual(response.body, b\"12345678\")\n\n    def test_native_body_producer_content_length(self):\n        async def body_producer(write):\n            await write(b\"1234\")\n            import asyncio\n\n            await asyncio.sleep(0)\n            await write(b\"5678\")\n\n        response = self.fetch(\n            \"/echo_post\",\n            method=\"POST\",\n            body_producer=body_producer,\n            headers={\"Content-Length\": \"8\"},\n        )\n        response.rethrow()\n        self.assertEqual(response.body, b\"12345678\")\n\n    def test_100_continue(self):\n        response = self.fetch(\n            \"/echo_post\", method=\"POST\", body=b\"1234\", expect_100_continue=True\n        )\n        self.assertEqual(response.body, b\"1234\")\n\n    def test_100_continue_early_response(self):\n        def body_producer(write):\n            raise Exception(\"should not be called\")\n\n        response = self.fetch(\n            \"/respond_in_prepare\",\n            method=\"POST\",\n            body_producer=body_producer,\n            expect_100_continue=True,\n        )\n        self.assertEqual(response.code, 403)\n\n    def test_streaming_follow_redirects(self):\n        # When following redirects, header and streaming callbacks\n        # should only be called for the final result.\n        # TODO(bdarnell): this test belongs in httpclient_test instead of\n        # simple_httpclient_test, but it fails with the version of libcurl\n        # available on travis-ci. Move it when that has been upgraded\n        # or we have a better framework to skip tests based on curl version.\n        headers: list[str] = []\n        chunk_bytes: list[bytes] = []\n        self.fetch(\n            \"/redirect?url=/hello\",\n            header_callback=headers.append,\n            streaming_callback=chunk_bytes.append,\n        )\n        chunks = list(map(to_unicode, chunk_bytes))\n        self.assertEqual(chunks, [\"Hello world!\"])\n        # Make sure we only got one set of headers.\n        num_start_lines = len([h for h in headers if h.startswith(\"HTTP/\")])\n        self.assertEqual(num_start_lines, 1)\n\n    def test_streaming_callback_coroutine(self: typing.Any):\n        headers: list[str] = []\n        chunk_bytes: list[bytes] = []\n\n        import asyncio\n\n        async def _put_chunk(chunk):\n            await asyncio.sleep(0)\n            chunk_bytes.append(chunk)\n\n        self.fetch(\n            \"/chunk\",\n            header_callback=headers.append,\n            streaming_callback=_put_chunk,\n        )\n        chunks = list(map(to_unicode, chunk_bytes))\n        self.assertEqual(\"\".join(chunks), \"asdfqwer\")\n        # Make sure we only got one set of headers.\n        num_start_lines = len([h for h in headers if h.startswith(\"HTTP/\")])\n        self.assertEqual(num_start_lines, 1)\n\n\nclass SimpleHTTPClientTestCase(AsyncHTTPTestCase, SimpleHTTPClientTestMixin):\n    def setUp(self):\n        super().setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return self.mixin_get_app()\n\n    def create_client(self, **kwargs):\n        return SimpleAsyncHTTPClient(force_instance=True, **kwargs)\n\n\nclass SimpleHTTPSClientTestCase(AsyncHTTPSTestCase, SimpleHTTPClientTestMixin):\n    def setUp(self):\n        super().setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return self.mixin_get_app()\n\n    def create_client(self, **kwargs):\n        return SimpleAsyncHTTPClient(\n            force_instance=True, defaults=dict(validate_cert=False), **kwargs\n        )\n\n    def test_ssl_options(self):\n        resp = self.fetch(\"/hello\", ssl_options={\"cert_reqs\": ssl.CERT_NONE})\n        self.assertEqual(resp.body, b\"Hello world!\")\n\n    def test_ssl_context(self):\n        ssl_ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)\n        ssl_ctx.check_hostname = False\n        ssl_ctx.verify_mode = ssl.CERT_NONE\n        resp = self.fetch(\"/hello\", ssl_options=ssl_ctx)\n        self.assertEqual(resp.body, b\"Hello world!\")\n\n    def test_ssl_options_handshake_fail(self):\n        with ExpectLog(gen_log, \"SSL Error|Uncaught exception\", required=False):\n            with self.assertRaises(ssl.SSLError):\n                self.fetch(\n                    \"/hello\",\n                    ssl_options=dict(cert_reqs=ssl.CERT_REQUIRED),\n                    raise_error=True,\n                )\n\n    def test_ssl_context_handshake_fail(self):\n        with ExpectLog(gen_log, \"SSL Error|Uncaught exception\"):\n            # CERT_REQUIRED is set by default.\n            ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)\n            with self.assertRaises(ssl.SSLError):\n                self.fetch(\"/hello\", ssl_options=ctx, raise_error=True)\n\n    def test_error_logging(self):\n        # No stack traces are logged for SSL errors (in this case,\n        # failure to validate the testing self-signed cert).\n        # The SSLError is exposed through ssl.SSLError.\n        with ExpectLog(gen_log, \".*\") as expect_log:\n            with self.assertRaises(ssl.SSLError):\n                self.fetch(\"/\", validate_cert=True, raise_error=True)\n        self.assertFalse(expect_log.logged_stack)\n\n\nclass CreateAsyncHTTPClientTestCase(AsyncTestCase):\n    def setUp(self):\n        super().setUp()\n        self.saved = AsyncHTTPClient._save_configuration()\n\n    def tearDown(self):\n        AsyncHTTPClient._restore_configuration(self.saved)\n        super().tearDown()\n\n    def test_max_clients(self):\n        AsyncHTTPClient.configure(SimpleAsyncHTTPClient)\n        with closing(AsyncHTTPClient(force_instance=True)) as client:\n            self.assertEqual(client.max_clients, 10)  # type: ignore\n        with closing(AsyncHTTPClient(max_clients=11, force_instance=True)) as client:\n            self.assertEqual(client.max_clients, 11)  # type: ignore\n\n        # Now configure max_clients statically and try overriding it\n        # with each way max_clients can be passed\n        AsyncHTTPClient.configure(SimpleAsyncHTTPClient, max_clients=12)\n        with closing(AsyncHTTPClient(force_instance=True)) as client:\n            self.assertEqual(client.max_clients, 12)  # type: ignore\n        with closing(AsyncHTTPClient(max_clients=13, force_instance=True)) as client:\n            self.assertEqual(client.max_clients, 13)  # type: ignore\n        with closing(AsyncHTTPClient(max_clients=14, force_instance=True)) as client:\n            self.assertEqual(client.max_clients, 14)  # type: ignore\n\n\nclass HTTP100ContinueTestCase(AsyncHTTPTestCase):\n    def respond_100(self, request):\n        self.http1 = request.version.startswith(\"HTTP/1.\")\n        if not self.http1:\n            request.connection.write_headers(\n                ResponseStartLine(\"\", 200, \"OK\"), HTTPHeaders()\n            )\n            request.connection.finish()\n            return\n        self.request = request\n        fut = self.request.connection.stream.write(b\"HTTP/1.1 100 CONTINUE\\r\\n\\r\\n\")\n        fut.add_done_callback(self.respond_200)\n\n    def respond_200(self, fut):\n        fut.result()\n        fut = self.request.connection.stream.write(\n            b\"HTTP/1.1 200 OK\\r\\nContent-Length: 1\\r\\n\\r\\nA\"\n        )\n        fut.add_done_callback(lambda f: self.request.connection.stream.close())\n\n    def get_app(self):\n        # Not a full Application, but works as an HTTPServer callback\n        return self.respond_100\n\n    def test_100_continue(self):\n        res = self.fetch(\"/\")\n        if not self.http1:\n            self.skipTest(\"requires HTTP/1.x\")\n        self.assertEqual(res.body, b\"A\")\n\n\nclass HTTP204NoContentTestCase(AsyncHTTPTestCase):\n    def respond_204(self, request):\n        self.http1 = request.version.startswith(\"HTTP/1.\")\n        if not self.http1:\n            # Close the request cleanly in HTTP/2; it will be skipped anyway.\n            request.connection.write_headers(\n                ResponseStartLine(\"\", 200, \"OK\"), HTTPHeaders()\n            )\n            request.connection.finish()\n            return\n\n        # A 204 response never has a body, even if doesn't have a content-length\n        # (which would otherwise mean read-until-close).  We simulate here a\n        # server that sends no content length and does not close the connection.\n        #\n        # Tests of a 204 response with no Content-Length header are included\n        # in SimpleHTTPClientTestMixin.\n        stream = request.connection.detach()\n        stream.write(b\"HTTP/1.1 204 No content\\r\\n\")\n        if request.arguments.get(\"error\", [False])[-1]:\n            stream.write(b\"Content-Length: 5\\r\\n\")\n        else:\n            stream.write(b\"Content-Length: 0\\r\\n\")\n        stream.write(b\"\\r\\n\")\n        stream.close()\n\n    def get_app(self):\n        return self.respond_204\n\n    def test_204_no_content(self):\n        resp = self.fetch(\"/\")\n        if not self.http1:\n            self.skipTest(\"requires HTTP/1.x\")\n        self.assertEqual(resp.code, 204)\n        self.assertEqual(resp.body, b\"\")\n\n    def test_204_invalid_content_length(self):\n        # 204 status with non-zero content length is malformed\n        with ExpectLog(\n            gen_log, \".*Response with code 204 should not have body\", level=logging.INFO\n        ):\n            with self.assertRaises(HTTPStreamClosedError):\n                self.fetch(\"/?error=1\", raise_error=True)\n                if not self.http1:\n                    self.skipTest(\"requires HTTP/1.x\")\n                if self.http_client.configured_class != SimpleAsyncHTTPClient:\n                    self.skipTest(\"curl client accepts invalid headers\")\n\n\nclass HostnameMappingTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super().setUp()\n        self.http_client = SimpleAsyncHTTPClient(\n            hostname_mapping={\n                \"www.example.com\": \"127.0.0.1\",\n                (\"foo.example.com\", 8000): (\"127.0.0.1\", self.get_http_port()),\n            }\n        )\n\n    def get_app(self):\n        return Application([url(\"/hello\", HelloWorldHandler)])\n\n    def test_hostname_mapping(self):\n        response = self.fetch(\"http://www.example.com:%d/hello\" % self.get_http_port())\n        response.rethrow()\n        self.assertEqual(response.body, b\"Hello world!\")\n\n    def test_port_mapping(self):\n        response = self.fetch(\"http://foo.example.com:8000/hello\")\n        response.rethrow()\n        self.assertEqual(response.body, b\"Hello world!\")\n\n\nclass ResolveTimeoutTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        self.cleanup_event = Event()\n        test = self\n\n        # Dummy Resolver subclass that never finishes.\n        class BadResolver(Resolver):\n            @gen.coroutine\n            def resolve(self, *args, **kwargs):\n                yield test.cleanup_event.wait()\n                # Return something valid so the test doesn't raise during cleanup.\n                return [(socket.AF_INET, (\"127.0.0.1\", test.get_http_port()))]\n\n        super().setUp()\n        self.http_client = SimpleAsyncHTTPClient(resolver=BadResolver())\n\n    def get_app(self):\n        return Application([url(\"/hello\", HelloWorldHandler)])\n\n    def test_resolve_timeout(self):\n        with self.assertRaises(HTTPTimeoutError):\n            self.fetch(\"/hello\", connect_timeout=0.1, raise_error=True)\n\n        # Let the hanging coroutine clean up after itself\n        self.cleanup_event.set()\n        self.io_loop.run_sync(lambda: gen.sleep(0))\n\n\nclass MaxHeaderSizeTest(AsyncHTTPTestCase):\n    def get_app(self):\n        class SmallHeaders(RequestHandler):\n            def get(self):\n                self.set_header(\"X-Filler\", \"a\" * 100)\n                self.write(\"ok\")\n\n        class LargeHeaders(RequestHandler):\n            def get(self):\n                self.set_header(\"X-Filler\", \"a\" * 1000)\n                self.write(\"ok\")\n\n        return Application([(\"/small\", SmallHeaders), (\"/large\", LargeHeaders)])\n\n    def get_http_client(self):\n        return SimpleAsyncHTTPClient(max_header_size=1024)\n\n    def test_small_headers(self):\n        response = self.fetch(\"/small\")\n        response.rethrow()\n        self.assertEqual(response.body, b\"ok\")\n\n    def test_large_headers(self):\n        with ExpectLog(gen_log, \"Unsatisfiable read\", level=logging.INFO):\n            with self.assertRaises(UnsatisfiableReadError):\n                self.fetch(\"/large\", raise_error=True)\n\n\nclass MaxBodySizeTest(AsyncHTTPTestCase):\n    def get_app(self):\n        class SmallBody(RequestHandler):\n            def get(self):\n                self.write(\"a\" * 1024 * 64)\n\n        class LargeBody(RequestHandler):\n            def get(self):\n                self.write(\"a\" * 1024 * 100)\n\n        return Application([(\"/small\", SmallBody), (\"/large\", LargeBody)])\n\n    def get_http_client(self):\n        return SimpleAsyncHTTPClient(max_body_size=1024 * 64)\n\n    def test_small_body(self):\n        response = self.fetch(\"/small\")\n        response.rethrow()\n        self.assertEqual(response.body, b\"a\" * 1024 * 64)\n\n    def test_large_body(self):\n        with ExpectLog(\n            gen_log,\n            \"Malformed HTTP message from None: Content-Length too long\",\n            level=logging.INFO,\n        ):\n            with self.assertRaises(HTTPStreamClosedError):\n                self.fetch(\"/large\", raise_error=True)\n\n\nclass MaxBufferSizeTest(AsyncHTTPTestCase):\n    def get_app(self):\n        class LargeBody(RequestHandler):\n            def get(self):\n                self.write(\"a\" * 1024 * 100)\n\n        return Application([(\"/large\", LargeBody)])\n\n    def get_http_client(self):\n        # 100KB body with 64KB buffer\n        return SimpleAsyncHTTPClient(\n            max_body_size=1024 * 100, max_buffer_size=1024 * 64\n        )\n\n    def test_large_body(self):\n        response = self.fetch(\"/large\")\n        response.rethrow()\n        self.assertEqual(response.body, b\"a\" * 1024 * 100)\n\n\nclass ChunkedWithContentLengthTest(AsyncHTTPTestCase):\n    def get_app(self):\n        class ChunkedWithContentLength(RequestHandler):\n            def get(self):\n                # Add an invalid Transfer-Encoding to the response\n                self.set_header(\"Transfer-Encoding\", \"chunked\")\n                self.write(\"Hello world\")\n\n        return Application([(\"/chunkwithcl\", ChunkedWithContentLength)])\n\n    def get_http_client(self):\n        return SimpleAsyncHTTPClient()\n\n    def test_chunked_with_content_length(self):\n        # Make sure the invalid headers are detected\n        with ExpectLog(\n            gen_log,\n            (\n                \"Malformed HTTP message from None: Message \"\n                \"with both Transfer-Encoding and Content-Length\"\n            ),\n            level=logging.INFO,\n        ):\n            with self.assertRaises(HTTPStreamClosedError):\n                self.fetch(\"/chunkwithcl\", raise_error=True)\n"
  },
  {
    "path": "tornado/test/static/dir/index.html",
    "content": "this is the index\n"
  },
  {
    "path": "tornado/test/static/robots.txt",
    "content": "User-agent: *\nDisallow: /\n"
  },
  {
    "path": "tornado/test/static/sample.xml",
    "content": "<?xml version=\"1.0\"?>\n<data>\n    <country name=\"Liechtenstein\">\n        <rank>1</rank>\n        <year>2008</year>\n        <gdppc>141100</gdppc>\n        <neighbor name=\"Austria\" direction=\"E\"/>\n        <neighbor name=\"Switzerland\" direction=\"W\"/>\n    </country>\n    <country name=\"Singapore\">\n        <rank>4</rank>\n        <year>2011</year>\n        <gdppc>59900</gdppc>\n        <neighbor name=\"Malaysia\" direction=\"N\"/>\n    </country>\n    <country name=\"Panama\">\n        <rank>68</rank>\n        <year>2011</year>\n        <gdppc>13600</gdppc>\n        <neighbor name=\"Costa Rica\" direction=\"W\"/>\n        <neighbor name=\"Colombia\" direction=\"E\"/>\n    </country>\n</data>\n"
  },
  {
    "path": "tornado/test/static_foo.txt",
    "content": "This file should not be served by StaticFileHandler even though\nits name starts with \"static\".\n"
  },
  {
    "path": "tornado/test/tcpclient_test.py",
    "content": "#\n# Copyright 2014 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\nimport getpass\nimport socket\nimport typing\nimport unittest\nfrom contextlib import closing\n\nfrom tornado.concurrent import Future\nfrom tornado.gen import TimeoutError\nfrom tornado.iostream import IOStream\nfrom tornado.netutil import Resolver, bind_sockets\nfrom tornado.queues import Queue\nfrom tornado.tcpclient import TCPClient, _Connector\nfrom tornado.tcpserver import TCPServer\nfrom tornado.test.util import refusing_port, skipIfNoIPv6, skipIfNonUnix\nfrom tornado.testing import AsyncTestCase, gen_test\n\n# Fake address families for testing.  Used in place of AF_INET\n# and AF_INET6 because some installations do not have AF_INET6.\nAF1, AF2 = 1, 2\n\n\nclass TestTCPServer(TCPServer):\n    def __init__(self, family):\n        super().__init__()\n        self.streams: list[IOStream] = []\n        self.queue: Queue[IOStream] = Queue()\n        sockets = bind_sockets(0, \"localhost\", family)\n        self.add_sockets(sockets)\n        self.port = sockets[0].getsockname()[1]\n\n    def handle_stream(self, stream, address):\n        self.streams.append(stream)\n        self.queue.put(stream)\n\n    def stop(self):\n        super().stop()\n        for stream in self.streams:\n            stream.close()\n\n\nclass TCPClientTest(AsyncTestCase):\n    def setUp(self):\n        super().setUp()\n        self.server = None\n        self.client = TCPClient()\n\n    def start_server(self, family):\n        self.server = TestTCPServer(family)\n        return self.server.port\n\n    def stop_server(self):\n        if self.server is not None:\n            self.server.stop()\n            self.server = None\n\n    def tearDown(self):\n        self.client.close()\n        self.stop_server()\n        super().tearDown()\n\n    def skipIfLocalhostV4(self):\n        # The port used here doesn't matter, but some systems require it\n        # to be non-zero if we do not also pass AI_PASSIVE.\n        addrinfo = self.io_loop.run_sync(lambda: Resolver().resolve(\"localhost\", 80))\n        families = {addr[0] for addr in addrinfo}\n        if socket.AF_INET6 not in families:\n            self.skipTest(\"localhost does not resolve to ipv6\")\n\n    @gen_test\n    def do_test_connect(self, family, host, source_ip=None, source_port=None):\n        port = self.start_server(family)\n        stream = yield self.client.connect(\n            host,\n            port,\n            source_ip=source_ip,\n            source_port=source_port,\n            af=family,\n        )\n        assert self.server is not None\n        server_stream = yield self.server.queue.get()\n        with closing(stream):\n            stream.write(b\"hello\")\n            data = yield server_stream.read_bytes(5)\n            self.assertEqual(data, b\"hello\")\n\n    def test_connect_ipv4_ipv4(self):\n        self.do_test_connect(socket.AF_INET, \"127.0.0.1\")\n\n    def test_connect_ipv4_dual(self):\n        self.do_test_connect(socket.AF_INET, \"localhost\")\n\n    @skipIfNoIPv6\n    def test_connect_ipv6_ipv6(self):\n        self.skipIfLocalhostV4()\n        self.do_test_connect(socket.AF_INET6, \"::1\")\n\n    @skipIfNoIPv6\n    def test_connect_ipv6_dual(self):\n        self.skipIfLocalhostV4()\n        self.do_test_connect(socket.AF_INET6, \"localhost\")\n\n    def test_connect_unspec_ipv4(self):\n        self.do_test_connect(socket.AF_UNSPEC, \"127.0.0.1\")\n\n    @skipIfNoIPv6\n    def test_connect_unspec_ipv6(self):\n        self.skipIfLocalhostV4()\n        self.do_test_connect(socket.AF_UNSPEC, \"::1\")\n\n    def test_connect_unspec_dual(self):\n        self.do_test_connect(socket.AF_UNSPEC, \"localhost\")\n\n    @gen_test\n    def test_refused_ipv4(self):\n        cleanup_func, port = refusing_port()\n        self.addCleanup(cleanup_func)\n        with self.assertRaises(IOError):\n            yield self.client.connect(\"127.0.0.1\", port)\n\n    def test_source_ip_fail(self):\n        \"\"\"Fail when trying to use the source IP Address '8.8.8.8'.\"\"\"\n        self.assertRaises(\n            socket.error,\n            self.do_test_connect,\n            socket.AF_INET,\n            \"127.0.0.1\",\n            source_ip=\"8.8.8.8\",\n        )\n\n    def test_source_ip_success(self):\n        \"\"\"Success when trying to use the source IP Address '127.0.0.1'.\"\"\"\n        self.do_test_connect(socket.AF_INET, \"127.0.0.1\", source_ip=\"127.0.0.1\")\n\n    @skipIfNonUnix\n    def test_source_port_fail(self):\n        \"\"\"Fail when trying to use source port 1.\"\"\"\n        if getpass.getuser() == \"root\":\n            # Root can use any port so we can't easily force this to fail.\n            # This is mainly relevant for docker.\n            self.skipTest(\"running as root\")\n        self.assertRaises(\n            socket.error,\n            self.do_test_connect,\n            socket.AF_INET,\n            \"127.0.0.1\",\n            source_port=1,\n        )\n\n    @gen_test\n    def test_connect_timeout(self):\n        timeout = 0.05\n\n        class TimeoutResolver(Resolver):\n            def resolve(self, *args, **kwargs):\n                return Future()  # never completes\n\n        with self.assertRaises(TimeoutError):\n            yield TCPClient(resolver=TimeoutResolver()).connect(\n                \"1.2.3.4\", 12345, timeout=timeout\n            )\n\n\nclass TestConnectorSplit(unittest.TestCase):\n    def test_one_family(self):\n        # These addresses aren't in the right format, but split doesn't care.\n        primary, secondary = _Connector.split([(AF1, \"a\"), (AF1, \"b\")])\n        self.assertEqual(primary, [(AF1, \"a\"), (AF1, \"b\")])\n        self.assertEqual(secondary, [])\n\n    def test_mixed(self):\n        primary, secondary = _Connector.split(\n            [(AF1, \"a\"), (AF2, \"b\"), (AF1, \"c\"), (AF2, \"d\")]\n        )\n        self.assertEqual(primary, [(AF1, \"a\"), (AF1, \"c\")])\n        self.assertEqual(secondary, [(AF2, \"b\"), (AF2, \"d\")])\n\n\nclass ConnectorTest(AsyncTestCase):\n    class FakeStream:\n        def __init__(self):\n            self.closed = False\n\n        def close(self):\n            self.closed = True\n\n    def setUp(self):\n        super().setUp()\n        self.connect_futures: dict[\n            tuple[int, typing.Any], Future[ConnectorTest.FakeStream]\n        ] = {}\n        self.streams: dict[typing.Any, ConnectorTest.FakeStream] = {}\n        self.addrinfo = [(AF1, \"a\"), (AF1, \"b\"), (AF2, \"c\"), (AF2, \"d\")]\n\n    def tearDown(self):\n        # Unless explicitly checked (and popped) in the test, we shouldn't\n        # be closing any streams\n        for stream in self.streams.values():\n            self.assertFalse(stream.closed)\n        super().tearDown()\n\n    def create_stream(self, af, addr):\n        stream = ConnectorTest.FakeStream()\n        self.streams[addr] = stream\n        future: Future[ConnectorTest.FakeStream] = Future()\n        self.connect_futures[(af, addr)] = future\n        return stream, future\n\n    def assert_pending(self, *keys):\n        self.assertEqual(sorted(self.connect_futures.keys()), sorted(keys))\n\n    def resolve_connect(self, af, addr, success):\n        future = self.connect_futures.pop((af, addr))\n        if success:\n            future.set_result(self.streams[addr])\n        else:\n            self.streams.pop(addr)\n            future.set_exception(IOError())\n        # Run the loop to allow callbacks to be run.\n        self.io_loop.add_callback(self.stop)\n        self.wait()\n\n    def assert_connector_streams_closed(self, conn):\n        for stream in conn.streams:\n            self.assertTrue(stream.closed)\n\n    def start_connect(self, addrinfo):\n        conn = _Connector(addrinfo, self.create_stream)\n        # Give it a huge timeout; we'll trigger timeouts manually.\n        future = conn.start(3600, connect_timeout=self.io_loop.time() + 3600)\n        return conn, future\n\n    def test_immediate_success(self):\n        conn, future = self.start_connect(self.addrinfo)\n        self.assertEqual(list(self.connect_futures.keys()), [(AF1, \"a\")])\n        self.resolve_connect(AF1, \"a\", True)\n        self.assertEqual(future.result(), (AF1, \"a\", self.streams[\"a\"]))\n\n    def test_immediate_failure(self):\n        # Fail with just one address.\n        conn, future = self.start_connect([(AF1, \"a\")])\n        self.assert_pending((AF1, \"a\"))\n        self.resolve_connect(AF1, \"a\", False)\n        self.assertRaises(IOError, future.result)\n\n    def test_one_family_second_try(self):\n        conn, future = self.start_connect([(AF1, \"a\"), (AF1, \"b\")])\n        self.assert_pending((AF1, \"a\"))\n        self.resolve_connect(AF1, \"a\", False)\n        self.assert_pending((AF1, \"b\"))\n        self.resolve_connect(AF1, \"b\", True)\n        self.assertEqual(future.result(), (AF1, \"b\", self.streams[\"b\"]))\n\n    def test_one_family_second_try_failure(self):\n        conn, future = self.start_connect([(AF1, \"a\"), (AF1, \"b\")])\n        self.assert_pending((AF1, \"a\"))\n        self.resolve_connect(AF1, \"a\", False)\n        self.assert_pending((AF1, \"b\"))\n        self.resolve_connect(AF1, \"b\", False)\n        self.assertRaises(IOError, future.result)\n\n    def test_one_family_second_try_timeout(self):\n        conn, future = self.start_connect([(AF1, \"a\"), (AF1, \"b\")])\n        self.assert_pending((AF1, \"a\"))\n        # trigger the timeout while the first lookup is pending;\n        # nothing happens.\n        conn.on_timeout()\n        self.assert_pending((AF1, \"a\"))\n        self.resolve_connect(AF1, \"a\", False)\n        self.assert_pending((AF1, \"b\"))\n        self.resolve_connect(AF1, \"b\", True)\n        self.assertEqual(future.result(), (AF1, \"b\", self.streams[\"b\"]))\n\n    def test_two_families_immediate_failure(self):\n        conn, future = self.start_connect(self.addrinfo)\n        self.assert_pending((AF1, \"a\"))\n        self.resolve_connect(AF1, \"a\", False)\n        self.assert_pending((AF1, \"b\"), (AF2, \"c\"))\n        self.resolve_connect(AF1, \"b\", False)\n        self.resolve_connect(AF2, \"c\", True)\n        self.assertEqual(future.result(), (AF2, \"c\", self.streams[\"c\"]))\n\n    def test_two_families_timeout(self):\n        conn, future = self.start_connect(self.addrinfo)\n        self.assert_pending((AF1, \"a\"))\n        conn.on_timeout()\n        self.assert_pending((AF1, \"a\"), (AF2, \"c\"))\n        self.resolve_connect(AF2, \"c\", True)\n        self.assertEqual(future.result(), (AF2, \"c\", self.streams[\"c\"]))\n        # resolving 'a' after the connection has completed doesn't start 'b'\n        self.resolve_connect(AF1, \"a\", False)\n        self.assert_pending()\n\n    def test_success_after_timeout(self):\n        conn, future = self.start_connect(self.addrinfo)\n        self.assert_pending((AF1, \"a\"))\n        conn.on_timeout()\n        self.assert_pending((AF1, \"a\"), (AF2, \"c\"))\n        self.resolve_connect(AF1, \"a\", True)\n        self.assertEqual(future.result(), (AF1, \"a\", self.streams[\"a\"]))\n        # resolving 'c' after completion closes the connection.\n        self.resolve_connect(AF2, \"c\", True)\n        self.assertTrue(self.streams.pop(\"c\").closed)\n\n    def test_all_fail(self):\n        conn, future = self.start_connect(self.addrinfo)\n        self.assert_pending((AF1, \"a\"))\n        conn.on_timeout()\n        self.assert_pending((AF1, \"a\"), (AF2, \"c\"))\n        self.resolve_connect(AF2, \"c\", False)\n        self.assert_pending((AF1, \"a\"), (AF2, \"d\"))\n        self.resolve_connect(AF2, \"d\", False)\n        # one queue is now empty\n        self.assert_pending((AF1, \"a\"))\n        self.resolve_connect(AF1, \"a\", False)\n        self.assert_pending((AF1, \"b\"))\n        self.assertFalse(future.done())\n        self.resolve_connect(AF1, \"b\", False)\n        self.assertRaises(IOError, future.result)\n\n    def test_one_family_timeout_after_connect_timeout(self):\n        conn, future = self.start_connect([(AF1, \"a\"), (AF1, \"b\")])\n        self.assert_pending((AF1, \"a\"))\n        conn.on_connect_timeout()\n        # the connector will close all streams on connect timeout, we\n        # should explicitly pop the connect_future.\n        self.connect_futures.pop((AF1, \"a\"))\n        self.assertTrue(self.streams.pop(\"a\").closed)\n        conn.on_timeout()\n        # if the future is set with TimeoutError, we will not iterate next\n        # possible address.\n        self.assert_pending()\n        self.assertEqual(len(conn.streams), 1)\n        self.assert_connector_streams_closed(conn)\n        self.assertRaises(TimeoutError, future.result)\n\n    def test_one_family_success_before_connect_timeout(self):\n        conn, future = self.start_connect([(AF1, \"a\"), (AF1, \"b\")])\n        self.assert_pending((AF1, \"a\"))\n        self.resolve_connect(AF1, \"a\", True)\n        conn.on_connect_timeout()\n        self.assert_pending()\n        self.assertFalse(self.streams[\"a\"].closed)\n        # success stream will be pop\n        self.assertEqual(len(conn.streams), 0)\n        # streams in connector should be closed after connect timeout\n        self.assert_connector_streams_closed(conn)\n        self.assertEqual(future.result(), (AF1, \"a\", self.streams[\"a\"]))\n\n    def test_one_family_second_try_after_connect_timeout(self):\n        conn, future = self.start_connect([(AF1, \"a\"), (AF1, \"b\")])\n        self.assert_pending((AF1, \"a\"))\n        self.resolve_connect(AF1, \"a\", False)\n        self.assert_pending((AF1, \"b\"))\n        conn.on_connect_timeout()\n        self.connect_futures.pop((AF1, \"b\"))\n        self.assertTrue(self.streams.pop(\"b\").closed)\n        self.assert_pending()\n        self.assertEqual(len(conn.streams), 2)\n        self.assert_connector_streams_closed(conn)\n        self.assertRaises(TimeoutError, future.result)\n\n    def test_one_family_second_try_failure_before_connect_timeout(self):\n        conn, future = self.start_connect([(AF1, \"a\"), (AF1, \"b\")])\n        self.assert_pending((AF1, \"a\"))\n        self.resolve_connect(AF1, \"a\", False)\n        self.assert_pending((AF1, \"b\"))\n        self.resolve_connect(AF1, \"b\", False)\n        conn.on_connect_timeout()\n        self.assert_pending()\n        self.assertEqual(len(conn.streams), 2)\n        self.assert_connector_streams_closed(conn)\n        self.assertRaises(IOError, future.result)\n\n    def test_two_family_timeout_before_connect_timeout(self):\n        conn, future = self.start_connect(self.addrinfo)\n        self.assert_pending((AF1, \"a\"))\n        conn.on_timeout()\n        self.assert_pending((AF1, \"a\"), (AF2, \"c\"))\n        conn.on_connect_timeout()\n        self.connect_futures.pop((AF1, \"a\"))\n        self.assertTrue(self.streams.pop(\"a\").closed)\n        self.connect_futures.pop((AF2, \"c\"))\n        self.assertTrue(self.streams.pop(\"c\").closed)\n        self.assert_pending()\n        self.assertEqual(len(conn.streams), 2)\n        self.assert_connector_streams_closed(conn)\n        self.assertRaises(TimeoutError, future.result)\n\n    def test_two_family_success_after_timeout(self):\n        conn, future = self.start_connect(self.addrinfo)\n        self.assert_pending((AF1, \"a\"))\n        conn.on_timeout()\n        self.assert_pending((AF1, \"a\"), (AF2, \"c\"))\n        self.resolve_connect(AF1, \"a\", True)\n        # if one of streams succeed, connector will close all other streams\n        self.connect_futures.pop((AF2, \"c\"))\n        self.assertTrue(self.streams.pop(\"c\").closed)\n        self.assert_pending()\n        self.assertEqual(len(conn.streams), 1)\n        self.assert_connector_streams_closed(conn)\n        self.assertEqual(future.result(), (AF1, \"a\", self.streams[\"a\"]))\n\n    def test_two_family_timeout_after_connect_timeout(self):\n        conn, future = self.start_connect(self.addrinfo)\n        self.assert_pending((AF1, \"a\"))\n        conn.on_connect_timeout()\n        self.connect_futures.pop((AF1, \"a\"))\n        self.assertTrue(self.streams.pop(\"a\").closed)\n        self.assert_pending()\n        conn.on_timeout()\n        # if the future is set with TimeoutError, connector will not\n        # trigger secondary address.\n        self.assert_pending()\n        self.assertEqual(len(conn.streams), 1)\n        self.assert_connector_streams_closed(conn)\n        self.assertRaises(TimeoutError, future.result)\n"
  },
  {
    "path": "tornado/test/tcpserver_test.py",
    "content": "import socket\nimport subprocess\nimport sys\nimport textwrap\nimport unittest\n\nfrom tornado import gen\nfrom tornado.iostream import IOStream\nfrom tornado.log import app_log\nfrom tornado.tcpserver import TCPServer\nfrom tornado.test.util import skipIfNonUnix\nfrom tornado.testing import AsyncTestCase, ExpectLog, bind_unused_port, gen_test\n\n\nclass TCPServerTest(AsyncTestCase):\n    @gen_test\n    def test_handle_stream_coroutine_logging(self):\n        # handle_stream may be a coroutine and any exception in its\n        # Future will be logged.\n        class TestServer(TCPServer):\n            @gen.coroutine\n            def handle_stream(self, stream, address):\n                yield stream.read_bytes(len(b\"hello\"))\n                stream.close()\n                1 / 0\n\n        server = client = None\n        try:\n            sock, port = bind_unused_port()\n            server = TestServer()\n            server.add_socket(sock)\n            client = IOStream(socket.socket())\n            with ExpectLog(app_log, \"Exception in callback\"):\n                yield client.connect((\"localhost\", port))\n                yield client.write(b\"hello\")\n                yield client.read_until_close()\n                yield gen.moment\n        finally:\n            if server is not None:\n                server.stop()\n            if client is not None:\n                client.close()\n\n    @gen_test\n    def test_handle_stream_native_coroutine(self):\n        # handle_stream may be a native coroutine.\n\n        class TestServer(TCPServer):\n            async def handle_stream(self, stream, address):\n                stream.write(b\"data\")\n                stream.close()\n\n        sock, port = bind_unused_port()\n        server = TestServer()\n        server.add_socket(sock)\n        client = IOStream(socket.socket())\n        yield client.connect((\"localhost\", port))\n        result = yield client.read_until_close()\n        self.assertEqual(result, b\"data\")\n        server.stop()\n        client.close()\n\n    def test_stop_twice(self):\n        sock, port = bind_unused_port()\n        server = TCPServer()\n        server.add_socket(sock)\n        server.stop()\n        server.stop()\n\n    @gen_test\n    def test_stop_in_callback(self):\n        # Issue #2069: calling server.stop() in a loop callback should not\n        # raise EBADF when the loop handles other server connection\n        # requests in the same loop iteration\n\n        class TestServer(TCPServer):\n            @gen.coroutine\n            def handle_stream(self, stream, address):\n                server.stop()  # type: ignore\n                yield stream.read_until_close()\n\n        sock, port = bind_unused_port()\n        server = TestServer()\n        server.add_socket(sock)\n        server_addr = (\"localhost\", port)\n        N = 40\n        clients = [IOStream(socket.socket()) for i in range(N)]\n        connected_clients = []\n\n        @gen.coroutine\n        def connect(c):\n            try:\n                yield c.connect(server_addr)\n            except OSError:\n                pass\n            else:\n                connected_clients.append(c)\n\n        yield [connect(c) for c in clients]\n\n        self.assertGreater(len(connected_clients), 0, \"all clients failed connecting\")\n        try:\n            if len(connected_clients) == N:\n                # Ideally we'd make the test deterministic, but we're testing\n                # for a race condition in combination with the system's TCP stack...\n                self.skipTest(\n                    \"at least one client should fail connecting \"\n                    \"for the test to be meaningful\"\n                )\n        finally:\n            for c in connected_clients:\n                c.close()\n\n        # Here tearDown() would re-raise the EBADF encountered in the IO loop\n\n\n@skipIfNonUnix\nclass TestMultiprocess(unittest.TestCase):\n    # These tests verify that the two multiprocess examples from the\n    # TCPServer docs work. Both tests start a server with three worker\n    # processes, each of which prints its task id to stdout (a single\n    # byte, so we don't have to worry about atomicity of the shared\n    # stdout stream) and then exits.\n    def run_subproc(self, code: str) -> tuple[str, str]:\n        try:\n            result = subprocess.run(\n                [sys.executable, \"-Werror::DeprecationWarning\"],\n                capture_output=True,\n                input=code,\n                encoding=\"utf8\",\n                check=True,\n            )\n        except subprocess.CalledProcessError as e:\n            raise RuntimeError(\n                f\"Process returned {e.returncode} stdout={e.stdout} stderr={e.stderr}\"\n            ) from e\n        return result.stdout, result.stderr\n\n    def test_listen_single(self):\n        # As a sanity check, run the single-process version through this test\n        # harness too.\n        code = textwrap.dedent(\"\"\"\n            import asyncio\n            from tornado.tcpserver import TCPServer\n\n            async def main():\n                server = TCPServer()\n                server.listen(0, address='127.0.0.1')\n\n            asyncio.run(main())\n            print('012', end='')\n        \"\"\")\n        out, err = self.run_subproc(code)\n        self.assertEqual(\"\".join(sorted(out)), \"012\")\n        self.assertEqual(err, \"\")\n\n    def test_bind_start(self):\n        code = textwrap.dedent(\"\"\"\n            import warnings\n\n            from tornado.ioloop import IOLoop\n            from tornado.process import task_id\n            from tornado.tcpserver import TCPServer\n\n            warnings.simplefilter(\"ignore\", DeprecationWarning)\n\n            server = TCPServer()\n            server.bind(0, address='127.0.0.1')\n            server.start(3)\n            IOLoop.current().run_sync(lambda: None)\n            print(task_id(), end='')\n        \"\"\")\n        out, err = self.run_subproc(code)\n        self.assertEqual(\"\".join(sorted(out)), \"012\")\n        self.assertEqual(err, \"\")\n\n    def test_add_sockets(self):\n        code = textwrap.dedent(\"\"\"\n            import asyncio\n            from tornado.netutil import bind_sockets\n            from tornado.process import fork_processes, task_id\n            from tornado.ioloop import IOLoop\n            from tornado.tcpserver import TCPServer\n\n            sockets = bind_sockets(0, address='127.0.0.1')\n            fork_processes(3)\n            async def post_fork_main():\n                server = TCPServer()\n                server.add_sockets(sockets)\n            asyncio.run(post_fork_main())\n            print(task_id(), end='')\n        \"\"\")\n        out, err = self.run_subproc(code)\n        self.assertEqual(\"\".join(sorted(out)), \"012\")\n        self.assertEqual(err, \"\")\n\n    def test_listen_multi_reuse_port(self):\n        code = textwrap.dedent(\"\"\"\n            import asyncio\n            import socket\n            from tornado.netutil import bind_sockets\n            from tornado.process import task_id, fork_processes\n            from tornado.tcpserver import TCPServer\n\n            # Pick an unused port which we will be able to bind to multiple times.\n            (sock,) = bind_sockets(0, address='127.0.0.1',\n                family=socket.AF_INET, reuse_port=True)\n            port = sock.getsockname()[1]\n\n            fork_processes(3)\n\n            async def main():\n                server = TCPServer()\n                server.listen(port, address='127.0.0.1', reuse_port=True)\n            asyncio.run(main())\n            print(task_id(), end='')\n            \"\"\")\n        out, err = self.run_subproc(code)\n        self.assertEqual(\"\".join(sorted(out)), \"012\")\n        self.assertEqual(err, \"\")\n"
  },
  {
    "path": "tornado/test/template_test.py",
    "content": "import os\nimport traceback\nimport unittest\n\nfrom tornado.escape import native_str, to_unicode, utf8\nfrom tornado.template import DictLoader, Loader, ParseError, Template\nfrom tornado.util import ObjectDict\n\n\nclass TemplateTest(unittest.TestCase):\n    def test_simple(self):\n        template = Template(\"Hello {{ name }}!\")\n        self.assertEqual(template.generate(name=\"Ben\"), b\"Hello Ben!\")\n\n    def test_bytes(self):\n        template = Template(\"Hello {{ name }}!\")\n        self.assertEqual(template.generate(name=utf8(\"Ben\")), b\"Hello Ben!\")\n\n    def test_expressions(self):\n        template = Template(\"2 + 2 = {{ 2 + 2 }}\")\n        self.assertEqual(template.generate(), b\"2 + 2 = 4\")\n\n    def test_comment(self):\n        template = Template(\"Hello{# TODO i18n #} {{ name }}!\")\n        self.assertEqual(template.generate(name=utf8(\"Ben\")), b\"Hello Ben!\")\n\n    def test_include(self):\n        loader = DictLoader(\n            {\n                \"index.html\": '{% include \"header.html\" %}\\nbody text',\n                \"header.html\": \"header text\",\n            }\n        )\n        self.assertEqual(\n            loader.load(\"index.html\").generate(), b\"header text\\nbody text\"\n        )\n\n    def test_extends(self):\n        loader = DictLoader(\n            {\n                \"base.html\": \"\"\"\\\n<title>{% block title %}default title{% end %}</title>\n<body>{% block body %}default body{% end %}</body>\n\"\"\",\n                \"page.html\": \"\"\"\\\n{% extends \"base.html\" %}\n{% block title %}page title{% end %}\n{% block body %}page body{% end %}\n\"\"\",\n            }\n        )\n        self.assertEqual(\n            loader.load(\"page.html\").generate(),\n            b\"<title>page title</title>\\n<body>page body</body>\\n\",\n        )\n\n    def test_relative_load(self):\n        loader = DictLoader(\n            {\n                \"a/1.html\": \"{% include '2.html' %}\",\n                \"a/2.html\": \"{% include '../b/3.html' %}\",\n                \"b/3.html\": \"ok\",\n            }\n        )\n        self.assertEqual(loader.load(\"a/1.html\").generate(), b\"ok\")\n\n    def test_escaping(self):\n        self.assertRaises(ParseError, lambda: Template(\"{{\"))\n        self.assertRaises(ParseError, lambda: Template(\"{%\"))\n        self.assertEqual(Template(\"{{!\").generate(), b\"{{\")\n        self.assertEqual(Template(\"{%!\").generate(), b\"{%\")\n        self.assertEqual(Template(\"{#!\").generate(), b\"{#\")\n        self.assertEqual(\n            Template(\"{{ 'expr' }} {{!jquery expr}}\").generate(),\n            b\"expr {{jquery expr}}\",\n        )\n\n    def test_unicode_template(self):\n        template = Template(utf8(\"\\u00e9\"))\n        self.assertEqual(template.generate(), utf8(\"\\u00e9\"))\n\n    def test_unicode_literal_expression(self):\n        # Unicode literals should be usable in templates.  Note that this\n        # test simulates unicode characters appearing directly in the\n        # template file (with utf8 encoding), i.e. \\u escapes would not\n        # be used in the template file itself.\n        template = Template(utf8('{{ \"\\u00e9\" }}'))\n        self.assertEqual(template.generate(), utf8(\"\\u00e9\"))\n\n    def test_custom_namespace(self):\n        loader = DictLoader(\n            {\"test.html\": \"{{ inc(5) }}\"}, namespace={\"inc\": lambda x: x + 1}\n        )\n        self.assertEqual(loader.load(\"test.html\").generate(), b\"6\")\n\n    def test_apply(self):\n        def upper(s):\n            return s.upper()\n\n        template = Template(utf8(\"{% apply upper %}foo{% end %}\"))\n        self.assertEqual(template.generate(upper=upper), b\"FOO\")\n\n    def test_unicode_apply(self):\n        def upper(s):\n            return to_unicode(s).upper()\n\n        template = Template(utf8(\"{% apply upper %}foo \\u00e9{% end %}\"))\n        self.assertEqual(template.generate(upper=upper), utf8(\"FOO \\u00c9\"))\n\n    def test_bytes_apply(self):\n        def upper(s):\n            return utf8(to_unicode(s).upper())\n\n        template = Template(utf8(\"{% apply upper %}foo \\u00e9{% end %}\"))\n        self.assertEqual(template.generate(upper=upper), utf8(\"FOO \\u00c9\"))\n\n    def test_if(self):\n        template = Template(utf8(\"{% if x > 4 %}yes{% else %}no{% end %}\"))\n        self.assertEqual(template.generate(x=5), b\"yes\")\n        self.assertEqual(template.generate(x=3), b\"no\")\n\n    def test_if_empty_body(self):\n        template = Template(utf8(\"{% if True %}{% else %}{% end %}\"))\n        self.assertEqual(template.generate(), b\"\")\n\n    def test_try(self):\n        template = Template(utf8(\"\"\"{% try %}\ntry{% set y = 1/x %}\n{% except %}-except\n{% else %}-else\n{% finally %}-finally\n{% end %}\"\"\"))\n        self.assertEqual(template.generate(x=1), b\"\\ntry\\n-else\\n-finally\\n\")\n        self.assertEqual(template.generate(x=0), b\"\\ntry-except\\n-finally\\n\")\n\n    def test_comment_directive(self):\n        template = Template(utf8(\"{% comment blah blah %}foo\"))\n        self.assertEqual(template.generate(), b\"foo\")\n\n    def test_break_continue(self):\n        template = Template(utf8(\"\"\"\\\n{% for i in range(10) %}\n    {% if i == 2 %}\n        {% continue %}\n    {% end %}\n    {{ i }}\n    {% if i == 6 %}\n        {% break %}\n    {% end %}\n{% end %}\"\"\"))\n        result = template.generate()\n        # remove extraneous whitespace\n        result = b\"\".join(result.split())\n        self.assertEqual(result, b\"013456\")\n\n    def test_break_outside_loop(self):\n        with self.assertRaises(ParseError, msg=\"Did not get expected exception\"):\n            Template(utf8(\"{% break %}\"))\n\n    def test_break_in_apply(self):\n        # This test verifies current behavior, although of course it would\n        # be nice if apply didn't cause seemingly unrelated breakage\n        with self.assertRaises(ParseError, msg=\"Did not get expected exception\"):\n            Template(\n                utf8(\"{% for i in [] %}{% apply foo %}{% break %}{% end %}{% end %}\")\n            )\n\n    @unittest.skip(\"no testable future imports\")\n    def test_no_inherit_future(self):\n        # TODO(bdarnell): make a test like this for one of the future\n        # imports available in python 3. Unfortunately they're harder\n        # to use in a template than division was.\n\n        # This file has from __future__ import division...\n        self.assertEqual(1 / 2, 0.5)\n        # ...but the template doesn't\n        template = Template(\"{{ 1 / 2 }}\")\n        self.assertEqual(template.generate(), \"0\")\n\n    def test_non_ascii_name(self):\n        loader = DictLoader({\"t\\u00e9st.html\": \"hello\"})\n        self.assertEqual(loader.load(\"t\\u00e9st.html\").generate(), b\"hello\")\n\n\nclass StackTraceTest(unittest.TestCase):\n    def test_error_line_number_expression(self):\n        loader = DictLoader({\"test.html\": \"\"\"one\ntwo{{1/0}}\nthree\n        \"\"\"})\n        try:\n            loader.load(\"test.html\").generate()\n            self.fail(\"did not get expected exception\")\n        except ZeroDivisionError:\n            self.assertTrue(\"# test.html:2\" in traceback.format_exc())\n\n    def test_error_line_number_directive(self):\n        loader = DictLoader({\"test.html\": \"\"\"one\ntwo{%if 1/0%}\nthree{%end%}\n        \"\"\"})\n        try:\n            loader.load(\"test.html\").generate()\n            self.fail(\"did not get expected exception\")\n        except ZeroDivisionError:\n            self.assertTrue(\"# test.html:2\" in traceback.format_exc())\n\n    def test_error_line_number_module(self):\n        loader: DictLoader | None = None\n\n        def load_generate(path, **kwargs):\n            assert loader is not None\n            return loader.load(path).generate(**kwargs)\n\n        loader = DictLoader(\n            {\"base.html\": \"{% module Template('sub.html') %}\", \"sub.html\": \"{{1/0}}\"},\n            namespace={\"_tt_modules\": ObjectDict(Template=load_generate)},\n        )\n        try:\n            loader.load(\"base.html\").generate()\n            self.fail(\"did not get expected exception\")\n        except ZeroDivisionError:\n            exc_stack = traceback.format_exc()\n            self.assertTrue(\"# base.html:1\" in exc_stack)\n            self.assertTrue(\"# sub.html:1\" in exc_stack)\n\n    def test_error_line_number_include(self):\n        loader = DictLoader(\n            {\"base.html\": \"{% include 'sub.html' %}\", \"sub.html\": \"{{1/0}}\"}\n        )\n        try:\n            loader.load(\"base.html\").generate()\n            self.fail(\"did not get expected exception\")\n        except ZeroDivisionError:\n            self.assertTrue(\"# sub.html:1 (via base.html:1)\" in traceback.format_exc())\n\n    def test_error_line_number_extends_base_error(self):\n        loader = DictLoader(\n            {\"base.html\": \"{{1/0}}\", \"sub.html\": \"{% extends 'base.html' %}\"}\n        )\n        try:\n            loader.load(\"sub.html\").generate()\n            self.fail(\"did not get expected exception\")\n        except ZeroDivisionError:\n            exc_stack = traceback.format_exc()\n        self.assertIn(\"# base.html:1\", exc_stack)\n\n    def test_error_line_number_extends_sub_error(self):\n        loader = DictLoader(\n            {\n                \"base.html\": \"{% block 'block' %}{% end %}\",\n                \"sub.html\": \"\"\"\n{% extends 'base.html' %}\n{% block 'block' %}\n{{1/0}}\n{% end %}\n            \"\"\",\n            }\n        )\n        try:\n            loader.load(\"sub.html\").generate()\n            self.fail(\"did not get expected exception\")\n        except ZeroDivisionError:\n            self.assertIn(\"# sub.html:4 (via base.html:1)\", traceback.format_exc())\n\n    def test_multi_includes(self):\n        loader = DictLoader(\n            {\n                \"a.html\": \"{% include 'b.html' %}\",\n                \"b.html\": \"{% include 'c.html' %}\",\n                \"c.html\": \"{{1/0}}\",\n            }\n        )\n        try:\n            loader.load(\"a.html\").generate()\n            self.fail(\"did not get expected exception\")\n        except ZeroDivisionError:\n            self.assertIn(\"# c.html:1 (via b.html:1, a.html:1)\", traceback.format_exc())\n\n\nclass ParseErrorDetailTest(unittest.TestCase):\n    def test_details(self):\n        loader = DictLoader({\"foo.html\": \"\\n\\n{{\"})\n        with self.assertRaises(ParseError) as cm:\n            loader.load(\"foo.html\")\n        self.assertEqual(\"Missing end expression }} at foo.html:3\", str(cm.exception))\n        self.assertEqual(\"foo.html\", cm.exception.filename)\n        self.assertEqual(3, cm.exception.lineno)\n\n    def test_custom_parse_error(self):\n        # Make sure that ParseErrors remain compatible with their\n        # pre-4.3 signature.\n        self.assertEqual(\"asdf at None:0\", str(ParseError(\"asdf\")))\n\n\nclass AutoEscapeTest(unittest.TestCase):\n    def setUp(self):\n        self.templates = {\n            \"escaped.html\": \"{% autoescape xhtml_escape %}{{ name }}\",\n            \"unescaped.html\": \"{% autoescape None %}{{ name }}\",\n            \"default.html\": \"{{ name }}\",\n            \"include.html\": \"\"\"\\\nescaped: {% include 'escaped.html' %}\nunescaped: {% include 'unescaped.html' %}\ndefault: {% include 'default.html' %}\n\"\"\",\n            \"escaped_block.html\": \"\"\"\\\n{% autoescape xhtml_escape %}\\\n{% block name %}base: {{ name }}{% end %}\"\"\",\n            \"unescaped_block.html\": \"\"\"\\\n{% autoescape None %}\\\n{% block name %}base: {{ name }}{% end %}\"\"\",\n            # Extend a base template with different autoescape policy,\n            # with and without overriding the base's blocks\n            \"escaped_extends_unescaped.html\": \"\"\"\\\n{% autoescape xhtml_escape %}\\\n{% extends \"unescaped_block.html\" %}\"\"\",\n            \"escaped_overrides_unescaped.html\": \"\"\"\\\n{% autoescape xhtml_escape %}\\\n{% extends \"unescaped_block.html\" %}\\\n{% block name %}extended: {{ name }}{% end %}\"\"\",\n            \"unescaped_extends_escaped.html\": \"\"\"\\\n{% autoescape None %}\\\n{% extends \"escaped_block.html\" %}\"\"\",\n            \"unescaped_overrides_escaped.html\": \"\"\"\\\n{% autoescape None %}\\\n{% extends \"escaped_block.html\" %}\\\n{% block name %}extended: {{ name }}{% end %}\"\"\",\n            \"raw_expression.html\": \"\"\"\\\n{% autoescape xhtml_escape %}\\\nexpr: {{ name }}\nraw: {% raw name %}\"\"\",\n        }\n\n    def test_default_off(self):\n        loader = DictLoader(self.templates, autoescape=None)\n        name = \"Bobby <table>s\"\n        self.assertEqual(\n            loader.load(\"escaped.html\").generate(name=name), b\"Bobby &lt;table&gt;s\"\n        )\n        self.assertEqual(\n            loader.load(\"unescaped.html\").generate(name=name), b\"Bobby <table>s\"\n        )\n        self.assertEqual(\n            loader.load(\"default.html\").generate(name=name), b\"Bobby <table>s\"\n        )\n\n        self.assertEqual(\n            loader.load(\"include.html\").generate(name=name),\n            b\"escaped: Bobby &lt;table&gt;s\\n\"\n            b\"unescaped: Bobby <table>s\\n\"\n            b\"default: Bobby <table>s\\n\",\n        )\n\n    def test_default_on(self):\n        loader = DictLoader(self.templates, autoescape=\"xhtml_escape\")\n        name = \"Bobby <table>s\"\n        self.assertEqual(\n            loader.load(\"escaped.html\").generate(name=name), b\"Bobby &lt;table&gt;s\"\n        )\n        self.assertEqual(\n            loader.load(\"unescaped.html\").generate(name=name), b\"Bobby <table>s\"\n        )\n        self.assertEqual(\n            loader.load(\"default.html\").generate(name=name), b\"Bobby &lt;table&gt;s\"\n        )\n\n        self.assertEqual(\n            loader.load(\"include.html\").generate(name=name),\n            b\"escaped: Bobby &lt;table&gt;s\\n\"\n            b\"unescaped: Bobby <table>s\\n\"\n            b\"default: Bobby &lt;table&gt;s\\n\",\n        )\n\n    def test_unextended_block(self):\n        loader = DictLoader(self.templates)\n        name = \"<script>\"\n        self.assertEqual(\n            loader.load(\"escaped_block.html\").generate(name=name),\n            b\"base: &lt;script&gt;\",\n        )\n        self.assertEqual(\n            loader.load(\"unescaped_block.html\").generate(name=name), b\"base: <script>\"\n        )\n\n    def test_extended_block(self):\n        loader = DictLoader(self.templates)\n\n        def render(name):\n            return loader.load(name).generate(name=\"<script>\")\n\n        self.assertEqual(render(\"escaped_extends_unescaped.html\"), b\"base: <script>\")\n        self.assertEqual(\n            render(\"escaped_overrides_unescaped.html\"), b\"extended: &lt;script&gt;\"\n        )\n\n        self.assertEqual(\n            render(\"unescaped_extends_escaped.html\"), b\"base: &lt;script&gt;\"\n        )\n        self.assertEqual(\n            render(\"unescaped_overrides_escaped.html\"), b\"extended: <script>\"\n        )\n\n    def test_raw_expression(self):\n        loader = DictLoader(self.templates)\n\n        def render(name):\n            return loader.load(name).generate(name='<>&\"')\n\n        self.assertEqual(\n            render(\"raw_expression.html\"), b\"expr: &lt;&gt;&amp;&quot;\\n\" b'raw: <>&\"'\n        )\n\n    def test_custom_escape(self):\n        loader = DictLoader({\"foo.py\": \"{% autoescape py_escape %}s = {{ name }}\\n\"})\n\n        def py_escape(s):\n            self.assertEqual(type(s), bytes)\n            return repr(native_str(s))\n\n        def render(template, name):\n            return loader.load(template).generate(py_escape=py_escape, name=name)\n\n        self.assertEqual(render(\"foo.py\", \"<html>\"), b\"s = '<html>'\\n\")\n        self.assertEqual(render(\"foo.py\", \"';sys.exit()\"), b\"\"\"s = \"';sys.exit()\"\\n\"\"\")\n        self.assertEqual(\n            render(\"foo.py\", [\"not a string\"]), b\"\"\"s = \"['not a string']\"\\n\"\"\"\n        )\n\n    def test_manual_minimize_whitespace(self):\n        # Whitespace including newlines is allowed within template tags\n        # and directives, and this is one way to avoid long lines while\n        # keeping extra whitespace out of the rendered output.\n        loader = DictLoader({\"foo.txt\": \"\"\"\\\n{% for i in items\n  %}{% if i > 0 %}, {% end %}{#\n  #}{{i\n  }}{% end\n%}\"\"\"})\n        self.assertEqual(\n            loader.load(\"foo.txt\").generate(items=range(5)), b\"0, 1, 2, 3, 4\"\n        )\n\n    def test_whitespace_by_filename(self):\n        # Default whitespace handling depends on the template filename.\n        loader = DictLoader(\n            {\n                \"foo.html\": \"   \\n\\t\\n asdf\\t   \",\n                \"bar.js\": \" \\n\\n\\n\\t qwer     \",\n                \"baz.txt\": \"\\t    zxcv\\n\\n\",\n                \"include.html\": \"  {% include baz.txt %} \\n \",\n                \"include.txt\": \"\\t\\t{% include foo.html %}    \",\n            }\n        )\n\n        # HTML and JS files have whitespace compressed by default.\n        self.assertEqual(loader.load(\"foo.html\").generate(), b\"\\nasdf \")\n        self.assertEqual(loader.load(\"bar.js\").generate(), b\"\\nqwer \")\n        # TXT files do not.\n        self.assertEqual(loader.load(\"baz.txt\").generate(), b\"\\t    zxcv\\n\\n\")\n\n        # Each file maintains its own status even when included in\n        # a file of the other type.\n        self.assertEqual(loader.load(\"include.html\").generate(), b\" \\t    zxcv\\n\\n\\n\")\n        self.assertEqual(loader.load(\"include.txt\").generate(), b\"\\t\\t\\nasdf     \")\n\n    def test_whitespace_by_loader(self):\n        templates = {\"foo.html\": \"\\t\\tfoo\\n\\n\", \"bar.txt\": \"\\t\\tbar\\n\\n\"}\n        loader = DictLoader(templates, whitespace=\"all\")\n        self.assertEqual(loader.load(\"foo.html\").generate(), b\"\\t\\tfoo\\n\\n\")\n        self.assertEqual(loader.load(\"bar.txt\").generate(), b\"\\t\\tbar\\n\\n\")\n\n        loader = DictLoader(templates, whitespace=\"single\")\n        self.assertEqual(loader.load(\"foo.html\").generate(), b\" foo\\n\")\n        self.assertEqual(loader.load(\"bar.txt\").generate(), b\" bar\\n\")\n\n        loader = DictLoader(templates, whitespace=\"oneline\")\n        self.assertEqual(loader.load(\"foo.html\").generate(), b\" foo \")\n        self.assertEqual(loader.load(\"bar.txt\").generate(), b\" bar \")\n\n    def test_whitespace_directive(self):\n        loader = DictLoader({\"foo.html\": \"\"\"\\\n{% whitespace oneline %}\n    {% for i in range(3) %}\n        {{ i }}\n    {% end %}\n{% whitespace all %}\n    pre\\tformatted\n\"\"\"})\n        self.assertEqual(\n            loader.load(\"foo.html\").generate(), b\"  0  1  2  \\n    pre\\tformatted\\n\"\n        )\n\n\nclass TemplateLoaderTest(unittest.TestCase):\n    def setUp(self):\n        self.loader = Loader(os.path.join(os.path.dirname(__file__), \"templates\"))\n\n    def test_utf8_in_file(self):\n        tmpl = self.loader.load(\"utf8.html\")\n        result = tmpl.generate()\n        self.assertEqual(to_unicode(result).strip(), \"H\\u00e9llo\")\n"
  },
  {
    "path": "tornado/test/templates/utf8.html",
    "content": "Héllo\n"
  },
  {
    "path": "tornado/test/test.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIC1TCCAb2gAwIBAgIJAOV36k+idrqDMA0GCSqGSIb3DQEBCwUAMBoxGDAWBgNV\nBAMMD2Zvby5leGFtcGxlLmNvbTAeFw0yMzExMTIwMTQ3MzhaFw0zMzExMDkwMTQ3\nMzhaMBoxGDAWBgNVBAMMD2Zvby5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBAKjfAL8hQ1G5yoR29D0NwqhL3EE9RShYLvzKvSNhOceR\ne390XJLAi8PN8Xv8LkmoMITaLdRDtBwXcdw+kfHjcfXZ0cORJkxJFdk/38SsiBKV\nZzMO+1PVULfnQa92tHtahNsTGI5367WEALn9UNJLmP+jpX+3zohatUTbhlnRSruH\nO/Mo5mLs1XJhQpdvp8BQNksJhiTQ7FsbcjGq6gZ75SnbfUR0PyohY0LTsrql00Tz\nhCAEvm2TNiQ5s+PT5fFOg6Jh2ZGj1lYLQY3dDeqt9sdabvj7LANqfygbt2cf9yYn\na25UTRcAN7CNdWwTEfvnOVMITzCE8F2FmKDvJR+TX30CAwEAAaMeMBwwGgYDVR0R\nBBMwEYIPZm9vLmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBjKz4gM4Bz\nJO7Ny1fwbBtraHCGYnDG8gBID3+/sQlMMFeuquJK+oc+1DOpr9wFlmgih67OszdM\nX2Xl/HjtHPKwNqaDHXu5bQPFT5fXzAZ8HHEOXSV9IpHaNyS7TC7bYmD/ClCZeqXU\nh7MBe5yPXfCCIqWyjZMZDQfT1v6J+WX3+lO9josMJCfNR5DzvJiPmSTUxrLD5SkT\n+7iKxhM6eI83D+I188sGc2IMinkFp8jSRTlaH8WYiOd5QQ2r8GSYNM9M3z1sK7zv\n0Bw3hWEQgpFbEaSH0OB72KYkMUZBqK9UoeSZWBrMXHFBNaY23tEKInEwlBGBELGc\nacSinK6OBC0z\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "tornado/test/test.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCo3wC/IUNRucqE\ndvQ9DcKoS9xBPUUoWC78yr0jYTnHkXt/dFySwIvDzfF7/C5JqDCE2i3UQ7QcF3Hc\nPpHx43H12dHDkSZMSRXZP9/ErIgSlWczDvtT1VC350GvdrR7WoTbExiOd+u1hAC5\n/VDSS5j/o6V/t86IWrVE24ZZ0Uq7hzvzKOZi7NVyYUKXb6fAUDZLCYYk0OxbG3Ix\nquoGe+Up231EdD8qIWNC07K6pdNE84QgBL5tkzYkObPj0+XxToOiYdmRo9ZWC0GN\n3Q3qrfbHWm74+ywDan8oG7dnH/cmJ2tuVE0XADewjXVsExH75zlTCE8whPBdhZig\n7yUfk199AgMBAAECggEBAIGFmXL/Nj0GvVfgTPBPD5A5rxOyxMpu6IsnjO4H8mMp\nKInXW/GLESf7W053W6FPCPe8yA3YZ9pr+P6uVw4qHwwsJwFS4Qb9v25D2YNluXBX\nezHkOcxQ/novO2gzKba69M961Ajh3b35Iv2EV2sUZKMehx9wgU6AFCxeG6vkJOez\nUCX0WG467cdo4alfe/oQZLioU3t+GGCb23m13B9xaN2tqONNh2E2yp73MVJ1Q74R\nHVBkQxciHd3iJee5/4AGUJl9TLv8wAT1cf3OhcGlvOlcfSYtuNUY32TPWit1Or1y\ni9fPkjo8SBw52TN5RRmjIlpNMxeK+G4+XtO1Y47TlZkCgYEA3Y+NK8mz9kTnorkf\nR7CSOAaiste8c3CJbbbaTk7oTxK4MISL1RX+sFK65cnV54Wo75hCZxsjV2oEQ+4r\nUOGw1JxcV16V6iP/AaQS41xsxZca/xnC//YojBN6kP+OV+/ByF4HNs5eDN6uHg0y\nOOfBWi6oc449CFFMxVnrQ0SymaMCgYEAwx7M9xQ1eth902Wq2VNszFIgdgptGYhj\nXbWsAFYKII+aUXC92sVL5sO33vNyhBbyMN1aSRXYe8B5EnwsP31o5csrHQw6i/Dp\njUx1AUBYkNPgL9ctqlTQf1nb0LenGlCUBD6jrSrJVHeOF4y+HIZHXNZ++otH7+eu\nb3dbHgV/9F8CgYBTopO0utAvH3WdDGqNYk7fzUlvX1ao8Qs/mi2wL8MrzjIvRmmO\nh137607X3Sfc3KyXvQ8b4relkMSJbAd34aohp+CHrpHCr9HcKbZjkwkQUWkEcRIW\nEzLdJaE3yPBPq5an7y6j9qS0EP8DIxIZPwrS4xf9fuz1DdOAD+BqJS2SJwKBgQCt\nzZzTpduxbnA+Qrx503cBVVJ28viVmsiwK2hn8DwbHu9eBegHnGDs0H/Th9UE1g+r\n+TA4E85/BUaTcapUb5hlwKDJwh/QkaroYyeCEtgRQbnbw3d41w3Vsqw78atWpFoE\noetYD9nAdLJMReD+NZoRlzsKX9CXYS8fORkf19RPTwKBgQCQdvDMicrtnJ4U2MOA\ny+59K7V77VRfHLecjAMGblBGmrtoQSBvQiFznXm0DUOSy3eZRITwe01/t+RUDhx9\nMVLlyNzwRCVHzPe7kUI10GM4W5ZKAf8f/t0KjBrQBeYtRUOEI3QVzsVzc1hY8Fv8\nYHOhmI8Tdd2biF+lqXKC6vGlvQ==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "tornado/test/testing_test.py",
    "content": "import asyncio\nimport contextlib\nimport gc\nimport os\nimport platform\nimport sys\nimport traceback\nimport unittest\nimport warnings\n\nfrom tornado import gen, ioloop\nfrom tornado.httpserver import HTTPServer\nfrom tornado.locks import Event\nfrom tornado.testing import AsyncHTTPTestCase, AsyncTestCase, bind_unused_port, gen_test\nfrom tornado.web import Application\n\n\n@contextlib.contextmanager\ndef set_environ(name, value):\n    old_value = os.environ.get(name)\n    os.environ[name] = value\n\n    try:\n        yield\n    finally:\n        if old_value is None:\n            del os.environ[name]\n        else:\n            os.environ[name] = old_value\n\n\nclass AsyncTestCaseTest(AsyncTestCase):\n    def test_wait_timeout(self):\n        time = self.io_loop.time\n\n        # Accept default 5-second timeout, no error\n        self.io_loop.add_timeout(time() + 0.01, self.stop)\n        self.wait()\n\n        # Timeout passed to wait()\n        self.io_loop.add_timeout(time() + 1, self.stop)\n        with self.assertRaises(self.failureException):\n            self.wait(timeout=0.01)\n\n        # Timeout set with environment variable\n        self.io_loop.add_timeout(time() + 1, self.stop)\n        with set_environ(\"ASYNC_TEST_TIMEOUT\", \"0.01\"):\n            with self.assertRaises(self.failureException):\n                self.wait()\n\n    def test_subsequent_wait_calls(self):\n        \"\"\"\n        This test makes sure that a second call to wait()\n        clears the first timeout.\n        \"\"\"\n        # The first wait ends with time left on the clock\n        self.io_loop.add_timeout(self.io_loop.time() + 0.00, self.stop)\n        self.wait(timeout=0.1)\n        # The second wait has enough time for itself but would fail if the\n        # first wait's deadline were still in effect.\n        self.io_loop.add_timeout(self.io_loop.time() + 0.2, self.stop)\n        self.wait(timeout=0.4)\n\n\nclass LeakTest(AsyncTestCase):\n    def tearDown(self):\n        super().tearDown()\n        # Trigger a gc to make warnings more deterministic.\n        gc.collect()\n\n    def test_leaked_coroutine(self):\n        # This test verifies that \"leaked\" coroutines are shut down\n        # without triggering warnings like \"task was destroyed but it\n        # is pending\". If this test were to fail, it would fail\n        # because runtests.py detected unexpected output to stderr.\n        event = Event()\n\n        async def callback():\n            try:\n                await event.wait()\n            except asyncio.CancelledError:\n                pass\n\n        self.io_loop.add_callback(callback)\n        self.io_loop.add_callback(self.stop)\n        self.wait()\n\n\nclass AsyncHTTPTestCaseTest(AsyncHTTPTestCase):\n    def setUp(self):\n        super().setUp()\n        # Bind a second port.\n        sock, port = bind_unused_port()\n        app = Application()\n        server = HTTPServer(app, **self.get_httpserver_options())\n        server.add_socket(sock)\n        self.second_port = port\n        self.second_server = server\n\n    def get_app(self):\n        return Application()\n\n    def test_fetch_segment(self):\n        path = \"/path\"\n        response = self.fetch(path)\n        self.assertEqual(response.request.url, self.get_url(path))\n\n    def test_fetch_full_http_url(self):\n        # Ensure that self.fetch() recognizes absolute urls and does\n        # not transform them into references to our main test server.\n        path = \"http://127.0.0.1:%d/path\" % self.second_port\n\n        response = self.fetch(path)\n        self.assertEqual(response.request.url, path)\n\n    def tearDown(self):\n        self.second_server.stop()\n        super().tearDown()\n\n\nclass AsyncTestCaseReturnAssertionsTest(unittest.TestCase):\n    # These tests verify that tests that return non-None values (without being decorated with\n    # @gen_test) raise errors instead of incorrectly succeeding. These tests should be removed or\n    # updated when the _callTestMethod method is removed from AsyncTestCase (the same checks will\n    # still happen, but they'll be performed in the stdlib as DeprecationWarnings)\n    def test_undecorated_generator(self):\n        class Test(AsyncTestCase):\n            def test_gen(self):\n                yield\n\n        test = Test(\"test_gen\")\n        result = unittest.TestResult()\n        test.run(result)\n        self.assertEqual(len(result.errors), 1)\n        self.assertIn(\"should be decorated\", result.errors[0][1])\n\n    @unittest.skipIf(\n        platform.python_implementation() == \"PyPy\",\n        \"pypy destructor warnings cannot be silenced\",\n    )\n    @unittest.skipIf(\n        # This check actually exists in 3.11 but it changed in 3.12 in a way that breaks\n        # this test.\n        sys.version_info >= (3, 12),\n        \"py312 has its own check for test case returns\",\n    )\n    def test_undecorated_coroutine(self):\n        class Test(AsyncTestCase):\n            async def test_coro(self):\n                pass\n\n        test = Test(\"test_coro\")\n        result = unittest.TestResult()\n\n        # Silence \"RuntimeWarning: coroutine 'test_coro' was never awaited\".\n        with warnings.catch_warnings():\n            warnings.simplefilter(\"ignore\")\n            test.run(result)\n\n        self.assertEqual(len(result.errors), 1)\n        self.assertIn(\"should be decorated\", result.errors[0][1])\n\n    def test_undecorated_generator_with_skip(self):\n        class Test(AsyncTestCase):\n            @unittest.skip(\"don't run this\")\n            def test_gen(self):\n                yield\n\n        test = Test(\"test_gen\")\n        result = unittest.TestResult()\n        test.run(result)\n        self.assertEqual(len(result.errors), 0)\n        self.assertEqual(len(result.skipped), 1)\n\n    def test_other_return(self):\n        class Test(AsyncTestCase):\n            def test_other_return(self):\n                return 42\n\n        test = Test(\"test_other_return\")\n        result = unittest.TestResult()\n        test.run(result)\n        self.assertEqual(len(result.errors), 1)\n        self.assertIn(\"Return value from test method ignored\", result.errors[0][1])\n\n\nclass SetUpTearDownTest(unittest.TestCase):\n    def test_set_up_tear_down(self):\n        \"\"\"\n        This test makes sure that AsyncTestCase calls super methods for\n        setUp and tearDown.\n\n        InheritBoth is a subclass of both AsyncTestCase and\n        SetUpTearDown, with the ordering so that the super of\n        AsyncTestCase will be SetUpTearDown.\n        \"\"\"\n        events = []\n        result = unittest.TestResult()\n\n        class SetUpTearDown(unittest.TestCase):\n            def setUp(self):\n                events.append(\"setUp\")\n\n            def tearDown(self):\n                events.append(\"tearDown\")\n\n        class InheritBoth(AsyncTestCase, SetUpTearDown):\n            def test(self):\n                events.append(\"test\")\n\n        InheritBoth(\"test\").run(result)\n        expected = [\"setUp\", \"test\", \"tearDown\"]\n        self.assertEqual(expected, events)\n\n\nclass AsyncHTTPTestCaseSetUpTearDownTest(unittest.TestCase):\n    def test_tear_down_releases_app_and_http_server(self):\n        result = unittest.TestResult()\n\n        class SetUpTearDown(AsyncHTTPTestCase):\n            def get_app(self):\n                return Application()\n\n            def test(self):\n                self.assertTrue(hasattr(self, \"_app\"))\n                self.assertTrue(hasattr(self, \"http_server\"))\n\n        test = SetUpTearDown(\"test\")\n        test.run(result)\n        self.assertFalse(hasattr(test, \"_app\"))\n        self.assertFalse(hasattr(test, \"http_server\"))\n\n\nclass GenTest(AsyncTestCase):\n    def setUp(self):\n        super().setUp()\n        self.finished = False\n\n    def tearDown(self):\n        self.assertTrue(self.finished)\n        super().tearDown()\n\n    @gen_test\n    def test_sync(self):\n        self.finished = True\n\n    @gen_test\n    def test_async(self):\n        yield gen.moment\n        self.finished = True\n\n    def test_timeout(self):\n        # Set a short timeout and exceed it.\n        @gen_test(timeout=0.1)\n        def test(self):\n            yield gen.sleep(1)\n\n        # This can't use assertRaises because we need to inspect the\n        # exc_info triple (and not just the exception object)\n        try:\n            test(self)\n            self.fail(\"did not get expected exception\")\n        except ioloop.TimeoutError:\n            # The stack trace should blame the add_timeout line, not just\n            # unrelated IOLoop/testing internals.\n            self.assertIn(\"gen.sleep(1)\", traceback.format_exc())\n\n        self.finished = True\n\n    def test_no_timeout(self):\n        # A test that does not exceed its timeout should succeed.\n        @gen_test(timeout=1)\n        def test(self):\n            yield gen.sleep(0.1)\n\n        test(self)\n        self.finished = True\n\n    def test_timeout_environment_variable(self):\n        @gen_test(timeout=0.5)\n        def test_long_timeout(self):\n            yield gen.sleep(0.25)\n\n        # Uses provided timeout of 0.5 seconds, doesn't time out.\n        with set_environ(\"ASYNC_TEST_TIMEOUT\", \"0.1\"):\n            test_long_timeout(self)\n\n        self.finished = True\n\n    def test_no_timeout_environment_variable(self):\n        @gen_test(timeout=0.01)\n        def test_short_timeout(self):\n            yield gen.sleep(1)\n\n        # Uses environment-variable timeout of 0.1, times out.\n        with set_environ(\"ASYNC_TEST_TIMEOUT\", \"0.1\"):\n            with self.assertRaises(ioloop.TimeoutError):\n                test_short_timeout(self)\n\n        self.finished = True\n\n    def test_with_method_args(self):\n        @gen_test\n        def test_with_args(self, *args):\n            self.assertEqual(args, (\"test\",))\n            yield gen.moment\n\n        test_with_args(self, \"test\")\n        self.finished = True\n\n    def test_with_method_kwargs(self):\n        @gen_test\n        def test_with_kwargs(self, **kwargs):\n            self.assertDictEqual(kwargs, {\"test\": \"test\"})\n            yield gen.moment\n\n        test_with_kwargs(self, test=\"test\")\n        self.finished = True\n\n    def test_native_coroutine(self):\n        @gen_test\n        async def test(self):\n            self.finished = True\n\n        test(self)\n\n    def test_native_coroutine_timeout(self):\n        # Set a short timeout and exceed it.\n        @gen_test(timeout=0.1)\n        async def test(self):\n            await gen.sleep(1)\n\n        try:\n            test(self)\n            self.fail(\"did not get expected exception\")\n        except ioloop.TimeoutError:\n            self.finished = True\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tornado/test/twisted_test.py",
    "content": "# Author: Ovidiu Predescu\n# Date: July 2011\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\nimport sys\nimport unittest\n\nfrom tornado.testing import AsyncTestCase, gen_test\n\ntry:\n    from twisted.internet.defer import inlineCallbacks  # type: ignore\n\n    have_twisted = True\nexcept ImportError:\n    have_twisted = False\nexcept Exception:\n    # Twisted is currently incompatible with the first 3.14 alpha release; disable this\n    # test until the beta when it will hopefully be fixed (note that this requires us to\n    # update our requirements.txt to pick up a new version of twisted).\n    if sys.version_info[:2] == (3, 14) and sys.version_info.releaselevel == \"alpha\":\n        have_twisted = False\n    else:\n        raise\nelse:\n    # Not used directly but needed for `yield deferred` to work.\n    import tornado.platform.twisted  # noqa: F401\n\nskipIfNoTwisted = unittest.skipUnless(have_twisted, \"twisted module not present\")\n\n\n@skipIfNoTwisted\nclass ConvertDeferredTest(AsyncTestCase):\n    @gen_test\n    def test_success(self):\n        @inlineCallbacks\n        def fn():\n            if False:\n                # inlineCallbacks doesn't work with regular functions;\n                # must have a yield even if it's unreachable.\n                yield\n            return 42\n\n        res = yield fn()\n        self.assertEqual(res, 42)\n\n    @gen_test\n    def test_failure(self):\n        @inlineCallbacks\n        def fn():\n            if False:\n                yield\n            1 / 0\n\n        with self.assertRaises(ZeroDivisionError):\n            yield fn()\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tornado/test/util.py",
    "content": "import contextlib\nimport os\nimport platform\nimport socket\nimport sys\nimport sysconfig\nimport textwrap\nimport typing\nimport unittest\nimport warnings\n\nfrom tornado.testing import bind_unused_port\n\n_TestCaseType = typing.TypeVar(\"_TestCaseType\", bound=type[unittest.TestCase])\n\nskipIfNonUnix = unittest.skipIf(\n    os.name != \"posix\" or sys.platform == \"cygwin\", \"non-unix platform\"\n)\n\n# Set the environment variable NO_NETWORK=1 to disable any tests that\n# depend on an external network.\nskipIfNoNetwork = unittest.skipIf(\"NO_NETWORK\" in os.environ, \"network access disabled\")\n\n# Set the environment variable EMULATION=1 to disable any tests that\n# are unreliable under emulation\nskipIfEmulated = unittest.skipIf(\n    \"EMULATION\" in os.environ, \"test unreliable under emulation\"\n)\n\nskipNotCPython = unittest.skipIf(\n    # \"CPython\" here essentially refers to the traditional synchronous refcounting GC,\n    # so we skip these tests in free-threading builds of cpython too.\n    platform.python_implementation() != \"CPython\"\n    or sysconfig.get_config_var(\"Py_GIL_DISABLED\"),\n    \"Not CPython implementation\",\n)\n\n\ndef _detect_ipv6():\n    if not socket.has_ipv6:\n        # socket.has_ipv6 check reports whether ipv6 was present at compile\n        # time. It's usually true even when ipv6 doesn't work for other reasons.\n        return False\n    sock = None\n    try:\n        sock = socket.socket(socket.AF_INET6)\n        sock.bind((\"::1\", 0))\n    except OSError:\n        return False\n    finally:\n        if sock is not None:\n            sock.close()\n    return True\n\n\nskipIfNoIPv6 = unittest.skipIf(not _detect_ipv6(), \"ipv6 support not present\")\n\n\ndef refusing_port():\n    \"\"\"Returns a local port number that will refuse all connections.\n\n    Return value is (cleanup_func, port); the cleanup function\n    must be called to free the port to be reused.\n    \"\"\"\n    # On travis-ci port numbers are reassigned frequently. To avoid\n    # collisions with other tests, we use an open client-side socket's\n    # ephemeral port number to ensure that nothing can listen on that\n    # port.\n    server_socket, port = bind_unused_port()\n    server_socket.setblocking(True)\n    client_socket = socket.socket()\n    client_socket.connect((\"127.0.0.1\", port))\n    conn, client_addr = server_socket.accept()\n    conn.close()\n    server_socket.close()\n    return (client_socket.close, client_addr[1])\n\n\ndef exec_test(caller_globals, caller_locals, s):\n    \"\"\"Execute ``s`` in a given context and return the result namespace.\n\n    Used to define functions for tests in particular python\n    versions that would be syntax errors in older versions.\n    \"\"\"\n    # Flatten the real global and local namespace into our fake\n    # globals: it's all global from the perspective of code defined\n    # in s.\n    global_namespace = dict(caller_globals, **caller_locals)  # type: ignore\n    local_namespace: dict[str, typing.Any] = {}\n    exec(textwrap.dedent(s), global_namespace, local_namespace)\n    return local_namespace\n\n\n@contextlib.contextmanager\ndef ignore_deprecation():\n    \"\"\"Context manager to ignore deprecation warnings.\"\"\"\n    with warnings.catch_warnings():\n        warnings.simplefilter(\"ignore\", DeprecationWarning)\n        yield\n\n\nABT_SKIP_MESSAGE = \"abstract base class\"\n\n\ndef abstract_base_test(cls: _TestCaseType) -> _TestCaseType:\n    \"\"\"Decorator to mark a test class as an \"abstract\" base class.\n\n    This is different from a regular abstract base class because\n    we do not limit instantiation of the class. (If we did, it would\n    interfere with test discovery). Instead, we prevent the tests from\n    being run.\n\n    Subclasses of an abstract base test are run as normal. There is\n    no support for the ``@abstractmethod`` decorator so there is no runtime\n    check that all such methods are implemented.\n\n    Note that while it is semantically cleaner to modify the test loader\n    to exclude abstract base tests, this is more complicated and would\n    interfere with third-party test runners. This approach degrades\n    gracefully to other tools such as editor-integrated testing.\n    \"\"\"\n\n    # Type-checking fails due to https://github.com/python/mypy/issues/14458\n    # @functools.wraps(cls)\n    class AbstractBaseWrapper(cls):  # type: ignore\n        @classmethod\n        def setUpClass(cls):\n            if cls is AbstractBaseWrapper:\n                raise unittest.SkipTest(ABT_SKIP_MESSAGE)\n            super().setUpClass()\n\n    return AbstractBaseWrapper  # type: ignore\n"
  },
  {
    "path": "tornado/test/util_test.py",
    "content": "import datetime\nimport re\nimport sys\nimport textwrap\nimport unittest\nfrom typing import Any, cast\n\nimport tornado\nfrom tornado.escape import utf8\nfrom tornado.util import (\n    ArgReplacer,\n    Configurable,\n    exec_in,\n    import_object,\n    raise_exc_info,\n    re_unescape,\n    timedelta_to_seconds,\n)\n\n\nclass RaiseExcInfoTest(unittest.TestCase):\n    def test_two_arg_exception(self):\n        # This test would fail on python 3 if raise_exc_info were simply\n        # a three-argument raise statement, because TwoArgException\n        # doesn't have a \"copy constructor\"\n        class TwoArgException(Exception):\n            def __init__(self, a, b):\n                super().__init__()\n                self.a, self.b = a, b\n\n        try:\n            raise TwoArgException(1, 2)\n        except TwoArgException:\n            exc_info = sys.exc_info()\n        try:\n            raise_exc_info(exc_info)\n            self.fail(\"didn't get expected exception\")\n        except TwoArgException as e:\n            self.assertIs(e, exc_info[1])\n\n\nclass TestConfigurable(Configurable):\n    @classmethod\n    def configurable_base(cls):\n        return TestConfigurable\n\n    @classmethod\n    def configurable_default(cls):\n        return TestConfig1\n\n\nclass TestConfig1(TestConfigurable):\n    def initialize(self, pos_arg=None, a=None):\n        self.a = a\n        self.pos_arg = pos_arg\n\n\nclass TestConfig2(TestConfigurable):\n    def initialize(self, pos_arg=None, b=None):\n        self.b = b\n        self.pos_arg = pos_arg\n\n\nclass TestConfig3(TestConfigurable):\n    # TestConfig3 is a configuration option that is itself configurable.\n    @classmethod\n    def configurable_base(cls):\n        return TestConfig3\n\n    @classmethod\n    def configurable_default(cls):\n        return TestConfig3A\n\n\nclass TestConfig3A(TestConfig3):\n    def initialize(self, a=None):\n        self.a = a\n\n\nclass TestConfig3B(TestConfig3):\n    def initialize(self, b=None):\n        self.b = b\n\n\nclass ConfigurableTest(unittest.TestCase):\n    def setUp(self):\n        self.saved = TestConfigurable._save_configuration()\n        self.saved3 = TestConfig3._save_configuration()\n\n    def tearDown(self):\n        TestConfigurable._restore_configuration(self.saved)\n        TestConfig3._restore_configuration(self.saved3)\n\n    def checkSubclasses(self):\n        # no matter how the class is configured, it should always be\n        # possible to instantiate the subclasses directly\n        self.assertIsInstance(TestConfig1(), TestConfig1)\n        self.assertIsInstance(TestConfig2(), TestConfig2)\n\n        obj = TestConfig1(a=1)\n        self.assertEqual(obj.a, 1)\n        obj2 = TestConfig2(b=2)\n        self.assertEqual(obj2.b, 2)\n\n    def test_default(self):\n        # In these tests we combine a typing.cast to satisfy mypy with\n        # a runtime type-assertion. Without the cast, mypy would only\n        # let us access attributes of the base class.\n        obj = cast(TestConfig1, TestConfigurable())\n        self.assertIsInstance(obj, TestConfig1)\n        self.assertIsNone(obj.a)\n\n        obj = cast(TestConfig1, TestConfigurable(a=1))\n        self.assertIsInstance(obj, TestConfig1)\n        self.assertEqual(obj.a, 1)\n\n        self.checkSubclasses()\n\n    def test_config_class(self):\n        TestConfigurable.configure(TestConfig2)\n        obj = cast(TestConfig2, TestConfigurable())\n        self.assertIsInstance(obj, TestConfig2)\n        self.assertIsNone(obj.b)\n\n        obj = cast(TestConfig2, TestConfigurable(b=2))\n        self.assertIsInstance(obj, TestConfig2)\n        self.assertEqual(obj.b, 2)\n\n        self.checkSubclasses()\n\n    def test_config_str(self):\n        TestConfigurable.configure(\"tornado.test.util_test.TestConfig2\")\n        obj = cast(TestConfig2, TestConfigurable())\n        self.assertIsInstance(obj, TestConfig2)\n        self.assertIsNone(obj.b)\n\n        obj = cast(TestConfig2, TestConfigurable(b=2))\n        self.assertIsInstance(obj, TestConfig2)\n        self.assertEqual(obj.b, 2)\n\n        self.checkSubclasses()\n\n    def test_config_args(self):\n        TestConfigurable.configure(None, a=3)\n        obj = cast(TestConfig1, TestConfigurable())\n        self.assertIsInstance(obj, TestConfig1)\n        self.assertEqual(obj.a, 3)\n\n        obj = cast(TestConfig1, TestConfigurable(42, a=4))\n        self.assertIsInstance(obj, TestConfig1)\n        self.assertEqual(obj.a, 4)\n        self.assertEqual(obj.pos_arg, 42)\n\n        self.checkSubclasses()\n        # args bound in configure don't apply when using the subclass directly\n        obj = TestConfig1()\n        self.assertIsNone(obj.a)\n\n    def test_config_class_args(self):\n        TestConfigurable.configure(TestConfig2, b=5)\n        obj = cast(TestConfig2, TestConfigurable())\n        self.assertIsInstance(obj, TestConfig2)\n        self.assertEqual(obj.b, 5)\n\n        obj = cast(TestConfig2, TestConfigurable(42, b=6))\n        self.assertIsInstance(obj, TestConfig2)\n        self.assertEqual(obj.b, 6)\n        self.assertEqual(obj.pos_arg, 42)\n\n        self.checkSubclasses()\n        # args bound in configure don't apply when using the subclass directly\n        obj = TestConfig2()\n        self.assertIsNone(obj.b)\n\n    def test_config_multi_level(self):\n        TestConfigurable.configure(TestConfig3, a=1)\n        obj = cast(TestConfig3A, TestConfigurable())\n        self.assertIsInstance(obj, TestConfig3A)\n        self.assertEqual(obj.a, 1)\n\n        TestConfigurable.configure(TestConfig3)\n        TestConfig3.configure(TestConfig3B, b=2)\n        obj2 = cast(TestConfig3B, TestConfigurable())\n        self.assertIsInstance(obj2, TestConfig3B)\n        self.assertEqual(obj2.b, 2)\n\n    def test_config_inner_level(self):\n        # The inner level can be used even when the outer level\n        # doesn't point to it.\n        obj = TestConfig3()\n        self.assertIsInstance(obj, TestConfig3A)\n\n        TestConfig3.configure(TestConfig3B)\n        obj = TestConfig3()\n        self.assertIsInstance(obj, TestConfig3B)\n\n        # Configuring the base doesn't configure the inner.\n        obj2 = TestConfigurable()\n        self.assertIsInstance(obj2, TestConfig1)\n        TestConfigurable.configure(TestConfig2)\n\n        obj3 = TestConfigurable()\n        self.assertIsInstance(obj3, TestConfig2)\n\n        obj = TestConfig3()\n        self.assertIsInstance(obj, TestConfig3B)\n\n\nclass UnicodeLiteralTest(unittest.TestCase):\n    def test_unicode_escapes(self):\n        self.assertEqual(utf8(\"\\u00e9\"), b\"\\xc3\\xa9\")\n\n\nclass ExecInTest(unittest.TestCase):\n    def test_no_inherit_future(self):\n        # Two files: the first has \"from __future__ import annotations\", and it executes the second\n        # which doesn't. The second file should not be affected by the first's __future__ imports.\n        #\n        # The annotations future became available in python 3.7 but has been replaced by PEP 649, so\n        # it should remain supported but off-by-default for the foreseeable future.\n        code1 = textwrap.dedent(\"\"\"\n            from __future__ import annotations\n            from tornado.util import exec_in\n\n            exec_in(code2, globals())\n            \"\"\")\n\n        code2 = textwrap.dedent(\"\"\"\n            def f(x: int) -> int:\n                return x + 1\n            output[0] = f.__annotations__\n            \"\"\")\n\n        # Make a mutable container to pass the result back to the caller\n        output = [None]\n        exec_in(code1, dict(code2=code2, output=output))\n        # If the annotations future were in effect, these would be strings instead of the int type\n        # object.\n        self.assertEqual(output[0], {\"x\": int, \"return\": int})\n\n\nclass ArgReplacerTest(unittest.TestCase):\n    def setUp(self):\n        def function(x, y, callback=None, z=None):\n            pass\n\n        self.replacer = ArgReplacer(function, \"callback\")\n\n    def test_omitted(self):\n        args = (1, 2)\n        kwargs: dict[str, Any] = dict()\n        self.assertIsNone(self.replacer.get_old_value(args, kwargs))\n        self.assertEqual(\n            self.replacer.replace(\"new\", args, kwargs),\n            (None, (1, 2), dict(callback=\"new\")),\n        )\n\n    def test_position(self):\n        args = (1, 2, \"old\", 3)\n        kwargs: dict[str, Any] = dict()\n        self.assertEqual(self.replacer.get_old_value(args, kwargs), \"old\")\n        self.assertEqual(\n            self.replacer.replace(\"new\", args, kwargs),\n            (\"old\", [1, 2, \"new\", 3], dict()),\n        )\n\n    def test_keyword(self):\n        args = (1,)\n        kwargs = dict(y=2, callback=\"old\", z=3)\n        self.assertEqual(self.replacer.get_old_value(args, kwargs), \"old\")\n        self.assertEqual(\n            self.replacer.replace(\"new\", args, kwargs),\n            (\"old\", (1,), dict(y=2, callback=\"new\", z=3)),\n        )\n\n\nclass TimedeltaToSecondsTest(unittest.TestCase):\n    def test_timedelta_to_seconds(self):\n        time_delta = datetime.timedelta(hours=1)\n        self.assertEqual(timedelta_to_seconds(time_delta), 3600.0)\n\n\nclass ImportObjectTest(unittest.TestCase):\n    def test_import_member(self):\n        self.assertIs(import_object(\"tornado.escape.utf8\"), utf8)\n\n    def test_import_member_unicode(self):\n        self.assertIs(import_object(\"tornado.escape.utf8\"), utf8)\n\n    def test_import_module(self):\n        self.assertIs(import_object(\"tornado.escape\"), tornado.escape)\n\n    def test_import_module_unicode(self):\n        # The internal implementation of __import__ differs depending on\n        # whether the thing being imported is a module or not.\n        # This variant requires a byte string in python 2.\n        self.assertIs(import_object(\"tornado.escape\"), tornado.escape)\n\n\nclass ReUnescapeTest(unittest.TestCase):\n    def test_re_unescape(self):\n        test_strings = (\"/favicon.ico\", \"index.html\", \"Hello, World!\", \"!$@#%;\")\n        for string in test_strings:\n            self.assertEqual(string, re_unescape(re.escape(string)))\n\n    def test_re_unescape_raises_error_on_invalid_input(self):\n        with self.assertRaises(ValueError):\n            re_unescape(\"\\\\d\")\n        with self.assertRaises(ValueError):\n            re_unescape(\"\\\\b\")\n        with self.assertRaises(ValueError):\n            re_unescape(\"\\\\Z\")\n\n\nclass VersionInfoTest(unittest.TestCase):\n    def assert_version_info_compatible(self, version, version_info):\n        # We map our version identifier string (a subset of\n        # https://packaging.python.org/en/latest/specifications/version-specifiers/#public-version-identifiers)\n        # to a 4-tuple of integers for easy comparisons. The last component is\n        # 0 for a final release, negative for a pre-release, and would be positive for a\n        # post-release if we did any of those. This test is not a promise that these are the\n        # only formats we will ever use, but it does catch accidents like\n        # https://github.com/tornadoweb/tornado/issues/3406.\n        major = minor = patch = \"0\"\n        is_pre = False\n        if m := re.fullmatch(r\"(\\d+)\\.(\\d+)\\.(\\d+)\", version):\n            # Regular 3-component version number\n            major, minor, patch = m.groups()\n        elif m := re.fullmatch(r\"(\\d+)\\.(\\d+)\", version):\n            # Two-component version number, equivalent to major.minor.0\n            major, minor = m.groups()\n        elif m := re.fullmatch(r\"(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.dev|a|b|rc)\\d+\", version):\n            # Pre-release 3-component version number.\n            major, minor, patch = m.groups()\n            is_pre = True\n        elif m := re.fullmatch(r\"(\\d+)\\.(\\d+)(?:\\.dev|a|b|rc)\\d+\", version):\n            # Pre-release 2-component version number.\n            major, minor = m.groups()\n            is_pre = True\n        else:\n            self.fail(f\"Unrecognized version format: {version}\")\n\n        self.assertEqual(version_info[:3], (int(major), int(minor), int(patch)))\n        if is_pre:\n            self.assertLess(int(version_info[3]), 0)\n        else:\n            self.assertEqual(int(version_info[3]), 0)\n\n    def test_version_info_compatible(self):\n        self.assert_version_info_compatible(\"6.5.0\", (6, 5, 0, 0))\n        self.assert_version_info_compatible(\"6.5\", (6, 5, 0, 0))\n        self.assert_version_info_compatible(\"6.5.1\", (6, 5, 1, 0))\n        self.assert_version_info_compatible(\"6.6.dev1\", (6, 6, 0, -100))\n        self.assert_version_info_compatible(\"6.6a1\", (6, 6, 0, -100))\n        self.assert_version_info_compatible(\"6.6b1\", (6, 6, 0, -100))\n        self.assert_version_info_compatible(\"6.6rc1\", (6, 6, 0, -100))\n        self.assertRaises(\n            AssertionError, self.assert_version_info_compatible, \"6.5.0\", (6, 5, 0, 1)\n        )\n        self.assertRaises(\n            AssertionError, self.assert_version_info_compatible, \"6.5.0\", (6, 4, 0, 0)\n        )\n        self.assertRaises(\n            AssertionError, self.assert_version_info_compatible, \"6.5.1\", (6, 5, 0, 1)\n        )\n\n    def test_current_version(self):\n        self.assert_version_info_compatible(tornado.version, tornado.version_info)\n"
  },
  {
    "path": "tornado/test/web_test.py",
    "content": "import binascii\nimport contextlib\nimport copy\nimport datetime\nimport email.utils\nimport gzip\nimport http\nimport itertools\nimport logging\nimport os\nimport re\nimport socket\nimport typing\nimport unittest\nimport urllib.parse\nfrom io import BytesIO\n\nfrom tornado import gen, locale\nfrom tornado.concurrent import Future\nfrom tornado.escape import (\n    json_decode,\n    native_str,\n    recursive_unicode,\n    to_basestring,\n    to_unicode,\n    utf8,\n)\nfrom tornado.httpclient import HTTPClientError\nfrom tornado.httputil import format_timestamp\nfrom tornado.iostream import IOStream\nfrom tornado.locks import Event\nfrom tornado.log import app_log, gen_log\nfrom tornado.simple_httpclient import SimpleAsyncHTTPClient\nfrom tornado.template import DictLoader\nfrom tornado.test.util import ignore_deprecation\nfrom tornado.testing import AsyncHTTPTestCase, AsyncTestCase, ExpectLog, gen_test\nfrom tornado.util import ObjectDict, unicode_type\nfrom tornado.web import (\n    Application,\n    ErrorHandler,\n    Finish,\n    GZipContentEncoding,\n    HTTPError,\n    MissingArgumentError,\n)\nfrom tornado.web import RedirectHandler as WebRedirectHandler\nfrom tornado.web import (\n    RequestHandler,\n    StaticFileHandler,\n    UIModule,\n    _create_signature_v1,\n    addslash,\n    authenticated,\n    create_signed_value,\n    decode_signed_value,\n    get_signature_key_version,\n    removeslash,\n    stream_request_body,\n    url,\n)\n\n\ndef relpath(*a):\n    return os.path.join(os.path.dirname(__file__), *a)\n\n\nclass WebTestCase(AsyncHTTPTestCase):\n    \"\"\"Base class for web tests that also supports WSGI mode.\n\n    Override get_handlers and get_app_kwargs instead of get_app.\n    This class is deprecated since WSGI mode is no longer supported.\n    \"\"\"\n\n    def get_app(self):\n        self.app = Application(self.get_handlers(), **self.get_app_kwargs())\n        return self.app\n\n    def get_handlers(self):\n        raise NotImplementedError()\n\n    def get_app_kwargs(self):\n        return {}\n\n\nclass SimpleHandlerTestCase(WebTestCase):\n    \"\"\"Simplified base class for tests that work with a single handler class.\n\n    To use, define a nested class named ``Handler``.\n    \"\"\"\n\n    Handler = None\n\n    def get_handlers(self):\n        return [(\"/\", self.Handler)]\n\n\nclass HelloHandler(RequestHandler):\n    def get(self):\n        self.write(\"hello\")\n\n\nclass CookieTestRequestHandler(RequestHandler):\n    # stub out enough methods to make the signed_cookie functions work\n    def __init__(self, cookie_secret=\"0123456789\", key_version=None):\n        # don't call super.__init__\n        self._cookies: dict[str, bytes] = {}\n        if key_version is None:\n            self.application = ObjectDict(  # type: ignore\n                settings=dict(cookie_secret=cookie_secret)\n            )\n        else:\n            self.application = ObjectDict(  # type: ignore\n                settings=dict(cookie_secret=cookie_secret, key_version=key_version)\n            )\n\n    def get_cookie(self, name) -> str | None:  # type: ignore[override]\n        return to_unicode(self._cookies.get(name))\n\n    def set_cookie(self, name, value, expires_days=None):  # type: ignore[override]\n        self._cookies[name] = value\n\n\n# See SignedValueTest below for more.\nclass SecureCookieV1Test(unittest.TestCase):\n    def test_round_trip(self):\n        handler = CookieTestRequestHandler()\n        handler.set_signed_cookie(\"foo\", b\"bar\", version=1)\n        self.assertEqual(handler.get_signed_cookie(\"foo\", min_version=1), b\"bar\")\n\n    def test_cookie_tampering_future_timestamp(self):\n        handler = CookieTestRequestHandler()\n        # this string base64-encodes to '12345678'\n        handler.set_signed_cookie(\"foo\", binascii.a2b_hex(b\"d76df8e7aefc\"), version=1)\n        cookie = handler._cookies[\"foo\"]\n        match = re.match(rb\"12345678\\|([0-9]+)\\|([0-9a-f]+)\", cookie)\n        self.assertIsNotNone(match)\n        assert match is not None  # for mypy\n        timestamp = match.group(1)\n        sig = match.group(2)\n        self.assertEqual(\n            _create_signature_v1(\n                handler.application.settings[\"cookie_secret\"],\n                \"foo\",\n                \"12345678\",\n                timestamp,\n            ),\n            sig,\n        )\n        # shifting digits from payload to timestamp doesn't alter signature\n        # (this is not desirable behavior, just confirming that that's how it\n        # works)\n        self.assertEqual(\n            _create_signature_v1(\n                handler.application.settings[\"cookie_secret\"],\n                \"foo\",\n                \"1234\",\n                b\"5678\" + timestamp,\n            ),\n            sig,\n        )\n        # tamper with the cookie\n        handler._cookies[\"foo\"] = utf8(\n            f\"1234|5678{to_basestring(timestamp)}|{to_basestring(sig)}\"\n        )\n        # it gets rejected\n        with ExpectLog(gen_log, \"Cookie timestamp in future\"):\n            self.assertIsNone(handler.get_signed_cookie(\"foo\", min_version=1))\n\n    def test_arbitrary_bytes(self):\n        # Secure cookies accept arbitrary data (which is base64 encoded).\n        # Note that normal cookies accept only a subset of ascii.\n        handler = CookieTestRequestHandler()\n        handler.set_signed_cookie(\"foo\", b\"\\xe9\", version=1)\n        self.assertEqual(handler.get_signed_cookie(\"foo\", min_version=1), b\"\\xe9\")\n\n\n# See SignedValueTest below for more.\nclass SecureCookieV2Test(unittest.TestCase):\n    KEY_VERSIONS = {0: \"ajklasdf0ojaisdf\", 1: \"aslkjasaolwkjsdf\"}\n\n    def test_round_trip(self):\n        handler = CookieTestRequestHandler()\n        handler.set_signed_cookie(\"foo\", b\"bar\", version=2)\n        self.assertEqual(handler.get_signed_cookie(\"foo\", min_version=2), b\"bar\")\n\n    def test_key_version_roundtrip(self):\n        handler = CookieTestRequestHandler(\n            cookie_secret=self.KEY_VERSIONS, key_version=0\n        )\n        handler.set_signed_cookie(\"foo\", b\"bar\")\n        self.assertEqual(handler.get_signed_cookie(\"foo\"), b\"bar\")\n\n    def test_key_version_roundtrip_differing_version(self):\n        handler = CookieTestRequestHandler(\n            cookie_secret=self.KEY_VERSIONS, key_version=1\n        )\n        handler.set_signed_cookie(\"foo\", b\"bar\")\n        self.assertEqual(handler.get_signed_cookie(\"foo\"), b\"bar\")\n\n    def test_key_version_increment_version(self):\n        handler = CookieTestRequestHandler(\n            cookie_secret=self.KEY_VERSIONS, key_version=0\n        )\n        handler.set_signed_cookie(\"foo\", b\"bar\")\n        new_handler = CookieTestRequestHandler(\n            cookie_secret=self.KEY_VERSIONS, key_version=1\n        )\n        new_handler._cookies = handler._cookies\n        self.assertEqual(new_handler.get_signed_cookie(\"foo\"), b\"bar\")\n\n    def test_key_version_invalidate_version(self):\n        handler = CookieTestRequestHandler(\n            cookie_secret=self.KEY_VERSIONS, key_version=0\n        )\n        handler.set_signed_cookie(\"foo\", b\"bar\")\n        new_key_versions = self.KEY_VERSIONS.copy()\n        new_key_versions.pop(0)\n        new_handler = CookieTestRequestHandler(\n            cookie_secret=new_key_versions, key_version=1\n        )\n        new_handler._cookies = handler._cookies\n        self.assertEqual(new_handler.get_signed_cookie(\"foo\"), None)\n\n\nclass FinalReturnTest(WebTestCase):\n    final_return: Future\n\n    def get_handlers(self):\n        test = self\n\n        class FinishHandler(RequestHandler):\n            @gen.coroutine\n            def get(self):\n                test.final_return = self.finish()\n                yield test.final_return\n\n            @gen.coroutine\n            def post(self):\n                self.write(\"hello,\")\n                yield self.flush()\n                test.final_return = self.finish(\"world\")\n                yield test.final_return\n\n        class RenderHandler(RequestHandler):\n            def create_template_loader(self, path):\n                return DictLoader({\"foo.html\": \"hi\"})\n\n            @gen.coroutine\n            def get(self):\n                test.final_return = self.render(\"foo.html\")\n\n        return [(\"/finish\", FinishHandler), (\"/render\", RenderHandler)]\n\n    def get_app_kwargs(self):\n        return dict(template_path=\"FinalReturnTest\")\n\n    def test_finish_method_return_future(self):\n        response = self.fetch(self.get_url(\"/finish\"))\n        self.assertEqual(response.code, 200)\n        self.assertIsInstance(self.final_return, Future)\n        self.assertTrue(self.final_return.done())\n\n        response = self.fetch(self.get_url(\"/finish\"), method=\"POST\", body=b\"\")\n        self.assertEqual(response.code, 200)\n        self.assertIsInstance(self.final_return, Future)\n        self.assertTrue(self.final_return.done())\n\n    def test_render_method_return_future(self):\n        response = self.fetch(self.get_url(\"/render\"))\n        self.assertEqual(response.code, 200)\n        self.assertIsInstance(self.final_return, Future)\n\n\nclass CookieTest(WebTestCase):\n    def get_handlers(self):\n        class SetCookieHandler(RequestHandler):\n            def get(self):\n                # Try setting cookies with different argument types\n                # to ensure that everything gets encoded correctly\n                self.set_cookie(\"str\", \"asdf\")\n                self.set_cookie(\"unicode\", \"qwer\")\n                self.set_cookie(\"bytes\", b\"zxcv\")\n\n        class GetCookieHandler(RequestHandler):\n            def get(self):\n                cookie = self.get_cookie(\"foo\", \"default\")\n                assert cookie is not None\n                self.write(cookie)\n\n        class SetCookieDomainHandler(RequestHandler):\n            def get(self):\n                # unicode domain and path arguments shouldn't break things\n                # either (see bug #285)\n                self.set_cookie(\"unicode_args\", \"blah\", domain=\"foo.com\", path=\"/foo\")\n\n        class SetCookieSpecialCharHandler(RequestHandler):\n            # \"Special\" characters are allowed in cookie values, but trigger special quoting.\n            def get(self):\n                self.set_cookie(\"equals\", \"a=b\")\n                self.set_cookie(\"semicolon\", \"a;b\")\n                self.set_cookie(\"quote\", 'a\"b')\n\n        class SetCookieForbiddenCharHandler(RequestHandler):\n            def get(self):\n                # Control characters and semicolons raise errors in cookie names and attributes\n                # (but not values, which are tested in SetCookieSpecialCharHandler)\n                for char in list(map(chr, range(0x20))) + [chr(0x7F), \";\"]:\n                    try:\n                        self.set_cookie(\"foo\" + char, \"bar\")\n                        self.write(\n                            \"Didn't get expected exception for char %r in name\\n\" % char\n                        )\n                    except http.cookies.CookieError as e:\n                        if \"Invalid cookie attribute name\" not in str(e):\n                            self.write(\n                                \"unexpected exception for char %r in name: %s\\n\"\n                                % (char, e)\n                            )\n\n                    try:\n                        self.set_cookie(\"foo\", \"bar\", domain=\"example\" + char + \".com\")\n                        self.write(\n                            \"Didn't get expected exception for char %r in domain\\n\"\n                            % char\n                        )\n                    except http.cookies.CookieError as e:\n                        if \"Invalid cookie attribute domain\" not in str(e):\n                            self.write(\n                                \"unexpected exception for char %r in domain: %s\\n\"\n                                % (char, e)\n                            )\n\n                    try:\n                        self.set_cookie(\"foo\", \"bar\", path=\"/\" + char)\n                        self.write(\n                            \"Didn't get expected exception for char %r in path\\n\" % char\n                        )\n                    except http.cookies.CookieError as e:\n                        if \"Invalid cookie attribute path\" not in str(e):\n                            self.write(\n                                \"unexpected exception for char %r in path: %s\\n\"\n                                % (char, e)\n                            )\n\n                    try:\n                        self.set_cookie(\"foo\", \"bar\", samesite=\"a\" + char)\n                        self.write(\n                            \"Didn't get expected exception for char %r in samesite\\n\"\n                            % char\n                        )\n                    except http.cookies.CookieError as e:\n                        if \"Invalid cookie attribute samesite\" not in str(e):\n                            self.write(\n                                \"unexpected exception for char %r in samesite: %s\\n\"\n                                % (char, e)\n                            )\n\n        class SetCookieOverwriteHandler(RequestHandler):\n            def get(self):\n                self.set_cookie(\"a\", \"b\", domain=\"example.com\")\n                self.set_cookie(\"c\", \"d\", domain=\"example.com\")\n                # A second call with the same name clobbers the first.\n                # Attributes from the first call are not carried over.\n                self.set_cookie(\"a\", \"e\")\n\n        class SetCookieMaxAgeHandler(RequestHandler):\n            def get(self):\n                self.set_cookie(\"foo\", \"bar\", max_age=10)\n\n        class SetCookieExpiresDaysHandler(RequestHandler):\n            def get(self):\n                self.set_cookie(\"foo\", \"bar\", expires_days=10)\n\n        class SetCookieFalsyFlags(RequestHandler):\n            def get(self):\n                self.set_cookie(\"a\", \"1\", secure=True)\n                self.set_cookie(\"b\", \"1\", secure=False)\n                self.set_cookie(\"c\", \"1\", httponly=True)\n                self.set_cookie(\"d\", \"1\", httponly=False)\n\n        class SetCookieDeprecatedArgs(RequestHandler):\n            def get(self):\n                # Mixed case is supported, but deprecated\n                with ignore_deprecation():\n                    self.set_cookie(\"a\", \"b\", HttpOnly=True, pATH=\"/foo\")\n\n        return [\n            (\"/set\", SetCookieHandler),\n            (\"/get\", GetCookieHandler),\n            (\"/set_domain\", SetCookieDomainHandler),\n            (\"/special_char\", SetCookieSpecialCharHandler),\n            (\"/forbidden_char\", SetCookieForbiddenCharHandler),\n            (\"/set_overwrite\", SetCookieOverwriteHandler),\n            (\"/set_max_age\", SetCookieMaxAgeHandler),\n            (\"/set_expires_days\", SetCookieExpiresDaysHandler),\n            (\"/set_falsy_flags\", SetCookieFalsyFlags),\n            (\"/set_deprecated\", SetCookieDeprecatedArgs),\n        ]\n\n    def test_set_cookie(self):\n        response = self.fetch(\"/set\")\n        self.assertEqual(\n            sorted(response.headers.get_list(\"Set-Cookie\")),\n            [\"bytes=zxcv; Path=/\", \"str=asdf; Path=/\", \"unicode=qwer; Path=/\"],\n        )\n\n    def test_get_cookie(self):\n        response = self.fetch(\"/get\", headers={\"Cookie\": \"foo=bar\"})\n        self.assertEqual(response.body, b\"bar\")\n\n        response = self.fetch(\"/get\", headers={\"Cookie\": 'foo=\"bar\"'})\n        self.assertEqual(response.body, b\"bar\")\n\n        response = self.fetch(\"/get\", headers={\"Cookie\": \"/=exception;\"})\n        self.assertEqual(response.body, b\"default\")\n\n    def test_set_cookie_domain(self):\n        response = self.fetch(\"/set_domain\")\n        self.assertEqual(\n            response.headers.get_list(\"Set-Cookie\"),\n            [\"unicode_args=blah; Domain=foo.com; Path=/foo\"],\n        )\n\n    def test_cookie_special_char(self):\n        response = self.fetch(\"/special_char\")\n        headers = sorted(response.headers.get_list(\"Set-Cookie\"))\n        self.assertEqual(len(headers), 3)\n        self.assertEqual(headers[0], 'equals=\"a=b\"; Path=/')\n        self.assertEqual(headers[1], 'quote=\"a\\\\\"b\"; Path=/')\n        # Semicolons are octal-escaped\n        self.assertIn(\n            headers[2],\n            ('semicolon=\"a;b\"; Path=/', 'semicolon=\"a\\\\073b\"; Path=/'),\n            headers[2],\n        )\n\n        data = [\n            (\"foo=a=b\", \"a=b\"),\n            ('foo=\"a=b\"', \"a=b\"),\n            ('foo=\"a;b\"', '\"a'),  # even quoted, \";\" is a delimiter\n            (\"foo=a\\\\073b\", \"a\\\\073b\"),  # escapes only decoded in quotes\n            ('foo=\"a\\\\073b\"', \"a;b\"),\n            ('foo=\"a\\\\\"b\"', 'a\"b'),\n        ]\n        for header, expected in data:\n            logging.debug(\"trying %r\", header)\n            response = self.fetch(\"/get\", headers={\"Cookie\": header})\n            self.assertEqual(response.body, utf8(expected))\n\n    def test_set_cookie_forbidden_char(self):\n        response = self.fetch(\"/forbidden_char\")\n        self.assertEqual(response.code, 200)\n        self.maxDiff = 10000\n        self.assertMultiLineEqual(to_unicode(response.body), \"\")\n\n    def test_set_cookie_overwrite(self):\n        response = self.fetch(\"/set_overwrite\")\n        headers = response.headers.get_list(\"Set-Cookie\")\n        self.assertEqual(\n            sorted(headers), [\"a=e; Path=/\", \"c=d; Domain=example.com; Path=/\"]\n        )\n\n    def test_set_cookie_max_age(self):\n        response = self.fetch(\"/set_max_age\")\n        headers = response.headers.get_list(\"Set-Cookie\")\n        self.assertEqual(sorted(headers), [\"foo=bar; Max-Age=10; Path=/\"])\n\n    def test_set_cookie_expires_days(self):\n        response = self.fetch(\"/set_expires_days\")\n        header = response.headers.get(\"Set-Cookie\")\n        self.assertIsNotNone(header)\n        assert header is not None  # for mypy\n        match = re.match(\"foo=bar; expires=(?P<expires>.+); Path=/\", header)\n        self.assertIsNotNone(match)\n        assert match is not None  # for mypy\n\n        expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(\n            days=10\n        )\n        header_expires = email.utils.parsedate_to_datetime(match.groupdict()[\"expires\"])\n        self.assertLess(abs((expires - header_expires).total_seconds()), 10)\n\n    def test_set_cookie_false_flags(self):\n        response = self.fetch(\"/set_falsy_flags\")\n        headers = sorted(response.headers.get_list(\"Set-Cookie\"))\n        self.assertEqual(headers[0], \"a=1; Path=/; Secure\")\n        self.assertEqual(headers[1], \"b=1; Path=/\")\n        self.assertEqual(headers[2], \"c=1; HttpOnly; Path=/\")\n        self.assertEqual(headers[3], \"d=1; Path=/\")\n\n    def test_set_cookie_deprecated(self):\n        response = self.fetch(\"/set_deprecated\")\n        header = response.headers.get(\"Set-Cookie\")\n        self.assertEqual(header, \"a=b; HttpOnly; Path=/foo\")\n\n\nclass AuthRedirectRequestHandler(RequestHandler):\n    def initialize(self, login_url):\n        self.login_url = login_url\n\n    def get_login_url(self):\n        return self.login_url\n\n    @authenticated\n    def get(self):\n        # we'll never actually get here because the test doesn't follow redirects\n        self.send_error(500)\n\n\nclass AuthRedirectTest(WebTestCase):\n    def get_handlers(self):\n        return [\n            (\"/relative\", AuthRedirectRequestHandler, dict(login_url=\"/login\")),\n            (\n                \"/absolute\",\n                AuthRedirectRequestHandler,\n                dict(login_url=\"http://example.com/login\"),\n            ),\n        ]\n\n    def test_relative_auth_redirect(self):\n        response = self.fetch(self.get_url(\"/relative\"), follow_redirects=False)\n        self.assertEqual(response.code, 302)\n        self.assertEqual(response.headers[\"Location\"], \"/login?next=%2Frelative\")\n\n    def test_absolute_auth_redirect(self):\n        response = self.fetch(self.get_url(\"/absolute\"), follow_redirects=False)\n        self.assertEqual(response.code, 302)\n        self.assertTrue(\n            re.match(\n                r\"http://example.com/login\\?next=http%3A%2F%2F127.0.0.1%3A[0-9]+%2Fabsolute\",\n                response.headers[\"Location\"],\n            ),\n            response.headers[\"Location\"],\n        )\n\n\nclass ConnectionCloseHandler(RequestHandler):\n    def initialize(self, test):\n        self.test = test\n\n    @gen.coroutine\n    def get(self):\n        self.test.on_handler_waiting()\n        yield self.test.cleanup_event.wait()\n\n    def on_connection_close(self):\n        self.test.on_connection_close()\n\n\nclass ConnectionCloseTest(WebTestCase):\n    def get_handlers(self):\n        self.cleanup_event = Event()\n        return [(\"/\", ConnectionCloseHandler, dict(test=self))]\n\n    def test_connection_close(self):\n        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)\n        s.connect((\"127.0.0.1\", self.get_http_port()))\n        self.stream = IOStream(s)\n        self.stream.write(b\"GET / HTTP/1.0\\r\\n\\r\\n\")\n        self.wait()\n        # Let the hanging coroutine clean up after itself\n        self.cleanup_event.set()\n        self.io_loop.run_sync(lambda: gen.sleep(0))\n\n    def on_handler_waiting(self):\n        logging.debug(\"handler waiting\")\n        self.stream.close()\n\n    def on_connection_close(self):\n        logging.debug(\"connection closed\")\n        self.stop()\n\n\nclass EchoHandler(RequestHandler):\n    def get(self, *path_args):\n        # Type checks: web.py interfaces convert argument values to\n        # unicode strings (by default, but see also decode_argument).\n        # In httpserver.py (i.e. self.request.arguments), they're left\n        # as bytes.  Keys are always native strings.\n        for key in self.request.arguments:\n            if type(key) is not str:\n                raise Exception(\"incorrect type for key: %r\" % type(key))\n            for bvalue in self.request.arguments[key]:\n                if type(bvalue) is not bytes:\n                    raise Exception(\"incorrect type for value: %r\" % type(bvalue))\n            for svalue in self.get_arguments(key):\n                if type(svalue) is not unicode_type:\n                    raise Exception(\"incorrect type for value: %r\" % type(svalue))\n        for arg in path_args:\n            if type(arg) is not unicode_type:\n                raise Exception(\"incorrect type for path arg: %r\" % type(arg))\n        self.write(\n            dict(\n                path=self.request.path,\n                path_args=path_args,\n                args=recursive_unicode(self.request.arguments),\n            )\n        )\n\n\nclass RequestEncodingTest(WebTestCase):\n    def get_handlers(self):\n        return [(\"/group/(.*)\", EchoHandler), (\"/slashes/([^/]*)/([^/]*)\", EchoHandler)]\n\n    def fetch_json(self, path):\n        return json_decode(self.fetch(path).body)\n\n    def test_group_question_mark(self):\n        # Ensure that url-encoded question marks are handled properly\n        self.assertEqual(\n            self.fetch_json(\"/group/%3F\"),\n            dict(path=\"/group/%3F\", path_args=[\"?\"], args={}),\n        )\n        self.assertEqual(\n            self.fetch_json(\"/group/%3F?%3F=%3F\"),\n            dict(path=\"/group/%3F\", path_args=[\"?\"], args={\"?\": [\"?\"]}),\n        )\n\n    def test_group_encoding(self):\n        # Path components and query arguments should be decoded the same way\n        self.assertEqual(\n            self.fetch_json(\"/group/%C3%A9?arg=%C3%A9\"),\n            {\n                \"path\": \"/group/%C3%A9\",\n                \"path_args\": [\"\\u00e9\"],\n                \"args\": {\"arg\": [\"\\u00e9\"]},\n            },\n        )\n\n    def test_slashes(self):\n        # Slashes may be escaped to appear as a single \"directory\" in the path,\n        # but they are then unescaped when passed to the get() method.\n        self.assertEqual(\n            self.fetch_json(\"/slashes/foo/bar\"),\n            dict(path=\"/slashes/foo/bar\", path_args=[\"foo\", \"bar\"], args={}),\n        )\n        self.assertEqual(\n            self.fetch_json(\"/slashes/a%2Fb/c%2Fd\"),\n            dict(path=\"/slashes/a%2Fb/c%2Fd\", path_args=[\"a/b\", \"c/d\"], args={}),\n        )\n\n    def test_error(self):\n        # Percent signs (encoded as %25) should not mess up printf-style\n        # messages in logs\n        with ExpectLog(gen_log, \".*Invalid unicode\"):\n            self.fetch(\"/group/?arg=%25%e9\")\n\n\nclass TypeCheckHandler(RequestHandler):\n    def prepare(self):\n        self.errors: dict[str, str] = {}\n\n        self.check_type(\"status\", self.get_status(), int)\n\n        # get_argument is an exception from the general rule of using\n        # type str for non-body data mainly for historical reasons.\n        self.check_type(\"argument\", self.get_argument(\"foo\"), unicode_type)\n        self.check_type(\"cookie_key\", list(self.cookies.keys())[0], str)\n        self.check_type(\"cookie_value\", list(self.cookies.values())[0].value, str)\n\n        # Secure cookies return bytes because they can contain arbitrary\n        # data, but regular cookies are native strings.\n        if list(self.cookies.keys()) != [\"asdf\"]:\n            raise Exception(\n                \"unexpected values for cookie keys: %r\" % self.cookies.keys()\n            )\n        self.check_type(\"get_signed_cookie\", self.get_signed_cookie(\"asdf\"), bytes)\n        self.check_type(\"get_cookie\", self.get_cookie(\"asdf\"), str)\n\n        self.check_type(\"xsrf_token\", self.xsrf_token, bytes)\n        self.check_type(\"xsrf_form_html\", self.xsrf_form_html(), str)\n\n        self.check_type(\"reverse_url\", self.reverse_url(\"typecheck\", \"foo\"), str)\n\n        self.check_type(\"request_summary\", self._request_summary(), str)\n\n    def get(self, path_component):\n        # path_component uses type unicode instead of str for consistency\n        # with get_argument()\n        self.check_type(\"path_component\", path_component, unicode_type)\n        self.write(self.errors)\n\n    def post(self, path_component):\n        self.check_type(\"path_component\", path_component, unicode_type)\n        self.write(self.errors)\n\n    def check_type(self, name, obj, expected_type):\n        actual_type = type(obj)\n        if expected_type != actual_type:\n            self.errors[name] = f\"expected {expected_type}, got {actual_type}\"\n\n\nclass DecodeArgHandler(RequestHandler):\n    def decode_argument(self, value, name=None):\n        if type(value) is not bytes:\n            raise Exception(\"unexpected type for value: %r\" % type(value))\n        # use self.request.arguments directly to avoid recursion\n        if \"encoding\" in self.request.arguments:\n            return value.decode(to_unicode(self.request.arguments[\"encoding\"][0]))\n        else:\n            return value\n\n    def get(self, arg):\n        def describe(s):\n            if type(s) is bytes:\n                return [\"bytes\", native_str(binascii.b2a_hex(s))]\n            elif type(s) is unicode_type:\n                return [\"unicode\", s]\n            raise Exception(\"unknown type\")\n\n        self.write({\"path\": describe(arg), \"query\": describe(self.get_argument(\"foo\"))})\n\n\nclass LinkifyHandler(RequestHandler):\n    def get(self):\n        self.render(\"linkify.html\", message=\"http://example.com\")\n\n\nclass UIModuleResourceHandler(RequestHandler):\n    def get(self):\n        self.render(\"page.html\", entries=[1, 2])\n\n\nclass OptionalPathHandler(RequestHandler):\n    def get(self, path):\n        self.write({\"path\": path})\n\n\nclass MultiHeaderHandler(RequestHandler):\n    def get(self):\n        self.set_header(\"x-overwrite\", \"1\")\n        self.set_header(\"X-Overwrite\", 2)\n        self.add_header(\"x-multi\", 3)\n        self.add_header(\"X-Multi\", \"4\")\n\n\nclass RedirectHandler(RequestHandler):\n    def get(self):\n        if self.get_argument(\"permanent\", None) is not None:\n            self.redirect(\"/\", permanent=bool(int(self.get_argument(\"permanent\"))))\n        elif self.get_argument(\"status\", None) is not None:\n            self.redirect(\"/\", status=int(self.get_argument(\"status\")))\n        else:\n            raise Exception(\"didn't get permanent or status arguments\")\n\n\nclass EmptyFlushCallbackHandler(RequestHandler):\n    @gen.coroutine\n    def get(self):\n        # Ensure that the flush callback is run whether or not there\n        # was any output.  The gen.Task and direct yield forms are\n        # equivalent.\n        yield self.flush()  # \"empty\" flush, but writes headers\n        yield self.flush()  # empty flush\n        self.write(\"o\")\n        yield self.flush()  # flushes the \"o\"\n        yield self.flush()  # empty flush\n        self.finish(\"k\")\n\n\nclass HeaderInjectionHandler(RequestHandler):\n    def get(self):\n        try:\n            self.set_header(\"X-Foo\", \"foo\\r\\nX-Bar: baz\")\n            raise Exception(\"Didn't get expected exception\")\n        except ValueError as e:\n            if \"Unsafe header value\" in str(e):\n                self.finish(b\"ok\")\n            else:\n                raise\n\n\nclass SetHeaderHandler(RequestHandler):\n    def get(self):\n        # tests the validity of web.RequestHandler._VALID_HEADER_CHARS\n        illegal_chars = [chr(o) for o in range(0, 0x20)]\n        illegal_chars.append(chr(0x7F))\n        illegal_chars.remove(\"\\t\")\n        for char in illegal_chars:\n            try:\n                self.set_header(\"X-Foo\", \"foo\" + char + \"bar\")\n                raise Exception(\"Didn't get expected exception\")\n            except ValueError as e:\n                if \"Unsafe header value\" not in str(e):\n                    raise\n\n        # an empty header value is valid as well\n        self.set_header(\"X-Foo\", \"\")\n\n        self.finish(b\"ok\")\n\n\nclass GetArgumentHandler(RequestHandler):\n    def prepare(self):\n        if self.get_argument(\"source\", None) == \"query\":\n            method = self.get_query_argument\n        elif self.get_argument(\"source\", None) == \"body\":\n            method = self.get_body_argument\n        else:\n            method = self.get_argument  # type: ignore\n        self.finish(method(\"foo\", \"default\"))\n\n\nclass GetArgumentsHandler(RequestHandler):\n    def prepare(self):\n        self.finish(\n            dict(\n                default=self.get_arguments(\"foo\"),\n                query=self.get_query_arguments(\"foo\"),\n                body=self.get_body_arguments(\"foo\"),\n            )\n        )\n\n\n# This test was shared with wsgi_test.py; now the name is meaningless.\nclass WSGISafeWebTest(WebTestCase):\n    COOKIE_SECRET = \"WebTest.COOKIE_SECRET\"\n\n    def get_app_kwargs(self):\n        loader = DictLoader(\n            {\n                \"linkify.html\": \"{% module linkify(message) %}\",\n                \"page.html\": \"\"\"\\\n<html><head></head><body>\n{% for e in entries %}\n{% module Template(\"entry.html\", entry=e) %}\n{% end %}\n</body></html>\"\"\",\n                \"entry.html\": \"\"\"\\\n{{ set_resources(embedded_css=\".entry { margin-bottom: 1em; }\",\n                 embedded_javascript=\"js_embed()\",\n                 css_files=[\"/base.css\", \"/foo.css\"],\n                 javascript_files=\"/common.js\",\n                 html_head=\"<meta>\",\n                 html_body='<script src=\"/analytics.js\"/>') }}\n<div class=\"entry\">...</div>\"\"\",\n            }\n        )\n        return dict(\n            template_loader=loader,\n            autoescape=\"xhtml_escape\",\n            cookie_secret=self.COOKIE_SECRET,\n        )\n\n    def tearDown(self):\n        super().tearDown()\n        RequestHandler._template_loaders.clear()\n\n    def get_handlers(self):\n        urls = [\n            url(\"/typecheck/(.*)\", TypeCheckHandler, name=\"typecheck\"),\n            url(\"/decode_arg/(.*)\", DecodeArgHandler, name=\"decode_arg\"),\n            url(\"/decode_arg_kw/(?P<arg>.*)\", DecodeArgHandler),\n            url(\"/linkify\", LinkifyHandler),\n            url(\"/uimodule_resources\", UIModuleResourceHandler),\n            url(\"/optional_path/(.+)?\", OptionalPathHandler),\n            url(\"/multi_header\", MultiHeaderHandler),\n            url(\"/redirect\", RedirectHandler),\n            url(\n                \"/web_redirect_permanent\",\n                WebRedirectHandler,\n                {\"url\": \"/web_redirect_newpath\"},\n            ),\n            url(\n                \"/web_redirect\",\n                WebRedirectHandler,\n                {\"url\": \"/web_redirect_newpath\", \"permanent\": False},\n            ),\n            url(\n                \"//web_redirect_double_slash\",\n                WebRedirectHandler,\n                {\"url\": \"/web_redirect_newpath\"},\n            ),\n            url(\"/header_injection\", HeaderInjectionHandler),\n            url(\"/get_argument\", GetArgumentHandler),\n            url(\"/get_arguments\", GetArgumentsHandler),\n            url(\"/set_header\", SetHeaderHandler),\n        ]\n        return urls\n\n    def fetch_json(self, *args, **kwargs):\n        response = self.fetch(*args, **kwargs)\n        response.rethrow()\n        return json_decode(response.body)\n\n    def test_types(self):\n        cookie_value = to_unicode(\n            create_signed_value(self.COOKIE_SECRET, \"asdf\", \"qwer\")\n        )\n        response = self.fetch(\n            \"/typecheck/asdf?foo=bar\", headers={\"Cookie\": \"asdf=\" + cookie_value}\n        )\n        data = json_decode(response.body)\n        self.assertEqual(data, {})\n\n        response = self.fetch(\n            \"/typecheck/asdf?foo=bar\",\n            method=\"POST\",\n            headers={\"Cookie\": \"asdf=\" + cookie_value},\n            body=\"foo=bar\",\n        )\n\n    def test_decode_argument(self):\n        # These urls all decode to the same thing\n        urls = [\n            \"/decode_arg/%C3%A9?foo=%C3%A9&encoding=utf-8\",\n            \"/decode_arg/%E9?foo=%E9&encoding=latin1\",\n            \"/decode_arg_kw/%E9?foo=%E9&encoding=latin1\",\n        ]\n        for req_url in urls:\n            response = self.fetch(req_url)\n            response.rethrow()\n            data = json_decode(response.body)\n            self.assertEqual(\n                data,\n                {\"path\": [\"unicode\", \"\\u00e9\"], \"query\": [\"unicode\", \"\\u00e9\"]},\n            )\n\n        response = self.fetch(\"/decode_arg/%C3%A9?foo=%C3%A9\")\n        response.rethrow()\n        data = json_decode(response.body)\n        self.assertEqual(data, {\"path\": [\"bytes\", \"c3a9\"], \"query\": [\"bytes\", \"c3a9\"]})\n\n    def test_decode_argument_invalid_unicode(self):\n        # test that invalid unicode in URLs causes 400, not 500\n        with ExpectLog(gen_log, \".*Invalid unicode.*\"):\n            response = self.fetch(\"/typecheck/invalid%FF\")\n            self.assertEqual(response.code, 400)\n            response = self.fetch(\"/typecheck/invalid?foo=%FF\")\n            self.assertEqual(response.code, 400)\n\n    def test_decode_argument_plus(self):\n        # These urls are all equivalent.\n        urls = [\n            \"/decode_arg/1%20%2B%201?foo=1%20%2B%201&encoding=utf-8\",\n            \"/decode_arg/1%20+%201?foo=1+%2B+1&encoding=utf-8\",\n        ]\n        for req_url in urls:\n            response = self.fetch(req_url)\n            response.rethrow()\n            data = json_decode(response.body)\n            self.assertEqual(\n                data,\n                {\"path\": [\"unicode\", \"1 + 1\"], \"query\": [\"unicode\", \"1 + 1\"]},\n            )\n\n    def test_reverse_url(self):\n        self.assertEqual(self.app.reverse_url(\"decode_arg\", \"foo\"), \"/decode_arg/foo\")\n        self.assertEqual(self.app.reverse_url(\"decode_arg\", 42), \"/decode_arg/42\")\n        self.assertEqual(self.app.reverse_url(\"decode_arg\", b\"\\xe9\"), \"/decode_arg/%E9\")\n        self.assertEqual(\n            self.app.reverse_url(\"decode_arg\", \"\\u00e9\"), \"/decode_arg/%C3%A9\"\n        )\n        self.assertEqual(\n            self.app.reverse_url(\"decode_arg\", \"1 + 1\"), \"/decode_arg/1%20%2B%201\"\n        )\n\n    def test_uimodule_unescaped(self):\n        response = self.fetch(\"/linkify\")\n        self.assertEqual(\n            response.body, b'<a href=\"http://example.com\">http://example.com</a>'\n        )\n\n    def test_uimodule_resources(self):\n        response = self.fetch(\"/uimodule_resources\")\n        self.assertEqual(\n            response.body,\n            b\"\"\"\\\n<html><head><link href=\"/base.css\" type=\"text/css\" rel=\"stylesheet\"/><link href=\"/foo.css\" type=\"text/css\" rel=\"stylesheet\"/>\n<style type=\"text/css\">\n.entry { margin-bottom: 1em; }\n</style>\n<meta>\n</head><body>\n\n\n<div class=\"entry\">...</div>\n\n\n<div class=\"entry\">...</div>\n\n<script src=\"/common.js\" type=\"text/javascript\"></script>\n<script type=\"text/javascript\">\n//<![CDATA[\njs_embed()\n//]]>\n</script>\n<script src=\"/analytics.js\"/>\n</body></html>\"\"\",  # noqa: E501\n        )\n\n    def test_optional_path(self):\n        self.assertEqual(self.fetch_json(\"/optional_path/foo\"), {\"path\": \"foo\"})\n        self.assertEqual(self.fetch_json(\"/optional_path/\"), {\"path\": None})\n\n    def test_multi_header(self):\n        response = self.fetch(\"/multi_header\")\n        self.assertEqual(response.headers[\"x-overwrite\"], \"2\")\n        self.assertEqual(response.headers.get_list(\"x-multi\"), [\"3\", \"4\"])\n\n    def test_redirect(self):\n        response = self.fetch(\"/redirect?permanent=1\", follow_redirects=False)\n        self.assertEqual(response.code, 301)\n        response = self.fetch(\"/redirect?permanent=0\", follow_redirects=False)\n        self.assertEqual(response.code, 302)\n        response = self.fetch(\"/redirect?status=307\", follow_redirects=False)\n        self.assertEqual(response.code, 307)\n\n    def test_web_redirect(self):\n        response = self.fetch(\"/web_redirect_permanent\", follow_redirects=False)\n        self.assertEqual(response.code, 301)\n        self.assertEqual(response.headers[\"Location\"], \"/web_redirect_newpath\")\n        response = self.fetch(\"/web_redirect\", follow_redirects=False)\n        self.assertEqual(response.code, 302)\n        self.assertEqual(response.headers[\"Location\"], \"/web_redirect_newpath\")\n\n    def test_web_redirect_double_slash(self):\n        response = self.fetch(\"//web_redirect_double_slash\", follow_redirects=False)\n        self.assertEqual(response.code, 301)\n        self.assertEqual(response.headers[\"Location\"], \"/web_redirect_newpath\")\n\n    def test_header_injection(self):\n        response = self.fetch(\"/header_injection\")\n        self.assertEqual(response.body, b\"ok\")\n\n    def test_set_header(self):\n        response = self.fetch(\"/set_header\")\n        self.assertEqual(response.body, b\"ok\")\n\n    def test_get_argument(self):\n        response = self.fetch(\"/get_argument?foo=bar\")\n        self.assertEqual(response.body, b\"bar\")\n        response = self.fetch(\"/get_argument?foo=\")\n        self.assertEqual(response.body, b\"\")\n        response = self.fetch(\"/get_argument\")\n        self.assertEqual(response.body, b\"default\")\n\n        # Test merging of query and body arguments.\n        # In singular form, body arguments take precedence over query arguments.\n        body = urllib.parse.urlencode(dict(foo=\"hello\"))\n        response = self.fetch(\"/get_argument?foo=bar\", method=\"POST\", body=body)\n        self.assertEqual(response.body, b\"hello\")\n        # In plural methods they are merged.\n        response = self.fetch(\"/get_arguments?foo=bar\", method=\"POST\", body=body)\n        self.assertEqual(\n            json_decode(response.body),\n            dict(default=[\"bar\", \"hello\"], query=[\"bar\"], body=[\"hello\"]),\n        )\n\n    def test_get_query_arguments(self):\n        # send as a post so we can ensure the separation between query\n        # string and body arguments.\n        body = urllib.parse.urlencode(dict(foo=\"hello\"))\n        response = self.fetch(\n            \"/get_argument?source=query&foo=bar\", method=\"POST\", body=body\n        )\n        self.assertEqual(response.body, b\"bar\")\n        response = self.fetch(\n            \"/get_argument?source=query&foo=\", method=\"POST\", body=body\n        )\n        self.assertEqual(response.body, b\"\")\n        response = self.fetch(\"/get_argument?source=query\", method=\"POST\", body=body)\n        self.assertEqual(response.body, b\"default\")\n\n    def test_get_body_arguments(self):\n        body = urllib.parse.urlencode(dict(foo=\"bar\"))\n        response = self.fetch(\n            \"/get_argument?source=body&foo=hello\", method=\"POST\", body=body\n        )\n        self.assertEqual(response.body, b\"bar\")\n\n        body = urllib.parse.urlencode(dict(foo=\"\"))\n        response = self.fetch(\n            \"/get_argument?source=body&foo=hello\", method=\"POST\", body=body\n        )\n        self.assertEqual(response.body, b\"\")\n\n        body = urllib.parse.urlencode(dict())\n        response = self.fetch(\n            \"/get_argument?source=body&foo=hello\", method=\"POST\", body=body\n        )\n        self.assertEqual(response.body, b\"default\")\n\n    def test_no_gzip(self):\n        response = self.fetch(\"/get_argument\")\n        self.assertNotIn(\"Accept-Encoding\", response.headers.get(\"Vary\", \"\"))\n        self.assertNotIn(\"gzip\", response.headers.get(\"Content-Encoding\", \"\"))\n\n\nclass NonWSGIWebTests(WebTestCase):\n    def get_handlers(self):\n        return [(\"/empty_flush\", EmptyFlushCallbackHandler)]\n\n    def test_empty_flush(self):\n        response = self.fetch(\"/empty_flush\")\n        self.assertEqual(response.body, b\"ok\")\n\n\nclass ErrorResponseTest(WebTestCase):\n    def get_handlers(self):\n        class DefaultHandler(RequestHandler):\n            def get(self):\n                if self.get_argument(\"status\", None):\n                    raise HTTPError(int(self.get_argument(\"status\")))\n                1 / 0\n\n        class WriteErrorHandler(RequestHandler):\n            def get(self):\n                if self.get_argument(\"status\", None):\n                    self.send_error(int(self.get_argument(\"status\")))\n                else:\n                    1 / 0\n\n            def write_error(self, status_code, **kwargs):\n                self.set_header(\"Content-Type\", \"text/plain\")\n                if \"exc_info\" in kwargs:\n                    self.write(\"Exception: %s\" % kwargs[\"exc_info\"][0].__name__)\n                else:\n                    self.write(\"Status: %d\" % status_code)\n\n        class FailedWriteErrorHandler(RequestHandler):\n            def get(self):\n                1 / 0\n\n            def write_error(self, status_code, **kwargs):\n                raise Exception(\"exception in write_error\")\n\n        return [\n            url(\"/default\", DefaultHandler),\n            url(\"/write_error\", WriteErrorHandler),\n            url(\"/failed_write_error\", FailedWriteErrorHandler),\n        ]\n\n    def test_default(self):\n        with ExpectLog(app_log, \"Uncaught exception\"):\n            response = self.fetch(\"/default\")\n            self.assertEqual(response.code, 500)\n            self.assertIn(b\"500: Internal Server Error\", response.body)\n\n            response = self.fetch(\"/default?status=503\")\n            self.assertEqual(response.code, 503)\n            self.assertIn(b\"503: Service Unavailable\", response.body)\n\n            response = self.fetch(\"/default?status=435\")\n            self.assertEqual(response.code, 435)\n            self.assertIn(b\"435: Unknown\", response.body)\n\n    def test_write_error(self):\n        with ExpectLog(app_log, \"Uncaught exception\"):\n            response = self.fetch(\"/write_error\")\n            self.assertEqual(response.code, 500)\n            self.assertEqual(b\"Exception: ZeroDivisionError\", response.body)\n\n            response = self.fetch(\"/write_error?status=503\")\n            self.assertEqual(response.code, 503)\n            self.assertEqual(b\"Status: 503\", response.body)\n\n    def test_failed_write_error(self):\n        with ExpectLog(app_log, \"Uncaught exception\"):\n            response = self.fetch(\"/failed_write_error\")\n            self.assertEqual(response.code, 500)\n            self.assertEqual(b\"\", response.body)\n\n\nclass StaticFileTest(WebTestCase):\n    # The expected SHA-512 hash of robots.txt, used in tests that call\n    # StaticFileHandler.get_version\n    robots_txt_hash = (\n        b\"63a36e950e134b5217e33c763e88840c10a07d80e6057d92b9ac97508de7fb1f\"\n        b\"a6f0e9b7531e169657165ea764e8963399cb6d921ffe6078425aaafe54c04563\"\n    )\n    static_dir = os.path.join(os.path.dirname(__file__), \"static\")\n\n    def get_handlers(self):\n        class StaticUrlHandler(RequestHandler):\n            def get(self, path):\n                with_v = int(self.get_argument(\"include_version\", \"1\"))\n                self.write(self.static_url(path, include_version=with_v))\n\n        class AbsoluteStaticUrlHandler(StaticUrlHandler):\n            include_host = True\n\n        class OverrideStaticUrlHandler(RequestHandler):\n            def get(self, path):\n                do_include = bool(self.get_argument(\"include_host\"))\n                self.include_host = not do_include\n\n                regular_url = self.static_url(path)\n                override_url = self.static_url(path, include_host=do_include)\n                if override_url == regular_url:\n                    return self.write(str(False))\n\n                protocol = self.request.protocol + \"://\"\n                protocol_length = len(protocol)\n                check_regular = regular_url.find(protocol, 0, protocol_length)\n                check_override = override_url.find(protocol, 0, protocol_length)\n\n                if do_include:\n                    result = check_override == 0 and check_regular == -1\n                else:\n                    result = check_override == -1 and check_regular == 0\n                self.write(str(result))\n\n        return [\n            (\"/static_url/(.*)\", StaticUrlHandler),\n            (\"/abs_static_url/(.*)\", AbsoluteStaticUrlHandler),\n            (\"/override_static_url/(.*)\", OverrideStaticUrlHandler),\n            (\"/root_static/(.*)\", StaticFileHandler, dict(path=\"/\")),\n        ]\n\n    def get_app_kwargs(self):\n        return dict(static_path=relpath(\"static\"))\n\n    def test_static_files(self):\n        response = self.fetch(\"/robots.txt\")\n        self.assertIn(b\"Disallow: /\", response.body)\n\n        response = self.fetch(\"/static/robots.txt\")\n        self.assertIn(b\"Disallow: /\", response.body)\n        self.assertEqual(response.headers.get(\"Content-Type\"), \"text/plain\")\n\n    def test_static_files_cacheable(self):\n        # Test that the version parameter triggers cache-control headers. This\n        # test is pretty weak but it gives us coverage of the code path which\n        # was important for detecting the deprecation of datetime.utcnow.\n        response = self.fetch(\"/robots.txt?v=12345\")\n        self.assertIn(b\"Disallow: /\", response.body)\n        self.assertIn(\"Cache-Control\", response.headers)\n        self.assertIn(\"Expires\", response.headers)\n\n    def test_static_compressed_files(self):\n        response = self.fetch(\"/static/sample.xml.gz\")\n        self.assertEqual(response.headers.get(\"Content-Type\"), \"application/gzip\")\n        response = self.fetch(\"/static/sample.xml.bz2\")\n        self.assertEqual(\n            response.headers.get(\"Content-Type\"), \"application/octet-stream\"\n        )\n        # make sure the uncompressed file still has the correct type\n        response = self.fetch(\"/static/sample.xml\")\n        self.assertIn(\n            response.headers.get(\"Content-Type\"), {\"text/xml\", \"application/xml\"}\n        )\n\n    def test_static_windows_special_filenames(self):\n        # Windows has some magic filenames that are special and (in some ways) \"exist\"\n        # in every directory. These filenames are used to access stdio and hardware\n        # devices and so should not be served as static files.\n        filenames = [\n            \"CON\",\n            \"PRN\",\n            \"AUX\",\n            \"NUL\",\n            \"COM1\",\n            \"LPT1\",\n        ]\n        for filename in filenames:\n            with self.subTest(filename=filename):\n                response = self.fetch(f\"/static/{filename}\")\n                # The exact behavior of these filenames differs across versions of\n                # Windows and Python.\n                # https://github.com/python/cpython/issues/90520#issuecomment-1093942179\n                # This sometimes hits the \"file not in static root directory\" check (which\n                # returns 403) and sometimes the \"file does not exist\" check (which returns 404).\n                # Either outcome is fine as long as we don't actually try to serve the file.\n                self.assertIn(response.code, (404, 403))\n\n    def test_static_url(self):\n        response = self.fetch(\"/static_url/robots.txt\")\n        self.assertEqual(response.body, b\"/static/robots.txt?v=\" + self.robots_txt_hash)\n\n    def test_absolute_static_url(self):\n        response = self.fetch(\"/abs_static_url/robots.txt\")\n        self.assertEqual(\n            response.body,\n            (utf8(self.get_url(\"/\")) + b\"static/robots.txt?v=\" + self.robots_txt_hash),\n        )\n\n    def test_relative_version_exclusion(self):\n        response = self.fetch(\"/static_url/robots.txt?include_version=0\")\n        self.assertEqual(response.body, b\"/static/robots.txt\")\n\n    def test_absolute_version_exclusion(self):\n        response = self.fetch(\"/abs_static_url/robots.txt?include_version=0\")\n        self.assertEqual(response.body, utf8(self.get_url(\"/\") + \"static/robots.txt\"))\n\n    def test_include_host_override(self):\n        self._trigger_include_host_check(False)\n        self._trigger_include_host_check(True)\n\n    def _trigger_include_host_check(self, include_host):\n        path = \"/override_static_url/robots.txt?include_host=%s\"\n        response = self.fetch(path % int(include_host))\n        self.assertEqual(response.body, utf8(str(True)))\n\n    def get_and_head(self, *args, **kwargs):\n        \"\"\"Performs a GET and HEAD request and returns the GET response.\n\n        Fails if any ``Content-*`` headers returned by the two requests\n        differ.\n        \"\"\"\n        head_response = self.fetch(*args, method=\"HEAD\", **kwargs)\n        get_response = self.fetch(*args, method=\"GET\", **kwargs)\n        content_headers = set()\n        for h in itertools.chain(head_response.headers, get_response.headers):\n            if h.startswith(\"Content-\"):\n                content_headers.add(h)\n        for h in content_headers:\n            self.assertEqual(\n                head_response.headers.get(h),\n                get_response.headers.get(h),\n                \"%s differs between GET (%s) and HEAD (%s)\"\n                % (h, head_response.headers.get(h), get_response.headers.get(h)),\n            )\n        return get_response\n\n    def test_static_304_if_modified_since(self):\n        response1 = self.get_and_head(\"/static/robots.txt\")\n        response2 = self.get_and_head(\n            \"/static/robots.txt\",\n            headers={\"If-Modified-Since\": response1.headers[\"Last-Modified\"]},\n        )\n        self.assertEqual(response2.code, 304)\n        self.assertNotIn(\"Content-Length\", response2.headers)\n\n    def test_static_304_if_none_match(self):\n        response1 = self.get_and_head(\"/static/robots.txt\")\n        response2 = self.get_and_head(\n            \"/static/robots.txt\", headers={\"If-None-Match\": response1.headers[\"Etag\"]}\n        )\n        self.assertEqual(response2.code, 304)\n\n    def test_static_304_etag_modified_bug(self):\n        response1 = self.get_and_head(\"/static/robots.txt\")\n        response2 = self.get_and_head(\n            \"/static/robots.txt\",\n            headers={\n                \"If-None-Match\": '\"MISMATCH\"',\n                \"If-Modified-Since\": response1.headers[\"Last-Modified\"],\n            },\n        )\n        self.assertEqual(response2.code, 200)\n\n    def test_static_304_if_modified_since_invalid(self):\n        response = self.get_and_head(\n            \"/static/robots.txt\",\n            headers={\"If-Modified-Since\": \"!nv@l!d\"},\n        )\n        self.assertEqual(response.code, 200)\n\n    def test_static_if_modified_since_pre_epoch(self):\n        # On windows, the functions that work with time_t do not accept\n        # negative values, and at least one client (processing.js) seems\n        # to use if-modified-since 1/1/1960 as a cache-busting technique.\n        response = self.get_and_head(\n            \"/static/robots.txt\",\n            headers={\"If-Modified-Since\": \"Fri, 01 Jan 1960 00:00:00 GMT\"},\n        )\n        self.assertEqual(response.code, 200)\n\n    def test_static_if_modified_since_time_zone(self):\n        # Instead of the value from Last-Modified, make requests with times\n        # chosen just before and after the known modification time\n        # of the file to ensure that the right time zone is being used\n        # when parsing If-Modified-Since.\n        stat = os.stat(relpath(\"static/robots.txt\"))\n\n        response = self.get_and_head(\n            \"/static/robots.txt\",\n            headers={\"If-Modified-Since\": format_timestamp(stat.st_mtime - 1)},\n        )\n        self.assertEqual(response.code, 200)\n        response = self.get_and_head(\n            \"/static/robots.txt\",\n            headers={\"If-Modified-Since\": format_timestamp(stat.st_mtime + 1)},\n        )\n        self.assertEqual(response.code, 304)\n\n    def test_static_etag(self):\n        response = self.get_and_head(\"/static/robots.txt\")\n        self.assertEqual(\n            utf8(response.headers.get(\"Etag\")), b'\"' + self.robots_txt_hash + b'\"'\n        )\n\n    def test_static_with_range(self):\n        response = self.get_and_head(\n            \"/static/robots.txt\", headers={\"Range\": \"bytes=0-9\"}\n        )\n        self.assertEqual(response.code, 206)\n        self.assertEqual(response.body, b\"User-agent\")\n        self.assertEqual(\n            utf8(response.headers.get(\"Etag\")), b'\"' + self.robots_txt_hash + b'\"'\n        )\n        self.assertEqual(response.headers.get(\"Content-Length\"), \"10\")\n        self.assertEqual(response.headers.get(\"Content-Range\"), \"bytes 0-9/26\")\n\n    def test_static_with_range_full_file(self):\n        response = self.get_and_head(\n            \"/static/robots.txt\", headers={\"Range\": \"bytes=0-\"}\n        )\n        # Note: Chrome refuses to play audio if it gets an HTTP 206 in response\n        # to ``Range: bytes=0-`` :(\n        self.assertEqual(response.code, 200)\n        robots_file_path = os.path.join(self.static_dir, \"robots.txt\")\n        with open(robots_file_path, encoding=\"utf-8\") as f:\n            self.assertEqual(response.body, utf8(f.read()))\n        self.assertEqual(response.headers.get(\"Content-Length\"), \"26\")\n        self.assertIsNone(response.headers.get(\"Content-Range\"))\n\n    def test_static_with_range_full_past_end(self):\n        response = self.get_and_head(\n            \"/static/robots.txt\", headers={\"Range\": \"bytes=0-10000000\"}\n        )\n        self.assertEqual(response.code, 200)\n        robots_file_path = os.path.join(self.static_dir, \"robots.txt\")\n        with open(robots_file_path, encoding=\"utf-8\") as f:\n            self.assertEqual(response.body, utf8(f.read()))\n        self.assertEqual(response.headers.get(\"Content-Length\"), \"26\")\n        self.assertIsNone(response.headers.get(\"Content-Range\"))\n\n    def test_static_with_range_partial_past_end(self):\n        response = self.get_and_head(\n            \"/static/robots.txt\", headers={\"Range\": \"bytes=1-10000000\"}\n        )\n        self.assertEqual(response.code, 206)\n        robots_file_path = os.path.join(self.static_dir, \"robots.txt\")\n        with open(robots_file_path, encoding=\"utf-8\") as f:\n            self.assertEqual(response.body, utf8(f.read()[1:]))\n        self.assertEqual(response.headers.get(\"Content-Length\"), \"25\")\n        self.assertEqual(response.headers.get(\"Content-Range\"), \"bytes 1-25/26\")\n\n    def test_static_with_range_end_edge(self):\n        response = self.get_and_head(\n            \"/static/robots.txt\", headers={\"Range\": \"bytes=22-\"}\n        )\n        self.assertEqual(response.body, b\": /\\n\")\n        self.assertEqual(response.headers.get(\"Content-Length\"), \"4\")\n        self.assertEqual(response.headers.get(\"Content-Range\"), \"bytes 22-25/26\")\n\n    def test_static_with_range_neg_end(self):\n        response = self.get_and_head(\n            \"/static/robots.txt\", headers={\"Range\": \"bytes=-4\"}\n        )\n        self.assertEqual(response.body, b\": /\\n\")\n        self.assertEqual(response.headers.get(\"Content-Length\"), \"4\")\n        self.assertEqual(response.headers.get(\"Content-Range\"), \"bytes 22-25/26\")\n\n    def test_static_with_range_neg_past_start(self):\n        response = self.get_and_head(\n            \"/static/robots.txt\", headers={\"Range\": \"bytes=-1000000\"}\n        )\n        self.assertEqual(response.code, 200)\n        robots_file_path = os.path.join(self.static_dir, \"robots.txt\")\n        with open(robots_file_path, encoding=\"utf-8\") as f:\n            self.assertEqual(response.body, utf8(f.read()))\n        self.assertEqual(response.headers.get(\"Content-Length\"), \"26\")\n        self.assertIsNone(response.headers.get(\"Content-Range\"))\n\n    def test_static_invalid_range(self):\n        response = self.get_and_head(\"/static/robots.txt\", headers={\"Range\": \"asdf\"})\n        self.assertEqual(response.code, 200)\n\n    def test_static_unsatisfiable_range_zero_suffix(self):\n        response = self.get_and_head(\n            \"/static/robots.txt\", headers={\"Range\": \"bytes=-0\"}\n        )\n        self.assertEqual(response.headers.get(\"Content-Range\"), \"bytes */26\")\n        self.assertEqual(response.code, 416)\n\n    def test_static_unsatisfiable_range_invalid_start(self):\n        response = self.get_and_head(\n            \"/static/robots.txt\", headers={\"Range\": \"bytes=26\"}\n        )\n        self.assertEqual(response.code, 416)\n        self.assertEqual(response.headers.get(\"Content-Range\"), \"bytes */26\")\n\n    def test_static_unsatisfiable_range_end_less_than_start(self):\n        response = self.get_and_head(\n            \"/static/robots.txt\", headers={\"Range\": \"bytes=10-3\"}\n        )\n        self.assertEqual(response.code, 416)\n        self.assertEqual(response.headers.get(\"Content-Range\"), \"bytes */26\")\n\n    def test_static_head(self):\n        response = self.fetch(\"/static/robots.txt\", method=\"HEAD\")\n        self.assertEqual(response.code, 200)\n        # No body was returned, but we did get the right content length.\n        self.assertEqual(response.body, b\"\")\n        self.assertEqual(response.headers[\"Content-Length\"], \"26\")\n        self.assertEqual(\n            utf8(response.headers[\"Etag\"]), b'\"' + self.robots_txt_hash + b'\"'\n        )\n\n    def test_static_head_range(self):\n        response = self.fetch(\n            \"/static/robots.txt\", method=\"HEAD\", headers={\"Range\": \"bytes=1-4\"}\n        )\n        self.assertEqual(response.code, 206)\n        self.assertEqual(response.body, b\"\")\n        self.assertEqual(response.headers[\"Content-Length\"], \"4\")\n        self.assertEqual(\n            utf8(response.headers[\"Etag\"]), b'\"' + self.robots_txt_hash + b'\"'\n        )\n\n    def test_static_range_if_none_match(self):\n        response = self.get_and_head(\n            \"/static/robots.txt\",\n            headers={\n                \"Range\": \"bytes=1-4\",\n                \"If-None-Match\": b'\"' + self.robots_txt_hash + b'\"',\n            },\n        )\n        self.assertEqual(response.code, 304)\n        self.assertEqual(response.body, b\"\")\n        self.assertNotIn(\"Content-Length\", response.headers)\n        self.assertEqual(\n            utf8(response.headers[\"Etag\"]), b'\"' + self.robots_txt_hash + b'\"'\n        )\n\n    def test_static_404(self):\n        response = self.get_and_head(\"/static/blarg\")\n        self.assertEqual(response.code, 404)\n\n    def test_path_traversal_protection(self):\n        # curl_httpclient processes \"..\" on the client side, so we\n        # must test this with simple_httpclient.\n        self.http_client.close()\n        self.http_client = SimpleAsyncHTTPClient()\n        with ExpectLog(gen_log, \".*not in root static directory\"):\n            response = self.get_and_head(\"/static/../static_foo.txt\")\n        # Attempted path traversal should result in 403, not 200\n        # (which means the check failed and the file was served)\n        # or 404 (which means that the file didn't exist and\n        # is probably a packaging error).\n        self.assertEqual(response.code, 403)\n\n    @unittest.skipIf(os.name != \"posix\", \"non-posix OS\")\n    def test_root_static_path(self):\n        # Sometimes people set the StaticFileHandler's path to '/'\n        # to disable Tornado's path validation (in conjunction with\n        # their own validation in get_absolute_path). Make sure\n        # that the stricter validation in 4.2.1 doesn't break them.\n        path = os.path.join(\n            os.path.dirname(os.path.abspath(__file__)), \"static/robots.txt\"\n        )\n        response = self.get_and_head(\"/root_static\" + urllib.parse.quote(path))\n        self.assertEqual(response.code, 200)\n\n\nclass StaticDefaultFilenameTest(WebTestCase):\n    def get_app_kwargs(self):\n        return dict(\n            static_path=relpath(\"static\"),\n            static_handler_args=dict(default_filename=\"index.html\"),\n        )\n\n    def get_handlers(self):\n        return []\n\n    def test_static_default_filename(self):\n        response = self.fetch(\"/static/dir/\", follow_redirects=False)\n        self.assertEqual(response.code, 200)\n        self.assertEqual(b\"this is the index\\n\", response.body)\n\n    def test_static_default_redirect(self):\n        response = self.fetch(\"/static/dir\", follow_redirects=False)\n        self.assertEqual(response.code, 301)\n        self.assertTrue(response.headers[\"Location\"].endswith(\"/static/dir/\"))\n\n\nclass StaticDefaultFilenameRootTest(WebTestCase):\n    def get_app_kwargs(self):\n        return dict(\n            static_path=os.path.abspath(relpath(\"static\")),\n            static_handler_args=dict(default_filename=\"index.html\"),\n            static_url_prefix=\"/\",\n        )\n\n    def get_handlers(self):\n        return []\n\n    def get_http_client(self):\n        # simple_httpclient only: curl doesn't let you send a request starting\n        # with two slashes.\n        return SimpleAsyncHTTPClient()\n\n    def test_no_open_redirect(self):\n        # This test verifies that the open redirect that affected some configurations\n        # prior to Tornado 6.3.2 is no longer possible. The vulnerability required\n        # a static_url_prefix of \"/\" and a default_filename (any value) to be set.\n        # The absolute* server-side path to the static directory must also be known.\n        #\n        # * Almost absolute: On windows, the drive letter is stripped from the path.\n        test_dir = os.path.dirname(__file__)\n        drive, tail = os.path.splitdrive(test_dir)\n        if os.name == \"posix\":\n            self.assertEqual(tail, test_dir)\n        else:\n            test_dir = tail\n        with ExpectLog(gen_log, \".*cannot redirect path with two initial slashes\"):\n            response = self.fetch(\n                f\"//evil.com/../{test_dir}/static/dir\",\n                follow_redirects=False,\n            )\n        self.assertEqual(response.code, 403)\n\n\nclass StaticFileWithPathTest(WebTestCase):\n    def get_app_kwargs(self):\n        return dict(\n            static_path=relpath(\"static\"),\n            static_handler_args=dict(default_filename=\"index.html\"),\n        )\n\n    def get_handlers(self):\n        return [(\"/foo/(.*)\", StaticFileHandler, {\"path\": relpath(\"templates/\")})]\n\n    def test_serve(self):\n        response = self.fetch(\"/foo/utf8.html\")\n        self.assertEqual(response.body, b\"H\\xc3\\xa9llo\\n\")\n\n\nclass CustomStaticFileTest(WebTestCase):\n    def get_handlers(self):\n        class MyStaticFileHandler(StaticFileHandler):\n            @classmethod\n            def make_static_url(cls, settings, path):\n                version_hash = cls.get_version(settings, path)\n                extension_index = path.rindex(\".\")\n                before_version = path[:extension_index]\n                after_version = path[(extension_index + 1) :]\n                return \"/static/{}.{}.{}\".format(\n                    before_version,\n                    version_hash,\n                    after_version,\n                )\n\n            def parse_url_path(self, url_path):\n                extension_index = url_path.rindex(\".\")\n                version_index = url_path.rindex(\".\", 0, extension_index)\n                return f\"{url_path[:version_index]}{url_path[extension_index:]}\"\n\n            @classmethod\n            def get_absolute_path(cls, settings, path):\n                return \"CustomStaticFileTest:\" + path\n\n            def validate_absolute_path(self, root, absolute_path):\n                return absolute_path\n\n            @classmethod\n            def get_content(self, path, start=None, end=None):\n                assert start is None and end is None\n                if path == \"CustomStaticFileTest:foo.txt\":\n                    return b\"bar\"\n                raise Exception(\"unexpected path %r\" % path)\n\n            def get_content_size(self):\n                if self.absolute_path == \"CustomStaticFileTest:foo.txt\":\n                    return 3\n                raise Exception(\"unexpected path %r\" % self.absolute_path)\n\n            def get_modified_time(self):\n                return None\n\n            @classmethod\n            def get_version(cls, settings, path):\n                return \"42\"\n\n        class StaticUrlHandler(RequestHandler):\n            def get(self, path):\n                self.write(self.static_url(path))\n\n        self.static_handler_class = MyStaticFileHandler\n\n        return [(\"/static_url/(.*)\", StaticUrlHandler)]\n\n    def get_app_kwargs(self):\n        return dict(static_path=\"dummy\", static_handler_class=self.static_handler_class)\n\n    def test_serve(self):\n        response = self.fetch(\"/static/foo.42.txt\")\n        self.assertEqual(response.body, b\"bar\")\n\n    def test_static_url(self):\n        with ExpectLog(gen_log, \"Could not open static file\", required=False):\n            response = self.fetch(\"/static_url/foo.txt\")\n            self.assertEqual(response.body, b\"/static/foo.42.txt\")\n\n\nclass HostMatchingTest(WebTestCase):\n    class Handler(RequestHandler):\n        def initialize(self, reply):\n            self.reply = reply\n\n        def get(self):\n            self.write(self.reply)\n\n    def get_handlers(self):\n        return [(\"/foo\", HostMatchingTest.Handler, {\"reply\": \"wildcard\"})]\n\n    def test_host_matching(self):\n        self.app.add_handlers(\n            \"www.example.com\", [(\"/foo\", HostMatchingTest.Handler, {\"reply\": \"[0]\"})]\n        )\n        self.app.add_handlers(\n            r\"www\\.example\\.com\", [(\"/bar\", HostMatchingTest.Handler, {\"reply\": \"[1]\"})]\n        )\n        self.app.add_handlers(\n            \"www.example.com\", [(\"/baz\", HostMatchingTest.Handler, {\"reply\": \"[2]\"})]\n        )\n        self.app.add_handlers(\n            \"www.e.*e.com\", [(\"/baz\", HostMatchingTest.Handler, {\"reply\": \"[3]\"})]\n        )\n\n        response = self.fetch(\"/foo\")\n        self.assertEqual(response.body, b\"wildcard\")\n        response = self.fetch(\"/bar\")\n        self.assertEqual(response.code, 404)\n        response = self.fetch(\"/baz\")\n        self.assertEqual(response.code, 404)\n\n        response = self.fetch(\"/foo\", headers={\"Host\": \"www.example.com\"})\n        self.assertEqual(response.body, b\"[0]\")\n        response = self.fetch(\"/bar\", headers={\"Host\": \"www.example.com\"})\n        self.assertEqual(response.body, b\"[1]\")\n        response = self.fetch(\"/baz\", headers={\"Host\": \"www.example.com\"})\n        self.assertEqual(response.body, b\"[2]\")\n        response = self.fetch(\"/baz\", headers={\"Host\": \"www.exe.com\"})\n        self.assertEqual(response.body, b\"[3]\")\n\n\nclass DefaultHostMatchingTest(WebTestCase):\n    def get_handlers(self):\n        return []\n\n    def get_app_kwargs(self):\n        return {\"default_host\": \"www.example.com\"}\n\n    def test_default_host_matching(self):\n        self.app.add_handlers(\n            \"www.example.com\", [(\"/foo\", HostMatchingTest.Handler, {\"reply\": \"[0]\"})]\n        )\n        self.app.add_handlers(\n            r\"www\\.example\\.com\", [(\"/bar\", HostMatchingTest.Handler, {\"reply\": \"[1]\"})]\n        )\n        self.app.add_handlers(\n            \"www.test.com\", [(\"/baz\", HostMatchingTest.Handler, {\"reply\": \"[2]\"})]\n        )\n\n        response = self.fetch(\"/foo\")\n        self.assertEqual(response.body, b\"[0]\")\n        response = self.fetch(\"/bar\")\n        self.assertEqual(response.body, b\"[1]\")\n        response = self.fetch(\"/baz\")\n        self.assertEqual(response.code, 404)\n\n        response = self.fetch(\"/foo\", headers={\"X-Real-Ip\": \"127.0.0.1\"})\n        self.assertEqual(response.code, 404)\n\n        self.app.default_host = \"www.test.com\"\n\n        response = self.fetch(\"/baz\")\n        self.assertEqual(response.body, b\"[2]\")\n\n\nclass NamedURLSpecGroupsTest(WebTestCase):\n    def get_handlers(self):\n        class EchoHandler(RequestHandler):\n            def get(self, path):\n                self.write(path)\n\n        return [\n            (\"/str/(?P<path>.*)\", EchoHandler),\n            (\"/unicode/(?P<path>.*)\", EchoHandler),\n        ]\n\n    def test_named_urlspec_groups(self):\n        response = self.fetch(\"/str/foo\")\n        self.assertEqual(response.body, b\"foo\")\n\n        response = self.fetch(\"/unicode/bar\")\n        self.assertEqual(response.body, b\"bar\")\n\n\nclass ClearHeaderTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            self.set_header(\"h1\", \"foo\")\n            self.set_header(\"h2\", \"bar\")\n            self.clear_header(\"h1\")\n            self.clear_header(\"nonexistent\")\n\n    def test_clear_header(self):\n        response = self.fetch(\"/\")\n        self.assertNotIn(\"h1\", response.headers)\n        self.assertEqual(response.headers[\"h2\"], \"bar\")\n\n\nclass Header204Test(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            self.set_status(204)\n            self.finish()\n\n    def test_204_headers(self):\n        response = self.fetch(\"/\")\n        self.assertEqual(response.code, 204)\n        self.assertNotIn(\"Content-Length\", response.headers)\n        self.assertNotIn(\"Transfer-Encoding\", response.headers)\n\n\nclass Header304Test(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            self.set_header(\"Content-Language\", \"en_US\")\n            self.write(\"hello\")\n\n    def test_304_headers(self):\n        response1 = self.fetch(\"/\")\n        self.assertEqual(response1.headers[\"Content-Length\"], \"5\")\n        self.assertEqual(response1.headers[\"Content-Language\"], \"en_US\")\n\n        response2 = self.fetch(\n            \"/\", headers={\"If-None-Match\": response1.headers[\"Etag\"]}\n        )\n        self.assertEqual(response2.code, 304)\n        self.assertNotIn(\"Content-Length\", response2.headers)\n        self.assertNotIn(\"Content-Language\", response2.headers)\n        # Not an entity header, but should not be added to 304s by chunking\n        self.assertNotIn(\"Transfer-Encoding\", response2.headers)\n\n\nclass StatusReasonTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            reason = self.request.arguments.get(\"reason\", [])\n            raise HTTPError(\n                int(self.get_argument(\"code\")),\n                reason=to_unicode(reason[0]) if reason else None,\n            )\n\n    def get_http_client(self):\n        # simple_httpclient only: curl doesn't expose the reason string\n        return SimpleAsyncHTTPClient()\n\n    def test_status(self):\n        response = self.fetch(\"/?code=304\")\n        self.assertEqual(response.code, 304)\n        self.assertEqual(response.reason, \"Not Modified\")\n        response = self.fetch(\"/?code=304&reason=Foo\")\n        self.assertEqual(response.code, 304)\n        self.assertEqual(response.reason, \"Foo\")\n        response = self.fetch(\"/?code=682&reason=Bar\")\n        self.assertEqual(response.code, 682)\n        self.assertEqual(response.reason, \"Bar\")\n        response = self.fetch(\"/?code=682\")\n        self.assertEqual(response.code, 682)\n        self.assertEqual(response.reason, \"Unknown\")\n\n    def test_header_injection(self):\n        response = self.fetch(\"/?code=200&reason=OK%0D%0AX-Injection:injected\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.reason, \"Unknown\")\n        self.assertNotIn(\"X-Injection\", response.headers)\n\n    def test_reason_xss(self):\n        response = self.fetch(\"/?code=400&reason=<script>alert(1)</script>\")\n        self.assertEqual(response.code, 400)\n        self.assertEqual(response.reason, \"Unknown\")\n        self.assertNotIn(b\"script\", response.body)\n        self.assertIn(b\"Unknown\", response.body)\n\n\nclass DateHeaderTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            self.write(\"hello\")\n\n    def test_date_header(self):\n        response = self.fetch(\"/\")\n        header_date = email.utils.parsedate_to_datetime(response.headers[\"Date\"])\n        self.assertLess(\n            header_date - datetime.datetime.now(datetime.timezone.utc),\n            datetime.timedelta(seconds=2),\n        )\n\n\nclass RaiseWithReasonTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            raise HTTPError(682, reason=\"Foo\")\n\n    def get_http_client(self):\n        # simple_httpclient only: curl doesn't expose the reason string\n        return SimpleAsyncHTTPClient()\n\n    def test_raise_with_reason(self):\n        response = self.fetch(\"/\")\n        self.assertEqual(response.code, 682)\n        self.assertEqual(response.reason, \"Foo\")\n        self.assertIn(b\"682: Foo\", response.body)\n\n    def test_httperror_str(self):\n        self.assertEqual(str(HTTPError(682, reason=\"Foo\")), \"HTTP 682: Foo\")\n\n    def test_httperror_str_from_httputil(self):\n        self.assertEqual(str(HTTPError(682)), \"HTTP 682: Unknown\")\n\n\nclass ErrorHandlerXSRFTest(WebTestCase):\n    def get_handlers(self):\n        # note that if the handlers list is empty we get the default_host\n        # redirect fallback instead of a 404, so test with both an\n        # explicitly defined error handler and an implicit 404.\n        return [(\"/error\", ErrorHandler, dict(status_code=417))]\n\n    def get_app_kwargs(self):\n        return dict(xsrf_cookies=True)\n\n    def test_error_xsrf(self):\n        response = self.fetch(\"/error\", method=\"POST\", body=\"\")\n        self.assertEqual(response.code, 417)\n\n    def test_404_xsrf(self):\n        response = self.fetch(\"/404\", method=\"POST\", body=\"\")\n        self.assertEqual(response.code, 404)\n\n\nclass GzipTestCase(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            for v in self.get_arguments(\"vary\"):\n                self.add_header(\"Vary\", v)\n            # Must write at least MIN_LENGTH bytes to activate compression.\n            self.write(\"hello world\" + (\"!\" * GZipContentEncoding.MIN_LENGTH))\n\n    def get_app_kwargs(self):\n        return dict(\n            gzip=True, static_path=os.path.join(os.path.dirname(__file__), \"static\")\n        )\n\n    def assert_compressed(self, response):\n        # simple_httpclient renames the content-encoding header;\n        # curl_httpclient doesn't.\n        self.assertEqual(\n            response.headers.get(\n                \"Content-Encoding\", response.headers.get(\"X-Consumed-Content-Encoding\")\n            ),\n            \"gzip\",\n        )\n\n    def test_gzip(self):\n        response = self.fetch(\"/\")\n        self.assert_compressed(response)\n        self.assertEqual(response.headers[\"Vary\"], \"Accept-Encoding\")\n\n    def test_gzip_static(self):\n        # The streaming responses in StaticFileHandler have subtle\n        # interactions with the gzip output so test this case separately.\n        response = self.fetch(\"/robots.txt\")\n        self.assert_compressed(response)\n        self.assertEqual(response.headers[\"Vary\"], \"Accept-Encoding\")\n\n    def test_gzip_not_requested(self):\n        response = self.fetch(\"/\", use_gzip=False)\n        self.assertNotIn(\"Content-Encoding\", response.headers)\n        self.assertEqual(response.headers[\"Vary\"], \"Accept-Encoding\")\n\n    def test_vary_already_present(self):\n        response = self.fetch(\"/?vary=Accept-Language\")\n        self.assert_compressed(response)\n        self.assertEqual(\n            [s.strip() for s in response.headers[\"Vary\"].split(\",\")],\n            [\"Accept-Language\", \"Accept-Encoding\"],\n        )\n\n    def test_vary_already_present_multiple(self):\n        # Regression test for https://github.com/tornadoweb/tornado/issues/1670\n        response = self.fetch(\"/?vary=Accept-Language&vary=Cookie\")\n        self.assert_compressed(response)\n        self.assertEqual(\n            [s.strip() for s in response.headers[\"Vary\"].split(\",\")],\n            [\"Accept-Language\", \"Cookie\", \"Accept-Encoding\"],\n        )\n\n\nclass PathArgsInPrepareTest(WebTestCase):\n    class Handler(RequestHandler):\n        def prepare(self):\n            self.write(dict(args=self.path_args, kwargs=self.path_kwargs))\n\n        def get(self, path):\n            assert path == \"foo\"\n            self.finish()\n\n    def get_handlers(self):\n        return [(\"/pos/(.*)\", self.Handler), (\"/kw/(?P<path>.*)\", self.Handler)]\n\n    def test_pos(self):\n        response = self.fetch(\"/pos/foo\")\n        response.rethrow()\n        data = json_decode(response.body)\n        self.assertEqual(data, {\"args\": [\"foo\"], \"kwargs\": {}})\n\n    def test_kw(self):\n        response = self.fetch(\"/kw/foo\")\n        response.rethrow()\n        data = json_decode(response.body)\n        self.assertEqual(data, {\"args\": [], \"kwargs\": {\"path\": \"foo\"}})\n\n\nclass ClearAllCookiesTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            self.clear_all_cookies()\n            self.write(\"ok\")\n\n    def test_clear_all_cookies(self):\n        response = self.fetch(\"/\", headers={\"Cookie\": \"foo=bar; baz=xyzzy\"})\n        set_cookies = sorted(response.headers.get_list(\"Set-Cookie\"))\n        # Python 3.5 sends 'baz=\"\";'; older versions use 'baz=;'\n        self.assertTrue(set_cookies[0].startswith('baz=\"\";'))\n        self.assertTrue(set_cookies[1].startswith('foo=\"\";'))\n\n\nclass PermissionError(Exception):\n    pass\n\n\nclass ExceptionHandlerTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            exc = self.get_argument(\"exc\")\n            if exc == \"http\":\n                raise HTTPError(410, \"no longer here\")\n            elif exc == \"zero\":\n                1 / 0\n            elif exc == \"permission\":\n                raise PermissionError(\"not allowed\")\n\n        def write_error(self, status_code, **kwargs):\n            if \"exc_info\" in kwargs:\n                typ, value, tb = kwargs[\"exc_info\"]\n                if isinstance(value, PermissionError):\n                    self.set_status(403)\n                    self.write(\"PermissionError\")\n                    return\n            RequestHandler.write_error(self, status_code, **kwargs)\n\n        def log_exception(self, typ, value, tb):\n            if isinstance(value, PermissionError):\n                app_log.warning(\"custom logging for PermissionError: %s\", value.args[0])\n            else:\n                RequestHandler.log_exception(self, typ, value, tb)\n\n    def test_http_error(self):\n        # HTTPErrors are logged as warnings with no stack trace.\n        # TODO: extend ExpectLog to test this more precisely\n        with ExpectLog(gen_log, \".*no longer here\"):\n            response = self.fetch(\"/?exc=http\")\n            self.assertEqual(response.code, 410)\n\n    def test_unknown_error(self):\n        # Unknown errors are logged as errors with a stack trace.\n        with ExpectLog(app_log, \"Uncaught exception\"):\n            response = self.fetch(\"/?exc=zero\")\n            self.assertEqual(response.code, 500)\n\n    def test_known_error(self):\n        # log_exception can override logging behavior, and write_error\n        # can override the response.\n        with ExpectLog(app_log, \"custom logging for PermissionError: not allowed\"):\n            response = self.fetch(\"/?exc=permission\")\n            self.assertEqual(response.code, 403)\n\n\nclass BuggyLoggingTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            1 / 0\n\n        def log_exception(self, typ, value, tb):\n            1 / 0\n\n    def test_buggy_log_exception(self):\n        # Something gets logged even though the application's\n        # logger is broken.\n        with ExpectLog(app_log, \".*\"):\n            self.fetch(\"/\")\n\n\nclass UIMethodUIModuleTest(SimpleHandlerTestCase):\n    \"\"\"Test that UI methods and modules are created correctly and\n    associated with the handler.\n    \"\"\"\n\n    class Handler(RequestHandler):\n        def get(self):\n            self.render(\"foo.html\")\n\n        def value(self):\n            return self.get_argument(\"value\")\n\n    def get_app_kwargs(self):\n        def my_ui_method(handler, x):\n            return f\"In my_ui_method({x}) with handler value {handler.value()}.\"\n\n        class MyModule(UIModule):\n            def render(self, x):\n                return \"In MyModule({}) with handler value {}.\".format(\n                    x,\n                    typing.cast(UIMethodUIModuleTest.Handler, self.handler).value(),\n                )\n\n        loader = DictLoader(\n            {\"foo.html\": \"{{ my_ui_method(42) }} {% module MyModule(123) %}\"}\n        )\n        return dict(\n            template_loader=loader,\n            ui_methods={\"my_ui_method\": my_ui_method},\n            ui_modules={\"MyModule\": MyModule},\n        )\n\n    def tearDown(self):\n        super().tearDown()\n        # TODO: fix template loader caching so this isn't necessary.\n        RequestHandler._template_loaders.clear()\n\n    def test_ui_method(self):\n        response = self.fetch(\"/?value=asdf\")\n        self.assertEqual(\n            response.body,\n            b\"In my_ui_method(42) with handler value asdf. \"\n            b\"In MyModule(123) with handler value asdf.\",\n        )\n\n\nclass GetArgumentErrorTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            try:\n                self.get_argument(\"foo\")\n                self.write({})\n            except MissingArgumentError as e:\n                self.write({\"arg_name\": e.arg_name, \"log_message\": e.log_message})\n\n    def test_catch_error(self):\n        response = self.fetch(\"/\")\n        self.assertEqual(\n            json_decode(response.body),\n            {\"arg_name\": \"foo\", \"log_message\": \"Missing argument foo\"},\n        )\n\n\nclass SetLazyPropertiesTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def prepare(self):\n            self.current_user = \"Ben\"\n            self.locale = locale.get(\"en_US\")\n\n        def get_user_locale(self):\n            raise NotImplementedError()\n\n        def get_current_user(self):\n            raise NotImplementedError()\n\n        def get(self):\n            self.write(f\"Hello {self.current_user} ({self.locale.code})\")\n\n    def test_set_properties(self):\n        # Ensure that current_user can be assigned to normally for apps\n        # that want to forgo the lazy get_current_user property\n        response = self.fetch(\"/\")\n        self.assertEqual(response.body, b\"Hello Ben (en_US)\")\n\n\nclass GetCurrentUserTest(WebTestCase):\n    def get_app_kwargs(self):\n        class WithoutUserModule(UIModule):\n            def render(self):\n                return \"\"\n\n        class WithUserModule(UIModule):\n            def render(self):\n                return str(self.current_user)\n\n        loader = DictLoader(\n            {\n                \"without_user.html\": \"\",\n                \"with_user.html\": \"{{ current_user }}\",\n                \"without_user_module.html\": \"{% module WithoutUserModule() %}\",\n                \"with_user_module.html\": \"{% module WithUserModule() %}\",\n            }\n        )\n        return dict(\n            template_loader=loader,\n            ui_modules={\n                \"WithUserModule\": WithUserModule,\n                \"WithoutUserModule\": WithoutUserModule,\n            },\n        )\n\n    def tearDown(self):\n        super().tearDown()\n        RequestHandler._template_loaders.clear()\n\n    def get_handlers(self):\n        class CurrentUserHandler(RequestHandler):\n            def prepare(self):\n                self.has_loaded_current_user = False\n\n            def get_current_user(self):\n                self.has_loaded_current_user = True\n                return \"\"\n\n        class WithoutUserHandler(CurrentUserHandler):\n            def get(self):\n                self.render_string(\"without_user.html\")\n                self.finish(str(self.has_loaded_current_user))\n\n        class WithUserHandler(CurrentUserHandler):\n            def get(self):\n                self.render_string(\"with_user.html\")\n                self.finish(str(self.has_loaded_current_user))\n\n        class CurrentUserModuleHandler(CurrentUserHandler):\n            def get_template_namespace(self):\n                # If RequestHandler.get_template_namespace is called, then\n                # get_current_user is evaluated. Until #820 is fixed, this\n                # is a small hack to circumvent the issue.\n                return self.ui\n\n        class WithoutUserModuleHandler(CurrentUserModuleHandler):\n            def get(self):\n                self.render_string(\"without_user_module.html\")\n                self.finish(str(self.has_loaded_current_user))\n\n        class WithUserModuleHandler(CurrentUserModuleHandler):\n            def get(self):\n                self.render_string(\"with_user_module.html\")\n                self.finish(str(self.has_loaded_current_user))\n\n        return [\n            (\"/without_user\", WithoutUserHandler),\n            (\"/with_user\", WithUserHandler),\n            (\"/without_user_module\", WithoutUserModuleHandler),\n            (\"/with_user_module\", WithUserModuleHandler),\n        ]\n\n    @unittest.skip(\"needs fix\")\n    def test_get_current_user_is_lazy(self):\n        # TODO: Make this test pass. See #820.\n        response = self.fetch(\"/without_user\")\n        self.assertEqual(response.body, b\"False\")\n\n    def test_get_current_user_works(self):\n        response = self.fetch(\"/with_user\")\n        self.assertEqual(response.body, b\"True\")\n\n    def test_get_current_user_from_ui_module_is_lazy(self):\n        response = self.fetch(\"/without_user_module\")\n        self.assertEqual(response.body, b\"False\")\n\n    def test_get_current_user_from_ui_module_works(self):\n        response = self.fetch(\"/with_user_module\")\n        self.assertEqual(response.body, b\"True\")\n\n\nclass UnimplementedHTTPMethodsTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        pass\n\n    def test_unimplemented_standard_methods(self):\n        for method in [\"HEAD\", \"GET\", \"DELETE\", \"OPTIONS\"]:\n            response = self.fetch(\"/\", method=method)\n            self.assertEqual(response.code, 405)\n        for method in [\"POST\", \"PUT\"]:\n            response = self.fetch(\"/\", method=method, body=b\"\")\n            self.assertEqual(response.code, 405)\n\n\nclass UnimplementedNonStandardMethodsTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def other(self):\n            # Even though this method exists, it won't get called automatically\n            # because it is not in SUPPORTED_METHODS.\n            self.write(\"other\")\n\n    def test_unimplemented_patch(self):\n        # PATCH is recently standardized; Tornado supports it by default\n        # but wsgiref.validate doesn't like it.\n        response = self.fetch(\"/\", method=\"PATCH\", body=b\"\")\n        self.assertEqual(response.code, 405)\n\n    def test_unimplemented_other(self):\n        response = self.fetch(\"/\", method=\"OTHER\", allow_nonstandard_methods=True)\n        self.assertEqual(response.code, 405)\n\n\nclass AllHTTPMethodsTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def method(self):\n            assert self.request.method is not None\n            self.write(self.request.method)\n\n        get = delete = options = post = put = method  # type: ignore\n\n    def test_standard_methods(self):\n        response = self.fetch(\"/\", method=\"HEAD\")\n        self.assertEqual(response.body, b\"\")\n        for method in [\"GET\", \"DELETE\", \"OPTIONS\"]:\n            response = self.fetch(\"/\", method=method)\n            self.assertEqual(response.body, utf8(method))\n        for method in [\"POST\", \"PUT\"]:\n            response = self.fetch(\"/\", method=method, body=b\"\")\n            self.assertEqual(response.body, utf8(method))\n\n\nclass PatchMethodTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        SUPPORTED_METHODS = RequestHandler.SUPPORTED_METHODS + (  # type: ignore\n            \"OTHER\",\n        )\n\n        def patch(self):\n            self.write(\"patch\")\n\n        def other(self):\n            self.write(\"other\")\n\n    def test_patch(self):\n        response = self.fetch(\"/\", method=\"PATCH\", body=b\"\")\n        self.assertEqual(response.body, b\"patch\")\n\n    def test_other(self):\n        response = self.fetch(\"/\", method=\"OTHER\", allow_nonstandard_methods=True)\n        self.assertEqual(response.body, b\"other\")\n\n\nclass FinishInPrepareTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def prepare(self):\n            self.finish(\"done\")\n\n        def get(self):\n            # It's difficult to assert for certain that a method did not\n            # or will not be called in an asynchronous context, but this\n            # will be logged noisily if it is reached.\n            raise Exception(\"should not reach this method\")\n\n    def test_finish_in_prepare(self):\n        response = self.fetch(\"/\")\n        self.assertEqual(response.body, b\"done\")\n\n\nclass Default404Test(WebTestCase):\n    def get_handlers(self):\n        # If there are no handlers at all a default redirect handler gets added.\n        return [(\"/foo\", RequestHandler)]\n\n    def test_404(self):\n        response = self.fetch(\"/\")\n        self.assertEqual(response.code, 404)\n        self.assertEqual(\n            response.body,\n            b\"<html><title>404: Not Found</title>\"\n            b\"<body>404: Not Found</body></html>\",\n        )\n\n\nclass Custom404Test(WebTestCase):\n    def get_handlers(self):\n        return [(\"/foo\", RequestHandler)]\n\n    def get_app_kwargs(self):\n        class Custom404Handler(RequestHandler):\n            def get(self):\n                self.set_status(404)\n                self.write(\"custom 404 response\")\n\n        return dict(default_handler_class=Custom404Handler)\n\n    def test_404(self):\n        response = self.fetch(\"/\")\n        self.assertEqual(response.code, 404)\n        self.assertEqual(response.body, b\"custom 404 response\")\n\n\nclass DefaultHandlerArgumentsTest(WebTestCase):\n    def get_handlers(self):\n        return [(\"/foo\", RequestHandler)]\n\n    def get_app_kwargs(self):\n        return dict(\n            default_handler_class=ErrorHandler,\n            default_handler_args=dict(status_code=403),\n        )\n\n    def test_403(self):\n        response = self.fetch(\"/\")\n        self.assertEqual(response.code, 403)\n\n\nclass HandlerByNameTest(WebTestCase):\n    def get_handlers(self):\n        # All three are equivalent.\n        return [\n            (\"/hello1\", HelloHandler),\n            (\"/hello2\", \"tornado.test.web_test.HelloHandler\"),\n            url(\"/hello3\", \"tornado.test.web_test.HelloHandler\"),\n        ]\n\n    def test_handler_by_name(self):\n        resp = self.fetch(\"/hello1\")\n        self.assertEqual(resp.body, b\"hello\")\n        resp = self.fetch(\"/hello2\")\n        self.assertEqual(resp.body, b\"hello\")\n        resp = self.fetch(\"/hello3\")\n        self.assertEqual(resp.body, b\"hello\")\n\n\nclass StreamingRequestBodyTest(WebTestCase):\n    def get_handlers(self):\n        @stream_request_body\n        class StreamingBodyHandler(RequestHandler):\n            def initialize(self, test):\n                self.test = test\n\n            def prepare(self):\n                self.test.prepared.set_result(None)\n\n            def data_received(self, data):\n                self.test.data.set_result(data)\n\n            def get(self):\n                self.test.finished.set_result(None)\n                self.write({})\n\n        @stream_request_body\n        class EarlyReturnHandler(RequestHandler):\n            def prepare(self):\n                # If we finish the response in prepare, it won't continue to\n                # the (non-existent) data_received.\n                raise HTTPError(401)\n\n        @stream_request_body\n        class CloseDetectionHandler(RequestHandler):\n            def initialize(self, test):\n                self.test = test\n\n            def on_connection_close(self):\n                super().on_connection_close()\n                self.test.close_future.set_result(None)\n\n        return [\n            (\"/stream_body\", StreamingBodyHandler, dict(test=self)),\n            (\"/early_return\", EarlyReturnHandler),\n            (\"/close_detection\", CloseDetectionHandler, dict(test=self)),\n        ]\n\n    def connect(self, url, connection_close):\n        # Use a raw connection so we can control the sending of data.\n        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)\n        s.connect((\"127.0.0.1\", self.get_http_port()))\n        stream = IOStream(s)\n        stream.write(b\"GET \" + url + b\" HTTP/1.1\\r\\nHost: 127.0.0.1\\r\\n\")\n        if connection_close:\n            stream.write(b\"Connection: close\\r\\n\")\n        stream.write(b\"Transfer-Encoding: chunked\\r\\n\\r\\n\")\n        return stream\n\n    @gen_test\n    def test_streaming_body(self):\n        self.prepared: Future[None] = Future()\n        self.data: Future[bytes] = Future()\n        self.finished: Future[None] = Future()\n\n        stream = self.connect(b\"/stream_body\", connection_close=True)\n        yield self.prepared\n        stream.write(b\"4\\r\\nasdf\\r\\n\")\n        # Ensure the first chunk is received before we send the second.\n        data = yield self.data\n        self.assertEqual(data, b\"asdf\")\n        self.data = Future()\n        stream.write(b\"4\\r\\nqwer\\r\\n\")\n        data = yield self.data\n        self.assertEqual(data, b\"qwer\")\n        stream.write(b\"0\\r\\n\\r\\n\")\n        yield self.finished\n        data = yield stream.read_until_close()\n        # This would ideally use an HTTP1Connection to read the response.\n        self.assertTrue(data.endswith(b\"{}\"))\n        stream.close()\n\n    @gen_test\n    def test_early_return(self):\n        stream = self.connect(b\"/early_return\", connection_close=False)\n        data = yield stream.read_until_close()\n        self.assertTrue(data.startswith(b\"HTTP/1.1 401\"))\n\n    @gen_test\n    def test_early_return_with_data(self):\n        stream = self.connect(b\"/early_return\", connection_close=False)\n        stream.write(b\"4\\r\\nasdf\\r\\n\")\n        data = yield stream.read_until_close()\n        self.assertTrue(data.startswith(b\"HTTP/1.1 401\"))\n\n    @gen_test\n    def test_close_during_upload(self):\n        self.close_future: Future[None] = Future()\n        stream = self.connect(b\"/close_detection\", connection_close=False)\n        stream.close()\n        yield self.close_future\n\n\n# Each method in this handler returns a yieldable object and yields to the\n# IOLoop so the future is not immediately ready.  Ensure that the\n# yieldables are respected and no method is called before the previous\n# one has completed.\n@stream_request_body\nclass BaseFlowControlHandler(RequestHandler):\n    def initialize(self, test):\n        self.test = test\n        self.method = None\n        self.methods: list[str] = []\n\n    @contextlib.contextmanager\n    def in_method(self, method):\n        if self.method is not None:\n            self.test.fail(f\"entered method {method} while in {self.method}\")\n        self.method = method\n        self.methods.append(method)\n        try:\n            yield\n        finally:\n            self.method = None\n\n    @gen.coroutine\n    def prepare(self):\n        # Note that asynchronous prepare() does not block data_received,\n        # so we don't use in_method here.\n        self.methods.append(\"prepare\")\n        yield gen.moment\n\n    @gen.coroutine\n    def post(self):\n        with self.in_method(\"post\"):\n            yield gen.moment\n        self.write(dict(methods=self.methods))\n\n\nclass BaseStreamingRequestFlowControlTest:\n    def get_httpserver_options(self):\n        # Use a small chunk size so flow control is relevant even though\n        # all the data arrives at once.\n        return dict(chunk_size=10, decompress_request=True)\n\n    def get_http_client(self):\n        # simple_httpclient only: curl doesn't support body_producer.\n        return SimpleAsyncHTTPClient()\n\n    # Test all the slightly different code paths for fixed, chunked, etc bodies.\n    def test_flow_control_fixed_body(self: typing.Any):\n        response = self.fetch(\"/\", body=\"abcdefghijklmnopqrstuvwxyz\", method=\"POST\")\n        response.rethrow()\n        self.assertEqual(\n            json_decode(response.body),\n            dict(\n                methods=[\n                    \"prepare\",\n                    \"data_received\",\n                    \"data_received\",\n                    \"data_received\",\n                    \"post\",\n                ]\n            ),\n        )\n\n    def test_flow_control_chunked_body(self: typing.Any):\n        chunks = [b\"abcd\", b\"efgh\", b\"ijkl\"]\n\n        @gen.coroutine\n        def body_producer(write):\n            for i in chunks:\n                yield write(i)\n\n        response = self.fetch(\"/\", body_producer=body_producer, method=\"POST\")\n        response.rethrow()\n        self.assertEqual(\n            json_decode(response.body),\n            dict(\n                methods=[\n                    \"prepare\",\n                    \"data_received\",\n                    \"data_received\",\n                    \"data_received\",\n                    \"post\",\n                ]\n            ),\n        )\n\n    def test_flow_control_compressed_body(self: typing.Any):\n        bytesio = BytesIO()\n        gzip_file = gzip.GzipFile(mode=\"w\", fileobj=bytesio)\n        gzip_file.write(b\"abcdefghijklmnopqrstuvwxyz\")\n        gzip_file.close()\n        compressed_body = bytesio.getvalue()\n        response = self.fetch(\n            \"/\",\n            body=compressed_body,\n            method=\"POST\",\n            headers={\"Content-Encoding\": \"gzip\"},\n        )\n        response.rethrow()\n        self.assertEqual(\n            json_decode(response.body),\n            dict(\n                methods=[\n                    \"prepare\",\n                    \"data_received\",\n                    \"data_received\",\n                    \"data_received\",\n                    \"post\",\n                ]\n            ),\n        )\n\n\nclass DecoratedStreamingRequestFlowControlTest(\n    BaseStreamingRequestFlowControlTest, WebTestCase\n):\n    def get_handlers(self):\n        class DecoratedFlowControlHandler(BaseFlowControlHandler):\n            @gen.coroutine\n            def data_received(self, data):\n                with self.in_method(\"data_received\"):\n                    yield gen.moment\n\n        return [(\"/\", DecoratedFlowControlHandler, dict(test=self))]\n\n\nclass NativeStreamingRequestFlowControlTest(\n    BaseStreamingRequestFlowControlTest, WebTestCase\n):\n    def get_handlers(self):\n        class NativeFlowControlHandler(BaseFlowControlHandler):\n            async def data_received(self, data):\n                with self.in_method(\"data_received\"):\n                    import asyncio\n\n                    await asyncio.sleep(0)\n\n        return [(\"/\", NativeFlowControlHandler, dict(test=self))]\n\n\nclass IncorrectContentLengthTest(SimpleHandlerTestCase):\n    def get_handlers(self):\n        test = self\n        self.server_error = None\n\n        # Manually set a content-length that doesn't match the actual content.\n        class TooHigh(RequestHandler):\n            def get(self):\n                self.set_header(\"Content-Length\", \"42\")\n                try:\n                    self.finish(\"ok\")\n                except Exception as e:\n                    test.server_error = e\n                    raise\n\n        class TooLow(RequestHandler):\n            def get(self):\n                self.set_header(\"Content-Length\", \"2\")\n                try:\n                    self.finish(\"hello\")\n                except Exception as e:\n                    test.server_error = e\n                    raise\n\n        return [(\"/high\", TooHigh), (\"/low\", TooLow)]\n\n    def test_content_length_too_high(self):\n        # When the content-length is too high, the connection is simply\n        # closed without completing the response.  An error is logged on\n        # the server.\n        with ExpectLog(app_log, \"(Uncaught exception|Exception in callback)\"):\n            with ExpectLog(\n                gen_log,\n                \"(Cannot send error response after headers written\"\n                \"|Failed to flush partial response)\",\n            ):\n                with self.assertRaises(HTTPClientError):\n                    self.fetch(\"/high\", raise_error=True)\n        self.assertEqual(\n            str(self.server_error), \"Tried to write 40 bytes less than Content-Length\"\n        )\n\n    def test_content_length_too_low(self):\n        # When the content-length is too low, the connection is closed\n        # without writing the last chunk, so the client never sees the request\n        # complete (which would be a framing error).\n        with ExpectLog(app_log, \"(Uncaught exception|Exception in callback)\"):\n            with ExpectLog(\n                gen_log,\n                \"(Cannot send error response after headers written\"\n                \"|Failed to flush partial response)\",\n            ):\n                with self.assertRaises(HTTPClientError):\n                    self.fetch(\"/low\", raise_error=True)\n        self.assertEqual(\n            str(self.server_error), \"Tried to write more data than Content-Length\"\n        )\n\n\nclass ClientCloseTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            if self.request.version.startswith(\"HTTP/1\"):\n                # Simulate a connection closed by the client during\n                # request processing.  The client will see an error, but the\n                # server should respond gracefully (without logging errors\n                # because we were unable to write out as many bytes as\n                # Content-Length said we would)\n                self.request.connection.stream.close()  # type: ignore\n                self.write(\"hello\")\n            else:\n                # TODO: add a HTTP2-compatible version of this test.\n                self.write(\"requires HTTP/1.x\")\n\n    def test_client_close(self):\n        with self.assertRaises((HTTPClientError, unittest.SkipTest)):  # type: ignore\n            response = self.fetch(\"/\", raise_error=True)\n            if response.body == b\"requires HTTP/1.x\":\n                self.skipTest(\"requires HTTP/1.x\")\n            self.assertEqual(response.code, 599)\n\n\nclass SignedValueTest(unittest.TestCase):\n    SECRET = \"It's a secret to everybody\"\n    SECRET_DICT = {0: \"asdfbasdf\", 1: \"12312312\", 2: \"2342342\"}\n\n    def past(self):\n        return self.present() - 86400 * 32\n\n    def present(self):\n        return 1300000000\n\n    def test_known_values(self):\n        signed_v1 = create_signed_value(\n            SignedValueTest.SECRET, \"key\", \"value\", version=1, clock=self.present\n        )\n        self.assertEqual(\n            signed_v1, b\"dmFsdWU=|1300000000|31c934969f53e48164c50768b40cbd7e2daaaa4f\"\n        )\n\n        signed_v2 = create_signed_value(\n            SignedValueTest.SECRET, \"key\", \"value\", version=2, clock=self.present\n        )\n        self.assertEqual(\n            signed_v2,\n            b\"2|1:0|10:1300000000|3:key|8:dmFsdWU=|\"\n            b\"3d4e60b996ff9c5d5788e333a0cba6f238a22c6c0f94788870e1a9ecd482e152\",\n        )\n\n        signed_default = create_signed_value(\n            SignedValueTest.SECRET, \"key\", \"value\", clock=self.present\n        )\n        self.assertEqual(signed_default, signed_v2)\n\n        decoded_v1 = decode_signed_value(\n            SignedValueTest.SECRET, \"key\", signed_v1, min_version=1, clock=self.present\n        )\n        self.assertEqual(decoded_v1, b\"value\")\n\n        decoded_v2 = decode_signed_value(\n            SignedValueTest.SECRET, \"key\", signed_v2, min_version=2, clock=self.present\n        )\n        self.assertEqual(decoded_v2, b\"value\")\n\n    def test_name_swap(self):\n        signed1 = create_signed_value(\n            SignedValueTest.SECRET, \"key1\", \"value\", clock=self.present\n        )\n        signed2 = create_signed_value(\n            SignedValueTest.SECRET, \"key2\", \"value\", clock=self.present\n        )\n        # Try decoding each string with the other's \"name\"\n        decoded1 = decode_signed_value(\n            SignedValueTest.SECRET, \"key2\", signed1, clock=self.present\n        )\n        self.assertIsNone(decoded1)\n        decoded2 = decode_signed_value(\n            SignedValueTest.SECRET, \"key1\", signed2, clock=self.present\n        )\n        self.assertIsNone(decoded2)\n\n    def test_expired(self):\n        signed = create_signed_value(\n            SignedValueTest.SECRET, \"key1\", \"value\", clock=self.past\n        )\n        decoded_past = decode_signed_value(\n            SignedValueTest.SECRET, \"key1\", signed, clock=self.past\n        )\n        self.assertEqual(decoded_past, b\"value\")\n        decoded_present = decode_signed_value(\n            SignedValueTest.SECRET, \"key1\", signed, clock=self.present\n        )\n        self.assertIsNone(decoded_present)\n\n    def test_payload_tampering(self):\n        # These cookies are variants of the one in test_known_values.\n        sig = \"3d4e60b996ff9c5d5788e333a0cba6f238a22c6c0f94788870e1a9ecd482e152\"\n\n        def validate(prefix):\n            return b\"value\" == decode_signed_value(\n                SignedValueTest.SECRET, \"key\", prefix + sig, clock=self.present\n            )\n\n        self.assertTrue(validate(\"2|1:0|10:1300000000|3:key|8:dmFsdWU=|\"))\n        # Change key version\n        self.assertFalse(validate(\"2|1:1|10:1300000000|3:key|8:dmFsdWU=|\"))\n        # length mismatch (field too short)\n        self.assertFalse(validate(\"2|1:0|10:130000000|3:key|8:dmFsdWU=|\"))\n        # length mismatch (field too long)\n        self.assertFalse(validate(\"2|1:0|10:1300000000|3:keey|8:dmFsdWU=|\"))\n\n    def test_signature_tampering(self):\n        prefix = \"2|1:0|10:1300000000|3:key|8:dmFsdWU=|\"\n\n        def validate(sig):\n            return b\"value\" == decode_signed_value(\n                SignedValueTest.SECRET, \"key\", prefix + sig, clock=self.present\n            )\n\n        self.assertTrue(\n            validate(\"3d4e60b996ff9c5d5788e333a0cba6f238a22c6c0f94788870e1a9ecd482e152\")\n        )\n        # All zeros\n        self.assertFalse(validate(\"0\" * 32))\n        # Change one character\n        self.assertFalse(\n            validate(\"4d4e60b996ff9c5d5788e333a0cba6f238a22c6c0f94788870e1a9ecd482e152\")\n        )\n        # Change another character\n        self.assertFalse(\n            validate(\"3d4e60b996ff9c5d5788e333a0cba6f238a22c6c0f94788870e1a9ecd482e153\")\n        )\n        # Truncate\n        self.assertFalse(\n            validate(\"3d4e60b996ff9c5d5788e333a0cba6f238a22c6c0f94788870e1a9ecd482e15\")\n        )\n        # Lengthen\n        self.assertFalse(\n            validate(\n                \"3d4e60b996ff9c5d5788e333a0cba6f238a22c6c0f94788870e1a9ecd482e1538\"\n            )\n        )\n\n    def test_non_ascii(self):\n        value = b\"\\xe9\"\n        signed = create_signed_value(\n            SignedValueTest.SECRET, \"key\", value, clock=self.present\n        )\n        decoded = decode_signed_value(\n            SignedValueTest.SECRET, \"key\", signed, clock=self.present\n        )\n        self.assertEqual(value, decoded)\n\n    def test_key_versioning_read_write_default_key(self):\n        value = b\"\\xe9\"\n        signed = create_signed_value(\n            SignedValueTest.SECRET_DICT, \"key\", value, clock=self.present, key_version=0\n        )\n        decoded = decode_signed_value(\n            SignedValueTest.SECRET_DICT, \"key\", signed, clock=self.present\n        )\n        self.assertEqual(value, decoded)\n\n    def test_key_versioning_read_write_non_default_key(self):\n        value = b\"\\xe9\"\n        signed = create_signed_value(\n            SignedValueTest.SECRET_DICT, \"key\", value, clock=self.present, key_version=1\n        )\n        decoded = decode_signed_value(\n            SignedValueTest.SECRET_DICT, \"key\", signed, clock=self.present\n        )\n        self.assertEqual(value, decoded)\n\n    def test_key_versioning_invalid_key(self):\n        value = b\"\\xe9\"\n        signed = create_signed_value(\n            SignedValueTest.SECRET_DICT, \"key\", value, clock=self.present, key_version=0\n        )\n        newkeys = SignedValueTest.SECRET_DICT.copy()\n        newkeys.pop(0)\n        decoded = decode_signed_value(newkeys, \"key\", signed, clock=self.present)\n        self.assertIsNone(decoded)\n\n    def test_key_version_retrieval(self):\n        value = b\"\\xe9\"\n        signed = create_signed_value(\n            SignedValueTest.SECRET_DICT, \"key\", value, clock=self.present, key_version=1\n        )\n        key_version = get_signature_key_version(signed)\n        self.assertEqual(1, key_version)\n\n\nclass XSRFTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            version = int(self.get_argument(\"version\", \"2\"))\n            # This would be a bad idea in a real app, but in this test\n            # it's fine.\n            self.settings[\"xsrf_cookie_version\"] = version\n            self.write(self.xsrf_token)\n\n        def post(self):\n            self.write(\"ok\")\n\n    def get_app_kwargs(self):\n        return dict(xsrf_cookies=True)\n\n    def setUp(self):\n        super().setUp()\n        self.xsrf_token = self.get_token()\n\n    def get_token(self, old_token=None, version=None):\n        if old_token is not None:\n            headers = self.cookie_headers(old_token)\n        else:\n            headers = None\n        response = self.fetch(\n            \"/\" if version is None else (\"/?version=%d\" % version), headers=headers\n        )\n        response.rethrow()\n        return native_str(response.body)\n\n    def cookie_headers(self, token=None):\n        if token is None:\n            token = self.xsrf_token\n        return {\"Cookie\": \"_xsrf=\" + token}\n\n    def test_xsrf_fail_no_token(self):\n        with ExpectLog(gen_log, \".*'_xsrf' argument missing\"):\n            response = self.fetch(\"/\", method=\"POST\", body=b\"\")\n        self.assertEqual(response.code, 403)\n\n    def test_xsrf_fail_body_no_cookie(self):\n        with ExpectLog(gen_log, \".*XSRF cookie does not match POST\"):\n            response = self.fetch(\n                \"/\",\n                method=\"POST\",\n                body=urllib.parse.urlencode(dict(_xsrf=self.xsrf_token)),\n            )\n        self.assertEqual(response.code, 403)\n\n    def test_xsrf_fail_argument_invalid_format(self):\n        with ExpectLog(gen_log, \".*'_xsrf' argument has invalid format\"):\n            response = self.fetch(\n                \"/\",\n                method=\"POST\",\n                headers=self.cookie_headers(),\n                body=urllib.parse.urlencode(dict(_xsrf=\"3|\")),\n            )\n        self.assertEqual(response.code, 403)\n\n    def test_xsrf_fail_cookie_invalid_format(self):\n        with ExpectLog(gen_log, \".*XSRF cookie does not match POST\"):\n            response = self.fetch(\n                \"/\",\n                method=\"POST\",\n                headers=self.cookie_headers(token=\"3|\"),\n                body=urllib.parse.urlencode(dict(_xsrf=self.xsrf_token)),\n            )\n        self.assertEqual(response.code, 403)\n\n    def test_xsrf_fail_cookie_no_body(self):\n        with ExpectLog(gen_log, \".*'_xsrf' argument missing\"):\n            response = self.fetch(\n                \"/\", method=\"POST\", body=b\"\", headers=self.cookie_headers()\n            )\n        self.assertEqual(response.code, 403)\n\n    def test_xsrf_success_short_token(self):\n        response = self.fetch(\n            \"/\",\n            method=\"POST\",\n            body=urllib.parse.urlencode(dict(_xsrf=\"deadbeef\")),\n            headers=self.cookie_headers(token=\"deadbeef\"),\n        )\n        self.assertEqual(response.code, 200)\n\n    def test_xsrf_success_non_hex_token(self):\n        response = self.fetch(\n            \"/\",\n            method=\"POST\",\n            body=urllib.parse.urlencode(dict(_xsrf=\"xoxo\")),\n            headers=self.cookie_headers(token=\"xoxo\"),\n        )\n        self.assertEqual(response.code, 200)\n\n    def test_xsrf_success_post_body(self):\n        response = self.fetch(\n            \"/\",\n            method=\"POST\",\n            body=urllib.parse.urlencode(dict(_xsrf=self.xsrf_token)),\n            headers=self.cookie_headers(),\n        )\n        self.assertEqual(response.code, 200)\n\n    def test_xsrf_success_query_string(self):\n        response = self.fetch(\n            \"/?\" + urllib.parse.urlencode(dict(_xsrf=self.xsrf_token)),\n            method=\"POST\",\n            body=b\"\",\n            headers=self.cookie_headers(),\n        )\n        self.assertEqual(response.code, 200)\n\n    def test_xsrf_success_header(self):\n        response = self.fetch(\n            \"/\",\n            method=\"POST\",\n            body=b\"\",\n            headers=dict(\n                {\"X-Xsrftoken\": self.xsrf_token},  # type: ignore\n                **self.cookie_headers(),\n            ),\n        )\n        self.assertEqual(response.code, 200)\n\n    def test_distinct_tokens(self):\n        # Every request gets a distinct token.\n        NUM_TOKENS = 10\n        tokens = set()\n        for i in range(NUM_TOKENS):\n            tokens.add(self.get_token())\n        self.assertEqual(len(tokens), NUM_TOKENS)\n\n    def test_cross_user(self):\n        token2 = self.get_token()\n        # Each token can be used to authenticate its own request.\n        for token in (self.xsrf_token, token2):\n            response = self.fetch(\n                \"/\",\n                method=\"POST\",\n                body=urllib.parse.urlencode(dict(_xsrf=token)),\n                headers=self.cookie_headers(token),\n            )\n            self.assertEqual(response.code, 200)\n        # Sending one in the cookie and the other in the body is not allowed.\n        for cookie_token, body_token in (\n            (self.xsrf_token, token2),\n            (token2, self.xsrf_token),\n        ):\n            with ExpectLog(gen_log, \".*XSRF cookie does not match POST\"):\n                response = self.fetch(\n                    \"/\",\n                    method=\"POST\",\n                    body=urllib.parse.urlencode(dict(_xsrf=body_token)),\n                    headers=self.cookie_headers(cookie_token),\n                )\n            self.assertEqual(response.code, 403)\n\n    def test_refresh_token(self):\n        token = self.xsrf_token\n        tokens_seen = {token}\n        # A user's token is stable over time.  Refreshing the page in one tab\n        # might update the cookie while an older tab still has the old cookie\n        # in its DOM.  Simulate this scenario by passing a constant token\n        # in the body and re-querying for the token.\n        for i in range(5):\n            token = self.get_token(token)\n            # Tokens are encoded uniquely each time\n            tokens_seen.add(token)\n            response = self.fetch(\n                \"/\",\n                method=\"POST\",\n                body=urllib.parse.urlencode(dict(_xsrf=self.xsrf_token)),\n                headers=self.cookie_headers(token),\n            )\n            self.assertEqual(response.code, 200)\n        self.assertEqual(len(tokens_seen), 6)\n\n    def test_versioning(self):\n        # Version 1 still produces distinct tokens per request.\n        self.assertNotEqual(self.get_token(version=1), self.get_token(version=1))\n\n        # Refreshed v1 tokens are all identical.\n        v1_token = self.get_token(version=1)\n        for i in range(5):\n            self.assertEqual(self.get_token(v1_token, version=1), v1_token)\n\n        # Upgrade to a v2 version of the same token\n        v2_token = self.get_token(v1_token)\n        self.assertNotEqual(v1_token, v2_token)\n        # Each v1 token can map to many v2 tokens.\n        self.assertNotEqual(v2_token, self.get_token(v1_token))\n\n        # The tokens are cross-compatible.\n        for cookie_token, body_token in ((v1_token, v2_token), (v2_token, v1_token)):\n            response = self.fetch(\n                \"/\",\n                method=\"POST\",\n                body=urllib.parse.urlencode(dict(_xsrf=body_token)),\n                headers=self.cookie_headers(cookie_token),\n            )\n            self.assertEqual(response.code, 200)\n\n\n# A subset of the previous test with a different cookie name\nclass XSRFCookieNameTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            self.write(self.xsrf_token)\n\n        def post(self):\n            self.write(\"ok\")\n\n    def get_app_kwargs(self):\n        return dict(\n            xsrf_cookies=True,\n            xsrf_cookie_name=\"__Host-xsrf\",\n            xsrf_cookie_kwargs={\"secure\": True},\n        )\n\n    def setUp(self):\n        super().setUp()\n        self.xsrf_token = self.get_token()\n\n    def get_token(self, old_token=None):\n        if old_token is not None:\n            headers = self.cookie_headers(old_token)\n        else:\n            headers = None\n        response = self.fetch(\"/\", headers=headers)\n        response.rethrow()\n        return native_str(response.body)\n\n    def cookie_headers(self, token=None):\n        if token is None:\n            token = self.xsrf_token\n        return {\"Cookie\": \"__Host-xsrf=\" + token}\n\n    def test_xsrf_fail_no_token(self):\n        with ExpectLog(gen_log, \".*'_xsrf' argument missing\"):\n            response = self.fetch(\"/\", method=\"POST\", body=b\"\")\n        self.assertEqual(response.code, 403)\n\n    def test_xsrf_fail_body_no_cookie(self):\n        with ExpectLog(gen_log, \".*XSRF cookie does not match POST\"):\n            response = self.fetch(\n                \"/\",\n                method=\"POST\",\n                body=urllib.parse.urlencode(dict(_xsrf=self.xsrf_token)),\n            )\n        self.assertEqual(response.code, 403)\n\n    def test_xsrf_success_post_body(self):\n        response = self.fetch(\n            \"/\",\n            method=\"POST\",\n            # Note that renaming the cookie doesn't rename the POST param\n            body=urllib.parse.urlencode(dict(_xsrf=self.xsrf_token)),\n            headers=self.cookie_headers(),\n        )\n        self.assertEqual(response.code, 200)\n\n\nclass XSRFCookieKwargsTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            self.write(self.xsrf_token)\n\n    def get_app_kwargs(self):\n        return dict(\n            xsrf_cookies=True, xsrf_cookie_kwargs=dict(httponly=True, expires_days=2)\n        )\n\n    def test_xsrf_httponly(self):\n        response = self.fetch(\"/\")\n        self.assertIn(\"httponly;\", response.headers[\"Set-Cookie\"].lower())\n        self.assertIn(\"expires=\", response.headers[\"Set-Cookie\"].lower())\n        header = response.headers.get(\"Set-Cookie\")\n        assert header is not None\n        match = re.match(\".*; expires=(?P<expires>.+);.*\", header)\n        assert match is not None\n\n        expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(\n            days=2\n        )\n        header_expires = email.utils.parsedate_to_datetime(match.groupdict()[\"expires\"])\n        if header_expires.tzinfo is None:\n            header_expires = header_expires.replace(tzinfo=datetime.timezone.utc)\n        self.assertTrue(abs((expires - header_expires).total_seconds()) < 10)\n\n\nclass FinishExceptionTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            self.set_status(401)\n            self.set_header(\"WWW-Authenticate\", 'Basic realm=\"something\"')\n            if self.get_argument(\"finish_value\", \"\"):\n                raise Finish(\"authentication required\")\n            else:\n                self.write(\"authentication required\")\n                raise Finish()\n\n    def test_finish_exception(self):\n        for u in [\"/\", \"/?finish_value=1\"]:\n            response = self.fetch(u)\n            self.assertEqual(response.code, 401)\n            self.assertEqual(\n                'Basic realm=\"something\"', response.headers.get(\"WWW-Authenticate\")\n            )\n            self.assertEqual(b\"authentication required\", response.body)\n\n\nclass DecoratorTest(WebTestCase):\n    def get_handlers(self):\n        class RemoveSlashHandler(RequestHandler):\n            @removeslash\n            def get(self):\n                pass\n\n        class AddSlashHandler(RequestHandler):\n            @addslash\n            def get(self):\n                pass\n\n        return [(\"/removeslash/\", RemoveSlashHandler), (\"/addslash\", AddSlashHandler)]\n\n    def test_removeslash(self):\n        response = self.fetch(\"/removeslash/\", follow_redirects=False)\n        self.assertEqual(response.code, 301)\n        self.assertEqual(response.headers[\"Location\"], \"/removeslash\")\n\n        response = self.fetch(\"/removeslash/?foo=bar\", follow_redirects=False)\n        self.assertEqual(response.code, 301)\n        self.assertEqual(response.headers[\"Location\"], \"/removeslash?foo=bar\")\n\n    def test_addslash(self):\n        response = self.fetch(\"/addslash\", follow_redirects=False)\n        self.assertEqual(response.code, 301)\n        self.assertEqual(response.headers[\"Location\"], \"/addslash/\")\n\n        response = self.fetch(\"/addslash?foo=bar\", follow_redirects=False)\n        self.assertEqual(response.code, 301)\n        self.assertEqual(response.headers[\"Location\"], \"/addslash/?foo=bar\")\n\n\nclass CacheTest(WebTestCase):\n    def get_handlers(self):\n        class EtagHandler(RequestHandler):\n            def get(self, computed_etag):\n                self.write(computed_etag)\n\n            def compute_etag(self):\n                return self._write_buffer[0]\n\n        return [(\"/etag/(.*)\", EtagHandler)]\n\n    def test_wildcard_etag(self):\n        computed_etag = '\"xyzzy\"'\n        etags = \"*\"\n        self._test_etag(computed_etag, etags, 304)\n\n    def test_strong_etag_match(self):\n        computed_etag = '\"xyzzy\"'\n        etags = '\"xyzzy\"'\n        self._test_etag(computed_etag, etags, 304)\n\n    def test_multiple_strong_etag_match(self):\n        computed_etag = '\"xyzzy1\"'\n        etags = '\"xyzzy1\", \"xyzzy2\"'\n        self._test_etag(computed_etag, etags, 304)\n\n    def test_strong_etag_not_match(self):\n        computed_etag = '\"xyzzy\"'\n        etags = '\"xyzzy1\"'\n        self._test_etag(computed_etag, etags, 200)\n\n    def test_multiple_strong_etag_not_match(self):\n        computed_etag = '\"xyzzy\"'\n        etags = '\"xyzzy1\", \"xyzzy2\"'\n        self._test_etag(computed_etag, etags, 200)\n\n    def test_weak_etag_match(self):\n        computed_etag = '\"xyzzy1\"'\n        etags = 'W/\"xyzzy1\"'\n        self._test_etag(computed_etag, etags, 304)\n\n    def test_multiple_weak_etag_match(self):\n        computed_etag = '\"xyzzy2\"'\n        etags = 'W/\"xyzzy1\", W/\"xyzzy2\"'\n        self._test_etag(computed_etag, etags, 304)\n\n    def test_weak_etag_not_match(self):\n        computed_etag = '\"xyzzy2\"'\n        etags = 'W/\"xyzzy1\"'\n        self._test_etag(computed_etag, etags, 200)\n\n    def test_multiple_weak_etag_not_match(self):\n        computed_etag = '\"xyzzy3\"'\n        etags = 'W/\"xyzzy1\", W/\"xyzzy2\"'\n        self._test_etag(computed_etag, etags, 200)\n\n    def _test_etag(self, computed_etag, etags, status_code):\n        response = self.fetch(\n            \"/etag/\" + computed_etag, headers={\"If-None-Match\": etags}\n        )\n        self.assertEqual(response.code, status_code)\n\n\nclass RequestSummaryTest(SimpleHandlerTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            # remote_ip is optional, although it's set by\n            # both HTTPServer and WSGIAdapter.\n            # Clobber it to make sure it doesn't break logging.\n            self.request.remote_ip = None\n            self.finish(self._request_summary())\n\n    def test_missing_remote_ip(self):\n        resp = self.fetch(\"/\")\n        self.assertEqual(resp.body, b\"GET / (None)\")\n\n\nclass HTTPErrorTest(unittest.TestCase):\n    def test_copy(self):\n        e = HTTPError(403, reason=\"Go away\")\n        e2 = copy.copy(e)\n        self.assertIsNot(e, e2)\n        self.assertEqual(e.status_code, e2.status_code)\n        self.assertEqual(e.reason, e2.reason)\n\n\nclass ApplicationTest(AsyncTestCase):\n    def test_listen(self):\n        app = Application([])\n        server = app.listen(0, address=\"127.0.0.1\")\n        server.stop()\n\n\nclass URLSpecReverseTest(unittest.TestCase):\n    def test_reverse(self):\n        self.assertEqual(\"/favicon.ico\", url(r\"/favicon\\.ico\", None).reverse())\n        self.assertEqual(\"/favicon.ico\", url(r\"^/favicon\\.ico$\", None).reverse())\n\n    def test_non_reversible(self):\n        # URLSpecs are non-reversible if they include non-constant\n        # regex features outside capturing groups. Currently, this is\n        # only strictly enforced for backslash-escaped character\n        # classes.\n        paths = [r\"^/api/v\\d+/foo/(\\w+)$\"]\n        for path in paths:\n            # A URLSpec can still be created even if it cannot be reversed.\n            url_spec = url(path, None)\n            try:\n                result = url_spec.reverse()\n                self.fail(\n                    \"did not get expected exception when reversing %s. \"\n                    \"result: %s\" % (path, result)\n                )\n            except ValueError:\n                pass\n\n    def test_reverse_arguments(self):\n        self.assertEqual(\n            \"/api/v1/foo/bar\", url(r\"^/api/v1/foo/(\\w+)$\", None).reverse(\"bar\")\n        )\n        self.assertEqual(\n            \"/api.v1/foo/5/icon.png\",\n            url(r\"/api\\.v1/foo/([0-9]+)/icon\\.png\", None).reverse(5),\n        )\n\n\nclass RedirectHandlerTest(WebTestCase):\n    def get_handlers(self):\n        return [\n            (\"/src\", WebRedirectHandler, {\"url\": \"/dst\"}),\n            (\"/src2\", WebRedirectHandler, {\"url\": \"/dst2?foo=bar\"}),\n            (r\"/(.*?)/(.*?)/(.*)\", WebRedirectHandler, {\"url\": \"/{1}/{0}/{2}\"}),\n        ]\n\n    def test_basic_redirect(self):\n        response = self.fetch(\"/src\", follow_redirects=False)\n        self.assertEqual(response.code, 301)\n        self.assertEqual(response.headers[\"Location\"], \"/dst\")\n\n    def test_redirect_with_argument(self):\n        response = self.fetch(\"/src?foo=bar\", follow_redirects=False)\n        self.assertEqual(response.code, 301)\n        self.assertEqual(response.headers[\"Location\"], \"/dst?foo=bar\")\n\n    def test_redirect_with_appending_argument(self):\n        response = self.fetch(\"/src2?foo2=bar2\", follow_redirects=False)\n        self.assertEqual(response.code, 301)\n        self.assertEqual(response.headers[\"Location\"], \"/dst2?foo=bar&foo2=bar2\")\n\n    def test_redirect_pattern(self):\n        response = self.fetch(\"/a/b/c\", follow_redirects=False)\n        self.assertEqual(response.code, 301)\n        self.assertEqual(response.headers[\"Location\"], \"/b/a/c\")\n\n\nclass AcceptLanguageTest(WebTestCase):\n    \"\"\"Test evaluation of Accept-Language header\"\"\"\n\n    def get_handlers(self):\n        locale.load_gettext_translations(\n            os.path.join(os.path.dirname(__file__), \"gettext_translations\"),\n            \"tornado_test\",\n        )\n\n        class AcceptLanguageHandler(RequestHandler):\n            def get(self):\n                self.set_header(\n                    \"Content-Language\", self.get_browser_locale().code.replace(\"_\", \"-\")\n                )\n                self.finish(b\"\")\n\n        return [\n            (\"/\", AcceptLanguageHandler),\n        ]\n\n    def test_accept_language(self):\n        response = self.fetch(\"/\", headers={\"Accept-Language\": \"fr-FR;q=0.9\"})\n        self.assertEqual(response.headers[\"Content-Language\"], \"fr-FR\")\n\n        response = self.fetch(\"/\", headers={\"Accept-Language\": \"fr-FR; q=0.9\"})\n        self.assertEqual(response.headers[\"Content-Language\"], \"fr-FR\")\n\n    def test_accept_language_ignore(self):\n        response = self.fetch(\"/\", headers={\"Accept-Language\": \"fr-FR;q=0\"})\n        self.assertEqual(response.headers[\"Content-Language\"], \"en-US\")\n\n    def test_accept_language_invalid(self):\n        response = self.fetch(\"/\", headers={\"Accept-Language\": \"fr-FR;q=-1\"})\n        self.assertEqual(response.headers[\"Content-Language\"], \"en-US\")\n"
  },
  {
    "path": "tornado/test/websocket_test.py",
    "content": "import asyncio\nimport contextlib\nimport datetime\nimport functools\nimport socket\nimport traceback\nimport typing\nimport unittest\n\nfrom tornado import gen\nfrom tornado.concurrent import Future\nfrom tornado.httpclient import HTTPError, HTTPRequest\nfrom tornado.locks import Event\nfrom tornado.log import app_log, gen_log\nfrom tornado.netutil import Resolver\nfrom tornado.simple_httpclient import SimpleAsyncHTTPClient\nfrom tornado.template import DictLoader\nfrom tornado.test.util import abstract_base_test, ignore_deprecation\nfrom tornado.testing import AsyncHTTPTestCase, ExpectLog, bind_unused_port, gen_test\nfrom tornado.web import Application, RequestHandler\n\ntry:\n    import tornado.websocket  # noqa: F401\n    from tornado.util import _websocket_mask_python\nexcept ImportError:\n    # The unittest module presents misleading errors on ImportError\n    # (it acts as if websocket_test could not be found, hiding the underlying\n    # error).  If we get an ImportError here (which could happen due to\n    # TORNADO_EXTENSION=1), print some extra information before failing.\n    traceback.print_exc()\n    raise\n\nfrom tornado.websocket import (\n    WebSocketClosedError,\n    WebSocketError,\n    WebSocketHandler,\n    websocket_connect,\n)\n\ntry:\n    from tornado import speedups\nexcept ImportError:\n    speedups = None  # type: ignore\n\n\nclass TestWebSocketHandler(WebSocketHandler):\n    \"\"\"Base class for testing handlers that exposes the on_close event.\n\n    This allows for tests to see the close code and reason on the\n    server side.\n\n    \"\"\"\n\n    def initialize(self, close_future=None, compression_options=None):\n        self.close_future = close_future\n        self.compression_options = compression_options\n\n    def get_compression_options(self):\n        return self.compression_options\n\n    def on_close(self):\n        if self.close_future is not None:\n            self.close_future.set_result((self.close_code, self.close_reason))\n\n\nclass EchoHandler(TestWebSocketHandler):\n    @gen.coroutine\n    def on_message(self, message):\n        try:\n            yield self.write_message(message, isinstance(message, bytes))\n        except asyncio.CancelledError:\n            pass\n        except WebSocketClosedError:\n            pass\n\n\nclass ErrorInOnMessageHandler(TestWebSocketHandler):\n    def on_message(self, message):\n        1 / 0\n\n\nclass HeaderHandler(TestWebSocketHandler):\n    def open(self):\n        methods_to_test = [\n            functools.partial(self.write, \"This should not work\"),\n            functools.partial(self.redirect, \"http://localhost/elsewhere\"),\n            functools.partial(self.set_header, \"X-Test\", \"\"),\n            functools.partial(self.set_cookie, \"Chocolate\", \"Chip\"),\n            functools.partial(self.set_status, 503),\n            self.flush,\n            self.finish,\n        ]\n        for method in methods_to_test:\n            try:\n                # In a websocket context, many RequestHandler methods\n                # raise RuntimeErrors.\n                method()  # type: ignore\n                raise Exception(\"did not get expected exception\")\n            except RuntimeError:\n                pass\n        self.write_message(self.request.headers.get(\"X-Test\", \"\"))\n\n\nclass HeaderEchoHandler(TestWebSocketHandler):\n    def set_default_headers(self):\n        self.set_header(\"X-Extra-Response-Header\", \"Extra-Response-Value\")\n\n    def prepare(self):\n        for k, v in self.request.headers.get_all():\n            if k.lower().startswith(\"x-test\"):\n                self.set_header(k, v)\n\n\nclass NonWebSocketHandler(RequestHandler):\n    def get(self):\n        self.write(\"ok\")\n\n\nclass RedirectHandler(RequestHandler):\n    def get(self):\n        self.redirect(\"/echo\")\n\n\nclass CloseReasonHandler(TestWebSocketHandler):\n    def open(self):\n        self.on_close_called = False\n        self.close(1001, \"goodbye\")\n\n\nclass AsyncPrepareHandler(TestWebSocketHandler):\n    @gen.coroutine\n    def prepare(self):\n        yield gen.moment\n\n    def on_message(self, message):\n        self.write_message(message)\n\n\nclass PathArgsHandler(TestWebSocketHandler):\n    def open(self, arg):\n        self.write_message(arg)\n\n\nclass CoroutineOnMessageHandler(TestWebSocketHandler):\n    def initialize(self, **kwargs):  # type: ignore[override]\n        super().initialize(**kwargs)\n        self.sleeping = 0\n\n    @gen.coroutine\n    def on_message(self, message):\n        if self.sleeping > 0:\n            self.write_message(\"another coroutine is already sleeping\")\n        self.sleeping += 1\n        yield gen.sleep(0.01)\n        self.sleeping -= 1\n        self.write_message(message)\n\n\nclass RenderMessageHandler(TestWebSocketHandler):\n    def on_message(self, message):\n        self.write_message(self.render_string(\"message.html\", message=message))\n\n\nclass SubprotocolHandler(TestWebSocketHandler):\n    def initialize(self, **kwargs):  # type: ignore[override]\n        super().initialize(**kwargs)\n        self.select_subprotocol_called = False\n\n    def select_subprotocol(self, subprotocols):\n        if self.select_subprotocol_called:\n            raise Exception(\"select_subprotocol called twice\")\n        self.select_subprotocol_called = True\n        if \"goodproto\" in subprotocols:\n            return \"goodproto\"\n        return None\n\n    def open(self):\n        if not self.select_subprotocol_called:\n            raise Exception(\"select_subprotocol not called\")\n        self.write_message(\"subprotocol=%s\" % self.selected_subprotocol)\n\n\nclass OpenCoroutineHandler(TestWebSocketHandler):\n    def initialize(self, test, **kwargs):  # type: ignore[override]\n        super().initialize(**kwargs)\n        self.test = test\n        self.open_finished = False\n\n    @gen.coroutine\n    def open(self):\n        yield self.test.message_sent.wait()\n        yield gen.sleep(0.010)\n        self.open_finished = True\n\n    def on_message(self, message):\n        if not self.open_finished:\n            raise Exception(\"on_message called before open finished\")\n        self.write_message(\"ok\")\n\n\nclass ErrorInOpenHandler(TestWebSocketHandler):\n    def open(self):\n        raise Exception(\"boom\")\n\n\nclass ErrorInAsyncOpenHandler(TestWebSocketHandler):\n    async def open(self):\n        await asyncio.sleep(0)\n        raise Exception(\"boom\")\n\n\nclass NoDelayHandler(TestWebSocketHandler):\n    def open(self):\n        self.set_nodelay(True)\n        self.write_message(\"hello\")\n\n\nclass WebSocketBaseTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super().setUp()\n        self.conns_to_close = []\n\n    def tearDown(self):\n        for conn in self.conns_to_close:\n            conn.close()\n        super().tearDown()\n\n    @gen.coroutine\n    def ws_connect(self, path, **kwargs):\n        ws = yield websocket_connect(\n            \"ws://127.0.0.1:%d%s\" % (self.get_http_port(), path), **kwargs\n        )\n        self.conns_to_close.append(ws)\n        raise gen.Return(ws)\n\n\nclass WebSocketTest(WebSocketBaseTestCase):\n    def get_app(self):\n        self.close_future: Future[None] = Future()\n        return Application(\n            [\n                (\"/echo\", EchoHandler, dict(close_future=self.close_future)),\n                (\"/non_ws\", NonWebSocketHandler),\n                (\"/redirect\", RedirectHandler),\n                (\"/header\", HeaderHandler, dict(close_future=self.close_future)),\n                (\n                    \"/header_echo\",\n                    HeaderEchoHandler,\n                    dict(close_future=self.close_future),\n                ),\n                (\n                    \"/close_reason\",\n                    CloseReasonHandler,\n                    dict(close_future=self.close_future),\n                ),\n                (\n                    \"/error_in_on_message\",\n                    ErrorInOnMessageHandler,\n                    dict(close_future=self.close_future),\n                ),\n                (\n                    \"/async_prepare\",\n                    AsyncPrepareHandler,\n                    dict(close_future=self.close_future),\n                ),\n                (\n                    \"/path_args/(.*)\",\n                    PathArgsHandler,\n                    dict(close_future=self.close_future),\n                ),\n                (\n                    \"/coroutine\",\n                    CoroutineOnMessageHandler,\n                    dict(close_future=self.close_future),\n                ),\n                (\"/render\", RenderMessageHandler, dict(close_future=self.close_future)),\n                (\n                    \"/subprotocol\",\n                    SubprotocolHandler,\n                    dict(close_future=self.close_future),\n                ),\n                (\n                    \"/open_coroutine\",\n                    OpenCoroutineHandler,\n                    dict(close_future=self.close_future, test=self),\n                ),\n                (\"/error_in_open\", ErrorInOpenHandler),\n                (\"/error_in_async_open\", ErrorInAsyncOpenHandler),\n                (\"/nodelay\", NoDelayHandler),\n            ],\n            template_loader=DictLoader({\"message.html\": \"<b>{{ message }}</b>\"}),\n        )\n\n    def get_http_client(self):\n        # These tests require HTTP/1; force the use of SimpleAsyncHTTPClient.\n        return SimpleAsyncHTTPClient()\n\n    def tearDown(self):\n        super().tearDown()\n        RequestHandler._template_loaders.clear()\n\n    def test_http_request(self):\n        # WS server, HTTP client.\n        response = self.fetch(\"/echo\")\n        self.assertEqual(response.code, 400)\n\n    def test_missing_websocket_key(self):\n        response = self.fetch(\n            \"/echo\",\n            headers={\n                \"Connection\": \"Upgrade\",\n                \"Upgrade\": \"WebSocket\",\n                \"Sec-WebSocket-Version\": \"13\",\n            },\n        )\n        self.assertEqual(response.code, 400)\n\n    def test_bad_websocket_version(self):\n        response = self.fetch(\n            \"/echo\",\n            headers={\n                \"Connection\": \"Upgrade\",\n                \"Upgrade\": \"WebSocket\",\n                \"Sec-WebSocket-Version\": \"12\",\n            },\n        )\n        self.assertEqual(response.code, 426)\n\n    @gen_test\n    def test_websocket_gen(self):\n        ws = yield self.ws_connect(\"/echo\")\n        yield ws.write_message(\"hello\")\n        response = yield ws.read_message()\n        self.assertEqual(response, \"hello\")\n\n    def test_websocket_callbacks(self):\n        with ignore_deprecation():\n            websocket_connect(\n                \"ws://127.0.0.1:%d/echo\" % self.get_http_port(), callback=self.stop\n            )\n        ws = self.wait().result()\n        ws.write_message(\"hello\")\n        ws.read_message(self.stop)\n        response = self.wait().result()\n        self.assertEqual(response, \"hello\")\n        self.close_future.add_done_callback(lambda f: self.stop())\n        ws.close()\n        self.wait()\n\n    @gen_test\n    def test_binary_message(self):\n        ws = yield self.ws_connect(\"/echo\")\n        ws.write_message(b\"hello \\xe9\", binary=True)\n        response = yield ws.read_message()\n        self.assertEqual(response, b\"hello \\xe9\")\n\n    @gen_test\n    def test_unicode_message(self):\n        ws = yield self.ws_connect(\"/echo\")\n        ws.write_message(\"hello \\u00e9\")\n        response = yield ws.read_message()\n        self.assertEqual(response, \"hello \\u00e9\")\n\n    @gen_test\n    def test_error_in_closed_client_write_message(self):\n        ws = yield self.ws_connect(\"/echo\")\n        ws.close()\n        with self.assertRaises(WebSocketClosedError):\n            ws.write_message(\"hello \\u00e9\")\n\n    @gen_test\n    def test_render_message(self):\n        ws = yield self.ws_connect(\"/render\")\n        ws.write_message(\"hello\")\n        response = yield ws.read_message()\n        self.assertEqual(response, \"<b>hello</b>\")\n\n    @gen_test\n    def test_error_in_on_message(self):\n        ws = yield self.ws_connect(\"/error_in_on_message\")\n        ws.write_message(\"hello\")\n        with ExpectLog(app_log, \"Uncaught exception\"):\n            response = yield ws.read_message()\n        self.assertIsNone(response)\n\n    @gen_test\n    def test_websocket_http_fail(self):\n        with self.assertRaises(HTTPError) as cm:\n            yield self.ws_connect(\"/notfound\")\n        self.assertEqual(cm.exception.code, 404)\n\n    @gen_test\n    def test_websocket_http_success(self):\n        with self.assertRaises(WebSocketError):\n            yield self.ws_connect(\"/non_ws\")\n\n    @gen_test\n    def test_websocket_http_redirect(self):\n        with self.assertRaises(HTTPError):\n            yield self.ws_connect(\"/redirect\")\n\n    @gen_test\n    def test_websocket_network_fail(self):\n        sock, port = bind_unused_port()\n        sock.close()\n        with self.assertRaises(IOError):\n            with ExpectLog(gen_log, \".*\", required=False):\n                yield websocket_connect(\n                    \"ws://127.0.0.1:%d/\" % port, connect_timeout=3600\n                )\n\n    @gen_test\n    def test_websocket_close_buffered_data(self):\n        with contextlib.closing(\n            (yield websocket_connect(\"ws://127.0.0.1:%d/echo\" % self.get_http_port()))\n        ) as ws:\n            ws.write_message(\"hello\")\n            ws.write_message(\"world\")\n            # Close the underlying stream.\n            ws.stream.close()\n\n    @gen_test\n    def test_websocket_headers(self):\n        # Ensure that arbitrary headers can be passed through websocket_connect.\n        with contextlib.closing(\n            (\n                yield websocket_connect(\n                    HTTPRequest(\n                        \"ws://127.0.0.1:%d/header\" % self.get_http_port(),\n                        headers={\"X-Test\": \"hello\"},\n                    )\n                )\n            )\n        ) as ws:\n            response = yield ws.read_message()\n            self.assertEqual(response, \"hello\")\n\n    @gen_test\n    def test_websocket_header_echo(self):\n        # Ensure that headers can be returned in the response.\n        # Specifically, that arbitrary headers passed through websocket_connect\n        # can be returned.\n        with contextlib.closing(\n            (\n                yield websocket_connect(\n                    HTTPRequest(\n                        \"ws://127.0.0.1:%d/header_echo\" % self.get_http_port(),\n                        headers={\"X-Test-Hello\": \"hello\"},\n                    )\n                )\n            )\n        ) as ws:\n            self.assertEqual(ws.headers.get(\"X-Test-Hello\"), \"hello\")\n            self.assertEqual(\n                ws.headers.get(\"X-Extra-Response-Header\"), \"Extra-Response-Value\"\n            )\n\n    @gen_test\n    def test_server_close_reason(self):\n        ws = yield self.ws_connect(\"/close_reason\")\n        msg = yield ws.read_message()\n        # A message of None means the other side closed the connection.\n        self.assertIs(msg, None)\n        self.assertEqual(ws.close_code, 1001)\n        self.assertEqual(ws.close_reason, \"goodbye\")\n        # The on_close callback is called no matter which side closed.\n        code, reason = yield self.close_future\n        # The client echoed the close code it received to the server,\n        # so the server's close code (returned via close_future) is\n        # the same.\n        self.assertEqual(code, 1001)\n\n    @gen_test\n    def test_client_close_reason(self):\n        ws = yield self.ws_connect(\"/echo\")\n        ws.close(1001, \"goodbye\")\n        code, reason = yield self.close_future\n        self.assertEqual(code, 1001)\n        self.assertEqual(reason, \"goodbye\")\n\n    @gen_test\n    def test_write_after_close(self):\n        ws = yield self.ws_connect(\"/close_reason\")\n        msg = yield ws.read_message()\n        self.assertIs(msg, None)\n        with self.assertRaises(WebSocketClosedError):\n            ws.write_message(\"hello\")\n\n    @gen_test\n    def test_async_prepare(self):\n        # Previously, an async prepare method triggered a bug that would\n        # result in a timeout on test shutdown (and a memory leak).\n        ws = yield self.ws_connect(\"/async_prepare\")\n        ws.write_message(\"hello\")\n        res = yield ws.read_message()\n        self.assertEqual(res, \"hello\")\n\n    @gen_test\n    def test_path_args(self):\n        ws = yield self.ws_connect(\"/path_args/hello\")\n        res = yield ws.read_message()\n        self.assertEqual(res, \"hello\")\n\n    @gen_test\n    def test_coroutine(self):\n        ws = yield self.ws_connect(\"/coroutine\")\n        # Send both messages immediately, coroutine must process one at a time.\n        yield ws.write_message(\"hello1\")\n        yield ws.write_message(\"hello2\")\n        res = yield ws.read_message()\n        self.assertEqual(res, \"hello1\")\n        res = yield ws.read_message()\n        self.assertEqual(res, \"hello2\")\n\n    @gen_test\n    def test_check_origin_valid_no_path(self):\n        port = self.get_http_port()\n\n        url = \"ws://127.0.0.1:%d/echo\" % port\n        headers = {\"Origin\": \"http://127.0.0.1:%d\" % port}\n\n        with contextlib.closing(\n            (yield websocket_connect(HTTPRequest(url, headers=headers)))\n        ) as ws:\n            ws.write_message(\"hello\")\n            response = yield ws.read_message()\n            self.assertEqual(response, \"hello\")\n\n    @gen_test\n    def test_check_origin_valid_with_path(self):\n        port = self.get_http_port()\n\n        url = \"ws://127.0.0.1:%d/echo\" % port\n        headers = {\"Origin\": \"http://127.0.0.1:%d/something\" % port}\n\n        with contextlib.closing(\n            (yield websocket_connect(HTTPRequest(url, headers=headers)))\n        ) as ws:\n            ws.write_message(\"hello\")\n            response = yield ws.read_message()\n            self.assertEqual(response, \"hello\")\n\n    @gen_test\n    def test_check_origin_invalid_partial_url(self):\n        port = self.get_http_port()\n\n        url = \"ws://127.0.0.1:%d/echo\" % port\n        headers = {\"Origin\": \"127.0.0.1:%d\" % port}\n\n        with self.assertRaises(HTTPError) as cm:\n            yield websocket_connect(HTTPRequest(url, headers=headers))\n        self.assertEqual(cm.exception.code, 403)\n\n    @gen_test\n    def test_check_origin_invalid(self):\n        port = self.get_http_port()\n\n        url = \"ws://127.0.0.1:%d/echo\" % port\n        # Host is 127.0.0.1, which should not be accessible from some other\n        # domain\n        headers = {\"Origin\": \"http://somewhereelse.com\"}\n\n        with self.assertRaises(HTTPError) as cm:\n            yield websocket_connect(HTTPRequest(url, headers=headers))\n\n        self.assertEqual(cm.exception.code, 403)\n\n    @gen_test\n    def test_check_origin_invalid_subdomains(self):\n        port = self.get_http_port()\n\n        # CaresResolver may return ipv6-only results for localhost, but our\n        # server is only running on ipv4. Test for this edge case and skip\n        # the test if it happens.\n        addrinfo = yield Resolver().resolve(\"localhost\", port)\n        families = {addr[0] for addr in addrinfo}\n        if socket.AF_INET not in families:\n            self.skipTest(\"localhost does not resolve to ipv4\")\n            return\n\n        url = \"ws://localhost:%d/echo\" % port\n        # Subdomains should be disallowed by default.  If we could pass a\n        # resolver to websocket_connect we could test sibling domains as well.\n        headers = {\"Origin\": \"http://subtenant.localhost\"}\n\n        with self.assertRaises(HTTPError) as cm:\n            yield websocket_connect(HTTPRequest(url, headers=headers))\n\n        self.assertEqual(cm.exception.code, 403)\n\n    @gen_test\n    def test_subprotocols(self):\n        ws = yield self.ws_connect(\n            \"/subprotocol\", subprotocols=[\"badproto\", \"goodproto\"]\n        )\n        self.assertEqual(ws.selected_subprotocol, \"goodproto\")\n        res = yield ws.read_message()\n        self.assertEqual(res, \"subprotocol=goodproto\")\n\n    @gen_test\n    def test_subprotocols_not_offered(self):\n        ws = yield self.ws_connect(\"/subprotocol\")\n        self.assertIs(ws.selected_subprotocol, None)\n        res = yield ws.read_message()\n        self.assertEqual(res, \"subprotocol=None\")\n\n    @gen_test\n    def test_open_coroutine(self):\n        self.message_sent = Event()\n        ws = yield self.ws_connect(\"/open_coroutine\")\n        yield ws.write_message(\"hello\")\n        self.message_sent.set()\n        res = yield ws.read_message()\n        self.assertEqual(res, \"ok\")\n\n    @gen_test\n    def test_error_in_open(self):\n        with ExpectLog(app_log, \"Uncaught exception\"):\n            ws = yield self.ws_connect(\"/error_in_open\")\n            res = yield ws.read_message()\n        self.assertIsNone(res)\n\n    @gen_test\n    def test_error_in_async_open(self):\n        with ExpectLog(app_log, \"Uncaught exception\"):\n            ws = yield self.ws_connect(\"/error_in_async_open\")\n            res = yield ws.read_message()\n        self.assertIsNone(res)\n\n    @gen_test\n    def test_nodelay(self):\n        ws = yield self.ws_connect(\"/nodelay\")\n        res = yield ws.read_message()\n        self.assertEqual(res, \"hello\")\n\n\nclass NativeCoroutineOnMessageHandler(TestWebSocketHandler):\n    def initialize(self, **kwargs):  # type: ignore[override]\n        super().initialize(**kwargs)\n        self.sleeping = 0\n\n    async def on_message(self, message):\n        if self.sleeping > 0:\n            self.write_message(\"another coroutine is already sleeping\")\n        self.sleeping += 1\n        await gen.sleep(0.01)\n        self.sleeping -= 1\n        self.write_message(message)\n\n\nclass WebSocketNativeCoroutineTest(WebSocketBaseTestCase):\n    def get_app(self):\n        return Application([(\"/native\", NativeCoroutineOnMessageHandler)])\n\n    @gen_test\n    def test_native_coroutine(self):\n        ws = yield self.ws_connect(\"/native\")\n        # Send both messages immediately, coroutine must process one at a time.\n        yield ws.write_message(\"hello1\")\n        yield ws.write_message(\"hello2\")\n        res = yield ws.read_message()\n        self.assertEqual(res, \"hello1\")\n        res = yield ws.read_message()\n        self.assertEqual(res, \"hello2\")\n\n\n@abstract_base_test\nclass CompressionTestMixin(WebSocketBaseTestCase):\n    MESSAGE = \"Hello world. Testing 123 123\"\n\n    def get_app(self):\n        class LimitedHandler(TestWebSocketHandler):\n            @property\n            def max_message_size(self):\n                return 1024\n\n            def on_message(self, message):\n                self.write_message(str(len(message)))\n\n        return Application(\n            [\n                (\n                    \"/echo\",\n                    EchoHandler,\n                    dict(compression_options=self.get_server_compression_options()),\n                ),\n                (\n                    \"/limited\",\n                    LimitedHandler,\n                    dict(compression_options=self.get_server_compression_options()),\n                ),\n            ]\n        )\n\n    def get_server_compression_options(self):\n        return None\n\n    def get_client_compression_options(self):\n        return None\n\n    def verify_wire_bytes(self, bytes_in: int, bytes_out: int) -> None:\n        raise NotImplementedError()\n\n    @gen_test\n    def test_message_sizes(self):\n        ws = yield self.ws_connect(\n            \"/echo\", compression_options=self.get_client_compression_options()\n        )\n        # Send the same message three times so we can measure the\n        # effect of the context_takeover options.\n        for i in range(3):\n            ws.write_message(self.MESSAGE)\n            response = yield ws.read_message()\n            self.assertEqual(response, self.MESSAGE)\n        self.assertEqual(ws.protocol._message_bytes_out, len(self.MESSAGE) * 3)\n        self.assertEqual(ws.protocol._message_bytes_in, len(self.MESSAGE) * 3)\n        self.verify_wire_bytes(ws.protocol._wire_bytes_in, ws.protocol._wire_bytes_out)\n\n    @gen_test\n    def test_size_limit(self):\n        ws = yield self.ws_connect(\n            \"/limited\", compression_options=self.get_client_compression_options()\n        )\n        # Small messages pass through.\n        ws.write_message(\"a\" * 128)\n        response = yield ws.read_message()\n        self.assertEqual(response, \"128\")\n        # This message is too big after decompression, but it compresses\n        # down to a size that will pass the initial checks.\n        ws.write_message(\"a\" * 2048)\n        response = yield ws.read_message()\n        self.assertIsNone(response)\n\n\n@abstract_base_test\nclass UncompressedTestMixin(CompressionTestMixin):\n    \"\"\"Specialization of CompressionTestMixin when we expect no compression.\"\"\"\n\n    def verify_wire_bytes(self, bytes_in, bytes_out):\n        # Bytes out includes the 4-byte mask key per message.\n        self.assertEqual(bytes_out, 3 * (len(self.MESSAGE) + 6))\n        self.assertEqual(bytes_in, 3 * (len(self.MESSAGE) + 2))\n\n\nclass NoCompressionTest(UncompressedTestMixin):\n    pass\n\n\n# If only one side tries to compress, the extension is not negotiated.\nclass ServerOnlyCompressionTest(UncompressedTestMixin):\n    def get_server_compression_options(self):\n        return {}\n\n\nclass ClientOnlyCompressionTest(UncompressedTestMixin):\n    def get_client_compression_options(self):\n        return {}\n\n\nclass DefaultCompressionTest(CompressionTestMixin):\n    def get_server_compression_options(self):\n        return {}\n\n    def get_client_compression_options(self):\n        return {}\n\n    def verify_wire_bytes(self, bytes_in, bytes_out):\n        self.assertLess(bytes_out, 3 * (len(self.MESSAGE) + 6))\n        self.assertLess(bytes_in, 3 * (len(self.MESSAGE) + 2))\n        # Bytes out includes the 4 bytes mask key per message.\n        self.assertEqual(bytes_out, bytes_in + 12)\n\n\n@abstract_base_test\nclass MaskFunctionMixin(unittest.TestCase):\n    # Subclasses should define self.mask(mask, data)\n    def mask(self, mask: bytes, data: bytes) -> bytes:\n        raise NotImplementedError()\n\n    def test_mask(self: typing.Any):\n        self.assertEqual(self.mask(b\"abcd\", b\"\"), b\"\")\n        self.assertEqual(self.mask(b\"abcd\", b\"b\"), b\"\\x03\")\n        self.assertEqual(self.mask(b\"abcd\", b\"54321\"), b\"TVPVP\")\n        self.assertEqual(self.mask(b\"ZXCV\", b\"98765432\"), b\"c`t`olpd\")\n        # Include test cases with \\x00 bytes (to ensure that the C\n        # extension isn't depending on null-terminated strings) and\n        # bytes with the high bit set (to smoke out signedness issues).\n        self.assertEqual(\n            self.mask(b\"\\x00\\x01\\x02\\x03\", b\"\\xff\\xfb\\xfd\\xfc\\xfe\\xfa\"),\n            b\"\\xff\\xfa\\xff\\xff\\xfe\\xfb\",\n        )\n        self.assertEqual(\n            self.mask(b\"\\xff\\xfb\\xfd\\xfc\", b\"\\x00\\x01\\x02\\x03\\x04\\x05\"),\n            b\"\\xff\\xfa\\xff\\xff\\xfb\\xfe\",\n        )\n\n\nclass PythonMaskFunctionTest(MaskFunctionMixin):\n    def mask(self, mask, data):\n        return _websocket_mask_python(mask, data)\n\n\n@unittest.skipIf(speedups is None, \"tornado.speedups module not present\")\nclass CythonMaskFunctionTest(MaskFunctionMixin):\n    def mask(self, mask, data):\n        return speedups.websocket_mask(mask, data)\n\n\nclass ServerPeriodicPingTest(WebSocketBaseTestCase):\n    def get_app(self):\n        class PingHandler(TestWebSocketHandler):\n            def on_pong(self, data):\n                self.write_message(\"got pong\")\n\n        return Application(\n            [(\"/\", PingHandler)],\n            websocket_ping_interval=0.01,\n            websocket_ping_timeout=0,\n        )\n\n    @gen_test\n    def test_server_ping(self):\n        ws = yield self.ws_connect(\"/\")\n        for i in range(3):\n            response = yield ws.read_message()\n            self.assertEqual(response, \"got pong\")\n        # TODO: test that the connection gets closed if ping responses stop.\n\n\nclass ClientPeriodicPingTest(WebSocketBaseTestCase):\n    def get_app(self):\n        class PingHandler(TestWebSocketHandler):\n            def on_ping(self, data):\n                self.write_message(\"got ping\")\n\n        return Application([(\"/\", PingHandler)])\n\n    @gen_test\n    def test_client_ping(self):\n        ws = yield self.ws_connect(\"/\", ping_interval=0.01, ping_timeout=0)\n        for i in range(3):\n            response = yield ws.read_message()\n            self.assertEqual(response, \"got ping\")\n        ws.close()\n\n\nclass ServerPingTimeoutTest(WebSocketBaseTestCase):\n    def get_app(self):\n        self.handlers: list[WebSocketHandler] = []\n        test = self\n\n        class PingHandler(TestWebSocketHandler):\n            def initialize(self, close_future=None, compression_options=None):\n                self.handlers = test.handlers\n                # capture the handler instance so we can interrogate it later\n                self.handlers.append(self)\n                return super().initialize(\n                    close_future=close_future, compression_options=compression_options\n                )\n\n        app = Application([(\"/\", PingHandler)])\n        return app\n\n    @staticmethod\n    def install_hook(ws):\n        \"\"\"Optionally suppress the client's \"pong\" response.\"\"\"\n\n        ws.drop_pongs = False\n        ws.pongs_received = 0\n\n        def wrapper(fcn):\n            def _inner(opcode: int, data: bytes):\n                if opcode == 0xA:  # NOTE: 0x9=ping, 0xA=pong\n                    ws.pongs_received += 1\n                    if ws.drop_pongs:\n                        # prevent pong responses\n                        return\n                # leave all other responses unchanged\n                return fcn(opcode, data)\n\n            return _inner\n\n        ws.protocol._handle_message = wrapper(ws.protocol._handle_message)\n\n    @gen_test\n    def test_client_ping_timeout(self):\n        # websocket client\n        interval = 0.2\n        ws = yield self.ws_connect(\n            \"/\", ping_interval=interval, ping_timeout=interval / 4\n        )\n        self.install_hook(ws)\n\n        # websocket handler (server side)\n        handler = self.handlers[0]\n\n        for _ in range(5):\n            # wait for the ping period\n            yield gen.sleep(interval)\n\n            # connection should still be open from the server end\n            self.assertIsNone(handler.close_code)\n            self.assertIsNone(handler.close_reason)\n\n            # connection should still be open from the client end\n            assert ws.protocol.close_code is None\n\n        # Check that our hook is intercepting messages; allow for\n        # some variance in timing (due to e.g. cpu load)\n        self.assertGreaterEqual(ws.pongs_received, 4)\n\n        # suppress the pong response message\n        ws.drop_pongs = True\n\n        # give the server time to register this\n        yield gen.sleep(interval * 1.5)\n\n        # connection should be closed from the server side\n        self.assertEqual(handler.close_code, 1000)\n        self.assertEqual(handler.close_reason, \"ping timed out\")\n\n        # client should have received a close operation\n        self.assertEqual(ws.protocol.close_code, 1000)\n\n\nclass PingCalculationTest(unittest.TestCase):\n    def test_ping_sleep_time(self):\n        from tornado.websocket import WebSocketProtocol13\n\n        now = datetime.datetime(2025, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc)\n        interval = 10  # seconds\n        last_ping_time = datetime.datetime(\n            2025, 1, 1, 11, 59, 54, tzinfo=datetime.timezone.utc\n        )\n        sleep_time = WebSocketProtocol13.ping_sleep_time(\n            last_ping_time=last_ping_time.timestamp(),\n            interval=interval,\n            now=now.timestamp(),\n        )\n        self.assertEqual(sleep_time, 4)\n\n\nclass ManualPingTest(WebSocketBaseTestCase):\n    def get_app(self):\n        class PingHandler(TestWebSocketHandler):\n            def on_ping(self, data):\n                self.write_message(data, binary=isinstance(data, bytes))\n\n        return Application([(\"/\", PingHandler)])\n\n    @gen_test\n    def test_manual_ping(self):\n        ws = yield self.ws_connect(\"/\")\n\n        self.assertRaises(ValueError, ws.ping, \"a\" * 126)\n\n        ws.ping(\"hello\")\n        resp = yield ws.read_message()\n        # on_ping always sees bytes.\n        self.assertEqual(resp, b\"hello\")\n\n        ws.ping(b\"binary hello\")\n        resp = yield ws.read_message()\n        self.assertEqual(resp, b\"binary hello\")\n\n\nclass MaxMessageSizeTest(WebSocketBaseTestCase):\n    def get_app(self):\n        return Application([(\"/\", EchoHandler)], websocket_max_message_size=1024)\n\n    @gen_test\n    def test_large_message(self):\n        ws = yield self.ws_connect(\"/\")\n\n        # Write a message that is allowed.\n        msg = \"a\" * 1024\n        ws.write_message(msg)\n        resp = yield ws.read_message()\n        self.assertEqual(resp, msg)\n\n        # Write a message that is too large.\n        ws.write_message(msg + \"b\")\n        resp = yield ws.read_message()\n        # A message of None means the other side closed the connection.\n        self.assertIs(resp, None)\n        self.assertEqual(ws.close_code, 1009)\n        self.assertEqual(ws.close_reason, \"message too big\")\n        # TODO: Needs tests of messages split over multiple\n        # continuation frames.\n"
  },
  {
    "path": "tornado/test/wsgi_test.py",
    "content": "import asyncio\nimport concurrent.futures\nimport threading\nfrom wsgiref.validate import validator\n\nfrom tornado.routing import RuleRouter\nfrom tornado.testing import AsyncHTTPTestCase, gen_test\nfrom tornado.wsgi import WSGIContainer\n\n\nclass WSGIAppMixin:\n    # TODO: Now that WSGIAdapter is gone, this is a pretty weak test.\n    def get_executor(self):\n        raise NotImplementedError()\n\n    def get_app(self):\n        executor = self.get_executor()\n        # The barrier test in DummyExecutorTest will always wait the full\n        # value of this timeout, so we don't want it to be too high.\n        self.barrier = threading.Barrier(2, timeout=0.3)\n\n        def make_container(app):\n            return WSGIContainer(validator(app), executor=executor)\n\n        return RuleRouter(\n            [\n                (\"/simple\", make_container(self.simple_wsgi_app)),\n                (\"/barrier\", make_container(self.barrier_wsgi_app)),\n                (\"/streaming_barrier\", make_container(self.streaming_barrier_wsgi_app)),\n            ]\n        )\n\n    def respond_plain(self, start_response):\n        status = \"200 OK\"\n        response_headers = [(\"Content-Type\", \"text/plain\")]\n        start_response(status, response_headers)\n\n    def simple_wsgi_app(self, environ, start_response):\n        self.respond_plain(start_response)\n        return [b\"Hello world!\"]\n\n    def barrier_wsgi_app(self, environ, start_response):\n        self.respond_plain(start_response)\n        try:\n            n = self.barrier.wait()\n        except threading.BrokenBarrierError:\n            return [b\"broken barrier\"]\n        else:\n            return [b\"ok %d\" % n]\n\n    def streaming_barrier_wsgi_app(self, environ, start_response):\n        self.respond_plain(start_response)\n        yield b\"ok \"\n        try:\n            n = self.barrier.wait()\n        except threading.BrokenBarrierError:\n            yield b\"broken barrier\"\n        else:\n            yield b\"%d\" % n\n\n\nclass WSGIContainerDummyExecutorTest(WSGIAppMixin, AsyncHTTPTestCase):\n    def get_executor(self):\n        return None\n\n    def test_simple(self):\n        response = self.fetch(\"/simple\")\n        self.assertEqual(response.body, b\"Hello world!\")\n\n    @gen_test\n    async def test_concurrent_barrier(self):\n        self.barrier.reset()\n        resps = await asyncio.gather(\n            self.http_client.fetch(self.get_url(\"/barrier\")),\n            self.http_client.fetch(self.get_url(\"/barrier\")),\n        )\n        for resp in resps:\n            self.assertEqual(resp.body, b\"broken barrier\")\n\n    @gen_test\n    async def test_concurrent_streaming_barrier(self):\n        self.barrier.reset()\n        resps = await asyncio.gather(\n            self.http_client.fetch(self.get_url(\"/streaming_barrier\")),\n            self.http_client.fetch(self.get_url(\"/streaming_barrier\")),\n        )\n        for resp in resps:\n            self.assertEqual(resp.body, b\"ok broken barrier\")\n\n\nclass WSGIContainerThreadPoolTest(WSGIAppMixin, AsyncHTTPTestCase):\n    def get_executor(self):\n        return concurrent.futures.ThreadPoolExecutor()\n\n    def test_simple(self):\n        response = self.fetch(\"/simple\")\n        self.assertEqual(response.body, b\"Hello world!\")\n\n    @gen_test\n    async def test_concurrent_barrier(self):\n        self.barrier.reset()\n        resps = await asyncio.gather(\n            self.http_client.fetch(self.get_url(\"/barrier\")),\n            self.http_client.fetch(self.get_url(\"/barrier\")),\n        )\n        self.assertEqual([b\"ok 0\", b\"ok 1\"], sorted([resp.body for resp in resps]))\n\n    @gen_test\n    async def test_concurrent_streaming_barrier(self):\n        self.barrier.reset()\n        resps = await asyncio.gather(\n            self.http_client.fetch(self.get_url(\"/streaming_barrier\")),\n            self.http_client.fetch(self.get_url(\"/streaming_barrier\")),\n        )\n        self.assertEqual([b\"ok 0\", b\"ok 1\"], sorted([resp.body for resp in resps]))\n"
  },
  {
    "path": "tornado/testing.py",
    "content": "\"\"\"Support classes for automated testing.\n\n* `AsyncTestCase` and `AsyncHTTPTestCase`:  Subclasses of unittest.TestCase\n  with additional support for testing asynchronous (`.IOLoop`-based) code.\n\n* `ExpectLog`: Make test logs less spammy.\n\n* `main()`: A simple test runner (wrapper around unittest.main()) with support\n  for the tornado.autoreload module to rerun the tests when code changes.\n\"\"\"\n\nimport asyncio\nimport functools\nimport inspect\nimport logging\nimport os\nimport re\nimport signal\nimport socket\nimport sys\nimport typing\nimport unittest\nimport warnings\nfrom collections.abc import Callable, Coroutine, Generator\nfrom types import TracebackType\nfrom typing import Any, Optional, Type, Union\n\nfrom tornado import gen, netutil\nfrom tornado.httpclient import AsyncHTTPClient, HTTPResponse\nfrom tornado.httpserver import HTTPServer\nfrom tornado.ioloop import IOLoop, TimeoutError\nfrom tornado.log import app_log\nfrom tornado.platform.asyncio import AsyncIOMainLoop\nfrom tornado.process import Subprocess\nfrom tornado.util import basestring_type, raise_exc_info\nfrom tornado.web import Application\n\n_ExcInfoTuple = tuple[\n    type[BaseException] | None, BaseException | None, TracebackType | None\n]\n\n\n_NON_OWNED_IOLOOPS = AsyncIOMainLoop\n\n\ndef bind_unused_port(\n    reuse_port: bool = False, address: str = \"127.0.0.1\"\n) -> tuple[socket.socket, int]:\n    \"\"\"Binds a server socket to an available port on localhost.\n\n    Returns a tuple (socket, port).\n\n    .. versionchanged:: 4.4\n       Always binds to ``127.0.0.1`` without resolving the name\n       ``localhost``.\n\n    .. versionchanged:: 6.2\n       Added optional ``address`` argument to\n       override the default \"127.0.0.1\".\n    \"\"\"\n    sock = netutil.bind_sockets(\n        0, address, family=socket.AF_INET, reuse_port=reuse_port\n    )[0]\n    port = sock.getsockname()[1]\n    return sock, port\n\n\ndef get_async_test_timeout() -> float:\n    \"\"\"Get the global timeout setting for async tests.\n\n    Returns a float, the timeout in seconds.\n\n    .. versionadded:: 3.1\n    \"\"\"\n    env = os.environ.get(\"ASYNC_TEST_TIMEOUT\")\n    if env is not None:\n        try:\n            return float(env)\n        except ValueError:\n            pass\n    return 5\n\n\nclass AsyncTestCase(unittest.TestCase):\n    \"\"\"`~unittest.TestCase` subclass for testing `.IOLoop`-based\n    asynchronous code.\n\n    The unittest framework is synchronous, so the test must be\n    complete by the time the test method returns. This means that\n    asynchronous code cannot be used in quite the same way as usual\n    and must be adapted to fit. To write your tests with coroutines,\n    decorate your test methods with `tornado.testing.gen_test` instead\n    of `tornado.gen.coroutine`.\n\n    This class also provides the (deprecated) `stop()` and `wait()`\n    methods for a more manual style of testing. The test method itself\n    must call ``self.wait()``, and asynchronous callbacks should call\n    ``self.stop()`` to signal completion.\n\n    By default, a new `.IOLoop` is constructed for each test and is available\n    as ``self.io_loop``.  If the code being tested requires a\n    reused global `.IOLoop`, subclasses should override `get_new_ioloop` to return it,\n    although this is deprecated as of Tornado 6.3.\n\n    The `.IOLoop`'s ``start`` and ``stop`` methods should not be\n    called directly.  Instead, use `self.stop <stop>` and `self.wait\n    <wait>`.  Arguments passed to ``self.stop`` are returned from\n    ``self.wait``.  It is possible to have multiple ``wait``/``stop``\n    cycles in the same test.\n\n    Example::\n\n        # This test uses coroutine style.\n        class MyTestCase(AsyncTestCase):\n            @tornado.testing.gen_test\n            def test_http_fetch(self):\n                client = AsyncHTTPClient()\n                response = yield client.fetch(\"http://www.tornadoweb.org\")\n                # Test contents of response\n                self.assertIn(\"FriendFeed\", response.body)\n\n        # This test uses argument passing between self.stop and self.wait.\n        class MyTestCase2(AsyncTestCase):\n            def test_http_fetch(self):\n                client = AsyncHTTPClient()\n                client.fetch(\"http://www.tornadoweb.org/\", self.stop)\n                response = self.wait()\n                # Test contents of response\n                self.assertIn(\"FriendFeed\", response.body)\n    \"\"\"\n\n    def __init__(self, methodName: str = \"runTest\") -> None:\n        super().__init__(methodName)\n        self.__stopped = False\n        self.__running = False\n        self.__failure: _ExcInfoTuple | None = None\n        self.__stop_args: Any = None\n        self.__timeout: object | None = None\n\n        # Not used in this class itself, but used by @gen_test\n        self._test_generator: Generator | Coroutine | None = None\n\n    def setUp(self) -> None:\n        py_ver = sys.version_info\n        if ((3, 10, 0) <= py_ver < (3, 10, 9)) or ((3, 11, 0) <= py_ver <= (3, 11, 1)):\n            # Early releases in the Python 3.10 and 3.1 series had deprecation\n            # warnings that were later reverted; we must suppress them here.\n            setup_with_context_manager(self, warnings.catch_warnings())\n            warnings.filterwarnings(\n                \"ignore\",\n                message=\"There is no current event loop\",\n                category=DeprecationWarning,\n                module=r\"tornado\\..*\",\n            )\n        super().setUp()\n        if type(self).get_new_ioloop is not AsyncTestCase.get_new_ioloop:\n            warnings.warn(\"get_new_ioloop is deprecated\", DeprecationWarning)\n        self.io_loop = self.get_new_ioloop()\n        asyncio.set_event_loop(self.io_loop.asyncio_loop)  # type: ignore[attr-defined]\n\n    def tearDown(self) -> None:\n        # Native coroutines tend to produce warnings if they're not\n        # allowed to run to completion. It's difficult to ensure that\n        # this always happens in tests, so cancel any tasks that are\n        # still pending by the time we get here.\n        asyncio_loop = self.io_loop.asyncio_loop  # type: ignore\n        tasks = asyncio.all_tasks(asyncio_loop)\n        # Tasks that are done may still appear here and may contain\n        # non-cancellation exceptions, so filter them out.\n        tasks = [t for t in tasks if not t.done()]  # type: ignore\n        for t in tasks:\n            t.cancel()\n        # Allow the tasks to run and finalize themselves (which means\n        # raising a CancelledError inside the coroutine). This may\n        # just transform the \"task was destroyed but it is pending\"\n        # warning into a \"uncaught CancelledError\" warning, but\n        # catching CancelledErrors in coroutines that may leak is\n        # simpler than ensuring that no coroutines leak.\n        if tasks:\n            done, pending = self.io_loop.run_sync(lambda: asyncio.wait(tasks))\n            assert not pending\n            # If any task failed with anything but a CancelledError, raise it.\n            for f in done:\n                try:\n                    f.result()\n                except asyncio.CancelledError:\n                    pass\n\n        # Clean up Subprocess, so it can be used again with a new ioloop.\n        Subprocess.uninitialize()\n        asyncio.set_event_loop(None)\n        if not isinstance(self.io_loop, _NON_OWNED_IOLOOPS):\n            # Try to clean up any file descriptors left open in the ioloop.\n            # This avoids leaks, especially when tests are run repeatedly\n            # in the same process with autoreload (because curl does not\n            # set FD_CLOEXEC on its file descriptors)\n            self.io_loop.close(all_fds=True)\n        super().tearDown()\n        # In case an exception escaped or the StackContext caught an exception\n        # when there wasn't a wait() to re-raise it, do so here.\n        # This is our last chance to raise an exception in a way that the\n        # unittest machinery understands.\n        self.__rethrow()\n\n    def get_new_ioloop(self) -> IOLoop:\n        \"\"\"Returns the `.IOLoop` to use for this test.\n\n        By default, a new `.IOLoop` is created for each test.\n        Subclasses may override this method to return\n        `.IOLoop.current()` if it is not appropriate to use a new\n        `.IOLoop` in each tests (for example, if there are global\n        singletons using the default `.IOLoop`) or if a per-test event\n        loop is being provided by another system (such as\n        ``pytest-asyncio``).\n\n        .. deprecated:: 6.3\n           This method will be removed in Tornado 7.0.\n        \"\"\"\n        return IOLoop(make_current=False)\n\n    def _handle_exception(\n        self, typ: type[Exception], value: Exception, tb: TracebackType\n    ) -> bool:\n        if self.__failure is None:\n            self.__failure = (typ, value, tb)\n        else:\n            app_log.error(\n                \"multiple unhandled exceptions in test\", exc_info=(typ, value, tb)\n            )\n        self.stop()\n        return True\n\n    def __rethrow(self) -> None:\n        if self.__failure is not None:\n            failure = self.__failure\n            self.__failure = None\n            raise_exc_info(failure)\n\n    def run(\n        self, result: unittest.TestResult | None = None\n    ) -> unittest.TestResult | None:\n        ret = super().run(result)\n        # As a last resort, if an exception escaped super.run() and wasn't\n        # re-raised in tearDown, raise it here.  This will cause the\n        # unittest run to fail messily, but that's better than silently\n        # ignoring an error.\n        self.__rethrow()\n        return ret\n\n    def _callTestMethod(self, method: Callable) -> None:\n        \"\"\"Run the given test method, raising an error if it returns non-None.\n\n        Failure to decorate asynchronous test methods with ``@gen_test`` can lead to tests\n        incorrectly passing.\n\n        Remove this override when Python 3.10 support is dropped. This check (in the form of a\n        DeprecationWarning) became a part of the standard library in 3.11.\n\n        Note that ``_callTestMethod`` is not documented as a public interface. However, it is\n        present in all supported versions of Python (3.8+), and if it goes away in the future that's\n        OK because we can just remove this override as noted above.\n        \"\"\"\n        # Calling super()._callTestMethod would hide the return value, even in python 3.8-3.10\n        # where the check isn't being done for us.\n        result = method()\n        if isinstance(result, Generator) or inspect.iscoroutine(result):\n            raise TypeError(\n                \"Generator and coroutine test methods should be\"\n                \" decorated with tornado.testing.gen_test\"\n            )\n        elif result is not None:\n            raise ValueError(\"Return value from test method ignored: %r\" % result)\n\n    def stop(self, _arg: Any = None, **kwargs: Any) -> None:\n        \"\"\"Stops the `.IOLoop`, causing one pending (or future) call to `wait()`\n        to return.\n\n        Keyword arguments or a single positional argument passed to `stop()` are\n        saved and will be returned by `wait()`.\n\n        .. deprecated:: 5.1\n\n           `stop` and `wait` are deprecated; use ``@gen_test`` instead.\n        \"\"\"\n        assert _arg is None or not kwargs\n        self.__stop_args = kwargs or _arg\n        if self.__running:\n            self.io_loop.stop()\n            self.__running = False\n        self.__stopped = True\n\n    def wait(\n        self,\n        condition: Callable[..., bool] | None = None,\n        timeout: float | None = None,\n    ) -> Any:\n        \"\"\"Runs the `.IOLoop` until stop is called or timeout has passed.\n\n        In the event of a timeout, an exception will be thrown. The\n        default timeout is 5 seconds; it may be overridden with a\n        ``timeout`` keyword argument or globally with the\n        ``ASYNC_TEST_TIMEOUT`` environment variable.\n\n        If ``condition`` is not ``None``, the `.IOLoop` will be restarted\n        after `stop()` until ``condition()`` returns ``True``.\n\n        .. versionchanged:: 3.1\n           Added the ``ASYNC_TEST_TIMEOUT`` environment variable.\n\n        .. deprecated:: 5.1\n\n           `stop` and `wait` are deprecated; use ``@gen_test`` instead.\n        \"\"\"\n        if timeout is None:\n            timeout = get_async_test_timeout()\n\n        if not self.__stopped:\n            if timeout:\n\n                def timeout_func() -> None:\n                    try:\n                        raise self.failureException(\n                            \"Async operation timed out after %s seconds\" % timeout\n                        )\n                    except Exception:\n                        self.__failure = sys.exc_info()\n                    self.stop()\n\n                self.__timeout = self.io_loop.add_timeout(\n                    self.io_loop.time() + timeout, timeout_func\n                )\n            while True:\n                self.__running = True\n                self.io_loop.start()\n                if self.__failure is not None or condition is None or condition():\n                    break\n            if self.__timeout is not None:\n                self.io_loop.remove_timeout(self.__timeout)\n                self.__timeout = None\n        assert self.__stopped\n        self.__stopped = False\n        self.__rethrow()\n        result = self.__stop_args\n        self.__stop_args = None\n        return result\n\n\nclass AsyncHTTPTestCase(AsyncTestCase):\n    \"\"\"A test case that starts up an HTTP server.\n\n    Subclasses must override `get_app()`, which returns the\n    `tornado.web.Application` (or other `.HTTPServer` callback) to be tested.\n    Tests will typically use the provided ``self.http_client`` to fetch\n    URLs from this server.\n\n    Example, assuming the \"Hello, world\" example from the user guide is in\n    ``hello.py``::\n\n        import hello\n\n        class TestHelloApp(AsyncHTTPTestCase):\n            def get_app(self):\n                return hello.make_app()\n\n            def test_homepage(self):\n                response = self.fetch('/')\n                self.assertEqual(response.code, 200)\n                self.assertEqual(response.body, 'Hello, world')\n\n    That call to ``self.fetch()`` is equivalent to ::\n\n        self.http_client.fetch(self.get_url('/'), self.stop)\n        response = self.wait()\n\n    which illustrates how AsyncTestCase can turn an asynchronous operation,\n    like ``http_client.fetch()``, into a synchronous operation. If you need\n    to do other asynchronous operations in tests, you'll probably need to use\n    ``stop()`` and ``wait()`` yourself.\n    \"\"\"\n\n    def setUp(self) -> None:\n        super().setUp()\n        sock, port = bind_unused_port()\n        self.__port = port\n\n        self.http_client = self.get_http_client()\n        self._app = self.get_app()\n        self.http_server = self.get_http_server()\n        self.http_server.add_sockets([sock])\n\n    def get_http_client(self) -> AsyncHTTPClient:\n        return AsyncHTTPClient()\n\n    def get_http_server(self) -> HTTPServer:\n        return HTTPServer(self._app, **self.get_httpserver_options())\n\n    def get_app(self) -> Application:\n        \"\"\"Should be overridden by subclasses to return a\n        `tornado.web.Application` or other `.HTTPServer` callback.\n        \"\"\"\n        raise NotImplementedError()\n\n    def fetch(\n        self, path: str, raise_error: bool = False, **kwargs: Any\n    ) -> HTTPResponse:\n        \"\"\"Convenience method to synchronously fetch a URL.\n\n        The given path will be appended to the local server's host and\n        port.  Any additional keyword arguments will be passed directly to\n        `.AsyncHTTPClient.fetch` (and so could be used to pass\n        ``method=\"POST\"``, ``body=\"...\"``, etc).\n\n        If the path begins with http:// or https://, it will be treated as a\n        full URL and will be fetched as-is.\n\n        If ``raise_error`` is ``True``, a `tornado.httpclient.HTTPError` will\n        be raised if the response code is not 200. This is the same behavior\n        as the ``raise_error`` argument to `.AsyncHTTPClient.fetch`, but\n        the default is ``False`` here (it's ``True`` in `.AsyncHTTPClient`)\n        because tests often need to deal with non-200 response codes.\n\n        .. versionchanged:: 5.0\n           Added support for absolute URLs.\n\n        .. versionchanged:: 5.1\n\n           Added the ``raise_error`` argument.\n\n        .. deprecated:: 5.1\n\n           This method currently turns any exception into an\n           `.HTTPResponse` with status code 599. In Tornado 6.0,\n           errors other than `tornado.httpclient.HTTPError` will be\n           passed through, and ``raise_error=False`` will only\n           suppress errors that would be raised due to non-200\n           response codes.\n\n        \"\"\"\n        if path.lower().startswith((\"http://\", \"https://\")):\n            url = path\n        else:\n            url = self.get_url(path)\n        return self.io_loop.run_sync(\n            lambda: self.http_client.fetch(url, raise_error=raise_error, **kwargs),\n            timeout=get_async_test_timeout(),\n        )\n\n    def get_httpserver_options(self) -> dict[str, Any]:\n        \"\"\"May be overridden by subclasses to return additional\n        keyword arguments for the server.\n        \"\"\"\n        return {}\n\n    def get_http_port(self) -> int:\n        \"\"\"Returns the port used by the server.\n\n        A new port is chosen for each test.\n        \"\"\"\n        return self.__port\n\n    def get_protocol(self) -> str:\n        return \"http\"\n\n    def get_url(self, path: str) -> str:\n        \"\"\"Returns an absolute url for the given path on the test server.\"\"\"\n        return f\"{self.get_protocol()}://127.0.0.1:{self.get_http_port()}{path}\"\n\n    def tearDown(self) -> None:\n        self.http_server.stop()\n        self.io_loop.run_sync(\n            self.http_server.close_all_connections, timeout=get_async_test_timeout()\n        )\n        self.http_client.close()\n        del self.http_server\n        del self._app\n        super().tearDown()\n\n\nclass AsyncHTTPSTestCase(AsyncHTTPTestCase):\n    \"\"\"A test case that starts an HTTPS server.\n\n    Interface is generally the same as `AsyncHTTPTestCase`.\n    \"\"\"\n\n    def get_http_client(self) -> AsyncHTTPClient:\n        return AsyncHTTPClient(force_instance=True, defaults=dict(validate_cert=False))\n\n    def get_httpserver_options(self) -> dict[str, Any]:\n        return dict(ssl_options=self.get_ssl_options())\n\n    def get_ssl_options(self) -> dict[str, Any]:\n        \"\"\"May be overridden by subclasses to select SSL options.\n\n        By default includes a self-signed testing certificate.\n        \"\"\"\n        return AsyncHTTPSTestCase.default_ssl_options()\n\n    @staticmethod\n    def default_ssl_options() -> dict[str, Any]:\n        # Testing keys were generated with:\n        # openssl req -new -keyout tornado/test/test.key \\\n        #     -out tornado/test/test.crt \\\n        #     -nodes -days 3650 -x509 \\\n        #     -subj \"/CN=foo.example.com\" -addext \"subjectAltName = DNS:foo.example.com\"\n        module_dir = os.path.dirname(__file__)\n        return dict(\n            certfile=os.path.join(module_dir, \"test\", \"test.crt\"),\n            keyfile=os.path.join(module_dir, \"test\", \"test.key\"),\n        )\n\n    def get_protocol(self) -> str:\n        return \"https\"\n\n\n@typing.overload\ndef gen_test(\n    *, timeout: float | None = None\n) -> Callable[[Callable[..., Union[Generator, \"Coroutine\"]]], Callable[..., None]]:\n    pass\n\n\n@typing.overload\ndef gen_test(func: Callable[..., Union[Generator, \"Coroutine\"]]) -> Callable[..., None]:\n    pass\n\n\ndef gen_test(\n    func: Callable[..., Union[Generator, \"Coroutine\"]] | None = None,\n    timeout: float | None = None,\n) -> (\n    Callable[..., None]\n    | Callable[[Callable[..., Union[Generator, \"Coroutine\"]]], Callable[..., None]]\n):\n    \"\"\"Testing equivalent of ``@gen.coroutine``, to be applied to test methods.\n\n    ``@gen.coroutine`` cannot be used on tests because the `.IOLoop` is not\n    already running.  ``@gen_test`` should be applied to test methods\n    on subclasses of `AsyncTestCase`.\n\n    Example::\n\n        class MyTest(AsyncHTTPTestCase):\n            @gen_test\n            def test_something(self):\n                response = yield self.http_client.fetch(self.get_url('/'))\n\n    By default, ``@gen_test`` times out after 5 seconds. The timeout may be\n    overridden globally with the ``ASYNC_TEST_TIMEOUT`` environment variable,\n    or for each test with the ``timeout`` keyword argument::\n\n        class MyTest(AsyncHTTPTestCase):\n            @gen_test(timeout=10)\n            def test_something_slow(self):\n                response = yield self.http_client.fetch(self.get_url('/'))\n\n    Note that ``@gen_test`` is incompatible with `AsyncTestCase.stop`,\n    `AsyncTestCase.wait`, and `AsyncHTTPTestCase.fetch`. Use ``yield\n    self.http_client.fetch(self.get_url())`` as shown above instead.\n\n    .. versionadded:: 3.1\n       The ``timeout`` argument and ``ASYNC_TEST_TIMEOUT`` environment\n       variable.\n\n    .. versionchanged:: 4.0\n       The wrapper now passes along ``*args, **kwargs`` so it can be used\n       on functions with arguments.\n\n    \"\"\"\n    if timeout is None:\n        timeout = get_async_test_timeout()\n\n    def wrap(f: Callable[..., Union[Generator, \"Coroutine\"]]) -> Callable[..., None]:\n        # Stack up several decorators to allow us to access the generator\n        # object itself.  In the innermost wrapper, we capture the generator\n        # and save it in an attribute of self.  Next, we run the wrapped\n        # function through @gen.coroutine.  Finally, the coroutine is\n        # wrapped again to make it synchronous with run_sync.\n        #\n        # This is a good case study arguing for either some sort of\n        # extensibility in the gen decorators or cancellation support.\n        @functools.wraps(f)\n        def pre_coroutine(\n            self: AsyncTestCase, *args: Any, **kwargs: Any\n        ) -> Generator | Coroutine:\n            # Type comments used to avoid pypy3 bug.\n            result = f(self, *args, **kwargs)\n            if isinstance(result, Generator) or inspect.iscoroutine(result):\n                self._test_generator = result\n            else:\n                self._test_generator = None\n            return result\n\n        if inspect.iscoroutinefunction(f):\n            coro = pre_coroutine\n        else:\n            coro = gen.coroutine(pre_coroutine)  # type: ignore[assignment]\n\n        @functools.wraps(coro)\n        def post_coroutine(self: AsyncTestCase, *args: Any, **kwargs: Any) -> None:\n            try:\n                return self.io_loop.run_sync(\n                    functools.partial(coro, self, *args, **kwargs), timeout=timeout\n                )\n            except TimeoutError as e:\n                # run_sync raises an error with an unhelpful traceback.\n                # If the underlying generator is still running, we can throw the\n                # exception back into it so the stack trace is replaced by the\n                # point where the test is stopped. The only reason the generator\n                # would not be running would be if it were cancelled, which means\n                # a native coroutine, so we can rely on the cr_running attribute.\n                if self._test_generator is not None and getattr(\n                    self._test_generator, \"cr_running\", True\n                ):\n                    self._test_generator.throw(e)\n                    # In case the test contains an overly broad except\n                    # clause, we may get back here.\n                # Coroutine was stopped or didn't raise a useful stack trace,\n                # so re-raise the original exception which is better than nothing.\n                raise\n\n        return post_coroutine\n\n    if func is not None:\n        # Used like:\n        #     @gen_test\n        #     def f(self):\n        #         pass\n        return wrap(func)\n    else:\n        # Used like @gen_test(timeout=10)\n        return wrap\n\n\n# Without this attribute, nosetests will try to run gen_test as a test\n# anywhere it is imported.\ngen_test.__test__ = False  # type: ignore\n\n\nclass ExpectLog(logging.Filter):\n    \"\"\"Context manager to capture and suppress expected log output.\n\n    Useful to make tests of error conditions less noisy, while still\n    leaving unexpected log entries visible.  *Not thread safe.*\n\n    The attribute ``logged_stack`` is set to ``True`` if any exception\n    stack trace was logged.\n\n    Usage::\n\n        with ExpectLog('tornado.application', \"Uncaught exception\"):\n            error_response = self.fetch(\"/some_page\")\n\n    .. versionchanged:: 4.3\n       Added the ``logged_stack`` attribute.\n    \"\"\"\n\n    def __init__(\n        self,\n        logger: logging.Logger | basestring_type,\n        regex: str,\n        required: bool = True,\n        level: int | None = None,\n    ) -> None:\n        \"\"\"Constructs an ExpectLog context manager.\n\n        :param logger: Logger object (or name of logger) to watch.  Pass an\n            empty string to watch the root logger.\n        :param regex: Regular expression to match.  Any log entries on the\n            specified logger that match this regex will be suppressed.\n        :param required: If true, an exception will be raised if the end of the\n            ``with`` statement is reached without matching any log entries.\n        :param level: A constant from the ``logging`` module indicating the\n            expected log level. If this parameter is provided, only log messages\n            at this level will be considered to match. Additionally, the\n            supplied ``logger`` will have its level adjusted if necessary (for\n            the duration of the ``ExpectLog`` to enable the expected message.\n\n        .. versionchanged:: 6.1\n           Added the ``level`` parameter.\n\n        .. deprecated:: 6.3\n           In Tornado 7.0, only ``WARNING`` and higher logging levels will be\n           matched by default. To match ``INFO`` and lower levels, the ``level``\n           argument must be used. This is changing to minimize differences\n           between ``tornado.testing.main`` (which enables ``INFO`` logs by\n           default) and most other test runners (including those in IDEs)\n           which have ``INFO`` logs disabled by default.\n        \"\"\"\n        if isinstance(logger, basestring_type):\n            logger = logging.getLogger(logger)\n        self.logger = logger\n        self.regex = re.compile(regex)\n        self.required = required\n        # matched and deprecated_level_matched are a counter for the respective event.\n        self.matched = 0\n        self.deprecated_level_matched = 0\n        self.logged_stack = False\n        self.level = level\n        self.orig_level: int | None = None\n\n    def filter(self, record: logging.LogRecord) -> bool:\n        if record.exc_info:\n            self.logged_stack = True\n        message = record.getMessage()\n        if self.regex.match(message):\n            if self.level is None and record.levelno < logging.WARNING:\n                # We're inside the logging machinery here so generating a DeprecationWarning\n                # here won't be reported cleanly (if warnings-as-errors is enabled, the error\n                # just gets swallowed by the logging module), and even if it were it would\n                # have the wrong stack trace. Just remember this fact and report it in\n                # __exit__ instead.\n                self.deprecated_level_matched += 1\n            if self.level is not None and record.levelno != self.level:\n                app_log.warning(\n                    \"Got expected log message %r at unexpected level (%s vs %s)\"\n                    % (message, logging.getLevelName(self.level), record.levelname)\n                )\n                return True\n            self.matched += 1\n            return False\n        return True\n\n    def __enter__(self) -> \"ExpectLog\":\n        if self.level is not None and self.level < self.logger.getEffectiveLevel():\n            self.orig_level = self.logger.level\n            self.logger.setLevel(self.level)\n        self.logger.addFilter(self)\n        return self\n\n    def __exit__(\n        self,\n        typ: \"Optional[Type[BaseException]]\",\n        value: BaseException | None,\n        tb: TracebackType | None,\n    ) -> None:\n        if self.orig_level is not None:\n            self.logger.setLevel(self.orig_level)\n        self.logger.removeFilter(self)\n        if not typ and self.required and not self.matched:\n            raise Exception(\"did not get expected log message\")\n        if (\n            not typ\n            and self.required\n            and (self.deprecated_level_matched >= self.matched)\n        ):\n            warnings.warn(\n                \"ExpectLog matched at INFO or below without level argument\",\n                DeprecationWarning,\n            )\n\n\n# From https://nedbatchelder.com/blog/201508/using_context_managers_in_test_setup.html\ndef setup_with_context_manager(testcase: unittest.TestCase, cm: Any) -> Any:\n    \"\"\"Use a context manager to setUp a test case.\n\n    Example::\n\n        def setUp(self):\n            setup_with_context_manager(self, warnings.catch_warnings())\n            warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n            # The catch_warnings context manager will be deactivated\n            # automatically in tearDown.\n\n    \"\"\"\n    val = cm.__enter__()\n    testcase.addCleanup(cm.__exit__, None, None, None)\n    return val\n\n\ndef main(**kwargs: Any) -> None:\n    \"\"\"A simple test runner.\n\n    This test runner is essentially equivalent to `unittest.main` from\n    the standard library, but adds support for Tornado-style option\n    parsing and log formatting. It is *not* necessary to use this\n    `main` function to run tests using `AsyncTestCase`; these tests\n    are self-contained and can run with any test runner.\n\n    The easiest way to run a test is via the command line::\n\n        python -m tornado.testing tornado.test.web_test\n\n    See the standard library ``unittest`` module for ways in which\n    tests can be specified.\n\n    Projects with many tests may wish to define a test script like\n    ``tornado/test/runtests.py``.  This script should define a method\n    ``all()`` which returns a test suite and then call\n    `tornado.testing.main()`.  Note that even when a test script is\n    used, the ``all()`` test suite may be overridden by naming a\n    single test on the command line::\n\n        # Runs all tests\n        python -m tornado.test.runtests\n        # Runs one test\n        python -m tornado.test.runtests tornado.test.web_test\n\n    Additional keyword arguments passed through to ``unittest.main()``.\n    For example, use ``tornado.testing.main(verbosity=2)``\n    to show many test details as they are run.\n    See http://docs.python.org/library/unittest.html#unittest.main\n    for full argument list.\n\n    .. versionchanged:: 5.0\n\n       This function produces no output of its own; only that produced\n       by the `unittest` module (previously it would add a PASS or FAIL\n       log message).\n    \"\"\"\n    from tornado.options import define, options, parse_command_line\n\n    define(\n        \"exception_on_interrupt\",\n        type=bool,\n        default=True,\n        help=(\n            \"If true (default), ctrl-c raises a KeyboardInterrupt \"\n            \"exception.  This prints a stack trace but cannot interrupt \"\n            \"certain operations.  If false, the process is more reliably \"\n            \"killed, but does not print a stack trace.\"\n        ),\n    )\n\n    # support the same options as unittest's command-line interface\n    define(\"verbose\", type=bool)\n    define(\"quiet\", type=bool)\n    define(\"failfast\", type=bool)\n    define(\"catch\", type=bool)\n    define(\"buffer\", type=bool)\n\n    argv = [sys.argv[0]] + parse_command_line(sys.argv)\n\n    if not options.exception_on_interrupt:\n        signal.signal(signal.SIGINT, signal.SIG_DFL)\n\n    if options.verbose is not None:\n        kwargs[\"verbosity\"] = 2\n    if options.quiet is not None:\n        kwargs[\"verbosity\"] = 0\n    if options.failfast is not None:\n        kwargs[\"failfast\"] = True\n    if options.catch is not None:\n        kwargs[\"catchbreak\"] = True\n    if options.buffer is not None:\n        kwargs[\"buffer\"] = True\n\n    if __name__ == \"__main__\" and len(argv) == 1:\n        print(\"No tests specified\", file=sys.stderr)\n        sys.exit(1)\n    # In order to be able to run tests by their fully-qualified name\n    # on the command line without importing all tests here,\n    # module must be set to None.  Python 3.2's unittest.main ignores\n    # defaultTest if no module is given (it tries to do its own\n    # test discovery, which is incompatible with auto2to3), so don't\n    # set module if we're not asking for a specific test.\n    if len(argv) > 1:\n        unittest.main(module=None, argv=argv, **kwargs)  # type: ignore\n    else:\n        unittest.main(defaultTest=\"all\", argv=argv, **kwargs)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "tornado/util.py",
    "content": "\"\"\"Miscellaneous utility functions and classes.\n\nThis module is used internally by Tornado.  It is not necessarily expected\nthat the functions and classes defined here will be useful to other\napplications, but they are documented here in case they are.\n\nThe one public-facing part of this module is the `Configurable` class\nand its `~Configurable.configure` method, which becomes a part of the\ninterface of its subclasses, including `.AsyncHTTPClient`, `.IOLoop`,\nand `.Resolver`.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport array\nimport asyncio\nimport os\nimport re\nimport typing\nimport zlib\nfrom collections.abc import Callable, Mapping, Sequence\nfrom inspect import getfullargspec\nfrom re import Match\nfrom typing import (\n    Any,\n)\n\nif typing.TYPE_CHECKING:\n    # Additional imports only used in type comments.\n    # This lets us make these imports lazy.\n    import datetime\n    import unittest\n    from types import TracebackType\n\n# Aliases for types that are spelled differently in different Python\n# versions. bytes_type is deprecated and no longer used in Tornado\n# itself but is left in case anyone outside Tornado is using it.\nbytes_type = bytes\nunicode_type = str\nbasestring_type = str\n\n\n# versionchanged:: 6.2\n# no longer our own TimeoutError, use standard asyncio class\nTimeoutError = asyncio.TimeoutError\n\n\nclass ObjectDict(dict[str, Any]):\n    \"\"\"Makes a dictionary behave like an object, with attribute-style access.\"\"\"\n\n    def __getattr__(self, name: str) -> Any:\n        try:\n            return self[name]\n        except KeyError:\n            raise AttributeError(name)\n\n    def __setattr__(self, name: str, value: Any) -> None:\n        self[name] = value\n\n\nclass GzipDecompressor:\n    \"\"\"Streaming gzip decompressor.\n\n    The interface is like that of `zlib.decompressobj` (without some of the\n    optional arguments, but it understands gzip headers and checksums.\n    \"\"\"\n\n    def __init__(self) -> None:\n        # Magic parameter makes zlib module understand gzip header\n        # http://stackoverflow.com/questions/1838699/how-can-i-decompress-a-gzip-stream-with-zlib\n        # This works on cpython and pypy, but not jython.\n        self.decompressobj = zlib.decompressobj(16 + zlib.MAX_WBITS)\n\n    def decompress(self, value: bytes, max_length: int = 0) -> bytes:\n        \"\"\"Decompress a chunk, returning newly-available data.\n\n        Some data may be buffered for later processing; `flush` must\n        be called when there is no more input data to ensure that\n        all data was processed.\n\n        If ``max_length`` is given, some input data may be left over\n        in ``unconsumed_tail``; you must retrieve this value and pass\n        it back to a future call to `decompress` if it is not empty.\n        \"\"\"\n        return self.decompressobj.decompress(value, max_length)\n\n    @property\n    def unconsumed_tail(self) -> bytes:\n        \"\"\"Returns the unconsumed portion left over\"\"\"\n        return self.decompressobj.unconsumed_tail\n\n    def flush(self) -> bytes:\n        \"\"\"Return any remaining buffered data not yet returned by decompress.\n\n        Also checks for errors such as truncated input.\n        No other methods may be called on this object after `flush`.\n        \"\"\"\n        return self.decompressobj.flush()\n\n\ndef import_object(name: str) -> Any:\n    \"\"\"Imports an object by name.\n\n    ``import_object('x')`` is equivalent to ``import x``.\n    ``import_object('x.y.z')`` is equivalent to ``from x.y import z``.\n\n    >>> import tornado.escape\n    >>> import_object('tornado.escape') is tornado.escape\n    True\n    >>> import_object('tornado.escape.utf8') is tornado.escape.utf8\n    True\n    >>> import_object('tornado') is tornado\n    True\n    >>> import_object('tornado.missing_module')\n    Traceback (most recent call last):\n        ...\n    ImportError: No module named missing_module\n    \"\"\"\n    if name.count(\".\") == 0:\n        return __import__(name)\n\n    parts = name.split(\".\")\n    obj = __import__(\".\".join(parts[:-1]), fromlist=[parts[-1]])\n    try:\n        return getattr(obj, parts[-1])\n    except AttributeError:\n        raise ImportError(\"No module named %s\" % parts[-1])\n\n\ndef exec_in(\n    code: Any, glob: dict[str, Any], loc: Mapping[str, Any] | None | None = None\n) -> None:\n    if isinstance(code, str):\n        # exec(string) inherits the caller's future imports; compile\n        # the string first to prevent that.\n        code = compile(code, \"<string>\", \"exec\", dont_inherit=True)\n    exec(code, glob, loc)\n\n\ndef raise_exc_info(\n    exc_info: tuple[type | None, BaseException | None, TracebackType | None],\n) -> typing.NoReturn:\n    try:\n        if exc_info[1] is not None:\n            raise exc_info[1].with_traceback(exc_info[2])\n        else:\n            raise TypeError(\"raise_exc_info called with no exception\")\n    finally:\n        # Clear the traceback reference from our stack frame to\n        # minimize circular references that slow down GC.\n        exc_info = (None, None, None)\n\n\ndef errno_from_exception(e: BaseException) -> int | None:\n    \"\"\"Provides the errno from an Exception object.\n\n    There are cases that the errno attribute was not set so we pull\n    the errno out of the args but if someone instantiates an Exception\n    without any args you will get a tuple error. So this function\n    abstracts all that behavior to give you a safe way to get the\n    errno.\n    \"\"\"\n\n    if hasattr(e, \"errno\"):\n        return e.errno  # type: ignore\n    elif e.args:\n        return e.args[0]\n    else:\n        return None\n\n\n_alphanum = frozenset(\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\")\n\n\ndef _re_unescape_replacement(match: Match[str]) -> str:\n    group = match.group(1)\n    if group[0] in _alphanum:\n        raise ValueError(\"cannot unescape '\\\\\\\\%s'\" % group[0])\n    return group\n\n\n_re_unescape_pattern = re.compile(r\"\\\\(.)\", re.DOTALL)\n\n\ndef re_unescape(s: str) -> str:\n    r\"\"\"Unescape a string escaped by `re.escape`.\n\n    May raise ``ValueError`` for regular expressions which could not\n    have been produced by `re.escape` (for example, strings containing\n    ``\\d`` cannot be unescaped).\n\n    .. versionadded:: 4.4\n    \"\"\"\n    return _re_unescape_pattern.sub(_re_unescape_replacement, s)\n\n\nclass Configurable:\n    \"\"\"Base class for configurable interfaces.\n\n    A configurable interface is an (abstract) class whose constructor\n    acts as a factory function for one of its implementation subclasses.\n    The implementation subclass as well as optional keyword arguments to\n    its initializer can be set globally at runtime with `configure`.\n\n    By using the constructor as the factory method, the interface\n    looks like a normal class, `isinstance` works as usual, etc.  This\n    pattern is most useful when the choice of implementation is likely\n    to be a global decision (e.g. when `~select.epoll` is available,\n    always use it instead of `~select.select`), or when a\n    previously-monolithic class has been split into specialized\n    subclasses.\n\n    Configurable subclasses must define the class methods\n    `configurable_base` and `configurable_default`, and use the instance\n    method `initialize` instead of ``__init__``.\n\n    .. versionchanged:: 5.0\n\n       It is now possible for configuration to be specified at\n       multiple levels of a class hierarchy.\n\n    \"\"\"\n\n    # Type annotations on this class are mostly done with comments\n    # because they need to refer to Configurable, which isn't defined\n    # until after the class definition block. These can use regular\n    # annotations when our minimum python version is 3.7.\n    #\n    # There may be a clever way to use generics here to get more\n    # precise types (i.e. for a particular Configurable subclass T,\n    # all the types are subclasses of T, not just Configurable).\n    __impl_class: type[Configurable] | None = None\n    __impl_kwargs: dict[str, Any] | None = None\n\n    def __new__(cls, *args: Any, **kwargs: Any) -> Any:\n        base = cls.configurable_base()\n        init_kwargs: dict[str, Any] = {}\n        if cls is base:\n            impl = cls.configured_class()\n            if base.__impl_kwargs:\n                init_kwargs.update(base.__impl_kwargs)\n        else:\n            impl = cls\n        init_kwargs.update(kwargs)\n        if impl.configurable_base() is not base:\n            # The impl class is itself configurable, so recurse.\n            return impl(*args, **init_kwargs)\n        instance = super().__new__(impl)\n        # initialize vs __init__ chosen for compatibility with AsyncHTTPClient\n        # singleton magic.  If we get rid of that we can switch to __init__\n        # here too.\n        instance.initialize(*args, **init_kwargs)\n        return instance\n\n    @classmethod\n    def configurable_base(cls) -> type[Configurable]:\n        \"\"\"Returns the base class of a configurable hierarchy.\n\n        This will normally return the class in which it is defined.\n        (which is *not* necessarily the same as the ``cls`` classmethod\n        parameter).\n\n        \"\"\"\n        raise NotImplementedError()\n\n    @classmethod\n    def configurable_default(cls) -> type[Configurable]:\n        \"\"\"Returns the implementation class to be used if none is configured.\"\"\"\n        raise NotImplementedError()\n\n    def _initialize(self) -> None:\n        pass\n\n    initialize: Callable[..., None] = _initialize\n    \"\"\"Initialize a `Configurable` subclass instance.\n\n    Configurable classes should use `initialize` instead of ``__init__``.\n\n    .. versionchanged:: 4.2\n       Now accepts positional arguments in addition to keyword arguments.\n    \"\"\"\n\n    @classmethod\n    def configure(cls, impl: None | str | type[Configurable], **kwargs: Any) -> None:\n        \"\"\"Sets the class to use when the base class is instantiated.\n\n        Keyword arguments will be saved and added to the arguments passed\n        to the constructor.  This can be used to set global defaults for\n        some parameters.\n        \"\"\"\n        base = cls.configurable_base()\n        if isinstance(impl, str):\n            impl = typing.cast(type[Configurable], import_object(impl))\n        if impl is not None and not issubclass(impl, cls):\n            raise ValueError(\"Invalid subclass of %s\" % cls)\n        base.__impl_class = impl\n        base.__impl_kwargs = kwargs\n\n    @classmethod\n    def configured_class(cls) -> type[Configurable]:\n        \"\"\"Returns the currently configured class.\"\"\"\n        base = cls.configurable_base()\n        # Manually mangle the private name to see whether this base\n        # has been configured (and not another base higher in the\n        # hierarchy).\n        if base.__dict__.get(\"_Configurable__impl_class\") is None:\n            base.__impl_class = cls.configurable_default()\n        if base.__impl_class is not None:\n            return base.__impl_class\n        else:\n            # Should be impossible, but mypy wants an explicit check.\n            raise ValueError(\"configured class not found\")\n\n    @classmethod\n    def _save_configuration(\n        cls,\n    ) -> tuple[type[Configurable] | None, dict[str, Any] | None]:\n        base = cls.configurable_base()\n        return (base.__impl_class, base.__impl_kwargs)\n\n    @classmethod\n    def _restore_configuration(\n        cls, saved: tuple[type[Configurable] | None, dict[str, Any] | None]\n    ) -> None:\n        base = cls.configurable_base()\n        base.__impl_class = saved[0]\n        base.__impl_kwargs = saved[1]\n\n\nclass ArgReplacer:\n    \"\"\"Replaces one value in an ``args, kwargs`` pair.\n\n    Inspects the function signature to find an argument by name\n    whether it is passed by position or keyword.  For use in decorators\n    and similar wrappers.\n    \"\"\"\n\n    def __init__(self, func: Callable, name: str) -> None:\n        self.name = name\n        try:\n            self.arg_pos: int | None = self._getargnames(func).index(name)\n        except ValueError:\n            # Not a positional parameter\n            self.arg_pos = None\n\n    def _getargnames(self, func: Callable) -> list[str]:\n        try:\n            return getfullargspec(func).args\n        except TypeError:\n            if hasattr(func, \"func_code\"):\n                # Cython-generated code has all the attributes needed\n                # by inspect.getfullargspec, but the inspect module only\n                # works with ordinary functions. Inline the portion of\n                # getfullargspec that we need here. Note that for static\n                # functions the @cython.binding(True) decorator must\n                # be used (for methods it works out of the box).\n                code = func.func_code  # type: ignore\n                return code.co_varnames[: code.co_argcount]\n            raise\n\n    def get_old_value(\n        self, args: Sequence[Any], kwargs: dict[str, Any], default: Any = None\n    ) -> Any:\n        \"\"\"Returns the old value of the named argument without replacing it.\n\n        Returns ``default`` if the argument is not present.\n        \"\"\"\n        if self.arg_pos is not None and len(args) > self.arg_pos:\n            return args[self.arg_pos]\n        else:\n            return kwargs.get(self.name, default)\n\n    def replace(\n        self, new_value: Any, args: Sequence[Any], kwargs: dict[str, Any]\n    ) -> tuple[Any, Sequence[Any], dict[str, Any]]:\n        \"\"\"Replace the named argument in ``args, kwargs`` with ``new_value``.\n\n        Returns ``(old_value, args, kwargs)``.  The returned ``args`` and\n        ``kwargs`` objects may not be the same as the input objects, or\n        the input objects may be mutated.\n\n        If the named argument was not found, ``new_value`` will be added\n        to ``kwargs`` and None will be returned as ``old_value``.\n        \"\"\"\n        if self.arg_pos is not None and len(args) > self.arg_pos:\n            # The arg to replace is passed positionally\n            old_value = args[self.arg_pos]\n            args = list(args)  # *args is normally a tuple\n            args[self.arg_pos] = new_value\n        else:\n            # The arg to replace is either omitted or passed by keyword.\n            old_value = kwargs.get(self.name)\n            kwargs[self.name] = new_value\n        return old_value, args, kwargs\n\n\ndef timedelta_to_seconds(td: datetime.timedelta) -> float:\n    \"\"\"Equivalent to ``td.total_seconds()`` (introduced in Python 2.7).\"\"\"\n    return td.total_seconds()\n\n\ndef _websocket_mask_python(mask: bytes, data: bytes) -> bytes:\n    \"\"\"Websocket masking function.\n\n    `mask` is a `bytes` object of length 4; `data` is a `bytes` object of any length.\n    Returns a `bytes` object of the same length as `data` with the mask applied\n    as specified in section 5.3 of RFC 6455.\n\n    This pure-python implementation may be replaced by an optimized version when available.\n    \"\"\"\n    mask_arr = array.array(\"B\", mask)\n    unmasked_arr = array.array(\"B\", data)\n    for i in range(len(data)):\n        unmasked_arr[i] = unmasked_arr[i] ^ mask_arr[i % 4]\n    return unmasked_arr.tobytes()\n\n\nif os.environ.get(\"TORNADO_NO_EXTENSION\") or os.environ.get(\"TORNADO_EXTENSION\") == \"0\":\n    # These environment variables exist to make it easier to do performance\n    # comparisons; they are not guaranteed to remain supported in the future.\n    _websocket_mask = _websocket_mask_python\nelse:\n    try:\n        from tornado.speedups import websocket_mask as _websocket_mask\n    except ImportError:\n        if os.environ.get(\"TORNADO_EXTENSION\") == \"1\":\n            raise\n        _websocket_mask = _websocket_mask_python\n\n\ndef doctests() -> unittest.TestSuite:\n    import doctest\n\n    return doctest.DocTestSuite()\n"
  },
  {
    "path": "tornado/web.py",
    "content": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"``tornado.web`` provides a simple web framework with asynchronous\nfeatures that allow it to scale to large numbers of open connections,\nmaking it ideal for `long polling\n<http://en.wikipedia.org/wiki/Push_technology#Long_polling>`_.\n\nHere is a simple \"Hello, world\" example app:\n\n.. testcode::\n\n    import asyncio\n    import tornado\n\n    class MainHandler(tornado.web.RequestHandler):\n        def get(self):\n            self.write(\"Hello, world\")\n\n    async def main():\n        application = tornado.web.Application([\n            (r\"/\", MainHandler),\n        ])\n        application.listen(8888)\n        await asyncio.Event().wait()\n\n    if __name__ == \"__main__\":\n        asyncio.run(main())\n\nSee the :doc:`guide` for additional information.\n\nThread-safety notes\n-------------------\n\nIn general, methods on `RequestHandler` and elsewhere in Tornado are\nnot thread-safe. In particular, methods such as\n`~RequestHandler.write()`, `~RequestHandler.finish()`, and\n`~RequestHandler.flush()` must only be called from the main thread. If\nyou use multiple threads it is important to use `.IOLoop.add_callback`\nto transfer control back to the main thread before finishing the\nrequest, or to limit your use of other threads to\n`.IOLoop.run_in_executor` and ensure that your callbacks running in\nthe executor do not refer to Tornado objects.\n\n\"\"\"\n\nimport base64\nimport binascii\nimport datetime\nimport email.utils\nimport functools\nimport gzip\nimport hashlib\nimport hmac\nimport http.cookies\nimport mimetypes\nimport numbers\nimport os.path\nimport re\nimport socket\nimport sys\nimport threading\nimport time\nimport traceback\nimport types\nimport urllib.parse\nimport warnings\nfrom inspect import isclass\nfrom io import BytesIO\nfrom urllib.parse import urlencode\n\nimport tornado\nfrom tornado import escape, gen, httputil, iostream, locale, template\nfrom tornado.concurrent import Future, future_set_result_unless_cancelled\nfrom tornado.escape import _unicode, utf8\nfrom tornado.httpserver import HTTPServer\nfrom tornado.log import access_log, app_log, gen_log\nfrom tornado.routing import (\n    AnyMatches,\n    DefaultHostMatches,\n    HostMatches,\n    ReversibleRouter,\n    ReversibleRuleRouter,\n    Rule,\n    URLSpec,\n    _RuleList,\n)\nfrom tornado.util import ObjectDict, _websocket_mask, unicode_type\n\nurl = URLSpec\n\nfrom collections.abc import Awaitable, Callable, Generator, Iterable\nfrom types import TracebackType\nfrom typing import (\n    Any,\n    Optional,\n    Type,\n    TypeVar,\n    Union,\n    cast,\n    overload,\n)\n\n# The following types are accepted by RequestHandler.set_header\n# and related methods.\n_HeaderTypes = Union[bytes, unicode_type, int, numbers.Integral, datetime.datetime]\n\n_CookieSecretTypes = Union[str, bytes, dict[int, str], dict[int, bytes]]\n\n\nMIN_SUPPORTED_SIGNED_VALUE_VERSION = 1\n\"\"\"The oldest signed value version supported by this version of Tornado.\n\nSigned values older than this version cannot be decoded.\n\n.. versionadded:: 3.2.1\n\"\"\"\n\nMAX_SUPPORTED_SIGNED_VALUE_VERSION = 2\n\"\"\"The newest signed value version supported by this version of Tornado.\n\nSigned values newer than this version cannot be decoded.\n\n.. versionadded:: 3.2.1\n\"\"\"\n\nDEFAULT_SIGNED_VALUE_VERSION = 2\n\"\"\"The signed value version produced by `.RequestHandler.create_signed_value`.\n\nMay be overridden by passing a ``version`` keyword argument.\n\n.. versionadded:: 3.2.1\n\"\"\"\n\nDEFAULT_SIGNED_VALUE_MIN_VERSION = 1\n\"\"\"The oldest signed value accepted by `.RequestHandler.get_signed_cookie`.\n\nMay be overridden by passing a ``min_version`` keyword argument.\n\n.. versionadded:: 3.2.1\n\"\"\"\n\n\nclass _ArgDefaultMarker:\n    pass\n\n\n_ARG_DEFAULT = _ArgDefaultMarker()\n\n\nclass RequestHandler:\n    \"\"\"Base class for HTTP request handlers.\n\n    Subclasses must define at least one of the methods defined in the\n    \"Entry points\" section below.\n\n    Applications should not construct `RequestHandler` objects\n    directly and subclasses should not override ``__init__`` (override\n    `~RequestHandler.initialize` instead).\n\n    \"\"\"\n\n    SUPPORTED_METHODS: tuple[str, ...] = (\n        \"GET\",\n        \"HEAD\",\n        \"POST\",\n        \"DELETE\",\n        \"PATCH\",\n        \"PUT\",\n        \"OPTIONS\",\n    )\n\n    _template_loaders: dict[str, template.BaseLoader] = {}\n    _template_loader_lock = threading.Lock()\n    _remove_control_chars_regex = re.compile(r\"[\\x00-\\x08\\x0e-\\x1f]\")\n\n    _stream_request_body = False\n\n    # Will be set in _execute.\n    _transforms: list[\"OutputTransform\"]\n    path_args: list[str]\n    path_kwargs: dict[str, str]\n\n    def __init__(\n        self,\n        application: \"Application\",\n        request: httputil.HTTPServerRequest,\n        **kwargs: Any,\n    ) -> None:\n        super().__init__()\n\n        self.application = application\n        self.request = request\n        self._headers_written = False\n        self._finished = False\n        self._auto_finish = True\n        self._prepared_future = None\n        self.ui = ObjectDict(\n            (n, self._ui_method(m)) for n, m in application.ui_methods.items()\n        )\n        # UIModules are available as both `modules` and `_tt_modules` in the\n        # template namespace.  Historically only `modules` was available\n        # but could be clobbered by user additions to the namespace.\n        # The template {% module %} directive looks in `_tt_modules` to avoid\n        # possible conflicts.\n        self.ui[\"_tt_modules\"] = _UIModuleNamespace(self, application.ui_modules)\n        self.ui[\"modules\"] = self.ui[\"_tt_modules\"]\n        self.clear()\n        assert self.request.connection is not None\n        # TODO: need to add set_close_callback to HTTPConnection interface\n        self.request.connection.set_close_callback(  # type: ignore\n            self.on_connection_close\n        )\n        self.initialize(**kwargs)  # type: ignore\n\n    def _initialize(self) -> None:\n        pass\n\n    initialize: Callable[..., None] = _initialize\n    \"\"\"Hook for subclass initialization. Called for each request.\n\n    A dictionary passed as the third argument of a ``URLSpec`` will be\n    supplied as keyword arguments to ``initialize()``.\n\n    Example::\n\n        class ProfileHandler(RequestHandler):\n            def initialize(self, database):\n                self.database = database\n\n            def get(self, username):\n                ...\n\n        app = Application([\n            (r'/user/(.*)', ProfileHandler, dict(database=database)),\n            ])\n    \"\"\"\n\n    @property\n    def settings(self) -> dict[str, Any]:\n        \"\"\"An alias for `self.application.settings <Application.settings>`.\"\"\"\n        return self.application.settings\n\n    def _unimplemented_method(self, *args: str, **kwargs: str) -> None:\n        raise HTTPError(405)\n\n    head: Callable[..., Awaitable[None] | None] = _unimplemented_method\n    get: Callable[..., Awaitable[None] | None] = _unimplemented_method\n    post: Callable[..., Awaitable[None] | None] = _unimplemented_method\n    delete: Callable[..., Awaitable[None] | None] = _unimplemented_method\n    patch: Callable[..., Awaitable[None] | None] = _unimplemented_method\n    put: Callable[..., Awaitable[None] | None] = _unimplemented_method\n    options: Callable[..., Awaitable[None] | None] = _unimplemented_method\n\n    def prepare(self) -> Awaitable[None] | None:\n        \"\"\"Called at the beginning of a request before  `get`/`post`/etc.\n\n        Override this method to perform common initialization regardless\n        of the request method. There is no guarantee that ``prepare`` will\n        be called if an error occurs that is handled by the framework.\n\n        Asynchronous support: Use ``async def`` or decorate this method with\n        `.gen.coroutine` to make it asynchronous.\n        If this method returns an  ``Awaitable`` execution will not proceed\n        until the ``Awaitable`` is done.\n\n        .. versionadded:: 3.1\n           Asynchronous support.\n        \"\"\"\n        pass\n\n    def on_finish(self) -> None:\n        \"\"\"Called after the end of a request.\n\n        Override this method to perform cleanup, logging, etc. This method is primarily intended as\n        a counterpart to `prepare`. However, there are a few error cases where ``on_finish`` may be\n        called when ``prepare`` has not. (These are considered bugs and may be fixed in the future,\n        but for now you may need to check to see if the initialization work done in ``prepare`` has\n        occurred)\n\n        ``on_finish`` may not produce any output, as it is called after the response has been sent\n        to the client.\n        \"\"\"\n        pass\n\n    def on_connection_close(self) -> None:\n        \"\"\"Called in async handlers if the client closed the connection.\n\n        Override this to clean up resources associated with\n        long-lived connections.  Note that this method is called only if\n        the connection was closed during asynchronous processing; if you\n        need to do cleanup after every request override `on_finish`\n        instead.\n\n        Proxies may keep a connection open for a time (perhaps\n        indefinitely) after the client has gone away, so this method\n        may not be called promptly after the end user closes their\n        connection.\n        \"\"\"\n        if _has_stream_request_body(self.__class__):\n            if not self.request._body_future.done():\n                self.request._body_future.set_exception(iostream.StreamClosedError())\n                self.request._body_future.exception()\n\n    def clear(self) -> None:\n        \"\"\"Resets all headers and content for this response.\"\"\"\n        self._headers = httputil.HTTPHeaders(\n            {\n                \"Server\": \"TornadoServer/%s\" % tornado.version,\n                \"Content-Type\": \"text/html; charset=UTF-8\",\n                \"Date\": httputil.format_timestamp(time.time()),\n            }\n        )\n        self.set_default_headers()\n        self._write_buffer: list[bytes] = []\n        self._status_code = 200\n        self._reason = httputil.responses[200]\n\n    def set_default_headers(self) -> None:\n        \"\"\"Override this to set HTTP headers at the beginning of the request.\n\n        For example, this is the place to set a custom ``Server`` header.\n        Note that setting such headers in the normal flow of request\n        processing may not do what you want, since headers may be reset\n        during error handling.\n        \"\"\"\n        pass\n\n    def set_status(self, status_code: int, reason: str | None = None) -> None:\n        \"\"\"Sets the status code for our response.\n\n        :arg int status_code: Response status code.\n        :arg str reason: Human-readable reason phrase describing the status\n            code (for example, the \"Not Found\" in ``HTTP/1.1 404 Not Found``).\n            Normally determined automatically from `http.client.responses`; this\n            argument should only be used if you need to use a non-standard\n            status code.\n\n        .. versionchanged:: 5.0\n\n           No longer validates that the response code is in\n           `http.client.responses`.\n        \"\"\"\n        self._status_code = status_code\n        if reason is not None:\n            if \"<\" in reason or not httputil._ABNF.reason_phrase.fullmatch(reason):\n                # Logically this would be better as an exception, but this method\n                # is called on error-handling paths that would need some refactoring\n                # to tolerate internal errors cleanly.\n                #\n                # The check for \"<\" is a defense-in-depth against XSS attacks (we also\n                # escape the reason when rendering error pages).\n                reason = \"Unknown\"\n            self._reason = escape.native_str(reason)\n        else:\n            self._reason = httputil.responses.get(status_code, \"Unknown\")\n\n    def get_status(self) -> int:\n        \"\"\"Returns the status code for our response.\"\"\"\n        return self._status_code\n\n    def set_header(self, name: str, value: _HeaderTypes) -> None:\n        \"\"\"Sets the given response header name and value.\n\n        All header values are converted to strings (`datetime` objects\n        are formatted according to the HTTP specification for the\n        ``Date`` header).\n\n        \"\"\"\n        self._headers[name] = self._convert_header_value(value)\n\n    def add_header(self, name: str, value: _HeaderTypes) -> None:\n        \"\"\"Adds the given response header and value.\n\n        Unlike `set_header`, `add_header` may be called multiple times\n        to return multiple values for the same header.\n        \"\"\"\n        self._headers.add(name, self._convert_header_value(value))\n\n    def clear_header(self, name: str) -> None:\n        \"\"\"Clears an outgoing header, undoing a previous `set_header` call.\n\n        Note that this method does not apply to multi-valued headers\n        set by `add_header`.\n        \"\"\"\n        if name in self._headers:\n            del self._headers[name]\n\n    # https://www.rfc-editor.org/rfc/rfc9110#name-field-values\n    _VALID_HEADER_CHARS = re.compile(r\"[\\x09\\x20-\\x7e\\x80-\\xff]*\")\n\n    def _convert_header_value(self, value: _HeaderTypes) -> str:\n        # Convert the input value to a str. This type check is a bit\n        # subtle: The bytes case only executes on python 3, and the\n        # unicode case only executes on python 2, because the other\n        # cases are covered by the first match for str.\n        if isinstance(value, str):\n            retval = value\n        elif isinstance(value, bytes):\n            # Non-ascii characters in headers are not well supported,\n            # but if you pass bytes, use latin1 so they pass through as-is.\n            retval = value.decode(\"latin1\")\n        elif isinstance(value, numbers.Integral):\n            # return immediately since we know the converted value will be safe\n            return str(value)\n        elif isinstance(value, datetime.datetime):\n            return httputil.format_timestamp(value)\n        else:\n            raise TypeError(\"Unsupported header value %r\" % value)\n        # If \\n is allowed into the header, it is possible to inject\n        # additional headers or split the request.\n        if RequestHandler._VALID_HEADER_CHARS.fullmatch(retval) is None:\n            raise ValueError(\"Unsafe header value %r\", retval)\n        return retval\n\n    @overload\n    def get_argument(self, name: str, default: str, strip: bool = True) -> str:\n        pass\n\n    @overload\n    def get_argument(\n        self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True\n    ) -> str:\n        pass\n\n    @overload\n    def get_argument(self, name: str, default: None, strip: bool = True) -> str | None:\n        pass\n\n    def get_argument(\n        self,\n        name: str,\n        default: None | str | _ArgDefaultMarker = _ARG_DEFAULT,\n        strip: bool = True,\n    ) -> str | None:\n        \"\"\"Returns the value of the argument with the given name.\n\n        If default is not provided, the argument is considered to be\n        required, and we raise a `MissingArgumentError` if it is missing.\n\n        If the argument appears in the request more than once, we return the\n        last value.\n\n        This method searches both the query and body arguments.\n        \"\"\"\n        return self._get_argument(name, default, self.request.arguments, strip)\n\n    def get_arguments(self, name: str, strip: bool = True) -> list[str]:\n        \"\"\"Returns a list of the arguments with the given name.\n\n        If the argument is not present, returns an empty list.\n\n        This method searches both the query and body arguments.\n        \"\"\"\n\n        # Make sure `get_arguments` isn't accidentally being called with a\n        # positional argument that's assumed to be a default (like in\n        # `get_argument`.)\n        assert isinstance(strip, bool)\n\n        return self._get_arguments(name, self.request.arguments, strip)\n\n    @overload\n    def get_body_argument(self, name: str, default: str, strip: bool = True) -> str:\n        pass\n\n    @overload\n    def get_body_argument(\n        self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True\n    ) -> str:\n        pass\n\n    @overload\n    def get_body_argument(\n        self, name: str, default: None, strip: bool = True\n    ) -> str | None:\n        pass\n\n    def get_body_argument(\n        self,\n        name: str,\n        default: None | str | _ArgDefaultMarker = _ARG_DEFAULT,\n        strip: bool = True,\n    ) -> str | None:\n        \"\"\"Returns the value of the argument with the given name\n        from the request body.\n\n        If default is not provided, the argument is considered to be\n        required, and we raise a `MissingArgumentError` if it is missing.\n\n        If the argument appears in the url more than once, we return the\n        last value.\n\n        .. versionadded:: 3.2\n        \"\"\"\n        return self._get_argument(name, default, self.request.body_arguments, strip)\n\n    def get_body_arguments(self, name: str, strip: bool = True) -> list[str]:\n        \"\"\"Returns a list of the body arguments with the given name.\n\n        If the argument is not present, returns an empty list.\n\n        .. versionadded:: 3.2\n        \"\"\"\n        return self._get_arguments(name, self.request.body_arguments, strip)\n\n    @overload\n    def get_query_argument(self, name: str, default: str, strip: bool = True) -> str:\n        pass\n\n    @overload\n    def get_query_argument(\n        self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True\n    ) -> str:\n        pass\n\n    @overload\n    def get_query_argument(\n        self, name: str, default: None, strip: bool = True\n    ) -> str | None:\n        pass\n\n    def get_query_argument(\n        self,\n        name: str,\n        default: None | str | _ArgDefaultMarker = _ARG_DEFAULT,\n        strip: bool = True,\n    ) -> str | None:\n        \"\"\"Returns the value of the argument with the given name\n        from the request query string.\n\n        If default is not provided, the argument is considered to be\n        required, and we raise a `MissingArgumentError` if it is missing.\n\n        If the argument appears in the url more than once, we return the\n        last value.\n\n        .. versionadded:: 3.2\n        \"\"\"\n        return self._get_argument(name, default, self.request.query_arguments, strip)\n\n    def get_query_arguments(self, name: str, strip: bool = True) -> list[str]:\n        \"\"\"Returns a list of the query arguments with the given name.\n\n        If the argument is not present, returns an empty list.\n\n        .. versionadded:: 3.2\n        \"\"\"\n        return self._get_arguments(name, self.request.query_arguments, strip)\n\n    def _get_argument(\n        self,\n        name: str,\n        default: None | str | _ArgDefaultMarker,\n        source: dict[str, list[bytes]],\n        strip: bool = True,\n    ) -> str | None:\n        args = self._get_arguments(name, source, strip=strip)\n        if not args:\n            if isinstance(default, _ArgDefaultMarker):\n                raise MissingArgumentError(name)\n            return default\n        return args[-1]\n\n    def _get_arguments(\n        self, name: str, source: dict[str, list[bytes]], strip: bool = True\n    ) -> list[str]:\n        values = []\n        for v in source.get(name, []):\n            s = self.decode_argument(v, name=name)\n            if isinstance(s, unicode_type):\n                # Get rid of any weird control chars (unless decoding gave\n                # us bytes, in which case leave it alone)\n                s = RequestHandler._remove_control_chars_regex.sub(\" \", s)\n            if strip:\n                s = s.strip()\n            values.append(s)\n        return values\n\n    def decode_argument(self, value: bytes, name: str | None = None) -> str:\n        \"\"\"Decodes an argument from the request.\n\n        The argument has been percent-decoded and is now a byte string.\n        By default, this method decodes the argument as utf-8 and returns\n        a unicode string, but this may be overridden in subclasses.\n\n        This method is used as a filter for both `get_argument()` and for\n        values extracted from the url and passed to `get()`/`post()`/etc.\n\n        The name of the argument is provided if known, but may be None\n        (e.g. for unnamed groups in the url regex).\n        \"\"\"\n        try:\n            return _unicode(value)\n        except UnicodeDecodeError:\n            raise HTTPError(\n                400, \"Invalid unicode in {}: {!r}\".format(name or \"url\", value[:40])\n            )\n\n    @property\n    def cookies(self) -> dict[str, http.cookies.Morsel]:\n        \"\"\"An alias for\n        `self.request.cookies <.httputil.HTTPServerRequest.cookies>`.\"\"\"\n        return self.request.cookies\n\n    @overload\n    def get_cookie(self, name: str, default: str) -> str:\n        pass\n\n    @overload\n    def get_cookie(self, name: str, default: None = None) -> str | None:\n        pass\n\n    def get_cookie(self, name: str, default: str | None = None) -> str | None:\n        \"\"\"Returns the value of the request cookie with the given name.\n\n        If the named cookie is not present, returns ``default``.\n\n        This method only returns cookies that were present in the request.\n        It does not see the outgoing cookies set by `set_cookie` in this\n        handler.\n        \"\"\"\n        if self.request.cookies is not None and name in self.request.cookies:\n            return self.request.cookies[name].value\n        return default\n\n    def set_cookie(\n        self,\n        name: str,\n        value: str | bytes,\n        domain: str | None = None,\n        expires: float | tuple | datetime.datetime | None = None,\n        path: str = \"/\",\n        expires_days: float | None = None,\n        # Keyword-only args start here for historical reasons.\n        *,\n        max_age: int | None = None,\n        httponly: bool = False,\n        secure: bool = False,\n        samesite: str | None = None,\n        **kwargs: Any,\n    ) -> None:\n        \"\"\"Sets an outgoing cookie name/value with the given options.\n\n        Newly-set cookies are not immediately visible via `get_cookie`;\n        they are not present until the next request.\n\n        Most arguments are passed directly to `http.cookies.Morsel` directly.\n        See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie\n        for more information.\n\n        ``expires`` may be a numeric timestamp as returned by `time.time`,\n        a time tuple as returned by `time.gmtime`, or a\n        `datetime.datetime` object. ``expires_days`` is provided as a convenience\n        to set an expiration time in days from today (if both are set, ``expires``\n        is used).\n\n        .. deprecated:: 6.3\n           Keyword arguments are currently accepted case-insensitively.\n           In Tornado 7.0 this will be changed to only accept lowercase\n           arguments.\n        \"\"\"\n        # The cookie library only accepts type str, in both python 2 and 3\n        name = escape.native_str(name)\n        value = escape.native_str(value)\n        if re.search(r\"[\\x00-\\x20]\", value):\n            # Legacy check for control characters in cookie values. This check is no longer needed\n            # since the cookie library escapes these characters correctly now. It will be removed\n            # in the next feature release.\n            raise ValueError(f\"Invalid cookie {name!r}: {value!r}\")\n        for attr_name, attr_value in [\n            (\"name\", name),\n            (\"domain\", domain),\n            (\"path\", path),\n            (\"samesite\", samesite),\n        ]:\n            # Cookie attributes may not contain control characters or semicolons (except when\n            # escaped in the value). A check for control characters was added to the http.cookies\n            # library in a Feb 2026 security release; as of March it still does not check for\n            # semicolons.\n            #\n            # When a semicolon check is added to the standard library (and the release has had time\n            # for adoption), this check may be removed, but be mindful of the fact that this may\n            # change the timing of the exception (to the generation of the Set-Cookie header in\n            # flush()). We may want to add a call to self._new_cookie.output() at the end of this\n            # method to ensure that exceptions are raised when they will be most useful.\n            if attr_value is not None and re.search(r\"[\\x00-\\x20\\x3b\\x7f]\", attr_value):\n                raise http.cookies.CookieError(\n                    f\"Invalid cookie attribute {attr_name}={attr_value!r} for cookie {name!r}\"\n                )\n        if not hasattr(self, \"_new_cookie\"):\n            self._new_cookie: http.cookies.SimpleCookie = http.cookies.SimpleCookie()\n        if name in self._new_cookie:\n            del self._new_cookie[name]\n        self._new_cookie[name] = value\n        morsel = self._new_cookie[name]\n        if domain:\n            morsel[\"domain\"] = domain\n        if expires_days is not None and not expires:\n            expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(\n                days=expires_days\n            )\n        if expires:\n            morsel[\"expires\"] = httputil.format_timestamp(expires)\n        if path:\n            morsel[\"path\"] = path\n        if max_age:\n            # Note change from _ to -.\n            morsel[\"max-age\"] = str(max_age)\n        if httponly:\n            # Note that SimpleCookie ignores the value here. The presense of an\n            # httponly (or secure) key is treated as true.\n            morsel[\"httponly\"] = True\n        if secure:\n            morsel[\"secure\"] = True\n        if samesite:\n            morsel[\"samesite\"] = samesite\n        if kwargs:\n            # The setitem interface is case-insensitive, so continue to support\n            # kwargs for backwards compatibility until we can remove deprecated\n            # features.\n            for k, v in kwargs.items():\n                morsel[k] = v\n            warnings.warn(\n                f\"Deprecated arguments to set_cookie: {set(kwargs.keys())} \"\n                \"(should be lowercase)\",\n                DeprecationWarning,\n            )\n\n    def clear_cookie(self, name: str, **kwargs: Any) -> None:\n        \"\"\"Deletes the cookie with the given name.\n\n        This method accepts the same arguments as `set_cookie`, except for\n        ``expires`` and ``max_age``. Clearing a cookie requires the same\n        ``domain`` and ``path`` arguments as when it was set. In some cases the\n        ``samesite`` and ``secure`` arguments are also required to match. Other\n        arguments are ignored.\n\n        Similar to `set_cookie`, the effect of this method will not be\n        seen until the following request.\n\n        .. versionchanged:: 6.3\n\n           Now accepts all keyword arguments that ``set_cookie`` does.\n           The ``samesite`` and ``secure`` flags have recently become\n           required for clearing ``samesite=\"none\"`` cookies.\n        \"\"\"\n        for excluded_arg in [\"expires\", \"max_age\"]:\n            if excluded_arg in kwargs:\n                raise TypeError(\n                    f\"clear_cookie() got an unexpected keyword argument '{excluded_arg}'\"\n                )\n        expires = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(\n            days=365\n        )\n        self.set_cookie(name, value=\"\", expires=expires, **kwargs)\n\n    def clear_all_cookies(self, **kwargs: Any) -> None:\n        \"\"\"Attempt to delete all the cookies the user sent with this request.\n\n        See `clear_cookie` for more information on keyword arguments. Due to\n        limitations of the cookie protocol, it is impossible to determine on the\n        server side which values are necessary for the ``domain``, ``path``,\n        ``samesite``, or ``secure`` arguments, this method can only be\n        successful if you consistently use the same values for these arguments\n        when setting cookies.\n\n        Similar to `set_cookie`, the effect of this method will not be seen\n        until the following request.\n\n        .. versionchanged:: 3.2\n\n           Added the ``path`` and ``domain`` parameters.\n\n        .. versionchanged:: 6.3\n\n           Now accepts all keyword arguments that ``set_cookie`` does.\n\n        .. deprecated:: 6.3\n\n           The increasingly complex rules governing cookies have made it\n           impossible for a ``clear_all_cookies`` method to work reliably\n           since all we know about cookies are their names. Applications\n           should generally use ``clear_cookie`` one at a time instead.\n        \"\"\"\n        for name in self.request.cookies:\n            self.clear_cookie(name, **kwargs)\n\n    def set_signed_cookie(\n        self,\n        name: str,\n        value: str | bytes,\n        expires_days: float | None = 30,\n        version: int | None = None,\n        **kwargs: Any,\n    ) -> None:\n        \"\"\"Signs and timestamps a cookie so it cannot be forged.\n\n        You must specify the ``cookie_secret`` setting in your Application\n        to use this method. It should be a long, random sequence of bytes\n        to be used as the HMAC secret for the signature.\n\n        To read a cookie set with this method, use `get_signed_cookie()`.\n\n        Note that the ``expires_days`` parameter sets the lifetime of the\n        cookie in the browser, but is independent of the ``max_age_days``\n        parameter to `get_signed_cookie`.\n        A value of None limits the lifetime to the current browser session.\n\n        Secure cookies may contain arbitrary byte values, not just unicode\n        strings (unlike regular cookies)\n\n        Similar to `set_cookie`, the effect of this method will not be\n        seen until the following request.\n\n        .. versionchanged:: 3.2.1\n\n           Added the ``version`` argument.  Introduced cookie version 2\n           and made it the default.\n\n        .. versionchanged:: 6.3\n\n           Renamed from ``set_secure_cookie`` to ``set_signed_cookie`` to\n           avoid confusion with other uses of \"secure\" in cookie attributes\n           and prefixes. The old name remains as an alias.\n        \"\"\"\n        self.set_cookie(\n            name,\n            self.create_signed_value(name, value, version=version),\n            expires_days=expires_days,\n            **kwargs,\n        )\n\n    set_secure_cookie = set_signed_cookie\n\n    def create_signed_value(\n        self, name: str, value: str | bytes, version: int | None = None\n    ) -> bytes:\n        \"\"\"Signs and timestamps a string so it cannot be forged.\n\n        Normally used via set_signed_cookie, but provided as a separate\n        method for non-cookie uses.  To decode a value not stored\n        as a cookie use the optional value argument to get_signed_cookie.\n\n        .. versionchanged:: 3.2.1\n\n           Added the ``version`` argument.  Introduced cookie version 2\n           and made it the default.\n        \"\"\"\n        self.require_setting(\"cookie_secret\", \"secure cookies\")\n        secret = self.application.settings[\"cookie_secret\"]\n        key_version = None\n        if isinstance(secret, dict):\n            if self.application.settings.get(\"key_version\") is None:\n                raise Exception(\"key_version setting must be used for secret_key dicts\")\n            key_version = self.application.settings[\"key_version\"]\n\n        return create_signed_value(\n            secret, name, value, version=version, key_version=key_version\n        )\n\n    def get_signed_cookie(\n        self,\n        name: str,\n        value: str | None = None,\n        max_age_days: float = 31,\n        min_version: int | None = None,\n    ) -> bytes | None:\n        \"\"\"Returns the given signed cookie if it validates, or None.\n\n        The decoded cookie value is returned as a byte string (unlike\n        `get_cookie`).\n\n        Similar to `get_cookie`, this method only returns cookies that\n        were present in the request. It does not see outgoing cookies set by\n        `set_signed_cookie` in this handler.\n\n        .. versionchanged:: 3.2.1\n\n           Added the ``min_version`` argument.  Introduced cookie version 2;\n           both versions 1 and 2 are accepted by default.\n\n         .. versionchanged:: 6.3\n\n           Renamed from ``get_secure_cookie`` to ``get_signed_cookie`` to\n           avoid confusion with other uses of \"secure\" in cookie attributes\n           and prefixes. The old name remains as an alias.\n\n        \"\"\"\n        self.require_setting(\"cookie_secret\", \"secure cookies\")\n        if value is None:\n            value = self.get_cookie(name)\n        return decode_signed_value(\n            self.application.settings[\"cookie_secret\"],\n            name,\n            value,\n            max_age_days=max_age_days,\n            min_version=min_version,\n        )\n\n    get_secure_cookie = get_signed_cookie\n\n    def get_signed_cookie_key_version(\n        self, name: str, value: str | None = None\n    ) -> int | None:\n        \"\"\"Returns the signing key version of the secure cookie.\n\n        The version is returned as int.\n\n        .. versionchanged:: 6.3\n\n           Renamed from ``get_secure_cookie_key_version`` to\n           ``set_signed_cookie_key_version`` to avoid confusion with other\n           uses of \"secure\" in cookie attributes and prefixes. The old name\n           remains as an alias.\n\n        \"\"\"\n        self.require_setting(\"cookie_secret\", \"secure cookies\")\n        if value is None:\n            value = self.get_cookie(name)\n        if value is None:\n            return None\n        return get_signature_key_version(value)\n\n    get_secure_cookie_key_version = get_signed_cookie_key_version\n\n    def redirect(\n        self, url: str, permanent: bool = False, status: int | None = None\n    ) -> None:\n        \"\"\"Sends a redirect to the given (optionally relative) URL.\n\n        If the ``status`` argument is specified, that value is used as the\n        HTTP status code; otherwise either 301 (permanent) or 302\n        (temporary) is chosen based on the ``permanent`` argument.\n        The default is 302 (temporary).\n        \"\"\"\n        if self._headers_written:\n            raise Exception(\"Cannot redirect after headers have been written\")\n        if status is None:\n            status = 301 if permanent else 302\n        else:\n            assert isinstance(status, int) and 300 <= status <= 399\n        self.set_status(status)\n        self.set_header(\"Location\", utf8(url))\n        self.finish()\n\n    def write(self, chunk: str | bytes | dict) -> None:\n        \"\"\"Writes the given chunk to the output buffer.\n\n        To write the output to the network, use the `flush()` method below.\n\n        If the given chunk is a dictionary, we write it as JSON and set\n        the Content-Type of the response to be ``application/json``.\n        (if you want to send JSON as a different ``Content-Type``, call\n        ``set_header`` *after* calling ``write()``).\n\n        Note that lists are not converted to JSON because of a potential\n        cross-site security vulnerability.  All JSON output should be\n        wrapped in a dictionary.  More details at\n        http://haacked.com/archive/2009/06/25/json-hijacking.aspx/ and\n        https://github.com/facebook/tornado/issues/1009\n        \"\"\"\n        if self._finished:\n            raise RuntimeError(\"Cannot write() after finish()\")\n        if not isinstance(chunk, (bytes, unicode_type, dict)):\n            message = \"write() only accepts bytes, unicode, and dict objects\"\n            if isinstance(chunk, list):\n                message += (\n                    \". Lists not accepted for security reasons; see \"\n                    + \"http://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.write\"  # noqa: E501\n                )\n            raise TypeError(message)\n        if isinstance(chunk, dict):\n            chunk = escape.json_encode(chunk)\n            self.set_header(\"Content-Type\", \"application/json; charset=UTF-8\")\n        chunk = utf8(chunk)\n        self._write_buffer.append(chunk)\n\n    def render(self, template_name: str, **kwargs: Any) -> \"Future[None]\":\n        \"\"\"Renders the template with the given arguments as the response.\n\n        ``render()`` calls ``finish()``, so no other output methods can be called\n        after it.\n\n        Returns a `.Future` with the same semantics as the one returned by `finish`.\n        Awaiting this `.Future` is optional.\n\n        .. versionchanged:: 5.1\n\n           Now returns a `.Future` instead of ``None``.\n        \"\"\"\n        if self._finished:\n            raise RuntimeError(\"Cannot render() after finish()\")\n        html = self.render_string(template_name, **kwargs)\n\n        # Insert the additional JS and CSS added by the modules on the page\n        js_embed = []\n        js_files = []\n        css_embed = []\n        css_files = []\n        html_heads = []\n        html_bodies = []\n        for module in getattr(self, \"_active_modules\", {}).values():\n            embed_part = module.embedded_javascript()\n            if embed_part:\n                js_embed.append(utf8(embed_part))\n            file_part = module.javascript_files()\n            if file_part:\n                if isinstance(file_part, (unicode_type, bytes)):\n                    js_files.append(_unicode(file_part))\n                else:\n                    js_files.extend(file_part)\n            embed_part = module.embedded_css()\n            if embed_part:\n                css_embed.append(utf8(embed_part))\n            file_part = module.css_files()\n            if file_part:\n                if isinstance(file_part, (unicode_type, bytes)):\n                    css_files.append(_unicode(file_part))\n                else:\n                    css_files.extend(file_part)\n            head_part = module.html_head()\n            if head_part:\n                html_heads.append(utf8(head_part))\n            body_part = module.html_body()\n            if body_part:\n                html_bodies.append(utf8(body_part))\n\n        if js_files:\n            # Maintain order of JavaScript files given by modules\n            js = self.render_linked_js(js_files)\n            sloc = html.rindex(b\"</body>\")\n            html = html[:sloc] + utf8(js) + b\"\\n\" + html[sloc:]\n        if js_embed:\n            js_bytes = self.render_embed_js(js_embed)\n            sloc = html.rindex(b\"</body>\")\n            html = html[:sloc] + js_bytes + b\"\\n\" + html[sloc:]\n        if css_files:\n            css = self.render_linked_css(css_files)\n            hloc = html.index(b\"</head>\")\n            html = html[:hloc] + utf8(css) + b\"\\n\" + html[hloc:]\n        if css_embed:\n            css_bytes = self.render_embed_css(css_embed)\n            hloc = html.index(b\"</head>\")\n            html = html[:hloc] + css_bytes + b\"\\n\" + html[hloc:]\n        if html_heads:\n            hloc = html.index(b\"</head>\")\n            html = html[:hloc] + b\"\".join(html_heads) + b\"\\n\" + html[hloc:]\n        if html_bodies:\n            hloc = html.index(b\"</body>\")\n            html = html[:hloc] + b\"\".join(html_bodies) + b\"\\n\" + html[hloc:]\n        return self.finish(html)\n\n    def render_linked_js(self, js_files: Iterable[str]) -> str:\n        \"\"\"Default method used to render the final js links for the\n        rendered webpage.\n\n        Override this method in a sub-classed controller to change the output.\n        \"\"\"\n        paths = []\n        unique_paths: set[str] = set()\n\n        for path in js_files:\n            if not is_absolute(path):\n                path = self.static_url(path)\n            if path not in unique_paths:\n                paths.append(path)\n                unique_paths.add(path)\n\n        return \"\".join(\n            '<script src=\"'\n            + escape.xhtml_escape(p)\n            + '\" type=\"text/javascript\"></script>'\n            for p in paths\n        )\n\n    def render_embed_js(self, js_embed: Iterable[bytes]) -> bytes:\n        \"\"\"Default method used to render the final embedded js for the\n        rendered webpage.\n\n        Override this method in a sub-classed controller to change the output.\n        \"\"\"\n        return (\n            b'<script type=\"text/javascript\">\\n//<![CDATA[\\n'\n            + b\"\\n\".join(js_embed)\n            + b\"\\n//]]>\\n</script>\"\n        )\n\n    def render_linked_css(self, css_files: Iterable[str]) -> str:\n        \"\"\"Default method used to render the final css links for the\n        rendered webpage.\n\n        Override this method in a sub-classed controller to change the output.\n        \"\"\"\n        paths = []\n        unique_paths: set[str] = set()\n\n        for path in css_files:\n            if not is_absolute(path):\n                path = self.static_url(path)\n            if path not in unique_paths:\n                paths.append(path)\n                unique_paths.add(path)\n\n        return \"\".join(\n            '<link href=\"' + escape.xhtml_escape(p) + '\" '\n            'type=\"text/css\" rel=\"stylesheet\"/>'\n            for p in paths\n        )\n\n    def render_embed_css(self, css_embed: Iterable[bytes]) -> bytes:\n        \"\"\"Default method used to render the final embedded css for the\n        rendered webpage.\n\n        Override this method in a sub-classed controller to change the output.\n        \"\"\"\n        return b'<style type=\"text/css\">\\n' + b\"\\n\".join(css_embed) + b\"\\n</style>\"\n\n    def render_string(self, template_name: str, **kwargs: Any) -> bytes:\n        \"\"\"Generate the given template with the given arguments.\n\n        We return the generated byte string (in utf8). To generate and\n        write a template as a response, use render() above.\n        \"\"\"\n        # If no template_path is specified, use the path of the calling file\n        template_path = self.get_template_path()\n        if not template_path:\n            frame = sys._getframe(0)\n            web_file = frame.f_code.co_filename\n            while frame.f_code.co_filename == web_file and frame.f_back is not None:\n                frame = frame.f_back\n            assert frame.f_code.co_filename is not None\n            template_path = os.path.dirname(frame.f_code.co_filename)\n        with RequestHandler._template_loader_lock:\n            if template_path not in RequestHandler._template_loaders:\n                loader = self.create_template_loader(template_path)\n                RequestHandler._template_loaders[template_path] = loader\n            else:\n                loader = RequestHandler._template_loaders[template_path]\n        t = loader.load(template_name)\n        namespace = self.get_template_namespace()\n        namespace.update(kwargs)\n        return t.generate(**namespace)\n\n    def get_template_namespace(self) -> dict[str, Any]:\n        \"\"\"Returns a dictionary to be used as the default template namespace.\n\n        May be overridden by subclasses to add or modify values.\n\n        The results of this method will be combined with additional\n        defaults in the `tornado.template` module and keyword arguments\n        to `render` or `render_string`.\n        \"\"\"\n        namespace = dict(\n            handler=self,\n            request=self.request,\n            current_user=self.current_user,\n            locale=self.locale,\n            _=self.locale.translate,\n            pgettext=self.locale.pgettext,\n            static_url=self.static_url,\n            xsrf_form_html=self.xsrf_form_html,\n            reverse_url=self.reverse_url,\n        )\n        namespace.update(self.ui)\n        return namespace\n\n    def create_template_loader(self, template_path: str) -> template.BaseLoader:\n        \"\"\"Returns a new template loader for the given path.\n\n        May be overridden by subclasses.  By default returns a\n        directory-based loader on the given path, using the\n        ``autoescape`` and ``template_whitespace`` application\n        settings.  If a ``template_loader`` application setting is\n        supplied, uses that instead.\n        \"\"\"\n        settings = self.application.settings\n        if \"template_loader\" in settings:\n            return settings[\"template_loader\"]\n        kwargs = {}\n        if \"autoescape\" in settings:\n            # autoescape=None means \"no escaping\", so we have to be sure\n            # to only pass this kwarg if the user asked for it.\n            kwargs[\"autoescape\"] = settings[\"autoescape\"]\n        if \"template_whitespace\" in settings:\n            kwargs[\"whitespace\"] = settings[\"template_whitespace\"]\n        return template.Loader(template_path, **kwargs)\n\n    def flush(self, include_footers: bool = False) -> \"Future[None]\":\n        \"\"\"Flushes the current output buffer to the network.\n\n        .. versionchanged:: 4.0\n           Now returns a `.Future` if no callback is given.\n\n        .. versionchanged:: 6.0\n\n           The ``callback`` argument was removed.\n        \"\"\"\n        assert self.request.connection is not None\n        chunk = b\"\".join(self._write_buffer)\n        self._write_buffer = []\n        if not self._headers_written:\n            self._headers_written = True\n            for transform in self._transforms:\n                assert chunk is not None\n                (\n                    self._status_code,\n                    self._headers,\n                    chunk,\n                ) = transform.transform_first_chunk(\n                    self._status_code, self._headers, chunk, include_footers\n                )\n            # Ignore the chunk and only write the headers for HEAD requests\n            if self.request.method == \"HEAD\":\n                chunk = b\"\"\n\n            # Finalize the cookie headers (which have been stored in a side\n            # object so an outgoing cookie could be overwritten before it\n            # is sent).\n            if hasattr(self, \"_new_cookie\"):\n                for cookie in self._new_cookie.values():\n                    self.add_header(\"Set-Cookie\", cookie.OutputString(None))\n\n            start_line = httputil.ResponseStartLine(\"\", self._status_code, self._reason)\n            return self.request.connection.write_headers(\n                start_line, self._headers, chunk\n            )\n        else:\n            for transform in self._transforms:\n                chunk = transform.transform_chunk(chunk, include_footers)\n            # Ignore the chunk and only write the headers for HEAD requests\n            if self.request.method != \"HEAD\":\n                return self.request.connection.write(chunk)\n            else:\n                future: Future[None] = Future()\n                future.set_result(None)\n                return future\n\n    def finish(self, chunk: str | bytes | dict | None = None) -> \"Future[None]\":\n        \"\"\"Finishes this response, ending the HTTP request.\n\n        Passing a ``chunk`` to ``finish()`` is equivalent to passing that\n        chunk to ``write()`` and then calling ``finish()`` with no arguments.\n\n        Returns a `.Future` which may optionally be awaited to track the sending\n        of the response to the client. This `.Future` resolves when all the response\n        data has been sent, and raises an error if the connection is closed before all\n        data can be sent.\n\n        .. versionchanged:: 5.1\n\n           Now returns a `.Future` instead of ``None``.\n        \"\"\"\n        if self._finished:\n            raise RuntimeError(\"finish() called twice\")\n\n        if chunk is not None:\n            self.write(chunk)\n\n        # Automatically support ETags and add the Content-Length header if\n        # we have not flushed any content yet.\n        if not self._headers_written:\n            if (\n                self._status_code == 200\n                and self.request.method in (\"GET\", \"HEAD\")\n                and \"Etag\" not in self._headers\n            ):\n                self.set_etag_header()\n                if self.check_etag_header():\n                    self._write_buffer = []\n                    self.set_status(304)\n            if self._status_code in (204, 304) or (100 <= self._status_code < 200):\n                assert not self._write_buffer, (\n                    \"Cannot send body with %s\" % self._status_code\n                )\n                self._clear_representation_headers()\n            elif \"Content-Length\" not in self._headers:\n                content_length = sum(len(part) for part in self._write_buffer)\n                self.set_header(\"Content-Length\", content_length)\n\n        assert self.request.connection is not None\n        # Now that the request is finished, clear the callback we\n        # set on the HTTPConnection (which would otherwise prevent the\n        # garbage collection of the RequestHandler when there\n        # are keepalive connections)\n        self.request.connection.set_close_callback(None)  # type: ignore\n\n        future = self.flush(include_footers=True)\n        self.request.connection.finish()\n        self._log()\n        self._finished = True\n        self.on_finish()\n        self._break_cycles()\n        return future\n\n    def detach(self) -> iostream.IOStream:\n        \"\"\"Take control of the underlying stream.\n\n        Returns the underlying `.IOStream` object and stops all\n        further HTTP processing. Intended for implementing protocols\n        like websockets that tunnel over an HTTP handshake.\n\n        This method is only supported when HTTP/1.1 is used.\n\n        .. versionadded:: 5.1\n        \"\"\"\n        self._finished = True\n        # TODO: add detach to HTTPConnection?\n        return self.request.connection.detach()  # type: ignore\n\n    def _break_cycles(self) -> None:\n        # Break up a reference cycle between this handler and the\n        # _ui_module closures to allow for faster GC on CPython.\n        self.ui = None  # type: ignore\n\n    def send_error(self, status_code: int = 500, **kwargs: Any) -> None:\n        \"\"\"Sends the given HTTP error code to the browser.\n\n        If `flush()` has already been called, it is not possible to send\n        an error, so this method will simply terminate the response.\n        If output has been written but not yet flushed, it will be discarded\n        and replaced with the error page.\n\n        Override `write_error()` to customize the error page that is returned.\n        Additional keyword arguments are passed through to `write_error`.\n        \"\"\"\n        if self._headers_written:\n            gen_log.error(\"Cannot send error response after headers written\")\n            if not self._finished:\n                # If we get an error between writing headers and finishing,\n                # we are unlikely to be able to finish due to a\n                # Content-Length mismatch. Try anyway to release the\n                # socket.\n                try:\n                    self.finish()\n                except Exception:\n                    gen_log.error(\"Failed to flush partial response\", exc_info=True)\n            return\n        self.clear()\n\n        reason = kwargs.get(\"reason\")\n        if \"exc_info\" in kwargs:\n            exception = kwargs[\"exc_info\"][1]\n            if isinstance(exception, HTTPError) and exception.reason:\n                reason = exception.reason\n        self.set_status(status_code, reason=reason)\n        try:\n            if status_code != 304:\n                self.write_error(status_code, **kwargs)\n        except Exception:\n            app_log.error(\"Uncaught exception in write_error\", exc_info=True)\n        if not self._finished:\n            self.finish()\n\n    def write_error(self, status_code: int, **kwargs: Any) -> None:\n        \"\"\"Override to implement custom error pages.\n\n        ``write_error`` may call `write`, `render`, `set_header`, etc\n        to produce output as usual.\n\n        If this error was caused by an uncaught exception (including\n        HTTPError), an ``exc_info`` triple will be available as\n        ``kwargs[\"exc_info\"]``.  Note that this exception may not be\n        the \"current\" exception for purposes of methods like\n        ``sys.exc_info()`` or ``traceback.format_exc``.\n        \"\"\"\n        if self.settings.get(\"serve_traceback\") and \"exc_info\" in kwargs:\n            # in debug mode, try to send a traceback\n            self.set_header(\"Content-Type\", \"text/plain\")\n            for line in traceback.format_exception(*kwargs[\"exc_info\"]):\n                self.write(line)\n            self.finish()\n        else:\n            self.finish(\n                \"<html><title>%(code)d: %(message)s</title>\"\n                \"<body>%(code)d: %(message)s</body></html>\"\n                % {\"code\": status_code, \"message\": escape.xhtml_escape(self._reason)}\n            )\n\n    @property\n    def locale(self) -> tornado.locale.Locale:\n        \"\"\"The locale for the current session.\n\n        Determined by either `get_user_locale`, which you can override to\n        set the locale based on, e.g., a user preference stored in a\n        database, or `get_browser_locale`, which uses the ``Accept-Language``\n        header.\n\n        .. versionchanged: 4.1\n           Added a property setter.\n        \"\"\"\n        if not hasattr(self, \"_locale\"):\n            loc = self.get_user_locale()\n            if loc is not None:\n                self._locale = loc\n            else:\n                self._locale = self.get_browser_locale()\n                assert self._locale\n        return self._locale\n\n    @locale.setter\n    def locale(self, value: tornado.locale.Locale) -> None:\n        self._locale = value\n\n    def get_user_locale(self) -> tornado.locale.Locale | None:\n        \"\"\"Override to determine the locale from the authenticated user.\n\n        If None is returned, we fall back to `get_browser_locale()`.\n\n        This method should return a `tornado.locale.Locale` object,\n        most likely obtained via a call like ``tornado.locale.get(\"en\")``\n        \"\"\"\n        return None\n\n    def get_browser_locale(self, default: str = \"en_US\") -> tornado.locale.Locale:\n        \"\"\"Determines the user's locale from ``Accept-Language`` header.\n\n        See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4\n        \"\"\"\n        if \"Accept-Language\" in self.request.headers:\n            languages = self.request.headers[\"Accept-Language\"].split(\",\")\n            locales = []\n            for language in languages:\n                parts = language.strip().split(\";\")\n                if len(parts) > 1 and parts[1].strip().startswith(\"q=\"):\n                    try:\n                        score = float(parts[1].strip()[2:])\n                        if score < 0:\n                            raise ValueError()\n                    except (ValueError, TypeError):\n                        score = 0.0\n                else:\n                    score = 1.0\n                if score > 0:\n                    locales.append((parts[0], score))\n            if locales:\n                locales.sort(key=lambda pair: pair[1], reverse=True)\n                codes = [loc[0] for loc in locales]\n                return locale.get(*codes)\n        return locale.get(default)\n\n    @property\n    def current_user(self) -> Any:\n        \"\"\"The authenticated user for this request.\n\n        This is set in one of two ways:\n\n        * A subclass may override `get_current_user()`, which will be called\n          automatically the first time ``self.current_user`` is accessed.\n          `get_current_user()` will only be called once per request,\n          and is cached for future access::\n\n              def get_current_user(self):\n                  user_cookie = self.get_signed_cookie(\"user\")\n                  if user_cookie:\n                      return json.loads(user_cookie)\n                  return None\n\n        * It may be set as a normal variable, typically from an overridden\n          `prepare()`::\n\n              @gen.coroutine\n              def prepare(self):\n                  user_id_cookie = self.get_signed_cookie(\"user_id\")\n                  if user_id_cookie:\n                      self.current_user = yield load_user(user_id_cookie)\n\n        Note that `prepare()` may be a coroutine while `get_current_user()`\n        may not, so the latter form is necessary if loading the user requires\n        asynchronous operations.\n\n        The user object may be any type of the application's choosing.\n        \"\"\"\n        if not hasattr(self, \"_current_user\"):\n            self._current_user = self.get_current_user()\n        return self._current_user\n\n    @current_user.setter\n    def current_user(self, value: Any) -> None:\n        self._current_user = value\n\n    def get_current_user(self) -> Any:\n        \"\"\"Override to determine the current user from, e.g., a cookie.\n\n        This method may not be a coroutine.\n        \"\"\"\n        return None\n\n    def get_login_url(self) -> str:\n        \"\"\"Override to customize the login URL based on the request.\n\n        By default, we use the ``login_url`` application setting.\n        \"\"\"\n        self.require_setting(\"login_url\", \"@tornado.web.authenticated\")\n        return self.application.settings[\"login_url\"]\n\n    def get_template_path(self) -> str | None:\n        \"\"\"Override to customize template path for each handler.\n\n        By default, we use the ``template_path`` application setting.\n        Return None to load templates relative to the calling file.\n        \"\"\"\n        return self.application.settings.get(\"template_path\")\n\n    @property\n    def xsrf_token(self) -> bytes:\n        \"\"\"The XSRF-prevention token for the current user/session.\n\n        To prevent cross-site request forgery, we set an '_xsrf' cookie\n        and include the same '_xsrf' value as an argument with all POST\n        requests. If the two do not match, we reject the form submission\n        as a potential forgery.\n\n        See http://en.wikipedia.org/wiki/Cross-site_request_forgery\n\n        This property is of type `bytes`, but it contains only ASCII\n        characters. If a character string is required, there is no\n        need to base64-encode it; just decode the byte string as\n        UTF-8.\n\n        .. versionchanged:: 3.2.2\n           The xsrf token will now be have a random mask applied in every\n           request, which makes it safe to include the token in pages\n           that are compressed.  See http://breachattack.com for more\n           information on the issue fixed by this change.  Old (version 1)\n           cookies will be converted to version 2 when this method is called\n           unless the ``xsrf_cookie_version`` `Application` setting is\n           set to 1.\n\n        .. versionchanged:: 4.3\n           The ``xsrf_cookie_kwargs`` `Application` setting may be\n           used to supply additional cookie options (which will be\n           passed directly to `set_cookie`). For example,\n           ``xsrf_cookie_kwargs=dict(httponly=True, secure=True)``\n           will set the ``secure`` and ``httponly`` flags on the\n           ``_xsrf`` cookie.\n        \"\"\"\n        if not hasattr(self, \"_xsrf_token\"):\n            version, token, timestamp = self._get_raw_xsrf_token()\n            output_version = self.settings.get(\"xsrf_cookie_version\", 2)\n            cookie_kwargs = self.settings.get(\"xsrf_cookie_kwargs\", {})\n            if output_version == 1:\n                self._xsrf_token = binascii.b2a_hex(token)\n            elif output_version == 2:\n                mask = os.urandom(4)\n                self._xsrf_token = b\"|\".join(\n                    [\n                        b\"2\",\n                        binascii.b2a_hex(mask),\n                        binascii.b2a_hex(_websocket_mask(mask, token)),\n                        utf8(str(int(timestamp))),\n                    ]\n                )\n            else:\n                raise ValueError(\"unknown xsrf cookie version %d\", output_version)\n            if version is None:\n                if self.current_user and \"expires_days\" not in cookie_kwargs:\n                    cookie_kwargs[\"expires_days\"] = 30\n                cookie_name = self.settings.get(\"xsrf_cookie_name\", \"_xsrf\")\n                self.set_cookie(cookie_name, self._xsrf_token, **cookie_kwargs)\n        return self._xsrf_token\n\n    def _get_raw_xsrf_token(self) -> tuple[int | None, bytes, float]:\n        \"\"\"Read or generate the xsrf token in its raw form.\n\n        The raw_xsrf_token is a tuple containing:\n\n        * version: the version of the cookie from which this token was read,\n          or None if we generated a new token in this request.\n        * token: the raw token data; random (non-ascii) bytes.\n        * timestamp: the time this token was generated (will not be accurate\n          for version 1 cookies)\n        \"\"\"\n        if not hasattr(self, \"_raw_xsrf_token\"):\n            cookie_name = self.settings.get(\"xsrf_cookie_name\", \"_xsrf\")\n            cookie = self.get_cookie(cookie_name)\n            if cookie:\n                version, token, timestamp = self._decode_xsrf_token(cookie)\n            else:\n                version, token, timestamp = None, None, None\n            if token is None:\n                version = None\n                token = os.urandom(16)\n                timestamp = time.time()\n            assert token is not None\n            assert timestamp is not None\n            self._raw_xsrf_token = (version, token, timestamp)\n        return self._raw_xsrf_token\n\n    def _decode_xsrf_token(\n        self, cookie: str\n    ) -> tuple[int | None, bytes | None, float | None]:\n        \"\"\"Convert a cookie string into a the tuple form returned by\n        _get_raw_xsrf_token.\n        \"\"\"\n\n        try:\n            m = _signed_value_version_re.match(utf8(cookie))\n\n            if m:\n                version = int(m.group(1))\n                if version == 2:\n                    _, mask_str, masked_token, timestamp_str = cookie.split(\"|\")\n\n                    mask = binascii.a2b_hex(utf8(mask_str))\n                    token = _websocket_mask(mask, binascii.a2b_hex(utf8(masked_token)))\n                    timestamp = int(timestamp_str)\n                    return version, token, timestamp\n                else:\n                    # Treat unknown versions as not present instead of failing.\n                    raise Exception(\"Unknown xsrf cookie version\")\n            else:\n                version = 1\n                try:\n                    token = binascii.a2b_hex(utf8(cookie))\n                except (binascii.Error, TypeError):\n                    token = utf8(cookie)\n                # We don't have a usable timestamp in older versions.\n                timestamp = int(time.time())\n                return (version, token, timestamp)\n        except Exception:\n            # Catch exceptions and return nothing instead of failing.\n            gen_log.debug(\"Uncaught exception in _decode_xsrf_token\", exc_info=True)\n            return None, None, None\n\n    def check_xsrf_cookie(self) -> None:\n        \"\"\"Verifies that the ``_xsrf`` cookie matches the ``_xsrf`` argument.\n\n        To prevent cross-site request forgery, we set an ``_xsrf``\n        cookie and include the same value as a non-cookie\n        field with all ``POST`` requests. If the two do not match, we\n        reject the form submission as a potential forgery.\n\n        The ``_xsrf`` value may be set as either a form field named ``_xsrf``\n        or in a custom HTTP header named ``X-XSRFToken`` or ``X-CSRFToken``\n        (the latter is accepted for compatibility with Django).\n\n        See http://en.wikipedia.org/wiki/Cross-site_request_forgery\n\n        .. versionchanged:: 3.2.2\n           Added support for cookie version 2.  Both versions 1 and 2 are\n           supported.\n        \"\"\"\n        # Prior to release 1.1.1, this check was ignored if the HTTP header\n        # ``X-Requested-With: XMLHTTPRequest`` was present.  This exception\n        # has been shown to be insecure and has been removed.  For more\n        # information please see\n        # http://www.djangoproject.com/weblog/2011/feb/08/security/\n        # http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails\n        input_token = (\n            self.get_argument(\"_xsrf\", None)\n            or self.request.headers.get(\"X-Xsrftoken\")\n            or self.request.headers.get(\"X-Csrftoken\")\n        )\n        if not input_token:\n            raise HTTPError(403, \"'_xsrf' argument missing from POST\")\n        _, token, _ = self._decode_xsrf_token(input_token)\n        _, expected_token, _ = self._get_raw_xsrf_token()\n        if not token:\n            raise HTTPError(403, \"'_xsrf' argument has invalid format\")\n        if not hmac.compare_digest(utf8(token), utf8(expected_token)):\n            raise HTTPError(403, \"XSRF cookie does not match POST argument\")\n\n    def xsrf_form_html(self) -> str:\n        \"\"\"An HTML ``<input/>`` element to be included with all POST forms.\n\n        It defines the ``_xsrf`` input value, which we check on all POST\n        requests to prevent cross-site request forgery. If you have set\n        the ``xsrf_cookies`` application setting, you must include this\n        HTML within all of your HTML forms.\n\n        In a template, this method should be called with ``{% module\n        xsrf_form_html() %}``\n\n        See `check_xsrf_cookie()` above for more information.\n        \"\"\"\n        return (\n            '<input type=\"hidden\" name=\"_xsrf\" value=\"'\n            + escape.xhtml_escape(self.xsrf_token)\n            + '\"/>'\n        )\n\n    def static_url(\n        self, path: str, include_host: bool | None = None, **kwargs: Any\n    ) -> str:\n        \"\"\"Returns a static URL for the given relative static file path.\n\n        This method requires you set the ``static_path`` setting in your\n        application (which specifies the root directory of your static\n        files).\n\n        This method returns a versioned url (by default appending\n        ``?v=<signature>``), which allows the static files to be\n        cached indefinitely.  This can be disabled by passing\n        ``include_version=False`` (in the default implementation;\n        other static file implementations are not required to support\n        this, but they may support other options).\n\n        By default this method returns URLs relative to the current\n        host, but if ``include_host`` is true the URL returned will be\n        absolute.  If this handler has an ``include_host`` attribute,\n        that value will be used as the default for all `static_url`\n        calls that do not pass ``include_host`` as a keyword argument.\n\n        \"\"\"\n        self.require_setting(\"static_path\", \"static_url\")\n        get_url = self.settings.get(\n            \"static_handler_class\", StaticFileHandler\n        ).make_static_url\n\n        if include_host is None:\n            include_host = getattr(self, \"include_host\", False)\n\n        if include_host:\n            base = self.request.protocol + \"://\" + self.request.host\n        else:\n            base = \"\"\n\n        return base + get_url(self.settings, path, **kwargs)\n\n    def require_setting(self, name: str, feature: str = \"this feature\") -> None:\n        \"\"\"Raises an exception if the given app setting is not defined.\"\"\"\n        if not self.application.settings.get(name):\n            raise Exception(\n                \"You must define the '%s' setting in your \"\n                \"application to use %s\" % (name, feature)\n            )\n\n    def reverse_url(self, name: str, *args: Any) -> str:\n        \"\"\"Alias for `Application.reverse_url`.\"\"\"\n        return self.application.reverse_url(name, *args)\n\n    def compute_etag(self) -> str | None:\n        \"\"\"Computes the etag header to be used for this request.\n\n        By default uses a hash of the content written so far.\n\n        May be overridden to provide custom etag implementations,\n        or may return None to disable tornado's default etag support.\n        \"\"\"\n        hasher = hashlib.sha1()\n        for part in self._write_buffer:\n            hasher.update(part)\n        return '\"%s\"' % hasher.hexdigest()\n\n    def set_etag_header(self) -> None:\n        \"\"\"Sets the response's Etag header using ``self.compute_etag()``.\n\n        Note: no header will be set if ``compute_etag()`` returns ``None``.\n\n        This method is called automatically when the request is finished.\n        \"\"\"\n        etag = self.compute_etag()\n        if etag is not None:\n            self.set_header(\"Etag\", etag)\n\n    def check_etag_header(self) -> bool:\n        \"\"\"Checks the ``Etag`` header against requests's ``If-None-Match``.\n\n        Returns ``True`` if the request's Etag matches and a 304 should be\n        returned. For example::\n\n            self.set_etag_header()\n            if self.check_etag_header():\n                self.set_status(304)\n                return\n\n        This method is called automatically when the request is finished,\n        but may be called earlier for applications that override\n        `compute_etag` and want to do an early check for ``If-None-Match``\n        before completing the request.  The ``Etag`` header should be set\n        (perhaps with `set_etag_header`) before calling this method.\n        \"\"\"\n        computed_etag = utf8(self._headers.get(\"Etag\", \"\"))\n        # Find all weak and strong etag values from If-None-Match header\n        # because RFC 7232 allows multiple etag values in a single header.\n        etags = re.findall(\n            rb'\\*|(?:W/)?\"[^\"]*\"', utf8(self.request.headers.get(\"If-None-Match\", \"\"))\n        )\n        if not computed_etag or not etags:\n            return False\n\n        match = False\n        if etags[0] == b\"*\":\n            match = True\n        else:\n            # Use a weak comparison when comparing entity-tags.\n            def val(x: bytes) -> bytes:\n                return x[2:] if x.startswith(b\"W/\") else x\n\n            for etag in etags:\n                if val(etag) == val(computed_etag):\n                    match = True\n                    break\n        return match\n\n    async def _execute(\n        self, transforms: list[\"OutputTransform\"], *args: bytes, **kwargs: bytes\n    ) -> None:\n        \"\"\"Executes this request with the given output transforms.\"\"\"\n        self._transforms = transforms\n        try:\n            if self.request.method not in self.SUPPORTED_METHODS:\n                raise HTTPError(405)\n\n            # If we're not in stream_request_body mode, this is the place where we parse the body.\n            if not _has_stream_request_body(self.__class__):\n                try:\n                    self.request._parse_body()\n                except httputil.HTTPInputError as e:\n                    raise HTTPError(400, \"Invalid body: %s\" % e) from e\n\n            self.path_args = [self.decode_argument(arg) for arg in args]\n            self.path_kwargs = {\n                k: self.decode_argument(v, name=k) for (k, v) in kwargs.items()\n            }\n            # If XSRF cookies are turned on, reject form submissions without\n            # the proper cookie\n            if self.request.method not in (\n                \"GET\",\n                \"HEAD\",\n                \"OPTIONS\",\n            ) and self.application.settings.get(\"xsrf_cookies\"):\n                self.check_xsrf_cookie()\n\n            result = self.prepare()\n            if result is not None:\n                result = await result  # type: ignore\n            if self._prepared_future is not None:\n                # Tell the Application we've finished with prepare()\n                # and are ready for the body to arrive.\n                future_set_result_unless_cancelled(self._prepared_future, None)\n            if self._finished:\n                return\n\n            if _has_stream_request_body(self.__class__):\n                # In streaming mode request.body is a Future that signals\n                # the body has been completely received.  The Future has no\n                # result; the data has been passed to self.data_received\n                # instead.\n                try:\n                    await self.request._body_future\n                except iostream.StreamClosedError:\n                    return\n\n            method = getattr(self, self.request.method.lower())\n            result = method(*self.path_args, **self.path_kwargs)\n            if result is not None:\n                result = await result\n            if self._auto_finish and not self._finished:\n                self.finish()\n        except Exception as e:\n            try:\n                self._handle_request_exception(e)\n            except Exception:\n                app_log.error(\"Exception in exception handler\", exc_info=True)\n            finally:\n                # Unset result to avoid circular references\n                result = None\n            if self._prepared_future is not None and not self._prepared_future.done():\n                # In case we failed before setting _prepared_future, do it\n                # now (to unblock the HTTP server).  Note that this is not\n                # in a finally block to avoid GC issues prior to Python 3.4.\n                self._prepared_future.set_result(None)\n\n    def data_received(self, chunk: bytes) -> Awaitable[None] | None:\n        \"\"\"Implement this method to handle streamed request data.\n\n        Requires the `.stream_request_body` decorator.\n\n        May be a coroutine for flow control.\n        \"\"\"\n        raise NotImplementedError()\n\n    def _log(self) -> None:\n        \"\"\"Logs the current request.\n\n        Sort of deprecated since this functionality was moved to the\n        Application, but left in place for the benefit of existing apps\n        that have overridden this method.\n        \"\"\"\n        self.application.log_request(self)\n\n    def _request_summary(self) -> str:\n        return \"{} {} ({})\".format(\n            self.request.method,\n            self.request.uri,\n            self.request.remote_ip,\n        )\n\n    def _handle_request_exception(self, e: BaseException) -> None:\n        if isinstance(e, Finish):\n            # Not an error; just finish the request without logging.\n            if not self._finished:\n                self.finish(*e.args)\n            return\n        try:\n            self.log_exception(*sys.exc_info())\n        except Exception:\n            # An error here should still get a best-effort send_error()\n            # to avoid leaking the connection.\n            app_log.error(\"Error in exception logger\", exc_info=True)\n        if self._finished:\n            # Extra errors after the request has been finished should\n            # be logged, but there is no reason to continue to try and\n            # send a response.\n            return\n        if isinstance(e, HTTPError):\n            self.send_error(e.status_code, exc_info=sys.exc_info())\n        else:\n            self.send_error(500, exc_info=sys.exc_info())\n\n    def log_exception(\n        self,\n        typ: \"Optional[Type[BaseException]]\",\n        value: BaseException | None,\n        tb: TracebackType | None,\n    ) -> None:\n        \"\"\"Override to customize logging of uncaught exceptions.\n\n        By default logs instances of `HTTPError` as warnings without\n        stack traces (on the ``tornado.general`` logger), and all\n        other exceptions as errors with stack traces (on the\n        ``tornado.application`` logger).\n\n        .. versionadded:: 3.1\n        \"\"\"\n        if isinstance(value, HTTPError):\n            log_message = value.get_message()\n            if log_message:\n                format = \"%d %s: %s\"\n                args = [value.status_code, self._request_summary(), log_message]\n                gen_log.warning(format, *args)\n        else:\n            app_log.error(\n                \"Uncaught exception %s\\n%r\",\n                self._request_summary(),\n                self.request,\n                exc_info=(typ, value, tb),  # type: ignore\n            )\n\n    def _ui_module(self, name: str, module: type[\"UIModule\"]) -> Callable[..., str]:\n        def render(*args, **kwargs) -> str:  # type: ignore\n            if not hasattr(self, \"_active_modules\"):\n                self._active_modules: dict[str, UIModule] = {}\n            if name not in self._active_modules:\n                self._active_modules[name] = module(self)\n            rendered = self._active_modules[name].render(*args, **kwargs)\n            return _unicode(rendered)\n\n        return render\n\n    def _ui_method(self, method: Callable[..., str]) -> Callable[..., str]:\n        return lambda *args, **kwargs: method(self, *args, **kwargs)\n\n    def _clear_representation_headers(self) -> None:\n        # 304 responses should not contain representation metadata\n        # headers (defined in\n        # https://tools.ietf.org/html/rfc7231#section-3.1)\n        # not explicitly allowed by\n        # https://tools.ietf.org/html/rfc7232#section-4.1\n        headers = [\"Content-Encoding\", \"Content-Language\", \"Content-Type\"]\n        for h in headers:\n            self.clear_header(h)\n\n\n_RequestHandlerType = TypeVar(\"_RequestHandlerType\", bound=RequestHandler)\n\n\ndef stream_request_body(cls: type[_RequestHandlerType]) -> type[_RequestHandlerType]:\n    \"\"\"Apply to `RequestHandler` subclasses to enable streaming body support.\n\n    This decorator implies the following changes:\n\n    * `.HTTPServerRequest.body` is undefined, and body arguments will not\n      be included in `RequestHandler.get_argument`.\n    * `RequestHandler.prepare` is called when the request headers have been\n      read instead of after the entire body has been read.\n    * The subclass must define a method ``data_received(self, data):``, which\n      will be called zero or more times as data is available.  Note that\n      if the request has an empty body, ``data_received`` may not be called.\n    * ``prepare`` and ``data_received`` may return Futures (such as via\n      ``@gen.coroutine``, in which case the next method will not be called\n      until those futures have completed.\n    * The regular HTTP method (``post``, ``put``, etc) will be called after\n      the entire body has been read.\n\n    See the `file receiver demo <https://github.com/tornadoweb/tornado/tree/stable/demos/file_upload/>`_\n    for example usage.\n    \"\"\"  # noqa: E501\n    if not issubclass(cls, RequestHandler):\n        raise TypeError(\"expected subclass of RequestHandler, got %r\", cls)\n    cls._stream_request_body = True\n    return cls\n\n\ndef _has_stream_request_body(cls: type[RequestHandler]) -> bool:\n    if not issubclass(cls, RequestHandler):\n        raise TypeError(\"expected subclass of RequestHandler, got %r\", cls)\n    return cls._stream_request_body\n\n\ndef removeslash(\n    method: Callable[..., Awaitable[None] | None],\n) -> Callable[..., Awaitable[None] | None]:\n    \"\"\"Use this decorator to remove trailing slashes from the request path.\n\n    For example, a request to ``/foo/`` would redirect to ``/foo`` with this\n    decorator. Your request handler mapping should use a regular expression\n    like ``r'/foo/*'`` in conjunction with using the decorator.\n    \"\"\"\n\n    @functools.wraps(method)\n    def wrapper(  # type: ignore\n        self: RequestHandler, *args, **kwargs\n    ) -> Awaitable[None] | None:\n        if self.request.path.endswith(\"/\"):\n            if self.request.method in (\"GET\", \"HEAD\"):\n                uri = self.request.path.rstrip(\"/\")\n                if uri:  # don't try to redirect '/' to ''\n                    if self.request.query:\n                        uri += \"?\" + self.request.query\n                    self.redirect(uri, permanent=True)\n                    return None\n            else:\n                raise HTTPError(404)\n        return method(self, *args, **kwargs)\n\n    return wrapper\n\n\ndef addslash(\n    method: Callable[..., Awaitable[None] | None],\n) -> Callable[..., Awaitable[None] | None]:\n    \"\"\"Use this decorator to add a missing trailing slash to the request path.\n\n    For example, a request to ``/foo`` would redirect to ``/foo/`` with this\n    decorator. Your request handler mapping should use a regular expression\n    like ``r'/foo/?'`` in conjunction with using the decorator.\n    \"\"\"\n\n    @functools.wraps(method)\n    def wrapper(  # type: ignore\n        self: RequestHandler, *args, **kwargs\n    ) -> Awaitable[None] | None:\n        if not self.request.path.endswith(\"/\"):\n            if self.request.method in (\"GET\", \"HEAD\"):\n                uri = self.request.path + \"/\"\n                if self.request.query:\n                    uri += \"?\" + self.request.query\n                self.redirect(uri, permanent=True)\n                return None\n            raise HTTPError(404)\n        return method(self, *args, **kwargs)\n\n    return wrapper\n\n\nclass _ApplicationRouter(ReversibleRuleRouter):\n    \"\"\"Routing implementation used internally by `Application`.\n\n    Provides a binding between `Application` and `RequestHandler`.\n    This implementation extends `~.routing.ReversibleRuleRouter` in a couple of ways:\n        * it allows to use `RequestHandler` subclasses as `~.routing.Rule` target and\n        * it allows to use a list/tuple of rules as `~.routing.Rule` target.\n        ``process_rule`` implementation will substitute this list with an appropriate\n        `_ApplicationRouter` instance.\n    \"\"\"\n\n    def __init__(\n        self, application: \"Application\", rules: _RuleList | None = None\n    ) -> None:\n        assert isinstance(application, Application)\n        self.application = application\n        super().__init__(rules)\n\n    def process_rule(self, rule: Rule) -> Rule:\n        rule = super().process_rule(rule)\n\n        if isinstance(rule.target, (list, tuple)):\n            rule.target = _ApplicationRouter(\n                self.application, rule.target  # type: ignore\n            )\n\n        return rule\n\n    def get_target_delegate(\n        self, target: Any, request: httputil.HTTPServerRequest, **target_params: Any\n    ) -> httputil.HTTPMessageDelegate | None:\n        if isclass(target) and issubclass(target, RequestHandler):\n            return self.application.get_handler_delegate(\n                request, target, **target_params\n            )\n\n        return super().get_target_delegate(target, request, **target_params)\n\n\nclass Application(ReversibleRouter):\n    r\"\"\"A collection of request handlers that make up a web application.\n\n    Instances of this class are callable and can be passed directly to\n    HTTPServer to serve the application::\n\n        application = web.Application([\n            (r\"/\", MainPageHandler),\n        ])\n        http_server = httpserver.HTTPServer(application)\n        http_server.listen(8080)\n\n    The constructor for this class takes in a list of `~.routing.Rule`\n    objects or tuples of values corresponding to the arguments of\n    `~.routing.Rule` constructor: ``(matcher, target, [target_kwargs], [name])``,\n    the values in square brackets being optional. The default matcher is\n    `~.routing.PathMatches`, so ``(regexp, target)`` tuples can also be used\n    instead of ``(PathMatches(regexp), target)``.\n\n    A common routing target is a `RequestHandler` subclass, but you can also\n    use lists of rules as a target, which create a nested routing configuration::\n\n        application = web.Application([\n            (HostMatches(\"example.com\"), [\n                (r\"/\", MainPageHandler),\n                (r\"/feed\", FeedHandler),\n            ]),\n        ])\n\n    In addition to this you can use nested `~.routing.Router` instances,\n    `~.httputil.HTTPMessageDelegate` subclasses and callables as routing targets\n    (see `~.routing` module docs for more information).\n\n    When we receive requests, we iterate over the list in order and\n    instantiate an instance of the first request class whose regexp\n    matches the request path. The request class can be specified as\n    either a class object or a (fully-qualified) name.\n\n    A dictionary may be passed as the third element (``target_kwargs``)\n    of the tuple, which will be used as keyword arguments to the handler's\n    constructor and `~RequestHandler.initialize` method. This pattern\n    is used for the `StaticFileHandler` in this example (note that a\n    `StaticFileHandler` can be installed automatically with the\n    static_path setting described below)::\n\n        application = web.Application([\n            (r\"/static/(.*)\", web.StaticFileHandler, {\"path\": \"/var/www\"}),\n        ])\n\n    We support virtual hosts with the `add_handlers` method, which takes in\n    a host regular expression as the first argument::\n\n        application.add_handlers(r\"www\\.myhost\\.com\", [\n            (r\"/article/([0-9]+)\", ArticleHandler),\n        ])\n\n    If there's no match for the current request's host, then ``default_host``\n    parameter value is matched against host regular expressions.\n\n\n    .. warning::\n\n       Applications that do not use TLS may be vulnerable to :ref:`DNS\n       rebinding <dnsrebinding>` attacks. This attack is especially\n       relevant to applications that only listen on ``127.0.0.1`` or\n       other private networks. Appropriate host patterns must be used\n       (instead of the default of ``r'.*'``) to prevent this risk. The\n       ``default_host`` argument must not be used in applications that\n       may be vulnerable to DNS rebinding.\n\n    You can serve static files by sending the ``static_path`` setting\n    as a keyword argument. We will serve those files from the\n    ``/static/`` URI (this is configurable with the\n    ``static_url_prefix`` setting), and we will serve ``/favicon.ico``\n    and ``/robots.txt`` from the same directory.  A custom subclass of\n    `StaticFileHandler` can be specified with the\n    ``static_handler_class`` setting.\n\n    .. versionchanged:: 4.5\n       Integration with the new `tornado.routing` module.\n\n    \"\"\"\n\n    def __init__(\n        self,\n        handlers: _RuleList | None = None,\n        default_host: str | None = None,\n        transforms: list[type[\"OutputTransform\"]] | None = None,\n        **settings: Any,\n    ) -> None:\n        if transforms is None:\n            self.transforms: list[type[OutputTransform]] = []\n            if settings.get(\"compress_response\") or settings.get(\"gzip\"):\n                self.transforms.append(GZipContentEncoding)\n        else:\n            self.transforms = transforms\n        self.default_host = default_host\n        self.settings = settings\n        self.ui_modules = {\n            \"linkify\": _linkify,\n            \"xsrf_form_html\": _xsrf_form_html,\n            \"Template\": TemplateModule,\n        }\n        self.ui_methods: dict[str, Callable[..., str]] = {}\n        self._load_ui_modules(settings.get(\"ui_modules\", {}))\n        self._load_ui_methods(settings.get(\"ui_methods\", {}))\n        if self.settings.get(\"static_path\"):\n            path = self.settings[\"static_path\"]\n            handlers = list(handlers or [])\n            static_url_prefix = settings.get(\"static_url_prefix\", \"/static/\")\n            static_handler_class = settings.get(\n                \"static_handler_class\", StaticFileHandler\n            )\n            static_handler_args = settings.get(\"static_handler_args\", {})\n            static_handler_args[\"path\"] = path\n            for pattern in [\n                re.escape(static_url_prefix) + r\"(.*)\",\n                r\"/(favicon\\.ico)\",\n                r\"/(robots\\.txt)\",\n            ]:\n                handlers.insert(0, (pattern, static_handler_class, static_handler_args))\n\n        if self.settings.get(\"debug\"):\n            self.settings.setdefault(\"autoreload\", True)\n            self.settings.setdefault(\"compiled_template_cache\", False)\n            self.settings.setdefault(\"static_hash_cache\", False)\n            self.settings.setdefault(\"serve_traceback\", True)\n\n        self.wildcard_router = _ApplicationRouter(self, handlers)\n        self.default_router = _ApplicationRouter(\n            self, [Rule(AnyMatches(), self.wildcard_router)]\n        )\n\n        # Automatically reload modified modules\n        if self.settings.get(\"autoreload\"):\n            from tornado import autoreload\n\n            autoreload.start()\n\n    def listen(\n        self,\n        port: int,\n        address: str | None = None,\n        *,\n        family: socket.AddressFamily = socket.AF_UNSPEC,\n        backlog: int = tornado.netutil._DEFAULT_BACKLOG,\n        flags: int | None = None,\n        reuse_port: bool = False,\n        **kwargs: Any,\n    ) -> HTTPServer:\n        \"\"\"Starts an HTTP server for this application on the given port.\n\n        This is a convenience alias for creating an `.HTTPServer` object and\n        calling its listen method.  Keyword arguments not supported by\n        `HTTPServer.listen <.TCPServer.listen>` are passed to the `.HTTPServer`\n        constructor.  For advanced uses (e.g. multi-process mode), do not use\n        this method; create an `.HTTPServer` and call its\n        `.TCPServer.bind`/`.TCPServer.start` methods directly.\n\n        Note that after calling this method you still need to call\n        ``IOLoop.current().start()`` (or run within ``asyncio.run``) to start\n        the server.\n\n        Returns the `.HTTPServer` object.\n\n        .. versionchanged:: 4.3\n           Now returns the `.HTTPServer` object.\n\n        .. versionchanged:: 6.2\n           Added support for new keyword arguments in `.TCPServer.listen`,\n           including ``reuse_port``.\n        \"\"\"\n        server = HTTPServer(self, **kwargs)\n        server.listen(\n            port,\n            address=address,\n            family=family,\n            backlog=backlog,\n            flags=flags,\n            reuse_port=reuse_port,\n        )\n        return server\n\n    def add_handlers(self, host_pattern: str, host_handlers: _RuleList) -> None:\n        \"\"\"Appends the given handlers to our handler list.\n\n        Host patterns are processed sequentially in the order they were\n        added. All matching patterns will be considered.\n        \"\"\"\n        host_matcher = HostMatches(host_pattern)\n        rule = Rule(host_matcher, _ApplicationRouter(self, host_handlers))\n\n        self.default_router.rules.insert(-1, rule)\n\n        if self.default_host is not None:\n            self.wildcard_router.add_rules(\n                [(DefaultHostMatches(self, host_matcher.host_pattern), host_handlers)]\n            )\n\n    def add_transform(self, transform_class: type[\"OutputTransform\"]) -> None:\n        self.transforms.append(transform_class)\n\n    def _load_ui_methods(self, methods: Any) -> None:\n        if isinstance(methods, types.ModuleType):\n            self._load_ui_methods({n: getattr(methods, n) for n in dir(methods)})\n        elif isinstance(methods, list):\n            for m in methods:\n                self._load_ui_methods(m)\n        else:\n            for name, fn in methods.items():\n                if (\n                    not name.startswith(\"_\")\n                    and hasattr(fn, \"__call__\")\n                    and name[0].lower() == name[0]\n                ):\n                    self.ui_methods[name] = fn\n\n    def _load_ui_modules(self, modules: Any) -> None:\n        if isinstance(modules, types.ModuleType):\n            self._load_ui_modules({n: getattr(modules, n) for n in dir(modules)})\n        elif isinstance(modules, list):\n            for m in modules:\n                self._load_ui_modules(m)\n        else:\n            assert isinstance(modules, dict)\n            for name, cls in modules.items():\n                try:\n                    if issubclass(cls, UIModule):\n                        self.ui_modules[name] = cls\n                except TypeError:\n                    pass\n\n    def __call__(self, request: httputil.HTTPServerRequest) -> Awaitable[None] | None:\n        # Legacy HTTPServer interface\n        dispatcher = self.find_handler(request)\n        return dispatcher.execute()\n\n    def find_handler(\n        self, request: httputil.HTTPServerRequest, **kwargs: Any\n    ) -> \"_HandlerDelegate\":\n        route = self.default_router.find_handler(request)\n        if route is not None:\n            return cast(\"_HandlerDelegate\", route)\n\n        if self.settings.get(\"default_handler_class\"):\n            return self.get_handler_delegate(\n                request,\n                self.settings[\"default_handler_class\"],\n                self.settings.get(\"default_handler_args\", {}),\n            )\n\n        return self.get_handler_delegate(request, ErrorHandler, {\"status_code\": 404})\n\n    def get_handler_delegate(\n        self,\n        request: httputil.HTTPServerRequest,\n        target_class: type[RequestHandler],\n        target_kwargs: dict[str, Any] | None = None,\n        path_args: list[bytes] | None = None,\n        path_kwargs: dict[str, bytes] | None = None,\n    ) -> \"_HandlerDelegate\":\n        \"\"\"Returns `~.httputil.HTTPMessageDelegate` that can serve a request\n        for application and `RequestHandler` subclass.\n\n        :arg httputil.HTTPServerRequest request: current HTTP request.\n        :arg RequestHandler target_class: a `RequestHandler` class.\n        :arg dict target_kwargs: keyword arguments for ``target_class`` constructor.\n        :arg list path_args: positional arguments for ``target_class`` HTTP method that\n            will be executed while handling a request (``get``, ``post`` or any other).\n        :arg dict path_kwargs: keyword arguments for ``target_class`` HTTP method.\n        \"\"\"\n        return _HandlerDelegate(\n            self, request, target_class, target_kwargs, path_args, path_kwargs\n        )\n\n    def reverse_url(self, name: str, *args: Any) -> str:\n        \"\"\"Returns a URL path for handler named ``name``\n\n        The handler must be added to the application as a named `URLSpec`.\n\n        Args will be substituted for capturing groups in the `URLSpec` regex.\n        They will be converted to strings if necessary, encoded as utf8,\n        and url-escaped.\n        \"\"\"\n        reversed_url = self.default_router.reverse_url(name, *args)\n        if reversed_url is not None:\n            return reversed_url\n\n        raise KeyError(\"%s not found in named urls\" % name)\n\n    def log_request(self, handler: RequestHandler) -> None:\n        \"\"\"Writes a completed HTTP request to the logs.\n\n        By default writes to the python root logger.  To change\n        this behavior either subclass Application and override this method,\n        or pass a function in the application settings dictionary as\n        ``log_function``.\n        \"\"\"\n        if \"log_function\" in self.settings:\n            self.settings[\"log_function\"](handler)\n            return\n        if handler.get_status() < 400:\n            log_method = access_log.info\n        elif handler.get_status() < 500:\n            log_method = access_log.warning\n        else:\n            log_method = access_log.error\n        request_time = 1000.0 * handler.request.request_time()\n        log_method(\n            \"%d %s %.2fms\",\n            handler.get_status(),\n            handler._request_summary(),\n            request_time,\n        )\n\n\nclass _HandlerDelegate(httputil.HTTPMessageDelegate):\n    def __init__(\n        self,\n        application: Application,\n        request: httputil.HTTPServerRequest,\n        handler_class: type[RequestHandler],\n        handler_kwargs: dict[str, Any] | None,\n        path_args: list[bytes] | None,\n        path_kwargs: dict[str, bytes] | None,\n    ) -> None:\n        self.application = application\n        self.connection = request.connection\n        self.request = request\n        self.handler_class = handler_class\n        self.handler_kwargs = handler_kwargs or {}\n        self.path_args = path_args or []\n        self.path_kwargs = path_kwargs or {}\n        self.chunks: list[bytes] = []\n        self.stream_request_body = _has_stream_request_body(self.handler_class)\n\n    def headers_received(\n        self,\n        start_line: httputil.RequestStartLine | httputil.ResponseStartLine,\n        headers: httputil.HTTPHeaders,\n    ) -> Awaitable[None] | None:\n        if self.stream_request_body:\n            self.request._body_future = Future()\n            return self.execute()\n        return None\n\n    def data_received(self, data: bytes) -> Awaitable[None] | None:\n        if self.stream_request_body:\n            return self.handler.data_received(data)\n        else:\n            self.chunks.append(data)\n            return None\n\n    def finish(self) -> None:\n        if self.stream_request_body:\n            future_set_result_unless_cancelled(self.request._body_future, None)\n        else:\n            # Note that the body gets parsed in RequestHandler._execute so it can be in\n            # the right exception handler scope.\n            self.request.body = b\"\".join(self.chunks)\n            self.execute()\n\n    def on_connection_close(self) -> None:\n        if self.stream_request_body:\n            self.handler.on_connection_close()\n        else:\n            self.chunks = None  # type: ignore\n\n    def execute(self) -> Awaitable[None] | None:\n        # If template cache is disabled (usually in the debug mode),\n        # re-compile templates and reload static files on every\n        # request so you don't need to restart to see changes\n        if not self.application.settings.get(\"compiled_template_cache\", True):\n            with RequestHandler._template_loader_lock:\n                for loader in RequestHandler._template_loaders.values():\n                    loader.reset()\n        if not self.application.settings.get(\"static_hash_cache\", True):\n            static_handler_class = self.application.settings.get(\n                \"static_handler_class\", StaticFileHandler\n            )\n            static_handler_class.reset()\n\n        self.handler = self.handler_class(\n            self.application, self.request, **self.handler_kwargs\n        )\n        transforms = [t(self.request) for t in self.application.transforms]\n\n        if self.stream_request_body:\n            self.handler._prepared_future = Future()\n        # Note that if an exception escapes handler._execute it will be\n        # trapped in the Future it returns (which we are ignoring here,\n        # leaving it to be logged when the Future is GC'd).\n        # However, that shouldn't happen because _execute has a blanket\n        # except handler, and we cannot easily access the IOLoop here to\n        # call add_future (because of the requirement to remain compatible\n        # with WSGI)\n        fut = gen.convert_yielded(\n            self.handler._execute(transforms, *self.path_args, **self.path_kwargs)\n        )\n        fut.add_done_callback(lambda f: f.result())\n        # If we are streaming the request body, then execute() is finished\n        # when the handler has prepared to receive the body.  If not,\n        # it doesn't matter when execute() finishes (so we return None)\n        return self.handler._prepared_future\n\n\nclass HTTPError(Exception):\n    \"\"\"An exception that will turn into an HTTP error response.\n\n    Raising an `HTTPError` is a convenient alternative to calling\n    `RequestHandler.send_error` since it automatically ends the\n    current function.\n\n    To customize the response sent with an `HTTPError`, override\n    `RequestHandler.write_error`.\n\n    :arg int status_code: HTTP status code.  Must be listed in\n        `httplib.responses <http.client.responses>` unless the ``reason``\n        keyword argument is given.\n    :arg str log_message: Message to be written to the log for this error\n        (will not be shown to the user unless the `Application` is in debug\n        mode).  May contain ``%s``-style placeholders, which will be filled\n        in with remaining positional parameters.\n    :arg str reason: Keyword-only argument.  The HTTP \"reason\" phrase\n        to pass in the status line along with ``status_code`` (for example,\n        the \"Not Found\" in ``HTTP/1.1 404 Not Found``).  Normally\n        determined automatically from ``status_code``, but can be used\n        to use a non-standard numeric code. This is not a general-purpose\n        error message.\n    \"\"\"\n\n    def __init__(\n        self,\n        status_code: int = 500,\n        log_message: str | None = None,\n        *args: Any,\n        **kwargs: Any,\n    ) -> None:\n        self.status_code = status_code\n        self._log_message = log_message\n        self.args = args\n        self.reason = kwargs.get(\"reason\", None)\n\n    @property\n    def log_message(self) -> str | None:\n        \"\"\"\n        A backwards compatible way of accessing log_message.\n        \"\"\"\n        if self._log_message and not self.args:\n            return self._log_message.replace(\"%\", \"%%\")\n        return self._log_message\n\n    def get_message(self) -> str | None:\n        if self._log_message and self.args:\n            return self._log_message % self.args\n        return self._log_message\n\n    def __str__(self) -> str:\n        message = \"HTTP %d: %s\" % (\n            self.status_code,\n            self.reason or httputil.responses.get(self.status_code, \"Unknown\"),\n        )\n        log_message = self.get_message()\n        if log_message:\n            return message + \" (\" + log_message + \")\"\n        else:\n            return message\n\n\nclass Finish(Exception):\n    \"\"\"An exception that ends the request without producing an error response.\n\n    When `Finish` is raised in a `RequestHandler`, the request will\n    end (calling `RequestHandler.finish` if it hasn't already been\n    called), but the error-handling methods (including\n    `RequestHandler.write_error`) will not be called.\n\n    If `Finish()` was created with no arguments, the pending response\n    will be sent as-is. If `Finish()` was given an argument, that\n    argument will be passed to `RequestHandler.finish()`.\n\n    This can be a more convenient way to implement custom error pages\n    than overriding ``write_error`` (especially in library code)::\n\n        if self.current_user is None:\n            self.set_status(401)\n            self.set_header('WWW-Authenticate', 'Basic realm=\"something\"')\n            raise Finish()\n\n    .. versionchanged:: 4.3\n       Arguments passed to ``Finish()`` will be passed on to\n       `RequestHandler.finish`.\n    \"\"\"\n\n    pass\n\n\nclass MissingArgumentError(HTTPError):\n    \"\"\"Exception raised by `RequestHandler.get_argument`.\n\n    This is a subclass of `HTTPError`, so if it is uncaught a 400 response\n    code will be used instead of 500 (and a stack trace will not be logged).\n\n    .. versionadded:: 3.1\n    \"\"\"\n\n    def __init__(self, arg_name: str) -> None:\n        super().__init__(400, \"Missing argument %s\" % arg_name)\n        self.arg_name = arg_name\n\n\nclass ErrorHandler(RequestHandler):\n    \"\"\"Generates an error response with ``status_code`` for all requests.\"\"\"\n\n    def initialize(self, status_code: int) -> None:\n        self.set_status(status_code)\n\n    def prepare(self) -> None:\n        raise HTTPError(self._status_code)\n\n    def check_xsrf_cookie(self) -> None:\n        # POSTs to an ErrorHandler don't actually have side effects,\n        # so we don't need to check the xsrf token.  This allows POSTs\n        # to the wrong url to return a 404 instead of 403.\n        pass\n\n\nclass RedirectHandler(RequestHandler):\n    \"\"\"Redirects the client to the given URL for all GET requests.\n\n    You should provide the keyword argument ``url`` to the handler, e.g.::\n\n        application = web.Application([\n            (r\"/oldpath\", web.RedirectHandler, {\"url\": \"/newpath\"}),\n        ])\n\n    `RedirectHandler` supports regular expression substitutions. E.g., to\n    swap the first and second parts of a path while preserving the remainder::\n\n        application = web.Application([\n            (r\"/(.*?)/(.*?)/(.*)\", web.RedirectHandler, {\"url\": \"/{1}/{0}/{2}\"}),\n        ])\n\n    The final URL is formatted with `str.format` and the substrings that match\n    the capturing groups. In the above example, a request to \"/a/b/c\" would be\n    formatted like::\n\n        str.format(\"/{1}/{0}/{2}\", \"a\", \"b\", \"c\")  # -> \"/b/a/c\"\n\n    Use Python's :ref:`format string syntax <formatstrings>` to customize how\n    values are substituted.\n\n    .. versionchanged:: 4.5\n       Added support for substitutions into the destination URL.\n\n    .. versionchanged:: 5.0\n       If any query arguments are present, they will be copied to the\n       destination URL.\n    \"\"\"\n\n    def initialize(self, url: str, permanent: bool = True) -> None:\n        self._url = url\n        self._permanent = permanent\n\n    def get(self, *args: Any, **kwargs: Any) -> None:\n        to_url = self._url.format(*args, **kwargs)\n        if self.request.query_arguments:\n            # TODO: figure out typing for the next line.\n            to_url = httputil.url_concat(\n                to_url,\n                list(httputil.qs_to_qsl(self.request.query_arguments)),  # type: ignore\n            )\n        self.redirect(to_url, permanent=self._permanent)\n\n\nclass StaticFileHandler(RequestHandler):\n    \"\"\"A simple handler that can serve static content from a directory.\n\n    A `StaticFileHandler` is configured automatically if you pass the\n    ``static_path`` keyword argument to `Application`.  This handler\n    can be customized with the ``static_url_prefix``, ``static_handler_class``,\n    and ``static_handler_args`` settings.\n\n    To map an additional path to this handler for a static data directory\n    you would add a line to your application like::\n\n        application = web.Application([\n            (r\"/content/(.*)\", web.StaticFileHandler, {\"path\": \"/var/www\"}),\n        ])\n\n    The handler constructor requires a ``path`` argument, which specifies the\n    local root directory of the content to be served.\n\n    Note that a capture group in the regex is required to parse the value for\n    the ``path`` argument to the get() method (different than the constructor\n    argument above); see `URLSpec` for details.\n\n    To serve a file like ``index.html`` automatically when a directory is\n    requested, set ``static_handler_args=dict(default_filename=\"index.html\")``\n    in your application settings, or add ``default_filename`` as an initializer\n    argument for your ``StaticFileHandler``.\n\n    To maximize the effectiveness of browser caching, this class supports\n    versioned urls (by default using the argument ``?v=``).  If a version\n    is given, we instruct the browser to cache this file indefinitely.\n    `make_static_url` (also available as `RequestHandler.static_url`) can\n    be used to construct a versioned url.\n\n    This handler is intended primarily for use in development and light-duty\n    file serving; for heavy traffic it will be more efficient to use\n    a dedicated static file server (such as nginx or Apache).  We support\n    the HTTP ``Accept-Ranges`` mechanism to return partial content (because\n    some browsers require this functionality to be present to seek in\n    HTML5 audio or video).\n\n    **Subclassing notes**\n\n    This class is designed to be extensible by subclassing, but because\n    of the way static urls are generated with class methods rather than\n    instance methods, the inheritance patterns are somewhat unusual.\n    Be sure to use the ``@classmethod`` decorator when overriding a\n    class method.  Instance methods may use the attributes ``self.path``\n    ``self.absolute_path``, and ``self.modified``.\n\n    Subclasses should only override methods discussed in this section;\n    overriding other methods is error-prone.  Overriding\n    ``StaticFileHandler.get`` is particularly problematic due to the\n    tight coupling with ``compute_etag`` and other methods.\n\n    To change the way static urls are generated (e.g. to match the behavior\n    of another server or CDN), override `make_static_url`, `parse_url_path`,\n    `get_cache_time`, and/or `get_version`.\n\n    To replace all interaction with the filesystem (e.g. to serve\n    static content from a database), override `get_content`,\n    `get_content_size`, `get_modified_time`, `get_absolute_path`, and\n    `validate_absolute_path`.\n\n    .. versionchanged:: 3.1\n       Many of the methods for subclasses were added in Tornado 3.1.\n    \"\"\"\n\n    CACHE_MAX_AGE = 86400 * 365 * 10  # 10 years\n\n    _static_hashes: dict[str, str | None] = {}\n    _lock = threading.Lock()  # protects _static_hashes\n\n    def initialize(self, path: str, default_filename: str | None = None) -> None:\n        self.root = path\n        self.default_filename = default_filename\n\n    @classmethod\n    def reset(cls) -> None:\n        with cls._lock:\n            cls._static_hashes = {}\n\n    def head(self, path: str) -> Awaitable[None]:\n        return self.get(path, include_body=False)\n\n    async def get(self, path: str, include_body: bool = True) -> None:\n        # Set up our path instance variables.\n        self.path = self.parse_url_path(path)\n        del path  # make sure we don't refer to path instead of self.path again\n        absolute_path = self.get_absolute_path(self.root, self.path)\n        self.absolute_path = self.validate_absolute_path(self.root, absolute_path)\n        if self.absolute_path is None:\n            return\n\n        self.modified = self.get_modified_time()\n        self.set_headers()\n\n        if self.should_return_304():\n            self.set_status(304)\n            return\n\n        request_range = None\n        range_header = self.request.headers.get(\"Range\")\n        if range_header:\n            # As per RFC 2616 14.16, if an invalid Range header is specified,\n            # the request will be treated as if the header didn't exist.\n            request_range = httputil._parse_request_range(range_header)\n\n        size = self.get_content_size()\n        if request_range:\n            start, end = request_range\n            if start is not None and start < 0:\n                start += size\n                if start < 0:\n                    start = 0\n            if (\n                start is not None\n                and (start >= size or (end is not None and start >= end))\n            ) or end == 0:\n                # As per RFC 2616 14.35.1, a range is not satisfiable only: if\n                # the first requested byte is equal to or greater than the\n                # content, or when a suffix with length 0 is specified.\n                # https://tools.ietf.org/html/rfc7233#section-2.1\n                # A byte-range-spec is invalid if the last-byte-pos value is present\n                # and less than the first-byte-pos.\n                self.set_status(416)  # Range Not Satisfiable\n                self.set_header(\"Content-Type\", \"text/plain\")\n                self.set_header(\"Content-Range\", f\"bytes */{size}\")\n                return\n            if end is not None and end > size:\n                # Clients sometimes blindly use a large range to limit their\n                # download size; cap the endpoint at the actual file size.\n                end = size\n            # Note: only return HTTP 206 if less than the entire range has been\n            # requested. Not only is this semantically correct, but Chrome\n            # refuses to play audio if it gets an HTTP 206 in response to\n            # ``Range: bytes=0-``.\n            if size != (end or size) - (start or 0):\n                self.set_status(206)  # Partial Content\n                self.set_header(\n                    \"Content-Range\", httputil._get_content_range(start, end, size)\n                )\n        else:\n            start = end = None\n\n        if start is not None and end is not None:\n            content_length = end - start\n        elif end is not None:\n            content_length = end\n        elif start is not None:\n            content_length = size - start\n        else:\n            content_length = size\n        self.set_header(\"Content-Length\", content_length)\n\n        if include_body:\n            content = self.get_content(self.absolute_path, start, end)\n            if isinstance(content, bytes):\n                content = [content]\n            for chunk in content:\n                try:\n                    self.write(chunk)\n                    await self.flush()\n                except iostream.StreamClosedError:\n                    return\n        else:\n            assert self.request.method == \"HEAD\"\n\n    def compute_etag(self) -> str | None:\n        \"\"\"Sets the ``Etag`` header based on static url version.\n\n        This allows efficient ``If-None-Match`` checks against cached\n        versions, and sends the correct ``Etag`` for a partial response\n        (i.e. the same ``Etag`` as the full file).\n\n        .. versionadded:: 3.1\n        \"\"\"\n        assert self.absolute_path is not None\n        version_hash = self._get_cached_version(self.absolute_path)\n        if not version_hash:\n            return None\n        return f'\"{version_hash}\"'\n\n    def set_headers(self) -> None:\n        \"\"\"Sets the content and caching headers on the response.\n\n        .. versionadded:: 3.1\n        \"\"\"\n        self.set_header(\"Accept-Ranges\", \"bytes\")\n        self.set_etag_header()\n\n        if self.modified is not None:\n            self.set_header(\"Last-Modified\", self.modified)\n\n        content_type = self.get_content_type()\n        if content_type:\n            self.set_header(\"Content-Type\", content_type)\n\n        cache_time = self.get_cache_time(self.path, self.modified, content_type)\n        if cache_time > 0:\n            self.set_header(\n                \"Expires\",\n                datetime.datetime.now(datetime.timezone.utc)\n                + datetime.timedelta(seconds=cache_time),\n            )\n            self.set_header(\"Cache-Control\", \"max-age=\" + str(cache_time))\n\n        self.set_extra_headers(self.path)\n\n    def should_return_304(self) -> bool:\n        \"\"\"Returns True if the headers indicate that we should return 304.\n\n        .. versionadded:: 3.1\n        \"\"\"\n        # If client sent If-None-Match, use it, ignore If-Modified-Since\n        if self.request.headers.get(\"If-None-Match\"):\n            return self.check_etag_header()\n\n        # Check the If-Modified-Since, and don't send the result if the\n        # content has not been modified\n        ims_value = self.request.headers.get(\"If-Modified-Since\")\n        if ims_value is not None:\n            try:\n                if_since = email.utils.parsedate_to_datetime(ims_value)\n            except Exception:\n                return False\n            if if_since.tzinfo is None:\n                if_since = if_since.replace(tzinfo=datetime.timezone.utc)\n            assert self.modified is not None\n            if if_since >= self.modified:\n                return True\n\n        return False\n\n    @classmethod\n    def get_absolute_path(cls, root: str, path: str) -> str:\n        \"\"\"Returns the absolute location of ``path`` relative to ``root``.\n\n        ``root`` is the path configured for this `StaticFileHandler`\n        (in most cases the ``static_path`` `Application` setting).\n\n        This class method may be overridden in subclasses.  By default\n        it returns a filesystem path, but other strings may be used\n        as long as they are unique and understood by the subclass's\n        overridden `get_content`.\n\n        .. versionadded:: 3.1\n        \"\"\"\n        abspath = os.path.abspath(os.path.join(root, path))\n        return abspath\n\n    def validate_absolute_path(self, root: str, absolute_path: str) -> str | None:\n        \"\"\"Validate and return the absolute path.\n\n        ``root`` is the configured path for the `StaticFileHandler`,\n        and ``path`` is the result of `get_absolute_path`\n\n        This is an instance method called during request processing,\n        so it may raise `HTTPError` or use methods like\n        `RequestHandler.redirect` (return None after redirecting to\n        halt further processing).  This is where 404 errors for missing files\n        are generated.\n\n        This method may modify the path before returning it, but note that\n        any such modifications will not be understood by `make_static_url`.\n\n        In instance methods, this method's result is available as\n        ``self.absolute_path``.\n\n        .. versionadded:: 3.1\n        \"\"\"\n        # os.path.abspath strips a trailing /.\n        # We must add it back to `root` so that we only match files\n        # in a directory named `root` instead of files starting with\n        # that prefix.\n        root = os.path.abspath(root)\n        if not root.endswith(os.path.sep):\n            # abspath always removes a trailing slash, except when\n            # root is '/'. This is an unusual case, but several projects\n            # have independently discovered this technique to disable\n            # Tornado's path validation and (hopefully) do their own,\n            # so we need to support it.\n            root += os.path.sep\n        # The trailing slash also needs to be temporarily added back\n        # the requested path so a request to root/ will match.\n        if not (absolute_path + os.path.sep).startswith(root):\n            raise HTTPError(403, \"%s is not in root static directory\", self.path)\n        if os.path.isdir(absolute_path) and self.default_filename is not None:\n            # need to look at the request.path here for when path is empty\n            # but there is some prefix to the path that was already\n            # trimmed by the routing\n            if not self.request.path.endswith(\"/\"):\n                if self.request.path.startswith(\"//\"):\n                    # A redirect with two initial slashes is a \"protocol-relative\" URL.\n                    # This means the next path segment is treated as a hostname instead\n                    # of a part of the path, making this effectively an open redirect.\n                    # Reject paths starting with two slashes to prevent this.\n                    # This is only reachable under certain configurations.\n                    raise HTTPError(\n                        403, \"cannot redirect path with two initial slashes\"\n                    )\n                self.redirect(self.request.path + \"/\", permanent=True)\n                return None\n            absolute_path = os.path.join(absolute_path, self.default_filename)\n        if not os.path.exists(absolute_path):\n            raise HTTPError(404)\n        if not os.path.isfile(absolute_path):\n            raise HTTPError(403, \"%s is not a file\", self.path)\n        return absolute_path\n\n    @classmethod\n    def get_content(\n        cls, abspath: str, start: int | None = None, end: int | None = None\n    ) -> Generator[bytes, None, None]:\n        \"\"\"Retrieve the content of the requested resource which is located\n        at the given absolute path.\n\n        This class method may be overridden by subclasses.  Note that its\n        signature is different from other overridable class methods\n        (no ``settings`` argument); this is deliberate to ensure that\n        ``abspath`` is able to stand on its own as a cache key.\n\n        This method should either return a byte string or an iterator\n        of byte strings.  The latter is preferred for large files\n        as it helps reduce memory fragmentation.\n\n        .. versionadded:: 3.1\n        \"\"\"\n        with open(abspath, \"rb\") as file:\n            if start is not None:\n                file.seek(start)\n            if end is not None:\n                remaining: int | None = end - (start or 0)\n            else:\n                remaining = None\n            while True:\n                chunk_size = 64 * 1024\n                if remaining is not None and remaining < chunk_size:\n                    chunk_size = remaining\n                chunk = file.read(chunk_size)\n                if chunk:\n                    if remaining is not None:\n                        remaining -= len(chunk)\n                    yield chunk\n                else:\n                    if remaining is not None:\n                        assert remaining == 0\n                    return\n\n    @classmethod\n    def get_content_version(cls, abspath: str) -> str:\n        \"\"\"Returns a version string for the resource at the given path.\n\n        This class method may be overridden by subclasses.  The\n        default implementation is a SHA-512 hash of the file's contents.\n\n        .. versionadded:: 3.1\n        \"\"\"\n        data = cls.get_content(abspath)\n        hasher = hashlib.sha512()\n        if isinstance(data, bytes):\n            hasher.update(data)\n        else:\n            for chunk in data:\n                hasher.update(chunk)\n        return hasher.hexdigest()\n\n    def _stat(self) -> os.stat_result:\n        assert self.absolute_path is not None\n        if not hasattr(self, \"_stat_result\"):\n            self._stat_result = os.stat(self.absolute_path)\n        return self._stat_result\n\n    def get_content_size(self) -> int:\n        \"\"\"Retrieve the total size of the resource at the given path.\n\n        This method may be overridden by subclasses.\n\n        .. versionadded:: 3.1\n\n        .. versionchanged:: 4.0\n           This method is now always called, instead of only when\n           partial results are requested.\n        \"\"\"\n        stat_result = self._stat()\n        return stat_result.st_size\n\n    def get_modified_time(self) -> datetime.datetime | None:\n        \"\"\"Returns the time that ``self.absolute_path`` was last modified.\n\n        May be overridden in subclasses.  Should return a `~datetime.datetime`\n        object or None.\n\n        .. versionadded:: 3.1\n\n        .. versionchanged:: 6.4\n           Now returns an aware datetime object instead of a naive one.\n           Subclasses that override this method may return either kind.\n        \"\"\"\n        stat_result = self._stat()\n        # NOTE: Historically, this used stat_result[stat.ST_MTIME],\n        # which truncates the fractional portion of the timestamp. It\n        # was changed from that form to stat_result.st_mtime to\n        # satisfy mypy (which disallows the bracket operator), but the\n        # latter form returns a float instead of an int. For\n        # consistency with the past (and because we have a unit test\n        # that relies on this), we truncate the float here, although\n        # I'm not sure that's the right thing to do.\n        modified = datetime.datetime.fromtimestamp(\n            int(stat_result.st_mtime), datetime.timezone.utc\n        )\n        return modified\n\n    def get_content_type(self) -> str:\n        \"\"\"Returns the ``Content-Type`` header to be used for this request.\n\n        .. versionadded:: 3.1\n        \"\"\"\n        assert self.absolute_path is not None\n        mime_type, encoding = mimetypes.guess_type(self.absolute_path)\n        # per RFC 6713, use the appropriate type for a gzip compressed file\n        if encoding == \"gzip\":\n            return \"application/gzip\"\n        # As of 2015-07-21 there is no bzip2 encoding defined at\n        # http://www.iana.org/assignments/media-types/media-types.xhtml\n        # So for that (and any other encoding), use octet-stream.\n        elif encoding is not None:\n            return \"application/octet-stream\"\n        elif mime_type is not None:\n            return mime_type\n        # if mime_type not detected, use application/octet-stream\n        else:\n            return \"application/octet-stream\"\n\n    def set_extra_headers(self, path: str) -> None:\n        \"\"\"For subclass to add extra headers to the response\"\"\"\n        pass\n\n    def get_cache_time(\n        self, path: str, modified: datetime.datetime | None, mime_type: str\n    ) -> int:\n        \"\"\"Override to customize cache control behavior.\n\n        Return a positive number of seconds to make the result\n        cacheable for that amount of time or 0 to mark resource as\n        cacheable for an unspecified amount of time (subject to\n        browser heuristics).\n\n        By default returns cache expiry of 10 years for resources requested\n        with ``v`` argument.\n        \"\"\"\n        return self.CACHE_MAX_AGE if \"v\" in self.request.arguments else 0\n\n    @classmethod\n    def make_static_url(\n        cls, settings: dict[str, Any], path: str, include_version: bool = True\n    ) -> str:\n        \"\"\"Constructs a versioned url for the given path.\n\n        This method may be overridden in subclasses (but note that it\n        is a class method rather than an instance method).  Subclasses\n        are only required to implement the signature\n        ``make_static_url(cls, settings, path)``; other keyword\n        arguments may be passed through `~RequestHandler.static_url`\n        but are not standard.\n\n        ``settings`` is the `Application.settings` dictionary.  ``path``\n        is the static path being requested.  The url returned should be\n        relative to the current host.\n\n        ``include_version`` determines whether the generated URL should\n        include the query string containing the version hash of the\n        file corresponding to the given ``path``.\n\n        \"\"\"\n        url = settings.get(\"static_url_prefix\", \"/static/\") + path\n        if not include_version:\n            return url\n\n        version_hash = cls.get_version(settings, path)\n        if not version_hash:\n            return url\n\n        return f\"{url}?v={version_hash}\"\n\n    def parse_url_path(self, url_path: str) -> str:\n        \"\"\"Converts a static URL path into a filesystem path.\n\n        ``url_path`` is the path component of the URL with\n        ``static_url_prefix`` removed.  The return value should be\n        filesystem path relative to ``static_path``.\n\n        This is the inverse of `make_static_url`.\n        \"\"\"\n        if os.path.sep != \"/\":\n            url_path = url_path.replace(\"/\", os.path.sep)\n        return url_path\n\n    @classmethod\n    def get_version(cls, settings: dict[str, Any], path: str) -> str | None:\n        \"\"\"Generate the version string to be used in static URLs.\n\n        ``settings`` is the `Application.settings` dictionary and ``path``\n        is the relative location of the requested asset on the filesystem.\n        The returned value should be a string, or ``None`` if no version\n        could be determined.\n\n        .. versionchanged:: 3.1\n           This method was previously recommended for subclasses to override;\n           `get_content_version` is now preferred as it allows the base\n           class to handle caching of the result.\n        \"\"\"\n        abs_path = cls.get_absolute_path(settings[\"static_path\"], path)\n        return cls._get_cached_version(abs_path)\n\n    @classmethod\n    def _get_cached_version(cls, abs_path: str) -> str | None:\n        with cls._lock:\n            hashes = cls._static_hashes\n            if abs_path not in hashes:\n                try:\n                    hashes[abs_path] = cls.get_content_version(abs_path)\n                except Exception:\n                    gen_log.error(\"Could not open static file %r\", abs_path)\n                    hashes[abs_path] = None\n            hsh = hashes.get(abs_path)\n            if hsh:\n                return hsh\n        return None\n\n\nclass FallbackHandler(RequestHandler):\n    \"\"\"A `RequestHandler` that wraps another HTTP server callback.\n\n    The fallback is a callable object that accepts an\n    `~.httputil.HTTPServerRequest`, such as an `Application` or\n    `tornado.wsgi.WSGIContainer`.  This is most useful to use both\n    Tornado ``RequestHandlers`` and WSGI in the same server.  Typical\n    usage::\n\n        wsgi_app = tornado.wsgi.WSGIContainer(\n            django.core.handlers.wsgi.WSGIHandler())\n        application = tornado.web.Application([\n            (r\"/foo\", FooHandler),\n            (r\".*\", FallbackHandler, dict(fallback=wsgi_app)),\n        ])\n    \"\"\"\n\n    def initialize(\n        self, fallback: Callable[[httputil.HTTPServerRequest], None]\n    ) -> None:\n        self.fallback = fallback\n\n    def prepare(self) -> None:\n        self.fallback(self.request)\n        self._finished = True\n        self.on_finish()\n\n\nclass OutputTransform:\n    \"\"\"A transform modifies the result of an HTTP request (e.g., GZip encoding)\n\n    Applications are not expected to create their own OutputTransforms\n    or interact with them directly; the framework chooses which transforms\n    (if any) to apply.\n    \"\"\"\n\n    def __init__(self, request: httputil.HTTPServerRequest) -> None:\n        pass\n\n    def transform_first_chunk(\n        self,\n        status_code: int,\n        headers: httputil.HTTPHeaders,\n        chunk: bytes,\n        finishing: bool,\n    ) -> tuple[int, httputil.HTTPHeaders, bytes]:\n        return status_code, headers, chunk\n\n    def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes:\n        return chunk\n\n\nclass GZipContentEncoding(OutputTransform):\n    \"\"\"Applies the gzip content encoding to the response.\n\n    See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11\n\n    .. versionchanged:: 4.0\n        Now compresses all mime types beginning with ``text/``, instead\n        of just a whitelist. (the whitelist is still used for certain\n        non-text mime types).\n    \"\"\"\n\n    # Whitelist of compressible mime types (in addition to any types\n    # beginning with \"text/\").\n    CONTENT_TYPES = {\n        \"application/javascript\",\n        \"application/x-javascript\",\n        \"application/xml\",\n        \"application/atom+xml\",\n        \"application/json\",\n        \"application/xhtml+xml\",\n        \"image/svg+xml\",\n    }\n    # Python's GzipFile defaults to level 9, while most other gzip\n    # tools (including gzip itself) default to 6, which is probably a\n    # better CPU/size tradeoff.\n    GZIP_LEVEL = 6\n    # Responses that are too short are unlikely to benefit from gzipping\n    # after considering the \"Content-Encoding: gzip\" header and the header\n    # inside the gzip encoding.\n    # Note that responses written in multiple chunks will be compressed\n    # regardless of size.\n    MIN_LENGTH = 1024\n\n    def __init__(self, request: httputil.HTTPServerRequest) -> None:\n        self._gzipping = \"gzip\" in request.headers.get(\"Accept-Encoding\", \"\")\n\n    def _compressible_type(self, ctype: str) -> bool:\n        return ctype.startswith(\"text/\") or ctype in self.CONTENT_TYPES\n\n    def transform_first_chunk(\n        self,\n        status_code: int,\n        headers: httputil.HTTPHeaders,\n        chunk: bytes,\n        finishing: bool,\n    ) -> tuple[int, httputil.HTTPHeaders, bytes]:\n        # TODO: can/should this type be inherited from the superclass?\n        if \"Vary\" in headers:\n            headers[\"Vary\"] += \", Accept-Encoding\"\n        else:\n            headers[\"Vary\"] = \"Accept-Encoding\"\n        if self._gzipping:\n            ctype = _unicode(headers.get(\"Content-Type\", \"\")).split(\";\")[0]\n            self._gzipping = (\n                self._compressible_type(ctype)\n                and (not finishing or len(chunk) >= self.MIN_LENGTH)\n                and (\"Content-Encoding\" not in headers)\n            )\n        if self._gzipping:\n            headers[\"Content-Encoding\"] = \"gzip\"\n            self._gzip_value = BytesIO()\n            self._gzip_file = gzip.GzipFile(\n                mode=\"w\", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL\n            )\n            chunk = self.transform_chunk(chunk, finishing)\n            if \"Content-Length\" in headers:\n                # The original content length is no longer correct.\n                # If this is the last (and only) chunk, we can set the new\n                # content-length; otherwise we remove it and fall back to\n                # chunked encoding.\n                if finishing:\n                    headers[\"Content-Length\"] = str(len(chunk))\n                else:\n                    del headers[\"Content-Length\"]\n        return status_code, headers, chunk\n\n    def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes:\n        if self._gzipping:\n            self._gzip_file.write(chunk)\n            if finishing:\n                self._gzip_file.close()\n            else:\n                self._gzip_file.flush()\n            chunk = self._gzip_value.getvalue()\n            self._gzip_value.truncate(0)\n            self._gzip_value.seek(0)\n        return chunk\n\n\ndef authenticated(\n    method: Callable[..., Awaitable[None] | None],\n) -> Callable[..., Awaitable[None] | None]:\n    \"\"\"Decorate methods with this to require that the user be logged in.\n\n    If the user is not logged in, they will be redirected to the configured\n    `login url <RequestHandler.get_login_url>`.\n\n    If you configure a login url with a query parameter, Tornado will\n    assume you know what you're doing and use it as-is.  If not, it\n    will add a `next` parameter so the login page knows where to send\n    you once you're logged in.\n    \"\"\"\n\n    @functools.wraps(method)\n    def wrapper(  # type: ignore\n        self: RequestHandler, *args, **kwargs\n    ) -> Awaitable[None] | None:\n        if not self.current_user:\n            if self.request.method in (\"GET\", \"HEAD\"):\n                url = self.get_login_url()\n                if \"?\" not in url:\n                    if urllib.parse.urlsplit(url).scheme:\n                        # if login url is absolute, make next absolute too\n                        next_url = self.request.full_url()\n                    else:\n                        assert self.request.uri is not None\n                        next_url = self.request.uri\n                    url += \"?\" + urlencode(dict(next=next_url))\n                self.redirect(url)\n                return None\n            raise HTTPError(403)\n        return method(self, *args, **kwargs)\n\n    return wrapper\n\n\nclass UIModule:\n    \"\"\"A re-usable, modular UI unit on a page.\n\n    UI modules often execute additional queries, and they can include\n    additional CSS and JavaScript that will be included in the output\n    page, which is automatically inserted on page render.\n\n    Subclasses of UIModule must override the `render` method.\n    \"\"\"\n\n    def __init__(self, handler: RequestHandler) -> None:\n        self.handler = handler\n        self.request = handler.request\n        self.ui = handler.ui\n        self.locale = handler.locale\n\n    @property\n    def current_user(self) -> Any:\n        return self.handler.current_user\n\n    def render(self, *args: Any, **kwargs: Any) -> str | bytes:\n        \"\"\"Override in subclasses to return this module's output.\"\"\"\n        raise NotImplementedError()\n\n    def embedded_javascript(self) -> str | None:\n        \"\"\"Override to return a JavaScript string\n        to be embedded in the page.\"\"\"\n        return None\n\n    def javascript_files(self) -> Iterable[str] | None:\n        \"\"\"Override to return a list of JavaScript files needed by this module.\n\n        If the return values are relative paths, they will be passed to\n        `RequestHandler.static_url`; otherwise they will be used as-is.\n        \"\"\"\n        return None\n\n    def embedded_css(self) -> str | None:\n        \"\"\"Override to return a CSS string\n        that will be embedded in the page.\"\"\"\n        return None\n\n    def css_files(self) -> Iterable[str] | None:\n        \"\"\"Override to returns a list of CSS files required by this module.\n\n        If the return values are relative paths, they will be passed to\n        `RequestHandler.static_url`; otherwise they will be used as-is.\n        \"\"\"\n        return None\n\n    def html_head(self) -> str | None:\n        \"\"\"Override to return an HTML string that will be put in the <head/>\n        element.\n        \"\"\"\n        return None\n\n    def html_body(self) -> str | None:\n        \"\"\"Override to return an HTML string that will be put at the end of\n        the <body/> element.\n        \"\"\"\n        return None\n\n    def render_string(self, path: str, **kwargs: Any) -> bytes:\n        \"\"\"Renders a template and returns it as a string.\"\"\"\n        return self.handler.render_string(path, **kwargs)\n\n\nclass _linkify(UIModule):\n    def render(self, text: str, **kwargs: Any) -> str:\n        return escape.linkify(text, **kwargs)\n\n\nclass _xsrf_form_html(UIModule):\n    def render(self) -> str:\n        return self.handler.xsrf_form_html()\n\n\nclass TemplateModule(UIModule):\n    \"\"\"UIModule that simply renders the given template.\n\n    {% module Template(\"foo.html\") %} is similar to {% include \"foo.html\" %},\n    but the module version gets its own namespace (with kwargs passed to\n    Template()) instead of inheriting the outer template's namespace.\n\n    Templates rendered through this module also get access to UIModule's\n    automatic JavaScript/CSS features.  Simply call set_resources\n    inside the template and give it keyword arguments corresponding to\n    the methods on UIModule: {{ set_resources(js_files=static_url(\"my.js\")) }}\n    Note that these resources are output once per template file, not once\n    per instantiation of the template, so they must not depend on\n    any arguments to the template.\n    \"\"\"\n\n    def __init__(self, handler: RequestHandler) -> None:\n        super().__init__(handler)\n        # keep resources in both a list and a dict to preserve order\n        self._resource_list: list[dict[str, Any]] = []\n        self._resource_dict: dict[str, dict[str, Any]] = {}\n\n    def render(self, path: str, **kwargs: Any) -> bytes:\n        def set_resources(**kwargs) -> str:  # type: ignore\n            if path not in self._resource_dict:\n                self._resource_list.append(kwargs)\n                self._resource_dict[path] = kwargs\n            else:\n                if self._resource_dict[path] != kwargs:\n                    raise ValueError(\n                        \"set_resources called with different \"\n                        \"resources for the same template\"\n                    )\n            return \"\"\n\n        return self.render_string(path, set_resources=set_resources, **kwargs)\n\n    def _get_resources(self, key: str) -> Iterable[str]:\n        return (r[key] for r in self._resource_list if key in r)\n\n    def embedded_javascript(self) -> str:\n        return \"\\n\".join(self._get_resources(\"embedded_javascript\"))\n\n    def javascript_files(self) -> Iterable[str]:\n        result = []\n        for f in self._get_resources(\"javascript_files\"):\n            if isinstance(f, (unicode_type, bytes)):\n                result.append(f)\n            else:\n                result.extend(f)\n        return result\n\n    def embedded_css(self) -> str:\n        return \"\\n\".join(self._get_resources(\"embedded_css\"))\n\n    def css_files(self) -> Iterable[str]:\n        result = []\n        for f in self._get_resources(\"css_files\"):\n            if isinstance(f, (unicode_type, bytes)):\n                result.append(f)\n            else:\n                result.extend(f)\n        return result\n\n    def html_head(self) -> str:\n        return \"\".join(self._get_resources(\"html_head\"))\n\n    def html_body(self) -> str:\n        return \"\".join(self._get_resources(\"html_body\"))\n\n\nclass _UIModuleNamespace:\n    \"\"\"Lazy namespace which creates UIModule proxies bound to a handler.\"\"\"\n\n    def __init__(\n        self, handler: RequestHandler, ui_modules: dict[str, type[UIModule]]\n    ) -> None:\n        self.handler = handler\n        self.ui_modules = ui_modules\n\n    def __getitem__(self, key: str) -> Callable[..., str]:\n        return self.handler._ui_module(key, self.ui_modules[key])\n\n    def __getattr__(self, key: str) -> Callable[..., str]:\n        try:\n            return self[key]\n        except KeyError as e:\n            raise AttributeError(str(e))\n\n\ndef create_signed_value(\n    secret: _CookieSecretTypes,\n    name: str,\n    value: str | bytes,\n    version: int | None = None,\n    clock: Callable[[], float] | None = None,\n    key_version: int | None = None,\n) -> bytes:\n    if version is None:\n        version = DEFAULT_SIGNED_VALUE_VERSION\n    if clock is None:\n        clock = time.time\n\n    timestamp = utf8(str(int(clock())))\n    value = base64.b64encode(utf8(value))\n    if version == 1:\n        assert not isinstance(secret, dict)\n        signature = _create_signature_v1(secret, name, value, timestamp)\n        value = b\"|\".join([value, timestamp, signature])\n        return value\n    elif version == 2:\n        # The v2 format consists of a version number and a series of\n        # length-prefixed fields \"%d:%s\", the last of which is a\n        # signature, all separated by pipes.  All numbers are in\n        # decimal format with no leading zeros.  The signature is an\n        # HMAC-SHA256 of the whole string up to that point, including\n        # the final pipe.\n        #\n        # The fields are:\n        # - format version (i.e. 2; no length prefix)\n        # - key version (integer, default is 0)\n        # - timestamp (integer seconds since epoch)\n        # - name (not encoded; assumed to be ~alphanumeric)\n        # - value (base64-encoded)\n        # - signature (hex-encoded; no length prefix)\n        def format_field(s: str | bytes) -> bytes:\n            return utf8(\"%d:\" % len(s)) + utf8(s)\n\n        to_sign = b\"|\".join(\n            [\n                b\"2\",\n                format_field(str(key_version or 0)),\n                format_field(timestamp),\n                format_field(name),\n                format_field(value),\n                b\"\",\n            ]\n        )\n\n        if isinstance(secret, dict):\n            assert (\n                key_version is not None\n            ), \"Key version must be set when sign key dict is used\"\n            assert version >= 2, \"Version must be at least 2 for key version support\"\n            secret = secret[key_version]\n\n        signature = _create_signature_v2(secret, to_sign)\n        return to_sign + signature\n    else:\n        raise ValueError(\"Unsupported version %d\" % version)\n\n\n# A leading version number in decimal\n# with no leading zeros, followed by a pipe.\n_signed_value_version_re = re.compile(rb\"^([1-9][0-9]*)\\|(.*)$\")\n\n\ndef _get_version(value: bytes) -> int:\n    # Figures out what version value is.  Version 1 did not include an\n    # explicit version field and started with arbitrary base64 data,\n    # which makes this tricky.\n    m = _signed_value_version_re.match(value)\n    if m is None:\n        version = 1\n    else:\n        try:\n            version = int(m.group(1))\n            if version > 999:\n                # Certain payloads from the version-less v1 format may\n                # be parsed as valid integers.  Due to base64 padding\n                # restrictions, this can only happen for numbers whose\n                # length is a multiple of 4, so we can treat all\n                # numbers up to 999 as versions, and for the rest we\n                # fall back to v1 format.\n                version = 1\n        except ValueError:\n            version = 1\n    return version\n\n\ndef decode_signed_value(\n    secret: _CookieSecretTypes,\n    name: str,\n    value: None | str | bytes,\n    max_age_days: float = 31,\n    clock: Callable[[], float] | None = None,\n    min_version: int | None = None,\n) -> bytes | None:\n    if clock is None:\n        clock = time.time\n    if min_version is None:\n        min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION\n    if min_version > 2:\n        raise ValueError(\"Unsupported min_version %d\" % min_version)\n    if not value:\n        return None\n\n    value = utf8(value)\n    version = _get_version(value)\n\n    if version < min_version:\n        return None\n    if version == 1:\n        assert not isinstance(secret, dict)\n        return _decode_signed_value_v1(secret, name, value, max_age_days, clock)\n    elif version == 2:\n        return _decode_signed_value_v2(secret, name, value, max_age_days, clock)\n    else:\n        return None\n\n\ndef _decode_signed_value_v1(\n    secret: str | bytes,\n    name: str,\n    value: bytes,\n    max_age_days: float,\n    clock: Callable[[], float],\n) -> bytes | None:\n    parts = utf8(value).split(b\"|\")\n    if len(parts) != 3:\n        return None\n    signature = _create_signature_v1(secret, name, parts[0], parts[1])\n    if not hmac.compare_digest(parts[2], signature):\n        gen_log.warning(\"Invalid cookie signature %r\", value)\n        return None\n    timestamp = int(parts[1])\n    if timestamp < clock() - max_age_days * 86400:\n        gen_log.warning(\"Expired cookie %r\", value)\n        return None\n    if timestamp > clock() + 31 * 86400:\n        # _cookie_signature does not hash a delimiter between the\n        # parts of the cookie, so an attacker could transfer trailing\n        # digits from the payload to the timestamp without altering the\n        # signature.  For backwards compatibility, sanity-check timestamp\n        # here instead of modifying _cookie_signature.\n        gen_log.warning(\"Cookie timestamp in future; possible tampering %r\", value)\n        return None\n    if parts[1].startswith(b\"0\"):\n        gen_log.warning(\"Tampered cookie %r\", value)\n        return None\n    try:\n        return base64.b64decode(parts[0])\n    except Exception:\n        return None\n\n\ndef _decode_fields_v2(value: bytes) -> tuple[int, bytes, bytes, bytes, bytes]:\n    def _consume_field(s: bytes) -> tuple[bytes, bytes]:\n        length, _, rest = s.partition(b\":\")\n        n = int(length)\n        field_value = rest[:n]\n        # In python 3, indexing bytes returns small integers; we must\n        # use a slice to get a byte string as in python 2.\n        if rest[n : n + 1] != b\"|\":\n            raise ValueError(\"malformed v2 signed value field\")\n        rest = rest[n + 1 :]\n        return field_value, rest\n\n    rest = value[2:]  # remove version number\n    key_version, rest = _consume_field(rest)\n    timestamp, rest = _consume_field(rest)\n    name_field, rest = _consume_field(rest)\n    value_field, passed_sig = _consume_field(rest)\n    return int(key_version), timestamp, name_field, value_field, passed_sig\n\n\ndef _decode_signed_value_v2(\n    secret: _CookieSecretTypes,\n    name: str,\n    value: bytes,\n    max_age_days: float,\n    clock: Callable[[], float],\n) -> bytes | None:\n    try:\n        (\n            key_version,\n            timestamp_bytes,\n            name_field,\n            value_field,\n            passed_sig,\n        ) = _decode_fields_v2(value)\n    except ValueError:\n        return None\n    signed_string = value[: -len(passed_sig)]\n\n    if isinstance(secret, dict):\n        try:\n            secret = secret[key_version]\n        except KeyError:\n            return None\n\n    expected_sig = _create_signature_v2(secret, signed_string)\n    if not hmac.compare_digest(passed_sig, expected_sig):\n        return None\n    if name_field != utf8(name):\n        return None\n    timestamp = int(timestamp_bytes)\n    if timestamp < clock() - max_age_days * 86400:\n        # The signature has expired.\n        return None\n    try:\n        return base64.b64decode(value_field)\n    except Exception:\n        return None\n\n\ndef get_signature_key_version(value: str | bytes) -> int | None:\n    value = utf8(value)\n    version = _get_version(value)\n    if version < 2:\n        return None\n    try:\n        key_version, _, _, _, _ = _decode_fields_v2(value)\n    except ValueError:\n        return None\n\n    return key_version\n\n\ndef _create_signature_v1(secret: str | bytes, *parts: str | bytes) -> bytes:\n    hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)\n    for part in parts:\n        hash.update(utf8(part))\n    return utf8(hash.hexdigest())\n\n\ndef _create_signature_v2(secret: str | bytes, s: bytes) -> bytes:\n    hash = hmac.new(utf8(secret), digestmod=hashlib.sha256)\n    hash.update(utf8(s))\n    return utf8(hash.hexdigest())\n\n\ndef is_absolute(path: str) -> bool:\n    return any(path.startswith(x) for x in [\"/\", \"http:\", \"https:\"])\n"
  },
  {
    "path": "tornado/websocket.py",
    "content": "\"\"\"Implementation of the WebSocket protocol.\n\n`WebSockets <http://dev.w3.org/html5/websockets/>`_ allow for bidirectional\ncommunication between the browser and server. WebSockets are supported in the\ncurrent versions of all major browsers.\n\nThis module implements the final version of the WebSocket protocol as\ndefined in `RFC 6455 <http://tools.ietf.org/html/rfc6455>`_.\n\n.. versionchanged:: 4.0\n   Removed support for the draft 76 protocol version.\n\"\"\"\n\nimport abc\nimport asyncio\nimport base64\nimport functools\nimport hashlib\nimport logging\nimport os\nimport struct\nimport sys\nimport warnings\nimport zlib\nfrom collections.abc import Awaitable, Callable\nfrom types import TracebackType\nfrom typing import (\n    Any,\n    Optional,\n    Protocol,\n    Type,\n    Union,\n    cast,\n)\nfrom urllib.parse import urlparse\n\nimport tornado\nfrom tornado import gen, httpclient, httputil, simple_httpclient\nfrom tornado.concurrent import Future, future_set_result_unless_cancelled\nfrom tornado.escape import native_str, to_unicode, utf8\nfrom tornado.ioloop import IOLoop\nfrom tornado.iostream import IOStream, StreamClosedError\nfrom tornado.log import app_log, gen_log\nfrom tornado.netutil import Resolver\nfrom tornado.queues import Queue\nfrom tornado.tcpclient import TCPClient\nfrom tornado.util import _websocket_mask\n\n\n# The zlib compressor types aren't actually exposed anywhere\n# publicly, so declare protocols for the portions we use.\nclass _Compressor(Protocol):\n    def compress(self, data: bytes) -> bytes:\n        pass\n\n    def flush(self, mode: int) -> bytes:\n        pass\n\n\nclass _Decompressor(Protocol):\n    @property\n    def unconsumed_tail(self) -> bytes:\n        pass\n\n    def decompress(self, data: bytes, max_length: int) -> bytes:\n        pass\n\n\nclass _WebSocketDelegate(Protocol):\n    # The common base interface implemented by WebSocketHandler on\n    # the server side and WebSocketClientConnection on the client\n    # side.\n    def on_ws_connection_close(\n        self, close_code: int | None = None, close_reason: str | None = None\n    ) -> None:\n        pass\n\n    def on_message(self, message: str | bytes) -> Optional[\"Awaitable[None]\"]:\n        pass\n\n    def on_ping(self, data: bytes) -> None:\n        pass\n\n    def on_pong(self, data: bytes) -> None:\n        pass\n\n    def log_exception(\n        self,\n        typ: type[BaseException] | None,\n        value: BaseException | None,\n        tb: TracebackType | None,\n    ) -> None:\n        pass\n\n\n_default_max_message_size = 10 * 1024 * 1024\n\n# log to \"gen_log\" but suppress duplicate log messages\nde_dupe_gen_log = functools.lru_cache(gen_log.log)\n\n\nclass WebSocketError(Exception):\n    pass\n\n\nclass WebSocketClosedError(WebSocketError):\n    \"\"\"Raised by operations on a closed connection.\n\n    .. versionadded:: 3.2\n    \"\"\"\n\n    pass\n\n\nclass _DecompressTooLargeError(Exception):\n    pass\n\n\nclass _WebSocketParams:\n    def __init__(\n        self,\n        ping_interval: float | None = None,\n        ping_timeout: float | None = None,\n        max_message_size: int = _default_max_message_size,\n        compression_options: dict[str, Any] | None = None,\n    ) -> None:\n        self.ping_interval = ping_interval\n        self.ping_timeout = ping_timeout\n        self.max_message_size = max_message_size\n        self.compression_options = compression_options\n\n\nclass WebSocketHandler(tornado.web.RequestHandler):\n    \"\"\"Subclass this class to create a basic WebSocket handler.\n\n    Override `on_message` to handle incoming messages, and use\n    `write_message` to send messages to the client. You can also\n    override `open` and `on_close` to handle opened and closed\n    connections.\n\n    Custom upgrade response headers can be sent by overriding\n    `~tornado.web.RequestHandler.set_default_headers` or\n    `~tornado.web.RequestHandler.prepare`.\n\n    See http://dev.w3.org/html5/websockets/ for details on the\n    JavaScript interface.  The protocol is specified at\n    http://tools.ietf.org/html/rfc6455.\n\n    Here is an example WebSocket handler that echos back all received messages\n    back to the client:\n\n    .. testcode::\n\n      class EchoWebSocket(tornado.websocket.WebSocketHandler):\n          def open(self):\n              print(\"WebSocket opened\")\n\n          def on_message(self, message):\n              self.write_message(u\"You said: \" + message)\n\n          def on_close(self):\n              print(\"WebSocket closed\")\n\n    WebSockets are not standard HTTP connections. The \"handshake\" is\n    HTTP, but after the handshake, the protocol is\n    message-based. Consequently, most of the Tornado HTTP facilities\n    are not available in handlers of this type. The only communication\n    methods available to you are `write_message()`, `ping()`, and\n    `close()`. Likewise, your request handler class should implement\n    `open()` method rather than ``get()`` or ``post()``.\n\n    If you map the handler above to ``/websocket`` in your application, you can\n    invoke it in JavaScript with::\n\n      var ws = new WebSocket(\"ws://localhost:8888/websocket\");\n      ws.onopen = function() {\n         ws.send(\"Hello, world\");\n      };\n      ws.onmessage = function (evt) {\n         alert(evt.data);\n      };\n\n    This script pops up an alert box that says \"You said: Hello, world\".\n\n    Web browsers allow any site to open a websocket connection to any other,\n    instead of using the same-origin policy that governs other network\n    access from JavaScript.  This can be surprising and is a potential\n    security hole, so since Tornado 4.0 `WebSocketHandler` requires\n    applications that wish to receive cross-origin websockets to opt in\n    by overriding the `~WebSocketHandler.check_origin` method (see that\n    method's docs for details).  Failure to do so is the most likely\n    cause of 403 errors when making a websocket connection.\n\n    When using a secure websocket connection (``wss://``) with a self-signed\n    certificate, the connection from a browser may fail because it wants\n    to show the \"accept this certificate\" dialog but has nowhere to show it.\n    You must first visit a regular HTML page using the same certificate\n    to accept it before the websocket connection will succeed.\n\n    If the application setting ``websocket_ping_interval`` has a non-zero\n    value, a ping will be sent periodically, and the connection will be\n    closed if a response is not received before the ``websocket_ping_timeout``.\n    Both settings are in seconds; floating point values are allowed.\n    The default timeout is equal to the interval.\n\n    Messages larger than the ``websocket_max_message_size`` application setting\n    (default 10MiB) will not be accepted.\n\n    .. versionchanged:: 4.5\n       Added ``websocket_ping_interval``, ``websocket_ping_timeout``, and\n       ``websocket_max_message_size``.\n    \"\"\"\n\n    def __init__(\n        self,\n        application: tornado.web.Application,\n        request: httputil.HTTPServerRequest,\n        **kwargs: Any,\n    ) -> None:\n        super().__init__(application, request, **kwargs)\n        self.ws_connection: WebSocketProtocol | None = None\n        self.close_code: int | None = None\n        self.close_reason: str | None = None\n        self._on_close_called = False\n\n    async def get(self, *args: Any, **kwargs: Any) -> None:\n        self.open_args = args\n        self.open_kwargs = kwargs\n\n        # Upgrade header should be present and should be equal to WebSocket\n        if self.request.headers.get(\"Upgrade\", \"\").lower() != \"websocket\":\n            self.set_status(400)\n            log_msg = 'Can \"Upgrade\" only to \"WebSocket\".'\n            self.finish(log_msg)\n            gen_log.debug(log_msg)\n            return\n\n        # Connection header should be upgrade.\n        # Some proxy servers/load balancers\n        # might mess with it.\n        headers = self.request.headers\n        connection = map(\n            lambda s: s.strip().lower(), headers.get(\"Connection\", \"\").split(\",\")\n        )\n        if \"upgrade\" not in connection:\n            self.set_status(400)\n            log_msg = '\"Connection\" must be \"Upgrade\".'\n            self.finish(log_msg)\n            gen_log.debug(log_msg)\n            return\n\n        # Handle WebSocket Origin naming convention differences\n        # The difference between version 8 and 13 is that in 8 the\n        # client sends a \"Sec-Websocket-Origin\" header and in 13 it's\n        # simply \"Origin\".\n        if \"Origin\" in self.request.headers:\n            origin = self.request.headers.get(\"Origin\")\n        else:\n            origin = self.request.headers.get(\"Sec-Websocket-Origin\", None)\n\n        # If there was an origin header, check to make sure it matches\n        # according to check_origin. When the origin is None, we assume it\n        # did not come from a browser and that it can be passed on.\n        if origin is not None and not self.check_origin(origin):\n            self.set_status(403)\n            log_msg = \"Cross origin websockets not allowed\"\n            self.finish(log_msg)\n            gen_log.debug(log_msg)\n            return\n\n        self.ws_connection = self.get_websocket_protocol()\n        if self.ws_connection:\n            await self.ws_connection.accept_connection(self)\n        else:\n            self.set_status(426, \"Upgrade Required\")\n            self.set_header(\"Sec-WebSocket-Version\", \"7, 8, 13\")\n\n    @property\n    def ping_interval(self) -> float | None:\n        \"\"\"The interval for sending websocket pings.\n\n        If this is non-zero, the websocket will send a ping every\n        ping_interval seconds.\n        The client will respond with a \"pong\". The connection can be configured\n        to timeout on late pong delivery using ``websocket_ping_timeout``.\n\n        Set ``websocket_ping_interval = 0`` to disable pings.\n\n        Default: ``0``\n        \"\"\"\n        return self.settings.get(\"websocket_ping_interval\", None)\n\n    @property\n    def ping_timeout(self) -> float | None:\n        \"\"\"Timeout if no pong is received in this many seconds.\n\n        To be used in combination with ``websocket_ping_interval > 0``.\n        If a ping response (a \"pong\") is not received within\n        ``websocket_ping_timeout`` seconds, then the websocket connection\n        will be closed.\n\n        This can help to clean up clients which have disconnected without\n        cleanly closing the websocket connection.\n\n        Note, the ping timeout cannot be longer than the ping interval.\n\n        Set ``websocket_ping_timeout = 0`` to disable the ping timeout.\n\n        Default: equal to the ``ping_interval``.\n\n        .. versionchanged:: 6.5.0\n           Default changed from the max of 3 pings or 30 seconds.\n           The ping timeout can no longer be configured longer than the\n           ping interval.\n        \"\"\"\n        return self.settings.get(\"websocket_ping_timeout\", None)\n\n    @property\n    def max_message_size(self) -> int:\n        \"\"\"Maximum allowed message size.\n\n        If the remote peer sends a message larger than this, the connection\n        will be closed.\n\n        Default is 10MiB.\n        \"\"\"\n        return self.settings.get(\n            \"websocket_max_message_size\", _default_max_message_size\n        )\n\n    def write_message(\n        self, message: bytes | str | dict[str, Any], binary: bool = False\n    ) -> \"Future[None]\":\n        \"\"\"Sends the given message to the client of this Web Socket.\n\n        The message may be either a string or a dict (which will be\n        encoded as json).  If the ``binary`` argument is false, the\n        message will be sent as utf8; in binary mode any byte string\n        is allowed.\n\n        If the connection is already closed, raises `WebSocketClosedError`.\n        Returns a `.Future` which can be used for flow control.\n\n        .. versionchanged:: 3.2\n           `WebSocketClosedError` was added (previously a closed connection\n           would raise an `AttributeError`)\n\n        .. versionchanged:: 4.3\n           Returns a `.Future` which can be used for flow control.\n\n        .. versionchanged:: 5.0\n           Consistently raises `WebSocketClosedError`. Previously could\n           sometimes raise `.StreamClosedError`.\n        \"\"\"\n        if self.ws_connection is None or self.ws_connection.is_closing():\n            raise WebSocketClosedError()\n        if isinstance(message, dict):\n            message = tornado.escape.json_encode(message)\n        return self.ws_connection.write_message(message, binary=binary)\n\n    def select_subprotocol(self, subprotocols: list[str]) -> str | None:\n        \"\"\"Override to implement subprotocol negotiation.\n\n        ``subprotocols`` is a list of strings identifying the\n        subprotocols proposed by the client.  This method may be\n        overridden to return one of those strings to select it, or\n        ``None`` to not select a subprotocol.\n\n        Failure to select a subprotocol does not automatically abort\n        the connection, although clients may close the connection if\n        none of their proposed subprotocols was selected.\n\n        The list may be empty, in which case this method must return\n        None. This method is always called exactly once even if no\n        subprotocols were proposed so that the handler can be advised\n        of this fact.\n\n        .. versionchanged:: 5.1\n\n           Previously, this method was called with a list containing\n           an empty string instead of an empty list if no subprotocols\n           were proposed by the client.\n        \"\"\"\n        return None\n\n    @property\n    def selected_subprotocol(self) -> str | None:\n        \"\"\"The subprotocol returned by `select_subprotocol`.\n\n        .. versionadded:: 5.1\n        \"\"\"\n        assert self.ws_connection is not None\n        return self.ws_connection.selected_subprotocol\n\n    def get_compression_options(self) -> dict[str, Any] | None:\n        \"\"\"Override to return compression options for the connection.\n\n        If this method returns None (the default), compression will\n        be disabled.  If it returns a dict (even an empty one), it\n        will be enabled.  The contents of the dict may be used to\n        control the following compression options:\n\n        ``compression_level`` specifies the compression level.\n\n        ``mem_level`` specifies the amount of memory used for the internal compression state.\n\n         These parameters are documented in detail here:\n         https://docs.python.org/3.13/library/zlib.html#zlib.compressobj\n\n        .. versionadded:: 4.1\n\n        .. versionchanged:: 4.5\n\n           Added ``compression_level`` and ``mem_level``.\n        \"\"\"\n        # TODO: Add wbits option.\n        return None\n\n    def _open(self, *args: str, **kwargs: str) -> Awaitable[None] | None:\n        pass\n\n    open: Callable[..., Awaitable[None] | None] = _open\n    \"\"\"Invoked when a new WebSocket is opened.\n\n    The arguments to `open` are extracted from the `tornado.web.URLSpec`\n    regular expression, just like the arguments to\n    `tornado.web.RequestHandler.get`.\n\n    `open` may be a coroutine. `on_message` will not be called until\n    `open` has returned.\n\n    .. versionchanged:: 5.1\n\n        ``open`` may be a coroutine.\n    \"\"\"\n\n    def on_message(self, message: str | bytes) -> Awaitable[None] | None:\n        \"\"\"Handle incoming messages on the WebSocket\n\n        This method must be overridden.\n\n        .. versionchanged:: 4.5\n\n           ``on_message`` can be a coroutine.\n        \"\"\"\n        raise NotImplementedError\n\n    def ping(self, data: str | bytes = b\"\") -> None:\n        \"\"\"Send ping frame to the remote end.\n\n        The data argument allows a small amount of data (up to 125\n        bytes) to be sent as a part of the ping message. Note that not\n        all websocket implementations expose this data to\n        applications.\n\n        Consider using the ``websocket_ping_interval`` application\n        setting instead of sending pings manually.\n\n        .. versionchanged:: 5.1\n\n           The data argument is now optional.\n\n        \"\"\"\n        data = utf8(data)\n        if self.ws_connection is None or self.ws_connection.is_closing():\n            raise WebSocketClosedError()\n        self.ws_connection.write_ping(data)\n\n    def on_pong(self, data: bytes) -> None:\n        \"\"\"Invoked when the response to a ping frame is received.\"\"\"\n        pass\n\n    def on_ping(self, data: bytes) -> None:\n        \"\"\"Invoked when the a ping frame is received.\"\"\"\n        pass\n\n    def on_close(self) -> None:\n        \"\"\"Invoked when the WebSocket is closed.\n\n        If the connection was closed cleanly and a status code or reason\n        phrase was supplied, these values will be available as the attributes\n        ``self.close_code`` and ``self.close_reason``.\n\n        .. versionchanged:: 4.0\n\n           Added ``close_code`` and ``close_reason`` attributes.\n        \"\"\"\n        pass\n\n    def close(self, code: int | None = None, reason: str | None = None) -> None:\n        \"\"\"Closes this Web Socket.\n\n        Once the close handshake is successful the socket will be closed.\n\n        ``code`` may be a numeric status code, taken from the values\n        defined in `RFC 6455 section 7.4.1\n        <https://tools.ietf.org/html/rfc6455#section-7.4.1>`_.\n        ``reason`` may be a textual message about why the connection is\n        closing.  These values are made available to the client, but are\n        not otherwise interpreted by the websocket protocol.\n\n        .. versionchanged:: 4.0\n\n           Added the ``code`` and ``reason`` arguments.\n        \"\"\"\n        if self.ws_connection:\n            self.ws_connection.close(code, reason)\n            self.ws_connection = None\n\n    def check_origin(self, origin: str) -> bool:\n        \"\"\"Override to enable support for allowing alternate origins.\n\n        The ``origin`` argument is the value of the ``Origin`` HTTP\n        header, the url responsible for initiating this request.  This\n        method is not called for clients that do not send this header;\n        such requests are always allowed (because all browsers that\n        implement WebSockets support this header, and non-browser\n        clients do not have the same cross-site security concerns).\n\n        Should return ``True`` to accept the request or ``False`` to\n        reject it. By default, rejects all requests with an origin on\n        a host other than this one.\n\n        This is a security protection against cross site scripting attacks on\n        browsers, since WebSockets are allowed to bypass the usual same-origin\n        policies and don't use CORS headers.\n\n        .. warning::\n\n           This is an important security measure; don't disable it\n           without understanding the security implications. In\n           particular, if your authentication is cookie-based, you\n           must either restrict the origins allowed by\n           ``check_origin()`` or implement your own XSRF-like\n           protection for websocket connections. See `these\n           <https://www.christian-schneider.net/CrossSiteWebSocketHijacking.html>`_\n           `articles\n           <https://devcenter.heroku.com/articles/websocket-security>`_\n           for more.\n\n        To accept all cross-origin traffic (which was the default prior to\n        Tornado 4.0), simply override this method to always return ``True``::\n\n            def check_origin(self, origin):\n                return True\n\n        To allow connections from any subdomain of your site, you might\n        do something like::\n\n            def check_origin(self, origin):\n                parsed_origin = urllib.parse.urlparse(origin)\n                return parsed_origin.netloc.endswith(\".mydomain.com\")\n\n        .. versionadded:: 4.0\n\n        \"\"\"\n        parsed_origin = urlparse(origin)\n        origin = parsed_origin.netloc\n        origin = origin.lower()\n\n        host = self.request.headers.get(\"Host\")\n\n        # Check to see that origin matches host directly, including ports\n        return origin == host\n\n    def set_nodelay(self, value: bool) -> None:\n        \"\"\"Set the no-delay flag for this stream.\n\n        By default, small messages may be delayed and/or combined to minimize\n        the number of packets sent.  This can sometimes cause 200-500ms delays\n        due to the interaction between Nagle's algorithm and TCP delayed\n        ACKs.  To reduce this delay (at the expense of possibly increasing\n        bandwidth usage), call ``self.set_nodelay(True)`` once the websocket\n        connection is established.\n\n        See `.BaseIOStream.set_nodelay` for additional details.\n\n        .. versionadded:: 3.1\n        \"\"\"\n        assert self.ws_connection is not None\n        self.ws_connection.set_nodelay(value)\n\n    def on_connection_close(self) -> None:\n        if self.ws_connection:\n            self.ws_connection.on_connection_close()\n            self.ws_connection = None\n        if not self._on_close_called:\n            self._on_close_called = True\n            self.on_close()\n            self._break_cycles()\n\n    def on_ws_connection_close(\n        self, close_code: int | None = None, close_reason: str | None = None\n    ) -> None:\n        self.close_code = close_code\n        self.close_reason = close_reason\n        self.on_connection_close()\n\n    def _break_cycles(self) -> None:\n        # WebSocketHandlers call finish() early, but we don't want to\n        # break up reference cycles (which makes it impossible to call\n        # self.render_string) until after we've really closed the\n        # connection (if it was established in the first place,\n        # indicated by status code 101).\n        if self.get_status() != 101 or self._on_close_called:\n            super()._break_cycles()\n\n    def get_websocket_protocol(self) -> Optional[\"WebSocketProtocol\"]:\n        websocket_version = self.request.headers.get(\"Sec-WebSocket-Version\")\n        if websocket_version in (\"7\", \"8\", \"13\"):\n            params = _WebSocketParams(\n                ping_interval=self.ping_interval,\n                ping_timeout=self.ping_timeout,\n                max_message_size=self.max_message_size,\n                compression_options=self.get_compression_options(),\n            )\n            return WebSocketProtocol13(self, False, params)\n        return None\n\n    def _detach_stream(self) -> IOStream:\n        # disable non-WS methods\n        for method in [\n            \"write\",\n            \"redirect\",\n            \"set_header\",\n            \"set_cookie\",\n            \"set_status\",\n            \"flush\",\n            \"finish\",\n        ]:\n            setattr(self, method, _raise_not_supported_for_websockets)\n        return self.detach()\n\n\ndef _raise_not_supported_for_websockets(*args: Any, **kwargs: Any) -> None:\n    raise RuntimeError(\"Method not supported for Web Sockets\")\n\n\nclass WebSocketProtocol(abc.ABC):\n    \"\"\"Base class for WebSocket protocol versions.\"\"\"\n\n    def __init__(self, handler: \"_WebSocketDelegate\") -> None:\n        self.handler = handler\n        self.stream: IOStream | None = None\n        self.client_terminated = False\n        self.server_terminated = False\n\n    def _run_callback(\n        self, callback: Callable, *args: Any, **kwargs: Any\n    ) -> \"Optional[Future[Any]]\":\n        \"\"\"Runs the given callback with exception handling.\n\n        If the callback is a coroutine, returns its Future. On error, aborts the\n        websocket connection and returns None.\n        \"\"\"\n        try:\n            result = callback(*args, **kwargs)\n        except Exception:\n            self.handler.log_exception(*sys.exc_info())\n            self._abort()\n            return None\n        else:\n            if result is not None:\n                result = gen.convert_yielded(result)\n                assert self.stream is not None\n                self.stream.io_loop.add_future(result, lambda f: f.result())\n            return result\n\n    def on_connection_close(self) -> None:\n        self._abort()\n\n    def _abort(self) -> None:\n        \"\"\"Instantly aborts the WebSocket connection by closing the socket\"\"\"\n        self.client_terminated = True\n        self.server_terminated = True\n        if self.stream is not None:\n            self.stream.close()  # forcibly tear down the connection\n        self.close()  # let the subclass cleanup\n\n    @abc.abstractmethod\n    def close(self, code: int | None = None, reason: str | None = None) -> None:\n        raise NotImplementedError()\n\n    @abc.abstractmethod\n    def is_closing(self) -> bool:\n        raise NotImplementedError()\n\n    @abc.abstractmethod\n    async def accept_connection(self, handler: WebSocketHandler) -> None:\n        raise NotImplementedError()\n\n    @abc.abstractmethod\n    def write_message(\n        self, message: str | bytes | dict[str, Any], binary: bool = False\n    ) -> \"Future[None]\":\n        raise NotImplementedError()\n\n    @property\n    @abc.abstractmethod\n    def selected_subprotocol(self) -> str | None:\n        raise NotImplementedError()\n\n    @abc.abstractmethod\n    def write_ping(self, data: bytes) -> None:\n        raise NotImplementedError()\n\n    # The entry points below are used by WebSocketClientConnection,\n    # which was introduced after we only supported a single version of\n    # WebSocketProtocol. The WebSocketProtocol/WebSocketProtocol13\n    # boundary is currently pretty ad-hoc.\n    @abc.abstractmethod\n    def _process_server_headers(\n        self, key: str | bytes, headers: httputil.HTTPHeaders\n    ) -> None:\n        raise NotImplementedError()\n\n    @abc.abstractmethod\n    def start_pinging(self) -> None:\n        raise NotImplementedError()\n\n    @abc.abstractmethod\n    async def _receive_frame_loop(self) -> None:\n        raise NotImplementedError()\n\n    @abc.abstractmethod\n    def set_nodelay(self, x: bool) -> None:\n        raise NotImplementedError()\n\n\nclass _PerMessageDeflateCompressor:\n    def __init__(\n        self,\n        persistent: bool,\n        max_wbits: int | None,\n        compression_options: dict[str, Any] | None = None,\n    ) -> None:\n        if max_wbits is None:\n            max_wbits = zlib.MAX_WBITS\n        # There is no symbolic constant for the minimum wbits value.\n        if not (8 <= max_wbits <= zlib.MAX_WBITS):\n            raise ValueError(\n                \"Invalid max_wbits value %r; allowed range 8-%d\",\n                max_wbits,\n                zlib.MAX_WBITS,\n            )\n        self._max_wbits = max_wbits\n\n        if (\n            compression_options is None\n            or \"compression_level\" not in compression_options\n        ):\n            self._compression_level = tornado.web.GZipContentEncoding.GZIP_LEVEL\n        else:\n            self._compression_level = compression_options[\"compression_level\"]\n\n        if compression_options is None or \"mem_level\" not in compression_options:\n            self._mem_level = 8\n        else:\n            self._mem_level = compression_options[\"mem_level\"]\n\n        if persistent:\n            self._compressor: _Compressor | None = self._create_compressor()\n        else:\n            self._compressor = None\n\n    def _create_compressor(self) -> \"_Compressor\":\n        return zlib.compressobj(\n            self._compression_level, zlib.DEFLATED, -self._max_wbits, self._mem_level\n        )\n\n    def compress(self, data: bytes) -> bytes:\n        compressor = self._compressor or self._create_compressor()\n        data = compressor.compress(data) + compressor.flush(zlib.Z_SYNC_FLUSH)\n        assert data.endswith(b\"\\x00\\x00\\xff\\xff\")\n        return data[:-4]\n\n\nclass _PerMessageDeflateDecompressor:\n    def __init__(\n        self,\n        persistent: bool,\n        max_wbits: int | None,\n        max_message_size: int,\n        compression_options: dict[str, Any] | None = None,\n    ) -> None:\n        self._max_message_size = max_message_size\n        if max_wbits is None:\n            max_wbits = zlib.MAX_WBITS\n        if not (8 <= max_wbits <= zlib.MAX_WBITS):\n            raise ValueError(\n                \"Invalid max_wbits value %r; allowed range 8-%d\",\n                max_wbits,\n                zlib.MAX_WBITS,\n            )\n        self._max_wbits = max_wbits\n        if persistent:\n            self._decompressor: _Decompressor | None = self._create_decompressor()\n        else:\n            self._decompressor = None\n\n    def _create_decompressor(self) -> \"_Decompressor\":\n        return zlib.decompressobj(-self._max_wbits)\n\n    def decompress(self, data: bytes) -> bytes:\n        decompressor = self._decompressor or self._create_decompressor()\n        result = decompressor.decompress(\n            data + b\"\\x00\\x00\\xff\\xff\", self._max_message_size\n        )\n        if decompressor.unconsumed_tail:\n            raise _DecompressTooLargeError()\n        return result\n\n\nclass WebSocketProtocol13(WebSocketProtocol):\n    \"\"\"Implementation of the WebSocket protocol from RFC 6455.\n\n    This class supports versions 7 and 8 of the protocol in addition to the\n    final version 13.\n    \"\"\"\n\n    # Bit masks for the first byte of a frame.\n    FIN = 0x80\n    RSV1 = 0x40\n    RSV2 = 0x20\n    RSV3 = 0x10\n    RSV_MASK = RSV1 | RSV2 | RSV3\n    OPCODE_MASK = 0x0F\n\n    stream: IOStream\n\n    def __init__(\n        self,\n        handler: \"_WebSocketDelegate\",\n        mask_outgoing: bool,\n        params: _WebSocketParams,\n    ) -> None:\n        WebSocketProtocol.__init__(self, handler)\n        self.mask_outgoing = mask_outgoing\n        self.params = params\n        self._final_frame = False\n        self._frame_opcode = None\n        self._masked_frame = None\n        self._frame_mask: bytes | None = None\n        self._frame_length = None\n        self._fragmented_message_buffer: bytearray | None = None\n        self._fragmented_message_opcode = None\n        self._waiting: object = None\n        self._compression_options = params.compression_options\n        self._decompressor: _PerMessageDeflateDecompressor | None = None\n        self._compressor: _PerMessageDeflateCompressor | None = None\n        self._frame_compressed: bool | None = None\n        # The total uncompressed size of all messages received or sent.\n        # Unicode messages are encoded to utf8.\n        # Only for testing; subject to change.\n        self._message_bytes_in = 0\n        self._message_bytes_out = 0\n        # The total size of all packets received or sent.  Includes\n        # the effect of compression, frame overhead, and control frames.\n        self._wire_bytes_in = 0\n        self._wire_bytes_out = 0\n        self._received_pong: bool = False\n        self.close_code: int | None = None\n        self.close_reason: str | None = None\n        self._ping_coroutine: asyncio.Task | None = None\n\n    # Use a property for this to satisfy the abc.\n    @property\n    def selected_subprotocol(self) -> str | None:\n        return self._selected_subprotocol\n\n    @selected_subprotocol.setter\n    def selected_subprotocol(self, value: str | None) -> None:\n        self._selected_subprotocol = value\n\n    async def accept_connection(self, handler: WebSocketHandler) -> None:\n        try:\n            self._handle_websocket_headers(handler)\n        except ValueError:\n            handler.set_status(400)\n            log_msg = \"Missing/Invalid WebSocket headers\"\n            handler.finish(log_msg)\n            gen_log.debug(log_msg)\n            return\n\n        try:\n            await self._accept_connection(handler)\n        except asyncio.CancelledError:\n            self._abort()\n            return\n        except ValueError:\n            gen_log.debug(\"Malformed WebSocket request received\", exc_info=True)\n            self._abort()\n            return\n\n    def _handle_websocket_headers(self, handler: WebSocketHandler) -> None:\n        \"\"\"Verifies all invariant- and required headers\n\n        If a header is missing or have an incorrect value ValueError will be\n        raised\n        \"\"\"\n        fields = (\"Host\", \"Sec-Websocket-Key\", \"Sec-Websocket-Version\")\n        if not all(map(lambda f: handler.request.headers.get(f), fields)):\n            raise ValueError(\"Missing/Invalid WebSocket headers\")\n\n    @staticmethod\n    def compute_accept_value(key: str | bytes) -> str:\n        \"\"\"Computes the value for the Sec-WebSocket-Accept header,\n        given the value for Sec-WebSocket-Key.\n        \"\"\"\n        sha1 = hashlib.sha1()\n        sha1.update(utf8(key))\n        sha1.update(b\"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\")  # Magic value\n        return native_str(base64.b64encode(sha1.digest()))\n\n    def _challenge_response(self, handler: WebSocketHandler) -> str:\n        return WebSocketProtocol13.compute_accept_value(\n            cast(str, handler.request.headers.get(\"Sec-Websocket-Key\"))\n        )\n\n    async def _accept_connection(self, handler: WebSocketHandler) -> None:\n        subprotocol_header = handler.request.headers.get(\"Sec-WebSocket-Protocol\")\n        if subprotocol_header:\n            subprotocols = [s.strip() for s in subprotocol_header.split(\",\")]\n        else:\n            subprotocols = []\n        self.selected_subprotocol = handler.select_subprotocol(subprotocols)\n        if self.selected_subprotocol:\n            assert self.selected_subprotocol in subprotocols\n            handler.set_header(\"Sec-WebSocket-Protocol\", self.selected_subprotocol)\n\n        extensions = self._parse_extensions_header(handler.request.headers)\n        for ext in extensions:\n            if ext[0] == \"permessage-deflate\" and self._compression_options is not None:\n                # TODO: negotiate parameters if compression_options\n                # specifies limits.\n                self._create_compressors(\"server\", ext[1], self._compression_options)\n                if (\n                    \"client_max_window_bits\" in ext[1]\n                    and ext[1][\"client_max_window_bits\"] is None\n                ):\n                    # Don't echo an offered client_max_window_bits\n                    # parameter with no value.\n                    del ext[1][\"client_max_window_bits\"]\n                handler.set_header(\n                    \"Sec-WebSocket-Extensions\",\n                    httputil._encode_header(\"permessage-deflate\", ext[1]),\n                )\n                break\n\n        handler.clear_header(\"Content-Type\")\n        handler.set_status(101)\n        handler.set_header(\"Upgrade\", \"websocket\")\n        handler.set_header(\"Connection\", \"Upgrade\")\n        handler.set_header(\"Sec-WebSocket-Accept\", self._challenge_response(handler))\n        handler.finish()\n\n        self.stream = handler._detach_stream()\n\n        self.start_pinging()\n        try:\n            open_result = handler.open(*handler.open_args, **handler.open_kwargs)\n            if open_result is not None:\n                await open_result\n        except Exception:\n            handler.log_exception(*sys.exc_info())\n            self._abort()\n            return\n\n        await self._receive_frame_loop()\n\n    def _parse_extensions_header(\n        self, headers: httputil.HTTPHeaders\n    ) -> list[tuple[str, dict[str, str]]]:\n        extensions = headers.get(\"Sec-WebSocket-Extensions\", \"\")\n        if extensions:\n            return [httputil._parse_header(e.strip()) for e in extensions.split(\",\")]\n        return []\n\n    def _process_server_headers(\n        self, key: str | bytes, headers: httputil.HTTPHeaders\n    ) -> None:\n        \"\"\"Process the headers sent by the server to this client connection.\n\n        'key' is the websocket handshake challenge/response key.\n        \"\"\"\n        assert headers[\"Upgrade\"].lower() == \"websocket\"\n        assert headers[\"Connection\"].lower() == \"upgrade\"\n        accept = self.compute_accept_value(key)\n        assert headers[\"Sec-Websocket-Accept\"] == accept\n\n        extensions = self._parse_extensions_header(headers)\n        for ext in extensions:\n            if ext[0] == \"permessage-deflate\" and self._compression_options is not None:\n                self._create_compressors(\"client\", ext[1])\n            else:\n                raise ValueError(\"unsupported extension %r\", ext)\n\n        self.selected_subprotocol = headers.get(\"Sec-WebSocket-Protocol\", None)\n\n    def _get_compressor_options(\n        self,\n        side: str,\n        agreed_parameters: dict[str, Any],\n        compression_options: dict[str, Any] | None = None,\n    ) -> dict[str, Any]:\n        \"\"\"Converts a websocket agreed_parameters set to keyword arguments\n        for our compressor objects.\n        \"\"\"\n        options: dict[str, Any] = dict(\n            persistent=(side + \"_no_context_takeover\") not in agreed_parameters\n        )\n        wbits_header = agreed_parameters.get(side + \"_max_window_bits\", None)\n        if wbits_header is None:\n            options[\"max_wbits\"] = zlib.MAX_WBITS\n        else:\n            options[\"max_wbits\"] = int(wbits_header)\n        options[\"compression_options\"] = compression_options\n        return options\n\n    def _create_compressors(\n        self,\n        side: str,\n        agreed_parameters: dict[str, Any],\n        compression_options: dict[str, Any] | None = None,\n    ) -> None:\n        # TODO: handle invalid parameters gracefully\n        allowed_keys = {\n            \"server_no_context_takeover\",\n            \"client_no_context_takeover\",\n            \"server_max_window_bits\",\n            \"client_max_window_bits\",\n        }\n        for key in agreed_parameters:\n            if key not in allowed_keys:\n                raise ValueError(\"unsupported compression parameter %r\" % key)\n        other_side = \"client\" if (side == \"server\") else \"server\"\n        self._compressor = _PerMessageDeflateCompressor(\n            **self._get_compressor_options(side, agreed_parameters, compression_options)\n        )\n        self._decompressor = _PerMessageDeflateDecompressor(\n            max_message_size=self.params.max_message_size,\n            **self._get_compressor_options(\n                other_side, agreed_parameters, compression_options\n            ),\n        )\n\n    def _write_frame(\n        self, fin: bool, opcode: int, data: bytes, flags: int = 0\n    ) -> \"Future[None]\":\n        data_len = len(data)\n        if opcode & 0x8:\n            # All control frames MUST have a payload length of 125\n            # bytes or less and MUST NOT be fragmented.\n            if not fin:\n                raise ValueError(\"control frames may not be fragmented\")\n            if data_len > 125:\n                raise ValueError(\"control frame payloads may not exceed 125 bytes\")\n        if fin:\n            finbit = self.FIN\n        else:\n            finbit = 0\n        frame = struct.pack(\"B\", finbit | opcode | flags)\n        if self.mask_outgoing:\n            mask_bit = 0x80\n        else:\n            mask_bit = 0\n        if data_len < 126:\n            frame += struct.pack(\"B\", data_len | mask_bit)\n        elif data_len <= 0xFFFF:\n            frame += struct.pack(\"!BH\", 126 | mask_bit, data_len)\n        else:\n            frame += struct.pack(\"!BQ\", 127 | mask_bit, data_len)\n        if self.mask_outgoing:\n            mask = os.urandom(4)\n            data = mask + _websocket_mask(mask, data)\n        frame += data\n        self._wire_bytes_out += len(frame)\n        return self.stream.write(frame)\n\n    def write_message(\n        self, message: str | bytes | dict[str, Any], binary: bool = False\n    ) -> \"Future[None]\":\n        \"\"\"Sends the given message to the client of this Web Socket.\"\"\"\n        if binary:\n            opcode = 0x2\n        else:\n            opcode = 0x1\n        if isinstance(message, dict):\n            message = tornado.escape.json_encode(message)\n        message = tornado.escape.utf8(message)\n        assert isinstance(message, bytes)\n        self._message_bytes_out += len(message)\n        flags = 0\n        if self._compressor:\n            message = self._compressor.compress(message)\n            flags |= self.RSV1\n        # For historical reasons, write methods in Tornado operate in a semi-synchronous\n        # mode in which awaiting the Future they return is optional (But errors can\n        # still be raised). This requires us to go through an awkward dance here\n        # to transform the errors that may be returned while presenting the same\n        # semi-synchronous interface.\n        try:\n            fut = self._write_frame(True, opcode, message, flags=flags)\n        except StreamClosedError:\n            raise WebSocketClosedError()\n\n        async def wrapper() -> None:\n            try:\n                await fut\n            except StreamClosedError:\n                raise WebSocketClosedError()\n\n        return asyncio.ensure_future(wrapper())\n\n    def write_ping(self, data: bytes) -> None:\n        \"\"\"Send ping frame.\"\"\"\n        assert isinstance(data, bytes)\n        self._write_frame(True, 0x9, data)\n\n    async def _receive_frame_loop(self) -> None:\n        try:\n            while not self.client_terminated:\n                await self._receive_frame()\n        except StreamClosedError:\n            self._abort()\n        self.handler.on_ws_connection_close(self.close_code, self.close_reason)\n\n    async def _read_bytes(self, n: int) -> bytes:\n        data = await self.stream.read_bytes(n)\n        self._wire_bytes_in += n\n        return data\n\n    async def _receive_frame(self) -> None:\n        # Read the frame header.\n        data = await self._read_bytes(2)\n        header, mask_payloadlen = struct.unpack(\"BB\", data)\n        is_final_frame = header & self.FIN\n        reserved_bits = header & self.RSV_MASK\n        opcode = header & self.OPCODE_MASK\n        opcode_is_control = opcode & 0x8\n        if self._decompressor is not None and opcode != 0:\n            # Compression flag is present in the first frame's header,\n            # but we can't decompress until we have all the frames of\n            # the message.\n            self._frame_compressed = bool(reserved_bits & self.RSV1)\n            reserved_bits &= ~self.RSV1\n        if reserved_bits:\n            # client is using as-yet-undefined extensions; abort\n            self._abort()\n            return\n        is_masked = bool(mask_payloadlen & 0x80)\n        payloadlen = mask_payloadlen & 0x7F\n\n        # Parse and validate the length.\n        if opcode_is_control and payloadlen >= 126:\n            # control frames must have payload < 126\n            self._abort()\n            return\n        if payloadlen < 126:\n            self._frame_length = payloadlen\n        elif payloadlen == 126:\n            data = await self._read_bytes(2)\n            payloadlen = struct.unpack(\"!H\", data)[0]\n        elif payloadlen == 127:\n            data = await self._read_bytes(8)\n            payloadlen = struct.unpack(\"!Q\", data)[0]\n        new_len = payloadlen\n        if self._fragmented_message_buffer is not None:\n            new_len += len(self._fragmented_message_buffer)\n        if new_len > self.params.max_message_size:\n            self.close(1009, \"message too big\")\n            self._abort()\n            return\n\n        # Read the payload, unmasking if necessary.\n        if is_masked:\n            self._frame_mask = await self._read_bytes(4)\n        data = await self._read_bytes(payloadlen)\n        if is_masked:\n            assert self._frame_mask is not None\n            data = _websocket_mask(self._frame_mask, data)\n\n        # Decide what to do with this frame.\n        if opcode_is_control:\n            # control frames may be interleaved with a series of fragmented\n            # data frames, so control frames must not interact with\n            # self._fragmented_*\n            if not is_final_frame:\n                # control frames must not be fragmented\n                self._abort()\n                return\n        elif opcode == 0:  # continuation frame\n            if self._fragmented_message_buffer is None:\n                # nothing to continue\n                self._abort()\n                return\n            self._fragmented_message_buffer.extend(data)\n            if is_final_frame:\n                opcode = self._fragmented_message_opcode\n                data = bytes(self._fragmented_message_buffer)\n                self._fragmented_message_buffer = None\n        else:  # start of new data message\n            if self._fragmented_message_buffer is not None:\n                # can't start new message until the old one is finished\n                self._abort()\n                return\n            if not is_final_frame:\n                self._fragmented_message_opcode = opcode\n                self._fragmented_message_buffer = bytearray(data)\n\n        if is_final_frame:\n            handled_future = self._handle_message(opcode, data)\n            if handled_future is not None:\n                await handled_future\n\n    def _handle_message(self, opcode: int, data: bytes) -> \"Optional[Future[None]]\":\n        \"\"\"Execute on_message, returning its Future if it is a coroutine.\"\"\"\n        if self.client_terminated:\n            return None\n\n        if self._frame_compressed:\n            assert self._decompressor is not None\n            try:\n                data = self._decompressor.decompress(data)\n            except _DecompressTooLargeError:\n                self.close(1009, \"message too big after decompression\")\n                self._abort()\n                return None\n\n        if opcode == 0x1:\n            # UTF-8 data\n            self._message_bytes_in += len(data)\n            try:\n                decoded = data.decode(\"utf-8\")\n            except UnicodeDecodeError:\n                self._abort()\n                return None\n            return self._run_callback(self.handler.on_message, decoded)\n        elif opcode == 0x2:\n            # Binary data\n            self._message_bytes_in += len(data)\n            return self._run_callback(self.handler.on_message, data)\n        elif opcode == 0x8:\n            # Close\n            self.client_terminated = True\n            if len(data) >= 2:\n                self.close_code = struct.unpack(\">H\", data[:2])[0]\n            if len(data) > 2:\n                self.close_reason = to_unicode(data[2:])\n            # Echo the received close code, if any (RFC 6455 section 5.5.1).\n            self.close(self.close_code)\n        elif opcode == 0x9:\n            # Ping\n            try:\n                self._write_frame(True, 0xA, data)\n            except StreamClosedError:\n                self._abort()\n            self._run_callback(self.handler.on_ping, data)\n        elif opcode == 0xA:\n            # Pong\n            self._received_pong = True\n            return self._run_callback(self.handler.on_pong, data)\n        else:\n            self._abort()\n        return None\n\n    def close(self, code: int | None = None, reason: str | None = None) -> None:\n        \"\"\"Closes the WebSocket connection.\"\"\"\n        if not self.server_terminated:\n            if not self.stream.closed():\n                if code is None and reason is not None:\n                    code = 1000  # \"normal closure\" status code\n                if code is None:\n                    close_data = b\"\"\n                else:\n                    close_data = struct.pack(\">H\", code)\n                if reason is not None:\n                    close_data += utf8(reason)\n                try:\n                    self._write_frame(True, 0x8, close_data)\n                except StreamClosedError:\n                    self._abort()\n            self.server_terminated = True\n        if self.client_terminated:\n            if self._waiting is not None:\n                self.stream.io_loop.remove_timeout(self._waiting)\n                self._waiting = None\n            self.stream.close()\n        elif self._waiting is None:\n            # Give the client a few seconds to complete a clean shutdown,\n            # otherwise just close the connection.\n            self._waiting = self.stream.io_loop.add_timeout(\n                self.stream.io_loop.time() + 5, self._abort\n            )\n        if self._ping_coroutine:\n            self._ping_coroutine.cancel()\n            self._ping_coroutine = None\n\n    def is_closing(self) -> bool:\n        \"\"\"Return ``True`` if this connection is closing.\n\n        The connection is considered closing if either side has\n        initiated its closing handshake or if the stream has been\n        shut down uncleanly.\n        \"\"\"\n        return self.stream.closed() or self.client_terminated or self.server_terminated\n\n    def set_nodelay(self, x: bool) -> None:\n        self.stream.set_nodelay(x)\n\n    @property\n    def ping_interval(self) -> float:\n        interval = self.params.ping_interval\n        if interval is not None:\n            return interval\n        return 0\n\n    @property\n    def ping_timeout(self) -> float:\n        timeout = self.params.ping_timeout\n        if timeout is not None:\n            if self.ping_interval and timeout > self.ping_interval:\n                de_dupe_gen_log(\n                    # Note: using de_dupe_gen_log to prevent this message from\n                    # being duplicated for each connection\n                    logging.WARNING,\n                    f\"The websocket_ping_timeout ({timeout}) cannot be longer\"\n                    f\" than the websocket_ping_interval ({self.ping_interval}).\"\n                    f\"\\nSetting websocket_ping_timeout={self.ping_interval}\",\n                )\n                return self.ping_interval\n            return timeout\n        return self.ping_interval\n\n    def start_pinging(self) -> None:\n        \"\"\"Start sending periodic pings to keep the connection alive\"\"\"\n        if (\n            # prevent multiple ping coroutines being run in parallel\n            not self._ping_coroutine\n            # only run the ping coroutine if a ping interval is configured\n            and self.ping_interval > 0\n        ):\n            self._ping_coroutine = asyncio.create_task(self.periodic_ping())\n\n    @staticmethod\n    def ping_sleep_time(*, last_ping_time: float, interval: float, now: float) -> float:\n        \"\"\"Calculate the sleep time until the next ping should be sent.\"\"\"\n        return max(0, last_ping_time + interval - now)\n\n    async def periodic_ping(self) -> None:\n        \"\"\"Send a ping and wait for a pong if ping_timeout is configured.\n\n        Called periodically if the websocket_ping_interval is set and non-zero.\n        \"\"\"\n        interval = self.ping_interval\n        timeout = self.ping_timeout\n\n        await asyncio.sleep(interval)\n\n        while True:\n            # send a ping\n            self._received_pong = False\n            ping_time = IOLoop.current().time()\n            self.write_ping(b\"\")\n\n            # wait until the ping timeout\n            await asyncio.sleep(timeout)\n\n            # make sure we received a pong within the timeout\n            if timeout > 0 and not self._received_pong:\n                self.close(reason=\"ping timed out\")\n                return\n\n            # wait until the next scheduled ping\n            await asyncio.sleep(\n                self.ping_sleep_time(\n                    last_ping_time=ping_time,\n                    interval=interval,\n                    now=IOLoop.current().time(),\n                )\n            )\n\n\nclass WebSocketClientConnection(simple_httpclient._HTTPConnection):\n    \"\"\"WebSocket client connection.\n\n    This class should not be instantiated directly; use the\n    `websocket_connect` function instead.\n    \"\"\"\n\n    protocol: WebSocketProtocol | None = None\n\n    def __init__(\n        self,\n        request: httpclient.HTTPRequest,\n        on_message_callback: Callable[[None | str | bytes], None] | None = None,\n        compression_options: dict[str, Any] | None = None,\n        ping_interval: float | None = None,\n        ping_timeout: float | None = None,\n        max_message_size: int = _default_max_message_size,\n        subprotocols: list[str] | None = None,\n        resolver: Resolver | None = None,\n    ) -> None:\n        self.connect_future: Future[WebSocketClientConnection] = Future()\n        self.read_queue: Queue[None | str | bytes] = Queue(1)\n        self.key = base64.b64encode(os.urandom(16))\n        self._on_message_callback = on_message_callback\n        self.close_code: int | None = None\n        self.close_reason: str | None = None\n        self.params = _WebSocketParams(\n            ping_interval=ping_interval,\n            ping_timeout=ping_timeout,\n            max_message_size=max_message_size,\n            compression_options=compression_options,\n        )\n\n        scheme, sep, rest = request.url.partition(\":\")\n        scheme = {\"ws\": \"http\", \"wss\": \"https\"}[scheme]\n        request.url = scheme + sep + rest\n        request.headers.update(\n            {\n                \"Upgrade\": \"websocket\",\n                \"Connection\": \"Upgrade\",\n                \"Sec-WebSocket-Key\": to_unicode(self.key),\n                \"Sec-WebSocket-Version\": \"13\",\n            }\n        )\n        if subprotocols is not None:\n            request.headers[\"Sec-WebSocket-Protocol\"] = \",\".join(subprotocols)\n        if compression_options is not None:\n            # Always offer to let the server set our max_wbits (and even though\n            # we don't offer it, we will accept a client_no_context_takeover\n            # from the server).\n            # TODO: set server parameters for deflate extension\n            # if requested in self.compression_options.\n            request.headers[\"Sec-WebSocket-Extensions\"] = (\n                \"permessage-deflate; client_max_window_bits\"\n            )\n\n        # Websocket connection is currently unable to follow redirects\n        request.follow_redirects = False\n\n        self.tcp_client = TCPClient(resolver=resolver)\n        super().__init__(\n            None,\n            request,\n            lambda: None,\n            self._on_http_response,\n            104857600,\n            self.tcp_client,\n            65536,\n            104857600,\n        )\n\n    def __del__(self) -> None:\n        if self.protocol is not None:\n            # Unclosed client connections can sometimes log \"task was destroyed but\n            # was pending\" warnings if shutdown strikes at the wrong time (such as\n            # while a ping is being processed due to ping_interval). Log our own\n            # warning to make it a little more deterministic (although it's still\n            # dependent on GC timing).\n            warnings.warn(\"Unclosed WebSocketClientConnection\", ResourceWarning)\n\n    def close(self, code: int | None = None, reason: str | None = None) -> None:\n        \"\"\"Closes the websocket connection.\n\n        ``code`` and ``reason`` are documented under\n        `WebSocketHandler.close`.\n\n        .. versionadded:: 3.2\n\n        .. versionchanged:: 4.0\n\n           Added the ``code`` and ``reason`` arguments.\n        \"\"\"\n        if self.protocol is not None:\n            self.protocol.close(code, reason)\n            self.protocol = None  # type: ignore\n\n    def on_connection_close(self) -> None:\n        if not self.connect_future.done():\n            self.connect_future.set_exception(StreamClosedError())\n        self._on_message(None)\n        self.tcp_client.close()\n        super().on_connection_close()\n\n    def on_ws_connection_close(\n        self, close_code: int | None = None, close_reason: str | None = None\n    ) -> None:\n        self.close_code = close_code\n        self.close_reason = close_reason\n        self.on_connection_close()\n\n    def _on_http_response(self, response: httpclient.HTTPResponse) -> None:\n        if not self.connect_future.done():\n            if response.error:\n                self.connect_future.set_exception(response.error)\n            else:\n                self.connect_future.set_exception(\n                    WebSocketError(\"Non-websocket response\")\n                )\n\n    async def headers_received(\n        self,\n        start_line: httputil.RequestStartLine | httputil.ResponseStartLine,\n        headers: httputil.HTTPHeaders,\n    ) -> None:\n        assert isinstance(start_line, httputil.ResponseStartLine)\n        if start_line.code != 101:\n            await super().headers_received(start_line, headers)\n            return\n\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n            self._timeout = None\n\n        self.headers = headers\n        self.protocol = self.get_websocket_protocol()\n        self.protocol._process_server_headers(self.key, self.headers)\n        self.protocol.stream = self.connection.detach()\n\n        IOLoop.current().add_callback(self.protocol._receive_frame_loop)\n        self.protocol.start_pinging()\n\n        # Once we've taken over the connection, clear the final callback\n        # we set on the http request.  This deactivates the error handling\n        # in simple_httpclient that would otherwise interfere with our\n        # ability to see exceptions.\n        self.final_callback = None  # type: ignore\n\n        future_set_result_unless_cancelled(self.connect_future, self)\n\n    def write_message(\n        self, message: str | bytes | dict[str, Any], binary: bool = False\n    ) -> \"Future[None]\":\n        \"\"\"Sends a message to the WebSocket server.\n\n        If the stream is closed, raises `WebSocketClosedError`.\n        Returns a `.Future` which can be used for flow control.\n\n        .. versionchanged:: 5.0\n           Exception raised on a closed stream changed from `.StreamClosedError`\n           to `WebSocketClosedError`.\n        \"\"\"\n        if self.protocol is None:\n            raise WebSocketClosedError(\"Client connection has been closed\")\n        return self.protocol.write_message(message, binary=binary)\n\n    def read_message(\n        self,\n        callback: Callable[[\"Future[Union[None, str, bytes]]\"], None] | None = None,\n    ) -> Awaitable[None | str | bytes]:\n        \"\"\"Reads a message from the WebSocket server.\n\n        If on_message_callback was specified at WebSocket\n        initialization, this function will never return messages\n\n        Returns a future whose result is the message, or None\n        if the connection is closed.  If a callback argument\n        is given it will be called with the future when it is\n        ready.\n        \"\"\"\n\n        awaitable = self.read_queue.get()\n        if callback is not None:\n            self.io_loop.add_future(asyncio.ensure_future(awaitable), callback)\n        return awaitable\n\n    def on_message(self, message: str | bytes) -> Awaitable[None] | None:\n        return self._on_message(message)\n\n    def _on_message(self, message: None | str | bytes) -> Awaitable[None] | None:\n        if self._on_message_callback:\n            self._on_message_callback(message)\n            return None\n        else:\n            return self.read_queue.put(message)\n\n    def ping(self, data: bytes = b\"\") -> None:\n        \"\"\"Send ping frame to the remote end.\n\n        The data argument allows a small amount of data (up to 125\n        bytes) to be sent as a part of the ping message. Note that not\n        all websocket implementations expose this data to\n        applications.\n\n        Consider using the ``ping_interval`` argument to\n        `websocket_connect` instead of sending pings manually.\n\n        .. versionadded:: 5.1\n\n        \"\"\"\n        data = utf8(data)\n        if self.protocol is None:\n            raise WebSocketClosedError()\n        self.protocol.write_ping(data)\n\n    def on_pong(self, data: bytes) -> None:\n        pass\n\n    def on_ping(self, data: bytes) -> None:\n        pass\n\n    def get_websocket_protocol(self) -> WebSocketProtocol:\n        return WebSocketProtocol13(self, mask_outgoing=True, params=self.params)\n\n    @property\n    def selected_subprotocol(self) -> str | None:\n        \"\"\"The subprotocol selected by the server.\n\n        .. versionadded:: 5.1\n        \"\"\"\n        assert self.protocol is not None\n        return self.protocol.selected_subprotocol\n\n    def log_exception(\n        self,\n        typ: \"Optional[Type[BaseException]]\",\n        value: BaseException | None,\n        tb: TracebackType | None,\n    ) -> None:\n        assert typ is not None\n        assert value is not None\n        app_log.error(\"Uncaught exception %s\", value, exc_info=(typ, value, tb))\n\n\ndef websocket_connect(\n    url: str | httpclient.HTTPRequest,\n    callback: Callable[[\"Future[WebSocketClientConnection]\"], None] | None = None,\n    connect_timeout: float | None = None,\n    on_message_callback: Callable[[None | str | bytes], None] | None = None,\n    compression_options: dict[str, Any] | None = None,\n    ping_interval: float | None = None,\n    ping_timeout: float | None = None,\n    max_message_size: int = _default_max_message_size,\n    subprotocols: list[str] | None = None,\n    resolver: Resolver | None = None,\n) -> \"Awaitable[WebSocketClientConnection]\":\n    \"\"\"Client-side websocket support.\n\n    Takes a url and returns a Future whose result is a\n    `WebSocketClientConnection`.\n\n    ``compression_options`` is interpreted in the same way as the\n    return value of `.WebSocketHandler.get_compression_options`.\n\n    The connection supports two styles of operation. In the coroutine\n    style, the application typically calls\n    `~.WebSocketClientConnection.read_message` in a loop::\n\n        conn = yield websocket_connect(url)\n        while True:\n            msg = yield conn.read_message()\n            if msg is None: break\n            # Do something with msg\n\n    In the callback style, pass an ``on_message_callback`` to\n    ``websocket_connect``. In both styles, a message of ``None``\n    indicates that the connection has been closed.\n\n    ``subprotocols`` may be a list of strings specifying proposed\n    subprotocols. The selected protocol may be found on the\n    ``selected_subprotocol`` attribute of the connection object\n    when the connection is complete.\n\n    .. versionchanged:: 3.2\n       Also accepts ``HTTPRequest`` objects in place of urls.\n\n    .. versionchanged:: 4.1\n       Added ``compression_options`` and ``on_message_callback``.\n\n    .. versionchanged:: 4.5\n       Added the ``ping_interval``, ``ping_timeout``, and ``max_message_size``\n       arguments, which have the same meaning as in `WebSocketHandler`.\n\n    .. versionchanged:: 5.0\n       The ``io_loop`` argument (deprecated since version 4.1) has been removed.\n\n    .. versionchanged:: 5.1\n       Added the ``subprotocols`` argument.\n\n    .. versionchanged:: 6.3\n       Added the ``resolver`` argument.\n\n    .. deprecated:: 6.5\n       The ``callback`` argument is deprecated and will be removed in Tornado 7.0.\n       Use the returned Future instead. Note that ``on_message_callback`` is not\n       deprecated and may still be used.\n    \"\"\"\n    if isinstance(url, httpclient.HTTPRequest):\n        assert connect_timeout is None\n        request = url\n        # Copy and convert the headers dict/object (see comments in\n        # AsyncHTTPClient.fetch)\n        request.headers = httputil.HTTPHeaders(request.headers)\n    else:\n        request = httpclient.HTTPRequest(url, connect_timeout=connect_timeout)\n    request = cast(\n        httpclient.HTTPRequest,\n        httpclient._RequestProxy(request, httpclient.HTTPRequest._DEFAULTS),\n    )\n    conn = WebSocketClientConnection(\n        request,\n        on_message_callback=on_message_callback,\n        compression_options=compression_options,\n        ping_interval=ping_interval,\n        ping_timeout=ping_timeout,\n        max_message_size=max_message_size,\n        subprotocols=subprotocols,\n        resolver=resolver,\n    )\n    if callback is not None:\n        warnings.warn(\n            \"The callback argument to websocket_connect is deprecated. \"\n            \"Use the returned Future instead.\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n        IOLoop.current().add_future(conn.connect_future, callback)\n    return conn.connect_future\n"
  },
  {
    "path": "tornado/wsgi.py",
    "content": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# 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, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"WSGI support for the Tornado web framework.\n\nWSGI is the Python standard for web servers, and allows for interoperability\nbetween Tornado and other Python web frameworks and servers.\n\nThis module provides WSGI support via the `WSGIContainer` class, which\nmakes it possible to run applications using other WSGI frameworks on\nthe Tornado HTTP server. The reverse is not supported; the Tornado\n`.Application` and `.RequestHandler` classes are designed for use with\nthe Tornado `.HTTPServer` and cannot be used in a generic WSGI\ncontainer.\n\n\"\"\"\n\nimport concurrent.futures\nimport sys\nimport typing\nfrom collections.abc import Callable\nfrom io import BytesIO\nfrom types import TracebackType\nfrom typing import Any\n\nimport tornado\nfrom tornado import escape, httputil\nfrom tornado.concurrent import dummy_executor\nfrom tornado.ioloop import IOLoop\nfrom tornado.log import access_log\n\nif typing.TYPE_CHECKING:\n    from _typeshed.wsgi import WSGIApplication as WSGIAppType\n\n\n# PEP 3333 specifies that WSGI on python 3 generally deals with byte strings\n# that are smuggled inside objects of type unicode (via the latin1 encoding).\n# This function is like those in the tornado.escape module, but defined\n# here to minimize the temptation to use it in non-wsgi contexts.\ndef to_wsgi_str(s: bytes) -> str:\n    assert isinstance(s, bytes)\n    return s.decode(\"latin1\")\n\n\nclass WSGIContainer:\n    r\"\"\"Makes a WSGI-compatible application runnable on Tornado's HTTP server.\n\n    .. warning::\n\n       WSGI is a *synchronous* interface, while Tornado's concurrency model\n       is based on single-threaded *asynchronous* execution.  Many of Tornado's\n       distinguishing features are not available in WSGI mode, including efficient\n       long-polling and websockets. The primary purpose of `WSGIContainer` is\n       to support both WSGI applications and native Tornado ``RequestHandlers`` in\n       a single process. WSGI-only applications are likely to be better off\n       with a dedicated WSGI server such as ``gunicorn`` or ``uwsgi``.\n\n    Wrap a WSGI application in a `WSGIContainer` to make it implement the Tornado\n    `.HTTPServer` ``request_callback`` interface.  The `WSGIContainer` object can\n    then be passed to classes from the `tornado.routing` module,\n    `tornado.web.FallbackHandler`, or to `.HTTPServer` directly.\n\n    This class is intended to let other frameworks (Django, Flask, etc)\n    run on the Tornado HTTP server and I/O loop.\n\n    Realistic usage will be more complicated, but the simplest possible example uses a\n    hand-written WSGI application with `.HTTPServer`::\n\n        def simple_app(environ, start_response):\n            status = \"200 OK\"\n            response_headers = [(\"Content-type\", \"text/plain\")]\n            start_response(status, response_headers)\n            return [b\"Hello world!\\n\"]\n\n        async def main():\n            container = tornado.wsgi.WSGIContainer(simple_app)\n            http_server = tornado.httpserver.HTTPServer(container)\n            http_server.listen(8888)\n            await asyncio.Event().wait()\n\n        asyncio.run(main())\n\n    The recommended pattern is to use the `tornado.routing` module to set up routing\n    rules between your WSGI application and, typically, a `tornado.web.Application`.\n    Alternatively, `tornado.web.Application` can be used as the top-level router\n    and `tornado.web.FallbackHandler` can embed a `WSGIContainer` within it.\n\n    If the ``executor`` argument is provided, the WSGI application will be executed\n    on that executor. This must be an instance of `concurrent.futures.Executor`,\n    typically a ``ThreadPoolExecutor`` (``ProcessPoolExecutor`` is not supported).\n    If no ``executor`` is given, the application will run on the event loop thread in\n    Tornado 6.3; this will change to use an internal thread pool by default in\n    Tornado 7.0.\n\n    .. warning::\n       By default, the WSGI application is executed on the event loop's thread. This\n       limits the server to one request at a time (per process), making it less scalable\n       than most other WSGI servers. It is therefore highly recommended that you pass\n       a ``ThreadPoolExecutor`` when constructing the `WSGIContainer`, after verifying\n       that your application is thread-safe. The default will change to use a\n       ``ThreadPoolExecutor`` in Tornado 7.0.\n\n    .. versionadded:: 6.3\n       The ``executor`` parameter.\n\n    .. deprecated:: 6.3\n       The default behavior of running the WSGI application on the event loop thread\n       is deprecated and will change in Tornado 7.0 to use a thread pool by default.\n    \"\"\"\n\n    def __init__(\n        self,\n        wsgi_application: \"WSGIAppType\",\n        executor: concurrent.futures.Executor | None = None,\n    ) -> None:\n        self.wsgi_application = wsgi_application\n        self.executor = dummy_executor if executor is None else executor\n\n    def __call__(self, request: httputil.HTTPServerRequest) -> None:\n        IOLoop.current().spawn_callback(self.handle_request, request)\n\n    async def handle_request(self, request: httputil.HTTPServerRequest) -> None:\n        data: dict[str, Any] = {}\n        response: list[bytes] = []\n\n        def start_response(\n            status: str,\n            headers: list[tuple[str, str]],\n            exc_info: None | (\n                tuple[\n                    type[BaseException] | None,\n                    BaseException | None,\n                    TracebackType | None,\n                ]\n            ) = None,\n        ) -> Callable[[bytes], Any]:\n            data[\"status\"] = status\n            data[\"headers\"] = headers\n            return response.append\n\n        loop = IOLoop.current()\n        app_response = await loop.run_in_executor(\n            self.executor,\n            self.wsgi_application,\n            self.environ(request),\n            start_response,\n        )\n        try:\n            app_response_iter = iter(app_response)\n\n            def next_chunk() -> bytes | None:\n                try:\n                    return next(app_response_iter)\n                except StopIteration:\n                    # StopIteration is special and is not allowed to pass through\n                    # coroutines normally.\n                    return None\n\n            while True:\n                chunk = await loop.run_in_executor(self.executor, next_chunk)\n                if chunk is None:\n                    break\n                response.append(chunk)\n        finally:\n            if hasattr(app_response, \"close\"):\n                app_response.close()  # type: ignore\n        body = b\"\".join(response)\n        if not data:\n            raise Exception(\"WSGI app did not call start_response\")\n\n        status_code_str, reason = data[\"status\"].split(\" \", 1)\n        status_code = int(status_code_str)\n        headers: list[tuple[str, str]] = data[\"headers\"]\n        header_set = {k.lower() for (k, v) in headers}\n        body = escape.utf8(body)\n        if status_code != 304:\n            if \"content-length\" not in header_set:\n                headers.append((\"Content-Length\", str(len(body))))\n            if \"content-type\" not in header_set:\n                headers.append((\"Content-Type\", \"text/html; charset=UTF-8\"))\n        if \"server\" not in header_set:\n            headers.append((\"Server\", \"TornadoServer/%s\" % tornado.version))\n\n        start_line = httputil.ResponseStartLine(\"HTTP/1.1\", status_code, reason)\n        header_obj = httputil.HTTPHeaders()\n        for key, value in headers:\n            header_obj.add(key, value)\n        assert request.connection is not None\n        request.connection.write_headers(start_line, header_obj, chunk=body)\n        request.connection.finish()\n        self._log(status_code, request)\n\n    def environ(self, request: httputil.HTTPServerRequest) -> dict[str, Any]:\n        \"\"\"Converts a `tornado.httputil.HTTPServerRequest` to a WSGI environment.\n\n        .. versionchanged:: 6.3\n           No longer a static method.\n        \"\"\"\n        hostport = request.host.split(\":\")\n        if len(hostport) == 2:\n            host = hostport[0]\n            port = int(hostport[1])\n        else:\n            host = request.host\n            port = 443 if request.protocol == \"https\" else 80\n        environ = {\n            \"REQUEST_METHOD\": request.method,\n            \"SCRIPT_NAME\": \"\",\n            \"PATH_INFO\": to_wsgi_str(\n                escape.url_unescape(request.path, encoding=None, plus=False)\n            ),\n            \"QUERY_STRING\": request.query,\n            \"REMOTE_ADDR\": request.remote_ip,\n            \"SERVER_NAME\": host,\n            \"SERVER_PORT\": str(port),\n            \"SERVER_PROTOCOL\": request.version,\n            \"wsgi.version\": (1, 0),\n            \"wsgi.url_scheme\": request.protocol,\n            \"wsgi.input\": BytesIO(escape.utf8(request.body)),\n            \"wsgi.errors\": sys.stderr,\n            \"wsgi.multithread\": self.executor is not dummy_executor,\n            \"wsgi.multiprocess\": True,\n            \"wsgi.run_once\": False,\n        }\n        if \"Content-Type\" in request.headers:\n            environ[\"CONTENT_TYPE\"] = request.headers.pop(\"Content-Type\")\n        if \"Content-Length\" in request.headers:\n            environ[\"CONTENT_LENGTH\"] = request.headers.pop(\"Content-Length\")\n        for key, value in request.headers.items():\n            environ[\"HTTP_\" + key.replace(\"-\", \"_\").upper()] = value\n        return environ\n\n    def _log(self, status_code: int, request: httputil.HTTPServerRequest) -> None:\n        if status_code < 400:\n            log_method = access_log.info\n        elif status_code < 500:\n            log_method = access_log.warning\n        else:\n            log_method = access_log.error\n        request_time = 1000.0 * request.request_time()\n        assert request.method is not None\n        assert request.uri is not None\n        summary = (\n            request.method  # type: ignore[operator]\n            + \" \"\n            + request.uri\n            + \" (\"\n            + request.remote_ip\n            + \")\"\n        )\n        log_method(\"%d %s %.2fms\", status_code, summary, request_time)\n\n\nHTTPRequest = httputil.HTTPServerRequest\n"
  },
  {
    "path": "tox.ini",
    "content": "# Tox (https://tox.readthedocs.io) is a tool for running tests\n# in multiple virtualenvs.  This configuration file will run the tornado\n# test suite on all supported python versions.  To use it, \"pip install tox\"\n# and then run \"tox\" from this directory.\n#\n# This configuration requires tox 1.8 or higher.\n#\n# Installation tips:\n# When building pycurl on my macports-based setup, I need to either set the\n# environment variable ARCHFLAGS='-arch x86_64' or use\n# 'port install curl +universal' to get both 32- and 64-bit versions of\n# libcurl.\n[tox]\nenvlist =\n        # Basic configurations: Run the tests for each python version.\n        # [[[cog\n        #   versions = [f\"py3{m}\" for m in range(int(min_python_minor), int(max_python_minor) + 1)]\n        #   versions += [\"pypy3\"]\n        #   cog.outl(','.join(versions))\n        #  ]]]\n        py310,py311,py312,py313,py314,pypy3\n        # [[[end]]]\n\n        # Build and test the docs with sphinx.\n        docs\n\n        # Run the linters.\n        lint\n\n[testenv]\nbasepython =\n           # In theory, it doesn't matter which python version is used here.\n           # In practice, things like changes to the ast module can alter\n           # the outputs of the tools (especially where exactly the\n           # linter warning-suppression comments go), so we specify a\n           # python version for these builds.\n           # These versions must be synced with the versions in .github/workflows/test.yml\n           # [[[cog\n           #    cog.outl(f\"docs: python3.{default_python_minor}\")\n           #    cog.outl(f\"lint: python3.{default_python_minor}\")\n           #    ]]]\n           docs: python3.13\n           lint: python3.13\n           # [[[end]]]\n\ndeps =\n     full: pycurl\n     full: twisted\n     # Pycares 5 has some backwards-incompatible changes that we don't support.\n     # And since CaresResolver is deprecated, I do not expect to fix it, so just\n     # pin the previous version. (This should really be in requirements.{in,txt} instead)\n     full: pycares<5\n     docs: -r{toxinidir}/requirements.txt\n     lint: -r{toxinidir}/requirements.txt\n\nsetenv =\n       # Treat the extension as mandatory in testing (but not on pypy)\n       # [[[cog\n       #    versions = [f\"py3{m}\" for m in range(int(min_python_minor), int(max_python_minor) + 1)]\n       #    versions.insert(0, \"py3\")\n       #    cog.outl(f\"{{{\",\".join(versions)}}}: TORNADO_EXTENSION=1\")\n       # ]]]\n       {py3,py310,py311,py312,py313,py314}: TORNADO_EXTENSION=1\n       # [[[end]]]\n       # CI workers are often overloaded and can cause our tests to exceed\n       # the default timeout of 5s.\n       ASYNC_TEST_TIMEOUT=25\n       # Treat warnings as errors by default. We have a whitelist of\n       # allowed warnings in runtests.py, but we want to be strict\n       # about any import-time warnings before that setup code is\n       # reached. Note that syntax warnings are only reported in\n       # -opt builds because regular builds reuse pycs created\n       # during sdist installation (and it doesn't seem to be\n       # possible to set environment variables during that phase of\n       # tox).\n       PYTHONWARNINGS=error:::tornado\n       # Warn if we try to open a file with an unspecified encoding.\n       # (New in python 3.10, becomes obsolete when utf8 becomes the\n       # default in 3.15)\n       PYTHONWARNDEFAULTENCODING=1\n\n# Allow shell commands in tests\nallowlist_externals = sh, env\n\n\n# Tox filters line-by-line based on the environment name.\ncommands =\n         # py3*: -b turns on an extra warning when calling\n         # str(bytes), and -bb makes it an error.\n         python -bb -m tornado.test {posargs:}\n         # Python's optimized mode disables the assert statement, so\n         # run the tests in this mode to ensure we haven't fallen into\n         # the trap of relying on an assertion's side effects or using\n         # them for things that should be runtime errors.\n         full: python -O -m tornado.test\n         # Note that httpclient_test is always run with both client\n         # implementations; this flag controls which client all the\n         # other tests use.\n         full: python -m tornado.test --httpclient=tornado.curl_httpclient.CurlAsyncHTTPClient\n         full: python -m tornado.test --resolver=tornado.platform.caresresolver.CaresResolver\n\n# python will import relative to the current working directory by default,\n# so cd into the tox working directory to avoid picking up the working\n# copy of the files (especially important for the speedups module).\nchangedir = {toxworkdir}\n\n[testenv:docs]\nchangedir = docs\n# For some reason the extension fails to load in this configuration,\n# but it's not really needed for docs anyway.\nsetenv = TORNADO_EXTENSION=0\ncommands =\n    # Build the docs\n    sphinx-build -q -E -n -W -b html . {envtmpdir}/html\n    # Ensure that everything is either documented or ignored in conf.py\n    sphinx-build -q -E -n -W -b coverage . {envtmpdir}/coverage\n    # Run the doctests\n    sphinx-build -q -E -n -W -b doctest . {envtmpdir}/doctest\n\n[testenv:lint]\ncommands =\n         flake8 {posargs:}\n         black --check --diff {posargs:tornado demos}\n         # Many syscalls are defined differently on linux and windows,\n         # so we have to typecheck both.\n         # Mypy currently uses the default encoding so we must unset the warning variable\n         # here (must be completely unset, not just set to zero/empty). Remove this\n         # (and the allowlist_externals for env) when mypy sets the encoding explicitly.\n         env -u PYTHONWARNDEFAULTENCODING mypy --platform linux {posargs:tornado}\n         env -u PYTHONWARNDEFAULTENCODING mypy --platform windows {posargs:tornado}\n         # We mainly lint on the oldest version of Python we support, since\n         # we're more likely to catch problems of accidentally depending on\n         # something new than of depending on something old and deprecated.\n         # But sometimes something we depend on gets removed so we should also\n         # test the newest version.\n         # [[[cog cog.outl(f\"env -u PYTHONWARNDEFAULTENCODING mypy --platform linux --python-version 3.{default_python_minor} {{posargs:tornado}}\")]]]\n         env -u PYTHONWARNDEFAULTENCODING mypy --platform linux --python-version 3.13 {posargs:tornado}\n         # [[[end]]]\nchangedir = {toxinidir}\n"
  }
]