├── .cherry_picker.toml ├── .codecov.yml ├── .coveragerc ├── .editorconfig ├── .git-blame-ignore-revs ├── .gitattributes ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md ├── codeql.yml ├── config.yml ├── dependabot.yml ├── lock.yml └── workflows │ ├── auto-merge.yml │ ├── ci-cd.yml │ ├── codeql.yml │ ├── labels.yml │ ├── stale.yml │ └── update-pre-commit.yml ├── .gitignore ├── .gitmodules ├── .lgtm.yml ├── .mypy.ini ├── .pip-tools.toml ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── CHANGES.rst ├── CHANGES ├── .TEMPLATE.rst ├── .gitignore ├── 11105.bugfix.rst ├── 11106.bugfix.rst ├── 11107.misc.rst ├── 11112.bugfix.rst ├── 11114.misc.rst ├── 2174.bugfix ├── 2683.bugfix.rst ├── 2835.breaking.rst ├── 2977.breaking.rst ├── 3310.bugfix ├── 3462.feature ├── 3463.breaking.rst ├── 3482.bugfix ├── 3538.breaking.rst ├── 3539.breaking.rst ├── 3540.feature ├── 3542.breaking.rst ├── 3545.feature ├── 3547.breaking.rst ├── 3548.breaking.rst ├── 3559.doc ├── 3562.bugfix ├── 3569.feature ├── 3580.breaking.rst ├── 3612.bugfix ├── 3613.bugfix ├── 3642.doc ├── 3685.doc ├── 3721.bugfix ├── 3767.feature ├── 3787.feature ├── 3796.feature ├── 3890.breaking.rst ├── 3901.breaking.rst ├── 3929.breaking.rst ├── 3931.breaking.rst ├── 3932.breaking.rst ├── 3933.breaking.rst ├── 3934.breaking.rst ├── 3935.breaking.rst ├── 3939.breaking.rst ├── 3940.breaking.rst ├── 3942.breaking.rst ├── 3948.breaking.rst ├── 3994.misc ├── 4161.doc ├── 4277.feature ├── 4283.bugfix ├── 4299.bugfix ├── 4302.bugfix ├── 4368.bugfix ├── 4452.doc ├── 4486.bugfix.rst ├── 4504.doc ├── 4526.bugfix ├── 4558.bugfix ├── 4656.bugfix ├── 4695.doc ├── 4706.feature ├── 5075.feature ├── 5191.doc ├── 5258.bugfix ├── 5278.breaking.rst ├── 5284.breaking.rst ├── 5284.feature ├── 5287.feature ├── 5397.bugfix.rst ├── 5516.misc ├── 5533.misc ├── 5558.bugfix ├── 5634.feature ├── 5783.feature ├── 5806.misc ├── 5829.misc ├── 5870.misc ├── 5894.bugfix ├── 6180.bugfix ├── 6181.bugfix ├── 6193.feature ├── 6547.bugfix ├── 6721.misc ├── 6979.doc ├── 6998.doc ├── 7107.breaking.rst ├── 7265.breaking.rst ├── 7319.breaking.rst ├── 7319.feature.rst ├── 7677.bugfix ├── 7772.bugfix ├── 7815.bugfix ├── 7993.bugfix.rst ├── 8048.breaking.rst ├── 8139.contrib.rst ├── 8197.doc ├── 8303.breaking.rst ├── 8596.breaking.rst ├── 8698.breaking.rst ├── 8957.breaking.rst ├── 9109.breaking.rst ├── 9212.breaking.rst ├── 9212.packaging.rst ├── 9254.breaking.rst ├── 9292.breaking.rst ├── 9413.misc.rst └── README.rst ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.rst ├── CONTRIBUTORS.txt ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.rst ├── aiohttp ├── __init__.py ├── _cookie_helpers.py ├── _cparser.pxd ├── _find_header.h ├── _find_header.pxd ├── _http_parser.pyx ├── _http_writer.pyx ├── _websocket │ ├── __init__.py │ ├── helpers.py │ ├── mask.pxd │ ├── mask.pyx │ ├── models.py │ ├── reader.py │ ├── reader_c.pxd │ ├── reader_c.py │ ├── reader_py.py │ └── writer.py ├── abc.py ├── base_protocol.py ├── client.py ├── client_exceptions.py ├── client_middleware_digest_auth.py ├── client_middlewares.py ├── client_proto.py ├── client_reqrep.py ├── client_ws.py ├── compression_utils.py ├── connector.py ├── cookiejar.py ├── formdata.py ├── hdrs.py ├── helpers.py ├── http.py ├── http_exceptions.py ├── http_parser.py ├── http_websocket.py ├── http_writer.py ├── log.py ├── multipart.py ├── payload.py ├── py.typed ├── pytest_plugin.py ├── resolver.py ├── streams.py ├── tcp_helpers.py ├── test_utils.py ├── tracing.py ├── typedefs.py ├── web.py ├── web_app.py ├── web_exceptions.py ├── web_fileresponse.py ├── web_log.py ├── web_middlewares.py ├── web_protocol.py ├── web_request.py ├── web_response.py ├── web_routedef.py ├── web_runner.py ├── web_server.py ├── web_urldispatcher.py ├── web_ws.py └── worker.py ├── docs ├── Makefile ├── _static │ ├── css │ │ └── logo-adjustments.css │ └── img │ │ ├── contributing-cov-comment.svg │ │ ├── contributing-cov-header.svg │ │ ├── contributing-cov-miss.svg │ │ └── contributing-cov-partial.svg ├── abc.rst ├── aiohttp-icon.svg ├── aiohttp-plain.svg ├── built_with.rst ├── changes.rst ├── client.rst ├── client_advanced.rst ├── client_middleware_cookbook.rst ├── client_quickstart.rst ├── client_reference.rst ├── code │ └── client_middleware_cookbook.py ├── conf.py ├── contributing-admins.rst ├── contributing.rst ├── deployment.rst ├── essays.rst ├── external.rst ├── faq.rst ├── favicon.ico ├── glossary.rst ├── http_request_lifecycle.rst ├── index.rst ├── logging.rst ├── make.bat ├── migration_to_2xx.rst ├── misc.rst ├── multipart.rst ├── multipart_reference.rst ├── new_router.rst ├── old-logo.png ├── old-logo.svg ├── powered_by.rst ├── spelling_wordlist.txt ├── streams.rst ├── structures.rst ├── testing.rst ├── third_party.rst ├── tracing_reference.rst ├── utilities.rst ├── web.rst ├── web_advanced.rst ├── web_exceptions.rst ├── web_lowlevel.rst ├── web_quickstart.rst ├── web_reference.rst ├── websocket_utilities.rst ├── whats_new_1_1.rst └── whats_new_3_0.rst ├── examples ├── background_tasks.py ├── basic_auth_middleware.py ├── cli_app.py ├── client_auth.py ├── client_json.py ├── client_ws.py ├── combined_middleware.py ├── curl.py ├── digest_auth_qop_auth.py ├── fake_server.py ├── logging_middleware.py ├── lowlevel_srv.py ├── retry_middleware.py ├── server.crt ├── server.csr ├── server.key ├── server_simple.py ├── static_files.py ├── token_refresh_middleware.py ├── web_classview.py ├── web_cookies.py ├── web_rewrite_headers_middleware.py ├── web_srv.py ├── web_srv_route_deco.py ├── web_srv_route_table.py ├── web_ws.py └── websocket.html ├── pyproject.toml ├── requirements ├── base.in ├── base.txt ├── constraints.in ├── constraints.txt ├── cython.in ├── cython.txt ├── dev.in ├── dev.txt ├── doc-spelling.in ├── doc-spelling.txt ├── doc.in ├── doc.txt ├── lint.in ├── lint.txt ├── multidict.in ├── multidict.txt ├── runtime-deps.in ├── runtime-deps.txt ├── sync-direct-runtime-deps.py ├── test.in └── test.txt ├── setup.cfg ├── setup.py ├── tests ├── aiohttp.jpg ├── aiohttp.png ├── autobahn │ ├── Dockerfile.aiohttp │ ├── Dockerfile.autobahn │ ├── client │ │ ├── client.py │ │ └── fuzzingserver.json │ ├── server │ │ ├── fuzzingclient.json │ │ └── server.py │ └── test_autobahn.py ├── conftest.py ├── data.unknown_mime_type ├── data.zero_bytes ├── github-urls.json ├── isolated │ ├── check_for_client_response_leak.py │ └── check_for_request_leak.py ├── sample.txt ├── test_base_protocol.py ├── test_benchmarks_client.py ├── test_benchmarks_client_request.py ├── test_benchmarks_client_ws.py ├── test_benchmarks_cookiejar.py ├── test_benchmarks_http_websocket.py ├── test_benchmarks_http_writer.py ├── test_benchmarks_web_fileresponse.py ├── test_benchmarks_web_middleware.py ├── test_benchmarks_web_response.py ├── test_benchmarks_web_urldispatcher.py ├── test_circular_imports.py ├── test_classbasedview.py ├── test_client_connection.py ├── test_client_exceptions.py ├── test_client_fingerprint.py ├── test_client_functional.py ├── test_client_middleware.py ├── test_client_middleware_digest_auth.py ├── test_client_proto.py ├── test_client_request.py ├── test_client_response.py ├── test_client_session.py ├── test_client_ws.py ├── test_client_ws_functional.py ├── test_compression_utils.py ├── test_connector.py ├── test_cookie_helpers.py ├── test_cookiejar.py ├── test_flowcontrol_streams.py ├── test_formdata.py ├── test_helpers.py ├── test_http_exceptions.py ├── test_http_parser.py ├── test_http_writer.py ├── test_imports.py ├── test_leaks.py ├── test_loop.py ├── test_multipart.py ├── test_multipart_helpers.py ├── test_payload.py ├── test_proxy.py ├── test_proxy_functional.py ├── test_pytest_plugin.py ├── test_resolver.py ├── test_route_def.py ├── test_run_app.py ├── test_streams.py ├── test_tcp_helpers.py ├── test_test_utils.py ├── test_tracing.py ├── test_urldispatch.py ├── test_web_app.py ├── test_web_cli.py ├── test_web_exceptions.py ├── test_web_functional.py ├── test_web_log.py ├── test_web_middleware.py ├── test_web_request.py ├── test_web_request_handler.py ├── test_web_response.py ├── test_web_runner.py ├── test_web_sendfile.py ├── test_web_sendfile_functional.py ├── test_web_server.py ├── test_web_urldispatcher.py ├── test_web_websocket.py ├── test_web_websocket_functional.py ├── test_websocket_data_queue.py ├── test_websocket_handshake.py ├── test_websocket_parser.py ├── test_websocket_writer.py └── test_worker.py ├── tools ├── bench-asyncio-write.py ├── check_changes.py ├── check_sum.py ├── cleanup_changes.py ├── drop_merged_branches.sh ├── gen.py └── testing │ ├── Dockerfile │ ├── Dockerfile.dockerignore │ └── entrypoint.sh └── vendor └── README.rst /.cherry_picker.toml: -------------------------------------------------------------------------------- 1 | team = "aio-libs" 2 | repo = "aiohttp" 3 | check_sha = "f382b5ffc445e45a110734f5396728da7914aeb6" 4 | fix_commit_msg = false 5 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | branch: master 3 | notify: 4 | after_n_builds: 13 5 | 6 | coverage: 7 | range: "95..100" 8 | 9 | status: 10 | project: no 11 | 12 | flags: 13 | library: 14 | paths: 15 | - aiohttp/ 16 | configs: 17 | paths: 18 | - requirements/ 19 | - ".git*" 20 | - "*.toml" 21 | - "*.yml" 22 | changelog: 23 | paths: 24 | - CHANGES/ 25 | - CHANGES.rst 26 | docs: 27 | paths: 28 | - docs/ 29 | - "*.md" 30 | - "*.rst" 31 | - "*.txt" 32 | tests: 33 | paths: 34 | - tests/ 35 | tools: 36 | paths: 37 | - tools/ 38 | third-party: 39 | paths: 40 | - vendor/ 41 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = aiohttp, tests 4 | omit = site-packages 5 | 6 | [report] 7 | exclude_also = 8 | if TYPE_CHECKING 9 | assert False 10 | : \.\.\.(\s*#.*)?$ 11 | ^ +\.\.\.$ 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 4 12 | trim_trailing_whitespace = true 13 | charset = utf-8 14 | 15 | [Makefile] 16 | indent_style = tab 17 | 18 | [*.{yml,yaml}] 19 | indent_size = 2 20 | 21 | [*.rst] 22 | max_line_length = 80 23 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # git hyper-blame master ignore list. 2 | # 3 | # This file contains a list of git hashes of revisions to be ignored by git 4 | # hyper-blame (in depot_tools). These revisions are considered "unimportant" in 5 | # that they are unlikely to be what you are interested in when blaming. 6 | # 7 | # Instructions: 8 | # - Only large (generally automated) reformatting or renaming CLs should be 9 | # added to this list. Do not put things here just because you feel they are 10 | # trivial or unimportant. If in doubt, do not put it on this list. 11 | # - Precede each revision with a comment containing the first line of its log. 12 | # For bulk work over many commits, place all commits in a block with a single 13 | # comment at the top describing the work done in those commits. 14 | # - Only put full 40-character hashes on this list (not short hashes or any 15 | # other revision reference). 16 | # - Append to the bottom of the file (revisions should be in chronological order 17 | # from oldest to newest). 18 | # - Because you must use a hash, you need to append to this list in a follow-up 19 | # CL to the actual reformatting CL that you are trying to ignore. 20 | 21 | # Black 22 | 6ab76b084bf5012b7185046162ed92bedcf073b5 23 | 24 | # Apply new hooks 25 | 41c5467a62fb1041b77356ea22b81a74305941ef 26 | 27 | # Tune C source generation 28 | 3f7d64798d46b8b166139811a377ba231b4f36bf 29 | 30 | # Apply pyupgrade 31 | 32833c3fe081ff75c0b08ace9cc71e821a72fc5e 32 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | tests/data.unknown_mime_type binary 2 | tests/sample.* binary 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @asvetlov 2 | /.github/* @webknjaz @asvetlov 3 | /.circleci/* @webknjaz @asvetlov 4 | /CHANGES/* @asvetlov 5 | /docs/* @asvetlov 6 | /examples/* @asvetlov 7 | /requirements/* @webknjaz @asvetlov 8 | /tests/* @asvetlov 9 | /tools/* @webknjaz @asvetlov 10 | /vendor/* @webknjaz @asvetlov 11 | *.ini @webknjaz @asvetlov 12 | *.md @webknjaz @asvetlov 13 | *.rst @webknjaz @asvetlov 14 | *.toml @webknjaz @asvetlov 15 | *.txt @webknjaz @asvetlov 16 | *.yml @webknjaz @asvetlov 17 | *.yaml @webknjaz @asvetlov 18 | .editorconfig @webknjaz @asvetlov 19 | .git* @webknjaz 20 | Makefile @webknjaz @asvetlov 21 | setup.py @webknjaz @asvetlov 22 | setup.cfg @webknjaz @asvetlov 23 | tox.ini @webknjaz 24 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | - asvetlov 5 | - webknjaz 6 | - Dreamsorcerer 7 | # patreon: # Replace with a single Patreon username 8 | # open_collective: # Replace with a single Open Collective username 9 | # ko_fi: # Replace with a single Ko-fi username 10 | # tidelift: pypi/aiohttp # A single Tidelift platform-name/package-name e.g., npm/babel 11 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 12 | # liberapay: # Replace with a single Liberapay username 13 | # issuehunt: # Replace with a single IssueHunt username 14 | # otechie: # Replace with a single Otechie username 15 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Long story short 2 | 3 | 4 | 5 | ## Expected behaviour 6 | 7 | 8 | 9 | ## Actual behaviour 10 | 11 | 12 | 13 | ## Steps to reproduce 14 | 15 | 20 | 21 | ## Your environment 22 | 23 | 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser 2 | blank_issues_enabled: false # default: true 3 | contact_links: 4 | - name: 🤷💻🤦 StackOverflow 5 | url: https://stackoverflow.com/questions/tagged/aiohttp 6 | about: Please ask typical Q&A here 7 | - name: 💬 Github Discussions 8 | url: https://github.com/aio-libs/aiohttp/discussions 9 | about: Please start usage discussions here 10 | - name: 💬 Gitter Chat 11 | url: https://gitter.im/aio-libs/Lobby 12 | about: Chat with devs and community 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature request 3 | description: Suggest an idea for this project. 4 | labels: enhancement 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | **Thanks for taking a minute to file a feature for aiohttp!** 10 | 11 | ⚠ 12 | Verify first that your feature request is not [already reported on 13 | GitHub][issue search]. 14 | 15 | _Please fill out the form below with as many precise 16 | details as possible._ 17 | 18 | [issue search]: ../search?q=is%3Aissue&type=issues 19 | 20 | - type: textarea 21 | attributes: 22 | label: Is your feature request related to a problem? 23 | description: >- 24 | Please add a clear and concise description of what 25 | the problem is. _Ex. I'm always frustrated when [...]_ 26 | 27 | - type: textarea 28 | attributes: 29 | label: Describe the solution you'd like 30 | description: >- 31 | A clear and concise description of what you want to happen. 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | attributes: 37 | label: Describe alternatives you've considered 38 | description: >- 39 | A clear and concise description of any alternative solutions 40 | or features you've considered. 41 | validations: 42 | required: true 43 | 44 | - type: dropdown 45 | attributes: 46 | label: Related component 47 | description: >- 48 | aiohttp is both server framework and client library. 49 | For getting rid of confusing make sure to select 50 | 'server', 'client' or both. 51 | multiple: true 52 | options: 53 | - Server 54 | - Client 55 | validations: 56 | required: true 57 | 58 | - type: textarea 59 | attributes: 60 | label: Additional context 61 | description: >- 62 | Add any other context or screenshots about 63 | the feature request here. 64 | 65 | - type: checkboxes 66 | attributes: 67 | label: Code of Conduct 68 | description: | 69 | Read the [aio-libs Code of Conduct][CoC] first. 70 | 71 | [CoC]: https://github.com/aio-libs/.github/blob/master/CODE_OF_CONDUCT.md 72 | options: 73 | - label: I agree to follow the aio-libs Code of Conduct 74 | required: true 75 | ... 76 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## What do these changes do? 4 | 5 | 6 | 7 | ## Are there changes in behavior for the user? 8 | 9 | 10 | 11 | ## Is it a substantial burden for the maintainers to support this? 12 | 13 | 24 | 25 | ## Related issue number 26 | 27 | 28 | 29 | 30 | ## Checklist 31 | 32 | - [ ] I think the code is well written 33 | - [ ] Unit tests for the changes exist 34 | - [ ] Documentation reflects the changes 35 | - [ ] If you provide code modification, please add yourself to `CONTRIBUTORS.txt` 36 | * The format is <Name> <Surname>. 37 | * Please keep alphabetical order, the file is sorted by names. 38 | - [ ] Add a new news fragment into the `CHANGES/` folder 39 | * name it `..rst` (e.g. `588.bugfix.rst`) 40 | * if you don't have an issue number, change it to the pull request 41 | number after creating the PR 42 | * `.bugfix`: A bug fix for something the maintainers deemed an 43 | improper undesired behavior that got corrected to match 44 | pre-agreed expectations. 45 | * `.feature`: A new behavior, public APIs. That sort of stuff. 46 | * `.deprecation`: A declaration of future API removals and breaking 47 | changes in behavior. 48 | * `.breaking`: When something public is removed in a breaking way. 49 | Could be deprecated in an earlier release. 50 | * `.doc`: Notable updates to the documentation structure or build 51 | process. 52 | * `.packaging`: Notes for downstreams about unobvious side effects 53 | and tooling. Changes in the test invocation considerations and 54 | runtime assumptions. 55 | * `.contrib`: Stuff that affects the contributor experience. e.g. 56 | Running tests, building the docs, setting up the development 57 | environment. 58 | * `.misc`: Changes that are hard to assign to any of the above 59 | categories. 60 | * Make sure to use full sentences with correct case and punctuation, 61 | for example: 62 | ```rst 63 | Fixed issue with non-ascii contents in doctest text files 64 | -- by :user:`contributor-gh-handle`. 65 | ``` 66 | 67 | Use the past tense or the present tense a non-imperative mood, 68 | referring to what's changed compared to the last released version 69 | of this project. 70 | -------------------------------------------------------------------------------- /.github/codeql.yml: -------------------------------------------------------------------------------- 1 | query-filters: 2 | - exclude: 3 | id: 4 | - py/ineffectual-statement 5 | - py/unsafe-cyclic-import 6 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | chronographer: 2 | exclude: 3 | bots: 4 | - dependabot-preview 5 | - dependabot 6 | - patchback 7 | humans: 8 | - pyup-bot 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # Maintain dependencies for GitHub Actions 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | labels: 8 | - dependencies 9 | schedule: 10 | interval: "daily" 11 | 12 | # Maintain dependencies for Python 13 | - package-ecosystem: "pip" 14 | directory: "/" 15 | allow: 16 | - dependency-type: "all" 17 | labels: 18 | - dependencies 19 | schedule: 20 | interval: "daily" 21 | open-pull-requests-limit: 10 22 | 23 | # Maintain dependencies for GitHub Actions aiohttp backport 24 | - package-ecosystem: "github-actions" 25 | directory: "/" 26 | labels: 27 | - dependencies 28 | target-branch: "3.13" 29 | schedule: 30 | interval: "daily" 31 | open-pull-requests-limit: 10 32 | 33 | # Maintain dependencies for Python aiohttp backport 34 | - package-ecosystem: "pip" 35 | directory: "/" 36 | allow: 37 | - dependency-type: "all" 38 | labels: 39 | - dependencies 40 | target-branch: "3.13" 41 | schedule: 42 | interval: "daily" 43 | open-pull-requests-limit: 10 44 | -------------------------------------------------------------------------------- /.github/lock.yml: -------------------------------------------------------------------------------- 1 | # Configuration for Lock Threads - https://github.com/dessant/lock-threads 2 | # GitHub App - https://github.com/apps/lock 3 | 4 | --- 5 | # Number of days of inactivity before a closed issue or pull request is locked 6 | daysUntilLock: 365 7 | 8 | # Skip issues and pull requests created before a given timestamp. Timestamp must 9 | # follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable 10 | skipCreatedBefore: false 11 | 12 | # Issues and pull requests with these labels will be ignored. 13 | # Set to `[]` to disable 14 | exemptLabels: [] 15 | 16 | # Label to add before locking, such as `outdated`. 17 | # Set to `false` to disable 18 | lockLabel: outdated 19 | 20 | # Comment to post before locking. Set to `false` to disable 21 | lockComment: false 22 | 23 | # Assign `resolved` as the reason for locking. Set to `false` to disable 24 | setLockReason: true 25 | 26 | # Limit to only `issues` or `pulls` 27 | # only: issues 28 | 29 | # Optionally, specify configuration settings just for `issues` or `pulls` 30 | # issues: 31 | # exemptLabels: 32 | # - help-wanted 33 | # lockLabel: outdated 34 | 35 | # pulls: 36 | # daysUntilLock: 30 37 | 38 | # Repository to extend settings from 39 | # _extends: repo 40 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: pull_request_target 3 | 4 | permissions: 5 | pull-requests: write 6 | contents: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor == 'dependabot[bot]' }} 12 | steps: 13 | - name: Dependabot metadata 14 | id: metadata 15 | uses: dependabot/fetch-metadata@v2 16 | with: 17 | github-token: "${{ secrets.GITHUB_TOKEN }}" 18 | - name: Enable auto-merge for Dependabot PRs 19 | run: gh pr merge --auto --squash "$PR_URL" 20 | env: 21 | PR_URL: ${{github.event.pull_request.html_url}} 22 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 23 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | - '[0-9].[0-9]+' # matches to backport branches, e.g. 3.6 8 | pull_request: 9 | branches: [ "master" ] 10 | schedule: 11 | - cron: "9 1 * * 4" 12 | 13 | jobs: 14 | analyze: 15 | name: Analyze 16 | runs-on: ubuntu-latest 17 | permissions: 18 | actions: read 19 | contents: read 20 | security-events: write 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | language: [ python, javascript ] 26 | 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | 31 | - name: Initialize CodeQL 32 | uses: github/codeql-action/init@v3 33 | with: 34 | languages: ${{ matrix.language }} 35 | config-file: ./.github/codeql.yml 36 | queries: +security-and-quality 37 | 38 | - name: Autobuild 39 | uses: github/codeql-action/autobuild@v3 40 | if: ${{ matrix.language == 'python' || matrix.language == 'javascript' }} 41 | 42 | - name: Perform CodeQL Analysis 43 | uses: github/codeql-action/analyze@v3 44 | with: 45 | category: "/language:${{ matrix.language }}" 46 | -------------------------------------------------------------------------------- /.github/workflows/labels.yml: -------------------------------------------------------------------------------- 1 | name: Labels 2 | on: 3 | pull_request: 4 | branches: 5 | - 'master' 6 | types: [labeled, opened, synchronize, reopened, unlabeled] 7 | 8 | jobs: 9 | backport: 10 | runs-on: ubuntu-latest 11 | name: Backport label added 12 | if: ${{ github.event.pull_request.user.type != 'Bot' }} 13 | steps: 14 | - uses: actions/github-script@v7 15 | with: 16 | github-token: ${{ secrets.GITHUB_TOKEN }} 17 | script: | 18 | const pr = await github.rest.pulls.get({ 19 | owner: context.repo.owner, 20 | repo: context.repo.repo, 21 | pull_number: context.payload.pull_request.number 22 | }); 23 | if (!pr.data.labels.find(l => l.name.startsWith("backport"))) 24 | process.exit(1); 25 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues' 2 | on: 3 | schedule: 4 | - cron: '50 5 * * *' 5 | 6 | permissions: 7 | issues: write 8 | 9 | jobs: 10 | stale: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/stale@v9 14 | with: 15 | days-before-stale: 30 16 | any-of-labels: needs-info 17 | labels-to-remove-when-unstale: needs-info 18 | -------------------------------------------------------------------------------- /.github/workflows/update-pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: Pre-commit auto-update 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | permissions: {} 6 | jobs: 7 | auto-update: 8 | if: github.repository_owner == 'aiohttp' 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Set up Python 13 | uses: actions/setup-python@v5 14 | with: 15 | python-version: 3.9 16 | - name: Install dependencies 17 | run: >- 18 | pip install -r requirements/lint.in -c requirements/lint.txt 19 | - name: Run pre-commit autoupdate 20 | run: pre-commit autoupdate 21 | - id: generate_token 22 | uses: tibdex/github-app-token@v2.1 23 | with: 24 | app_id: ${{ secrets.BOT_APP_ID }} 25 | private_key: ${{ secrets.BOT_PRIVATE_KEY }} 26 | - name: Create Pull Request 27 | uses: peter-evans/create-pull-request@v7 28 | with: 29 | token: ${{ steps.generate_token.outputs.token }} 30 | branch: update/pre-commit-autoupdate 31 | title: Auto-update pre-commit hooks 32 | commit-message: Auto-update pre-commit hooks 33 | body: | 34 | Update versions of tools in pre-commit 35 | configs to latest version 36 | labels: dependencies backport-3.10 backport-3.11 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bak 2 | *.egg 3 | *.egg-info 4 | *.eggs 5 | *.md5 6 | *.pyc 7 | *.pyd 8 | *.pyo 9 | *.so 10 | *.swp 11 | *.tar.gz 12 | *~ 13 | .DS_Store 14 | .Python 15 | .cache 16 | .codspeed 17 | .coverage 18 | .coverage.* 19 | .develop 20 | .direnv 21 | .envrc 22 | .flake 23 | .gitconfig 24 | .hash 25 | .idea 26 | .install-cython 27 | .install-deps 28 | .llhttp-gen 29 | .installed.cfg 30 | .mypy_cache 31 | .noseids 32 | .pytest_cache 33 | .python-version 34 | .test-results 35 | .tox 36 | .vimrc 37 | .vscode 38 | aiohttp/_find_header.c 39 | aiohttp/_headers.html 40 | aiohttp/_headers.pxi 41 | aiohttp/_http_parser.c 42 | aiohttp/_http_parser.html 43 | aiohttp/_http_writer.c 44 | aiohttp/_http_writer.html 45 | aiohttp/_websocket.c 46 | aiohttp/_websocket.html 47 | aiohttp/_websocket/mask.c 48 | aiohttp/_websocket/reader_c.c 49 | bin 50 | build 51 | coverage.xml 52 | develop-eggs 53 | dist 54 | docs/_build/ 55 | eggs 56 | htmlcov 57 | include/ 58 | lib/ 59 | man/ 60 | nosetests.xml 61 | parts 62 | pip-wheel-metadata 63 | pyvenv 64 | sources 65 | var/* 66 | venv 67 | virtualenv.py 68 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/llhttp"] 2 | path = vendor/llhttp 3 | url = https://github.com/nodejs/llhttp.git 4 | branch = main 5 | -------------------------------------------------------------------------------- /.lgtm.yml: -------------------------------------------------------------------------------- 1 | queries: 2 | - exclude: py/unsafe-cyclic-import 3 | -------------------------------------------------------------------------------- /.mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | files = aiohttp, docs/code, examples, tests 3 | check_untyped_defs = True 4 | follow_imports_for_stubs = True 5 | disallow_any_decorated = True 6 | disallow_any_generics = True 7 | disallow_any_unimported = True 8 | disallow_incomplete_defs = True 9 | disallow_subclassing_any = True 10 | disallow_untyped_calls = True 11 | disallow_untyped_decorators = True 12 | disallow_untyped_defs = True 13 | # TODO(PY312): explicit-override 14 | enable_error_code = deprecated, ignore-without-code, possibly-undefined, redundant-expr, redundant-self, truthy-bool, truthy-iterable, unused-awaitable 15 | extra_checks = True 16 | follow_untyped_imports = True 17 | implicit_reexport = False 18 | no_implicit_optional = True 19 | pretty = True 20 | show_column_numbers = True 21 | show_error_codes = True 22 | show_error_code_links = True 23 | strict_bytes = True 24 | strict_equality = True 25 | warn_incomplete_stub = True 26 | warn_redundant_casts = True 27 | warn_return_any = True 28 | warn_unreachable = True 29 | warn_unused_ignores = True 30 | 31 | [mypy-brotli] 32 | ignore_missing_imports = True 33 | 34 | [mypy-brotlicffi] 35 | ignore_missing_imports = True 36 | 37 | [mypy-gunicorn.*] 38 | ignore_missing_imports = True 39 | -------------------------------------------------------------------------------- /.pip-tools.toml: -------------------------------------------------------------------------------- 1 | [pip-tools] 2 | allow-unsafe = true 3 | resolver = "backtracking" 4 | strip-extras = true 5 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: local 3 | hooks: 4 | - id: changelogs-rst 5 | name: changelog filenames 6 | language: fail 7 | entry: >- 8 | Changelog files must be named 9 | ####.( 10 | bugfix 11 | | feature 12 | | deprecation 13 | | breaking 14 | | doc 15 | | packaging 16 | | contrib 17 | | misc 18 | )(.#)?(.rst)? 19 | exclude: >- 20 | (?x) 21 | ^ 22 | CHANGES/( 23 | \.gitignore 24 | |(\d+|[0-9a-f]{8}|[0-9a-f]{7}|[0-9a-f]{40})\.( 25 | bugfix 26 | |feature 27 | |deprecation 28 | |breaking 29 | |doc 30 | |packaging 31 | |contrib 32 | |misc 33 | )(\.\d+)?(\.rst)? 34 | |README\.rst 35 | |\.TEMPLATE\.rst 36 | ) 37 | $ 38 | files: ^CHANGES/ 39 | - id: changelogs-user-role 40 | name: Changelog files should use a non-broken :user:`name` role 41 | language: pygrep 42 | entry: :user:([^`]+`?|`[^`]+[\s,]) 43 | pass_filenames: true 44 | types: [file, rst] 45 | - id: check-changes 46 | name: Check CHANGES 47 | language: system 48 | entry: ./tools/check_changes.py 49 | pass_filenames: false 50 | - repo: https://github.com/pre-commit/pre-commit-hooks 51 | rev: 'v5.0.0' 52 | hooks: 53 | - id: check-merge-conflict 54 | - repo: https://github.com/asottile/yesqa 55 | rev: v1.5.0 56 | hooks: 57 | - id: yesqa 58 | - repo: https://github.com/PyCQA/isort 59 | rev: '6.0.1' 60 | hooks: 61 | - id: isort 62 | - repo: https://github.com/psf/black 63 | rev: '25.1.0' 64 | hooks: 65 | - id: black 66 | language_version: python3 # Should be a command that runs python 67 | - repo: https://github.com/pre-commit/pre-commit-hooks 68 | rev: 'v5.0.0' 69 | hooks: 70 | - id: end-of-file-fixer 71 | exclude: >- 72 | ^docs/[^/]*\.svg$ 73 | - id: requirements-txt-fixer 74 | files: requirements/.*\.in$ 75 | - id: trailing-whitespace 76 | - id: file-contents-sorter 77 | args: ['--ignore-case'] 78 | files: | 79 | CONTRIBUTORS.txt| 80 | docs/spelling_wordlist.txt| 81 | .gitignore| 82 | .gitattributes 83 | - id: check-case-conflict 84 | - id: check-json 85 | - id: check-xml 86 | - id: check-executables-have-shebangs 87 | - id: check-toml 88 | - id: check-yaml 89 | - id: debug-statements 90 | - id: check-added-large-files 91 | - id: check-symlinks 92 | - id: fix-byte-order-marker 93 | - id: fix-encoding-pragma 94 | args: ['--remove'] 95 | - id: detect-aws-credentials 96 | args: ['--allow-missing-credentials'] 97 | - id: detect-private-key 98 | exclude: ^examples/ 99 | - repo: https://github.com/asottile/pyupgrade 100 | rev: 'v3.20.0' 101 | hooks: 102 | - id: pyupgrade 103 | args: ['--py37-plus'] 104 | - repo: https://github.com/PyCQA/flake8 105 | rev: '7.2.0' 106 | hooks: 107 | - id: flake8 108 | additional_dependencies: 109 | - flake8-docstrings==1.6.0 110 | - flake8-no-implicit-concat==0.3.4 111 | - flake8-requirements==1.7.8 112 | exclude: "^docs/" 113 | - repo: https://github.com/Lucas-C/pre-commit-hooks-markup 114 | rev: v1.0.1 115 | hooks: 116 | - id: rst-linter 117 | files: >- 118 | ^[^/]+[.]rst$ 119 | exclude: >- 120 | ^CHANGES\.rst$ 121 | - repo: https://github.com/codespell-project/codespell 122 | rev: v2.4.1 123 | hooks: 124 | - id: codespell 125 | additional_dependencies: 126 | - tomli 127 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html 3 | # for details 4 | 5 | --- 6 | version: 2 7 | 8 | sphinx: 9 | # Path to your Sphinx configuration file. 10 | configuration: docs/conf.py 11 | 12 | submodules: 13 | include: all 14 | exclude: [] 15 | recursive: true 16 | 17 | build: 18 | os: ubuntu-24.04 19 | tools: 20 | python: "3.11" 21 | apt_packages: 22 | - graphviz 23 | 24 | jobs: 25 | post_create_environment: 26 | - >- 27 | pip install 28 | . -c requirements/runtime-deps.txt 29 | -r requirements/doc.in -c requirements/doc.txt 30 | 31 | ... 32 | -------------------------------------------------------------------------------- /CHANGES/.TEMPLATE.rst: -------------------------------------------------------------------------------- 1 | {# TOWNCRIER TEMPLATE #} 2 | {% for section, _ in sections.items() %} 3 | {% set underline = underlines[0] %}{% if section %}{{section}} 4 | {{ underline * section|length }}{% set underline = underlines[1] %} 5 | 6 | {% endif %} 7 | 8 | {% if sections[section] %} 9 | {% for category, val in definitions.items() if category in sections[section]%} 10 | {{ definitions[category]['name'] }} 11 | {{ underline * definitions[category]['name']|length }} 12 | 13 | {% if definitions[category]['showcontent'] %} 14 | {% for text, change_note_refs in sections[section][category].items() %} 15 | - {{ text + '\n' }} 16 | 17 | {# 18 | NOTE: Replacing 'e' with 'f' is a hack that prevents Jinja's `int` 19 | NOTE: filter internal implementation from treating the input as an 20 | NOTE: infinite float when it looks like a scientific notation (with a 21 | NOTE: single 'e' char in between digits), raising an `OverflowError`, 22 | NOTE: subsequently. 'f' is still a hex letter so it won't affect the 23 | NOTE: check for whether it's a (short or long) commit hash or not. 24 | Ref: https://github.com/pallets/jinja/issues/1921 25 | -#} 26 | {%- 27 | set pr_issue_numbers = change_note_refs 28 | | map('lower') 29 | | map('replace', 'e', 'f') 30 | | map('int', default=None) 31 | | select('integer') 32 | | map('string') 33 | | list 34 | -%} 35 | {%- set arbitrary_refs = [] -%} 36 | {%- set commit_refs = [] -%} 37 | {%- with -%} 38 | {%- set commit_ref_candidates = change_note_refs | reject('in', pr_issue_numbers) -%} 39 | {%- for cf in commit_ref_candidates -%} 40 | {%- if cf | length in (7, 8, 40) and cf | int(default=None, base=16) is not none -%} 41 | {%- set _ = commit_refs.append(cf) -%} 42 | {%- else -%} 43 | {%- set _ = arbitrary_refs.append(cf) -%} 44 | {%- endif -%} 45 | {%- endfor -%} 46 | {%- endwith -%} 47 | 48 | {% if pr_issue_numbers -%} 49 | *Related issues and pull requests on GitHub:* 50 | :issue:`{{ pr_issue_numbers | join('`, :issue:`') }}`. 51 | {% endif %} 52 | 53 | {% if commit_refs -%} 54 | *Related commits on GitHub:* 55 | :commit:`{{ commit_refs | join('`, :commit:`') }}`. 56 | {% endif %} 57 | 58 | {% if arbitrary_refs -%} 59 | *Unlinked references:* 60 | {{ arbitrary_refs | join(', ') }}`. 61 | {% endif %} 62 | 63 | {% endfor %} 64 | {% else %} 65 | - {{ sections[section][category]['']|join(', ') }} 66 | 67 | {% endif %} 68 | {% if sections[section][category]|length == 0 %} 69 | No significant changes. 70 | 71 | {% else %} 72 | {% endif %} 73 | 74 | {% endfor %} 75 | {% else %} 76 | No significant changes. 77 | 78 | 79 | {% endif %} 80 | {% endfor %} 81 | ---- 82 | {{ '\n' * 2 }} 83 | -------------------------------------------------------------------------------- /CHANGES/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.TEMPLATE.rst 3 | !.gitignore 4 | !README.rst 5 | !*.bugfix 6 | !*.bugfix.rst 7 | !*.bugfix.*.rst 8 | !*.breaking 9 | !*.breaking.rst 10 | !*.breaking.*.rst 11 | !*.contrib 12 | !*.contrib.rst 13 | !*.contrib.*.rst 14 | !*.deprecation 15 | !*.deprecation.rst 16 | !*.deprecation.*.rst 17 | !*.doc 18 | !*.doc.rst 19 | !*.doc.*.rst 20 | !*.feature 21 | !*.feature.rst 22 | !*.feature.*.rst 23 | !*.misc 24 | !*.misc.rst 25 | !*.misc.*.rst 26 | !*.packaging 27 | !*.packaging.rst 28 | !*.packaging.*.rst 29 | -------------------------------------------------------------------------------- /CHANGES/11105.bugfix.rst: -------------------------------------------------------------------------------- 1 | Fixed an issue where cookies with duplicate names but different domains or paths 2 | were lost when updating the cookie jar. The :class:`~aiohttp.ClientSession` 3 | cookie jar now correctly stores all cookies even if they have the same name but 4 | different domain or path, following the :rfc:`6265#section-5.3` storage model -- by :user:`bdraco`. 5 | 6 | Note that :attr:`ClientResponse.cookies ` returns 7 | a :class:`~http.cookies.SimpleCookie` which uses the cookie name as a key, so 8 | only the last cookie with each name is accessible via this interface. All cookies 9 | can be accessed via :meth:`ClientResponse.headers.getall('Set-Cookie') 10 | ` if needed. 11 | -------------------------------------------------------------------------------- /CHANGES/11106.bugfix.rst: -------------------------------------------------------------------------------- 1 | 11105.bugfix.rst -------------------------------------------------------------------------------- /CHANGES/11107.misc.rst: -------------------------------------------------------------------------------- 1 | Avoided creating closed futures in ``ResponseHandler`` that will never be awaited -- by :user:`bdraco`. 2 | -------------------------------------------------------------------------------- /CHANGES/11112.bugfix.rst: -------------------------------------------------------------------------------- 1 | Fixed cookie parsing to be more lenient when handling cookies with special characters 2 | in names or values. Cookies with characters like ``{``, ``}``, and ``/`` in names are now 3 | accepted instead of causing a :exc:`~http.cookies.CookieError` and 500 errors. Additionally, 4 | cookies with mismatched quotes in values are now parsed correctly, and quoted cookie 5 | values are now handled consistently whether or not they include special attributes 6 | like ``Domain``. Also fixed :class:`~aiohttp.CookieJar` to ensure shared cookies (domain="", path="") 7 | respect the ``quote_cookie`` parameter, making cookie quoting behavior consistent for 8 | all cookies -- by :user:`bdraco`. 9 | -------------------------------------------------------------------------------- /CHANGES/11114.misc.rst: -------------------------------------------------------------------------------- 1 | Downgraded the logging level for connector close errors from ERROR to DEBUG, as these are expected behavior with TLS 1.3 connections -- by :user:`bdraco`. 2 | -------------------------------------------------------------------------------- /CHANGES/2174.bugfix: -------------------------------------------------------------------------------- 1 | Raise 400 Bad Request on server-side `await request.json()` if incorrect content-type received. 2 | -------------------------------------------------------------------------------- /CHANGES/2683.bugfix.rst: -------------------------------------------------------------------------------- 1 | 11112.bugfix.rst -------------------------------------------------------------------------------- /CHANGES/2835.breaking.rst: -------------------------------------------------------------------------------- 1 | Drop lowercased enum items of ``WSMsgType`` (text, binary, ...), use uppercased items instead (TEXT, BINARY, ...). 2 | -------------------------------------------------------------------------------- /CHANGES/2977.breaking.rst: -------------------------------------------------------------------------------- 1 | Drop aiodns<1.1 support. 2 | -------------------------------------------------------------------------------- /CHANGES/3310.bugfix: -------------------------------------------------------------------------------- 1 | Docs clarification that aiohttp client does not support HTTP Pipelining. 2 | -------------------------------------------------------------------------------- /CHANGES/3462.feature: -------------------------------------------------------------------------------- 1 | ``web.HTTPException`` and derived classes are not inherited from ``web.Response`` anymore. 2 | -------------------------------------------------------------------------------- /CHANGES/3463.breaking.rst: -------------------------------------------------------------------------------- 1 | Make ``ClientSession`` slot-based class, convert debug-mode warning about a wild session modification into a strict error. 2 | -------------------------------------------------------------------------------- /CHANGES/3482.bugfix: -------------------------------------------------------------------------------- 1 | Do not return `None` on `await response.json()` when body is empty. Instead, raise `json.JSONDecodeError` as expected. 2 | -------------------------------------------------------------------------------- /CHANGES/3538.breaking.rst: -------------------------------------------------------------------------------- 1 | Drop ``@aiohttp.streamer`` decorator, use async generators instead. 2 | -------------------------------------------------------------------------------- /CHANGES/3539.breaking.rst: -------------------------------------------------------------------------------- 1 | Disallow creation of aiohttp objects (``ClientSession``, ``Connector`` etc.) without running event loop. 2 | -------------------------------------------------------------------------------- /CHANGES/3540.feature: -------------------------------------------------------------------------------- 1 | Make sanity check for web-handler return value working in release mode 2 | -------------------------------------------------------------------------------- /CHANGES/3542.breaking.rst: -------------------------------------------------------------------------------- 1 | Setting ``web.Application`` custom attributes is now forbidden 2 | -------------------------------------------------------------------------------- /CHANGES/3545.feature: -------------------------------------------------------------------------------- 1 | Drop custom router support 2 | -------------------------------------------------------------------------------- /CHANGES/3547.breaking.rst: -------------------------------------------------------------------------------- 1 | Remove deprecated resp.url_obj 2 | -------------------------------------------------------------------------------- /CHANGES/3548.breaking.rst: -------------------------------------------------------------------------------- 1 | Drop deprecated SSL client settings. 2 | -------------------------------------------------------------------------------- /CHANGES/3559.doc: -------------------------------------------------------------------------------- 1 | Clarified ``WebSocketResponse`` closure in the quick start example. 2 | -------------------------------------------------------------------------------- /CHANGES/3562.bugfix: -------------------------------------------------------------------------------- 1 | Raise ``web_exceptions.HTTPUnsupportedMediaType`` when invalid `Content-Type` encoding passed. 2 | -------------------------------------------------------------------------------- /CHANGES/3569.feature: -------------------------------------------------------------------------------- 1 | Make new style middleware default, deprecate the @middleware decorator and 2 | remove support for old-style middleware. 3 | -------------------------------------------------------------------------------- /CHANGES/3580.breaking.rst: -------------------------------------------------------------------------------- 1 | Drop explicit loop. Use ``asyncio.get_event_loop()`` instead if the loop instance is needed. 2 | All aiohttp objects work with the currently running loop, a creation of aiohttp instances, e.g. ClientSession when the loop is not running is forbidden. 3 | As a side effect of PR passing callables to ``aiohttp_server()`` and ``aiohttp_client()`` pytest fixtures are forbidden, please call these callables explicitly. 4 | -------------------------------------------------------------------------------- /CHANGES/3612.bugfix: -------------------------------------------------------------------------------- 1 | Fixed a grammatical error in documentation 2 | -------------------------------------------------------------------------------- /CHANGES/3613.bugfix: -------------------------------------------------------------------------------- 1 | Use sanitized URL as Location header in redirects 2 | -------------------------------------------------------------------------------- /CHANGES/3642.doc: -------------------------------------------------------------------------------- 1 | Modify documentation for Resolvers to make it clear that asynchronous resolver is not used by default when aiodns is installed. 2 | -------------------------------------------------------------------------------- /CHANGES/3685.doc: -------------------------------------------------------------------------------- 1 | Add documentation regarding creating and destroying persistent session. 2 | -------------------------------------------------------------------------------- /CHANGES/3721.bugfix: -------------------------------------------------------------------------------- 1 | Add the missing `TestClient.scheme` property. 2 | -------------------------------------------------------------------------------- /CHANGES/3767.feature: -------------------------------------------------------------------------------- 1 | Add ``AbstractAsyncAccessLogger`` to allow IO while logging. 2 | -------------------------------------------------------------------------------- /CHANGES/3787.feature: -------------------------------------------------------------------------------- 1 | Added ability to use contextvars in logger 2 | -------------------------------------------------------------------------------- /CHANGES/3796.feature: -------------------------------------------------------------------------------- 1 | Add a debug argument to `web.run_app()` for enabling debug mode on loop. 2 | -------------------------------------------------------------------------------- /CHANGES/3890.breaking.rst: -------------------------------------------------------------------------------- 1 | Drop deprecated `read_timeout` and `conn_timeout` in `ClientSession` constructor, please use `timeout` argument instead. 2 | -------------------------------------------------------------------------------- /CHANGES/3901.breaking.rst: -------------------------------------------------------------------------------- 1 | Drop sync context managers that raises ``TypeError`` already. 2 | -------------------------------------------------------------------------------- /CHANGES/3929.breaking.rst: -------------------------------------------------------------------------------- 1 | Drop processing sync web-handlers (deprecated since aiohttp 3.0) 2 | -------------------------------------------------------------------------------- /CHANGES/3931.breaking.rst: -------------------------------------------------------------------------------- 1 | Drop deprecated ``BaseRequest.message``, ``BaseRequest.loop``, ``BaseRequest.has_body`` 2 | -------------------------------------------------------------------------------- /CHANGES/3932.breaking.rst: -------------------------------------------------------------------------------- 1 | Drop deprecated ``unused_port``, ``test_server``, ``raw_test_server`` and ``test_client`` pytest fixtures. 2 | -------------------------------------------------------------------------------- /CHANGES/3933.breaking.rst: -------------------------------------------------------------------------------- 1 | Forbid inheritance from ``ClientSession`` and ``Application`` 2 | -------------------------------------------------------------------------------- /CHANGES/3934.breaking.rst: -------------------------------------------------------------------------------- 1 | Drop deprecated ``ClientResponseError.code`` attribute 2 | -------------------------------------------------------------------------------- /CHANGES/3935.breaking.rst: -------------------------------------------------------------------------------- 1 | Drop deprecated ``ClientSession.loop`` and ``Connection.loop``. Forbid changing ``ClientSession.requote_redirect_url``. 2 | -------------------------------------------------------------------------------- /CHANGES/3939.breaking.rst: -------------------------------------------------------------------------------- 1 | Drop deprecated ``Application.make_handler()`` 2 | -------------------------------------------------------------------------------- /CHANGES/3940.breaking.rst: -------------------------------------------------------------------------------- 1 | Drop HTTP chunk size from client and server, remove deprecated `response.output_length`. 2 | -------------------------------------------------------------------------------- /CHANGES/3942.breaking.rst: -------------------------------------------------------------------------------- 1 | Make `web.BaseRequest`, `web.Request`, `web.StreamResponse`, `web.Response` and `web.WebSocketResponse` slot-based, prevent custom instance attributes. 2 | -------------------------------------------------------------------------------- /CHANGES/3948.breaking.rst: -------------------------------------------------------------------------------- 1 | Forbid changing frozen app properties. 2 | -------------------------------------------------------------------------------- /CHANGES/3994.misc: -------------------------------------------------------------------------------- 1 | correct the names of some functions in ``tests/test_client_functional.py`` 2 | -------------------------------------------------------------------------------- /CHANGES/4161.doc: -------------------------------------------------------------------------------- 1 | Update contributing guide so new contributors can successfully install dependencies 2 | -------------------------------------------------------------------------------- /CHANGES/4277.feature: -------------------------------------------------------------------------------- 1 | Added ``set_cookie`` and ``del_cookie`` methods to ``HTTPException`` 2 | -------------------------------------------------------------------------------- /CHANGES/4283.bugfix: -------------------------------------------------------------------------------- 1 | Fix incorrect code in example 2 | -------------------------------------------------------------------------------- /CHANGES/4299.bugfix: -------------------------------------------------------------------------------- 1 | Delete older code in example (:file:`examples/web_classview.py`) 2 | -------------------------------------------------------------------------------- /CHANGES/4302.bugfix: -------------------------------------------------------------------------------- 1 | Fixed the support of route handlers wrapped by :py:func:`functools.partial` 2 | -------------------------------------------------------------------------------- /CHANGES/4368.bugfix: -------------------------------------------------------------------------------- 1 | Make `web.BaseRequest`, `web.Request`, `web.StreamResponse`, `web.Response` and `web.WebSocketResponse` weak referenceable again. 2 | -------------------------------------------------------------------------------- /CHANGES/4452.doc: -------------------------------------------------------------------------------- 1 | Fixed a typo in the ``client_quickstart`` doc. 2 | -------------------------------------------------------------------------------- /CHANGES/4486.bugfix.rst: -------------------------------------------------------------------------------- 1 | 11105.bugfix.rst -------------------------------------------------------------------------------- /CHANGES/4504.doc: -------------------------------------------------------------------------------- 1 | Updated the contribution guide to reflect the automatic thread locking policy. 2 | -------------------------------------------------------------------------------- /CHANGES/4526.bugfix: -------------------------------------------------------------------------------- 1 | Ignore protocol exceptions after it is closed. 2 | -------------------------------------------------------------------------------- /CHANGES/4558.bugfix: -------------------------------------------------------------------------------- 1 | Fixed body_size comparison to client_max_size for web request. 2 | -------------------------------------------------------------------------------- /CHANGES/4656.bugfix: -------------------------------------------------------------------------------- 1 | Propagate all warnings captured in coroutine test functions to pytest. 2 | -------------------------------------------------------------------------------- /CHANGES/4695.doc: -------------------------------------------------------------------------------- 1 | Added documentation on how to patch unittest cases with decorator for python < 3.8 2 | -------------------------------------------------------------------------------- /CHANGES/4706.feature: -------------------------------------------------------------------------------- 1 | Add a fixture ``aiohttp_client_cls`` that allows usage of ``aiohttp.test_utils.TestClient`` custom implementations in tests. 2 | -------------------------------------------------------------------------------- /CHANGES/5075.feature: -------------------------------------------------------------------------------- 1 | Multidict > 5 is now supported 2 | -------------------------------------------------------------------------------- /CHANGES/5191.doc: -------------------------------------------------------------------------------- 1 | Add pytest-aiohttp-client library to third party usage list 2 | -------------------------------------------------------------------------------- /CHANGES/5258.bugfix: -------------------------------------------------------------------------------- 1 | Fixed github workflow `update-pre-commit` on forks, 2 | since this workflow should run only in the main repository and also because it was giving failed jobs on all the forks. 3 | Now it will show up as skipped workflow. 4 | -------------------------------------------------------------------------------- /CHANGES/5278.breaking.rst: -------------------------------------------------------------------------------- 1 | Drop Python 3.6 support 2 | -------------------------------------------------------------------------------- /CHANGES/5284.breaking.rst: -------------------------------------------------------------------------------- 1 | ``attrs`` library was replaced with ``dataclasses``. Replace ``attr.evolve()`` with ``dataclasses.replace()`` if needed. 2 | -------------------------------------------------------------------------------- /CHANGES/5284.feature: -------------------------------------------------------------------------------- 1 | Use ``dataclasses`` instead of ``attrs`` for ``ClientTimeout``, client signals, and other few internal structures. 2 | -------------------------------------------------------------------------------- /CHANGES/5287.feature: -------------------------------------------------------------------------------- 1 | Before ``sentinel`` was processed as either ``object`` or ``Any``, both variants are far from perfectness. 2 | 3 | Now ``sentinel`` has a dedicated type which is not equal to anything. 4 | -------------------------------------------------------------------------------- /CHANGES/5397.bugfix.rst: -------------------------------------------------------------------------------- 1 | 11112.bugfix.rst -------------------------------------------------------------------------------- /CHANGES/5516.misc: -------------------------------------------------------------------------------- 1 | Removed @unittest_run_loop. This is now the default behaviour. 2 | -------------------------------------------------------------------------------- /CHANGES/5533.misc: -------------------------------------------------------------------------------- 1 | Add regression test for 0 timeouts. 2 | -------------------------------------------------------------------------------- /CHANGES/5558.bugfix: -------------------------------------------------------------------------------- 1 | Add parsing boundary from Content-Type header while making POST request 2 | -------------------------------------------------------------------------------- /CHANGES/5634.feature: -------------------------------------------------------------------------------- 1 | A warning was added, when a cookie's length exceeds the :rfc:`6265` minimum client support -- :user:`anesabml`. 2 | -------------------------------------------------------------------------------- /CHANGES/5783.feature: -------------------------------------------------------------------------------- 1 | Started keeping the ``Authorization`` header during HTTP -> HTTPS redirects when the host remains the same. 2 | -------------------------------------------------------------------------------- /CHANGES/5806.misc: -------------------------------------------------------------------------------- 1 | Remove last remnants of attrs library. 2 | -------------------------------------------------------------------------------- /CHANGES/5829.misc: -------------------------------------------------------------------------------- 1 | Disallow untyped defs on internal tests. 2 | -------------------------------------------------------------------------------- /CHANGES/5870.misc: -------------------------------------------------------------------------------- 1 | Simplify generator expression. 2 | -------------------------------------------------------------------------------- /CHANGES/5894.bugfix: -------------------------------------------------------------------------------- 1 | Fix JSON media type suffix matching with main types other than application. 2 | -------------------------------------------------------------------------------- /CHANGES/6180.bugfix: -------------------------------------------------------------------------------- 1 | Fixed matching the JSON media type to not accept arbitrary characters after ``application/json`` or the ``+json`` media type suffix. 2 | -------------------------------------------------------------------------------- /CHANGES/6181.bugfix: -------------------------------------------------------------------------------- 1 | Make JSON media type matching case insensitive per RFC 2045. 2 | -------------------------------------------------------------------------------- /CHANGES/6193.feature: -------------------------------------------------------------------------------- 1 | Bump async-timeout to >=4.0 2 | -------------------------------------------------------------------------------- /CHANGES/6547.bugfix: -------------------------------------------------------------------------------- 1 | Remove overlapping slots in ``RequestHandler``, 2 | fix broken slots inheritance in :py:class:`~aiohttp.web.StreamResponse`. 3 | -------------------------------------------------------------------------------- /CHANGES/6721.misc: -------------------------------------------------------------------------------- 1 | Remove unused argument `max_headers` of HeadersParser. 2 | -------------------------------------------------------------------------------- /CHANGES/6979.doc: -------------------------------------------------------------------------------- 1 | Improve grammar and brevity in communication in the Policy for Backward Incompatible Changes section of ``docs/index.rst`` -- :user:`Paarth`. 2 | -------------------------------------------------------------------------------- /CHANGES/6998.doc: -------------------------------------------------------------------------------- 1 | Added documentation on client authentication and updating headers. -- by :user:`faph` 2 | -------------------------------------------------------------------------------- /CHANGES/7107.breaking.rst: -------------------------------------------------------------------------------- 1 | Removed deprecated ``.loop``, ``.setUpAsync()``, ``.tearDownAsync()`` and ``.get_app()`` from ``AioHTTPTestCase``. 2 | -------------------------------------------------------------------------------- /CHANGES/7265.breaking.rst: -------------------------------------------------------------------------------- 1 | Deleted ``size`` arg from ``StreamReader.feed_data`` -- by :user:`DavidRomanovizc`. 2 | -------------------------------------------------------------------------------- /CHANGES/7319.breaking.rst: -------------------------------------------------------------------------------- 1 | 7319.feature.rst -------------------------------------------------------------------------------- /CHANGES/7319.feature.rst: -------------------------------------------------------------------------------- 1 | Changed ``WSMessage`` to a tagged union of ``NamedTuple`` -- by :user:`Dreamsorcerer`. 2 | 3 | This change allows type checkers to know the precise type of ``data`` 4 | after checking the ``type`` attribute. 5 | 6 | If accessing messages by tuple indexes, the order has now changed. 7 | Code such as: 8 | ``typ, data, extra = ws_message`` 9 | will need to be changed to: 10 | ``data, extra, typ = ws_message`` 11 | 12 | No changes are needed if accessing by attribute name. 13 | -------------------------------------------------------------------------------- /CHANGES/7677.bugfix: -------------------------------------------------------------------------------- 1 | Changed ``AppKey`` warning to ``web.NotAppKeyWarning`` and stop it being displayed by default. -- by :user:`Dreamsorcerer` 2 | -------------------------------------------------------------------------------- /CHANGES/7772.bugfix: -------------------------------------------------------------------------------- 1 | Fix CONNECT always being treated as having an empty body 2 | -------------------------------------------------------------------------------- /CHANGES/7815.bugfix: -------------------------------------------------------------------------------- 1 | Fixed an issue where the client could go into an infinite loop. -- by :user:`Dreamsorcerer` 2 | -------------------------------------------------------------------------------- /CHANGES/7993.bugfix.rst: -------------------------------------------------------------------------------- 1 | 11112.bugfix.rst -------------------------------------------------------------------------------- /CHANGES/8048.breaking.rst: -------------------------------------------------------------------------------- 1 | Removed deprecated support for `ssl=None` -- by :user:`Dreamsorcerer` 2 | -------------------------------------------------------------------------------- /CHANGES/8139.contrib.rst: -------------------------------------------------------------------------------- 1 | Two definitions for "test_invalid_route_name" existed, only one was being run. Refactored them into a single parameterized test. Enabled lint rule to prevent regression. -- by :user:`alexmac`. 2 | -------------------------------------------------------------------------------- /CHANGES/8197.doc: -------------------------------------------------------------------------------- 1 | Fixed false behavior of base_url param for ClientSession in client documentation -- by :user:`alexis974`. 2 | -------------------------------------------------------------------------------- /CHANGES/8303.breaking.rst: -------------------------------------------------------------------------------- 1 | Removed ``content_transfer_encoding`` parameter in :py:meth:`FormData.add_field() 2 | ` and passing bytes no longer creates a file 3 | field unless the ``filename`` parameter is used -- by :user:`Dreamsorcerer`. 4 | -------------------------------------------------------------------------------- /CHANGES/8596.breaking.rst: -------------------------------------------------------------------------------- 1 | Removed old async compatibility from ``ClientResponse.release()`` -- by :user:`Dreamsorcerer`. 2 | -------------------------------------------------------------------------------- /CHANGES/8698.breaking.rst: -------------------------------------------------------------------------------- 1 | Changed signature of ``content_disposition_header()`` so ``params`` is now passed as a dict, in order to reduce typing errors -- by :user:`Dreamsorcerer`. 2 | -------------------------------------------------------------------------------- /CHANGES/8957.breaking.rst: -------------------------------------------------------------------------------- 1 | Removed ``version`` parameter from ``.set_cookie()`` (this shouldn't exist in cookies today) -- by :user:`Dreamsorcerer`. 2 | -------------------------------------------------------------------------------- /CHANGES/9109.breaking.rst: -------------------------------------------------------------------------------- 1 | Changed default value to ``compress`` from ``None`` to ``False`` (``None`` is no longer an expected value) -- by :user:`Dreamsorcerer`. 2 | -------------------------------------------------------------------------------- /CHANGES/9212.breaking.rst: -------------------------------------------------------------------------------- 1 | 9212.packaging.rst -------------------------------------------------------------------------------- /CHANGES/9212.packaging.rst: -------------------------------------------------------------------------------- 1 | Removed remaining `make_mocked_coro` in the test suite -- by :user:`polkapolka`. 2 | -------------------------------------------------------------------------------- /CHANGES/9254.breaking.rst: -------------------------------------------------------------------------------- 1 | Stopped allowing use of ``ClientResponse.text()``/``ClientResponse.json()`` after leaving ``async with`` context. 2 | This now matches the behaviour of ``ClientResponse.read()`` -- by :user:`Dreamsorcerer`. 3 | -------------------------------------------------------------------------------- /CHANGES/9292.breaking.rst: -------------------------------------------------------------------------------- 1 | Started rejecting non string values in `FormData`, to avoid unexpected results -- by :user:`Dreamsorcerer`. 2 | -------------------------------------------------------------------------------- /CHANGES/9413.misc.rst: -------------------------------------------------------------------------------- 1 | Reduced memory required many small objects by adding ``__slots__`` to dataclasses -- by :user:`bdraco`. 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at andrew.svetlov@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Instructions for contributors 5 | ----------------------------- 6 | 7 | 8 | In order to make a clone of the GitHub_ repo: open the link and press the 9 | "Fork" button on the upper-right menu of the web page. 10 | 11 | I hope everybody knows how to work with git and github nowadays :) 12 | 13 | Workflow is pretty straightforward: 14 | 15 | 1. Clone the GitHub_ repo using the ``--recurse-submodules`` argument 16 | 17 | 2. Setup your machine with the required development environment 18 | 19 | 3. Make a change 20 | 21 | 4. Make sure all tests passed 22 | 23 | 5. Add a file into the ``CHANGES`` folder, named after the ticket or PR number 24 | 25 | 6. Commit changes to your own aiohttp clone 26 | 27 | 7. Make a pull request from the github page of your clone against the master branch 28 | 29 | 8. Optionally make backport Pull Request(s) for landing a bug fix into released aiohttp versions. 30 | 31 | .. important:: 32 | 33 | Please open the "`contributing `_" 34 | documentation page to get detailed information about all steps. 35 | 36 | .. _GitHub: https://github.com/aio-libs/aiohttp 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright aio-libs contributors. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.txt 2 | include CHANGES.rst 3 | include README.rst 4 | include CONTRIBUTORS.txt 5 | include Makefile 6 | graft aiohttp 7 | graft docs 8 | graft examples 9 | graft tests 10 | graft tools 11 | graft requirements 12 | recursive-include vendor * 13 | global-include aiohttp *.pyi 14 | global-exclude *.pyc 15 | global-exclude *.pyd 16 | global-exclude *.so 17 | global-exclude *.lib 18 | global-exclude *.dll 19 | global-exclude *.a 20 | global-exclude *.obj 21 | exclude aiohttp/*.html 22 | prune docs/_build 23 | -------------------------------------------------------------------------------- /aiohttp/_find_header.h: -------------------------------------------------------------------------------- 1 | #ifndef _FIND_HEADERS_H 2 | #define _FIND_HEADERS_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | int find_header(const char *str, int size); 9 | 10 | 11 | #ifdef __cplusplus 12 | } 13 | #endif 14 | #endif 15 | -------------------------------------------------------------------------------- /aiohttp/_find_header.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from "_find_header.h": 2 | int find_header(char *, int) 3 | -------------------------------------------------------------------------------- /aiohttp/_websocket/__init__.py: -------------------------------------------------------------------------------- 1 | """WebSocket protocol versions 13 and 8.""" 2 | -------------------------------------------------------------------------------- /aiohttp/_websocket/mask.pxd: -------------------------------------------------------------------------------- 1 | """Cython declarations for websocket masking.""" 2 | 3 | cpdef void _websocket_mask_cython(bytes mask, bytearray data) 4 | -------------------------------------------------------------------------------- /aiohttp/_websocket/mask.pyx: -------------------------------------------------------------------------------- 1 | from cpython cimport PyBytes_AsString 2 | 3 | 4 | #from cpython cimport PyByteArray_AsString # cython still not exports that 5 | cdef extern from "Python.h": 6 | char* PyByteArray_AsString(bytearray ba) except NULL 7 | 8 | from libc.stdint cimport uint32_t, uint64_t, uintmax_t 9 | 10 | 11 | cpdef void _websocket_mask_cython(bytes mask, bytearray data): 12 | """Note, this function mutates its `data` argument 13 | """ 14 | cdef: 15 | Py_ssize_t data_len, i 16 | # bit operations on signed integers are implementation-specific 17 | unsigned char * in_buf 18 | const unsigned char * mask_buf 19 | uint32_t uint32_msk 20 | uint64_t uint64_msk 21 | 22 | assert len(mask) == 4 23 | 24 | data_len = len(data) 25 | in_buf = PyByteArray_AsString(data) 26 | mask_buf = PyBytes_AsString(mask) 27 | uint32_msk = (mask_buf)[0] 28 | 29 | # TODO: align in_data ptr to achieve even faster speeds 30 | # does it need in python ?! malloc() always aligns to sizeof(long) bytes 31 | 32 | if sizeof(size_t) >= 8: 33 | uint64_msk = uint32_msk 34 | uint64_msk = (uint64_msk << 32) | uint32_msk 35 | 36 | while data_len >= 8: 37 | (in_buf)[0] ^= uint64_msk 38 | in_buf += 8 39 | data_len -= 8 40 | 41 | 42 | while data_len >= 4: 43 | (in_buf)[0] ^= uint32_msk 44 | in_buf += 4 45 | data_len -= 4 46 | 47 | for i in range(0, data_len): 48 | in_buf[i] ^= mask_buf[i] 49 | -------------------------------------------------------------------------------- /aiohttp/_websocket/models.py: -------------------------------------------------------------------------------- 1 | """Models for WebSocket protocol versions 13 and 8.""" 2 | 3 | import json 4 | from enum import IntEnum 5 | from typing import Any, Callable, Final, Literal, NamedTuple, Optional, Union, cast 6 | 7 | WS_DEFLATE_TRAILING: Final[bytes] = bytes([0x00, 0x00, 0xFF, 0xFF]) 8 | 9 | 10 | class WSCloseCode(IntEnum): 11 | OK = 1000 12 | GOING_AWAY = 1001 13 | PROTOCOL_ERROR = 1002 14 | UNSUPPORTED_DATA = 1003 15 | ABNORMAL_CLOSURE = 1006 16 | INVALID_TEXT = 1007 17 | POLICY_VIOLATION = 1008 18 | MESSAGE_TOO_BIG = 1009 19 | MANDATORY_EXTENSION = 1010 20 | INTERNAL_ERROR = 1011 21 | SERVICE_RESTART = 1012 22 | TRY_AGAIN_LATER = 1013 23 | BAD_GATEWAY = 1014 24 | 25 | 26 | class WSMsgType(IntEnum): 27 | # websocket spec types 28 | CONTINUATION = 0x0 29 | TEXT = 0x1 30 | BINARY = 0x2 31 | PING = 0x9 32 | PONG = 0xA 33 | CLOSE = 0x8 34 | 35 | # aiohttp specific types 36 | CLOSING = 0x100 37 | CLOSED = 0x101 38 | ERROR = 0x102 39 | 40 | 41 | class WSMessageContinuation(NamedTuple): 42 | data: bytes 43 | size: int 44 | extra: Optional[str] = None 45 | type: Literal[WSMsgType.CONTINUATION] = WSMsgType.CONTINUATION 46 | 47 | 48 | class WSMessageText(NamedTuple): 49 | data: str 50 | size: int 51 | extra: Optional[str] = None 52 | type: Literal[WSMsgType.TEXT] = WSMsgType.TEXT 53 | 54 | def json( 55 | self, *, loads: Callable[[Union[str, bytes, bytearray]], Any] = json.loads 56 | ) -> Any: 57 | """Return parsed JSON data.""" 58 | return loads(self.data) 59 | 60 | 61 | class WSMessageBinary(NamedTuple): 62 | data: bytes 63 | size: int 64 | extra: Optional[str] = None 65 | type: Literal[WSMsgType.BINARY] = WSMsgType.BINARY 66 | 67 | def json( 68 | self, *, loads: Callable[[Union[str, bytes, bytearray]], Any] = json.loads 69 | ) -> Any: 70 | """Return parsed JSON data.""" 71 | return loads(self.data) 72 | 73 | 74 | class WSMessagePing(NamedTuple): 75 | data: bytes 76 | size: int 77 | extra: Optional[str] = None 78 | type: Literal[WSMsgType.PING] = WSMsgType.PING 79 | 80 | 81 | class WSMessagePong(NamedTuple): 82 | data: bytes 83 | size: int 84 | extra: Optional[str] = None 85 | type: Literal[WSMsgType.PONG] = WSMsgType.PONG 86 | 87 | 88 | class WSMessageClose(NamedTuple): 89 | data: int 90 | size: int 91 | extra: Optional[str] = None 92 | type: Literal[WSMsgType.CLOSE] = WSMsgType.CLOSE 93 | 94 | 95 | class WSMessageClosing(NamedTuple): 96 | data: None = None 97 | size: int = 0 98 | extra: Optional[str] = None 99 | type: Literal[WSMsgType.CLOSING] = WSMsgType.CLOSING 100 | 101 | 102 | class WSMessageClosed(NamedTuple): 103 | data: None = None 104 | size: int = 0 105 | extra: Optional[str] = None 106 | type: Literal[WSMsgType.CLOSED] = WSMsgType.CLOSED 107 | 108 | 109 | class WSMessageError(NamedTuple): 110 | data: BaseException 111 | size: int = 0 112 | extra: Optional[str] = None 113 | type: Literal[WSMsgType.ERROR] = WSMsgType.ERROR 114 | 115 | 116 | WSMessage = Union[ 117 | WSMessageContinuation, 118 | WSMessageText, 119 | WSMessageBinary, 120 | WSMessagePing, 121 | WSMessagePong, 122 | WSMessageClose, 123 | WSMessageClosing, 124 | WSMessageClosed, 125 | WSMessageError, 126 | ] 127 | 128 | WS_CLOSED_MESSAGE = WSMessageClosed() 129 | WS_CLOSING_MESSAGE = WSMessageClosing() 130 | 131 | 132 | class WebSocketError(Exception): 133 | """WebSocket protocol parser error.""" 134 | 135 | def __init__(self, code: int, message: str) -> None: 136 | self.code = code 137 | super().__init__(code, message) 138 | 139 | def __str__(self) -> str: 140 | return cast(str, self.args[1]) 141 | 142 | 143 | class WSHandshakeError(Exception): 144 | """WebSocket protocol handshake error.""" 145 | -------------------------------------------------------------------------------- /aiohttp/_websocket/reader.py: -------------------------------------------------------------------------------- 1 | """Reader for WebSocket protocol versions 13 and 8.""" 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from ..helpers import NO_EXTENSIONS 6 | 7 | if TYPE_CHECKING or NO_EXTENSIONS: 8 | from .reader_py import ( 9 | WebSocketDataQueue as WebSocketDataQueuePython, 10 | WebSocketReader as WebSocketReaderPython, 11 | ) 12 | 13 | WebSocketReader = WebSocketReaderPython 14 | WebSocketDataQueue = WebSocketDataQueuePython 15 | else: 16 | try: 17 | from .reader_c import ( # type: ignore[import-not-found] 18 | WebSocketDataQueue as WebSocketDataQueueCython, 19 | WebSocketReader as WebSocketReaderCython, 20 | ) 21 | 22 | WebSocketReader = WebSocketReaderCython 23 | WebSocketDataQueue = WebSocketDataQueueCython 24 | except ImportError: # pragma: no cover 25 | from .reader_py import ( 26 | WebSocketDataQueue as WebSocketDataQueuePython, 27 | WebSocketReader as WebSocketReaderPython, 28 | ) 29 | 30 | WebSocketReader = WebSocketReaderPython 31 | WebSocketDataQueue = WebSocketDataQueuePython 32 | -------------------------------------------------------------------------------- /aiohttp/_websocket/reader_c.pxd: -------------------------------------------------------------------------------- 1 | import cython 2 | 3 | from .mask cimport _websocket_mask_cython as websocket_mask 4 | 5 | 6 | cdef unsigned int READ_HEADER 7 | cdef unsigned int READ_PAYLOAD_LENGTH 8 | cdef unsigned int READ_PAYLOAD_MASK 9 | cdef unsigned int READ_PAYLOAD 10 | 11 | cdef int OP_CODE_NOT_SET 12 | cdef int OP_CODE_CONTINUATION 13 | cdef int OP_CODE_TEXT 14 | cdef int OP_CODE_BINARY 15 | cdef int OP_CODE_CLOSE 16 | cdef int OP_CODE_PING 17 | cdef int OP_CODE_PONG 18 | 19 | cdef int COMPRESSED_NOT_SET 20 | cdef int COMPRESSED_FALSE 21 | cdef int COMPRESSED_TRUE 22 | 23 | cdef object UNPACK_LEN3 24 | cdef object UNPACK_CLOSE_CODE 25 | cdef object TUPLE_NEW 26 | 27 | cdef object WSMsgType 28 | 29 | cdef object WSMessageText 30 | cdef object WSMessageBinary 31 | cdef object WSMessagePing 32 | cdef object WSMessagePong 33 | cdef object WSMessageClose 34 | 35 | cdef object WS_MSG_TYPE_TEXT 36 | cdef object WS_MSG_TYPE_BINARY 37 | 38 | cdef set ALLOWED_CLOSE_CODES 39 | cdef set MESSAGE_TYPES_WITH_CONTENT 40 | 41 | cdef tuple EMPTY_FRAME 42 | cdef tuple EMPTY_FRAME_ERROR 43 | 44 | cdef class WebSocketDataQueue: 45 | 46 | cdef unsigned int _size 47 | cdef public object _protocol 48 | cdef unsigned int _limit 49 | cdef object _loop 50 | cdef bint _eof 51 | cdef object _waiter 52 | cdef object _exception 53 | cdef public object _buffer 54 | cdef object _get_buffer 55 | cdef object _put_buffer 56 | 57 | cdef void _release_waiter(self) 58 | 59 | @cython.locals(size="unsigned int") 60 | cpdef void feed_data(self, object data) 61 | 62 | @cython.locals(size="unsigned int") 63 | cdef _read_from_buffer(self) 64 | 65 | cdef class WebSocketReader: 66 | 67 | cdef WebSocketDataQueue queue 68 | cdef unsigned int _max_msg_size 69 | 70 | cdef Exception _exc 71 | cdef bytearray _partial 72 | cdef unsigned int _state 73 | 74 | cdef int _opcode 75 | cdef bint _frame_fin 76 | cdef int _frame_opcode 77 | cdef list _payload_fragments 78 | cdef Py_ssize_t _frame_payload_len 79 | 80 | cdef bytes _tail 81 | cdef bint _has_mask 82 | cdef bytes _frame_mask 83 | cdef Py_ssize_t _payload_bytes_to_read 84 | cdef unsigned int _payload_len_flag 85 | cdef int _compressed 86 | cdef object _decompressobj 87 | cdef bint _compress 88 | 89 | cpdef tuple feed_data(self, object data) 90 | 91 | @cython.locals( 92 | is_continuation=bint, 93 | fin=bint, 94 | has_partial=bint, 95 | payload_merged=bytes, 96 | ) 97 | cpdef void _handle_frame(self, bint fin, int opcode, object payload, int compressed) except * 98 | 99 | @cython.locals( 100 | start_pos=Py_ssize_t, 101 | data_len=Py_ssize_t, 102 | length=Py_ssize_t, 103 | chunk_size=Py_ssize_t, 104 | chunk_len=Py_ssize_t, 105 | data_len=Py_ssize_t, 106 | data_cstr="const unsigned char *", 107 | first_byte="unsigned char", 108 | second_byte="unsigned char", 109 | f_start_pos=Py_ssize_t, 110 | f_end_pos=Py_ssize_t, 111 | has_mask=bint, 112 | fin=bint, 113 | had_fragments=Py_ssize_t, 114 | payload_bytearray=bytearray, 115 | ) 116 | cpdef void _feed_data(self, bytes data) except * 117 | -------------------------------------------------------------------------------- /aiohttp/_websocket/reader_c.py: -------------------------------------------------------------------------------- 1 | reader_py.py -------------------------------------------------------------------------------- /aiohttp/base_protocol.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import Optional, cast 3 | 4 | from .client_exceptions import ClientConnectionResetError 5 | from .helpers import set_exception 6 | from .tcp_helpers import tcp_nodelay 7 | 8 | 9 | class BaseProtocol(asyncio.Protocol): 10 | __slots__ = ( 11 | "_loop", 12 | "_paused", 13 | "_drain_waiter", 14 | "_connection_lost", 15 | "_reading_paused", 16 | "transport", 17 | ) 18 | 19 | def __init__(self, loop: asyncio.AbstractEventLoop) -> None: 20 | self._loop: asyncio.AbstractEventLoop = loop 21 | self._paused = False 22 | self._drain_waiter: Optional[asyncio.Future[None]] = None 23 | self._reading_paused = False 24 | 25 | self.transport: Optional[asyncio.Transport] = None 26 | 27 | @property 28 | def connected(self) -> bool: 29 | """Return True if the connection is open.""" 30 | return self.transport is not None 31 | 32 | @property 33 | def writing_paused(self) -> bool: 34 | return self._paused 35 | 36 | def pause_writing(self) -> None: 37 | assert not self._paused 38 | self._paused = True 39 | 40 | def resume_writing(self) -> None: 41 | assert self._paused 42 | self._paused = False 43 | 44 | waiter = self._drain_waiter 45 | if waiter is not None: 46 | self._drain_waiter = None 47 | if not waiter.done(): 48 | waiter.set_result(None) 49 | 50 | def pause_reading(self) -> None: 51 | if not self._reading_paused and self.transport is not None: 52 | try: 53 | self.transport.pause_reading() 54 | except (AttributeError, NotImplementedError, RuntimeError): 55 | pass 56 | self._reading_paused = True 57 | 58 | def resume_reading(self) -> None: 59 | if self._reading_paused and self.transport is not None: 60 | try: 61 | self.transport.resume_reading() 62 | except (AttributeError, NotImplementedError, RuntimeError): 63 | pass 64 | self._reading_paused = False 65 | 66 | def connection_made(self, transport: asyncio.BaseTransport) -> None: 67 | tr = cast(asyncio.Transport, transport) 68 | tcp_nodelay(tr, True) 69 | self.transport = tr 70 | 71 | def connection_lost(self, exc: Optional[BaseException]) -> None: 72 | # Wake up the writer if currently paused. 73 | self.transport = None 74 | if not self._paused: 75 | return 76 | waiter = self._drain_waiter 77 | if waiter is None: 78 | return 79 | self._drain_waiter = None 80 | if waiter.done(): 81 | return 82 | if exc is None: 83 | waiter.set_result(None) 84 | else: 85 | set_exception( 86 | waiter, 87 | ConnectionError("Connection lost"), 88 | exc, 89 | ) 90 | 91 | async def _drain_helper(self) -> None: 92 | if self.transport is None: 93 | raise ClientConnectionResetError("Connection lost") 94 | if not self._paused: 95 | return 96 | waiter = self._drain_waiter 97 | if waiter is None: 98 | waiter = self._loop.create_future() 99 | self._drain_waiter = waiter 100 | await asyncio.shield(waiter) 101 | -------------------------------------------------------------------------------- /aiohttp/client_middlewares.py: -------------------------------------------------------------------------------- 1 | """Client middleware support.""" 2 | 3 | from collections.abc import Awaitable, Callable, Sequence 4 | 5 | from .client_reqrep import ClientRequest, ClientResponse 6 | 7 | __all__ = ("ClientMiddlewareType", "ClientHandlerType", "build_client_middlewares") 8 | 9 | # Type alias for client request handlers - functions that process requests and return responses 10 | ClientHandlerType = Callable[[ClientRequest], Awaitable[ClientResponse]] 11 | 12 | # Type for client middleware - similar to server but uses ClientRequest/ClientResponse 13 | ClientMiddlewareType = Callable[ 14 | [ClientRequest, ClientHandlerType], Awaitable[ClientResponse] 15 | ] 16 | 17 | 18 | def build_client_middlewares( 19 | handler: ClientHandlerType, 20 | middlewares: Sequence[ClientMiddlewareType], 21 | ) -> ClientHandlerType: 22 | """ 23 | Apply middlewares to request handler. 24 | 25 | The middlewares are applied in reverse order, so the first middleware 26 | in the list wraps all subsequent middlewares and the handler. 27 | 28 | This implementation avoids using partial/update_wrapper to minimize overhead 29 | and doesn't cache to avoid holding references to stateful middleware. 30 | """ 31 | # Optimize for single middleware case 32 | if len(middlewares) == 1: 33 | middleware = middlewares[0] 34 | 35 | async def single_middleware_handler(req: ClientRequest) -> ClientResponse: 36 | return await middleware(req, handler) 37 | 38 | return single_middleware_handler 39 | 40 | # Build the chain for multiple middlewares 41 | current_handler = handler 42 | 43 | for middleware in reversed(middlewares): 44 | # Create a new closure that captures the current state 45 | def make_wrapper( 46 | mw: ClientMiddlewareType, next_h: ClientHandlerType 47 | ) -> ClientHandlerType: 48 | async def wrapped(req: ClientRequest) -> ClientResponse: 49 | return await mw(req, next_h) 50 | 51 | return wrapped 52 | 53 | current_handler = make_wrapper(middleware, current_handler) 54 | 55 | return current_handler 56 | -------------------------------------------------------------------------------- /aiohttp/http.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from . import __version__ 4 | from .http_exceptions import HttpProcessingError 5 | from .http_parser import ( 6 | HeadersParser, 7 | HttpParser, 8 | HttpRequestParser, 9 | HttpResponseParser, 10 | RawRequestMessage, 11 | RawResponseMessage, 12 | ) 13 | from .http_websocket import ( 14 | WS_CLOSED_MESSAGE, 15 | WS_CLOSING_MESSAGE, 16 | WS_KEY, 17 | WebSocketError, 18 | WebSocketReader, 19 | WebSocketWriter, 20 | WSCloseCode, 21 | WSMessage, 22 | WSMsgType, 23 | ws_ext_gen, 24 | ws_ext_parse, 25 | ) 26 | from .http_writer import HttpVersion, HttpVersion10, HttpVersion11, StreamWriter 27 | 28 | __all__ = ( 29 | "HttpProcessingError", 30 | "SERVER_SOFTWARE", 31 | # .http_writer 32 | "StreamWriter", 33 | "HttpVersion", 34 | "HttpVersion10", 35 | "HttpVersion11", 36 | # .http_parser 37 | "HeadersParser", 38 | "HttpParser", 39 | "HttpRequestParser", 40 | "HttpResponseParser", 41 | "RawRequestMessage", 42 | "RawResponseMessage", 43 | # .http_websocket 44 | "WS_CLOSED_MESSAGE", 45 | "WS_CLOSING_MESSAGE", 46 | "WS_KEY", 47 | "WebSocketReader", 48 | "WebSocketWriter", 49 | "ws_ext_gen", 50 | "ws_ext_parse", 51 | "WSMessage", 52 | "WebSocketError", 53 | "WSMsgType", 54 | "WSCloseCode", 55 | ) 56 | 57 | 58 | SERVER_SOFTWARE: str = "Python/{0[0]}.{0[1]} aiohttp/{1}".format( 59 | sys.version_info, __version__ 60 | ) 61 | -------------------------------------------------------------------------------- /aiohttp/http_exceptions.py: -------------------------------------------------------------------------------- 1 | """Low-level http related exceptions.""" 2 | 3 | from textwrap import indent 4 | from typing import Optional, Union 5 | 6 | from .typedefs import _CIMultiDict 7 | 8 | __all__ = ("HttpProcessingError",) 9 | 10 | 11 | class HttpProcessingError(Exception): 12 | """HTTP error. 13 | 14 | Shortcut for raising HTTP errors with custom code, message and headers. 15 | 16 | code: HTTP Error code. 17 | message: (optional) Error message. 18 | headers: (optional) Headers to be sent in response, a list of pairs 19 | """ 20 | 21 | code = 0 22 | message = "" 23 | headers = None 24 | 25 | def __init__( 26 | self, 27 | *, 28 | code: Optional[int] = None, 29 | message: str = "", 30 | headers: Optional[_CIMultiDict] = None, 31 | ) -> None: 32 | if code is not None: 33 | self.code = code 34 | self.headers = headers 35 | self.message = message 36 | 37 | def __str__(self) -> str: 38 | msg = indent(self.message, " ") 39 | return f"{self.code}, message:\n{msg}" 40 | 41 | def __repr__(self) -> str: 42 | return f"<{self.__class__.__name__}: {self.code}, message={self.message!r}>" 43 | 44 | 45 | class BadHttpMessage(HttpProcessingError): 46 | code = 400 47 | message = "Bad Request" 48 | 49 | def __init__(self, message: str, *, headers: Optional[_CIMultiDict] = None) -> None: 50 | super().__init__(message=message, headers=headers) 51 | self.args = (message,) 52 | 53 | 54 | class HttpBadRequest(BadHttpMessage): 55 | code = 400 56 | message = "Bad Request" 57 | 58 | 59 | class PayloadEncodingError(BadHttpMessage): 60 | """Base class for payload errors""" 61 | 62 | 63 | class ContentEncodingError(PayloadEncodingError): 64 | """Content encoding error.""" 65 | 66 | 67 | class TransferEncodingError(PayloadEncodingError): 68 | """transfer encoding error.""" 69 | 70 | 71 | class ContentLengthError(PayloadEncodingError): 72 | """Not enough data to satisfy content length header.""" 73 | 74 | 75 | class LineTooLong(BadHttpMessage): 76 | def __init__( 77 | self, line: str, limit: str = "Unknown", actual_size: str = "Unknown" 78 | ) -> None: 79 | super().__init__( 80 | f"Got more than {limit} bytes ({actual_size}) when reading {line}." 81 | ) 82 | self.args = (line, limit, actual_size) 83 | 84 | 85 | class InvalidHeader(BadHttpMessage): 86 | def __init__(self, hdr: Union[bytes, str]) -> None: 87 | hdr_s = hdr.decode(errors="backslashreplace") if isinstance(hdr, bytes) else hdr 88 | super().__init__(f"Invalid HTTP header: {hdr!r}") 89 | self.hdr = hdr_s 90 | self.args = (hdr,) 91 | 92 | 93 | class BadStatusLine(BadHttpMessage): 94 | def __init__(self, line: str = "", error: Optional[str] = None) -> None: 95 | super().__init__(error or f"Bad status line {line!r}") 96 | self.args = (line,) 97 | self.line = line 98 | 99 | 100 | class BadHttpMethod(BadStatusLine): 101 | """Invalid HTTP method in status line.""" 102 | 103 | def __init__(self, line: str = "", error: Optional[str] = None) -> None: 104 | super().__init__(line, error or f"Bad HTTP method in status line {line!r}") 105 | 106 | 107 | class InvalidURLError(BadHttpMessage): 108 | pass 109 | -------------------------------------------------------------------------------- /aiohttp/http_websocket.py: -------------------------------------------------------------------------------- 1 | """WebSocket protocol versions 13 and 8.""" 2 | 3 | from ._websocket.helpers import WS_KEY, ws_ext_gen, ws_ext_parse 4 | from ._websocket.models import ( 5 | WS_CLOSED_MESSAGE, 6 | WS_CLOSING_MESSAGE, 7 | WebSocketError, 8 | WSCloseCode, 9 | WSHandshakeError, 10 | WSMessage, 11 | WSMessageBinary, 12 | WSMessageClose, 13 | WSMessageClosed, 14 | WSMessageClosing, 15 | WSMessageContinuation, 16 | WSMessageError, 17 | WSMessagePing, 18 | WSMessagePong, 19 | WSMessageText, 20 | WSMsgType, 21 | ) 22 | from ._websocket.reader import WebSocketReader 23 | from ._websocket.writer import WebSocketWriter 24 | 25 | # Messages that the WebSocketResponse.receive needs to handle internally 26 | _INTERNAL_RECEIVE_TYPES = frozenset( 27 | (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.PING, WSMsgType.PONG) 28 | ) 29 | 30 | 31 | __all__ = ( 32 | "WS_CLOSED_MESSAGE", 33 | "WS_CLOSING_MESSAGE", 34 | "WS_KEY", 35 | "WebSocketReader", 36 | "WebSocketWriter", 37 | "WSMessage", 38 | "WebSocketError", 39 | "WSMsgType", 40 | "WSCloseCode", 41 | "ws_ext_gen", 42 | "ws_ext_parse", 43 | "WSMessageError", 44 | "WSHandshakeError", 45 | "WSMessageClose", 46 | "WSMessageClosed", 47 | "WSMessageClosing", 48 | "WSMessagePong", 49 | "WSMessageBinary", 50 | "WSMessageText", 51 | "WSMessagePing", 52 | "WSMessageContinuation", 53 | ) 54 | -------------------------------------------------------------------------------- /aiohttp/log.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | access_logger = logging.getLogger("aiohttp.access") 4 | client_logger = logging.getLogger("aiohttp.client") 5 | internal_logger = logging.getLogger("aiohttp.internal") 6 | server_logger = logging.getLogger("aiohttp.server") 7 | web_logger = logging.getLogger("aiohttp.web") 8 | ws_logger = logging.getLogger("aiohttp.websocket") 9 | -------------------------------------------------------------------------------- /aiohttp/py.typed: -------------------------------------------------------------------------------- 1 | Marker 2 | -------------------------------------------------------------------------------- /aiohttp/tcp_helpers.py: -------------------------------------------------------------------------------- 1 | """Helper methods to tune a TCP connection""" 2 | 3 | import asyncio 4 | import socket 5 | from contextlib import suppress 6 | 7 | __all__ = ("tcp_keepalive", "tcp_nodelay") 8 | 9 | 10 | if hasattr(socket, "SO_KEEPALIVE"): 11 | 12 | def tcp_keepalive(transport: asyncio.Transport) -> None: 13 | sock = transport.get_extra_info("socket") 14 | if sock is not None: 15 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) 16 | 17 | else: 18 | 19 | def tcp_keepalive(transport: asyncio.Transport) -> None: 20 | """Noop when keepalive not supported.""" 21 | 22 | 23 | def tcp_nodelay(transport: asyncio.Transport, value: bool) -> None: 24 | sock = transport.get_extra_info("socket") 25 | 26 | if sock is None: 27 | return 28 | 29 | if sock.family not in (socket.AF_INET, socket.AF_INET6): 30 | return 31 | 32 | value = bool(value) 33 | 34 | # socket may be closed already, on windows OSError get raised 35 | with suppress(OSError): 36 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, value) 37 | -------------------------------------------------------------------------------- /aiohttp/typedefs.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from typing import ( 4 | TYPE_CHECKING, 5 | Any, 6 | Awaitable, 7 | Callable, 8 | Iterable, 9 | Mapping, 10 | Protocol, 11 | Tuple, 12 | Union, 13 | ) 14 | 15 | from multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy, istr 16 | from yarl import URL, Query as _Query 17 | 18 | Query = _Query 19 | 20 | DEFAULT_JSON_ENCODER = json.dumps 21 | DEFAULT_JSON_DECODER = json.loads 22 | 23 | if TYPE_CHECKING: 24 | _CIMultiDict = CIMultiDict[str] 25 | _CIMultiDictProxy = CIMultiDictProxy[str] 26 | _MultiDict = MultiDict[str] 27 | _MultiDictProxy = MultiDictProxy[str] 28 | from http.cookies import BaseCookie, Morsel 29 | 30 | from .web import Request, StreamResponse 31 | else: 32 | _CIMultiDict = CIMultiDict 33 | _CIMultiDictProxy = CIMultiDictProxy 34 | _MultiDict = MultiDict 35 | _MultiDictProxy = MultiDictProxy 36 | 37 | Byteish = Union[bytes, bytearray, memoryview] 38 | JSONEncoder = Callable[[Any], str] 39 | JSONDecoder = Callable[[str], Any] 40 | LooseHeaders = Union[ 41 | Mapping[str, str], 42 | Mapping[istr, str], 43 | _CIMultiDict, 44 | _CIMultiDictProxy, 45 | Iterable[Tuple[Union[str, istr], str]], 46 | ] 47 | RawHeaders = Tuple[Tuple[bytes, bytes], ...] 48 | StrOrURL = Union[str, URL] 49 | 50 | LooseCookiesMappings = Mapping[str, Union[str, "BaseCookie[str]", "Morsel[Any]"]] 51 | LooseCookiesIterables = Iterable[ 52 | Tuple[str, Union[str, "BaseCookie[str]", "Morsel[Any]"]] 53 | ] 54 | LooseCookies = Union[ 55 | LooseCookiesMappings, 56 | LooseCookiesIterables, 57 | "BaseCookie[str]", 58 | ] 59 | 60 | Handler = Callable[["Request"], Awaitable["StreamResponse"]] 61 | 62 | 63 | class Middleware(Protocol): 64 | def __call__( 65 | self, request: "Request", handler: Handler 66 | ) -> Awaitable["StreamResponse"]: ... 67 | 68 | 69 | PathLike = Union[str, "os.PathLike[str]"] 70 | -------------------------------------------------------------------------------- /docs/_static/css/logo-adjustments.css: -------------------------------------------------------------------------------- 1 | .sphinxsidebarwrapper>h1.logo { 2 | display: none; 3 | } 4 | 5 | .sphinxsidebarwrapper>p.logo>a>img.logo { 6 | width: 65%; 7 | } 8 | -------------------------------------------------------------------------------- /docs/_static/img/contributing-cov-header.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Codecov 10 | 11 | 12 | 13 | Report 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/aiohttp-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/aiohttp-plain.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/built_with.rst: -------------------------------------------------------------------------------- 1 | .. _aiohttp-built-with: 2 | 3 | Built with aiohttp 4 | ================== 5 | 6 | aiohttp is used to build useful libraries built on top of it, 7 | and there's a page dedicated to list them: :ref:`aiohttp-3rd-party`. 8 | 9 | There are also projects that leverage the power of aiohttp to 10 | provide end-user tools, like command lines or software with 11 | full user interfaces. 12 | 13 | This page aims to list those projects. If you are using aiohttp 14 | in your software and if it's playing a central role, you 15 | can add it here in this list. 16 | 17 | You can also add a **Built with aiohttp** link somewhere in your 18 | project, pointing to ``_. 19 | 20 | 21 | * `Pulp `_ Platform for managing repositories 22 | of software packages and making them available to consumers. 23 | * `repo-peek `_ CLI tool to open a remote repo locally quickly. 24 | * `Molotov `_ Load testing tool. 25 | * `Arsenic `_ Async WebDriver. 26 | * `Home Assistant `_ Home Automation Platform. 27 | * `Backend.AI `_ Code execution API service. 28 | * `doh-proxy `_ DNS Over HTTPS Proxy. 29 | * `Mariner `_ Command-line torrent searcher. 30 | * `DEEPaaS API `_ REST API for Machine learning, Deep learning and artificial intelligence applications. 31 | * `BentoML `_ Machine Learning model serving framework 32 | * `salted `_ fast link check library (for HTML, Markdown, LaTeX, ...) with CLI 33 | -------------------------------------------------------------------------------- /docs/changes.rst: -------------------------------------------------------------------------------- 1 | .. _aiohttp_changes: 2 | 3 | ========= 4 | Changelog 5 | ========= 6 | 7 | .. only:: not is_release 8 | 9 | To be included in v\ |release| (if present) 10 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 11 | 12 | .. towncrier-draft-entries:: |release| [UNRELEASED DRAFT] 13 | 14 | Released versions 15 | ^^^^^^^^^^^^^^^^^ 16 | 17 | .. include:: ../CHANGES.rst 18 | :start-after: .. towncrier release notes start 19 | -------------------------------------------------------------------------------- /docs/client.rst: -------------------------------------------------------------------------------- 1 | .. _aiohttp-client: 2 | 3 | Client 4 | ====== 5 | 6 | .. currentmodule:: aiohttp 7 | 8 | The page contains all information about aiohttp Client API: 9 | 10 | 11 | .. toctree:: 12 | :name: client 13 | :maxdepth: 3 14 | 15 | Quickstart 16 | Advanced Usage 17 | Client Middleware Cookbook 18 | Reference 19 | Tracing Reference 20 | The aiohttp Request Lifecycle 21 | -------------------------------------------------------------------------------- /docs/contributing-admins.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | Instructions for aiohttp admins 4 | =============================== 5 | 6 | This page is intended to document certain processes for admins of the aiohttp repository. 7 | For regular contributors, return to :doc:`contributing`. 8 | 9 | .. contents:: 10 | :local: 11 | 12 | Creating a new release 13 | ---------------------- 14 | 15 | .. note:: The example commands assume that ``origin`` refers to the ``aio-libs`` repository. 16 | 17 | To create a new release: 18 | 19 | #. Start on the branch for the release you are planning (e.g. ``3.8`` for v3.8.6): ``git checkout 3.8 && git pull`` 20 | #. Update the version number in ``__init__.py``. 21 | #. Run ``towncrier``. 22 | #. Check and cleanup the changes in ``CHANGES.rst``. 23 | #. Checkout a new branch: e.g. ``git checkout -b release/v3.8.6`` 24 | #. Commit and create a PR. Verify the changelog and release notes look good on Read the Docs. Once PR is merged, continue. 25 | #. Go back to the release branch: e.g. ``git checkout 3.8 && git pull`` 26 | #. Add a tag: e.g. ``git tag -a v3.8.6 -m 'Release 3.8.6' -s`` 27 | #. Push the tag: e.g. ``git push origin v3.8.6`` 28 | #. Monitor CI to ensure release process completes without errors. 29 | 30 | Once released, we need to complete some cleanup steps (no further steps are needed for 31 | non-stable releases though). If doing a patch release, we need to do the below steps twice, 32 | first merge into the newer release branch (e.g. 3.8 into 3.9) and then to master 33 | (e.g. 3.9 into master). If a new minor release, then just merge to master. 34 | 35 | #. Switch to target branch: e.g. ``git checkout 3.9 && git pull`` 36 | #. Start a merge: e.g. ``git merge 3.8 --no-commit --no-ff --gpg-sign`` 37 | #. Carefully review the changes and revert anything that should not be included (most 38 | things outside the changelog). 39 | #. To ensure change fragments are cleaned up properly, run: ``python tools/cleanup_changes.py`` 40 | #. Commit the merge (must be a normal merge commit, not squashed). 41 | #. Push the branch directly to Github (because a PR would get squashed). When pushing, 42 | you may get a rejected message. Follow these steps to resolve: 43 | 44 | #. Checkout to a new branch and push: e.g. ``git checkout -b do-not-merge && git push`` 45 | #. Open a *draft* PR with a title of 'DO NOT MERGE'. 46 | #. Once the CI has completed on that branch, you should be able to switch back and push 47 | the target branch (as tests have passed on the merge commit now). 48 | #. This should automatically consider the PR merged and delete the temporary branch. 49 | 50 | Back on the original release branch, bump the version number and append ``.dev0`` in ``__init__.py``. 51 | 52 | Post the release announcement to social media: 53 | - BlueSky: https://bsky.app/profile/aiohttp.org and re-post to https://bsky.app/profile/aio-libs.org 54 | - Mastodon: https://fosstodon.org/@aiohttp and re-post to https://fosstodon.org/@aio_libs 55 | 56 | If doing a minor release: 57 | 58 | #. Create a new release branch for future features to go to: e.g. ``git checkout -b 3.10 3.9 && git push`` 59 | #. Update both ``target-branch`` backports for Dependabot to reference the new branch name in ``.github/dependabot.yml``. 60 | #. Delete the older backport label (e.g. backport-3.8): https://github.com/aio-libs/aiohttp/labels 61 | #. Add a new backport label (e.g. backport-3.10). 62 | -------------------------------------------------------------------------------- /docs/essays.rst: -------------------------------------------------------------------------------- 1 | Essays 2 | ====== 3 | 4 | 5 | .. toctree:: 6 | 7 | new_router 8 | whats_new_1_1 9 | migration_to_2xx 10 | whats_new_3_0 11 | -------------------------------------------------------------------------------- /docs/external.rst: -------------------------------------------------------------------------------- 1 | Who uses aiohttp? 2 | ================= 3 | 4 | The list of *aiohttp* users: both libraries, big projects and web sites. 5 | 6 | Please don't hesitate to add your awesome project to the list by 7 | making a Pull Request on GitHub_. 8 | 9 | If you like the project -- please go to GitHub_ and press *Star* button! 10 | 11 | 12 | .. toctree:: 13 | 14 | third_party 15 | built_with 16 | powered_by 17 | 18 | .. _GitHub: https://github.com/aio-libs/aiohttp 19 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aio-libs/aiohttp/8edec635b65035ad819cd98abc7bfeb192f788a3/docs/favicon.ico -------------------------------------------------------------------------------- /docs/misc.rst: -------------------------------------------------------------------------------- 1 | .. _aiohttp-misc: 2 | 3 | Miscellaneous 4 | ============= 5 | 6 | Helpful pages. 7 | 8 | .. toctree:: 9 | :name: misc 10 | 11 | essays 12 | glossary 13 | 14 | .. toctree:: 15 | :titlesonly: 16 | 17 | changes 18 | 19 | Indices and tables 20 | ------------------ 21 | 22 | * :ref:`genindex` 23 | * :ref:`modindex` 24 | * :ref:`search` 25 | -------------------------------------------------------------------------------- /docs/new_router.rst: -------------------------------------------------------------------------------- 1 | .. _aiohttp-router-refactoring-021: 2 | 3 | Router refactoring in 0.21 4 | ========================== 5 | 6 | Rationale 7 | --------- 8 | 9 | First generation (v1) of router has mapped ``(method, path)`` pair to 10 | :term:`web-handler`. Mapping is named **route**. Routes used to have 11 | unique names if any. 12 | 13 | The main mistake with the design is coupling the **route** to 14 | ``(method, path)`` pair while really URL construction operates with 15 | **resources** (**location** is a synonym). HTTP method is not part of URI 16 | but applied on sending HTTP request only. 17 | 18 | Having different **route names** for the same path is confusing. Moreover 19 | **named routes** constructed for the same path should have unique 20 | non overlapping names which is cumbersome is certain situations. 21 | 22 | From other side sometimes it's desirable to bind several HTTP methods 23 | to the same web handler. For *v1* router it can be solved by passing '*' 24 | as HTTP method. Class based views require '*' method also usually. 25 | 26 | 27 | Implementation 28 | -------------- 29 | 30 | The change introduces **resource** as first class citizen:: 31 | 32 | resource = router.add_resource('/path/{to}', name='name') 33 | 34 | *Resource* has a **path** (dynamic or constant) and optional **name**. 35 | 36 | The name is **unique** in router context. 37 | 38 | *Resource* has **routes**. 39 | 40 | *Route* corresponds to *HTTP method* and :term:`web-handler` for the method:: 41 | 42 | route = resource.add_route('GET', handler) 43 | 44 | User still may use wildcard for accepting all HTTP methods (maybe we 45 | will add something like ``resource.add_wildcard(handler)`` later). 46 | 47 | Since **names** belongs to **resources** now ``app.router['name']`` 48 | returns a **resource** instance instead of :class:`aiohttp.web.AbstractRoute`. 49 | 50 | **resource** has ``.url()`` method, so 51 | ``app.router['name'].url(parts={'a': 'b'}, query={'arg': 'param'})`` 52 | still works as usual. 53 | 54 | 55 | The change allows to rewrite static file handling and implement nested 56 | applications as well. 57 | 58 | Decoupling of *HTTP location* and *HTTP method* makes life easier. 59 | 60 | Backward compatibility 61 | ---------------------- 62 | 63 | The refactoring is 99% compatible with previous implementation. 64 | 65 | 99% means all example and the most of current code works without 66 | modifications but we have subtle API backward incompatibles. 67 | 68 | ``app.router['name']`` returns a :class:`aiohttp.web.AbstractResource` 69 | instance instead of :class:`aiohttp.web.AbstractRoute` but resource has the 70 | same ``resource.url(...)`` most useful method, so end user should feel no 71 | difference. 72 | 73 | ``route.match(...)`` is **not** supported anymore, use 74 | :meth:`aiohttp.web.AbstractResource.resolve` instead. 75 | 76 | ``app.router.add_route(method, path, handler, name='name')`` now is just 77 | shortcut for:: 78 | 79 | resource = app.router.add_resource(path, name=name) 80 | route = resource.add_route(method, handler) 81 | return route 82 | 83 | ``app.router.register_route(...)`` is still supported, it creates 84 | ``aiohttp.web.ResourceAdapter`` for every call (but it's deprecated now). 85 | -------------------------------------------------------------------------------- /docs/old-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aio-libs/aiohttp/8edec635b65035ad819cd98abc7bfeb192f788a3/docs/old-logo.png -------------------------------------------------------------------------------- /docs/powered_by.rst: -------------------------------------------------------------------------------- 1 | .. _aiohttp-powered-by: 2 | 3 | Powered by aiohttp 4 | ================== 5 | 6 | Web sites powered by aiohttp. 7 | 8 | Feel free to fork documentation on github, add a link to your site and 9 | make a Pull Request! 10 | 11 | * `Farmer Business Network `_ 12 | * `Home Assistant `_ 13 | * `KeepSafe `_ 14 | * `Skyscanner Hotels `_ 15 | * `Ocean S.A. `_ 16 | * `GNS3 `_ 17 | * `TutorCruncher socket 18 | `_ 19 | * `Eyepea - Custom telephony solutions `_ 20 | * `ALLOcloud - Telephony in the cloud `_ 21 | * `helpmanual - comprehensive help and man page database 22 | `_ 23 | * `bedevere `_ - CPython's GitHub 24 | bot, helps maintain and identify issues with a CPython pull request. 25 | * `miss-islington `_ - 26 | CPython's GitHub bot, backports and merge CPython's pull requests 27 | * `noa technologies - Bike-sharing management platform 28 | `_ - SSE endpoint, pushes real time updates of 29 | bikes location. 30 | * `Wargaming: World of Tanks `_ 31 | * `Yandex `_ 32 | * `Rambler `_ 33 | * `Escargot `_ - Chat server 34 | * `Prom.ua `_ - Online trading platform 35 | * `globo.com `_ - (some parts) Brazilian largest media portal 36 | * `Glose `_ - Social reader for E-Books 37 | * `Emoji Generator `_ - Text icon generator 38 | * `SerpsBot Google Search API `_ - SerpsBot Google Search API 39 | * `PyChess `_ - Chess variant server 40 | -------------------------------------------------------------------------------- /docs/structures.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: aiohttp 2 | 3 | 4 | .. _aiohttp-structures: 5 | 6 | 7 | Common data structures 8 | ====================== 9 | 10 | Common data structures used by *aiohttp* internally. 11 | 12 | 13 | FrozenList 14 | ---------- 15 | 16 | A list-like structure which implements 17 | :class:`collections.abc.MutableSequence`. 18 | 19 | The list is *mutable* unless :meth:`FrozenList.freeze` is called, 20 | after that the list modification raises :exc:`RuntimeError`. 21 | 22 | 23 | .. class:: FrozenList(items) 24 | 25 | Construct a new *non-frozen* list from *items* iterable. 26 | 27 | The list implements all :class:`collections.abc.MutableSequence` 28 | methods plus two additional APIs. 29 | 30 | .. attribute:: frozen 31 | 32 | A read-only property, ``True`` is the list is *frozen* 33 | (modifications are forbidden). 34 | 35 | .. method:: freeze() 36 | 37 | Freeze the list. There is no way to *thaw* it back. 38 | 39 | 40 | ChainMapProxy 41 | ------------- 42 | 43 | An *immutable* version of :class:`collections.ChainMap`. Internally 44 | the proxy is a list of mappings (dictionaries), if the requested key 45 | is not present in the first mapping the second is looked up and so on. 46 | 47 | The class supports :class:`collections.abc.Mapping` interface. 48 | 49 | .. class:: ChainMapProxy(maps) 50 | 51 | Create a new chained mapping proxy from a list of mappings (*maps*). 52 | 53 | .. versionadded:: 3.2 54 | -------------------------------------------------------------------------------- /docs/utilities.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: aiohttp 2 | 3 | .. _aiohttp-utilities: 4 | 5 | Utilities 6 | ========= 7 | 8 | Miscellaneous API Shared between Client And Server. 9 | 10 | .. toctree:: 11 | :name: utilities 12 | :maxdepth: 2 13 | 14 | abc 15 | multipart 16 | multipart_reference 17 | streams 18 | structures 19 | websocket_utilities 20 | -------------------------------------------------------------------------------- /docs/web.rst: -------------------------------------------------------------------------------- 1 | .. _aiohttp-web: 2 | 3 | Server 4 | ====== 5 | 6 | .. module:: aiohttp.web 7 | 8 | The page contains all information about aiohttp Server API: 9 | 10 | 11 | .. toctree:: 12 | :name: server 13 | :maxdepth: 3 14 | 15 | Tutorial 16 | Quickstart 17 | Advanced Usage 18 | Low Level 19 | Reference 20 | Web Exceptions 21 | Logging 22 | Testing 23 | Deployment 24 | -------------------------------------------------------------------------------- /docs/web_lowlevel.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: aiohttp.web 2 | 3 | .. _aiohttp-web-lowlevel: 4 | 5 | Low Level Server 6 | ================ 7 | 8 | 9 | This topic describes :mod:`aiohttp.web` based *low level* API. 10 | 11 | Abstract 12 | -------- 13 | 14 | Sometimes users don't need high-level concepts introduced in 15 | :ref:`aiohttp-web`: applications, routers, middlewares and signals. 16 | 17 | All that may be needed is supporting an asynchronous callable which accepts a 18 | request and returns a response object. 19 | 20 | This is done by introducing :class:`aiohttp.web.Server` class which 21 | serves a *protocol factory* role for 22 | :meth:`asyncio.loop.create_server` and bridges data 23 | stream to *web handler* and sends result back. 24 | 25 | 26 | Low level *web handler* should accept the single :class:`BaseRequest` 27 | parameter and performs one of the following actions: 28 | 29 | 1. Return a :class:`Response` with the whole HTTP body stored in memory. 30 | 31 | 2. Create a :class:`StreamResponse`, send headers by 32 | :meth:`StreamResponse.prepare` call, send data chunks by 33 | :meth:`StreamResponse.write` and return finished response. 34 | 35 | 3. Raise :class:`HTTPException` derived exception (see 36 | :ref:`aiohttp-web-exceptions` section). 37 | 38 | All other exceptions not derived from :class:`HTTPException` 39 | leads to *500 Internal Server Error* response. 40 | 41 | 4. Initiate and process Web-Socket connection by 42 | :class:`WebSocketResponse` using (see :ref:`aiohttp-web-websockets`). 43 | 44 | 45 | Run a Basic Low-Level Server 46 | ---------------------------- 47 | 48 | The following code demonstrates very trivial usage example:: 49 | 50 | import asyncio 51 | from aiohttp import web 52 | 53 | 54 | async def handler(request): 55 | return web.Response(text="OK") 56 | 57 | 58 | async def main(): 59 | server = web.Server(handler) 60 | runner = web.ServerRunner(server) 61 | await runner.setup() 62 | site = web.TCPSite(runner, 'localhost', 8080) 63 | await site.start() 64 | 65 | print("======= Serving on http://127.0.0.1:8080/ ======") 66 | 67 | # pause here for very long time by serving HTTP requests and 68 | # waiting for keyboard interruption 69 | await asyncio.sleep(100*3600) 70 | 71 | 72 | asyncio.run(main()) 73 | 74 | 75 | In the snippet we have ``handler`` which returns a regular 76 | :class:`Response` with ``"OK"`` in BODY. 77 | 78 | This *handler* is processed by ``server`` (:class:`Server` which acts 79 | as *protocol factory*). Network communication is created by 80 | :ref:`runners API ` to serve 81 | ``http://127.0.0.1:8080/``. 82 | 83 | The handler should process every request for every *path*, e.g. 84 | ``GET``, ``POST``, Web-Socket. 85 | 86 | The example is very basic: it always return ``200 OK`` response, real 87 | life code is much more complex usually. 88 | -------------------------------------------------------------------------------- /docs/whats_new_3_0.rst: -------------------------------------------------------------------------------- 1 | .. _aiohttp_whats_new_3_0: 2 | 3 | ========================= 4 | What's new in aiohttp 3.0 5 | ========================= 6 | 7 | async/await everywhere 8 | ====================== 9 | 10 | The main change is dropping ``yield from`` support and using 11 | ``async``/``await`` everywhere. Farewell, Python 3.4. 12 | 13 | The minimal supported Python version is **3.5.3** now. 14 | 15 | Why not *3.5.0*? Because *3.5.3* has a crucial change: 16 | :func:`asyncio.get_event_loop()` returns the running loop instead of 17 | *default*, which may be different, e.g.:: 18 | 19 | loop = asyncio.new_event_loop() 20 | loop.run_until_complete(f()) 21 | 22 | Note, :func:`asyncio.set_event_loop` was not called and default loop 23 | is not equal to actually executed one. 24 | 25 | Application Runners 26 | =================== 27 | 28 | People constantly asked about ability to run aiohttp servers together 29 | with other asyncio code, but :func:`aiohttp.web.run_app` is blocking 30 | synchronous call. 31 | 32 | aiohttp had support for starting the application without ``run_app`` but the API 33 | was very low-level and cumbersome. 34 | 35 | Now application runners solve the task in a few lines of code, see 36 | :ref:`aiohttp-web-app-runners` for details. 37 | 38 | Client Tracing 39 | ============== 40 | 41 | Other long awaited feature is tracing client request life cycle to 42 | figure out when and why client request spends a time waiting for 43 | connection establishment, getting server response headers etc. 44 | 45 | Now it is possible by registering special signal handlers on every 46 | request processing stage. :ref:`aiohttp-client-tracing` provides more 47 | info about the feature. 48 | 49 | HTTPS support 50 | ============= 51 | 52 | Unfortunately asyncio has a bug with checking SSL certificates for 53 | non-ASCII site DNS names, e.g. `https://историк.рф `_ or 54 | `https://雜草工作室.香港 `_. 55 | 56 | The bug has been fixed in upcoming Python 3.7 only (the change 57 | requires breaking backward compatibility in :mod:`ssl` API). 58 | 59 | aiohttp installs a fix for older Python versions (3.5 and 3.6). 60 | 61 | 62 | Dropped obsolete API 63 | ==================== 64 | 65 | A switch to new major version is a great chance for dropping already 66 | deprecated features. 67 | 68 | The release dropped a lot, see :ref:`aiohttp_changes` for details. 69 | 70 | All removals was already marked as deprecated or related to very low 71 | level implementation details. 72 | 73 | If user code did not raise :exc:`DeprecationWarning` it is compatible 74 | with aiohttp 3.0 most likely. 75 | 76 | 77 | Summary 78 | ======= 79 | 80 | Enjoy aiohttp 3.0 release! 81 | 82 | The full change log is here: :ref:`aiohttp_changes`. 83 | -------------------------------------------------------------------------------- /examples/background_tasks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Example of aiohttp.web.Application.on_startup signal handler""" 3 | import asyncio 4 | from contextlib import suppress 5 | from typing import AsyncIterator, List 6 | 7 | import valkey.asyncio as valkey 8 | 9 | from aiohttp import web 10 | 11 | valkey_listener = web.AppKey("valkey_listener", asyncio.Task[None]) 12 | websockets = web.AppKey("websockets", List[web.WebSocketResponse]) 13 | 14 | 15 | async def websocket_handler(request: web.Request) -> web.StreamResponse: 16 | ws = web.WebSocketResponse() 17 | await ws.prepare(request) 18 | request.app[websockets].append(ws) 19 | try: 20 | async for msg in ws: 21 | print(msg) 22 | await asyncio.sleep(1) 23 | finally: 24 | request.app[websockets].remove(ws) 25 | return ws 26 | 27 | 28 | async def on_shutdown(app: web.Application) -> None: 29 | for ws in app[websockets]: 30 | await ws.close(code=999, message=b"Server shutdown") 31 | 32 | 33 | async def listen_to_valkey(app: web.Application) -> None: 34 | r = valkey.Valkey(host="localhost", port=6379, decode_responses=True) 35 | channel = "news" 36 | async with r.pubsub() as sub: 37 | await sub.subscribe(channel) 38 | async for msg in sub.listen(): 39 | if msg["type"] != "message": 40 | continue 41 | # Forward message to all connected websockets: 42 | for ws in app[websockets]: 43 | await ws.send_str(f"{channel}: {msg}") 44 | print(f"message in {channel}: {msg}") 45 | 46 | 47 | async def background_tasks(app: web.Application) -> AsyncIterator[None]: 48 | app[valkey_listener] = asyncio.create_task(listen_to_valkey(app)) 49 | 50 | yield 51 | 52 | print("cleanup background tasks...") 53 | app[valkey_listener].cancel() 54 | with suppress(asyncio.CancelledError): 55 | await app[valkey_listener] 56 | 57 | 58 | def init() -> web.Application: 59 | app = web.Application() 60 | l: List[web.WebSocketResponse] = [] 61 | app[websockets] = l 62 | app.router.add_get("/news", websocket_handler) 63 | app.cleanup_ctx.append(background_tasks) 64 | app.on_shutdown.append(on_shutdown) 65 | return app 66 | 67 | 68 | web.run_app(init()) 69 | -------------------------------------------------------------------------------- /examples/cli_app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Example of serving an Application using the `aiohttp.web` CLI. 4 | 5 | Serve this app using:: 6 | 7 | $ python -m aiohttp.web -H localhost -P 8080 --repeat 10 cli_app:init \ 8 | > "Hello World" 9 | 10 | Here ``--repeat`` & ``"Hello World"`` are application specific command-line 11 | arguments. `aiohttp.web` only parses & consumes the command-line arguments it 12 | needs (i.e. ``-H``, ``-P`` & ``entry-func``) and passes on any additional 13 | arguments to the `cli_app:init` function for processing. 14 | """ 15 | 16 | from argparse import ArgumentParser, Namespace 17 | from typing import Optional, Sequence 18 | 19 | from aiohttp import web 20 | 21 | args_key = web.AppKey("args_key", Namespace) 22 | 23 | 24 | async def display_message(req: web.Request) -> web.StreamResponse: 25 | args = req.app[args_key] 26 | text = "\n".join([args.message] * args.repeat) 27 | return web.Response(text=text) 28 | 29 | 30 | def init(argv: Optional[Sequence[str]]) -> web.Application: 31 | arg_parser = ArgumentParser( 32 | prog="aiohttp.web ...", description="Application CLI", add_help=False 33 | ) 34 | 35 | # Positional argument 36 | arg_parser.add_argument("message", help="message to print") 37 | 38 | # Optional argument 39 | arg_parser.add_argument( 40 | "--repeat", help="number of times to repeat message", type=int, default="1" 41 | ) 42 | 43 | # Avoid conflict with -h from `aiohttp.web` CLI parser 44 | arg_parser.add_argument( 45 | "--app-help", help="show this message and exit", action="help" 46 | ) 47 | 48 | args = arg_parser.parse_args(argv) 49 | 50 | app = web.Application() 51 | app[args_key] = args 52 | app.router.add_get("/", display_message) 53 | 54 | return app 55 | -------------------------------------------------------------------------------- /examples/client_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import asyncio 3 | 4 | import aiohttp 5 | 6 | 7 | async def fetch(session: aiohttp.ClientSession) -> None: 8 | print("Query http://httpbin.org/basic-auth/andrew/password") 9 | async with session.get("http://httpbin.org/basic-auth/andrew/password") as resp: 10 | print(resp.status) 11 | body = await resp.text() 12 | print(body) 13 | 14 | 15 | async def go() -> None: 16 | async with aiohttp.ClientSession( 17 | auth=aiohttp.BasicAuth("andrew", "password") 18 | ) as session: 19 | await fetch(session) 20 | 21 | 22 | loop = asyncio.get_event_loop() 23 | loop.run_until_complete(go()) 24 | -------------------------------------------------------------------------------- /examples/client_json.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import asyncio 3 | 4 | import aiohttp 5 | 6 | 7 | async def fetch(session: aiohttp.ClientSession) -> None: 8 | print("Query http://httpbin.org/get") 9 | async with session.get("http://httpbin.org/get") as resp: 10 | print(resp.status) 11 | data = await resp.json() 12 | print(data) 13 | 14 | 15 | async def go() -> None: 16 | async with aiohttp.ClientSession() as session: 17 | await fetch(session) 18 | 19 | 20 | loop = asyncio.get_event_loop() 21 | loop.run_until_complete(go()) 22 | loop.close() 23 | -------------------------------------------------------------------------------- /examples/client_ws.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """websocket cmd client for web_ws.py example.""" 3 | 4 | import argparse 5 | import asyncio 6 | import sys 7 | from contextlib import suppress 8 | 9 | import aiohttp 10 | 11 | 12 | async def start_client(url: str) -> None: 13 | name = input("Please enter your name: ") 14 | 15 | async def dispatch(ws: aiohttp.ClientWebSocketResponse) -> None: 16 | while True: 17 | msg = await ws.receive() 18 | 19 | if msg.type is aiohttp.WSMsgType.TEXT: 20 | print("Text: ", msg.data.strip()) 21 | elif msg.type is aiohttp.WSMsgType.BINARY: 22 | print("Binary: ", msg.data) 23 | elif msg.type is aiohttp.WSMsgType.PING: 24 | await ws.pong() 25 | elif msg.type is aiohttp.WSMsgType.PONG: 26 | print("Pong received") 27 | else: 28 | if msg.type is aiohttp.WSMsgType.CLOSE: 29 | await ws.close() 30 | elif msg.type is aiohttp.WSMsgType.ERROR: 31 | print("Error during receive %s" % ws.exception()) 32 | elif msg.type is aiohttp.WSMsgType.CLOSED: 33 | pass 34 | 35 | break 36 | 37 | async with aiohttp.ClientSession() as session: 38 | async with session.ws_connect(url, autoclose=False, autoping=False) as ws: 39 | # send request 40 | dispatch_task = asyncio.create_task(dispatch(ws)) 41 | 42 | # Exit with Ctrl+D 43 | while line := await asyncio.to_thread(sys.stdin.readline): 44 | await ws.send_str(name + ": " + line) 45 | 46 | dispatch_task.cancel() 47 | with suppress(asyncio.CancelledError): 48 | await dispatch_task 49 | 50 | 51 | ARGS = argparse.ArgumentParser( 52 | description="websocket console client for wssrv.py example." 53 | ) 54 | ARGS.add_argument( 55 | "--host", action="store", dest="host", default="127.0.0.1", help="Host name" 56 | ) 57 | ARGS.add_argument( 58 | "--port", action="store", dest="port", default=8080, type=int, help="Port number" 59 | ) 60 | 61 | if __name__ == "__main__": 62 | args = ARGS.parse_args() 63 | if ":" in args.host: 64 | args.host, port = args.host.split(":", 1) 65 | args.port = int(port) 66 | 67 | url = f"http://{args.host}:{args.port}" 68 | 69 | asyncio.run(start_client(url)) 70 | -------------------------------------------------------------------------------- /examples/curl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import asyncio 5 | import sys 6 | 7 | import aiohttp 8 | 9 | 10 | async def curl(url: str) -> None: 11 | async with aiohttp.ClientSession() as session: 12 | async with session.request("GET", url) as response: 13 | print(repr(response)) 14 | chunk = await response.content.read() 15 | print("Downloaded: %s" % len(chunk)) 16 | 17 | 18 | if __name__ == "__main__": 19 | ARGS = argparse.ArgumentParser(description="GET url example") 20 | ARGS.add_argument("url", nargs=1, metavar="URL", help="URL to download") 21 | ARGS.add_argument( 22 | "--iocp", 23 | default=False, 24 | action="store_true", 25 | help="Use ProactorEventLoop on Windows", 26 | ) 27 | options = ARGS.parse_args() 28 | 29 | if options.iocp and sys.platform == "win32": 30 | from asyncio import events, windows_events 31 | 32 | # https://github.com/python/mypy/issues/12286 33 | el = windows_events.ProactorEventLoop() # type: ignore[attr-defined] 34 | events.set_event_loop(el) 35 | 36 | loop = asyncio.get_event_loop() 37 | loop.run_until_complete(curl(options.url[0])) 38 | -------------------------------------------------------------------------------- /examples/digest_auth_qop_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Example of using digest authentication middleware with aiohttp client. 4 | 5 | This example shows how to use the DigestAuthMiddleware from 6 | aiohttp.client_middleware_digest_auth to authenticate with a server 7 | that requires digest authentication with different qop options. 8 | 9 | In this case, it connects to httpbin.org's digest auth endpoint. 10 | """ 11 | 12 | import asyncio 13 | from itertools import product 14 | 15 | from yarl import URL 16 | 17 | from aiohttp import ClientSession 18 | from aiohttp.client_middleware_digest_auth import DigestAuthMiddleware 19 | 20 | # Define QOP options available 21 | QOP_OPTIONS = ["auth", "auth-int"] 22 | 23 | # Algorithms supported by httpbin.org 24 | ALGORITHMS = ["MD5", "SHA-256", "SHA-512"] 25 | 26 | # Username and password for testing 27 | USERNAME = "my" 28 | PASSWORD = "dog" 29 | 30 | # All combinations of QOP options and algorithms 31 | TEST_COMBINATIONS = list(product(QOP_OPTIONS, ALGORITHMS)) 32 | 33 | 34 | async def main() -> None: 35 | # Create a DigestAuthMiddleware instance with appropriate credentials 36 | digest_auth = DigestAuthMiddleware(login=USERNAME, password=PASSWORD) 37 | 38 | # Create a client session with the digest auth middleware 39 | async with ClientSession(middlewares=(digest_auth,)) as session: 40 | # Test each combination of QOP and algorithm 41 | for qop, algorithm in TEST_COMBINATIONS: 42 | print(f"\n\n=== Testing with qop={qop}, algorithm={algorithm} ===\n") 43 | 44 | url = URL( 45 | f"https://httpbin.org/digest-auth/{qop}/{USERNAME}/{PASSWORD}/{algorithm}" 46 | ) 47 | 48 | async with session.get(url) as resp: 49 | print(f"Status: {resp.status}") 50 | print(f"Headers: {resp.headers}") 51 | 52 | # Parse the JSON response 53 | json_response = await resp.json() 54 | print(f"Response: {json_response}") 55 | 56 | # Verify authentication was successful 57 | if resp.status == 200: 58 | print("\nAuthentication successful!") 59 | print(f"Authenticated user: {json_response.get('user')}") 60 | print( 61 | f"Authentication method: {json_response.get('authenticated')}" 62 | ) 63 | else: 64 | print("\nAuthentication failed.") 65 | 66 | 67 | if __name__ == "__main__": 68 | asyncio.run(main()) 69 | -------------------------------------------------------------------------------- /examples/lowlevel_srv.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from aiohttp import web, web_request 4 | 5 | 6 | async def handler(request: web_request.BaseRequest) -> web.StreamResponse: 7 | return web.Response(text="OK") 8 | 9 | 10 | async def main(loop: asyncio.AbstractEventLoop) -> None: 11 | server = web.Server(handler) 12 | await loop.create_server(server, "127.0.0.1", 8080) 13 | print("======= Serving on http://127.0.0.1:8080/ ======") 14 | 15 | # pause here for very long time by serving HTTP requests and 16 | # waiting for keyboard interruption 17 | await asyncio.sleep(100 * 3600) 18 | 19 | 20 | loop = asyncio.get_event_loop() 21 | 22 | try: 23 | loop.run_until_complete(main(loop)) 24 | except KeyboardInterrupt: 25 | pass 26 | loop.close() 27 | -------------------------------------------------------------------------------- /examples/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDADCCAegCCQCgevpPMuTTLzANBgkqhkiG9w0BAQsFADBCMQswCQYDVQQGEwJV 3 | QTEQMA4GA1UECAwHVWtyYWluZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ 4 | dHkgTHRkMB4XDTE2MDgwNzIzMTMwOFoXDTI2MDgwNTIzMTMwOFowQjELMAkGA1UE 5 | BhMCVUExEDAOBgNVBAgMB1VrcmFpbmUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp 6 | dHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOUgkn3j 7 | X/sdg6GGueGDHCM+snIUVY3fM6D4jXjyBhnT3TqKG1lJwCGYR11AD+2SJYppU+w4 8 | QaF6YZwMeZBKy+mVQ9+CrVYyKQE7j9H8XgNEHV9BQzoragT8lia8eC5aOQzUeX8A 9 | xCSSbsnyT/X+S1IKdd0txLOeZOD6pWwJoc3dpDELglk2b1tzhyN2GjQv3aRHj55P 10 | x7127MeZyRXwODFpXrpbnwih4OqkA4EYtmqFbZttGEzMhd4Y5mkbyuRbGM+IE99o 11 | QJMvnIkjAfUo0aKnDrcAIkWCkwLIci9TIG6u3R1P2Tn+HYVntzQZ4BnxanbFNQ5S 12 | 9ARd3529EmO3BzUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAXyiw1+YUnTEDI3C/ 13 | vq1Vn9pnwZALVQPiPlTqEGkl/nbq0suMmeZZG7pwrOJp3wr+sGwRAv9sPTro6srf 14 | Vj12wTo4LrTRKEDuS+AUJl0Mut7cPGIUKo+MGeZmmnDjMqcjljN3AO47ef4eWYo5 15 | XGe4r4NDABEk5auOD/vQW5IiIMdmWsaMJ+0mZNpAV2NhAD/6ia28VvSL/yuaNqDW 16 | TYTUYHWLH08H6M6qrQ7FdoIDyYR5siqBukQzeqlnuq45bQ3ViYttNIkzZN4jbWJV 17 | /MFYLuJQ/fNoalDIC+ec0EIa9NbrfpoocJ8h6HlmWOqkES4QpBSOrkVid64Cdy3P 18 | JgiEWg== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /examples/server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIChzCCAW8CAQAwQjELMAkGA1UEBhMCVUExEDAOBgNVBAgMB1VrcmFpbmUxITAf 3 | BgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEB 4 | BQADggEPADCCAQoCggEBAOUgkn3jX/sdg6GGueGDHCM+snIUVY3fM6D4jXjyBhnT 5 | 3TqKG1lJwCGYR11AD+2SJYppU+w4QaF6YZwMeZBKy+mVQ9+CrVYyKQE7j9H8XgNE 6 | HV9BQzoragT8lia8eC5aOQzUeX8AxCSSbsnyT/X+S1IKdd0txLOeZOD6pWwJoc3d 7 | pDELglk2b1tzhyN2GjQv3aRHj55Px7127MeZyRXwODFpXrpbnwih4OqkA4EYtmqF 8 | bZttGEzMhd4Y5mkbyuRbGM+IE99oQJMvnIkjAfUo0aKnDrcAIkWCkwLIci9TIG6u 9 | 3R1P2Tn+HYVntzQZ4BnxanbFNQ5S9ARd3529EmO3BzUCAwEAAaAAMA0GCSqGSIb3 10 | DQEBCwUAA4IBAQDO/PSd29KgisTdGXhntg7yBEhBAjsDW7uQCrdrPSZtFyN6wUHy 11 | /1yrrWe56ZuW8jpuP5tG0eTZ+0bT2RXIRot8a2Cc3eBhpoe8M3d84yXjKAoHutGE 12 | 5IK+TViQdvT3pT3a7pTmjlf8Ojq9tx+U2ckiz8Ccnjd9yM47M9NgMhrS1aBpVZSt 13 | gOD+zzrqMML4xks9id94H7bi9Tgs3AbEJIyDpBpoK6i4OvK7KTidCngCg80qmdTy 14 | bcScLapoy1Ped2BKKuxWdOOlP+mDJatc/pcfBLE13AncQjJgMerS9M5RWCBjmRow 15 | A+aB6fBEU8bOTrqCryfBeTiV6xzyDDcIXtc6 16 | -----END CERTIFICATE REQUEST----- 17 | -------------------------------------------------------------------------------- /examples/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA5SCSfeNf+x2DoYa54YMcIz6ychRVjd8zoPiNePIGGdPdOoob 3 | WUnAIZhHXUAP7ZIlimlT7DhBoXphnAx5kErL6ZVD34KtVjIpATuP0fxeA0QdX0FD 4 | OitqBPyWJrx4Llo5DNR5fwDEJJJuyfJP9f5LUgp13S3Es55k4PqlbAmhzd2kMQuC 5 | WTZvW3OHI3YaNC/dpEePnk/HvXbsx5nJFfA4MWleulufCKHg6qQDgRi2aoVtm20Y 6 | TMyF3hjmaRvK5FsYz4gT32hAky+ciSMB9SjRoqcOtwAiRYKTAshyL1Mgbq7dHU/Z 7 | Of4dhWe3NBngGfFqdsU1DlL0BF3fnb0SY7cHNQIDAQABAoIBAG9BJ6B03VADfrzZ 8 | vDwh+3Gpqd/2u6wNqvYIejk123yDATLBiJIMW3x0goJm7tT+V7gjeJqEnmmYEPlC 9 | nWxQxT6AOdq3iw8FgB+XGjhuAAA5/MEZ4VjHZ81QEGBytzBaosT2DqB6cMMJTz5D 10 | qEvb1Brb9WsWJCLLUFRloBkbfDOG9lMvt34ixYTTmqjsVj5WByD5BhzKH51OJ72L 11 | 00IYpvrsEOtSev1hNV4199CHPYE90T/YQVooRBiHtTcfN+/KNVJu6Rf/zcaJ3WMS 12 | 1l3MBI8HwMimjKKkbddpoMHyFMtSNmS9Yq+4a9w7XZo1F5rt88hYSCtAF8HRAarX 13 | 0VBCJmkCgYEA9HenBBnmfDoN857femzoTHdWQQrZQ4YPAKHvKPlcgudizE5tQbs0 14 | iTpwm+IsecgJS2Rio7zY+P7A5nKFz3N5c0IX3smYo0J2PoakkLAm25KMxFZYBuz4 15 | MFWVdfByAU7d28BdNfyOVbA2kU2eal9lJ0yPLpMLbH8+bbvw5uBS808CgYEA7++p 16 | ftwib3DvKWMpl6G5eA1C2xprdbE0jm2fSr3LYp/vZ4QN2V6kK2YIlyUqQvhYCnxX 17 | oIP3v2MWDRHKKwJtBWR4+t23PaDaSXS2Ifm0qhRxwSm/oqpAJQXbR7VzxXp4/4FP 18 | 1SgkLe51bubc4h+cDngqBLcplCanvj52CqhqzDsCgYAEIhG8zANNjl22BLWaiETV 19 | Jh9bMifCMH4IcLRuaOjbfbX55kmKlvOobkiBGi3OUUd28teIFSVF8GiqfL0uaLFg 20 | 9XkZ1yaxe+or3HLjz1aY171xhFQwqcj4aDoCqHIE+6Rclr/8raxqXnRNuJY5DivT 21 | okO5cdr7lpsjl83W2WwNmQKBgCPXi1xWChbXqgJmu8nY8NnMMVaFpdPY+t7j5U3G 22 | +GDtP1gZU/BKwP9yqInblWqXqp82X+isjg/a/2pIZAj0vdB2Z9Qh1sOwCau7cZG1 23 | uZVGpI+UavojsJ1XOKCHrJmtZ/HTIVfYPT9XRdehSRHGYwuOS8iUi/ODqr8ymXOS 24 | IRINAoGBAMEmhTihgFz6Y8ezRK3QTubguehHZG1zIvtgVhOk+8hRUTSJPI9nBJPC 25 | 4gOZsPx4g2oLK6PiudPR79bhxRxPACCMnXkdwZ/8FaIdmvRHsWVs8T80wID0wthI 26 | r5hW4uqi9CcKZrGWH7mx9cVJktspeGUczvKyzNMfCaojwzA/49Z1 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /examples/server_simple.py: -------------------------------------------------------------------------------- 1 | # server_simple.py 2 | from aiohttp import web 3 | 4 | 5 | async def handle(request: web.Request) -> web.StreamResponse: 6 | name = request.match_info.get("name", "Anonymous") 7 | text = "Hello, " + name 8 | return web.Response(text=text) 9 | 10 | 11 | async def wshandle(request: web.Request) -> web.StreamResponse: 12 | ws = web.WebSocketResponse() 13 | await ws.prepare(request) 14 | 15 | async for msg in ws: 16 | if msg.type is web.WSMsgType.TEXT: 17 | await ws.send_str(f"Hello, {msg.data}") 18 | elif msg.type is web.WSMsgType.BINARY: 19 | await ws.send_bytes(msg.data) 20 | elif msg.type is web.WSMsgType.CLOSE: 21 | break 22 | 23 | return ws 24 | 25 | 26 | app = web.Application() 27 | app.add_routes( 28 | [web.get("/", handle), web.get("/echo", wshandle), web.get("/{name}", handle)] 29 | ) 30 | 31 | web.run_app(app) 32 | -------------------------------------------------------------------------------- /examples/static_files.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import pathlib 3 | 4 | from aiohttp import web 5 | 6 | app = web.Application() 7 | app.router.add_static("/", pathlib.Path(__file__).parent, show_index=True) 8 | 9 | web.run_app(app) 10 | -------------------------------------------------------------------------------- /examples/web_classview.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Example for aiohttp.web class based views.""" 3 | 4 | import functools 5 | import json 6 | 7 | from aiohttp import web 8 | 9 | 10 | class MyView(web.View): 11 | async def get(self) -> web.StreamResponse: 12 | return web.json_response( 13 | { 14 | "method": self.request.method, 15 | "args": dict(self.request.rel_url.query), 16 | "headers": dict(self.request.headers), 17 | }, 18 | dumps=functools.partial(json.dumps, indent=4), 19 | ) 20 | 21 | async def post(self) -> web.StreamResponse: 22 | data = await self.request.post() 23 | return web.json_response( 24 | { 25 | "method": self.request.method, 26 | "data": dict(data), 27 | "headers": dict(self.request.headers), 28 | }, 29 | dumps=functools.partial(json.dumps, indent=4), 30 | ) 31 | 32 | 33 | async def index(request: web.Request) -> web.StreamResponse: 34 | txt = """ 35 | 36 | 37 | Class based view example 38 | 39 | 40 |

Class based view example

41 |
    42 |
  • / This page 43 |
  • /get Returns GET data. 44 |
  • /post Returns POST data. 45 |
46 | 47 | 48 | """ 49 | return web.Response(text=txt, content_type="text/html") 50 | 51 | 52 | def init() -> web.Application: 53 | app = web.Application() 54 | app.router.add_get("/", index) 55 | app.router.add_get("/get", MyView) 56 | app.router.add_post("/post", MyView) 57 | return app 58 | 59 | 60 | web.run_app(init()) 61 | -------------------------------------------------------------------------------- /examples/web_cookies.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Example for aiohttp.web basic server with cookies.""" 3 | 4 | from pprint import pformat 5 | from typing import NoReturn 6 | 7 | from aiohttp import web 8 | 9 | tmpl = """\ 10 | 11 | 12 | Login
13 | Logout
14 |
{}
15 | 16 | """ 17 | 18 | 19 | async def root(request: web.Request) -> web.StreamResponse: 20 | resp = web.Response(content_type="text/html") 21 | resp.text = tmpl.format(pformat(request.cookies)) 22 | return resp 23 | 24 | 25 | async def login(request: web.Request) -> NoReturn: 26 | exc = web.HTTPFound(location="/") 27 | exc.set_cookie("AUTH", "secret") 28 | raise exc 29 | 30 | 31 | async def logout(request: web.Request) -> NoReturn: 32 | exc = web.HTTPFound(location="/") 33 | exc.del_cookie("AUTH") 34 | raise exc 35 | 36 | 37 | def init() -> web.Application: 38 | app = web.Application() 39 | app.router.add_get("/", root) 40 | app.router.add_get("/login", login) 41 | app.router.add_get("/logout", logout) 42 | return app 43 | 44 | 45 | web.run_app(init()) 46 | -------------------------------------------------------------------------------- /examples/web_rewrite_headers_middleware.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Example for rewriting response headers by middleware.""" 3 | 4 | from aiohttp import web 5 | from aiohttp.typedefs import Handler 6 | 7 | 8 | async def handler(request: web.Request) -> web.StreamResponse: 9 | return web.Response(text="Everything is fine") 10 | 11 | 12 | async def middleware(request: web.Request, handler: Handler) -> web.StreamResponse: 13 | try: 14 | response = await handler(request) 15 | except web.HTTPException as exc: 16 | raise exc 17 | if not response.prepared: 18 | response.headers["SERVER"] = "Secured Server Software" 19 | return response 20 | 21 | 22 | def init() -> web.Application: 23 | app = web.Application(middlewares=[middleware]) 24 | app.router.add_get("/", handler) 25 | return app 26 | 27 | 28 | web.run_app(init()) 29 | -------------------------------------------------------------------------------- /examples/web_srv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Example for aiohttp.web basic server.""" 3 | 4 | import textwrap 5 | 6 | from aiohttp import web 7 | 8 | 9 | async def intro(request: web.Request) -> web.StreamResponse: 10 | txt = textwrap.dedent( 11 | """\ 12 | Type {url}/hello/John {url}/simple or {url}/change_body 13 | in browser url bar 14 | """ 15 | ).format(url="127.0.0.1:8080") 16 | binary = txt.encode("utf8") 17 | resp = web.StreamResponse() 18 | resp.content_length = len(binary) 19 | resp.content_type = "text/plain" 20 | await resp.prepare(request) 21 | await resp.write(binary) 22 | return resp 23 | 24 | 25 | async def simple(request: web.Request) -> web.StreamResponse: 26 | return web.Response(text="Simple answer") 27 | 28 | 29 | async def change_body(request: web.Request) -> web.StreamResponse: 30 | resp = web.Response() 31 | resp.body = b"Body changed" 32 | resp.content_type = "text/plain" 33 | return resp 34 | 35 | 36 | async def hello(request: web.Request) -> web.StreamResponse: 37 | resp = web.StreamResponse() 38 | name = request.match_info.get("name", "Anonymous") 39 | answer = ("Hello, " + name).encode("utf8") 40 | resp.content_length = len(answer) 41 | resp.content_type = "text/plain" 42 | await resp.prepare(request) 43 | await resp.write(answer) 44 | await resp.write_eof() 45 | return resp 46 | 47 | 48 | def init() -> web.Application: 49 | app = web.Application() 50 | app.router.add_get("/", intro) 51 | app.router.add_get("/simple", simple) 52 | app.router.add_get("/change_body", change_body) 53 | app.router.add_get("/hello/{name}", hello) 54 | app.router.add_get("/hello", hello) 55 | return app 56 | 57 | 58 | web.run_app(init()) 59 | -------------------------------------------------------------------------------- /examples/web_srv_route_deco.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Example for aiohttp.web basic server with decorator definition for routes.""" 3 | 4 | import textwrap 5 | 6 | from aiohttp import web 7 | 8 | routes = web.RouteTableDef() 9 | 10 | 11 | @routes.get("/") 12 | async def intro(request: web.Request) -> web.StreamResponse: 13 | txt = textwrap.dedent( 14 | """\ 15 | Type {url}/hello/John {url}/simple or {url}/change_body 16 | in browser url bar 17 | """ 18 | ).format(url="127.0.0.1:8080") 19 | binary = txt.encode("utf8") 20 | resp = web.StreamResponse() 21 | resp.content_length = len(binary) 22 | resp.content_type = "text/plain" 23 | await resp.prepare(request) 24 | await resp.write(binary) 25 | return resp 26 | 27 | 28 | @routes.get("/simple") 29 | async def simple(request: web.Request) -> web.StreamResponse: 30 | return web.Response(text="Simple answer") 31 | 32 | 33 | @routes.get("/change_body") 34 | async def change_body(request: web.Request) -> web.StreamResponse: 35 | resp = web.Response() 36 | resp.body = b"Body changed" 37 | resp.content_type = "text/plain" 38 | return resp 39 | 40 | 41 | @routes.get("/hello") 42 | async def hello(request: web.Request) -> web.StreamResponse: 43 | resp = web.StreamResponse() 44 | name = request.match_info.get("name", "Anonymous") 45 | answer = ("Hello, " + name).encode("utf8") 46 | resp.content_length = len(answer) 47 | resp.content_type = "text/plain" 48 | await resp.prepare(request) 49 | await resp.write(answer) 50 | await resp.write_eof() 51 | return resp 52 | 53 | 54 | def init() -> web.Application: 55 | app = web.Application() 56 | app.router.add_routes(routes) 57 | return app 58 | 59 | 60 | web.run_app(init()) 61 | -------------------------------------------------------------------------------- /examples/web_srv_route_table.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Example for aiohttp.web basic server with table definition for routes.""" 3 | 4 | import textwrap 5 | 6 | from aiohttp import web 7 | 8 | 9 | async def intro(request: web.Request) -> web.StreamResponse: 10 | txt = textwrap.dedent( 11 | """\ 12 | Type {url}/hello/John {url}/simple or {url}/change_body 13 | in browser url bar 14 | """ 15 | ).format(url="127.0.0.1:8080") 16 | binary = txt.encode("utf8") 17 | resp = web.StreamResponse() 18 | resp.content_length = len(binary) 19 | resp.content_type = "text/plain" 20 | await resp.prepare(request) 21 | await resp.write(binary) 22 | return resp 23 | 24 | 25 | async def simple(request: web.Request) -> web.StreamResponse: 26 | return web.Response(text="Simple answer") 27 | 28 | 29 | async def change_body(request: web.Request) -> web.StreamResponse: 30 | resp = web.Response() 31 | resp.body = b"Body changed" 32 | resp.content_type = "text/plain" 33 | return resp 34 | 35 | 36 | async def hello(request: web.Request) -> web.StreamResponse: 37 | resp = web.StreamResponse() 38 | name = request.match_info.get("name", "Anonymous") 39 | answer = ("Hello, " + name).encode("utf8") 40 | resp.content_length = len(answer) 41 | resp.content_type = "text/plain" 42 | await resp.prepare(request) 43 | await resp.write(answer) 44 | await resp.write_eof() 45 | return resp 46 | 47 | 48 | def init() -> web.Application: 49 | app = web.Application() 50 | app.router.add_routes( 51 | [ 52 | web.get("/", intro), 53 | web.get("/simple", simple), 54 | web.get("/change_body", change_body), 55 | web.get("/hello/{name}", hello), 56 | web.get("/hello", hello), 57 | ] 58 | ) 59 | return app 60 | 61 | 62 | web.run_app(init()) 63 | -------------------------------------------------------------------------------- /examples/web_ws.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Example for aiohttp.web websocket server.""" 3 | 4 | # The extra strict mypy settings are here to help test that `Application[AppKey()]` 5 | # syntax is working correctly. A regression will cause mypy to raise an error. 6 | # mypy: disallow-any-expr, disallow-any-unimported, disallow-subclassing-any 7 | 8 | import os 9 | from typing import List, Union 10 | 11 | from aiohttp import web 12 | 13 | WS_FILE = os.path.join(os.path.dirname(__file__), "websocket.html") 14 | sockets = web.AppKey("sockets", List[web.WebSocketResponse]) 15 | 16 | 17 | async def wshandler(request: web.Request) -> Union[web.WebSocketResponse, web.Response]: 18 | resp = web.WebSocketResponse() 19 | available = resp.can_prepare(request) 20 | if not available: 21 | with open(WS_FILE, "rb") as fp: 22 | return web.Response(body=fp.read(), content_type="text/html") 23 | 24 | await resp.prepare(request) 25 | 26 | await resp.send_str("Welcome!!!") 27 | 28 | try: 29 | print("Someone joined.") 30 | for ws in request.app[sockets]: 31 | await ws.send_str("Someone joined") 32 | request.app[sockets].append(resp) 33 | 34 | async for msg in resp: 35 | if msg.type is web.WSMsgType.TEXT: 36 | for ws in request.app[sockets]: 37 | if ws is not resp: 38 | await ws.send_str(msg.data) 39 | else: 40 | return resp 41 | return resp 42 | 43 | finally: 44 | request.app[sockets].remove(resp) 45 | print("Someone disconnected.") 46 | for ws in request.app[sockets]: 47 | await ws.send_str("Someone disconnected.") 48 | 49 | 50 | async def on_shutdown(app: web.Application) -> None: 51 | for ws in app[sockets]: 52 | await ws.close() 53 | 54 | 55 | def init() -> web.Application: 56 | app = web.Application() 57 | l: List[web.WebSocketResponse] = [] 58 | app[sockets] = l 59 | app.router.add_get("/", wshandler) 60 | app.on_shutdown.append(on_shutdown) 61 | return app 62 | 63 | 64 | web.run_app(init()) 65 | -------------------------------------------------------------------------------- /examples/websocket.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 86 | 87 | 88 |

Chat!

89 |
90 | 91 |  | Status: 92 | disconnected 93 |
94 |
96 |
97 |
98 | 99 | 100 |
101 | 102 | 103 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "pkgconfig", 4 | "setuptools >= 46.4.0", 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | 8 | [tool.towncrier] 9 | package = "aiohttp" 10 | filename = "CHANGES.rst" 11 | directory = "CHANGES/" 12 | title_format = "{version} ({project_date})" 13 | template = "CHANGES/.TEMPLATE.rst" 14 | issue_format = "{issue}" 15 | 16 | # NOTE: The types are declared because: 17 | # NOTE: - there is no mechanism to override just the value of 18 | # NOTE: `tool.towncrier.type.misc.showcontent`; 19 | # NOTE: - and, we want to declare extra non-default types for 20 | # NOTE: clarity and flexibility. 21 | 22 | [[tool.towncrier.section]] 23 | path = "" 24 | 25 | [[tool.towncrier.type]] 26 | # Something we deemed an improper undesired behavior that got corrected 27 | # in the release to match pre-agreed expectations. 28 | directory = "bugfix" 29 | name = "Bug fixes" 30 | showcontent = true 31 | 32 | [[tool.towncrier.type]] 33 | # New behaviors, public APIs. That sort of stuff. 34 | directory = "feature" 35 | name = "Features" 36 | showcontent = true 37 | 38 | [[tool.towncrier.type]] 39 | # Declarations of future API removals and breaking changes in behavior. 40 | directory = "deprecation" 41 | name = "Deprecations (removal in next major release)" 42 | showcontent = true 43 | 44 | [[tool.towncrier.type]] 45 | # When something public gets removed in a breaking way. Could be 46 | # deprecated in an earlier release. 47 | directory = "breaking" 48 | name = "Removals and backward incompatible breaking changes" 49 | showcontent = true 50 | 51 | [[tool.towncrier.type]] 52 | # Notable updates to the documentation structure or build process. 53 | directory = "doc" 54 | name = "Improved documentation" 55 | showcontent = true 56 | 57 | [[tool.towncrier.type]] 58 | # Notes for downstreams about unobvious side effects and tooling. Changes 59 | # in the test invocation considerations and runtime assumptions. 60 | directory = "packaging" 61 | name = "Packaging updates and notes for downstreams" 62 | showcontent = true 63 | 64 | [[tool.towncrier.type]] 65 | # Stuff that affects the contributor experience. e.g. Running tests, 66 | # building the docs, setting up the development environment. 67 | directory = "contrib" 68 | name = "Contributor-facing changes" 69 | showcontent = true 70 | 71 | [[tool.towncrier.type]] 72 | # Changes that are hard to assign to any of the above categories. 73 | directory = "misc" 74 | name = "Miscellaneous internal changes" 75 | showcontent = true 76 | 77 | 78 | [tool.cibuildwheel] 79 | test-command = "" 80 | # don't build PyPy wheels, install from source instead 81 | skip = "pp*" 82 | 83 | [tool.codespell] 84 | skip = '.git,*.pdf,*.svg,Makefile,CONTRIBUTORS.txt,venvs,_build' 85 | ignore-words-list = 'te,assertIn' 86 | 87 | [tool.slotscheck] 88 | # TODO(3.13): Remove aiohttp.helpers once https://github.com/python/cpython/pull/106771 89 | # is available in all supported cpython versions 90 | exclude-modules = "(^aiohttp\\.helpers)" 91 | 92 | [tool.black] 93 | # TODO: Remove when project metadata is moved here. 94 | # Black can read the value from [project.requires-python]. 95 | target-version = ["py39", "py310", "py311", "py312", "py313"] 96 | -------------------------------------------------------------------------------- /requirements/base.in: -------------------------------------------------------------------------------- 1 | -r runtime-deps.in 2 | 3 | gunicorn 4 | uvloop; platform_system != "Windows" and implementation_name == "cpython" # MagicStack/uvloop#14 5 | winloop; platform_system == "Windows" and implementation_name == "cpython" 6 | -------------------------------------------------------------------------------- /requirements/base.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.10 3 | # by the following command: 4 | # 5 | # pip-compile --allow-unsafe --output-file=requirements/base.txt --strip-extras requirements/base.in 6 | # 7 | aiodns==3.4.0 8 | # via -r requirements/runtime-deps.in 9 | aiohappyeyeballs==2.6.1 10 | # via -r requirements/runtime-deps.in 11 | aiosignal==1.3.2 12 | # via -r requirements/runtime-deps.in 13 | async-timeout==5.0.1 ; python_version < "3.11" 14 | # via -r requirements/runtime-deps.in 15 | brotli==1.1.0 ; platform_python_implementation == "CPython" 16 | # via -r requirements/runtime-deps.in 17 | cffi==1.17.1 18 | # via pycares 19 | frozenlist==1.6.0 20 | # via 21 | # -r requirements/runtime-deps.in 22 | # aiosignal 23 | gunicorn==23.0.0 24 | # via -r requirements/base.in 25 | idna==3.6 26 | # via yarl 27 | multidict==6.4.4 28 | # via 29 | # -r requirements/runtime-deps.in 30 | # yarl 31 | packaging==25.0 32 | # via gunicorn 33 | propcache==0.3.1 34 | # via 35 | # -r requirements/runtime-deps.in 36 | # yarl 37 | pycares==4.8.0 38 | # via aiodns 39 | pycparser==2.22 40 | # via cffi 41 | typing-extensions==4.13.2 42 | # via multidict 43 | uvloop==0.21.0 ; platform_system != "Windows" and implementation_name == "cpython" 44 | winloop==0.1.8; platform_system == "Windows" and implementation_name == "cpython" 45 | # via -r requirements/base.in 46 | yarl==1.20.0 47 | # via -r requirements/runtime-deps.in 48 | -------------------------------------------------------------------------------- /requirements/constraints.in: -------------------------------------------------------------------------------- 1 | -r cython.in 2 | -r dev.in 3 | -r doc-spelling.in 4 | -r lint.in 5 | -------------------------------------------------------------------------------- /requirements/cython.in: -------------------------------------------------------------------------------- 1 | -r multidict.in 2 | 3 | Cython 4 | -------------------------------------------------------------------------------- /requirements/cython.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # by the following command: 4 | # 5 | # pip-compile --allow-unsafe --output-file=requirements/cython.txt --resolver=backtracking --strip-extras requirements/cython.in 6 | # 7 | cython==3.1.1 8 | # via -r requirements/cython.in 9 | multidict==6.4.4 10 | # via -r requirements/multidict.in 11 | typing-extensions==4.13.2 12 | # via multidict 13 | -------------------------------------------------------------------------------- /requirements/dev.in: -------------------------------------------------------------------------------- 1 | -r lint.in 2 | -r test.in 3 | -r doc.in 4 | 5 | cherry_picker 6 | pip-tools 7 | -------------------------------------------------------------------------------- /requirements/doc-spelling.in: -------------------------------------------------------------------------------- 1 | -r doc.in 2 | 3 | sphinxcontrib-spelling; platform_system!="Windows" # We only use it in GitHub Actions CI/CD 4 | -------------------------------------------------------------------------------- /requirements/doc-spelling.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.10 3 | # by the following command: 4 | # 5 | # pip-compile --allow-unsafe --output-file=requirements/doc-spelling.txt --strip-extras requirements/doc-spelling.in 6 | # 7 | aiohttp-theme==0.1.7 8 | # via -r requirements/doc.in 9 | alabaster==1.0.0 10 | # via sphinx 11 | babel==2.17.0 12 | # via sphinx 13 | certifi==2025.4.26 14 | # via requests 15 | charset-normalizer==3.4.2 16 | # via requests 17 | click==8.1.8 18 | # via towncrier 19 | docutils==0.21.2 20 | # via sphinx 21 | idna==3.6 22 | # via requests 23 | imagesize==1.4.1 24 | # via sphinx 25 | incremental==24.7.2 26 | # via towncrier 27 | jinja2==3.1.6 28 | # via 29 | # sphinx 30 | # towncrier 31 | markupsafe==3.0.2 32 | # via jinja2 33 | packaging==25.0 34 | # via sphinx 35 | pyenchant==3.2.2 36 | # via sphinxcontrib-spelling 37 | pygments==2.19.1 38 | # via sphinx 39 | requests==2.32.3 40 | # via 41 | # sphinx 42 | # sphinxcontrib-spelling 43 | snowballstemmer==2.2.0 44 | # via sphinx 45 | sphinx==8.1.3 46 | # via 47 | # -r requirements/doc.in 48 | # sphinxcontrib-spelling 49 | # sphinxcontrib-towncrier 50 | sphinxcontrib-applehelp==2.0.0 51 | # via sphinx 52 | sphinxcontrib-devhelp==2.0.0 53 | # via sphinx 54 | sphinxcontrib-htmlhelp==2.1.0 55 | # via sphinx 56 | sphinxcontrib-jsmath==1.0.1 57 | # via sphinx 58 | sphinxcontrib-qthelp==2.0.0 59 | # via sphinx 60 | sphinxcontrib-serializinghtml==2.0.0 61 | # via sphinx 62 | sphinxcontrib-spelling==8.0.1 ; platform_system != "Windows" 63 | # via -r requirements/doc-spelling.in 64 | sphinxcontrib-towncrier==0.5.0a0 65 | # via -r requirements/doc.in 66 | tomli==2.2.1 67 | # via 68 | # incremental 69 | # sphinx 70 | # towncrier 71 | towncrier==23.11.0 72 | # via 73 | # -r requirements/doc.in 74 | # sphinxcontrib-towncrier 75 | urllib3==2.4.0 76 | # via requests 77 | 78 | # The following packages are considered to be unsafe in a requirements file: 79 | setuptools==80.9.0 80 | # via incremental 81 | -------------------------------------------------------------------------------- /requirements/doc.in: -------------------------------------------------------------------------------- 1 | aiohttp-theme 2 | sphinx 3 | sphinxcontrib-towncrier 4 | towncrier 5 | -------------------------------------------------------------------------------- /requirements/doc.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # To update, run: 4 | # 5 | # pip-compile --allow-unsafe --output-file=requirements/doc.txt --resolver=backtracking --strip-extras requirements/doc.in 6 | # 7 | aiohttp-theme==0.1.7 8 | # via -r requirements/doc.in 9 | alabaster==1.0.0 10 | # via sphinx 11 | babel==2.17.0 12 | # via sphinx 13 | certifi==2025.4.26 14 | # via requests 15 | charset-normalizer==3.4.2 16 | # via requests 17 | click==8.1.8 18 | # via towncrier 19 | docutils==0.21.2 20 | # via sphinx 21 | idna==3.6 22 | # via requests 23 | imagesize==1.4.1 24 | # via sphinx 25 | incremental==24.7.2 26 | # via towncrier 27 | jinja2==3.1.6 28 | # via 29 | # sphinx 30 | # towncrier 31 | markupsafe==3.0.2 32 | # via jinja2 33 | packaging==25.0 34 | # via sphinx 35 | pygments==2.19.1 36 | # via sphinx 37 | requests==2.32.3 38 | # via sphinx 39 | snowballstemmer==2.2.0 40 | # via sphinx 41 | sphinx==8.1.3 42 | # via 43 | # -r requirements/doc.in 44 | # sphinxcontrib-towncrier 45 | sphinxcontrib-applehelp==2.0.0 46 | # via sphinx 47 | sphinxcontrib-devhelp==2.0.0 48 | # via sphinx 49 | sphinxcontrib-htmlhelp==2.1.0 50 | # via sphinx 51 | sphinxcontrib-jsmath==1.0.1 52 | # via sphinx 53 | sphinxcontrib-qthelp==2.0.0 54 | # via sphinx 55 | sphinxcontrib-serializinghtml==2.0.0 56 | # via sphinx 57 | sphinxcontrib-towncrier==0.5.0a0 58 | # via -r requirements/doc.in 59 | tomli==2.2.1 60 | # via 61 | # incremental 62 | # sphinx 63 | # towncrier 64 | towncrier==23.11.0 65 | # via 66 | # -r requirements/doc.in 67 | # sphinxcontrib-towncrier 68 | urllib3==2.4.0 69 | # via requests 70 | 71 | # The following packages are considered to be unsafe in a requirements file: 72 | setuptools==80.9.0 73 | # via incremental 74 | -------------------------------------------------------------------------------- /requirements/lint.in: -------------------------------------------------------------------------------- 1 | aiodns 2 | blockbuster 3 | freezegun 4 | isal 5 | mypy; implementation_name == "cpython" 6 | pre-commit 7 | proxy.py 8 | pytest 9 | pytest-mock 10 | pytest_codspeed 11 | python-on-whales 12 | slotscheck 13 | trustme 14 | uvloop; platform_system != "Windows" 15 | valkey 16 | zlib_ng 17 | -------------------------------------------------------------------------------- /requirements/lint.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # by the following command: 4 | # 5 | # pip-compile --allow-unsafe --output-file=requirements/lint.txt --strip-extras requirements/lint.in 6 | # 7 | aiodns==3.4.0 8 | # via -r requirements/lint.in 9 | annotated-types==0.7.0 10 | # via pydantic 11 | async-timeout==5.0.1 12 | # via valkey 13 | blockbuster==1.5.24 14 | # via -r requirements/lint.in 15 | cffi==1.17.1 16 | # via 17 | # cryptography 18 | # pycares 19 | # pytest-codspeed 20 | cfgv==3.4.0 21 | # via pre-commit 22 | click==8.1.8 23 | # via slotscheck 24 | cryptography==45.0.3 25 | # via trustme 26 | distlib==0.3.9 27 | # via virtualenv 28 | exceptiongroup==1.3.0 29 | # via pytest 30 | filelock==3.18.0 31 | # via virtualenv 32 | forbiddenfruit==0.1.4 33 | # via blockbuster 34 | freezegun==1.5.2 35 | # via -r requirements/lint.in 36 | identify==2.6.12 37 | # via pre-commit 38 | idna==3.7 39 | # via trustme 40 | iniconfig==2.1.0 41 | # via pytest 42 | isal==1.7.2 43 | # via -r requirements/lint.in 44 | markdown-it-py==3.0.0 45 | # via rich 46 | mdurl==0.1.2 47 | # via markdown-it-py 48 | mypy==1.15.0 ; implementation_name == "cpython" 49 | # via -r requirements/lint.in 50 | mypy-extensions==1.1.0 51 | # via mypy 52 | nodeenv==1.9.1 53 | # via pre-commit 54 | packaging==25.0 55 | # via pytest 56 | platformdirs==4.3.8 57 | # via virtualenv 58 | pluggy==1.6.0 59 | # via pytest 60 | pre-commit==4.2.0 61 | # via -r requirements/lint.in 62 | proxy-py==2.4.10 63 | # via -r requirements/lint.in 64 | pycares==4.8.0 65 | # via aiodns 66 | pycparser==2.22 67 | # via cffi 68 | pydantic==2.11.5 69 | # via python-on-whales 70 | pydantic-core==2.33.2 71 | # via pydantic 72 | pygments==2.19.1 73 | # via rich 74 | pytest==8.1.1 75 | # via 76 | # -r requirements/lint.in 77 | # pytest-codspeed 78 | # pytest-mock 79 | pytest-codspeed==3.2.0 80 | # via -r requirements/lint.in 81 | pytest-mock==3.14.1 82 | # via -r requirements/lint.in 83 | python-dateutil==2.9.0.post0 84 | # via freezegun 85 | python-on-whales==0.77.0 86 | # via -r requirements/lint.in 87 | pyyaml==6.0.2 88 | # via pre-commit 89 | rich==14.0.0 90 | # via pytest-codspeed 91 | six==1.17.0 92 | # via python-dateutil 93 | slotscheck==0.19.1 94 | # via -r requirements/lint.in 95 | tomli==2.2.1 96 | # via 97 | # mypy 98 | # pytest 99 | # slotscheck 100 | trustme==1.2.1 101 | # via -r requirements/lint.in 102 | typing-extensions==4.13.2 103 | # via 104 | # exceptiongroup 105 | # mypy 106 | # pydantic 107 | # pydantic-core 108 | # python-on-whales 109 | # rich 110 | # typing-inspection 111 | typing-inspection==0.4.1 112 | # via pydantic 113 | uvloop==0.21.0 ; platform_system != "Windows" 114 | # via -r requirements/lint.in 115 | valkey==6.1.0 116 | # via -r requirements/lint.in 117 | virtualenv==20.31.2 118 | # via pre-commit 119 | zlib-ng==0.5.1 120 | # via -r requirements/lint.in 121 | -------------------------------------------------------------------------------- /requirements/multidict.in: -------------------------------------------------------------------------------- 1 | multidict 2 | -------------------------------------------------------------------------------- /requirements/multidict.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # by the following command: 4 | # 5 | # pip-compile --allow-unsafe --output-file=requirements/multidict.txt --resolver=backtracking --strip-extras requirements/multidict.in 6 | # 7 | multidict==6.4.4 8 | # via -r requirements/multidict.in 9 | typing-extensions==4.13.2 10 | # via multidict 11 | -------------------------------------------------------------------------------- /requirements/runtime-deps.in: -------------------------------------------------------------------------------- 1 | # Extracted from `setup.cfg` via `make sync-direct-runtime-deps` 2 | 3 | aiodns >= 3.3.0 4 | aiohappyeyeballs >= 2.5.0 5 | aiosignal >= 1.1.2 6 | async-timeout >= 4.0, < 6.0 ; python_version < "3.11" 7 | Brotli; platform_python_implementation == 'CPython' 8 | brotlicffi; platform_python_implementation != 'CPython' 9 | frozenlist >= 1.1.1 10 | multidict >=4.5, < 7.0 11 | propcache >= 0.2.0 12 | yarl >= 1.17.0, < 2.0 13 | -------------------------------------------------------------------------------- /requirements/runtime-deps.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.10 3 | # by the following command: 4 | # 5 | # pip-compile --allow-unsafe --output-file=requirements/runtime-deps.txt --strip-extras requirements/runtime-deps.in 6 | # 7 | aiodns==3.4.0 8 | # via -r requirements/runtime-deps.in 9 | aiohappyeyeballs==2.6.1 10 | # via -r requirements/runtime-deps.in 11 | aiosignal==1.3.2 12 | # via -r requirements/runtime-deps.in 13 | async-timeout==5.0.1 ; python_version < "3.11" 14 | # via -r requirements/runtime-deps.in 15 | brotli==1.1.0 ; platform_python_implementation == "CPython" 16 | # via -r requirements/runtime-deps.in 17 | cffi==1.17.1 18 | # via pycares 19 | frozenlist==1.6.0 20 | # via 21 | # -r requirements/runtime-deps.in 22 | # aiosignal 23 | idna==3.6 24 | # via yarl 25 | multidict==6.4.4 26 | # via 27 | # -r requirements/runtime-deps.in 28 | # yarl 29 | propcache==0.3.1 30 | # via 31 | # -r requirements/runtime-deps.in 32 | # yarl 33 | pycares==4.8.0 34 | # via aiodns 35 | pycparser==2.22 36 | # via cffi 37 | typing-extensions==4.13.2 38 | # via multidict 39 | yarl==1.20.0 40 | # via -r requirements/runtime-deps.in 41 | -------------------------------------------------------------------------------- /requirements/sync-direct-runtime-deps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Sync direct runtime dependencies from setup.cfg to runtime-deps.in.""" 3 | 4 | from configparser import ConfigParser 5 | from pathlib import Path 6 | 7 | cfg = ConfigParser() 8 | cfg.read(Path("setup.cfg")) 9 | reqs = cfg["options"]["install_requires"] + cfg.items("options.extras_require")[0][1] 10 | reqs = sorted(reqs.split("\n"), key=str.casefold) 11 | reqs.remove("") 12 | 13 | with open(Path("requirements", "runtime-deps.in"), "w") as outfile: 14 | header = "# Extracted from `setup.cfg` via `make sync-direct-runtime-deps`\n\n" 15 | outfile.write(header) 16 | outfile.write("\n".join(reqs) + "\n") 17 | -------------------------------------------------------------------------------- /requirements/test.in: -------------------------------------------------------------------------------- 1 | -r base.in 2 | 3 | blockbuster 4 | coverage 5 | freezegun 6 | isal 7 | mypy; implementation_name == "cpython" 8 | pkgconfig 9 | proxy.py >= 2.4.4rc5 10 | pytest 11 | pytest-cov 12 | pytest-mock 13 | pytest-xdist 14 | pytest_codspeed 15 | python-on-whales 16 | setuptools-git 17 | trustme; platform_machine != "i686" # no 32-bit wheels 18 | wait-for-it 19 | zlib_ng 20 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | import sys 4 | 5 | from setuptools import Extension, setup 6 | 7 | if sys.version_info < (3, 9): 8 | raise RuntimeError("aiohttp 4.x requires Python 3.9+") 9 | 10 | 11 | USE_SYSTEM_DEPS = bool( 12 | os.environ.get("AIOHTTP_USE_SYSTEM_DEPS", os.environ.get("USE_SYSTEM_DEPS")) 13 | ) 14 | NO_EXTENSIONS: bool = bool(os.environ.get("AIOHTTP_NO_EXTENSIONS")) 15 | HERE = pathlib.Path(__file__).parent 16 | IS_GIT_REPO = (HERE / ".git").exists() 17 | 18 | 19 | if sys.implementation.name != "cpython": 20 | NO_EXTENSIONS = True 21 | 22 | 23 | if ( 24 | not USE_SYSTEM_DEPS 25 | and IS_GIT_REPO 26 | and not (HERE / "vendor/llhttp/README.md").exists() 27 | ): 28 | print("Install submodules when building from git clone", file=sys.stderr) 29 | print("Hint:", file=sys.stderr) 30 | print(" git submodule update --init", file=sys.stderr) 31 | sys.exit(2) 32 | 33 | 34 | # NOTE: makefile cythonizes all Cython modules 35 | 36 | if USE_SYSTEM_DEPS: 37 | import shlex 38 | 39 | import pkgconfig 40 | 41 | llhttp_sources = [] 42 | llhttp_kwargs = { 43 | "extra_compile_args": shlex.split(pkgconfig.cflags("libllhttp")), 44 | "extra_link_args": shlex.split(pkgconfig.libs("libllhttp")), 45 | } 46 | else: 47 | llhttp_sources = [ 48 | "vendor/llhttp/build/c/llhttp.c", 49 | "vendor/llhttp/src/native/api.c", 50 | "vendor/llhttp/src/native/http.c", 51 | ] 52 | llhttp_kwargs = { 53 | "define_macros": [("LLHTTP_STRICT_MODE", 0)], 54 | "include_dirs": ["vendor/llhttp/build"], 55 | } 56 | 57 | extensions = [ 58 | Extension("aiohttp._websocket.mask", ["aiohttp/_websocket/mask.c"]), 59 | Extension( 60 | "aiohttp._http_parser", 61 | [ 62 | "aiohttp/_http_parser.c", 63 | "aiohttp/_find_header.c", 64 | *llhttp_sources, 65 | ], 66 | **llhttp_kwargs, 67 | ), 68 | Extension("aiohttp._http_writer", ["aiohttp/_http_writer.c"]), 69 | Extension("aiohttp._websocket.reader_c", ["aiohttp/_websocket/reader_c.c"]), 70 | ] 71 | 72 | 73 | build_type = "Pure" if NO_EXTENSIONS else "Accelerated" 74 | setup_kwargs = {} if NO_EXTENSIONS else {"ext_modules": extensions} 75 | 76 | print("*********************", file=sys.stderr) 77 | print("* {build_type} build *".format_map(locals()), file=sys.stderr) 78 | print("*********************", file=sys.stderr) 79 | setup(**setup_kwargs) 80 | -------------------------------------------------------------------------------- /tests/aiohttp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aio-libs/aiohttp/8edec635b65035ad819cd98abc7bfeb192f788a3/tests/aiohttp.jpg -------------------------------------------------------------------------------- /tests/aiohttp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aio-libs/aiohttp/8edec635b65035ad819cd98abc7bfeb192f788a3/tests/aiohttp.png -------------------------------------------------------------------------------- /tests/autobahn/Dockerfile.aiohttp: -------------------------------------------------------------------------------- 1 | FROM python:3.9.5 2 | 3 | COPY ./ /src 4 | 5 | WORKDIR /src 6 | 7 | RUN pip install . 8 | -------------------------------------------------------------------------------- /tests/autobahn/Dockerfile.autobahn: -------------------------------------------------------------------------------- 1 | FROM crossbario/autobahn-testsuite:0.8.2 2 | 3 | RUN apt-get update && apt-get install python3 python3-pip -y 4 | RUN pip3 install wait-for-it 5 | 6 | CMD ["wstest", "--mode", "fuzzingserver", "--spec", "/config/fuzzingserver.json"] 7 | -------------------------------------------------------------------------------- /tests/autobahn/client/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import asyncio 4 | 5 | import aiohttp 6 | 7 | 8 | async def client(url: str, name: str) -> None: 9 | async with aiohttp.ClientSession() as session: 10 | async with session.ws_connect(url + "/getCaseCount") as ws: 11 | msg = await ws.receive() 12 | assert msg.type is aiohttp.WSMsgType.TEXT 13 | num_tests = int(msg.data) 14 | print("running %d cases" % num_tests) 15 | 16 | for i in range(1, num_tests + 1): 17 | print("running test case:", i) 18 | text_url = url + "/runCase?case=%d&agent=%s" % (i, name) 19 | async with session.ws_connect(text_url) as ws: 20 | async for msg in ws: 21 | if msg.type is aiohttp.WSMsgType.TEXT: 22 | await ws.send_str(msg.data) 23 | elif msg.type is aiohttp.WSMsgType.BINARY: 24 | await ws.send_bytes(msg.data) 25 | else: 26 | break 27 | 28 | url = url + "/updateReports?agent=%s" % name 29 | async with session.ws_connect(url) as ws: 30 | print("finally requesting %s" % url) 31 | 32 | 33 | async def run(url: str, name: str) -> None: 34 | try: 35 | await client(url, name) 36 | except Exception: 37 | import traceback 38 | 39 | traceback.print_exc() 40 | 41 | 42 | if __name__ == "__main__": 43 | asyncio.run(run("http://localhost:9001", "aiohttp")) 44 | -------------------------------------------------------------------------------- /tests/autobahn/client/fuzzingserver.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "url": "ws://localhost:9001", 4 | 5 | "options": {"failByDrop": false}, 6 | "outdir": "./reports/clients", 7 | "webport": 8080, 8 | 9 | "cases": ["*"], 10 | "exclude-cases": ["12.*", "13.*"], 11 | "exclude-agent-cases": {} 12 | } 13 | -------------------------------------------------------------------------------- /tests/autobahn/server/fuzzingclient.json: -------------------------------------------------------------------------------- 1 | { 2 | "options": { "failByDrop": false }, 3 | "outdir": "./reports/servers", 4 | 5 | "servers": [ 6 | { 7 | "agent": "AutobahnServer", 8 | "url": "ws://localhost:9001", 9 | "options": { "version": 18 } 10 | } 11 | ], 12 | 13 | "cases": ["*"], 14 | "exclude-cases": ["12.*", "13.*"], 15 | "exclude-agent-cases": {} 16 | } 17 | -------------------------------------------------------------------------------- /tests/autobahn/server/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import logging 4 | from typing import List 5 | 6 | from aiohttp import WSCloseCode, web 7 | 8 | websockets = web.AppKey("websockets", List[web.WebSocketResponse]) 9 | 10 | 11 | async def wshandler(request: web.Request) -> web.WebSocketResponse: 12 | ws = web.WebSocketResponse(autoclose=False) 13 | is_ws = ws.can_prepare(request) 14 | if not is_ws: 15 | raise web.HTTPBadRequest() 16 | 17 | await ws.prepare(request) 18 | 19 | request.app[websockets].append(ws) 20 | 21 | while True: 22 | msg = await ws.receive() 23 | 24 | if msg.type is web.WSMsgType.TEXT: 25 | await ws.send_str(msg.data) 26 | elif msg.type is web.WSMsgType.BINARY: 27 | await ws.send_bytes(msg.data) 28 | elif msg.type is web.WSMsgType.CLOSE: 29 | await ws.close() 30 | break 31 | else: 32 | break 33 | 34 | return ws 35 | 36 | 37 | async def on_shutdown(app: web.Application) -> None: 38 | ws_list = app[websockets] 39 | for ws in set(ws_list): 40 | await ws.close(code=WSCloseCode.GOING_AWAY, message=b"Server shutdown") 41 | 42 | 43 | if __name__ == "__main__": 44 | logging.basicConfig( 45 | level=logging.DEBUG, format="%(asctime)s %(levelname)s %(message)s" 46 | ) 47 | 48 | app = web.Application() 49 | l: List[web.WebSocketResponse] = [] 50 | app[websockets] = l 51 | app.router.add_route("GET", "/", wshandler) 52 | app.on_shutdown.append(on_shutdown) 53 | try: 54 | web.run_app(app, port=9001) 55 | except KeyboardInterrupt: 56 | print("Server stopped at http://127.0.0.1:9001") 57 | -------------------------------------------------------------------------------- /tests/data.unknown_mime_type: -------------------------------------------------------------------------------- 1 | file content 2 | -------------------------------------------------------------------------------- /tests/data.zero_bytes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aio-libs/aiohttp/8edec635b65035ad819cd98abc7bfeb192f788a3/tests/data.zero_bytes -------------------------------------------------------------------------------- /tests/isolated/check_for_client_response_leak.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import contextlib 3 | import gc 4 | import sys 5 | 6 | from aiohttp import ClientError, ClientSession, web 7 | from aiohttp.test_utils import get_unused_port_socket 8 | 9 | gc.set_debug(gc.DEBUG_LEAK) 10 | 11 | 12 | async def main() -> None: 13 | app = web.Application() 14 | 15 | async def stream_handler(request: web.Request) -> web.Response: 16 | assert request.transport is not None 17 | request.transport.close() # Forcefully closing connection 18 | return web.Response() 19 | 20 | app.router.add_get("/stream", stream_handler) 21 | sock = get_unused_port_socket("127.0.0.1") 22 | port = sock.getsockname()[1] 23 | 24 | runner = web.AppRunner(app) 25 | await runner.setup() 26 | site = web.SockSite(runner, sock) 27 | await site.start() 28 | 29 | session = ClientSession() 30 | 31 | async def fetch_stream(url: str) -> None: 32 | """Fetch a stream and read a few bytes from it.""" 33 | with contextlib.suppress(ClientError): 34 | await session.get(url) 35 | 36 | client_task = asyncio.create_task(fetch_stream(f"http://localhost:{port}/stream")) 37 | await client_task 38 | gc.collect() 39 | client_response_present = any( 40 | type(obj).__name__ == "ClientResponse" for obj in gc.garbage 41 | ) 42 | await session.close() 43 | await runner.cleanup() 44 | sys.exit(1 if client_response_present else 0) 45 | 46 | 47 | asyncio.run(main()) 48 | -------------------------------------------------------------------------------- /tests/isolated/check_for_request_leak.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import gc 3 | import sys 4 | from typing import NoReturn 5 | 6 | from aiohttp import ClientSession, web 7 | from aiohttp.test_utils import get_unused_port_socket 8 | 9 | gc.set_debug(gc.DEBUG_LEAK) 10 | 11 | 12 | async def main() -> None: 13 | app = web.Application() 14 | 15 | async def handler(request: web.Request) -> NoReturn: 16 | await request.json() 17 | assert False 18 | 19 | app.router.add_route("GET", "/json", handler) 20 | sock = get_unused_port_socket("127.0.0.1") 21 | port = sock.getsockname()[1] 22 | 23 | runner = web.AppRunner(app) 24 | await runner.setup() 25 | site = web.SockSite(runner, sock) 26 | await site.start() 27 | 28 | async with ClientSession() as session: 29 | async with session.get(f"http://127.0.0.1:{port}/json") as resp: 30 | await resp.read() 31 | 32 | # Give time for the cancelled task to be collected 33 | await asyncio.sleep(0.5) 34 | gc.collect() 35 | request_present = any(type(obj).__name__ == "Request" for obj in gc.garbage) 36 | await session.close() 37 | await runner.cleanup() 38 | sys.exit(1 if request_present else 0) 39 | 40 | 41 | asyncio.run(main()) 42 | -------------------------------------------------------------------------------- /tests/test_benchmarks_cookiejar.py: -------------------------------------------------------------------------------- 1 | """codspeed benchmarks for cookies.""" 2 | 3 | from http.cookies import BaseCookie 4 | 5 | from pytest_codspeed import BenchmarkFixture 6 | from yarl import URL 7 | 8 | from aiohttp.cookiejar import CookieJar 9 | 10 | 11 | async def test_load_cookies_into_temp_cookiejar(benchmark: BenchmarkFixture) -> None: 12 | """Benchmark for creating a temp CookieJar and filtering by URL. 13 | 14 | This benchmark matches what the client request does when cookies 15 | are passed to the request. 16 | """ 17 | all_cookies: BaseCookie[str] = BaseCookie() 18 | url = URL("http://example.com") 19 | cookies = {"cookie1": "value1", "cookie2": "value2"} 20 | 21 | @benchmark 22 | def _run() -> None: 23 | tmp_cookie_jar = CookieJar() 24 | tmp_cookie_jar.update_cookies(cookies) 25 | req_cookies = tmp_cookie_jar.filter_cookies(url) 26 | all_cookies.load(req_cookies) 27 | -------------------------------------------------------------------------------- /tests/test_benchmarks_http_writer.py: -------------------------------------------------------------------------------- 1 | """codspeed benchmarks for http writer.""" 2 | 3 | from multidict import CIMultiDict 4 | from pytest_codspeed import BenchmarkFixture 5 | 6 | from aiohttp import hdrs 7 | from aiohttp.http_writer import _serialize_headers 8 | 9 | 10 | def test_serialize_headers(benchmark: BenchmarkFixture) -> None: 11 | """Benchmark 100 calls to _serialize_headers.""" 12 | status_line = "HTTP/1.1 200 OK" 13 | headers = CIMultiDict( 14 | { 15 | hdrs.CONTENT_TYPE: "text/plain", 16 | hdrs.CONTENT_LENGTH: "100", 17 | hdrs.CONNECTION: "keep-alive", 18 | hdrs.DATE: "Mon, 23 May 2005 22:38:34 GMT", 19 | hdrs.SERVER: "Test/1.0", 20 | hdrs.CONTENT_ENCODING: "gzip", 21 | hdrs.VARY: "Accept-Encoding", 22 | hdrs.CACHE_CONTROL: "no-cache", 23 | hdrs.PRAGMA: "no-cache", 24 | hdrs.EXPIRES: "0", 25 | hdrs.LAST_MODIFIED: "Mon, 23 May 2005 22:38:34 GMT", 26 | hdrs.ETAG: "1234567890", 27 | } 28 | ) 29 | 30 | @benchmark 31 | def _run() -> None: 32 | for _ in range(100): 33 | _serialize_headers(status_line, headers) 34 | -------------------------------------------------------------------------------- /tests/test_benchmarks_web_fileresponse.py: -------------------------------------------------------------------------------- 1 | """codspeed benchmarks for the web file responses.""" 2 | 3 | import asyncio 4 | import pathlib 5 | 6 | from multidict import CIMultiDict 7 | from pytest_codspeed import BenchmarkFixture 8 | 9 | from aiohttp import ClientResponse, web 10 | from aiohttp.pytest_plugin import AiohttpClient 11 | 12 | 13 | def test_simple_web_file_response( 14 | loop: asyncio.AbstractEventLoop, 15 | aiohttp_client: AiohttpClient, 16 | benchmark: BenchmarkFixture, 17 | ) -> None: 18 | """Benchmark creating 100 simple web.FileResponse.""" 19 | response_count = 100 20 | filepath = pathlib.Path(__file__).parent / "sample.txt" 21 | 22 | async def handler(request: web.Request) -> web.FileResponse: 23 | return web.FileResponse(path=filepath) 24 | 25 | app = web.Application() 26 | app.router.add_route("GET", "/", handler) 27 | 28 | async def run_file_response_benchmark() -> None: 29 | client = await aiohttp_client(app) 30 | for _ in range(response_count): 31 | await client.get("/") 32 | await client.close() 33 | 34 | @benchmark 35 | def _run() -> None: 36 | loop.run_until_complete(run_file_response_benchmark()) 37 | 38 | 39 | def test_simple_web_file_sendfile_fallback_response( 40 | loop: asyncio.AbstractEventLoop, 41 | aiohttp_client: AiohttpClient, 42 | benchmark: BenchmarkFixture, 43 | ) -> None: 44 | """Benchmark creating 100 simple web.FileResponse without sendfile.""" 45 | response_count = 100 46 | filepath = pathlib.Path(__file__).parent / "sample.txt" 47 | 48 | async def handler(request: web.Request) -> web.FileResponse: 49 | transport = request.transport 50 | assert transport is not None 51 | transport._sendfile_compatible = False # type: ignore[attr-defined] 52 | return web.FileResponse(path=filepath) 53 | 54 | app = web.Application() 55 | app.router.add_route("GET", "/", handler) 56 | 57 | async def run_file_response_benchmark() -> None: 58 | client = await aiohttp_client(app) 59 | for _ in range(response_count): 60 | await client.get("/") 61 | await client.close() 62 | 63 | @benchmark 64 | def _run() -> None: 65 | loop.run_until_complete(run_file_response_benchmark()) 66 | 67 | 68 | def test_simple_web_file_response_not_modified( 69 | loop: asyncio.AbstractEventLoop, 70 | aiohttp_client: AiohttpClient, 71 | benchmark: BenchmarkFixture, 72 | ) -> None: 73 | """Benchmark web.FileResponse that return a 304.""" 74 | response_count = 100 75 | filepath = pathlib.Path(__file__).parent / "sample.txt" 76 | 77 | async def handler(request: web.Request) -> web.FileResponse: 78 | return web.FileResponse(path=filepath) 79 | 80 | app = web.Application() 81 | app.router.add_route("GET", "/", handler) 82 | 83 | async def make_last_modified_header() -> CIMultiDict[str]: 84 | client = await aiohttp_client(app) 85 | resp = await client.get("/") 86 | last_modified = resp.headers["Last-Modified"] 87 | headers = CIMultiDict({"If-Modified-Since": last_modified}) 88 | return headers 89 | 90 | async def run_file_response_benchmark( 91 | headers: CIMultiDict[str], 92 | ) -> ClientResponse: 93 | client = await aiohttp_client(app) 94 | for _ in range(response_count): 95 | resp = await client.get("/", headers=headers) 96 | 97 | await client.close() 98 | return resp # type: ignore[possibly-undefined] 99 | 100 | headers = loop.run_until_complete(make_last_modified_header()) 101 | 102 | @benchmark 103 | def _run() -> None: 104 | resp = loop.run_until_complete(run_file_response_benchmark(headers)) 105 | assert resp.status == 304 106 | -------------------------------------------------------------------------------- /tests/test_benchmarks_web_middleware.py: -------------------------------------------------------------------------------- 1 | """codspeed benchmarks for web middlewares.""" 2 | 3 | import asyncio 4 | 5 | from pytest_codspeed import BenchmarkFixture 6 | 7 | from aiohttp import web 8 | from aiohttp.pytest_plugin import AiohttpClient 9 | from aiohttp.typedefs import Handler 10 | 11 | 12 | def test_ten_web_middlewares( 13 | benchmark: BenchmarkFixture, 14 | loop: asyncio.AbstractEventLoop, 15 | aiohttp_client: AiohttpClient, 16 | ) -> None: 17 | """Benchmark 100 requests with 10 middlewares.""" 18 | message_count = 100 19 | 20 | async def handler(request: web.Request) -> web.Response: 21 | return web.Response() 22 | 23 | app = web.Application() 24 | app.router.add_route("GET", "/", handler) 25 | 26 | class MiddlewareClass: 27 | async def call( 28 | self, request: web.Request, handler: Handler 29 | ) -> web.StreamResponse: 30 | return await handler(request) 31 | 32 | for _ in range(10): 33 | app.middlewares.append(MiddlewareClass().call) 34 | 35 | async def run_client_benchmark() -> None: 36 | client = await aiohttp_client(app) 37 | for _ in range(message_count): 38 | await client.get("/") 39 | await client.close() 40 | 41 | @benchmark 42 | def _run() -> None: 43 | loop.run_until_complete(run_client_benchmark()) 44 | -------------------------------------------------------------------------------- /tests/test_benchmarks_web_response.py: -------------------------------------------------------------------------------- 1 | """codspeed benchmarks for the web responses.""" 2 | 3 | from pytest_codspeed import BenchmarkFixture 4 | 5 | from aiohttp import web 6 | 7 | 8 | def test_simple_web_response(benchmark: BenchmarkFixture) -> None: 9 | """Benchmark creating 100 simple web.Response.""" 10 | response_count = 100 11 | 12 | @benchmark 13 | def _run() -> None: 14 | for _ in range(response_count): 15 | web.Response() 16 | 17 | 18 | def test_web_response_with_headers(benchmark: BenchmarkFixture) -> None: 19 | """Benchmark creating 100 web.Response with headers.""" 20 | response_count = 100 21 | headers = { 22 | "Content-Type": "text/plain", 23 | "Server": "aiohttp", 24 | "Date": "Sun, 01 Aug 2021 12:00:00 GMT", 25 | } 26 | 27 | @benchmark 28 | def _run() -> None: 29 | for _ in range(response_count): 30 | web.Response(headers=headers) 31 | 32 | 33 | def test_web_response_with_bytes_body( 34 | benchmark: BenchmarkFixture, 35 | ) -> None: 36 | """Benchmark creating 100 web.Response with bytes.""" 37 | response_count = 100 38 | 39 | @benchmark 40 | def _run() -> None: 41 | for _ in range(response_count): 42 | web.Response(body=b"Hello, World!") 43 | 44 | 45 | def test_web_response_with_text_body(benchmark: BenchmarkFixture) -> None: 46 | """Benchmark creating 100 web.Response with text.""" 47 | response_count = 100 48 | 49 | @benchmark 50 | def _run() -> None: 51 | for _ in range(response_count): 52 | web.Response(text="Hello, World!") 53 | 54 | 55 | def test_simple_web_stream_response(benchmark: BenchmarkFixture) -> None: 56 | """Benchmark creating 100 simple web.StreamResponse.""" 57 | response_count = 100 58 | 59 | @benchmark 60 | def _run() -> None: 61 | for _ in range(response_count): 62 | web.StreamResponse() 63 | -------------------------------------------------------------------------------- /tests/test_classbasedview.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | 3 | import pytest 4 | 5 | from aiohttp import web 6 | from aiohttp.web_urldispatcher import View 7 | 8 | 9 | def test_ctor() -> None: 10 | request = mock.Mock() 11 | view = View(request) 12 | assert view.request is request 13 | 14 | 15 | async def test_render_ok() -> None: 16 | resp = web.Response(text="OK") 17 | 18 | class MyView(View): 19 | async def get(self) -> web.StreamResponse: 20 | return resp 21 | 22 | request = mock.Mock() 23 | request.method = "GET" 24 | resp2 = await MyView(request) 25 | assert resp is resp2 26 | 27 | 28 | async def test_render_unknown_method() -> None: 29 | class MyView(View): 30 | async def get(self) -> web.StreamResponse: 31 | return web.Response(text="OK") 32 | 33 | options = get 34 | 35 | request = mock.Mock() 36 | request.method = "UNKNOWN" 37 | with pytest.raises(web.HTTPMethodNotAllowed) as ctx: 38 | await MyView(request) 39 | assert ctx.value.headers["allow"] == "GET,OPTIONS" 40 | assert ctx.value.status == 405 41 | 42 | 43 | async def test_render_unsupported_method() -> None: 44 | class MyView(View): 45 | async def get(self) -> web.StreamResponse: 46 | return web.Response(text="OK") 47 | 48 | options = delete = get 49 | 50 | request = mock.Mock() 51 | request.method = "POST" 52 | with pytest.raises(web.HTTPMethodNotAllowed) as ctx: 53 | await MyView(request) 54 | assert ctx.value.headers["allow"] == "DELETE,GET,OPTIONS" 55 | assert ctx.value.status == 405 56 | -------------------------------------------------------------------------------- /tests/test_client_fingerprint.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | from unittest import mock 3 | 4 | import pytest 5 | 6 | import aiohttp 7 | 8 | ssl = pytest.importorskip("ssl") 9 | 10 | 11 | def test_fingerprint_sha256() -> None: 12 | sha256 = hashlib.sha256(b"12345678" * 64).digest() 13 | fp = aiohttp.Fingerprint(sha256) 14 | assert fp.fingerprint == sha256 15 | 16 | 17 | def test_fingerprint_sha1() -> None: 18 | sha1 = hashlib.sha1(b"12345678" * 64).digest() 19 | with pytest.raises(ValueError): 20 | aiohttp.Fingerprint(sha1) 21 | 22 | 23 | def test_fingerprint_md5() -> None: 24 | md5 = hashlib.md5(b"12345678" * 64).digest() 25 | with pytest.raises(ValueError): 26 | aiohttp.Fingerprint(md5) 27 | 28 | 29 | def test_fingerprint_check_no_ssl() -> None: 30 | sha256 = hashlib.sha256(b"12345678" * 64).digest() 31 | fp = aiohttp.Fingerprint(sha256) 32 | transport = mock.Mock() 33 | transport.get_extra_info.return_value = None 34 | fp.check(transport) 35 | -------------------------------------------------------------------------------- /tests/test_compression_utils.py: -------------------------------------------------------------------------------- 1 | """Tests for compression utils.""" 2 | 3 | import pytest 4 | 5 | from aiohttp.compression_utils import ZLibBackend, ZLibCompressor, ZLibDecompressor 6 | 7 | 8 | @pytest.mark.usefixtures("parametrize_zlib_backend") 9 | async def test_compression_round_trip_in_executor() -> None: 10 | """Ensure that compression and decompression work correctly in the executor.""" 11 | compressor = ZLibCompressor( 12 | strategy=ZLibBackend.Z_DEFAULT_STRATEGY, max_sync_chunk_size=1 13 | ) 14 | assert type(compressor._compressor) is type(ZLibBackend.compressobj()) 15 | decompressor = ZLibDecompressor(max_sync_chunk_size=1) 16 | assert type(decompressor._decompressor) is type(ZLibBackend.decompressobj()) 17 | data = b"Hi" * 100 18 | compressed_data = await compressor.compress(data) + compressor.flush() 19 | decompressed_data = await decompressor.decompress(compressed_data) 20 | assert data == decompressed_data 21 | 22 | 23 | @pytest.mark.usefixtures("parametrize_zlib_backend") 24 | async def test_compression_round_trip_in_event_loop() -> None: 25 | """Ensure that compression and decompression work correctly in the event loop.""" 26 | compressor = ZLibCompressor( 27 | strategy=ZLibBackend.Z_DEFAULT_STRATEGY, max_sync_chunk_size=10000 28 | ) 29 | assert type(compressor._compressor) is type(ZLibBackend.compressobj()) 30 | decompressor = ZLibDecompressor(max_sync_chunk_size=10000) 31 | assert type(decompressor._decompressor) is type(ZLibBackend.decompressobj()) 32 | data = b"Hi" * 100 33 | compressed_data = await compressor.compress(data) + compressor.flush() 34 | decompressed_data = await decompressor.decompress(compressed_data) 35 | assert data == decompressed_data 36 | -------------------------------------------------------------------------------- /tests/test_imports.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import sys 4 | from pathlib import Path 5 | 6 | import pytest 7 | 8 | 9 | def test___all__(pytester: pytest.Pytester) -> None: 10 | """See https://github.com/aio-libs/aiohttp/issues/6197""" 11 | pytester.makepyfile( 12 | test_a=""" 13 | from aiohttp import * 14 | assert 'GunicornWebWorker' in globals() 15 | """ 16 | ) 17 | result = pytester.runpytest("-vv") 18 | result.assert_outcomes(passed=0, errors=0) 19 | 20 | 21 | def test_web___all__(pytester: pytest.Pytester) -> None: 22 | pytester.makepyfile( 23 | test_b=""" 24 | from aiohttp.web import * 25 | """ 26 | ) 27 | result = pytester.runpytest("-vv") 28 | result.assert_outcomes(passed=0, errors=0) 29 | 30 | 31 | _IS_CI_ENV = os.getenv("CI") == "true" 32 | _XDIST_WORKER_COUNT = int(os.getenv("PYTEST_XDIST_WORKER_COUNT", 0)) 33 | _IS_XDIST_RUN = _XDIST_WORKER_COUNT > 1 34 | 35 | _TARGET_TIMINGS_BY_PYTHON_VERSION = { 36 | "3.12": ( 37 | # 3.12+ is expected to be a bit slower due to performance trade-offs, 38 | # and even slower under pytest-xdist, especially in CI 39 | _XDIST_WORKER_COUNT * 100 * (1 if _IS_CI_ENV else 1.53) 40 | if _IS_XDIST_RUN 41 | else 295 42 | ), 43 | } 44 | _TARGET_TIMINGS_BY_PYTHON_VERSION["3.13"] = _TARGET_TIMINGS_BY_PYTHON_VERSION["3.12"] 45 | 46 | 47 | @pytest.mark.internal 48 | @pytest.mark.dev_mode 49 | @pytest.mark.skipif( 50 | not sys.platform.startswith("linux") or platform.python_implementation() == "PyPy", 51 | reason="Timing is more reliable on Linux", 52 | ) 53 | def test_import_time(pytester: pytest.Pytester) -> None: 54 | """Check that importing aiohttp doesn't take too long. 55 | 56 | Obviously, the time may vary on different machines and may need to be adjusted 57 | from time to time, but this should provide an early warning if something is 58 | added that significantly increases import time. 59 | """ 60 | root = Path(__file__).parent.parent 61 | old_path = os.environ.get("PYTHONPATH") 62 | os.environ["PYTHONPATH"] = os.pathsep.join([str(root)] + sys.path) 63 | 64 | best_time_ms = 1000 65 | cmd = "import timeit; print(int(timeit.timeit('import aiohttp', number=1) * 1000))" 66 | try: 67 | for _ in range(3): 68 | r = pytester.run(sys.executable, "-We", "-c", cmd) 69 | 70 | assert not r.stderr.str() 71 | runtime_ms = int(r.stdout.str()) 72 | if runtime_ms < best_time_ms: 73 | best_time_ms = runtime_ms 74 | finally: 75 | if old_path is None: 76 | os.environ.pop("PYTHONPATH") 77 | else: 78 | os.environ["PYTHONPATH"] = old_path 79 | 80 | expected_time = _TARGET_TIMINGS_BY_PYTHON_VERSION.get( 81 | f"{sys.version_info.major}.{sys.version_info.minor}", 200 82 | ) 83 | assert best_time_ms < expected_time 84 | -------------------------------------------------------------------------------- /tests/test_leaks.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import platform 3 | import subprocess 4 | import sys 5 | 6 | import pytest 7 | 8 | IS_PYPY = platform.python_implementation() == "PyPy" 9 | 10 | 11 | @pytest.mark.skipif(IS_PYPY, reason="gc.DEBUG_LEAK not available on PyPy") 12 | @pytest.mark.parametrize( 13 | ("script", "message"), 14 | [ 15 | ( 16 | # Test that ClientResponse is collected after server disconnects. 17 | # https://github.com/aio-libs/aiohttp/issues/10535 18 | "check_for_client_response_leak.py", 19 | "ClientResponse leaked", 20 | ), 21 | ( 22 | # Test that Request object is collected when the handler raises. 23 | # https://github.com/aio-libs/aiohttp/issues/10548 24 | "check_for_request_leak.py", 25 | "Request leaked", 26 | ), 27 | ], 28 | ) 29 | def test_leak(script: str, message: str) -> None: 30 | """Run isolated leak test script and check for leaks.""" 31 | leak_test_script = pathlib.Path(__file__).parent.joinpath("isolated", script) 32 | 33 | with subprocess.Popen( 34 | [sys.executable, "-u", str(leak_test_script)], 35 | stdout=subprocess.PIPE, 36 | ) as proc: 37 | assert proc.wait() == 0, message 38 | -------------------------------------------------------------------------------- /tests/test_loop.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import platform 3 | import threading 4 | 5 | import pytest 6 | 7 | from aiohttp import web 8 | from aiohttp.test_utils import AioHTTPTestCase, loop_context 9 | 10 | 11 | @pytest.mark.skipif( 12 | platform.system() == "Windows", reason="the test is not valid for Windows" 13 | ) 14 | async def test_subprocess_co(loop: asyncio.AbstractEventLoop) -> None: 15 | proc = await asyncio.create_subprocess_shell( 16 | "exit 0", 17 | stdin=asyncio.subprocess.DEVNULL, 18 | stdout=asyncio.subprocess.DEVNULL, 19 | stderr=asyncio.subprocess.DEVNULL, 20 | ) 21 | await proc.wait() 22 | 23 | 24 | class TestCase(AioHTTPTestCase): 25 | on_startup_called: bool 26 | 27 | async def get_application(self) -> web.Application: 28 | app = web.Application() 29 | app.on_startup.append(self.on_startup_hook) 30 | return app 31 | 32 | async def on_startup_hook(self, app: web.Application) -> None: 33 | self.on_startup_called = True 34 | 35 | async def test_on_startup_hook(self) -> None: 36 | self.assertTrue(self.on_startup_called) 37 | 38 | 39 | def test_default_loop(loop: asyncio.AbstractEventLoop) -> None: 40 | assert asyncio.get_event_loop() is loop 41 | 42 | 43 | def test_setup_loop_non_main_thread() -> None: 44 | child_exc = None 45 | 46 | def target() -> None: 47 | try: 48 | with loop_context() as loop: 49 | assert asyncio.get_event_loop() is loop 50 | loop.run_until_complete(test_subprocess_co(loop)) 51 | except Exception as exc: 52 | nonlocal child_exc 53 | child_exc = exc 54 | 55 | # Ensures setup_test_loop can be called by pytest-xdist in non-main thread. 56 | t = threading.Thread(target=target) 57 | t.start() 58 | t.join() 59 | 60 | assert child_exc is None 61 | -------------------------------------------------------------------------------- /tests/test_tcp_helpers.py: -------------------------------------------------------------------------------- 1 | import socket 2 | from unittest import mock 3 | 4 | import pytest 5 | 6 | from aiohttp.tcp_helpers import tcp_nodelay 7 | 8 | has_ipv6: bool = socket.has_ipv6 9 | if has_ipv6: 10 | # The socket.has_ipv6 flag may be True if Python was built with IPv6 11 | # support, but the target system still may not have it. 12 | # So let's ensure that we really have IPv6 support. 13 | try: 14 | with socket.socket(socket.AF_INET6, socket.SOCK_STREAM): 15 | pass 16 | except OSError: 17 | has_ipv6 = False 18 | 19 | 20 | # nodelay 21 | 22 | 23 | def test_tcp_nodelay_exception() -> None: 24 | transport = mock.Mock() 25 | s = mock.Mock() 26 | s.setsockopt = mock.Mock() 27 | s.family = socket.AF_INET 28 | s.setsockopt.side_effect = OSError 29 | transport.get_extra_info.return_value = s 30 | tcp_nodelay(transport, True) 31 | s.setsockopt.assert_called_with(socket.IPPROTO_TCP, socket.TCP_NODELAY, True) 32 | 33 | 34 | def test_tcp_nodelay_enable() -> None: 35 | transport = mock.Mock() 36 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 37 | transport.get_extra_info.return_value = s 38 | tcp_nodelay(transport, True) 39 | assert s.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) 40 | 41 | 42 | def test_tcp_nodelay_enable_and_disable() -> None: 43 | transport = mock.Mock() 44 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 45 | transport.get_extra_info.return_value = s 46 | tcp_nodelay(transport, True) 47 | assert s.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) 48 | tcp_nodelay(transport, False) 49 | assert not s.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) 50 | 51 | 52 | @pytest.mark.skipif(not has_ipv6, reason="IPv6 is not available") 53 | def test_tcp_nodelay_enable_ipv6() -> None: 54 | transport = mock.Mock() 55 | with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 56 | transport.get_extra_info.return_value = s 57 | tcp_nodelay(transport, True) 58 | assert s.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) 59 | 60 | 61 | @pytest.mark.skipif(not hasattr(socket, "AF_UNIX"), reason="requires unix sockets") 62 | def test_tcp_nodelay_enable_unix() -> None: 63 | # do not set nodelay for unix socket 64 | transport = mock.Mock() 65 | s = mock.Mock(family=socket.AF_UNIX, type=socket.SOCK_STREAM) 66 | transport.get_extra_info.return_value = s 67 | tcp_nodelay(transport, True) 68 | assert not s.setsockopt.called 69 | 70 | 71 | def test_tcp_nodelay_enable_no_socket() -> None: 72 | transport = mock.Mock() 73 | transport.get_extra_info.return_value = None 74 | tcp_nodelay(transport, True) 75 | -------------------------------------------------------------------------------- /tests/test_web_request_handler.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | 3 | from aiohttp import web 4 | 5 | 6 | async def serve(request: web.BaseRequest) -> web.Response: 7 | return web.Response() 8 | 9 | 10 | async def test_repr() -> None: 11 | manager = web.Server(serve) 12 | handler = manager() 13 | 14 | assert "" == repr(handler) 15 | 16 | with mock.patch.object(handler, "transport", autospec=True): 17 | assert "" == repr(handler) 18 | 19 | 20 | async def test_connections() -> None: 21 | manager = web.Server(serve) 22 | assert manager.connections == [] 23 | 24 | handler = mock.Mock(spec_set=web.RequestHandler) 25 | handler._task_handler = None 26 | transport = object() 27 | manager.connection_made(handler, transport) # type: ignore[arg-type] 28 | assert manager.connections == [handler] 29 | 30 | manager.connection_lost(handler, None) 31 | assert manager.connections == [] 32 | 33 | 34 | async def test_shutdown_no_timeout() -> None: 35 | manager = web.Server(serve) 36 | 37 | handler = mock.Mock(spec_set=web.RequestHandler) 38 | handler._task_handler = None 39 | handler.shutdown = mock.AsyncMock(return_value=mock.Mock()) 40 | transport = mock.Mock() 41 | manager.connection_made(handler, transport) 42 | 43 | await manager.shutdown() 44 | 45 | manager.connection_lost(handler, None) 46 | assert manager.connections == [] 47 | handler.shutdown.assert_called_with(None) 48 | 49 | 50 | async def test_shutdown_timeout() -> None: 51 | manager = web.Server(serve) 52 | 53 | handler = mock.Mock() 54 | handler.shutdown = mock.AsyncMock(return_value=mock.Mock()) 55 | transport = mock.Mock() 56 | manager.connection_made(handler, transport) 57 | 58 | await manager.shutdown(timeout=0.1) 59 | 60 | manager.connection_lost(handler, None) 61 | assert manager.connections == [] 62 | handler.shutdown.assert_called_with(0.1) 63 | -------------------------------------------------------------------------------- /tests/test_websocket_data_queue.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from unittest import mock 3 | 4 | import pytest 5 | 6 | from aiohttp._websocket.models import WSMessageBinary 7 | from aiohttp._websocket.reader import WebSocketDataQueue 8 | from aiohttp.base_protocol import BaseProtocol 9 | 10 | 11 | @pytest.fixture 12 | def protocol() -> BaseProtocol: 13 | return mock.create_autospec(BaseProtocol, spec_set=True, instance=True, _reading_paused=False) # type: ignore[no-any-return] 14 | 15 | 16 | @pytest.fixture 17 | def buffer( 18 | loop: asyncio.AbstractEventLoop, protocol: BaseProtocol 19 | ) -> WebSocketDataQueue: 20 | return WebSocketDataQueue(protocol, limit=1, loop=loop) 21 | 22 | 23 | class TestWebSocketDataQueue: 24 | def test_feed_pause(self, buffer: WebSocketDataQueue) -> None: 25 | buffer._protocol._reading_paused = False 26 | for _ in range(3): 27 | buffer.feed_data(WSMessageBinary(b"x", size=1)) 28 | 29 | assert buffer._protocol.pause_reading.called # type: ignore[attr-defined] 30 | 31 | async def test_resume_on_read(self, buffer: WebSocketDataQueue) -> None: 32 | buffer.feed_data(WSMessageBinary(b"x", size=1)) 33 | 34 | buffer._protocol._reading_paused = True 35 | await buffer.read() 36 | assert buffer._protocol.resume_reading.called # type: ignore[attr-defined] 37 | -------------------------------------------------------------------------------- /tools/bench-asyncio-write.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import atexit 3 | import math 4 | import os 5 | import signal 6 | from typing import List, Tuple 7 | 8 | PORT = 8888 9 | 10 | server = os.fork() 11 | if server == 0: 12 | loop = asyncio.get_event_loop() 13 | coro = asyncio.start_server(lambda *_: None, port=PORT) 14 | loop.run_until_complete(coro) 15 | loop.run_forever() 16 | else: 17 | atexit.register(os.kill, server, signal.SIGTERM) 18 | 19 | 20 | async def write_joined_bytearray(writer, chunks): 21 | body = bytearray(chunks[0]) 22 | for c in chunks[1:]: 23 | body += c 24 | writer.write(body) 25 | 26 | 27 | async def write_joined_list(writer, chunks): 28 | body = b"".join(chunks) 29 | writer.write(body) 30 | 31 | 32 | async def write_separately(writer, chunks): 33 | for c in chunks: 34 | writer.write(c) 35 | 36 | 37 | def fm_size(s, _fms=("", "K", "M", "G")): 38 | i = 0 39 | while s >= 1024: 40 | s /= 1024 41 | i += 1 42 | return f"{s:.0f}{_fms[i]}B" 43 | 44 | 45 | def fm_time(s, _fms=("", "m", "µ", "n")): 46 | if s == 0: 47 | return "0" 48 | i = 0 49 | while s < 1: 50 | s *= 1000 51 | i += 1 52 | return f"{s:.2f}{_fms[i]}s" 53 | 54 | 55 | def _job(j: List[int]) -> Tuple[str, List[bytes]]: 56 | # Always start with a 256B headers chunk 57 | body = [b"0" * s for s in [256] + list(j)] 58 | job_title = f"{fm_size(sum(j))} / {len(j)}" 59 | return (job_title, body) 60 | 61 | 62 | writes = [ 63 | ("b''.join", write_joined_list), 64 | ("bytearray", write_joined_bytearray), 65 | ("multiple writes", write_separately), 66 | ] 67 | 68 | bodies = ( 69 | [], 70 | [10 * 2**0], 71 | [10 * 2**7], 72 | [10 * 2**17], 73 | [10 * 2**27], 74 | [50 * 2**27], 75 | [1 * 2**0 for _ in range(10)], 76 | [1 * 2**7 for _ in range(10)], 77 | [1 * 2**17 for _ in range(10)], 78 | [1 * 2**27 for _ in range(10)], 79 | [10 * 2**27 for _ in range(5)], 80 | ) 81 | 82 | 83 | jobs = [_job(j) for j in bodies] 84 | 85 | 86 | async def time(loop, fn, *args): 87 | spent = [] 88 | while not spent or sum(spent) < 0.2: 89 | s = loop.time() 90 | await fn(*args) 91 | e = loop.time() 92 | spent.append(e - s) 93 | mean = sum(spent) / len(spent) 94 | sd = sum((x - mean) ** 2 for x in spent) / len(spent) 95 | return len(spent), mean, math.sqrt(sd) 96 | 97 | 98 | async def main(loop): 99 | _, writer = await asyncio.open_connection(port=PORT) 100 | print("Loop:", loop) 101 | print("Transport:", writer._transport) 102 | res = [ 103 | ("size/chunks", "Write option", "Mean", "Std dev", "loops", "Variation"), 104 | ] 105 | res.append([":---", ":---", "---:", "---:", "---:", "---:"]) 106 | 107 | async def bench(job_title, w, body, base=None): 108 | it, mean, sd = await time(loop, w[1], writer, c) 109 | res.append( 110 | ( 111 | job_title, 112 | w[0], 113 | fm_time(mean), 114 | fm_time(sd), 115 | str(it), 116 | f"{mean / base - 1:.2%}" if base is not None else "", 117 | ) 118 | ) 119 | return mean 120 | 121 | for t, c in jobs: 122 | print("Doing", t) 123 | base = await bench(t, writes[0], c) 124 | for w in writes[1:]: 125 | await bench("", w, c, base) 126 | return res 127 | 128 | 129 | loop = asyncio.get_event_loop() 130 | results = loop.run_until_complete(main(loop)) 131 | with open("bench.md", "w") as f: 132 | for line in results: 133 | f.write("| {} |\n".format(" | ".join(line))) 134 | -------------------------------------------------------------------------------- /tools/check_changes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import re 4 | import sys 5 | from pathlib import Path 6 | 7 | ALLOWED_SUFFIXES = ( 8 | "bugfix", 9 | "feature", 10 | "deprecation", 11 | "breaking", 12 | "doc", 13 | "packaging", 14 | "contrib", 15 | "misc", 16 | ) 17 | PATTERN = re.compile( 18 | r"(\d+|[0-9a-f]{8}|[0-9a-f]{7}|[0-9a-f]{40})\.(" 19 | + "|".join(ALLOWED_SUFFIXES) 20 | + r")(\.\d+)?(\.rst)?", 21 | ) 22 | 23 | 24 | def get_root(script_path): 25 | folder = script_path.resolve().parent 26 | while not (folder / ".git").exists(): 27 | folder = folder.parent 28 | if folder == folder.anchor: 29 | raise RuntimeError("git repo not found") 30 | return folder 31 | 32 | 33 | def main(argv): 34 | print('Check "CHANGES" folder... ', end="", flush=True) 35 | here = Path(argv[0]) 36 | root = get_root(here) 37 | changes = root / "CHANGES" 38 | failed = False 39 | for fname in changes.iterdir(): 40 | if fname.name in (".gitignore", ".TEMPLATE.rst", "README.rst"): 41 | continue 42 | if not PATTERN.match(fname.name): 43 | if not failed: 44 | print("") 45 | print("Illegal CHANGES record", fname, file=sys.stderr) 46 | failed = True 47 | 48 | if failed: 49 | print("", file=sys.stderr) 50 | print("See ./CHANGES/README.rst for the naming instructions", file=sys.stderr) 51 | print("", file=sys.stderr) 52 | else: 53 | print("OK") 54 | 55 | return int(failed) 56 | 57 | 58 | if __name__ == "__main__": 59 | sys.exit(main(sys.argv)) 60 | -------------------------------------------------------------------------------- /tools/check_sum.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | import hashlib 5 | import pathlib 6 | import sys 7 | 8 | PARSER = argparse.ArgumentParser( 9 | description="Helper for check file hashes in Makefile instead of bare timestamps" 10 | ) 11 | PARSER.add_argument("dst", metavar="DST", type=pathlib.Path) 12 | PARSER.add_argument("-d", "--debug", action="store_true", default=False) 13 | 14 | 15 | def main(argv): 16 | args = PARSER.parse_args(argv) 17 | dst = args.dst 18 | assert dst.suffix == ".hash" 19 | dirname = dst.parent 20 | if dirname.name != ".hash": 21 | if args.debug: 22 | print(f"Invalid name {dst} -> dirname {dirname}", file=sys.stderr) 23 | return 0 24 | dirname.mkdir(exist_ok=True) 25 | src_dir = dirname.parent 26 | src_name = dst.stem # drop .hash 27 | full_src = src_dir / src_name 28 | hasher = hashlib.sha256() 29 | try: 30 | hasher.update(full_src.read_bytes()) 31 | except OSError: 32 | if args.debug: 33 | print(f"Cannot open {full_src}", file=sys.stderr) 34 | return 0 35 | src_hash = hasher.hexdigest() 36 | if dst.exists(): 37 | dst_hash = dst.read_text() 38 | else: 39 | dst_hash = "" 40 | if src_hash != dst_hash: 41 | dst.write_text(src_hash) 42 | print(f"re-hash {src_hash}") 43 | else: 44 | if args.debug: 45 | print(f"Skip {src_hash} checksum, up-to-date") 46 | return 0 47 | 48 | 49 | if __name__ == "__main__": 50 | sys.exit(main(sys.argv[1:])) 51 | -------------------------------------------------------------------------------- /tools/cleanup_changes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Run me after the backport branch release to cleanup CHANGES records 4 | # that was backported and published. 5 | 6 | import re 7 | import subprocess 8 | from pathlib import Path 9 | 10 | ALLOWED_SUFFIXES = ( 11 | "bugfix", 12 | "feature", 13 | "deprecation", 14 | "breaking", 15 | "doc", 16 | "packaging", 17 | "contrib", 18 | "misc", 19 | ) 20 | PATTERN = re.compile( 21 | r"(\d+|[0-9a-f]{8}|[0-9a-f]{7}|[0-9a-f]{40})\.(" 22 | + "|".join(ALLOWED_SUFFIXES) 23 | + r")(\.\d+)?(\.rst)?", 24 | ) 25 | 26 | 27 | def main(): 28 | root = Path(__file__).parent.parent 29 | delete = [] 30 | changes = (root / "CHANGES.rst").read_text() 31 | for fname in (root / "CHANGES").iterdir(): 32 | match = PATTERN.match(fname.name) 33 | if match is not None: 34 | commit_issue_or_pr = match.group(1) 35 | tst_issue_or_pr = f":issue:`{commit_issue_or_pr}`" 36 | tst_commit = f":commit:`{commit_issue_or_pr}`" 37 | if tst_issue_or_pr in changes or tst_commit in changes: 38 | subprocess.run(["git", "rm", fname]) 39 | delete.append(fname.name) 40 | print("Deleted CHANGES records:", " ".join(delete)) 41 | print("Please verify and commit") 42 | 43 | 44 | if __name__ == "__main__": 45 | main() 46 | -------------------------------------------------------------------------------- /tools/drop_merged_branches.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | git remote prune origin 4 | -------------------------------------------------------------------------------- /tools/testing/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PYTHON_VERSION 2 | FROM python:$PYTHON_VERSION 3 | 4 | ARG AIOHTTP_NO_EXTENSIONS 5 | ENV AIOHTTP_NO_EXTENSIONS=$AIOHTTP_NO_EXTENSIONS 6 | 7 | WORKDIR /deps 8 | ADD ./requirements ./requirements 9 | ADD Makefile . 10 | RUN make install 11 | 12 | ADD ./tools/testing/entrypoint.sh / 13 | 14 | WORKDIR /src 15 | ENTRYPOINT ["/bin/bash", "/entrypoint.sh"] 16 | -------------------------------------------------------------------------------- /tools/testing/Dockerfile.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !/requirements 3 | !/Makefile 4 | !/tools/testing/entrypoint.sh 5 | -------------------------------------------------------------------------------- /tools/testing/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | [[ "$AIOHTTP_NO_EXTENSIONS" != "y" ]] && make cythonize 4 | 5 | python -m pytest -qx --no-cov $1 6 | -------------------------------------------------------------------------------- /vendor/README.rst: -------------------------------------------------------------------------------- 1 | LLHTTP 2 | ------ 3 | 4 | When building aiohttp from source, there is a pure Python parser used by default. 5 | For better performance, you may want to build the higher performance C parser. 6 | 7 | To build this ``llhttp`` parser, first get/update the submodules (to update to a 8 | newer release, add ``--remote``):: 9 | 10 | git submodule update --init --recursive 11 | 12 | Then build ``llhttp``:: 13 | 14 | cd vendor/llhttp/ 15 | npm ci 16 | make 17 | 18 | Then build our parser:: 19 | 20 | cd - 21 | make cythonize 22 | 23 | Then you can build or install it with ``python -m build`` or ``pip install -e .`` 24 | --------------------------------------------------------------------------------