├── .github ├── dependabot.yml └── workflows │ ├── lint.yml │ └── tox.yml ├── .gitignore ├── .pylintrc ├── .readthedocs.yaml ├── CONTRIBUTING.md ├── LICENSE ├── MAINTAINERS ├── MANIFEST.in ├── Makefile ├── NOTICE ├── README.rst ├── SECURITY.md ├── THANKS ├── appveyor.yml ├── docs ├── Makefile ├── README.rst ├── gunicorn_ext.py ├── logo │ ├── gunicorn.png │ └── gunicorn.svg ├── make.bat ├── site │ ├── .nojekyll │ ├── CNAME │ ├── community.html │ ├── configuration.html │ ├── configure.html │ ├── css │ │ └── style.css │ ├── deploy.html │ ├── deployment.html │ ├── design.html │ ├── faq.html │ ├── images │ │ ├── about.jpg │ │ ├── arrow.png │ │ ├── banner-bg.jpg │ │ ├── community.jpg │ │ ├── documents.jpg │ │ ├── downloads.jpg │ │ ├── favicon.png │ │ ├── footer-arrow.png │ │ ├── footer-logo.jpg │ │ ├── greenbutton.jpg │ │ ├── gunicorn.png │ │ ├── large_gunicorn.png │ │ ├── logo-bottom.png │ │ ├── logo.jpg │ │ ├── logo.png │ │ ├── redbutton.jpg │ │ ├── separator.jpg │ │ ├── title.png │ │ └── user1.jpg │ ├── index.html │ ├── install.html │ ├── installation.html │ ├── js │ │ └── main.js │ ├── news.html │ ├── run.html │ ├── sitemap.xml │ ├── tuning.html │ └── usage.html ├── sitemap_gen.py └── source │ ├── 2010-news.rst │ ├── 2011-news.rst │ ├── 2012-news.rst │ ├── 2013-news.rst │ ├── 2014-news.rst │ ├── 2015-news.rst │ ├── 2016-news.rst │ ├── 2017-news.rst │ ├── 2018-news.rst │ ├── 2019-news.rst │ ├── 2020-news.rst │ ├── 2021-news.rst │ ├── 2023-news.rst │ ├── 2024-news.rst │ ├── _static │ └── gunicorn.png │ ├── community.rst │ ├── conf.py │ ├── configure.rst │ ├── custom.rst │ ├── deploy.rst │ ├── design.rst │ ├── faq.rst │ ├── index.rst │ ├── install.rst │ ├── instrumentation.rst │ ├── news.rst │ ├── run.rst │ ├── settings.rst │ └── signals.rst ├── examples ├── alt_spec.py ├── bad.py ├── boot_fail.py ├── deep │ ├── __init__.py │ └── test.py ├── echo.py ├── example_config.py ├── frameworks │ ├── cherryapp.py │ ├── django │ │ ├── README │ │ └── testing │ │ │ ├── manage.py │ │ │ └── testing │ │ │ ├── __init__.py │ │ │ ├── apps │ │ │ ├── __init__.py │ │ │ └── someapp │ │ │ │ ├── __init__.py │ │ │ │ ├── middleware.py │ │ │ │ ├── models.py │ │ │ │ ├── templates │ │ │ │ ├── base.html │ │ │ │ └── home.html │ │ │ │ ├── tests.py │ │ │ │ ├── urls.py │ │ │ │ └── views.py │ │ │ ├── settings.py │ │ │ ├── urls.py │ │ │ └── wsgi.py │ ├── flask_sendfile.py │ ├── flaskapp.py │ ├── flaskapp_aiohttp_wsgi.py │ ├── pyramidapp.py │ ├── requirements.txt │ ├── requirements_cherryapp.txt │ ├── requirements_flaskapp.txt │ ├── requirements_pyramidapp.txt │ ├── requirements_tornadoapp.txt │ ├── requirements_webpyapp.txt │ ├── tornadoapp.py │ └── webpyapp.py ├── gunicorn_rc ├── hello.txt ├── log_app.ini ├── log_app.py ├── logging.conf ├── longpoll.py ├── multiapp.py ├── multidomainapp.py ├── nginx.conf ├── read_django_settings.py ├── readline_app.py ├── sendfile.py ├── server.crt ├── server.key ├── slowclient.py ├── standalone_app.py ├── supervisor.conf ├── test.py ├── timeout.py ├── websocket │ ├── gevent_websocket.py │ ├── websocket.html │ └── websocket.py └── when_ready.conf.py ├── gunicorn ├── __init__.py ├── __main__.py ├── app │ ├── __init__.py │ ├── base.py │ ├── pasterapp.py │ └── wsgiapp.py ├── arbiter.py ├── config.py ├── debug.py ├── errors.py ├── glogging.py ├── http │ ├── __init__.py │ ├── body.py │ ├── errors.py │ ├── message.py │ ├── parser.py │ ├── unreader.py │ └── wsgi.py ├── instrument │ ├── __init__.py │ └── statsd.py ├── pidfile.py ├── reloader.py ├── sock.py ├── systemd.py ├── util.py └── workers │ ├── __init__.py │ ├── base.py │ ├── base_async.py │ ├── geventlet.py │ ├── ggevent.py │ ├── gthread.py │ ├── gtornado.py │ ├── sync.py │ └── workertmp.py ├── pyproject.toml ├── requirements_dev.txt ├── requirements_test.txt ├── scripts └── update_thanks.py ├── tests ├── config │ ├── __init__.py │ ├── test_cfg.py │ ├── test_cfg_alt.py │ └── test_cfg_with_wsgi_app.py ├── requests │ ├── invalid │ │ ├── 001.http │ │ ├── 001.py │ │ ├── 002.http │ │ ├── 002.py │ │ ├── 003.http │ │ ├── 003.py │ │ ├── 003b.http │ │ ├── 003b.py │ │ ├── 003c.http │ │ ├── 003c.py │ │ ├── 004.http │ │ ├── 004.py │ │ ├── 005.http │ │ ├── 005.py │ │ ├── 006.http │ │ ├── 006.py │ │ ├── 007.http │ │ ├── 007.py │ │ ├── 008.http │ │ ├── 008.py │ │ ├── 009.http │ │ ├── 009.py │ │ ├── 010.http │ │ ├── 010.py │ │ ├── 011.http │ │ ├── 011.py │ │ ├── 012.http │ │ ├── 012.py │ │ ├── 013.http │ │ ├── 013.py │ │ ├── 014.http │ │ ├── 014.py │ │ ├── 015.http │ │ ├── 015.py │ │ ├── 016.http │ │ ├── 016.py │ │ ├── 017.http │ │ ├── 017.py │ │ ├── 018.http │ │ ├── 018.py │ │ ├── 019.http │ │ ├── 019.py │ │ ├── 020.http │ │ ├── 020.py │ │ ├── 021.http │ │ ├── 021.py │ │ ├── 022.http │ │ ├── 022.py │ │ ├── 023.http │ │ ├── 023.py │ │ ├── 024.http │ │ ├── 024.py │ │ ├── 040.http │ │ ├── 040.py │ │ ├── chunked_01.http │ │ ├── chunked_01.py │ │ ├── chunked_02.http │ │ ├── chunked_02.py │ │ ├── chunked_03.http │ │ ├── chunked_03.py │ │ ├── chunked_04.http │ │ ├── chunked_04.py │ │ ├── chunked_05.http │ │ ├── chunked_05.py │ │ ├── chunked_06.http │ │ ├── chunked_06.py │ │ ├── chunked_07.http │ │ ├── chunked_07.py │ │ ├── chunked_08.http │ │ ├── chunked_08.py │ │ ├── chunked_09.http │ │ ├── chunked_09.py │ │ ├── chunked_10.http │ │ ├── chunked_10.py │ │ ├── chunked_11.http │ │ ├── chunked_11.py │ │ ├── chunked_12.http │ │ ├── chunked_12.py │ │ ├── chunked_13.http │ │ ├── chunked_13.py │ │ ├── invalid_field_value_01.http │ │ ├── invalid_field_value_01.py │ │ ├── nonascii_01.http │ │ ├── nonascii_01.py │ │ ├── nonascii_02.http │ │ ├── nonascii_02.py │ │ ├── nonascii_03.http │ │ ├── nonascii_03.py │ │ ├── nonascii_04.http │ │ ├── nonascii_04.py │ │ ├── obs_fold_01.http │ │ ├── obs_fold_01.py │ │ ├── pp_01.http │ │ ├── pp_01.py │ │ ├── pp_02.http │ │ ├── pp_02.py │ │ ├── prefix_01.http │ │ ├── prefix_01.py │ │ ├── prefix_02.http │ │ ├── prefix_02.py │ │ ├── prefix_03.http │ │ ├── prefix_03.py │ │ ├── prefix_04.http │ │ ├── prefix_04.py │ │ ├── prefix_05.http │ │ ├── prefix_05.py │ │ ├── prefix_06.http │ │ ├── prefix_06.py │ │ ├── version_01.http │ │ ├── version_01.py │ │ ├── version_02.http │ │ └── version_02.py │ └── valid │ │ ├── 001.http │ │ ├── 001.py │ │ ├── 002.http │ │ ├── 002.py │ │ ├── 003.http │ │ ├── 003.py │ │ ├── 004.http │ │ ├── 004.py │ │ ├── 005.http │ │ ├── 005.py │ │ ├── 006.http │ │ ├── 006.py │ │ ├── 007.http │ │ ├── 007.py │ │ ├── 008.http │ │ ├── 008.py │ │ ├── 009.http │ │ ├── 009.py │ │ ├── 010.http │ │ ├── 010.py │ │ ├── 011.http │ │ ├── 011.py │ │ ├── 012.http │ │ ├── 012.py │ │ ├── 013.http │ │ ├── 013.py │ │ ├── 014.http │ │ ├── 014.py │ │ ├── 015.http │ │ ├── 015.py │ │ ├── 017.http │ │ ├── 017.py │ │ ├── 018.http │ │ ├── 018.py │ │ ├── 019.http │ │ ├── 019.py │ │ ├── 020.http │ │ ├── 020.py │ │ ├── 021.http │ │ ├── 021.py │ │ ├── 022.http │ │ ├── 022.py │ │ ├── 023.http │ │ ├── 023.py │ │ ├── 024.http │ │ ├── 024.py │ │ ├── 025.http │ │ ├── 025.py │ │ ├── 025_line.http │ │ ├── 025_line.py │ │ ├── 026.http │ │ ├── 026.py │ │ ├── 027.http │ │ ├── 027.py │ │ ├── 028.http │ │ ├── 028.py │ │ ├── 029.http │ │ ├── 029.py │ │ ├── 030.http │ │ ├── 030.py │ │ ├── 031.http │ │ ├── 031.py │ │ ├── 031compat.http │ │ ├── 031compat.py │ │ ├── 031compat2.http │ │ ├── 031compat2.py │ │ ├── 040.http │ │ ├── 040.py │ │ ├── 040_compat.http │ │ ├── 040_compat.py │ │ ├── 099.http │ │ ├── 099.py │ │ ├── 100.http │ │ ├── 100.py │ │ ├── compat_obs_fold.http │ │ ├── compat_obs_fold.py │ │ ├── compat_obs_fold_huge.http │ │ ├── compat_obs_fold_huge.py │ │ ├── padding_01.http │ │ ├── padding_01.py │ │ ├── pp_01.http │ │ ├── pp_01.py │ │ ├── pp_02.http │ │ └── pp_02.py ├── support.py ├── t.py ├── test_arbiter.py ├── test_config.py ├── test_http.py ├── test_invalid_requests.py ├── test_logger.py ├── test_pidfile.py ├── test_reload.py ├── test_sock.py ├── test_ssl.py ├── test_statsd.py ├── test_systemd.py ├── test_util.py ├── test_valid_requests.py ├── treq.py └── workers │ ├── __init__.py │ ├── test_geventlet.py │ └── test_ggevent.py └── tox.ini /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | on: [push, pull_request] 3 | permissions: 4 | contents: read # to fetch code (actions/checkout) 5 | env: 6 | # note that some tools care only for the name, not the value 7 | FORCE_COLOR: 1 8 | jobs: 9 | lint: 10 | name: ${{ matrix.python-version }} / tox-${{ matrix.toxenv || '(other)' }} 11 | timeout-minutes: 10 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | toxenv: [lint, docs-lint, pycodestyle] 17 | python-version: [ "3.10" ] 18 | include: 19 | # for actions that want git env, not tox env 20 | - toxenv: null 21 | python-version: "3.10" 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Using Python ${{ matrix.python-version }} 25 | uses: actions/setup-python@v5 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | cache: pip 29 | - name: Install Dependencies (tox) 30 | if: ${{ matrix.toxenv }} 31 | run: | 32 | python -m pip install --upgrade pip 33 | python -m pip install tox 34 | - run: tox -e ${{ matrix.toxenv }} 35 | if: ${{ matrix.toxenv }} 36 | - name: Install Dependencies (non-toxic) 37 | if: ${{ ! matrix.toxenv }} 38 | run: | 39 | python -m pip install sphinx 40 | - name: "Update docs" 41 | if: ${{ ! matrix.toxenv }} 42 | run: | 43 | # this will update docs/source/settings.rst - but will not create html output 44 | (cd docs && sphinx-build -b "dummy" -d _build/doctrees source "_build/dummy") 45 | if unclean=$(git status --untracked-files=no --porcelain) && [ -z "$unclean" ]; then 46 | echo "no uncommitted changes in working tree (as it should be)" 47 | else 48 | echo "did you forget to run `make -C docs html`?" 49 | echo "$unclean" 50 | exit 2 51 | fi 52 | -------------------------------------------------------------------------------- /.github/workflows/tox.yml: -------------------------------------------------------------------------------- 1 | name: tox 2 | on: [push, pull_request] 3 | permissions: 4 | contents: read # to fetch code (actions/checkout) 5 | env: 6 | # note that some tools care only for the name, not the value 7 | FORCE_COLOR: 1 8 | jobs: 9 | tox: 10 | name: ${{ matrix.os }} / ${{ matrix.python-version }} 11 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idtimeout-minutes 12 | timeout-minutes: 20 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | unsupported: [false] 18 | os: 19 | - ubuntu-latest 20 | # not defaulting to macos-latest: Python <= 3.9 was missing from macos-14 @ arm64 21 | - macos-13 22 | # Not testing Windows, because tests need Unix-only fcntl, grp, pwd, etc. 23 | python-version: 24 | # CPython <= 3.7 is EoL since 2023-06-27 25 | - "3.7" 26 | - "3.8" 27 | - "3.9" 28 | - "3.10" 29 | - "3.11" 30 | - "3.12" 31 | # PyPy <= 3.8 is EoL since 2023-06-16 32 | - "pypy-3.9" 33 | - "pypy-3.10" 34 | include: 35 | # Note: potentially "universal2" release 36 | # https://github.com/actions/runner-images/issues/9741 37 | - os: macos-latest 38 | python-version: "3.12" 39 | unsupported: false 40 | # will run these without showing red CI results should they fail 41 | - os: macos-latest 42 | python-version: "3.13" 43 | unsupported: true 44 | - os: ubuntu-latest 45 | python-version: "3.13" 46 | unsupported: true 47 | steps: 48 | - uses: actions/checkout@v4 49 | - name: Using Python ${{ matrix.python-version }} 50 | uses: actions/setup-python@v5 51 | with: 52 | python-version: ${{ matrix.python-version }} 53 | cache: pip 54 | cache-dependency-path: requirements_test.txt 55 | check-latest: true 56 | allow-prereleases: ${{ matrix.unsupported }} 57 | - name: Install Dependencies 58 | run: | 59 | python -m pip install --upgrade pip 60 | python -m pip install tox 61 | - run: tox -e run-module 62 | continue-on-error: ${{ matrix.unsupported }} 63 | - run: tox -e run-entrypoint 64 | continue-on-error: ${{ matrix.unsupported }} 65 | - run: tox -e py 66 | continue-on-error: ${{ matrix.unsupported }} 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg 2 | *.egg-info 3 | *.pyc 4 | *.so 5 | .coverage 6 | .pytest_cache 7 | .tox 8 | __pycache__ 9 | build 10 | docs/_build 11 | coverage.xml 12 | dist 13 | examples/frameworks/django/testing/testdb.sql 14 | examples/frameworks/pylonstest/PasteScript* 15 | examples/frameworks/pylonstest/pylonstest.egg-info/ 16 | MANIFEST 17 | nohup.out 18 | setuptools-* 19 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | ignore= 4 | build, 5 | docs, 6 | examples, 7 | scripts, 8 | _compat.py, 9 | _gaiohttp.py, 10 | 11 | [MESSAGES CONTROL] 12 | 13 | disable= 14 | attribute-defined-outside-init, 15 | bad-mcs-classmethod-argument, 16 | bare-except, 17 | broad-except, 18 | duplicate-bases, 19 | duplicate-code, 20 | eval-used, 21 | fixme, 22 | import-error, 23 | import-outside-toplevel, 24 | import-self, 25 | inconsistent-return-statements, 26 | invalid-name, 27 | missing-docstring, 28 | no-else-return, 29 | no-member, 30 | no-self-argument, 31 | no-staticmethod-decorator, 32 | not-callable, 33 | protected-access, 34 | raise-missing-from, 35 | redefined-outer-name, 36 | too-few-public-methods, 37 | too-many-arguments, 38 | too-many-branches, 39 | too-many-instance-attributes, 40 | too-many-lines, 41 | too-many-locals, 42 | too-many-nested-blocks, 43 | too-many-public-methods, 44 | too-many-statements, 45 | wrong-import-position, 46 | wrong-import-order, 47 | ungrouped-imports, 48 | unused-argument, 49 | useless-object-inheritance, 50 | useless-import-alias, 51 | comparison-with-callable, 52 | try-except-raise, 53 | consider-using-with, 54 | consider-using-f-string, 55 | unspecified-encoding 56 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.11" 13 | 14 | # Build documentation in the docs/ directory with Sphinx 15 | sphinx: 16 | configuration: docs/source/conf.py 17 | 18 | # We recommend specifying your dependencies to enable reproducible builds: 19 | # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 20 | # python: 21 | # install: 22 | # - requirements: docs/requirements.txt 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2009-2024 (c) Benoît Chesneau 2 | 2009-2015 (c) Paul J. Davis 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Core maintainers 2 | ================ 3 | 4 | Benoit Chesneau 5 | Konstantin Kapustin 6 | Randall Leeds 7 | Berker Peksağ 8 | Jason Madden 9 | Brett Randall 10 | 11 | Alumni 12 | ====== 13 | 14 | This list contains maintainers that are no longer active on the project. 15 | It is thanks to these people that the project has become what it is today. 16 | Thank you! 17 | 18 | 19 | Paul J. Davis 20 | Kenneth Reitz 21 | Nikolay Kim 22 | Andrew Svetlov 23 | Stéphane Wirtel -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include .gitignore 2 | include LICENSE 3 | include NOTICE 4 | include README.rst 5 | include THANKS 6 | include requirements_dev.txt 7 | include requirements_test.txt 8 | include tox.ini 9 | include .pylintrc 10 | recursive-include tests * 11 | recursive-include examples * 12 | recursive-include docs * 13 | recursive-include examples/frameworks * 14 | recursive-exclude * __pycache__ 15 | recursive-exclude docs/build * 16 | recursive-exclude docs/_build * 17 | recursive-exclude * *.py[co] 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | virtualenv venv 3 | venv/bin/pip install -e . 4 | venv/bin/pip install -r requirements_dev.txt 5 | 6 | test: 7 | venv/bin/python setup.py test 8 | 9 | coverage: 10 | venv/bin/python setup.py test --cov 11 | 12 | clean: 13 | @rm -rf .Python MANIFEST build dist venv* *.egg-info *.egg 14 | @find . -type f -name "*.py[co]" -delete 15 | @find . -type d -name "__pycache__" -delete 16 | 17 | .PHONY: build clean coverage test 18 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Gunicorn 2 | -------- 3 | 4 | .. image:: https://img.shields.io/pypi/v/gunicorn.svg?style=flat 5 | :alt: PyPI version 6 | :target: https://pypi.python.org/pypi/gunicorn 7 | 8 | .. image:: https://img.shields.io/pypi/pyversions/gunicorn.svg 9 | :alt: Supported Python versions 10 | :target: https://pypi.python.org/pypi/gunicorn 11 | 12 | .. image:: https://github.com/benoitc/gunicorn/actions/workflows/tox.yml/badge.svg 13 | :alt: Build Status 14 | :target: https://github.com/benoitc/gunicorn/actions/workflows/tox.yml 15 | 16 | .. image:: https://github.com/benoitc/gunicorn/actions/workflows/lint.yml/badge.svg 17 | :alt: Lint Status 18 | :target: https://github.com/benoitc/gunicorn/actions/workflows/lint.yml 19 | 20 | Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It's a pre-fork 21 | worker model ported from Ruby's Unicorn_ project. The Gunicorn server is broadly 22 | compatible with various web frameworks, simply implemented, light on server 23 | resource usage, and fairly speedy. 24 | 25 | Feel free to join us in `#gunicorn`_ on `Libera.chat`_. 26 | 27 | Documentation 28 | ------------- 29 | 30 | The documentation is hosted at https://docs.gunicorn.org. 31 | 32 | Installation 33 | ------------ 34 | 35 | Gunicorn requires **Python 3.x >= 3.7**. 36 | 37 | Install from PyPI:: 38 | 39 | $ pip install gunicorn 40 | 41 | 42 | Usage 43 | ----- 44 | 45 | Basic usage:: 46 | 47 | $ gunicorn [OPTIONS] APP_MODULE 48 | 49 | Where ``APP_MODULE`` is of the pattern ``$(MODULE_NAME):$(VARIABLE_NAME)``. The 50 | module name can be a full dotted path. The variable name refers to a WSGI 51 | callable that should be found in the specified module. 52 | 53 | Example with test app:: 54 | 55 | $ cd examples 56 | $ gunicorn --workers=2 test:app 57 | 58 | 59 | Contributing 60 | ------------ 61 | 62 | See `our complete contributor's guide `_ for more details. 63 | 64 | 65 | License 66 | ------- 67 | 68 | Gunicorn is released under the MIT License. See the LICENSE_ file for more 69 | details. 70 | 71 | .. _Unicorn: https://bogomips.org/unicorn/ 72 | .. _`#gunicorn`: https://web.libera.chat/?channels=#gunicorn 73 | .. _`Libera.chat`: https://libera.chat/ 74 | .. _LICENSE: https://github.com/benoitc/gunicorn/blob/master/LICENSE 75 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | **Please note that public Github issues are open for everyone to see!** 6 | 7 | If you believe you are found a problem in Gunicorn software, examples or documentation, we encourage you to send your 8 | report privately via [email](mailto:security@gunicorn.org?subject=Security%20issue%20in%20Gunicorn), or via Github 9 | using the *Report a vulnerability* button in the [Security](https://github.com/benoitc/gunicorn/security) section. 10 | 11 | ## Supported Releases 12 | 13 | At this time, **only the latest release** receives any security attention whatsoever. 14 | 15 | Please target reports against :white_check_mark: or current master. Please understand that :x: will 16 | not receive further security attention. 17 | 18 | | Version | Status | 19 | | ------- | ------------------ | 20 | | 23.0.0 | :white_check_mark: | 21 | | 22.0.0 | :x: | 22 | | 21.2.0 | :x: | 23 | | 20.0.0 | :x: | 24 | | < 20.0 | :x: | 25 | 26 | ## Python Versions 27 | 28 | Gunicorn runs on Python 3.7+, we *highly recommend* the latest release of a 29 | [supported series](https://devguide.python.org/versions/) and will not prioritize issues exclusively 30 | affecting in EoL environments. 31 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{branch}.{build}' 2 | environment: 3 | matrix: 4 | - TOXENV: lint 5 | PYTHON: "C:\\Python38-x64" 6 | - TOXENV: docs-lint 7 | PYTHON: "C:\\Python38-x64" 8 | - TOXENV: pycodestyle 9 | PYTHON: "C:\\Python38-x64" 10 | # Windows cannot even import the module when they unconditionally import, see below. 11 | #- TOXENV: run-module 12 | # PYTHON: "C:\\Python38-x64" 13 | #- TOXENV: run-entrypoint 14 | # PYTHON: "C:\\Python38-x64" 15 | # Windows is not ready for testing!!! 16 | # Python's fcntl, grp, pwd, os.geteuid(), and socket.AF_UNIX are all Unix-only. 17 | #- TOXENV: py35 18 | # PYTHON: "C:\\Python35-x64" 19 | #- TOXENV: py36 20 | # PYTHON: "C:\\Python36-x64" 21 | #- TOXENV: py37 22 | # PYTHON: "C:\\Python37-x64" 23 | #- TOXENV: py38 24 | # PYTHON: "C:\\Python38-x64" 25 | #- TOXENV: py39 26 | # PYTHON: "C:\\Python39-x64" 27 | #- TOXENV: py310 28 | # PYTHON: "C:\\Python310-x64" 29 | #- TOXENV: py311 30 | # PYTHON: "C:\\Python311-x64" 31 | #- TOXENV: py312 32 | # PYTHON: "C:\\Python312-x64" 33 | matrix: 34 | allow_failures: 35 | - TOXENV: py35 36 | - TOXENV: py36 37 | - TOXENV: py37 38 | - TOXENV: py38 39 | - TOXENV: py39 40 | init: 41 | - SET "PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" 42 | install: 43 | - pip install tox 44 | build: false 45 | test_script: 46 | - tox 47 | cache: 48 | # Not including the .tox directory since it takes longer to download/extract 49 | # the cache archive than for tox to clean install from the pip cache. 50 | - '%LOCALAPPDATA%\pip\Cache -> tox.ini' 51 | notifications: 52 | - provider: Email 53 | on_build_success: false 54 | on_build_status_changed: false 55 | -------------------------------------------------------------------------------- /docs/README.rst: -------------------------------------------------------------------------------- 1 | Generate Documentation 2 | ====================== 3 | 4 | Requirements 5 | ------------ 6 | 7 | To generate documentation you need to install: 8 | 9 | - Python >= 3.7 10 | - Sphinx (https://www.sphinx-doc.org/) 11 | 12 | 13 | Generate html 14 | ------------- 15 | :: 16 | 17 | $ make html 18 | 19 | The command generates html document inside ``build/html`` dir. 20 | -------------------------------------------------------------------------------- /docs/gunicorn_ext.py: -------------------------------------------------------------------------------- 1 | import os 2 | import inspect 3 | 4 | from docutils import nodes, utils 5 | 6 | import gunicorn.config as guncfg 7 | 8 | HEAD = """\ 9 | .. Please update gunicorn/config.py instead. 10 | 11 | .. _settings: 12 | 13 | Settings 14 | ======== 15 | 16 | This is an exhaustive list of settings for Gunicorn. Some settings are only 17 | able to be set from a configuration file. The setting name is what should be 18 | used in the configuration file. The command line arguments are listed as well 19 | for reference on setting at the command line. 20 | 21 | .. note:: 22 | 23 | Settings can be specified by using environment variable 24 | ``GUNICORN_CMD_ARGS``. All available command line arguments can be used. 25 | For example, to specify the bind address and number of workers:: 26 | 27 | $ GUNICORN_CMD_ARGS="--bind=127.0.0.1 --workers=3" gunicorn app:app 28 | 29 | .. versionadded:: 19.7 30 | 31 | """ 32 | ISSUE_URI = 'https://github.com/benoitc/gunicorn/issues/%s' 33 | PULL_REQUEST_URI = 'https://github.com/benoitc/gunicorn/pull/%s' 34 | 35 | 36 | def format_settings(app): 37 | settings_file = os.path.join(app.srcdir, "settings.rst") 38 | ret = [] 39 | known_settings = sorted(guncfg.KNOWN_SETTINGS, key=lambda s: s.section) 40 | for i, s in enumerate(known_settings): 41 | if i == 0 or s.section != known_settings[i - 1].section: 42 | ret.append("%s\n%s\n\n" % (s.section, "-" * len(s.section))) 43 | ret.append(fmt_setting(s)) 44 | 45 | with open(settings_file, 'w') as settings: 46 | settings.write(HEAD) 47 | settings.write(''.join(ret)) 48 | 49 | 50 | def fmt_setting(s): 51 | if hasattr(s, "default_doc"): 52 | val = s.default_doc 53 | elif callable(s.default): 54 | val = inspect.getsource(s.default) 55 | val = "\n".join(" %s" % line for line in val.splitlines()) 56 | val = "\n\n.. code-block:: python\n\n" + val 57 | elif s.default == '': 58 | val = "``''``" 59 | else: 60 | val = "``%r``" % s.default 61 | 62 | if s.cli and s.meta: 63 | cli = " or ".join("``%s %s``" % (arg, s.meta) for arg in s.cli) 64 | elif s.cli: 65 | cli = " or ".join("``%s``" % arg for arg in s.cli) 66 | else: 67 | cli = "" 68 | 69 | out = [] 70 | out.append(".. _%s:\n" % s.name.replace("_", "-")) 71 | out.append("``%s``" % s.name) 72 | out.append("~" * (len(s.name) + 4)) 73 | out.append("") 74 | if s.cli: 75 | out.append("**Command line:** %s" % cli) 76 | out.append("") 77 | out.append("**Default:** %s" % val) 78 | out.append("") 79 | out.append(s.desc) 80 | out.append("") 81 | out.append("") 82 | return "\n".join(out) 83 | 84 | 85 | def issue_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): 86 | issue = utils.unescape(text) 87 | text = 'issue ' + issue 88 | refnode = nodes.reference(text, text, refuri=ISSUE_URI % issue) 89 | return [refnode], [] 90 | 91 | 92 | def pull_request_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): 93 | issue = utils.unescape(text) 94 | text = 'pull request ' + issue 95 | refnode = nodes.reference(text, text, refuri=PULL_REQUEST_URI % issue) 96 | return [refnode], [] 97 | 98 | 99 | def setup(app): 100 | app.connect('builder-inited', format_settings) 101 | app.add_role('issue', issue_role) 102 | app.add_role('pr', pull_request_role) 103 | -------------------------------------------------------------------------------- /docs/logo/gunicorn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/logo/gunicorn.png -------------------------------------------------------------------------------- /docs/site/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/.nojekyll -------------------------------------------------------------------------------- /docs/site/CNAME: -------------------------------------------------------------------------------- 1 | gunicorn.org 2 | -------------------------------------------------------------------------------- /docs/site/community.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Green Unicorn - Community 7 | 8 | 9 |

10 | Redirecting to here 11 |

12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/site/configuration.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Green Unicorn - Configuration 7 | 8 | 9 |

10 | Redirecting to here 11 |

12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/site/configure.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Green Unicorn - Configure 7 | 8 | 9 |

10 | Redirecting to here 11 |

12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/site/deploy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Green Unicorn - Deployment 7 | 8 | 9 |

10 | Redirecting to here 11 |

12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/site/deployment.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Green Unicorn - Deployment 7 | 8 | 9 |

10 | Redirecting to here 11 |

12 | 13 | -------------------------------------------------------------------------------- /docs/site/design.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Green Unicorn - Design 7 | 8 | 9 |

10 | Redirecting to here 11 |

12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/site/faq.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Green Unicorn - FAQ 7 | 8 | 9 |

10 | Redirecting to here 11 |

12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/site/images/about.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/about.jpg -------------------------------------------------------------------------------- /docs/site/images/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/arrow.png -------------------------------------------------------------------------------- /docs/site/images/banner-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/banner-bg.jpg -------------------------------------------------------------------------------- /docs/site/images/community.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/community.jpg -------------------------------------------------------------------------------- /docs/site/images/documents.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/documents.jpg -------------------------------------------------------------------------------- /docs/site/images/downloads.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/downloads.jpg -------------------------------------------------------------------------------- /docs/site/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/favicon.png -------------------------------------------------------------------------------- /docs/site/images/footer-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/footer-arrow.png -------------------------------------------------------------------------------- /docs/site/images/footer-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/footer-logo.jpg -------------------------------------------------------------------------------- /docs/site/images/greenbutton.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/greenbutton.jpg -------------------------------------------------------------------------------- /docs/site/images/gunicorn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/gunicorn.png -------------------------------------------------------------------------------- /docs/site/images/large_gunicorn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/large_gunicorn.png -------------------------------------------------------------------------------- /docs/site/images/logo-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/logo-bottom.png -------------------------------------------------------------------------------- /docs/site/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/logo.jpg -------------------------------------------------------------------------------- /docs/site/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/logo.png -------------------------------------------------------------------------------- /docs/site/images/redbutton.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/redbutton.jpg -------------------------------------------------------------------------------- /docs/site/images/separator.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/separator.jpg -------------------------------------------------------------------------------- /docs/site/images/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/title.png -------------------------------------------------------------------------------- /docs/site/images/user1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/site/images/user1.jpg -------------------------------------------------------------------------------- /docs/site/install.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Green Unicorn - Install 7 | 8 | 9 |

10 | Redirecting to here 11 |

12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/site/installation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Green Unicorn - Install 7 | 8 | 9 |

10 | Redirecting to here 11 |

12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/site/js/main.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | Tabs.init(); 3 | }); 4 | 5 | var Tabs = { 6 | init: function(){ 7 | var activateTab = function ($tab) { 8 | var // this links tabs set 9 | $tabs = $tab.parents('.tabs'), 10 | // currently active tab 11 | activeTab = { 12 | 'tab' : $tabs.find('ul').children('li.active'), 13 | 'content' : $tabs.find('div[data-tab].active') 14 | }, 15 | // newly clicked tab 16 | newTab = { 17 | 'tab' : $tab.parent('li'), 18 | 'content' : $tabs.find('[data-tab=' + $tab.attr('href').replace('#', '') + ']') 19 | }, 20 | x, y; 21 | 22 | // remove active class from tab and content 23 | for (x in activeTab) { 24 | activeTab[x].removeClass('active'); 25 | } 26 | 27 | // add active class to tab and content 28 | for (y in newTab) { 29 | newTab[y].addClass('active'); 30 | } 31 | }; 32 | // hook up tab links 33 | $(document).on('click', '.tabs ul li a', function(e) { 34 | activateTab($(this)); 35 | //alert($(this)); 36 | }); 37 | 38 | // hook up initial load active tab 39 | if (window.location.hash) { 40 | var $activeTab = $('a[href="' + window.location.hash + '"]'); 41 | if ($activeTab.length && $activeTab.parents('.tabs').length) { 42 | activateTab($activeTab); 43 | } 44 | } 45 | } 46 | }; -------------------------------------------------------------------------------- /docs/site/news.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Green Unicorn - News 7 | 8 | 9 |

10 | Redirecting to here 11 |

12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/site/run.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Green Unicorn - Run 7 | 8 | 9 |

10 | Redirecting to here 11 |

12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/site/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | http://gunicorn.org/ 5 | 2019-11-27T00:02:48+01:00 6 | 1.0 7 | 8 | 9 | http://gunicorn.org/community.html 10 | 2012-10-04T00:43:15+05:45 11 | 0.5 12 | 13 | 14 | http://gunicorn.org/configuration.html 15 | 2012-10-04T00:43:15+05:45 16 | 0.5 17 | 18 | 19 | http://gunicorn.org/configure.html 20 | 2012-10-04T00:43:15+05:45 21 | 0.5 22 | 23 | 24 | http://gunicorn.org/deploy.html 25 | 2012-10-04T00:43:15+05:45 26 | 0.5 27 | 28 | 29 | http://gunicorn.org/deployment.html 30 | 2012-10-04T00:43:15+05:45 31 | 0.5 32 | 33 | 34 | http://gunicorn.org/design.html 35 | 2012-10-04T00:43:15+05:45 36 | 0.5 37 | 38 | 39 | http://gunicorn.org/faq.html 40 | 2012-10-04T00:43:15+05:45 41 | 0.5 42 | 43 | 44 | http://gunicorn.org/install.html 45 | 2012-10-04T00:43:15+05:45 46 | 0.5 47 | 48 | 49 | http://gunicorn.org/installation.html 50 | 2012-10-04T00:43:15+05:45 51 | 0.5 52 | 53 | 54 | http://gunicorn.org/news.html 55 | 2012-10-04T00:43:15+05:45 56 | 0.5 57 | 58 | 59 | http://gunicorn.org/run.html 60 | 2012-10-04T00:43:15+05:45 61 | 0.5 62 | 63 | 64 | http://gunicorn.org/tuning.html 65 | 2012-10-04T00:43:15+05:45 66 | 0.5 67 | 68 | 69 | http://gunicorn.org/usage.html 70 | 2012-10-04T00:43:15+05:45 71 | 0.5 72 | 73 | 74 | -------------------------------------------------------------------------------- /docs/site/tuning.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Green Unicorn - FAQ 7 | 8 | 9 |

10 | Redirecting to here 11 |

12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/site/usage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Green Unicorn - Run 7 | 8 | 9 |

10 | Redirecting to here 11 |

12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/sitemap_gen.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | from xml.etree import ElementTree 4 | 5 | 6 | def main(): 7 | generate( 8 | site_path=os.path.join(os.path.dirname(__file__), 'site'), 9 | special_priorities={'index.html': 1.0}) 10 | 11 | 12 | def generate(site_path, special_priorities, directory_index='index.html'): 13 | urlset = ElementTree.Element('urlset', xmlns='http://www.sitemaps.org/schemas/sitemap/0.9') 14 | urlset.text = '\n ' 15 | for root, dirs, filenames in os.walk(site_path): 16 | for filename in filenames: 17 | if filename.endswith('.html'): 18 | absolute_filepath = os.path.join(root, filename) 19 | relative_path = os.path.relpath(absolute_filepath, site_path) 20 | relative_url = os.path.dirname(relative_path) if filename == directory_index else relative_path 21 | last_modification = subprocess.check_output( 22 | ['git', 'log', '-1', '--pretty="%cI"', absolute_filepath]).decode('ascii').strip('\n"') 23 | url_element = ElementTree.SubElement(urlset, 'url') 24 | loc_element = ElementTree.SubElement(url_element, 'loc') 25 | loc_element.text = 'http://gunicorn.org/' + relative_url 26 | lastmod_element = ElementTree.SubElement(url_element, 'lastmod') 27 | lastmod_element.text = last_modification 28 | priority_element = ElementTree.SubElement(url_element, 'priority') 29 | priority_element.text = str(special_priorities.get(relative_path, 0.5)) 30 | url_element.tail = priority_element.tail = '\n ' 31 | url_element.text = loc_element.tail = lastmod_element.tail = '\n ' 32 | # We sort the url nodes instead of the filenames because 33 | # filenames might be altered by the directory_index option 34 | urlset[:] = sorted([url for url in urlset], key=lambda url: url[0].text) 35 | urlset.tail = urlset[-1].tail = '\n' 36 | with open(os.path.join(site_path, 'sitemap.xml'), 'wb') as sitemap_file: 37 | ElementTree.ElementTree(urlset).write(sitemap_file, encoding='UTF-8', xml_declaration=True) 38 | 39 | 40 | if __name__ == '__main__': 41 | main() 42 | -------------------------------------------------------------------------------- /docs/source/2011-news.rst: -------------------------------------------------------------------------------- 1 | Changelog - 2011 2 | ================ 3 | 4 | 0.13.4 / 2011-09-23 5 | ------------------- 6 | 7 | - fix util.closerange function used to prevent leaking fds on python 2.5 8 | (typo) 9 | 10 | 0.13.3 / 2011-09-19 11 | ------------------- 12 | - refactor gevent worker 13 | - prevent leaking fds on reexec 14 | - fix inverted request_time computation 15 | 16 | 0.13.2 / 2011-09-17 17 | ------------------- 18 | 19 | - Add support for Tornado 2.0 in tornado worker 20 | - Improve access logs: allows customisation of the log format & add 21 | request time 22 | - Logger module is now pluggable 23 | - Improve graceful shutdown in Python versions >= 2.6 24 | - Fix post_request root arity for compatibility 25 | - Fix sendfile support 26 | - Fix Django reloading 27 | 28 | 0.13.1 / 2011-08-22 29 | ------------------- 30 | 31 | - Fix unix socket. log argument was missing. 32 | 33 | 0.13.0 / 2011-08-22 34 | ------------------- 35 | 36 | - Improve logging: allows file-reopening and add access log file 37 | compatible with the `apache combined log format `_ 38 | - Add the possibility to set custom SSL headers. X-Forwarded-Protocol 39 | and X-Forwarded-SSL are still the default 40 | - New `on_reload` hook to customize how gunicorn spawn new workers on 41 | SIGHUP 42 | - Handle projects with relative path in django_gunicorn command 43 | - Preserve path parameters in PATH_INFO 44 | - post_request hook now accepts the environ as argument. 45 | - When stopping the arbiter, close the listener asap. 46 | - Fix Django command `run_gunicorn` in settings reloading 47 | - Fix Tornado_ worker exiting 48 | - Fix the use of sendfile in wsgi.file_wrapper 49 | 50 | 51 | 0.12.2 / 2011-05-18 52 | ------------------- 53 | 54 | - Add wsgi.file_wrapper optimised for FreeBSD, Linux & MacOSX (use 55 | sendfile if available) 56 | - Fix django run_gunicorn command. Make sure we reload the application 57 | code. 58 | - Fix django localisation 59 | - Compatible with gevent 0.14dev 60 | 61 | 0.12.1 / 2011-03-23 62 | ------------------- 63 | 64 | - Add "on_starting" hook. This hook can be used to set anything before 65 | the arbiter really start 66 | - Support bdist_rpm in setup 67 | - Improve content-length handling (pep 3333) 68 | - Improve Django support 69 | - Fix daemonizing (#142) 70 | - Fix ipv6 handling 71 | 72 | 73 | .. _Tornado: http://www.tornadoweb.org/ 74 | -------------------------------------------------------------------------------- /docs/source/2016-news.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | Changelog - 2016 3 | ================ 4 | 5 | .. note:: 6 | 7 | Please see :doc:`news` for the latest changes 8 | 9 | 19.6.0 / 2016/05/21 10 | =================== 11 | 12 | Core & Logging 13 | ++++++++++++++ 14 | 15 | - improvement of the binary upgrade behaviour using USR2: remove file locking (:issue:`1270`) 16 | - add the ``--capture-output`` setting to capture stdout/stderr tot the log 17 | file (:issue:`1271`) 18 | - Allow disabling ``sendfile()`` via the ``SENDFILE`` environment variable 19 | (:issue:`1252`) 20 | - fix reload under pycharm (:issue:`1129`) 21 | 22 | Workers 23 | +++++++ 24 | 25 | - fix: make sure to remove the signal from the worker pipe (:issue:`1269`) 26 | - fix: **gthread** worker, handle removed socket in the select loop 27 | (:issue:`1258`) 28 | 29 | 19.5.0 / 2016/05/10 30 | =================== 31 | 32 | Core 33 | ++++ 34 | 35 | - fix: Ensure response to HEAD request won't have message body 36 | - fix: lock domain socket and remove on last arbiter exit (:issue:`1220`) 37 | - improvement: use EnvironmentError instead of socket.error (:issue:`939`) 38 | - add: new ``FORWARDED_ALLOW_IPS`` environment variable (:issue:`1205`) 39 | - fix: infinite recursion when destroying sockets (:issue:`1219`) 40 | - fix: close sockets on shutdown (:issue:`922`) 41 | - fix: clean up sys.exc_info calls to drop circular refs (:issue:`1228`) 42 | - fix: do post_worker_init after load_wsgi (:issue:`1248`) 43 | 44 | Workers 45 | +++++++ 46 | 47 | - fix access logging in gaiohttp worker (:issue:`1193`) 48 | - eventlet: handle QUIT in a new coroutine (:issue:`1217`) 49 | - gevent: remove obsolete exception clauses in run (:issue:`1218`) 50 | - tornado: fix extra "Server" response header (:issue:`1246`) 51 | - fix: unblock the wait loop under python 3.5 in sync worker (:issue:`1256`) 52 | 53 | Logging 54 | +++++++ 55 | 56 | - fix: log message for listener reloading (:issue:`1181`) 57 | - Let logging module handle traceback printing (:issue:`1201`) 58 | - improvement: Allow configuring logger_class with statsd_host (:issue:`1188`) 59 | - fix: traceback formatting (:issue:`1235`) 60 | - fix: print error logs on stderr and access logs on stdout (:issue:`1184`) 61 | 62 | 63 | Documentation 64 | +++++++++++++ 65 | 66 | - Simplify installation instructions in gunicorn.org (:issue:`1072`) 67 | - Fix URL and default worker type in example_config (:issue:`1209`) 68 | - update django doc url to 1.8 lts (:issue:`1213`) 69 | - fix: miscellaneous wording corrections (:issue:`1216`) 70 | - Add PSF License Agreement of selectors.py to NOTICE (:issue: `1226`) 71 | - document LOGGING overriding (:issue:`1051`) 72 | - put a note that error logs are only errors from Gunicorn (:issue:`1124`) 73 | - add a note about the requirements of the threads workers under python 2.x (:issue:`1200`) 74 | - add access_log_format to config example (:issue:`1251`) 75 | 76 | Tests 77 | +++++ 78 | 79 | - Use more pytest.raises() in test_http.py 80 | 81 | 82 | 19.4.5 / 2016/01/05 83 | =================== 84 | 85 | - fix: NameError fileno in gunicorn.http.wsgi (:issue:`1178`) 86 | 87 | 19.4.4 / 2016/01/04 88 | =================== 89 | 90 | - fix: check if a fileobject can be used with sendfile(2) (:issue:`1174`) 91 | - doc: be more descriptive in errorlog option (:issue:`1173`) 92 | -------------------------------------------------------------------------------- /docs/source/2017-news.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | Changelog - 2017 3 | ================ 4 | 5 | .. note:: 6 | 7 | Please see :doc:`news` for the latest changes 8 | 9 | 19.7.1 / 2017/03/21 10 | =================== 11 | 12 | - fix: continue if SO_REUSEPORT seems to be available but fails (:issue:`1480`) 13 | - fix: support non-decimal values for the umask command line option (:issue:`1325`) 14 | 15 | 19.7.0 / 2017/03/01 16 | =================== 17 | 18 | - The previously deprecated ``gunicorn_django`` command has been removed. 19 | Use the :ref:`gunicorn-cmd` command-line interface instead. 20 | - The previously deprecated ``django_settings`` setting has been removed. 21 | Use the :ref:`raw-env` setting instead. 22 | - The default value of :ref:`ssl-version` has been changed from 23 | ``ssl.PROTOCOL_TLSv1`` to ``ssl.PROTOCOL_SSLv23``. 24 | - fix: initialize the group access list when initgroups is set (:issue:`1297`) 25 | - add environment variables to gunicorn access log format (:issue:`1291`) 26 | - add --paste-global-conf option (:issue:`1304`) 27 | - fix: print access logs to STDOUT (:issue:`1184`) 28 | - remove upper limit on max header size config (:issue:`1313`) 29 | - fix: print original exception on AppImportError (:issue:`1334`) 30 | - use SO_REUSEPORT if available (:issue:`1344`) 31 | - `fix leak `_ of duplicate file descriptor for bound sockets. 32 | - add --reload-engine option, support inotify and other backends (:issue:`1368`, :issue:`1459`) 33 | - fix: reject request with invalid HTTP versions 34 | - add ``child_exit`` callback (:issue:`1394`) 35 | - add support for eventlets _AlreadyHandled object (:issue:`1406`) 36 | - format boot tracebacks properly with reloader (:issue:`1408`) 37 | - refactor socket activation and fd inheritance for better support of SystemD (:issue:`1310`) 38 | - fix: o fds are given by default in gunicorn (:issue:`1423`) 39 | - add ability to pass settings to GUNICORN_CMD_ARGS environment variable which helps in container world (:issue:`1385`) 40 | - fix: catch access denied to pid file (:issue:`1091`) 41 | - many additions and improvements to the documentation 42 | 43 | Breaking Change 44 | +++++++++++++++ 45 | 46 | - **Python 2.6.0** is the last supported version 47 | -------------------------------------------------------------------------------- /docs/source/2018-news.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | Changelog - 2018 3 | ================ 4 | 5 | .. note:: 6 | 7 | Please see :doc:`news` for the latest changes 8 | 9 | 19.9.0 / 2018/07/03 10 | =================== 11 | 12 | - fix: address a regression that prevented syslog support from working 13 | (:issue:`1668`, :pr:`1773`) 14 | - fix: correctly set `REMOTE_ADDR` on versions of Python 3 affected by 15 | `Python Issue 30205 `_ 16 | (:issue:`1755`, :pr:`1796`) 17 | - fix: show zero response length correctly in access log (:pr:`1787`) 18 | - fix: prevent raising :exc:`AttributeError` when ``--reload`` is not passed 19 | in case of a :exc:`SyntaxError` raised from the WSGI application. 20 | (:issue:`1805`, :pr:`1806`) 21 | - The internal module ``gunicorn.workers.async`` was renamed to ``gunicorn.workers.base_async`` 22 | since ``async`` is now a reserved word in Python 3.7. 23 | (:pr:`1527`) 24 | 25 | 19.8.1 / 2018/04/30 26 | =================== 27 | 28 | - fix: secure scheme headers when bound to a unix socket 29 | (:issue:`1766`, :pr:`1767`) 30 | 31 | 19.8.0 / 2018/04/28 32 | =================== 33 | 34 | - Eventlet 0.21.0 support (:issue:`1584`) 35 | - Tornado 5 support (:issue:`1728`, :pr:`1752`) 36 | - support watching additional files with ``--reload-extra-file`` 37 | (:pr:`1527`) 38 | - support configuring logging with a dictionary with ``--logging-config-dict`` 39 | (:issue:`1087`, :pr:`1110`, :pr:`1602`) 40 | - add support for the ``--config`` flag in the ``GUNICORN_CMD_ARGS`` environment 41 | variable (:issue:`1576`, :pr:`1581`) 42 | - disable ``SO_REUSEPORT`` by default and add the ``--reuse-port`` setting 43 | (:issue:`1553`, :issue:`1603`, :pr:`1669`) 44 | - fix: installing `inotify` on MacOS no longer breaks the reloader 45 | (:issue:`1540`, :pr:`1541`) 46 | - fix: do not throw ``TypeError`` when ``SO_REUSEPORT`` is not available 47 | (:issue:`1501`, :pr:`1491`) 48 | - fix: properly decode HTTP paths containing certain non-ASCII characters 49 | (:issue:`1577`, :pr:`1578`) 50 | - fix: remove whitespace when logging header values under gevent (:pr:`1607`) 51 | - fix: close unlinked temporary files (:issue:`1327`, :pr:`1428`) 52 | - fix: parse ``--umask=0`` correctly (:issue:`1622`, :pr:`1632`) 53 | - fix: allow loading applications using relative file paths 54 | (:issue:`1349`, :pr:`1481`) 55 | - fix: force blocking mode on the gevent sockets (:issue:`880`, :pr:`1616`) 56 | - fix: preserve leading `/` in request path (:issue:`1512`, :pr:`1511`) 57 | - fix: forbid contradictory secure scheme headers 58 | - fix: handle malformed basic authentication headers in access log 59 | (:issue:`1683`, :pr:`1684`) 60 | - fix: defer handling of ``USR1`` signal to a new greenlet under gevent 61 | (:issue:`1645`, :pr:`1651`) 62 | - fix: the threaded worker would sometimes close the wrong keep-alive 63 | connection under Python 2 (:issue:`1698`, :pr:`1699`) 64 | - fix: re-open log files on ``USR1`` signal using ``handler._open`` to 65 | support subclasses of ``FileHandler`` (:issue:`1739`, :pr:`1742`) 66 | - deprecation: the ``gaiohttp`` worker is deprecated, see the 67 | :ref:`worker-class` documentation for more information 68 | (:issue:`1338`, :pr:`1418`, :pr:`1569`) -------------------------------------------------------------------------------- /docs/source/2020-news.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | Changelog - 2020 3 | ================ 4 | 5 | .. note:: 6 | 7 | Please see :doc:`news` for the latest changes 8 | -------------------------------------------------------------------------------- /docs/source/2021-news.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | Changelog - 2021 3 | ================ 4 | 5 | .. note:: 6 | 7 | Please see :doc:`news` for the latest changes 8 | 9 | 20.1.0 - 2021-02-12 10 | =================== 11 | 12 | - document WEB_CONCURRENCY is set by, at least, Heroku 13 | - capture peername from accept: Avoid calls to getpeername by capturing the peer name returned by 14 | accept 15 | - log a warning when a worker was terminated due to a signal 16 | - fix tornado usage with latest versions of Django 17 | - add support for python -m gunicorn 18 | - fix systemd socket activation example 19 | - allows to set wsgi application in config file using `wsgi_app` 20 | - document `--timeout = 0` 21 | - always close a connection when the number of requests exceeds the max requests 22 | - Disable keepalive during graceful shutdown 23 | - kill tasks in the gthread workers during upgrade 24 | - fix latency in gevent worker when accepting new requests 25 | - fix file watcher: handle errors when new worker reboot and ensure the list of files is kept 26 | - document the default name and path of the configuration file 27 | - document how variable impact configuration 28 | - document the `$PORT` environment variable 29 | - added milliseconds option to request_time in access_log 30 | - added PIP requirements to be used for example 31 | - remove version from the Server header 32 | - fix sendfile: use `socket.sendfile` instead of `os.sendfile` 33 | - reloader: use absolute path to prevent empty to prevent0 `InotifyError` when a file 34 | is added to the working directory 35 | - Add --print-config option to print the resolved settings at startup. 36 | - remove the `--log-dict-config` CLI flag because it never had a working format 37 | (the `logconfig_dict` setting in configuration files continues to work) 38 | 39 | 40 | ** Breaking changes ** 41 | 42 | - minimum version is Python 3.5 43 | - remove version from the Server header 44 | 45 | ** Documentation ** 46 | 47 | 48 | 49 | ** Others ** 50 | 51 | - miscellaneous changes in the code base to be a better citizen with Python 3 52 | - remove dead code 53 | - fix documentation generation 54 | 55 | -------------------------------------------------------------------------------- /docs/source/2023-news.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | Changelog - 2023 3 | ================ 4 | 5 | 21.2.0 - 2023-07-19 6 | =================== 7 | 8 | - fix thread worker: revert change considering connection as idle . 9 | 10 | *** NOTE *** 11 | 12 | This is fixing the bad file description error. 13 | 14 | 21.1.0 - 2023-07-18 15 | =================== 16 | 17 | - fix thread worker: fix socket removal from the queue 18 | 19 | 21.0.1 - 2023-07-17 20 | =================== 21 | 22 | - fix documentation build 23 | 24 | 21.0.0 - 2023-07-17 25 | =================== 26 | 27 | - support python 3.11 28 | - fix gevent and eventlet workers 29 | - fix threads support (gththread): improve performance and unblock requests 30 | - SSL: now use SSLContext object 31 | - HTTP parser: miscellaneous fixes 32 | - remove unnecessary setuid calls 33 | - fix testing 34 | - improve logging 35 | - miscellaneous fixes to core engine 36 | 37 | *** RELEASE NOTE *** 38 | 39 | We made this release major to start our new release cycle. More info will be provided on our discussion forum. 40 | -------------------------------------------------------------------------------- /docs/source/_static/gunicorn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/docs/source/_static/gunicorn.png -------------------------------------------------------------------------------- /docs/source/community.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Community 3 | ========= 4 | 5 | Use these channels to communicate about the project. 6 | 7 | Project Management & Discussions 8 | ================================ 9 | 10 | Gunicorn uses `GitHub for the project management `_. GitHub issues are used 11 | for 3 different purposes: 12 | 13 | * `Bug tracker `_ : to check latest bug 14 | * `Forum `_ : Stackoverflow-style questions about Gunicorn usage 15 | * `Mailing list `_ : Discussion of Gunicorn development, new features 16 | and project management. 17 | 18 | Project maintenance guidelines are available on the `wiki `_ 19 | . 20 | 21 | IRC 22 | === 23 | 24 | The Gunicorn channel is on the `Freenode `_ IRC 25 | network. You can chat with other on `#gunicorn channel 26 | `_. 27 | 28 | Issue Tracking 29 | ============== 30 | 31 | Bug reports, enhancement requests and tasks generally go in the `Github 32 | issue tracker `_. 33 | 34 | Security Issues 35 | =============== 36 | 37 | The security mailing list is a place to report security issues. Only 38 | developers are subscribed to it. To post a message to the list use the address 39 | to `security@gunicorn.org `_ . 40 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # 2 | # Gunicorn documentation build configuration file 3 | # 4 | 5 | import os 6 | import sys 7 | import time 8 | 9 | DOCS_DIR = os.path.abspath(os.path.dirname(__file__)) 10 | 11 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 12 | 13 | # for gunicorn_ext.py 14 | sys.path.append(os.path.join(DOCS_DIR, os.pardir)) 15 | sys.path.insert(0, os.path.join(DOCS_DIR, os.pardir, os.pardir)) 16 | 17 | extensions = ['gunicorn_ext'] 18 | templates_path = ['_templates'] 19 | source_suffix = '.rst' 20 | master_doc = 'index' 21 | 22 | # General information about the project. 23 | project = 'Gunicorn' 24 | copyright = '2009-%s, Benoit Chesneau' % time.strftime('%Y') 25 | # gunicorn version 26 | import gunicorn 27 | release = version = gunicorn.__version__ 28 | 29 | exclude_patterns = [] 30 | pygments_style = 'sphinx' 31 | 32 | 33 | # -- Options for HTML output --------------------------------------------------- 34 | 35 | if not on_rtd: # only import and set the theme if we're building docs locally 36 | try: 37 | import sphinx_rtd_theme 38 | except ImportError: 39 | html_theme = 'default' 40 | else: 41 | html_theme = 'sphinx_rtd_theme' 42 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 43 | else: 44 | html_theme = 'default' 45 | 46 | html_static_path = ['_static'] 47 | htmlhelp_basename = 'Gunicorndoc' 48 | 49 | 50 | # -- Options for LaTeX output -------------------------------------------------- 51 | 52 | latex_elements = { 53 | 54 | } 55 | 56 | latex_documents = [ 57 | ('index', 'Gunicorn.tex', 'Gunicorn Documentation', 58 | 'Benoit Chesneau', 'manual'), 59 | ] 60 | 61 | 62 | # -- Options for manual page output -------------------------------------------- 63 | man_pages = [ 64 | ('index', 'gunicorn', 'Gunicorn Documentation', 65 | ['Benoit Chesneau'], 1) 66 | ] 67 | 68 | texinfo_documents = [ 69 | ('index', 'Gunicorn', 'Gunicorn Documentation', 70 | 'Benoit Chesneau', 'Gunicorn', 'One line description of project.', 71 | 'Miscellaneous'), 72 | ] 73 | -------------------------------------------------------------------------------- /docs/source/custom.rst: -------------------------------------------------------------------------------- 1 | .. _custom: 2 | 3 | ================== 4 | Custom Application 5 | ================== 6 | 7 | .. versionadded:: 19.0 8 | 9 | Sometimes, you want to integrate Gunicorn with your WSGI application. In this 10 | case, you can inherit from :class:`gunicorn.app.base.BaseApplication`. 11 | 12 | Here is a small example where we create a very small WSGI app and load it with 13 | a custom Application: 14 | 15 | .. literalinclude:: ../../examples/standalone_app.py 16 | :start-after: # See the NOTICE for more information 17 | :lines: 2- 18 | 19 | Using server hooks 20 | ------------------ 21 | 22 | If you wish to include server hooks in your custom application, you can specify a function in the config options. Here is an example with the `pre_fork` hook: 23 | 24 | .. code-block:: python 25 | 26 | def pre_fork(server, worker): 27 | print(f"pre-fork server {server} worker {worker}", file=sys.stderr) 28 | 29 | # ... 30 | if __name__ == '__main__': 31 | options = { 32 | 'bind': '%s:%s' % ('127.0.0.1', '8080'), 33 | 'workers': number_of_workers(), 34 | 'pre_fork': pre_fork, 35 | } 36 | 37 | 38 | Direct Usage of Existing WSGI Apps 39 | ---------------------------------- 40 | 41 | If necessary, you can run Gunicorn straight from Python, allowing you to 42 | specify a WSGI-compatible application at runtime. This can be handy for 43 | rolling deploys or in the case of using PEX files to deploy your application, 44 | as the app and Gunicorn can be bundled in the same PEX file. Gunicorn has 45 | this functionality built-in as a first class citizen known as 46 | :class:`gunicorn.app.wsgiapp`. This can be used to run WSGI-compatible app 47 | instances such as those produced by Flask or Django. Assuming your WSGI API 48 | package is *exampleapi*, and your application instance is *app*, this is all 49 | you need to get going:: 50 | 51 | gunicorn.app.wsgiapp exampleapi:app 52 | 53 | This command will work with any Gunicorn CLI parameters or a config file - just 54 | pass them along as if you're directly giving them to Gunicorn: 55 | 56 | .. code-block:: bash 57 | 58 | # Custom parameters 59 | $ python gunicorn.app.wsgiapp exampleapi:app --bind=0.0.0.0:8081 --workers=4 60 | # Using a config file 61 | $ python gunicorn.app.wsgiapp exampleapi:app -c config.py 62 | 63 | Note for those using PEX: use ``-c gunicorn`` as your entry at build 64 | time, and your compiled app should work with the entry point passed to it at 65 | run time. 66 | 67 | .. code-block:: bash 68 | 69 | # Generic pex build command via bash from root of exampleapi project 70 | $ pex . -v -c gunicorn -o compiledapp.pex 71 | # Running it 72 | ./compiledapp.pex exampleapi:app -c gunicorn_config.py 73 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Gunicorn - WSGI server 3 | ====================== 4 | 5 | .. image:: _static/gunicorn.png 6 | 7 | :Website: http://gunicorn.org 8 | :Source code: https://github.com/benoitc/gunicorn 9 | :Issue tracker: https://github.com/benoitc/gunicorn/issues 10 | :IRC: ``#gunicorn`` on Libera Chat 11 | :Usage questions: https://github.com/benoitc/gunicorn/issues 12 | 13 | Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It's a pre-fork 14 | worker model ported from Ruby's Unicorn project. The Gunicorn server is broadly 15 | compatible with various web frameworks, simply implemented, light on server 16 | resources, and fairly speedy. 17 | 18 | Features 19 | -------- 20 | 21 | * Natively supports WSGI, Django, and Paster 22 | * Automatic worker process management 23 | * Simple Python configuration 24 | * Multiple worker configurations 25 | * Various server hooks for extensibility 26 | * Compatible with Python 3.x >= 3.7 27 | 28 | 29 | Contents 30 | -------- 31 | 32 | .. toctree:: 33 | :maxdepth: 2 34 | 35 | install 36 | run 37 | configure 38 | settings 39 | instrumentation 40 | deploy 41 | signals 42 | custom 43 | design 44 | faq 45 | community 46 | news 47 | -------------------------------------------------------------------------------- /docs/source/instrumentation.rst: -------------------------------------------------------------------------------- 1 | .. _instrumentation: 2 | 3 | =============== 4 | Instrumentation 5 | =============== 6 | 7 | .. versionadded:: 19.1 8 | 9 | Gunicorn provides an optional instrumentation of the arbiter and 10 | workers using the statsD_ protocol over UDP. Thanks to the 11 | ``gunicorn.instrument.statsd`` module, Gunicorn becomes a statsD client. 12 | The use of UDP cleanly isolates Gunicorn from the receiving end of the statsD 13 | metrics so that instrumentation does not cause Gunicorn to be held up by a slow 14 | statsD consumer. 15 | 16 | To use statsD, just tell Gunicorn where the statsD server is: 17 | 18 | .. code-block:: bash 19 | 20 | $ gunicorn --statsd-host=localhost:8125 --statsd-prefix=service.app ... 21 | 22 | The ``Statsd`` logger overrides ``gunicorn.glogging.Logger`` to track 23 | all requests. The following metrics are generated: 24 | 25 | * ``gunicorn.requests``: request rate per second 26 | * ``gunicorn.request.duration``: histogram of request duration (in millisecond) 27 | * ``gunicorn.workers``: number of workers managed by the arbiter (gauge) 28 | * ``gunicorn.log.critical``: rate of critical log messages 29 | * ``gunicorn.log.error``: rate of error log messages 30 | * ``gunicorn.log.warning``: rate of warning log messages 31 | * ``gunicorn.log.exception``: rate of exceptional log messages 32 | 33 | See the statsd-host_ setting for more information. 34 | 35 | .. _statsd-host: settings.html#statsd-host 36 | .. _statsD: https://github.com/etsy/statsd 37 | -------------------------------------------------------------------------------- /examples/alt_spec.py: -------------------------------------------------------------------------------- 1 | # 2 | # An example of how to pass information from the command line to 3 | # a WSGI app. Only applies to the native WSGI workers used by 4 | # Gunicorn sync (default) workers. 5 | # 6 | # $ gunicorn 'alt_spec:load(arg)' 7 | # 8 | # Single quoting is generally necessary for shell escape semantics. 9 | # 10 | # This file is part of gunicorn released under the MIT license. 11 | # See the NOTICE for more information. 12 | 13 | def load(arg): 14 | def app(environ, start_response): 15 | data = b'Hello, %s!\n' % arg 16 | status = '200 OK' 17 | response_headers = [ 18 | ('Content-type', 'text/plain'), 19 | ('Content-Length', str(len(data))) 20 | ] 21 | start_response(status, response_headers) 22 | return iter([data]) 23 | return app 24 | -------------------------------------------------------------------------------- /examples/bad.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | files = [] 3 | def app(environ, start_response): 4 | files.append(tempfile.mkstemp()) 5 | start_response('200 OK', [('Content-type', 'text/plain'), ('Content-length', '2')]) 6 | return ['ok'] 7 | -------------------------------------------------------------------------------- /examples/boot_fail.py: -------------------------------------------------------------------------------- 1 | raise RuntimeError("Bad app!") 2 | 3 | def app(environ, start_response): 4 | assert 1 == 2, "Shouldn't get here." 5 | -------------------------------------------------------------------------------- /examples/deep/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/examples/deep/__init__.py -------------------------------------------------------------------------------- /examples/deep/test.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | # 5 | # Example code from Eventlet sources 6 | 7 | from wsgiref.validate import validator 8 | 9 | from gunicorn import __version__ 10 | 11 | 12 | @validator 13 | def app(environ, start_response): 14 | """Simplest possible application object""" 15 | 16 | data = b'Hello, World!\n' 17 | status = '200 OK' 18 | 19 | response_headers = [ 20 | ('Content-type', 'text/plain'), 21 | ('Content-Length', str(len(data))), 22 | ('X-Gunicorn-Version', __version__), 23 | ('Foo', 'B\u00e5r'), # Foo: Bår 24 | ] 25 | start_response(status, response_headers) 26 | return iter([data]) 27 | -------------------------------------------------------------------------------- /examples/echo.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | # 5 | # Example code from Eventlet sources 6 | 7 | from gunicorn import __version__ 8 | 9 | 10 | def app(environ, start_response): 11 | """Simplest possible application object""" 12 | 13 | if environ['REQUEST_METHOD'].upper() != 'POST': 14 | data = b'Hello, World!\n' 15 | else: 16 | data = environ['wsgi.input'].read() 17 | 18 | status = '200 OK' 19 | 20 | response_headers = [ 21 | ('Content-type', 'text/plain'), 22 | ('Content-Length', str(len(data))), 23 | ('X-Gunicorn-Version', __version__) 24 | ] 25 | start_response(status, response_headers) 26 | return iter([data]) 27 | -------------------------------------------------------------------------------- /examples/frameworks/cherryapp.py: -------------------------------------------------------------------------------- 1 | import cherrypy 2 | 3 | 4 | class Root: 5 | @cherrypy.expose 6 | def index(self): 7 | return 'Hello World!' 8 | 9 | cherrypy.config.update({'environment': 'embedded'}) 10 | 11 | app = cherrypy.tree.mount(Root()) 12 | -------------------------------------------------------------------------------- /examples/frameworks/django/README: -------------------------------------------------------------------------------- 1 | Applications to test Django support: 2 | 3 | testing -> Django 1.4 4 | -------------------------------------------------------------------------------- /examples/frameworks/django/testing/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os, sys 3 | 4 | if __name__ == "__main__": 5 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testing.settings") 6 | 7 | from django.core.management import execute_from_command_line 8 | 9 | execute_from_command_line(sys.argv) 10 | -------------------------------------------------------------------------------- /examples/frameworks/django/testing/testing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/examples/frameworks/django/testing/testing/__init__.py -------------------------------------------------------------------------------- /examples/frameworks/django/testing/testing/apps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/examples/frameworks/django/testing/testing/apps/__init__.py -------------------------------------------------------------------------------- /examples/frameworks/django/testing/testing/apps/someapp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/examples/frameworks/django/testing/testing/apps/someapp/__init__.py -------------------------------------------------------------------------------- /examples/frameworks/django/testing/testing/apps/someapp/middleware.py: -------------------------------------------------------------------------------- 1 | from multiprocessing import Process, Queue 2 | import requests 3 | 4 | 5 | def child_process(queue): 6 | while True: 7 | print(queue.get()) 8 | requests.get('http://requestb.in/15s95oz1') 9 | 10 | 11 | class GunicornSubProcessTestMiddleware: 12 | def __init__(self): 13 | super().__init__() 14 | self.queue = Queue() 15 | self.process = Process(target=child_process, args=(self.queue,)) 16 | self.process.start() 17 | 18 | def process_request(self, request): 19 | self.queue.put(('REQUEST',)) 20 | 21 | def process_response(self, request, response): 22 | self.queue.put(('RESPONSE', response.status_code)) 23 | return response 24 | -------------------------------------------------------------------------------- /examples/frameworks/django/testing/testing/apps/someapp/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/examples/frameworks/django/testing/testing/apps/someapp/models.py -------------------------------------------------------------------------------- /examples/frameworks/django/testing/testing/apps/someapp/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | gunicorn django example app 6 | 20 | 21 | 22 | 23 |
24 |

test app

25 |
26 | 27 | {% block content %}{% endblock %} 28 | 29 | 30 | 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/frameworks/django/testing/testing/apps/someapp/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 | {% csrf_token %} 6 | 7 | {{ form.as_table }} 8 |
9 | 10 |
11 | 12 |

Got

13 | {% if subject %} 14 |

subject:
{{ subject}}

15 |

message:
{{ message }}

16 |

size:
{{ size }}

17 | {% endif %} 18 | {% endblock content %} 19 | -------------------------------------------------------------------------------- /examples/frameworks/django/testing/testing/apps/someapp/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates two different styles of tests (one doctest and one 3 | unittest). These will both pass when you run "manage.py test". 4 | 5 | Replace these with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | class SimpleTest(TestCase): 11 | def test_basic_addition(self): 12 | """ 13 | Tests that 1 + 1 always equals 2. 14 | """ 15 | self.assertEqual(1 + 1, 2) 16 | 17 | __test__ = {"doctest": """ 18 | Another way to test that 1 + 1 is equal to 2. 19 | 20 | >>> 1 + 1 == 2 21 | True 22 | """} 23 | -------------------------------------------------------------------------------- /examples/frameworks/django/testing/testing/apps/someapp/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | url(r'^acsv$', views.acsv), 7 | url(r'^$', views.home), 8 | ] 9 | -------------------------------------------------------------------------------- /examples/frameworks/django/testing/testing/apps/someapp/views.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import io 3 | import os 4 | from django import forms 5 | from django.http import HttpResponse 6 | from django.shortcuts import render 7 | from django.template import RequestContext 8 | 9 | 10 | class MsgForm(forms.Form): 11 | subject = forms.CharField(max_length=100) 12 | message = forms.CharField() 13 | f = forms.FileField() 14 | 15 | 16 | def home(request): 17 | from django.conf import settings 18 | print(settings.SOME_VALUE) 19 | subject = None 20 | message = None 21 | size = 0 22 | print(request.META) 23 | if request.POST: 24 | form = MsgForm(request.POST, request.FILES) 25 | print(request.FILES) 26 | if form.is_valid(): 27 | subject = form.cleaned_data['subject'] 28 | message = form.cleaned_data['message'] 29 | f = request.FILES['f'] 30 | 31 | if not hasattr(f, "fileno"): 32 | size = len(f.read()) 33 | else: 34 | try: 35 | size = int(os.fstat(f.fileno())[6]) 36 | except io.UnsupportedOperation: 37 | size = len(f.read()) 38 | else: 39 | form = MsgForm() 40 | 41 | 42 | 43 | return render(request, 'home.html', { 44 | 'form': form, 45 | 'subject': subject, 46 | 'message': message, 47 | 'size': size 48 | }) 49 | 50 | 51 | def acsv(request): 52 | rows = [ 53 | {'a': 1, 'b': 2}, 54 | {'a': 3, 'b': 3} 55 | ] 56 | 57 | response = HttpResponse(mimetype='text/csv') 58 | response['Content-Disposition'] = 'attachment; filename=report.csv' 59 | 60 | writer = csv.writer(response) 61 | writer.writerow(['a', 'b']) 62 | 63 | for r in rows: 64 | writer.writerow([r['a'], r['b']]) 65 | 66 | return response 67 | -------------------------------------------------------------------------------- /examples/frameworks/django/testing/testing/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | 3 | # Uncomment the next two lines to enable the admin: 4 | from django.contrib import admin 5 | admin.autodiscover() 6 | 7 | urlpatterns = [ 8 | # Examples: 9 | # url(r'^$', 'testing.views.home', name='home'), 10 | # url(r'^testing/', include('testing.foo.urls')), 11 | 12 | # Uncomment the admin/doc line below to enable admin documentation: 13 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 14 | 15 | # Uncomment the next line to enable the admin: 16 | url(r'^admin/', admin.site.urls), 17 | 18 | url(r'^', include("testing.apps.someapp.urls")), 19 | ] 20 | -------------------------------------------------------------------------------- /examples/frameworks/django/testing/testing/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for testing project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | import sys 18 | 19 | # make sure the current project is in PYTHONPATH 20 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 21 | ".."))) 22 | 23 | # set the environment settings 24 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testing.settings") 25 | 26 | # This application object is used by any WSGI server configured to use this 27 | # file. This includes Django's development server, if the WSGI_APPLICATION 28 | # setting points here. 29 | from django.core.wsgi import get_wsgi_application 30 | application = get_wsgi_application() 31 | 32 | # Apply WSGI middleware here. 33 | # from helloworld.wsgi import HelloWorldApplication 34 | # application = HelloWorldApplication(application) 35 | -------------------------------------------------------------------------------- /examples/frameworks/flask_sendfile.py: -------------------------------------------------------------------------------- 1 | import io 2 | 3 | from flask import Flask, send_file 4 | 5 | app = Flask(__name__) 6 | 7 | @app.route('/') 8 | def index(): 9 | buf = io.BytesIO() 10 | buf.write(b'hello world') 11 | buf.seek(0) 12 | return send_file(buf, 13 | attachment_filename="testing.txt", 14 | as_attachment=True) 15 | -------------------------------------------------------------------------------- /examples/frameworks/flaskapp.py: -------------------------------------------------------------------------------- 1 | # Run with: 2 | # 3 | # $ gunicorn flaskapp:app 4 | # 5 | 6 | from flask import Flask 7 | app = Flask(__name__) 8 | 9 | @app.route("/") 10 | def hello(): 11 | return "Hello World!" 12 | -------------------------------------------------------------------------------- /examples/frameworks/flaskapp_aiohttp_wsgi.py: -------------------------------------------------------------------------------- 1 | # Example command to run the example: 2 | # 3 | # $ gunicorn flaskapp_aiohttp_wsgi:aioapp -k aiohttp.worker.GunicornWebWorker 4 | # 5 | 6 | from aiohttp import web 7 | from aiohttp_wsgi import WSGIHandler 8 | from flask import Flask 9 | 10 | app = Flask(__name__) 11 | 12 | 13 | @app.route('/') 14 | def hello(): 15 | return 'Hello, world!' 16 | 17 | 18 | def make_aiohttp_app(app): 19 | wsgi_handler = WSGIHandler(app) 20 | aioapp = web.Application() 21 | aioapp.router.add_route('*', '/{path_info:.*}', wsgi_handler) 22 | return aioapp 23 | 24 | aioapp = make_aiohttp_app(app) 25 | -------------------------------------------------------------------------------- /examples/frameworks/pyramidapp.py: -------------------------------------------------------------------------------- 1 | from pyramid.config import Configurator 2 | from pyramid.response import Response 3 | 4 | def hello_world(request): 5 | return Response('Hello world!') 6 | 7 | def goodbye_world(request): 8 | return Response('Goodbye world!') 9 | 10 | config = Configurator() 11 | config.add_view(hello_world) 12 | config.add_view(goodbye_world, name='goodbye') 13 | app = config.make_wsgi_app() 14 | -------------------------------------------------------------------------------- /examples/frameworks/requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements_flaskapp.txt 2 | -r requirements_cherryapp.txt 3 | -r requirements_pyramidapp.txt 4 | -r requirements_tornadoapp.txt 5 | -r requirements_webpyapp.txt 6 | -------------------------------------------------------------------------------- /examples/frameworks/requirements_cherryapp.txt: -------------------------------------------------------------------------------- 1 | cherrypy 2 | -------------------------------------------------------------------------------- /examples/frameworks/requirements_flaskapp.txt: -------------------------------------------------------------------------------- 1 | flask 2 | -------------------------------------------------------------------------------- /examples/frameworks/requirements_pyramidapp.txt: -------------------------------------------------------------------------------- 1 | pyramid 2 | -------------------------------------------------------------------------------- /examples/frameworks/requirements_tornadoapp.txt: -------------------------------------------------------------------------------- 1 | tornado<6 2 | -------------------------------------------------------------------------------- /examples/frameworks/requirements_webpyapp.txt: -------------------------------------------------------------------------------- 1 | web-py 2 | -------------------------------------------------------------------------------- /examples/frameworks/tornadoapp.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | # 5 | # Run with: 6 | # 7 | # $ gunicorn -k tornado tornadoapp:app 8 | # 9 | 10 | import tornado.ioloop 11 | import tornado.web 12 | from tornado import gen 13 | 14 | class MainHandler(tornado.web.RequestHandler): 15 | @gen.coroutine 16 | def get(self): 17 | # Your asynchronous code here 18 | yield gen.sleep(1) # Example of an asynchronous operation 19 | self.write("Hello, World!") 20 | 21 | def make_app(): 22 | return tornado.web.Application([ 23 | (r"/", MainHandler), 24 | ]) 25 | 26 | if __name__ == "__main__": 27 | app = make_app() 28 | app.listen(8888) 29 | tornado.ioloop.IOLoop.current().start() 30 | -------------------------------------------------------------------------------- /examples/frameworks/webpyapp.py: -------------------------------------------------------------------------------- 1 | # Run with 2 | # 3 | # $ gunicorn webpyapp:app 4 | # 5 | 6 | import web 7 | 8 | urls = ( 9 | '/', 'index' 10 | ) 11 | 12 | class index: 13 | def GET(self): 14 | return "Hello, world!" 15 | 16 | app = web.application(urls, globals()).wsgifunc() 17 | -------------------------------------------------------------------------------- /examples/gunicorn_rc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | GUNICORN=/usr/local/bin/gunicorn 4 | ROOT=/path/to/project 5 | PID=/var/run/gunicorn.pid 6 | 7 | APP=main:application 8 | 9 | if [ -f $PID ]; then rm $PID; fi 10 | 11 | cd $ROOT 12 | exec $GUNICORN -c $ROOT/gunicorn.conf.py --pid=$PID $APP 13 | -------------------------------------------------------------------------------- /examples/hello.txt: -------------------------------------------------------------------------------- 1 | Hello world! 2 | -------------------------------------------------------------------------------- /examples/log_app.ini: -------------------------------------------------------------------------------- 1 | [app:main] 2 | paste.app_factory = log_app:app_factory 3 | 4 | [server:main] 5 | use = egg:gunicorn#main 6 | host = 127.0.0.1 7 | port = 8080 8 | workers = 3 9 | 10 | [loggers] 11 | keys=root 12 | 13 | [handlers] 14 | keys=console 15 | 16 | [formatters] 17 | keys=default 18 | 19 | [logger_root] 20 | level=INFO 21 | qualname=root 22 | handlers=console 23 | 24 | [handler_console] 25 | class=StreamHandler 26 | formatter=default 27 | args=(sys.stdout, ) 28 | 29 | [formatter_default] 30 | format=[%(asctime)s] [%(levelname)-7s] - %(process)d:%(name)s:%(funcName)s - %(message)s 31 | -------------------------------------------------------------------------------- /examples/log_app.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | log.addHandler(logging.StreamHandler()) 6 | 7 | def app_factory(global_options, **local_options): 8 | return app 9 | 10 | def app(environ, start_response): 11 | start_response("200 OK", []) 12 | log.debug("Hello Debug!") 13 | log.info("Hello Info!") 14 | log.warn("Hello Warn!") 15 | log.error("Hello Error!") 16 | return [b"Hello World!\n"] 17 | -------------------------------------------------------------------------------- /examples/logging.conf: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root, gunicorn.error, gunicorn.access 3 | 4 | [handlers] 5 | keys=console, error_file, access_file 6 | 7 | [formatters] 8 | keys=generic, access 9 | 10 | [logger_root] 11 | level=INFO 12 | handlers=console 13 | 14 | [logger_gunicorn.error] 15 | level=INFO 16 | handlers=error_file 17 | propagate=1 18 | qualname=gunicorn.error 19 | 20 | [logger_gunicorn.access] 21 | level=INFO 22 | handlers=access_file 23 | propagate=0 24 | qualname=gunicorn.access 25 | 26 | [handler_console] 27 | class=StreamHandler 28 | formatter=generic 29 | args=(sys.stdout, ) 30 | 31 | [handler_error_file] 32 | class=logging.FileHandler 33 | formatter=generic 34 | args=('/tmp/gunicorn.error.log',) 35 | 36 | [handler_access_file] 37 | class=logging.FileHandler 38 | formatter=access 39 | args=('/tmp/gunicorn.access.log',) 40 | 41 | [formatter_generic] 42 | format=%(asctime)s [%(process)d] [%(levelname)s] %(message)s 43 | datefmt=%Y-%m-%d %H:%M:%S 44 | class=logging.Formatter 45 | 46 | [formatter_access] 47 | format=%(message)s 48 | class=logging.Formatter 49 | -------------------------------------------------------------------------------- /examples/longpoll.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | 6 | import sys 7 | import time 8 | 9 | class TestIter: 10 | 11 | def __iter__(self): 12 | lines = [b'line 1\n', b'line 2\n'] 13 | for line in lines: 14 | yield line 15 | time.sleep(20) 16 | 17 | def app(environ, start_response): 18 | """Application which cooperatively pauses 20 seconds (needed to surpass normal timeouts) before responding""" 19 | status = '200 OK' 20 | response_headers = [ 21 | ('Content-type', 'text/plain'), 22 | ('Transfer-Encoding', "chunked"), 23 | ] 24 | sys.stdout.write('request received') 25 | sys.stdout.flush() 26 | start_response(status, response_headers) 27 | return TestIter() 28 | -------------------------------------------------------------------------------- /examples/multiapp.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | # 5 | # Run this application with: 6 | # 7 | # $ gunicorn multiapp:app 8 | # 9 | # And then visit: 10 | # 11 | # http://127.0.0.1:8000/app1url 12 | # http://127.0.0.1:8000/app2url 13 | # http://127.0.0.1:8000/this_is_a_404 14 | # 15 | 16 | try: 17 | from routes import Mapper 18 | except ImportError: 19 | print("This example requires Routes to be installed") 20 | 21 | # Obviously you'd import your app callables 22 | # from different places... 23 | from test import app as app1 24 | from test import app as app2 25 | 26 | 27 | class Application: 28 | def __init__(self): 29 | self.map = Mapper() 30 | self.map.connect('app1', '/app1url', app=app1) 31 | self.map.connect('app2', '/app2url', app=app2) 32 | 33 | def __call__(self, environ, start_response): 34 | match = self.map.routematch(environ=environ) 35 | if not match: 36 | return self.error404(environ, start_response) 37 | return match[0]['app'](environ, start_response) 38 | 39 | def error404(self, environ, start_response): 40 | html = b"""\ 41 | 42 | 43 | 404 - Not Found 44 | 45 | 46 |

404 - Not Found

47 | 48 | 49 | """ 50 | headers = [ 51 | ('Content-Type', 'text/html'), 52 | ('Content-Length', str(len(html))) 53 | ] 54 | start_response('404 Not Found', headers) 55 | return [html] 56 | 57 | app = Application() 58 | -------------------------------------------------------------------------------- /examples/multidomainapp.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | import re 6 | 7 | class SubDomainApp: 8 | """WSGI application to delegate requests based on domain name. 9 | """ 10 | def __init__(self, mapping): 11 | self.mapping = mapping 12 | 13 | def __call__(self, environ, start_response): 14 | host = environ.get("HTTP_HOST", "") 15 | host = host.split(":")[0] # strip port 16 | 17 | for pattern, app in self.mapping: 18 | if re.match("^" + pattern + "$", host): 19 | return app(environ, start_response) 20 | else: 21 | start_response("404 Not Found", []) 22 | return [b""] 23 | 24 | def hello(environ, start_response): 25 | start_response("200 OK", [("Content-Type", "text/plain")]) 26 | return [b"Hello, world\n"] 27 | 28 | def bye(environ, start_response): 29 | start_response("200 OK", [("Content-Type", "text/plain")]) 30 | return [b"Goodbye!\n"] 31 | 32 | app = SubDomainApp([ 33 | ("localhost", hello), 34 | (".*", bye) 35 | ]) 36 | -------------------------------------------------------------------------------- /examples/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | 3 | user nobody nogroup; 4 | # 'user nobody nobody;' for systems with 'nobody' as a group instead 5 | error_log /var/log/nginx/error.log warn; 6 | pid /var/run/nginx.pid; 7 | 8 | events { 9 | worker_connections 1024; # increase if you have lots of clients 10 | accept_mutex off; # set to 'on' if nginx worker_processes > 1 11 | # 'use epoll;' to enable for Linux 2.6+ 12 | # 'use kqueue;' to enable for FreeBSD, OSX 13 | } 14 | 15 | http { 16 | include mime.types; 17 | # fallback in case we can't determine a type 18 | default_type application/octet-stream; 19 | access_log /var/log/nginx/access.log combined; 20 | sendfile on; 21 | 22 | upstream app_server { 23 | # fail_timeout=0 means we always retry an upstream even if it failed 24 | # to return a good HTTP response 25 | 26 | # for UNIX domain socket setups 27 | server unix:/tmp/gunicorn.sock fail_timeout=0; 28 | 29 | # for a TCP configuration 30 | # server 192.168.0.7:8000 fail_timeout=0; 31 | } 32 | 33 | server { 34 | # if no Host match, close the connection to prevent host spoofing 35 | listen 80 default_server; 36 | return 444; 37 | } 38 | 39 | server { 40 | # use 'listen 80 deferred;' for Linux 41 | # use 'listen 80 accept_filter=httpready;' for FreeBSD 42 | listen 80; 43 | client_max_body_size 4G; 44 | 45 | # set the correct host(s) for your site 46 | server_name example.com www.example.com; 47 | 48 | keepalive_timeout 5; 49 | 50 | # path for static files 51 | root /path/to/app/current/public; 52 | 53 | location / { 54 | # checks for static file, if not found proxy to app 55 | try_files $uri @proxy_to_app; 56 | } 57 | 58 | location @proxy_to_app { 59 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 60 | proxy_set_header X-Forwarded-Proto $scheme; 61 | proxy_set_header Host $http_host; 62 | # we don't want nginx trying to do something clever with 63 | # redirects, we set the Host: header above already. 64 | proxy_redirect off; 65 | proxy_pass http://app_server; 66 | } 67 | 68 | error_page 500 502 503 504 /500.html; 69 | location = /500.html { 70 | root /path/to/app/current/public; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/read_django_settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Use this config file in your script like this: 3 | 4 | $ gunicorn project_name.wsgi:application -c read_django_settings.py 5 | """ 6 | 7 | settings_dict = {} 8 | 9 | with open('frameworks/django/testing/testing/settings.py') as f: 10 | exec(f.read(), settings_dict) 11 | 12 | loglevel = 'warning' 13 | proc_name = 'web-project' 14 | workers = 1 15 | 16 | if settings_dict['DEBUG']: 17 | loglevel = 'debug' 18 | reload = True 19 | proc_name += '_debug' 20 | -------------------------------------------------------------------------------- /examples/readline_app.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | # 5 | # Simple example of readline, reading from a stream then echoing the response 6 | # 7 | # Usage: 8 | # 9 | # Launch a server with the app in a terminal 10 | # 11 | # $ gunicorn -w3 readline_app:app 12 | # 13 | # Then in another terminal launch the following command: 14 | # 15 | # $ curl -XPOST -d'test\r\ntest2\r\n' -H"Transfer-Encoding: Chunked" http://localhost:8000 16 | 17 | 18 | 19 | from gunicorn import __version__ 20 | 21 | 22 | def app(environ, start_response): 23 | """Simplest possible application object""" 24 | status = '200 OK' 25 | 26 | response_headers = [ 27 | ('Content-type', 'text/plain'), 28 | ('Transfer-Encoding', "chunked"), 29 | ('X-Gunicorn-Version', __version__) 30 | ] 31 | start_response(status, response_headers) 32 | 33 | body = environ['wsgi.input'] 34 | 35 | lines = [] 36 | while True: 37 | line = body.readline() 38 | if line == b"": 39 | break 40 | print(line) 41 | lines.append(line) 42 | 43 | return iter(lines) 44 | -------------------------------------------------------------------------------- /examples/sendfile.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | # 5 | # Example code from Eventlet sources 6 | 7 | import os 8 | from wsgiref.validate import validator 9 | 10 | 11 | # @validator # breaks sendfile 12 | def app(environ, start_response): 13 | """Simplest possible application object""" 14 | status = '200 OK' 15 | fname = os.path.join(os.path.dirname(__file__), "hello.txt") 16 | f = open(fname, 'rb') 17 | 18 | response_headers = [ 19 | ('Content-type', 'text/plain'), 20 | ] 21 | start_response(status, response_headers) 22 | 23 | return environ['wsgi.file_wrapper'](f) 24 | -------------------------------------------------------------------------------- /examples/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDdDCCAlwCCQC3MfdcOMwt6DANBgkqhkiG9w0BAQUFADB8MQswCQYDVQQGEwJG 3 | UjERMA8GA1UECBMIUGljYXJkaWUxDjAMBgNVBAcTBUNyZWlsMREwDwYDVQQKEwhn 4 | dW5pY29ybjEVMBMGA1UEAxMMZ3VuaWNvcm4ub3JnMSAwHgYJKoZIhvcNAQkBFhF1 5 | c2VyQGd1bmljb3JuLm9yZzAeFw0xMjEyMTQwODI2MDJaFw0xMzEyMTQwODI2MDJa 6 | MHwxCzAJBgNVBAYTAkZSMREwDwYDVQQIEwhQaWNhcmRpZTEOMAwGA1UEBxMFQ3Jl 7 | aWwxETAPBgNVBAoTCGd1bmljb3JuMRUwEwYDVQQDEwxndW5pY29ybi5vcmcxIDAe 8 | BgkqhkiG9w0BCQEWEXVzZXJAZ3VuaWNvcm4ub3JnMIIBIjANBgkqhkiG9w0BAQEF 9 | AAOCAQ8AMIIBCgKCAQEAy9RQSiGpB+HyjMpRCEfV9M/4g7gXq/qRizxDspJujoBz 10 | SW0d4FqMHaSRX2QOA+euhtlOYTgsvWZcyv5cvDfL1CtrNWSVBrlo7wIy5tg60Z3A 11 | JnWT/Zxj4WIqkPwdglB1sRBsI1Fn0o6nJu4HekZedXDK6fua4lOPfsQG84EhRQKS 12 | Mz2o7Nesk8/UMjb+5WoRmG7mxrpe0/OYlnydqzqwHUQ+I5CHl1kOhePo9ZBTFMA5 13 | Ece8kGQs37rFCEy92xCYHDgp+CjjyYbeBskF3o0/a88K2bt8J7uXkn4h14HjtFHq 14 | fYnqn60cwyIx3T/uMUh6EmhKQezaw60xyIivmjH8tQIDAQABMA0GCSqGSIb3DQEB 15 | BQUAA4IBAQAKu7kzTAqONFI1qC6mnwAixSd7ml6RtyQRiIWjg4FyTJmS2NMlqUSI 16 | CiV1g1+pv2cy9amld8hoO17ISYFZqMoRxJgD5GuN4y1lUefFe95GHI9loubIJZlR 17 | 5KlZEvCiaAQoGvYiacf4BNkljyrwgPVM5e71dGon7jyghmV6yUaUL6+1J8BU/KYg 18 | jz8RtMtptqkwKPKQVfuDcr/eoH6uZwPRbyfqSui8SuMz3Df6Dnx1hOtlQRJC6eNo 19 | U9L3jkmCsbbMNBAz6iQjyFHFa9iqzwL7nvqZTryjmI5Dpn+BnT7Q5cduK+N5vt4+ 20 | RjNVrz/l6+nR68B5GO96zUTV3/KrEmFr 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /examples/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEAy9RQSiGpB+HyjMpRCEfV9M/4g7gXq/qRizxDspJujoBzSW0d 3 | 4FqMHaSRX2QOA+euhtlOYTgsvWZcyv5cvDfL1CtrNWSVBrlo7wIy5tg60Z3AJnWT 4 | /Zxj4WIqkPwdglB1sRBsI1Fn0o6nJu4HekZedXDK6fua4lOPfsQG84EhRQKSMz2o 5 | 7Nesk8/UMjb+5WoRmG7mxrpe0/OYlnydqzqwHUQ+I5CHl1kOhePo9ZBTFMA5Ece8 6 | kGQs37rFCEy92xCYHDgp+CjjyYbeBskF3o0/a88K2bt8J7uXkn4h14HjtFHqfYnq 7 | n60cwyIx3T/uMUh6EmhKQezaw60xyIivmjH8tQIDAQABAoIBAQDFzhTc3C2daLhp 8 | yS06S/xmyCz0JwNR8qir5qAL++8ue5ll+G61+yle2wX4/LBdOck1NE3MKye/5kbG 9 | +HImdj9od3pjJmk5TVV4HToorE7ofZ6rtA8aX1rOruWALiq0/EA6xSUsYSPQQoAU 10 | V4sKLqAceIly6Kk2WsE21CWqyfXvcQOtfBYmFwmPCImWecJLypeheEpz1U2EYl65 11 | u6b0NsXeODrLXEAEFjdb6UBJtzRtTJ/OnbDvghu9xMjlT0Pj+inoAv/ePZB8bmvH 12 | XGhZo7dzgsDZ+eys7XnbeggUOhFImzCjO1f3pIVXWThGDgKIrpc9Evac2Q3AjTFY 13 | NV9HBV9BAoGBAOyWq7HDgeCEu54orPAmdkO4j/HFX+262BTQoVCg4OX3Iv9A/lH4 14 | lpVGrFlK0qJF9jb7mjDmXP2LW0fwzyHe42DGFbZkKdfiMBuE+qoPeAV9s+SjE4H3 15 | l3tHoAOFUt2wITcHK4EYjoLMAgrbRNiv9t/gqiMm1oIb3fkUbpOoGG3LAoGBANyN 16 | kLop3JfN1Kzto7gJq/tLS21joexTU+s4EJ+a4Q8KH1d47DLI+4119Ot+NWwi9X3S 17 | sbOKZOjfrGw9+HPI64i7hD9HPSK58IUbsfVR9vPlPei45inRfi6s7+EUzKifOKZU 18 | o1ecpOSPYQHZtDToGcQCTS0IFwMXHgrkP380We9/AoGBAI1wljyz8RVUxQWMs7bu 19 | h4187TFRGkR5i20GPSqCw3E4CkgnhuNihkO/+JF5VeuFf+jnCgtp7PX3Nh8QLATH 20 | x4+3XIup3goeQzxwh5rbnJlLyRxLEgKFDp6490SjlCLMhU7sjmmjUK+JXz82TzZs 21 | HF9DZPOW6G7oUg/y0xibSd95AoGAXpDEcU3pq50xh0QNYqei+gh6uthxYScJYF2V 22 | oxmBTjWE4riSbeQHF8xvy1k+BrOmluB0GQtJ4R+minK3yM1pUCM2vPsKl40qN6h8 23 | UTdnr4OnW9WLunp8o/66i8OjTNmYLJk1wCcF/IoNigGSZuztv0FNXfWOCGEtHHZp 24 | U11bAnkCgYEAoU0sdFL3IfmxnNQ9CDmgXdJM0SpUm4ECd2jM/fRdgLelL+WislCF 25 | gHjZw+3mplIzqQ9DMwRkjbaIxP0H92OopOBIqmShWUuzWw/Dj0L8PGe/7skcwsGD 26 | /VLEkGzrxJwP4kokUu1kvLOqHM429JXsb8wO16iMQAB93yUZ+X8PGfQ= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /examples/slowclient.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | import sys 6 | import time 7 | 8 | 9 | def app(environ, start_response): 10 | """Application which cooperatively pauses 10 seconds before responding""" 11 | data = b'Hello, World!\n' 12 | status = '200 OK' 13 | response_headers = [ 14 | ('Content-type', 'text/plain'), 15 | ('Content-Length', str(len(data))), 16 | ] 17 | sys.stdout.write('request received, pausing 10 seconds') 18 | sys.stdout.flush() 19 | time.sleep(10) 20 | start_response(status, response_headers) 21 | return iter([data]) 22 | -------------------------------------------------------------------------------- /examples/standalone_app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # An example of a standalone application using the internal API of Gunicorn. 4 | # 5 | # $ python standalone_app.py 6 | # 7 | # This file is part of gunicorn released under the MIT license. 8 | # See the NOTICE for more information. 9 | 10 | import multiprocessing 11 | 12 | import gunicorn.app.base 13 | 14 | 15 | def number_of_workers(): 16 | return (multiprocessing.cpu_count() * 2) + 1 17 | 18 | 19 | def handler_app(environ, start_response): 20 | response_body = b'Works fine' 21 | status = '200 OK' 22 | 23 | response_headers = [ 24 | ('Content-Type', 'text/plain'), 25 | ] 26 | 27 | start_response(status, response_headers) 28 | 29 | return [response_body] 30 | 31 | 32 | class StandaloneApplication(gunicorn.app.base.BaseApplication): 33 | 34 | def __init__(self, app, options=None): 35 | self.options = options or {} 36 | self.application = app 37 | super().__init__() 38 | 39 | def load_config(self): 40 | config = {key: value for key, value in self.options.items() 41 | if key in self.cfg.settings and value is not None} 42 | for key, value in config.items(): 43 | self.cfg.set(key.lower(), value) 44 | 45 | def load(self): 46 | return self.application 47 | 48 | 49 | if __name__ == '__main__': 50 | options = { 51 | 'bind': '%s:%s' % ('127.0.0.1', '8080'), 52 | 'workers': number_of_workers(), 53 | } 54 | StandaloneApplication(handler_app, options).run() 55 | -------------------------------------------------------------------------------- /examples/supervisor.conf: -------------------------------------------------------------------------------- 1 | [program:gunicorn] 2 | command=/usr/local/bin/gunicorn main:application -c /path/to/project/gunicorn.conf.py 3 | directory=/path/to/project 4 | user=nobody 5 | autorestart=true 6 | redirect_stderr=true 7 | -------------------------------------------------------------------------------- /examples/test.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | # 5 | # Example code from Eventlet sources 6 | 7 | from wsgiref.validate import validator 8 | 9 | from gunicorn import __version__ 10 | 11 | 12 | @validator 13 | def app(environ, start_response): 14 | """Simplest possible application object""" 15 | 16 | data = b'Hello, World!\n' 17 | status = '200 OK' 18 | 19 | response_headers = [ 20 | ('Content-type', 'text/plain'), 21 | ('Content-Length', str(len(data))), 22 | ('X-Gunicorn-Version', __version__), 23 | ('Foo', 'B\u00e5r'), # Foo: Bår 24 | ] 25 | start_response(status, response_headers) 26 | return iter([data]) 27 | -------------------------------------------------------------------------------- /examples/timeout.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | import sys 6 | import time 7 | 8 | 9 | def app(environ, start_response): 10 | """Application which pauses 35 seconds before responding. the worker 11 | will timeout in default case.""" 12 | data = b'Hello, World!\n' 13 | status = '200 OK' 14 | response_headers = [ 15 | ('Content-type', 'text/plain'), 16 | ('Content-Length', str(len(data))), 17 | ] 18 | sys.stdout.write('request will timeout') 19 | sys.stdout.flush() 20 | time.sleep(35) 21 | start_response(status, response_headers) 22 | return iter([data]) 23 | -------------------------------------------------------------------------------- /examples/websocket/websocket.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 39 | 40 | 41 |

Plot

42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/when_ready.conf.py: -------------------------------------------------------------------------------- 1 | import signal 2 | import commands 3 | import threading 4 | import time 5 | 6 | max_mem = 100000 7 | 8 | class MemoryWatch(threading.Thread): 9 | 10 | def __init__(self, server, max_mem): 11 | super().__init__() 12 | self.daemon = True 13 | self.server = server 14 | self.max_mem = max_mem 15 | self.timeout = server.timeout / 2 16 | 17 | def memory_usage(self, pid): 18 | try: 19 | out = commands.getoutput("ps -o rss -p %s" % pid) 20 | except OSError: 21 | return -1 22 | used_mem = sum(int(x) for x in out.split('\n')[1:]) 23 | return used_mem 24 | 25 | def run(self): 26 | while True: 27 | for (pid, worker) in list(self.server.WORKERS.items()): 28 | if self.memory_usage(pid) > self.max_mem: 29 | self.server.log.info("Pid %s killed (memory usage > %s)", 30 | pid, self.max_mem) 31 | self.server.kill_worker(pid, signal.SIGTERM) 32 | time.sleep(self.timeout) 33 | 34 | 35 | def when_ready(server): 36 | mw = MemoryWatch(server, max_mem) 37 | mw.start() 38 | -------------------------------------------------------------------------------- /gunicorn/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | version_info = (23, 0, 0) 6 | __version__ = ".".join([str(v) for v in version_info]) 7 | SERVER = "gunicorn" 8 | SERVER_SOFTWARE = "%s/%s" % (SERVER, __version__) 9 | -------------------------------------------------------------------------------- /gunicorn/__main__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | from gunicorn.app.wsgiapp import run 6 | 7 | if __name__ == "__main__": 8 | # see config.py - argparse defaults to basename(argv[0]) == "__main__.py" 9 | # todo: let runpy.run_module take care of argv[0] rewriting 10 | run(prog="gunicorn") 11 | -------------------------------------------------------------------------------- /gunicorn/app/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | -------------------------------------------------------------------------------- /gunicorn/app/pasterapp.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | import configparser 6 | import os 7 | 8 | from paste.deploy import loadapp 9 | 10 | from gunicorn.app.wsgiapp import WSGIApplication 11 | from gunicorn.config import get_default_config_file 12 | 13 | 14 | def get_wsgi_app(config_uri, name=None, defaults=None): 15 | if ':' not in config_uri: 16 | config_uri = "config:%s" % config_uri 17 | 18 | return loadapp( 19 | config_uri, 20 | name=name, 21 | relative_to=os.getcwd(), 22 | global_conf=defaults, 23 | ) 24 | 25 | 26 | def has_logging_config(config_file): 27 | parser = configparser.ConfigParser() 28 | parser.read([config_file]) 29 | return parser.has_section('loggers') 30 | 31 | 32 | def serve(app, global_conf, **local_conf): 33 | """\ 34 | A Paste Deployment server runner. 35 | 36 | Example configuration: 37 | 38 | [server:main] 39 | use = egg:gunicorn#main 40 | host = 127.0.0.1 41 | port = 5000 42 | """ 43 | config_file = global_conf['__file__'] 44 | gunicorn_config_file = local_conf.pop('config', None) 45 | 46 | host = local_conf.pop('host', '') 47 | port = local_conf.pop('port', '') 48 | if host and port: 49 | local_conf['bind'] = '%s:%s' % (host, port) 50 | elif host: 51 | local_conf['bind'] = host.split(',') 52 | 53 | class PasterServerApplication(WSGIApplication): 54 | def load_config(self): 55 | self.cfg.set("default_proc_name", config_file) 56 | 57 | if has_logging_config(config_file): 58 | self.cfg.set("logconfig", config_file) 59 | 60 | if gunicorn_config_file: 61 | self.load_config_from_file(gunicorn_config_file) 62 | else: 63 | default_gunicorn_config_file = get_default_config_file() 64 | if default_gunicorn_config_file is not None: 65 | self.load_config_from_file(default_gunicorn_config_file) 66 | 67 | for k, v in local_conf.items(): 68 | if v is not None: 69 | self.cfg.set(k.lower(), v) 70 | 71 | def load(self): 72 | return app 73 | 74 | PasterServerApplication().run() 75 | -------------------------------------------------------------------------------- /gunicorn/app/wsgiapp.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | import os 6 | 7 | from gunicorn.errors import ConfigError 8 | from gunicorn.app.base import Application 9 | from gunicorn import util 10 | 11 | 12 | class WSGIApplication(Application): 13 | def init(self, parser, opts, args): 14 | self.app_uri = None 15 | 16 | if opts.paste: 17 | from .pasterapp import has_logging_config 18 | 19 | config_uri = os.path.abspath(opts.paste) 20 | config_file = config_uri.split('#')[0] 21 | 22 | if not os.path.exists(config_file): 23 | raise ConfigError("%r not found" % config_file) 24 | 25 | self.cfg.set("default_proc_name", config_file) 26 | self.app_uri = config_uri 27 | 28 | if has_logging_config(config_file): 29 | self.cfg.set("logconfig", config_file) 30 | 31 | return 32 | 33 | if len(args) > 0: 34 | self.cfg.set("default_proc_name", args[0]) 35 | self.app_uri = args[0] 36 | 37 | def load_config(self): 38 | super().load_config() 39 | 40 | if self.app_uri is None: 41 | if self.cfg.wsgi_app is not None: 42 | self.app_uri = self.cfg.wsgi_app 43 | else: 44 | raise ConfigError("No application module specified.") 45 | 46 | def load_wsgiapp(self): 47 | return util.import_app(self.app_uri) 48 | 49 | def load_pasteapp(self): 50 | from .pasterapp import get_wsgi_app 51 | return get_wsgi_app(self.app_uri, defaults=self.cfg.paste_global_conf) 52 | 53 | def load(self): 54 | if self.cfg.paste is not None: 55 | return self.load_pasteapp() 56 | else: 57 | return self.load_wsgiapp() 58 | 59 | 60 | def run(prog=None): 61 | """\ 62 | The ``gunicorn`` command line runner for launching Gunicorn with 63 | generic WSGI applications. 64 | """ 65 | from gunicorn.app.wsgiapp import WSGIApplication 66 | WSGIApplication("%(prog)s [OPTIONS] [APP_MODULE]", prog=prog).run() 67 | 68 | 69 | if __name__ == '__main__': 70 | run() 71 | -------------------------------------------------------------------------------- /gunicorn/debug.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | """The debug module contains utilities and functions for better 6 | debugging Gunicorn.""" 7 | 8 | import sys 9 | import linecache 10 | import re 11 | import inspect 12 | 13 | __all__ = ['spew', 'unspew'] 14 | 15 | _token_spliter = re.compile(r'\W+') 16 | 17 | 18 | class Spew: 19 | 20 | def __init__(self, trace_names=None, show_values=True): 21 | self.trace_names = trace_names 22 | self.show_values = show_values 23 | 24 | def __call__(self, frame, event, arg): 25 | if event == 'line': 26 | lineno = frame.f_lineno 27 | if '__file__' in frame.f_globals: 28 | filename = frame.f_globals['__file__'] 29 | if (filename.endswith('.pyc') or 30 | filename.endswith('.pyo')): 31 | filename = filename[:-1] 32 | name = frame.f_globals['__name__'] 33 | line = linecache.getline(filename, lineno) 34 | else: 35 | name = '[unknown]' 36 | try: 37 | src = inspect.getsourcelines(frame) 38 | line = src[lineno] 39 | except OSError: 40 | line = 'Unknown code named [%s]. VM instruction #%d' % ( 41 | frame.f_code.co_name, frame.f_lasti) 42 | if self.trace_names is None or name in self.trace_names: 43 | print('%s:%s: %s' % (name, lineno, line.rstrip())) 44 | if not self.show_values: 45 | return self 46 | details = [] 47 | tokens = _token_spliter.split(line) 48 | for tok in tokens: 49 | if tok in frame.f_globals: 50 | details.append('%s=%r' % (tok, frame.f_globals[tok])) 51 | if tok in frame.f_locals: 52 | details.append('%s=%r' % (tok, frame.f_locals[tok])) 53 | if details: 54 | print("\t%s" % ' '.join(details)) 55 | return self 56 | 57 | 58 | def spew(trace_names=None, show_values=False): 59 | """Install a trace hook which writes incredibly detailed logs 60 | about what code is being executed to stdout. 61 | """ 62 | sys.settrace(Spew(trace_names, show_values)) 63 | 64 | 65 | def unspew(): 66 | """Remove the trace hook installed by spew. 67 | """ 68 | sys.settrace(None) 69 | -------------------------------------------------------------------------------- /gunicorn/errors.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | # We don't need to call super() in __init__ methods of our 6 | # BaseException and Exception classes because we also define 7 | # our own __str__ methods so there is no need to pass 'message' 8 | # to the base class to get a meaningful output from 'str(exc)'. 9 | # pylint: disable=super-init-not-called 10 | 11 | 12 | # we inherit from BaseException here to make sure to not be caught 13 | # at application level 14 | class HaltServer(BaseException): 15 | def __init__(self, reason, exit_status=1): 16 | self.reason = reason 17 | self.exit_status = exit_status 18 | 19 | def __str__(self): 20 | return "" % (self.reason, self.exit_status) 21 | 22 | 23 | class ConfigError(Exception): 24 | """ Exception raised on config error """ 25 | 26 | 27 | class AppImportError(Exception): 28 | """ Exception raised when loading an application """ 29 | -------------------------------------------------------------------------------- /gunicorn/http/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | from gunicorn.http.message import Message, Request 6 | from gunicorn.http.parser import RequestParser 7 | 8 | __all__ = ['Message', 'Request', 'RequestParser'] 9 | -------------------------------------------------------------------------------- /gunicorn/http/parser.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | from gunicorn.http.message import Request 6 | from gunicorn.http.unreader import SocketUnreader, IterUnreader 7 | 8 | 9 | class Parser: 10 | 11 | mesg_class = None 12 | 13 | def __init__(self, cfg, source, source_addr): 14 | self.cfg = cfg 15 | if hasattr(source, "recv"): 16 | self.unreader = SocketUnreader(source) 17 | else: 18 | self.unreader = IterUnreader(source) 19 | self.mesg = None 20 | self.source_addr = source_addr 21 | 22 | # request counter (for keepalive connetions) 23 | self.req_count = 0 24 | 25 | def __iter__(self): 26 | return self 27 | 28 | def __next__(self): 29 | # Stop if HTTP dictates a stop. 30 | if self.mesg and self.mesg.should_close(): 31 | raise StopIteration() 32 | 33 | # Discard any unread body of the previous message 34 | if self.mesg: 35 | data = self.mesg.body.read(8192) 36 | while data: 37 | data = self.mesg.body.read(8192) 38 | 39 | # Parse the next request 40 | self.req_count += 1 41 | self.mesg = self.mesg_class(self.cfg, self.unreader, self.source_addr, self.req_count) 42 | if not self.mesg: 43 | raise StopIteration() 44 | return self.mesg 45 | 46 | next = __next__ 47 | 48 | 49 | class RequestParser(Parser): 50 | 51 | mesg_class = Request 52 | -------------------------------------------------------------------------------- /gunicorn/http/unreader.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | import io 6 | import os 7 | 8 | # Classes that can undo reading data from 9 | # a given type of data source. 10 | 11 | 12 | class Unreader: 13 | def __init__(self): 14 | self.buf = io.BytesIO() 15 | 16 | def chunk(self): 17 | raise NotImplementedError() 18 | 19 | def read(self, size=None): 20 | if size is not None and not isinstance(size, int): 21 | raise TypeError("size parameter must be an int or long.") 22 | 23 | if size is not None: 24 | if size == 0: 25 | return b"" 26 | if size < 0: 27 | size = None 28 | 29 | self.buf.seek(0, os.SEEK_END) 30 | 31 | if size is None and self.buf.tell(): 32 | ret = self.buf.getvalue() 33 | self.buf = io.BytesIO() 34 | return ret 35 | if size is None: 36 | d = self.chunk() 37 | return d 38 | 39 | while self.buf.tell() < size: 40 | chunk = self.chunk() 41 | if not chunk: 42 | ret = self.buf.getvalue() 43 | self.buf = io.BytesIO() 44 | return ret 45 | self.buf.write(chunk) 46 | data = self.buf.getvalue() 47 | self.buf = io.BytesIO() 48 | self.buf.write(data[size:]) 49 | return data[:size] 50 | 51 | def unread(self, data): 52 | self.buf.seek(0, os.SEEK_END) 53 | self.buf.write(data) 54 | 55 | 56 | class SocketUnreader(Unreader): 57 | def __init__(self, sock, max_chunk=8192): 58 | super().__init__() 59 | self.sock = sock 60 | self.mxchunk = max_chunk 61 | 62 | def chunk(self): 63 | return self.sock.recv(self.mxchunk) 64 | 65 | 66 | class IterUnreader(Unreader): 67 | def __init__(self, iterable): 68 | super().__init__() 69 | self.iter = iter(iterable) 70 | 71 | def chunk(self): 72 | if not self.iter: 73 | return b"" 74 | try: 75 | return next(self.iter) 76 | except StopIteration: 77 | self.iter = None 78 | return b"" 79 | -------------------------------------------------------------------------------- /gunicorn/instrument/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/gunicorn/instrument/__init__.py -------------------------------------------------------------------------------- /gunicorn/pidfile.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | import errno 6 | import os 7 | import tempfile 8 | 9 | 10 | class Pidfile: 11 | """\ 12 | Manage a PID file. If a specific name is provided 13 | it and '"%s.oldpid" % name' will be used. Otherwise 14 | we create a temp file using os.mkstemp. 15 | """ 16 | 17 | def __init__(self, fname): 18 | self.fname = fname 19 | self.pid = None 20 | 21 | def create(self, pid): 22 | oldpid = self.validate() 23 | if oldpid: 24 | if oldpid == os.getpid(): 25 | return 26 | msg = "Already running on PID %s (or pid file '%s' is stale)" 27 | raise RuntimeError(msg % (oldpid, self.fname)) 28 | 29 | self.pid = pid 30 | 31 | # Write pidfile 32 | fdir = os.path.dirname(self.fname) 33 | if fdir and not os.path.isdir(fdir): 34 | raise RuntimeError("%s doesn't exist. Can't create pidfile." % fdir) 35 | fd, fname = tempfile.mkstemp(dir=fdir) 36 | os.write(fd, ("%s\n" % self.pid).encode('utf-8')) 37 | if self.fname: 38 | os.rename(fname, self.fname) 39 | else: 40 | self.fname = fname 41 | os.close(fd) 42 | 43 | # set permissions to -rw-r--r-- 44 | os.chmod(self.fname, 420) 45 | 46 | def rename(self, path): 47 | self.unlink() 48 | self.fname = path 49 | self.create(self.pid) 50 | 51 | def unlink(self): 52 | """ delete pidfile""" 53 | try: 54 | with open(self.fname) as f: 55 | pid1 = int(f.read() or 0) 56 | 57 | if pid1 == self.pid: 58 | os.unlink(self.fname) 59 | except Exception: 60 | pass 61 | 62 | def validate(self): 63 | """ Validate pidfile and make it stale if needed""" 64 | if not self.fname: 65 | return 66 | try: 67 | with open(self.fname) as f: 68 | try: 69 | wpid = int(f.read()) 70 | except ValueError: 71 | return 72 | 73 | try: 74 | os.kill(wpid, 0) 75 | return wpid 76 | except OSError as e: 77 | if e.args[0] == errno.EPERM: 78 | return wpid 79 | if e.args[0] == errno.ESRCH: 80 | return 81 | raise 82 | except OSError as e: 83 | if e.args[0] == errno.ENOENT: 84 | return 85 | raise 86 | -------------------------------------------------------------------------------- /gunicorn/systemd.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | import os 6 | import socket 7 | 8 | SD_LISTEN_FDS_START = 3 9 | 10 | 11 | def listen_fds(unset_environment=True): 12 | """ 13 | Get the number of sockets inherited from systemd socket activation. 14 | 15 | :param unset_environment: clear systemd environment variables unless False 16 | :type unset_environment: bool 17 | :return: the number of sockets to inherit from systemd socket activation 18 | :rtype: int 19 | 20 | Returns zero immediately if $LISTEN_PID is not set to the current pid. 21 | Otherwise, returns the number of systemd activation sockets specified by 22 | $LISTEN_FDS. 23 | 24 | When $LISTEN_PID matches the current pid, unsets the environment variables 25 | unless the ``unset_environment`` flag is ``False``. 26 | 27 | .. note:: 28 | Unlike the sd_listen_fds C function, this implementation does not set 29 | the FD_CLOEXEC flag because the gunicorn arbiter never needs to do this. 30 | 31 | .. seealso:: 32 | ``_ 33 | 34 | """ 35 | fds = int(os.environ.get('LISTEN_FDS', 0)) 36 | listen_pid = int(os.environ.get('LISTEN_PID', 0)) 37 | 38 | if listen_pid != os.getpid(): 39 | return 0 40 | 41 | if unset_environment: 42 | os.environ.pop('LISTEN_PID', None) 43 | os.environ.pop('LISTEN_FDS', None) 44 | 45 | return fds 46 | 47 | 48 | def sd_notify(state, logger, unset_environment=False): 49 | """Send a notification to systemd. state is a string; see 50 | the man page of sd_notify (http://www.freedesktop.org/software/systemd/man/sd_notify.html) 51 | for a description of the allowable values. 52 | 53 | If the unset_environment parameter is True, sd_notify() will unset 54 | the $NOTIFY_SOCKET environment variable before returning (regardless of 55 | whether the function call itself succeeded or not). Further calls to 56 | sd_notify() will then fail, but the variable is no longer inherited by 57 | child processes. 58 | """ 59 | 60 | addr = os.environ.get('NOTIFY_SOCKET') 61 | if addr is None: 62 | # not run in a service, just a noop 63 | return 64 | try: 65 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM | socket.SOCK_CLOEXEC) 66 | if addr[0] == '@': 67 | addr = '\0' + addr[1:] 68 | sock.connect(addr) 69 | sock.sendall(state.encode('utf-8')) 70 | except Exception: 71 | logger.debug("Exception while invoking sd_notify()", exc_info=True) 72 | finally: 73 | if unset_environment: 74 | os.environ.pop('NOTIFY_SOCKET') 75 | sock.close() 76 | -------------------------------------------------------------------------------- /gunicorn/workers/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | # supported gunicorn workers. 6 | SUPPORTED_WORKERS = { 7 | "sync": "gunicorn.workers.sync.SyncWorker", 8 | "eventlet": "gunicorn.workers.geventlet.EventletWorker", 9 | "gevent": "gunicorn.workers.ggevent.GeventWorker", 10 | "gevent_wsgi": "gunicorn.workers.ggevent.GeventPyWSGIWorker", 11 | "gevent_pywsgi": "gunicorn.workers.ggevent.GeventPyWSGIWorker", 12 | "tornado": "gunicorn.workers.gtornado.TornadoWorker", 13 | "gthread": "gunicorn.workers.gthread.ThreadWorker", 14 | } 15 | -------------------------------------------------------------------------------- /gunicorn/workers/workertmp.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | import os 6 | import time 7 | import platform 8 | import tempfile 9 | 10 | from gunicorn import util 11 | 12 | PLATFORM = platform.system() 13 | IS_CYGWIN = PLATFORM.startswith('CYGWIN') 14 | 15 | 16 | class WorkerTmp: 17 | 18 | def __init__(self, cfg): 19 | old_umask = os.umask(cfg.umask) 20 | fdir = cfg.worker_tmp_dir 21 | if fdir and not os.path.isdir(fdir): 22 | raise RuntimeError("%s doesn't exist. Can't create workertmp." % fdir) 23 | fd, name = tempfile.mkstemp(prefix="wgunicorn-", dir=fdir) 24 | os.umask(old_umask) 25 | 26 | # change the owner and group of the file if the worker will run as 27 | # a different user or group, so that the worker can modify the file 28 | if cfg.uid != os.geteuid() or cfg.gid != os.getegid(): 29 | util.chown(name, cfg.uid, cfg.gid) 30 | 31 | # unlink the file so we don't leak temporary files 32 | try: 33 | if not IS_CYGWIN: 34 | util.unlink(name) 35 | # In Python 3.8, open() emits RuntimeWarning if buffering=1 for binary mode. 36 | # Because we never write to this file, pass 0 to switch buffering off. 37 | self._tmp = os.fdopen(fd, 'w+b', 0) 38 | except Exception: 39 | os.close(fd) 40 | raise 41 | 42 | def notify(self): 43 | new_time = time.monotonic() 44 | os.utime(self._tmp.fileno(), (new_time, new_time)) 45 | 46 | def last_update(self): 47 | return os.fstat(self._tmp.fileno()).st_mtime 48 | 49 | def fileno(self): 50 | return self._tmp.fileno() 51 | 52 | def close(self): 53 | return self._tmp.close() 54 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.2"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | # see https://packaging.python.org/en/latest/specifications/pyproject-toml/ 7 | name = "gunicorn" 8 | authors = [{name = "Benoit Chesneau", email = "benoitc@gunicorn.org"}] 9 | license = {text = "MIT"} 10 | description = "WSGI HTTP Server for UNIX" 11 | readme = "README.rst" 12 | classifiers = [ 13 | "Development Status :: 5 - Production/Stable", 14 | "Environment :: Other Environment", 15 | "Intended Audience :: Developers", 16 | "License :: OSI Approved :: MIT License", 17 | "Operating System :: MacOS :: MacOS X", 18 | "Operating System :: POSIX", 19 | "Programming Language :: Python", 20 | "Programming Language :: Python :: 3", 21 | "Programming Language :: Python :: 3.7", 22 | "Programming Language :: Python :: 3.8", 23 | "Programming Language :: Python :: 3.9", 24 | "Programming Language :: Python :: 3.10", 25 | "Programming Language :: Python :: 3.11", 26 | "Programming Language :: Python :: 3.12", 27 | "Programming Language :: Python :: 3 :: Only", 28 | "Programming Language :: Python :: Implementation :: CPython", 29 | "Programming Language :: Python :: Implementation :: PyPy", 30 | "Topic :: Internet", 31 | "Topic :: Utilities", 32 | "Topic :: Software Development :: Libraries :: Python Modules", 33 | "Topic :: Internet :: WWW/HTTP", 34 | "Topic :: Internet :: WWW/HTTP :: WSGI", 35 | "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", 36 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content", 37 | ] 38 | requires-python = ">=3.7" 39 | dependencies = [ 40 | 'importlib_metadata; python_version<"3.8"', 41 | "packaging", 42 | ] 43 | dynamic = ["version"] 44 | 45 | [project.urls] 46 | Homepage = "https://gunicorn.org" 47 | Documentation = "https://docs.gunicorn.org" 48 | "Issue tracker" = "https://github.com/benoitc/gunicorn/issues" 49 | "Source code" = "https://github.com/benoitc/gunicorn" 50 | Changelog = "https://docs.gunicorn.org/en/stable/news.html" 51 | 52 | [project.optional-dependencies] 53 | gevent = ["gevent>=1.4.0"] 54 | eventlet = ["eventlet>=0.24.1,!=0.36.0"] 55 | tornado = ["tornado>=0.2"] 56 | gthread = [] 57 | setproctitle = ["setproctitle"] 58 | testing = [ 59 | "gevent", 60 | "eventlet", 61 | "coverage", 62 | "pytest", 63 | "pytest-cov", 64 | ] 65 | 66 | [project.scripts] 67 | # duplicates "python -m gunicorn" handling in __main__.py 68 | gunicorn = "gunicorn.app.wsgiapp:run" 69 | 70 | # note the quotes around "paste.server_runner" to escape the dot 71 | [project.entry-points."paste.server_runner"] 72 | main = "gunicorn.app.pasterapp:serve" 73 | 74 | [tool.pytest.ini_options] 75 | # # can override these: python -m pytest --override-ini="addopts=" 76 | norecursedirs = ["examples", "lib", "local", "src"] 77 | testpaths = ["tests/"] 78 | addopts = "--assert=plain --cov=gunicorn --cov-report=xml" 79 | 80 | [tool.setuptools] 81 | zip-safe = false 82 | include-package-data = true 83 | license-files = ["LICENSE"] 84 | 85 | [tool.setuptools.packages] 86 | find = {namespaces = false} 87 | 88 | [tool.setuptools.dynamic] 89 | version = {attr = "gunicorn.__version__"} 90 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | -r requirements_test.txt 2 | 3 | # setuptools v68.0 fails hard on invalid pyproject.toml 4 | # which a developer would want to know 5 | # otherwise, oldest known-working version is 61.2 6 | setuptools>=68.0 7 | 8 | sphinx 9 | sphinx_rtd_theme 10 | -------------------------------------------------------------------------------- /requirements_test.txt: -------------------------------------------------------------------------------- 1 | gevent 2 | eventlet 3 | coverage 4 | pytest>=7.2.0 5 | pytest-cov 6 | -------------------------------------------------------------------------------- /scripts/update_thanks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Usage: git log --format="%an <%ae>" | python update_thanks.py 3 | # You will get a result.txt file, you can work with the file (update, remove, ...) 4 | # 5 | # Install 6 | # ======= 7 | # pip install validate_email pyDNS 8 | # 9 | import sys 10 | 11 | from validate_email import validate_email 12 | from email.utils import parseaddr 13 | import DNS.Base 14 | 15 | addresses = set() 16 | bad_addresses = set() 17 | collection = [] 18 | 19 | lines = list(reversed(sys.stdin.readlines())) 20 | 21 | for author in map(str.strip, lines): 22 | realname, email_address = parseaddr(author) 23 | 24 | if email_address not in addresses: 25 | if email_address in bad_addresses: 26 | continue 27 | else: 28 | try: 29 | value = validate_email(email_address) 30 | if value: 31 | addresses.add(email_address) 32 | collection.append(author) 33 | else: 34 | bad_addresses.add(email_address) 35 | except DNS.Base.TimeoutError: 36 | bad_addresses.add(email_address) 37 | 38 | 39 | with open('result.txt', 'w') as output: 40 | output.write('\n'.join(collection)) 41 | -------------------------------------------------------------------------------- /tests/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/tests/config/__init__.py -------------------------------------------------------------------------------- /tests/config/test_cfg.py: -------------------------------------------------------------------------------- 1 | bind = "unix:/tmp/bar/baz" 2 | workers = 3 3 | proc_name = "fooey" 4 | default_proc_name = "blurgh" 5 | -------------------------------------------------------------------------------- /tests/config/test_cfg_alt.py: -------------------------------------------------------------------------------- 1 | proc_name = "not-fooey" 2 | -------------------------------------------------------------------------------- /tests/config/test_cfg_with_wsgi_app.py: -------------------------------------------------------------------------------- 1 | wsgi_app = "app1:app1" 2 | -------------------------------------------------------------------------------- /tests/requests/invalid/001.http: -------------------------------------------------------------------------------- 1 | GET /foo/bar HTTP/1.0\r\n 2 | baz -------------------------------------------------------------------------------- /tests/requests/invalid/001.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import NoMoreData 2 | request = NoMoreData -------------------------------------------------------------------------------- /tests/requests/invalid/002.http: -------------------------------------------------------------------------------- 1 | GET HTTP/1.1\r\n 2 | \r\n -------------------------------------------------------------------------------- /tests/requests/invalid/002.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidRequestLine 2 | request = InvalidRequestLine 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/003.http: -------------------------------------------------------------------------------- 1 | GET\n/\nHTTP/1.1\r\n 2 | \r\n 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/003.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidRequestLine 2 | request = InvalidRequestLine 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/003b.http: -------------------------------------------------------------------------------- 1 | bla:rgh /foo HTTP/1.1\r\n 2 | \r\n 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/003b.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidRequestMethod 2 | request = InvalidRequestMethod -------------------------------------------------------------------------------- /tests/requests/invalid/003c.http: -------------------------------------------------------------------------------- 1 | -bl /foo HTTP/1.1\r\n 2 | \r\n 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/003c.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidRequestMethod 2 | request = InvalidRequestMethod 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/004.http: -------------------------------------------------------------------------------- 1 | GET /foo FTP/1.1\r\n 2 | \r\n -------------------------------------------------------------------------------- /tests/requests/invalid/004.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidHTTPVersion 2 | request = InvalidHTTPVersion -------------------------------------------------------------------------------- /tests/requests/invalid/005.http: -------------------------------------------------------------------------------- 1 | GET /foo HTTP/1.1\r\n 2 | ba\0z: bar\r\n 3 | \r\n -------------------------------------------------------------------------------- /tests/requests/invalid/005.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidHeaderName 2 | request = InvalidHeaderName -------------------------------------------------------------------------------- /tests/requests/invalid/006.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import LimitRequestLine 2 | request = LimitRequestLine 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/007.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import LimitRequestHeaders 2 | request = LimitRequestHeaders 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/008.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import LimitRequestHeaders 2 | request = LimitRequestHeaders 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/009.http: -------------------------------------------------------------------------------- 1 | PUT /stuff/here?foo=bar HTTP/1.0\r\n 2 | Server: http://127.0.0.1:5984\r\n 3 | Content-Type: application/json\r\n 4 | Content-Length: 14\r\n 5 | header0: 0\r\n 6 | header1: 1\r\n 7 | header2: 2\r\n 8 | header3: 3\r\n 9 | header4: 4\r\n 10 | header5: 5\r\n 11 | header6: 6\r\n 12 | header7: 7\r\n 13 | header8: 8\r\n 14 | header9: 9\r\n 15 | header10: 10\r\n 16 | header11: 11\r\n 17 | header12: 12\r\n 18 | header13: 13\r\n 19 | header14: 14\r\n 20 | header15: 15\r\n 21 | header16: 16\r\n 22 | header17: 17\r\n 23 | header18: 18\r\n 24 | header19: 19\r\n 25 | header20: 20\r\n 26 | header21: 21\r\n 27 | header22: 22\r\n 28 | header23: 23\r\n 29 | header24: 24\r\n 30 | header25: 25\r\n 31 | header26: 26\r\n 32 | header27: 27\r\n 33 | header28: 28\r\n 34 | header29: 29\r\n 35 | header30: 30\r\n 36 | header31: 31\r\n 37 | header32: 32\r\n 38 | header33: 33\r\n 39 | header34: 34\r\n 40 | header35: 35\r\n 41 | header36: 36\r\n 42 | header37: 37\r\n 43 | header38: 38\r\n 44 | header39: 39\r\n 45 | header40: 40\r\n 46 | header41: 41\r\n 47 | header42: 42\r\n 48 | header43: 43\r\n 49 | header44: 44\r\n 50 | header45: 45\r\n 51 | header46: 46\r\n 52 | header47: 47\r\n 53 | header48: 48\r\n 54 | header49: 49\r\n 55 | header50: 50\r\n 56 | header51: 51\r\n 57 | header52: 52\r\n 58 | header53: 53\r\n 59 | header54: 54\r\n 60 | header55: 55\r\n 61 | header56: 56\r\n 62 | header57: 57\r\n 63 | header58: 58\r\n 64 | header59: 59\r\n 65 | header60: 60\r\n 66 | header61: 61\r\n 67 | header62: 62\r\n 68 | header63: 63\r\n 69 | header64: 64\r\n 70 | header65: 65\r\n 71 | header66: 66\r\n 72 | header67: 67\r\n 73 | header68: 68\r\n 74 | header69: 69\r\n 75 | header70: 70\r\n 76 | header71: 71\r\n 77 | header72: 72\r\n 78 | header73: 73\r\n 79 | header74: 74\r\n 80 | header75: 75\r\n 81 | header76: 76\r\n 82 | header77: 77\r\n 83 | header78: 78\r\n 84 | header79: 79\r\n 85 | header80: 80\r\n 86 | header81: 81\r\n 87 | header82: 82\r\n 88 | header83: 83\r\n 89 | header84: 84\r\n 90 | header85: 85\r\n 91 | header86: 86\r\n 92 | header87: 87\r\n 93 | header88: 88\r\n 94 | header89: 89\r\n 95 | header90: 90\r\n 96 | header91: 91\r\n 97 | header92: 92\r\n 98 | header93: 93\r\n 99 | header94: 94\r\n 100 | header95: 95\r\n 101 | header96: 96\r\n 102 | header97: 97\r\n 103 | header98: 98\r\n 104 | header99: 99\r\n 105 | \r\n 106 | {"nom": "nom"} 107 | -------------------------------------------------------------------------------- /tests/requests/invalid/009.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import LimitRequestHeaders 2 | request = LimitRequestHeaders 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/010.http: -------------------------------------------------------------------------------- 1 | GET /test HTTP/1.1\r\n 2 | Accept: */*\r\n 3 | \r\n 4 | -------------------------------------------------------------------------------- /tests/requests/invalid/010.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import LimitRequestHeaders 3 | 4 | request = LimitRequestHeaders 5 | cfg = Config() 6 | cfg.set('limit_request_field_size', 10) 7 | -------------------------------------------------------------------------------- /tests/requests/invalid/011.http: -------------------------------------------------------------------------------- 1 | GET /test HTTP/1.1\r\n 2 | User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n 3 | Host: 0.0.0.0=5000\r\n 4 | Accept: */*\r\n 5 | \r\n 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/011.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import LimitRequestHeaders 3 | 4 | request = LimitRequestHeaders 5 | cfg = Config() 6 | cfg.set('limit_request_fields', 2) 7 | -------------------------------------------------------------------------------- /tests/requests/invalid/012.http: -------------------------------------------------------------------------------- 1 | GET /test HTTP/1.1\r\n 2 | User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n 3 | Host: 0.0.0.0=5000\r\n 4 | Accept: */*\r\n 5 | \r\n 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/012.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import LimitRequestHeaders 3 | 4 | request = LimitRequestHeaders 5 | cfg = Config() 6 | cfg.set('limit_request_field_size', 98) 7 | -------------------------------------------------------------------------------- /tests/requests/invalid/013.http: -------------------------------------------------------------------------------- 1 | GET /test HTTP/1.1\r\n 2 | Accept:\r\n 3 | */*\r\n 4 | \r\n 5 | -------------------------------------------------------------------------------- /tests/requests/invalid/013.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import LimitRequestHeaders 3 | 4 | request = LimitRequestHeaders 5 | cfg = Config() 6 | cfg.set('limit_request_field_size', 14) 7 | 8 | # once this option is removed, this test should not be dropped; 9 | # rather, add something involving unnessessary padding 10 | cfg.set('permit_obsolete_folding', True) 11 | -------------------------------------------------------------------------------- /tests/requests/invalid/014.http: -------------------------------------------------------------------------------- 1 | PUT /stuff/here?foo=bar HTTP/1.0\r\n 2 | CONTENT-LENGTH: -1\r\n 3 | \r\n 4 | {"test": "-1} -------------------------------------------------------------------------------- /tests/requests/invalid/014.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidHeader 2 | 3 | request = InvalidHeader 4 | -------------------------------------------------------------------------------- /tests/requests/invalid/015.http: -------------------------------------------------------------------------------- 1 | POST /stuff/here?foo=bar HTTP/1.0\r\n 2 | CONTENT-LENGTH: bla-bla-bla\r\n 3 | \r\n 4 | {"test": "-1} -------------------------------------------------------------------------------- /tests/requests/invalid/015.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidHeader 2 | 3 | request = InvalidHeader 4 | -------------------------------------------------------------------------------- /tests/requests/invalid/016.http: -------------------------------------------------------------------------------- 1 | PUT s://]ufd/: HTTP/1.1\r\n 2 | \r\n 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/016.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidRequestLine 2 | 3 | request = InvalidRequestLine 4 | -------------------------------------------------------------------------------- /tests/requests/invalid/017.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import LimitRequestHeaders 3 | 4 | cfg = Config() 5 | request = LimitRequestHeaders 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/018.http: -------------------------------------------------------------------------------- 1 | GET /test HTTP/111\r\n 2 | Host: localhost\r\n 3 | \r\n 4 | -------------------------------------------------------------------------------- /tests/requests/invalid/018.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidHTTPVersion 2 | request = InvalidHTTPVersion 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/019.http: -------------------------------------------------------------------------------- 1 | GET /test HTTP/1.1\r\n 2 | X-Forwarded-Proto: https\r\n 3 | X-Forwarded-Ssl: off\r\n 4 | \r\n 5 | -------------------------------------------------------------------------------- /tests/requests/invalid/019.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidSchemeHeaders 3 | 4 | request = InvalidSchemeHeaders 5 | cfg = Config() 6 | cfg.set('forwarded_allow_ips', '*') 7 | -------------------------------------------------------------------------------- /tests/requests/invalid/020.http: -------------------------------------------------------------------------------- 1 | GET /stuff/here?foo=bar HTTP/1.1\r\n 2 | Content-Length : 3\r\n 3 | \r\n 4 | xyz 5 | -------------------------------------------------------------------------------- /tests/requests/invalid/020.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidHeaderName 3 | 4 | cfg = Config() 5 | request = InvalidHeaderName 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/021.http: -------------------------------------------------------------------------------- 1 | GET /stuff/here?foo=bar HTTP/1.1\r\n 2 | Content-Length: 3\r\n 3 | Content-Length: 2\r\n 4 | \r\n 5 | xyz 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/021.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidHeader 3 | 4 | cfg = Config() 5 | request = InvalidHeader 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/022.http: -------------------------------------------------------------------------------- 1 | GET /first HTTP/1.0\r\n 2 | Content-Length: -0\r\n 3 | \r\n -------------------------------------------------------------------------------- /tests/requests/invalid/022.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidHeader 3 | 4 | cfg = Config() 5 | request = InvalidHeader 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/023.http: -------------------------------------------------------------------------------- 1 | GET /first HTTP/1.0\r\n 2 | Content-Length: 0_1\r\n 3 | \r\n -------------------------------------------------------------------------------- /tests/requests/invalid/023.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidHeader 3 | 4 | cfg = Config() 5 | request = InvalidHeader 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/024.http: -------------------------------------------------------------------------------- 1 | GET /first HTTP/1.0\r\n 2 | Content-Length: +1\r\n 3 | \r\n -------------------------------------------------------------------------------- /tests/requests/invalid/024.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidHeader 3 | 4 | cfg = Config() 5 | request = InvalidHeader 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/040.http: -------------------------------------------------------------------------------- 1 | GET /keep/same/as?invalid/040 HTTP/1.0\r\n 2 | Transfer_Encoding: tricked\r\n 3 | Content-Length: 7\r\n 4 | Content_Length: -1E23\r\n 5 | \r\n 6 | tricked\r\n 7 | -------------------------------------------------------------------------------- /tests/requests/invalid/040.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidHeaderName 2 | from gunicorn.config import Config 3 | 4 | cfg = Config() 5 | cfg.set("header_map", "refuse") 6 | 7 | request = InvalidHeaderName 8 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_01.http: -------------------------------------------------------------------------------- 1 | POST /chunked_w_underscore_chunk_size HTTP/1.1\r\n 2 | Transfer-Encoding: chunked\r\n 3 | \r\n 4 | 5\r\n 5 | hello\r\n 6 | 6_0\r\n 7 | world\r\n 8 | 0\r\n 9 | \r\n 10 | POST /after HTTP/1.1\r\n 11 | Transfer-Encoding: identity\r\n 12 | \r\n 13 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_01.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidChunkSize 2 | request = InvalidChunkSize 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_02.http: -------------------------------------------------------------------------------- 1 | POST /chunked_with_prefixed_value HTTP/1.1\r\n 2 | Content-Length: 12\r\n 3 | Transfer-Encoding: \tchunked\r\n 4 | \r\n 5 | 5\r\n 6 | hello\r\n 7 | 6\r\n 8 | world\r\n 9 | \r\n 10 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_02.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidHeader 2 | request = InvalidHeader 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_03.http: -------------------------------------------------------------------------------- 1 | POST /double_chunked HTTP/1.1\r\n 2 | Transfer-Encoding: identity, chunked, identity, chunked\r\n 3 | \r\n 4 | 5\r\n 5 | hello\r\n 6 | 6\r\n 7 | world\r\n 8 | \r\n 9 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_03.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidHeader 2 | request = InvalidHeader 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_04.http: -------------------------------------------------------------------------------- 1 | POST /chunked_twice HTTP/1.1\r\n 2 | Transfer-Encoding: identity\r\n 3 | Transfer-Encoding: chunked\r\n 4 | Transfer-Encoding: identity\r\n 5 | Transfer-Encoding: chunked\r\n 6 | \r\n 7 | 5\r\n 8 | hello\r\n 9 | 6\r\n 10 | world\r\n 11 | \r\n 12 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_04.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidHeader 2 | request = InvalidHeader 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_05.http: -------------------------------------------------------------------------------- 1 | POST /chunked_HTTP_1.0 HTTP/1.0\r\n 2 | Transfer-Encoding: chunked\r\n 3 | \r\n 4 | 5\r\n 5 | hello\r\n 6 | 6\r\n 7 | world\r\n 8 | 0\r\n 9 | Vary: *\r\n 10 | Content-Type: text/plain\r\n 11 | \r\n 12 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_05.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidHeader 2 | request = InvalidHeader 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_06.http: -------------------------------------------------------------------------------- 1 | POST /chunked_not_last HTTP/1.1\r\n 2 | Transfer-Encoding: chunked\r\n 3 | Transfer-Encoding: gzip\r\n 4 | \r\n 5 | 5\r\n 6 | hello\r\n 7 | 6\r\n 8 | world\r\n 9 | \r\n 10 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_06.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidHeader 2 | request = InvalidHeader 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_07.http: -------------------------------------------------------------------------------- 1 | POST /chunked_ambiguous_header_mapping HTTP/1.1\r\n 2 | Transfer_Encoding: gzip\r\n 3 | Transfer-Encoding: chunked\r\n 4 | \r\n 5 | 5\r\n 6 | hello\r\n 7 | 6\r\n 8 | world\r\n 9 | 0\r\n 10 | \r\n 11 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_07.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidHeaderName 2 | from gunicorn.config import Config 3 | 4 | cfg = Config() 5 | cfg.set("header_map", "refuse") 6 | 7 | request = InvalidHeaderName 8 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_08.http: -------------------------------------------------------------------------------- 1 | POST /chunked_not_last HTTP/1.1\r\n 2 | Transfer-Encoding: chunked\r\n 3 | Transfer-Encoding: identity\r\n 4 | \r\n 5 | 5\r\n 6 | hello\r\n 7 | 6\r\n 8 | world\r\n 9 | \r\n 10 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_08.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidHeader 2 | request = InvalidHeader 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_09.http: -------------------------------------------------------------------------------- 1 | POST /chunked_ows_without_ext HTTP/1.1\r\n 2 | Transfer-Encoding: chunked\r\n 3 | \r\n 4 | 5\r\n 5 | hello\r\n 6 | 0 \r\n 7 | \r\n 8 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_09.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidChunkSize 2 | request = InvalidChunkSize 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_10.http: -------------------------------------------------------------------------------- 1 | POST /chunked_ows_before HTTP/1.1\r\n 2 | Transfer-Encoding: chunked\r\n 3 | \r\n 4 | 5\r\n 5 | hello\r\n 6 | 0\r\n 7 | \r\n 8 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_10.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidChunkSize 2 | request = InvalidChunkSize 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_11.http: -------------------------------------------------------------------------------- 1 | POST /chunked_ows_before HTTP/1.1\r\n 2 | Transfer-Encoding: chunked\r\n 3 | \r\n 4 | 5\n;\r\n 5 | hello\r\n 6 | 0\r\n 7 | \r\n 8 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_11.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidChunkSize 2 | request = InvalidChunkSize 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_12.http: -------------------------------------------------------------------------------- 1 | POST /chunked_no_chunk_size_but_ext HTTP/1.1\r\n 2 | Transfer-Encoding: chunked\r\n 3 | \r\n 4 | ;foo=bar\r\n 5 | hello\r\n 6 | 0\r\n 7 | \r\n 8 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_12.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidChunkSize 2 | request = InvalidChunkSize 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_13.http: -------------------------------------------------------------------------------- 1 | POST /chunked_no_chunk_size HTTP/1.1\r\n 2 | Transfer-Encoding: chunked\r\n 3 | \r\n 4 | \r\n 5 | hello\r\n 6 | 0\r\n 7 | \r\n 8 | -------------------------------------------------------------------------------- /tests/requests/invalid/chunked_13.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidChunkSize 2 | request = InvalidChunkSize 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/invalid_field_value_01.http: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1\r\n 2 | Host: x\r\n 3 | Newline: a\n 4 | Content-Length: 26\r\n 5 | GET / HTTP/1.1\n 6 | Host: x\r\n 7 | \r\n 8 | -------------------------------------------------------------------------------- /tests/requests/invalid/invalid_field_value_01.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidHeader 3 | 4 | cfg = Config() 5 | request = InvalidHeader 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/nonascii_01.http: -------------------------------------------------------------------------------- 1 | GETß /germans.. HTTP/1.1\r\n 2 | Content-Length: 3\r\n 3 | \r\n 4 | ÄÄÄ 5 | -------------------------------------------------------------------------------- /tests/requests/invalid/nonascii_01.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidRequestMethod 3 | 4 | cfg = Config() 5 | request = InvalidRequestMethod 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/nonascii_02.http: -------------------------------------------------------------------------------- 1 | GETÿ /french.. HTTP/1.1\r\n 2 | Content-Length: 3\r\n 3 | \r\n 4 | ÄÄÄ 5 | -------------------------------------------------------------------------------- /tests/requests/invalid/nonascii_02.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidRequestMethod 3 | 4 | cfg = Config() 5 | request = InvalidRequestMethod 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/nonascii_03.http: -------------------------------------------------------------------------------- 1 | GET /germans.. HTTP/1.1\r\n 2 | Content-Lengthß: 3\r\n 3 | Content-Length: 3\r\n 4 | \r\n 5 | ÄÄÄ 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/nonascii_03.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidHeaderName 3 | 4 | cfg = Config() 5 | request = InvalidHeaderName 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/nonascii_04.http: -------------------------------------------------------------------------------- 1 | GET /french.. HTTP/1.1\r\n 2 | Content-Lengthÿ: 3\r\n 3 | Content-Length: 3\r\n 4 | \r\n 5 | ÄÄÄ 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/nonascii_04.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidHeaderName 3 | 4 | cfg = Config() 5 | request = InvalidHeaderName 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/obs_fold_01.http: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1\r\n 2 | Long: one\r\n 3 | two\r\n 4 | Host: localhost\r\n 5 | \r\n 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/obs_fold_01.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import ObsoleteFolding 2 | 3 | request = ObsoleteFolding 4 | -------------------------------------------------------------------------------- /tests/requests/invalid/pp_01.http: -------------------------------------------------------------------------------- 1 | PROXY TCP4 192.168.0.1 192.16...\r\n 2 | -------------------------------------------------------------------------------- /tests/requests/invalid/pp_01.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidProxyLine 3 | 4 | cfg = Config() 5 | cfg.set("proxy_protocol", True) 6 | 7 | request = InvalidProxyLine 8 | -------------------------------------------------------------------------------- /tests/requests/invalid/pp_02.http: -------------------------------------------------------------------------------- 1 | PROXY TCP4 192.168.0.1 192.168.0.11 65iii 100000\r\n 2 | -------------------------------------------------------------------------------- /tests/requests/invalid/pp_02.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidProxyLine 3 | 4 | cfg = Config() 5 | cfg.set('proxy_protocol', True) 6 | 7 | request = InvalidProxyLine 8 | -------------------------------------------------------------------------------- /tests/requests/invalid/prefix_01.http: -------------------------------------------------------------------------------- 1 | GET\0PROXY /foo HTTP/1.1\r\n 2 | \r\n 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/prefix_01.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidRequestMethod 2 | request = InvalidRequestMethod -------------------------------------------------------------------------------- /tests/requests/invalid/prefix_02.http: -------------------------------------------------------------------------------- 1 | GET\0 /foo HTTP/1.1\r\n 2 | \r\n 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/prefix_02.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidRequestMethod 2 | request = InvalidRequestMethod -------------------------------------------------------------------------------- /tests/requests/invalid/prefix_03.http: -------------------------------------------------------------------------------- 1 | GET /stuff/here?foo=bar HTTP/1.1\r\n 2 | Content-Length: 0 1\r\n 3 | \r\n 4 | x 5 | -------------------------------------------------------------------------------- /tests/requests/invalid/prefix_03.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidHeader 3 | 4 | cfg = Config() 5 | request = InvalidHeader 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/prefix_04.http: -------------------------------------------------------------------------------- 1 | GET /stuff/here?foo=bar HTTP/1.1\r\n 2 | Content-Length: 3 1\r\n 3 | \r\n 4 | xyz 5 | abc123 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/prefix_04.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidHeader 3 | 4 | cfg = Config() 5 | request = InvalidHeader 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/prefix_05.http: -------------------------------------------------------------------------------- 1 | GET: /stuff/here?foo=bar HTTP/1.1\r\n 2 | Content-Length: 3\r\n 3 | \r\n 4 | xyz 5 | -------------------------------------------------------------------------------- /tests/requests/invalid/prefix_05.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidRequestMethod 3 | 4 | cfg = Config() 5 | request = InvalidRequestMethod 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/prefix_06.http: -------------------------------------------------------------------------------- 1 | GET /the/future HTTP/1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111.1\r\n 2 | Content-Length: 7\r\n 3 | \r\n 4 | Old Man 5 | -------------------------------------------------------------------------------- /tests/requests/invalid/prefix_06.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | from gunicorn.http.errors import InvalidHTTPVersion 3 | 4 | cfg = Config() 5 | request = InvalidHTTPVersion 6 | -------------------------------------------------------------------------------- /tests/requests/invalid/version_01.http: -------------------------------------------------------------------------------- 1 | GET /foo HTTP/0.99\r\n 2 | \r\n 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/version_01.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidHTTPVersion 2 | request = InvalidHTTPVersion 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/version_02.http: -------------------------------------------------------------------------------- 1 | GET /foo HTTP/2.0\r\n 2 | \r\n 3 | -------------------------------------------------------------------------------- /tests/requests/invalid/version_02.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import InvalidHTTPVersion 2 | request = InvalidHTTPVersion 3 | -------------------------------------------------------------------------------- /tests/requests/valid/001.http: -------------------------------------------------------------------------------- 1 | PUT /stuff/here?foo=bar HTTP/1.0\r\n 2 | Server: http://127.0.0.1:5984\r\n 3 | Content-Type: application/json\r\n 4 | Content-Length: 14\r\n 5 | \r\n 6 | {"nom": "nom"} 7 | -------------------------------------------------------------------------------- /tests/requests/valid/001.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "PUT", 3 | "uri": uri("/stuff/here?foo=bar"), 4 | "version": (1, 0), 5 | "headers": [ 6 | ("SERVER", "http://127.0.0.1:5984"), 7 | ("CONTENT-TYPE", "application/json"), 8 | ("CONTENT-LENGTH", "14") 9 | ], 10 | "body": b'{"nom": "nom"}' 11 | } 12 | -------------------------------------------------------------------------------- /tests/requests/valid/002.http: -------------------------------------------------------------------------------- 1 | GET /test HTTP/1.1\r\n 2 | User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n 3 | Host: 0.0.0.0=5000\r\n 4 | Accept: */*\r\n 5 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/002.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "GET", 3 | "uri": uri("/test"), 4 | "version": (1, 1), 5 | "headers": [ 6 | ("USER-AGENT", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1"), 7 | ("HOST", "0.0.0.0=5000"), 8 | ("ACCEPT", "*/*") 9 | ], 10 | "body": b"" 11 | } 12 | -------------------------------------------------------------------------------- /tests/requests/valid/003.http: -------------------------------------------------------------------------------- 1 | GET /favicon.ico HTTP/1.1\r\n 2 | Host: 0.0.0.0=5000\r\n 3 | User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n 4 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n 5 | Accept-Language: en-us,en;q=0.5\r\n 6 | Accept-Encoding: gzip,deflate\r\n 7 | Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n 8 | Keep-Alive: 300\r\n 9 | Connection: keep-alive\r\n 10 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/003.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "GET", 3 | "uri": uri("/favicon.ico"), 4 | "version": (1, 1), 5 | "headers": [ 6 | ("HOST", "0.0.0.0=5000"), 7 | ("USER-AGENT", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0"), 8 | ("ACCEPT", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), 9 | ("ACCEPT-LANGUAGE", "en-us,en;q=0.5"), 10 | ("ACCEPT-ENCODING", "gzip,deflate"), 11 | ("ACCEPT-CHARSET", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"), 12 | ("KEEP-ALIVE", "300"), 13 | ("CONNECTION", "keep-alive") 14 | ], 15 | "body": b"" 16 | } 17 | -------------------------------------------------------------------------------- /tests/requests/valid/004.http: -------------------------------------------------------------------------------- 1 | GET /silly HTTP/1.1\r\n 2 | aaaaaaaaaaaaa:++++++++++\r\n 3 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/004.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "GET", 3 | "uri": uri("/silly"), 4 | "version": (1, 1), 5 | "headers": [ 6 | ("AAAAAAAAAAAAA", "++++++++++") 7 | ], 8 | "body": b"" 9 | } 10 | -------------------------------------------------------------------------------- /tests/requests/valid/005.http: -------------------------------------------------------------------------------- 1 | GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n 2 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/005.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "GET", 3 | "uri": uri("/forums/1/topics/2375?page=1#posts-17408"), 4 | "version": (1, 1), 5 | "headers": [], 6 | "body": b"" 7 | } 8 | -------------------------------------------------------------------------------- /tests/requests/valid/006.http: -------------------------------------------------------------------------------- 1 | GET /get_no_headers_no_body/world HTTP/1.1\r\n 2 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/006.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "GET", 3 | "uri": uri("/get_no_headers_no_body/world"), 4 | "version": (1, 1), 5 | "headers": [], 6 | "body": b"" 7 | } 8 | -------------------------------------------------------------------------------- /tests/requests/valid/007.http: -------------------------------------------------------------------------------- 1 | GET /get_one_header_no_body HTTP/1.1\r\n 2 | Accept: */*\r\n 3 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/007.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "GET", 3 | "uri": uri("/get_one_header_no_body"), 4 | "version": (1, 1), 5 | "headers": [ 6 | ("ACCEPT", "*/*") 7 | ], 8 | "body": b"" 9 | } 10 | -------------------------------------------------------------------------------- /tests/requests/valid/008.http: -------------------------------------------------------------------------------- 1 | GET /unusual_content_length HTTP/1.0\r\n 2 | conTENT-Length: 5\r\n 3 | \r\n 4 | HELLO -------------------------------------------------------------------------------- /tests/requests/valid/008.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "GET", 3 | "uri": uri("/unusual_content_length"), 4 | "version": (1, 0), 5 | "headers": [ 6 | ("CONTENT-LENGTH", "5") 7 | ], 8 | "body": b"HELLO" 9 | } 10 | -------------------------------------------------------------------------------- /tests/requests/valid/009.http: -------------------------------------------------------------------------------- 1 | POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n 2 | Accept: */*\r\n 3 | Transfer-Encoding: identity\r\n 4 | Content-Length: 5\r\n 5 | \r\n 6 | World -------------------------------------------------------------------------------- /tests/requests/valid/009.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "POST", 3 | "uri": uri("/post_identity_body_world?q=search#hey"), 4 | "version": (1, 1), 5 | "headers": [ 6 | ("ACCEPT", "*/*"), 7 | ("TRANSFER-ENCODING", "identity"), 8 | ("CONTENT-LENGTH", "5") 9 | ], 10 | "body": b"World" 11 | } 12 | -------------------------------------------------------------------------------- /tests/requests/valid/010.http: -------------------------------------------------------------------------------- 1 | POST /post_chunked_all_your_base HTTP/1.1\r\n 2 | Transfer-Encoding: chunked\r\n 3 | \r\n 4 | 1e\r\n 5 | all your base are belong to us\r\n 6 | 0\r\n 7 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/010.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "POST", 3 | "uri": uri("/post_chunked_all_your_base"), 4 | "version": (1, 1), 5 | "headers": [ 6 | ("TRANSFER-ENCODING", "chunked"), 7 | ], 8 | "body": b"all your base are belong to us" 9 | } 10 | -------------------------------------------------------------------------------- /tests/requests/valid/011.http: -------------------------------------------------------------------------------- 1 | POST /two_chunks_mult_zero_end HTTP/1.1\r\n 2 | Transfer-Encoding: chunked\r\n 3 | \r\n 4 | 5\r\n 5 | hello\r\n 6 | 6\r\n 7 | world\r\n 8 | 000\r\n 9 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/011.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "POST", 3 | "uri": uri("/two_chunks_mult_zero_end"), 4 | "version": (1, 1), 5 | "headers": [ 6 | ("TRANSFER-ENCODING", "chunked") 7 | ], 8 | "body": b"hello world" 9 | } 10 | -------------------------------------------------------------------------------- /tests/requests/valid/012.http: -------------------------------------------------------------------------------- 1 | POST /chunked_w_trailing_headers HTTP/1.1\r\n 2 | Transfer-Encoding: chunked\r\n 3 | \r\n 4 | 5\r\n 5 | hello\r\n 6 | 6\r\n 7 | world\r\n 8 | 0\r\n 9 | Vary: *\r\n 10 | Content-Type: text/plain\r\n 11 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/012.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "POST", 3 | "uri": uri("/chunked_w_trailing_headers"), 4 | "version": (1, 1), 5 | "headers": [ 6 | ("TRANSFER-ENCODING", "chunked") 7 | ], 8 | "body": b"hello world", 9 | "trailers": [ 10 | ("VARY", "*"), 11 | ("CONTENT-TYPE", "text/plain") 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /tests/requests/valid/013.http: -------------------------------------------------------------------------------- 1 | POST /chunked_w_extensions HTTP/1.1\r\n 2 | Transfer-Encoding: chunked\r\n 3 | \r\n 4 | 5; some; parameters=stuff\r\n 5 | hello\r\n 6 | 6; blahblah; blah\r\n 7 | world\r\n 8 | 0\r\n 9 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/013.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "POST", 3 | "uri": uri("/chunked_w_extensions"), 4 | "version": (1, 1), 5 | "headers": [ 6 | ("TRANSFER-ENCODING", "chunked") 7 | ], 8 | "body": b"hello world" 9 | } 10 | -------------------------------------------------------------------------------- /tests/requests/valid/014.http: -------------------------------------------------------------------------------- 1 | GET /with_"quotes"?foo="bar" HTTP/1.1\r\n 2 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/014.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "GET", 3 | "uri": uri('/with_"quotes"?foo="bar"'), 4 | "version": (1, 1), 5 | "headers": [], 6 | "body": b"" 7 | } 8 | -------------------------------------------------------------------------------- /tests/requests/valid/015.http: -------------------------------------------------------------------------------- 1 | GET /test HTTP/1.0\r\n 2 | Host: 0.0.0.0:5000\r\n 3 | User-Agent: ApacheBench/2.3\r\n 4 | Accept: */*\r\n 5 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/015.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "GET", 3 | "uri": uri("/test"), 4 | "version": (1, 0), 5 | "headers": [ 6 | ("HOST", "0.0.0.0:5000"), 7 | ("USER-AGENT", "ApacheBench/2.3"), 8 | ("ACCEPT", "*/*") 9 | ], 10 | "body": b"" 11 | } 12 | -------------------------------------------------------------------------------- /tests/requests/valid/017.http: -------------------------------------------------------------------------------- 1 | GET /stuff/here?foo=bar HTTP/1.0\r\n 2 | If-Match: bazinga!\r\n 3 | If-Match: large-sound\r\n 4 | \r\n 5 | -------------------------------------------------------------------------------- /tests/requests/valid/017.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "GET", 3 | "uri": uri("/stuff/here?foo=bar"), 4 | "version": (1, 0), 5 | "headers": [ 6 | ("IF-MATCH", "bazinga!"), 7 | ("IF-MATCH", "large-sound") 8 | ], 9 | "body": b"" 10 | } 11 | -------------------------------------------------------------------------------- /tests/requests/valid/018.http: -------------------------------------------------------------------------------- 1 | GET /first HTTP/1.1\r\n 2 | \r\n 3 | GET /second HTTP/1.1\r\n 4 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/018.py: -------------------------------------------------------------------------------- 1 | req1 = { 2 | "method": "GET", 3 | "uri": uri("/first"), 4 | "version": (1, 1), 5 | "headers": [], 6 | "body": b"" 7 | } 8 | 9 | req2 = { 10 | "method": "GET", 11 | "uri": uri("/second"), 12 | "version": (1, 1), 13 | "headers": [], 14 | "body": b"" 15 | } 16 | 17 | request = [req1, req2] 18 | -------------------------------------------------------------------------------- /tests/requests/valid/019.http: -------------------------------------------------------------------------------- 1 | GET /first HTTP/1.0\r\n 2 | \r\n 3 | GET /second HTTP/1.1\r\n 4 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/019.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "GET", 3 | "uri": uri("/first"), 4 | "version": (1, 0), 5 | "headers": [], 6 | "body": b"" 7 | } 8 | -------------------------------------------------------------------------------- /tests/requests/valid/020.http: -------------------------------------------------------------------------------- 1 | GET /first HTTP/1.0\r\n 2 | Content-Length: 24\r\n 3 | \r\n 4 | GET /second HTTP/1.1\r\n 5 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/020.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "GET", 3 | "uri": uri("/first"), 4 | "version": (1, 0), 5 | "headers": [('CONTENT-LENGTH', '24')], 6 | "body": b"GET /second HTTP/1.1\r\n\r\n" 7 | } 8 | -------------------------------------------------------------------------------- /tests/requests/valid/021.http: -------------------------------------------------------------------------------- 1 | GET /first HTTP/1.1\r\n 2 | Connection: Close\r\n 3 | \r\n 4 | GET /second HTTP/1.1\r\n 5 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/021.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "GET", 3 | "uri": uri("/first"), 4 | "version": (1, 1), 5 | "headers": [("CONNECTION", "Close")], 6 | "body": b"" 7 | } 8 | -------------------------------------------------------------------------------- /tests/requests/valid/022.http: -------------------------------------------------------------------------------- 1 | GET /first HTTP/1.0\r\n 2 | Connection: Keep-Alive\r\n 3 | \r\n 4 | GET /second HTTP/1.1\r\n 5 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/022.py: -------------------------------------------------------------------------------- 1 | req1 = { 2 | "method": "GET", 3 | "uri": uri("/first"), 4 | "version": (1, 0), 5 | "headers": [("CONNECTION", "Keep-Alive")], 6 | "body": b"" 7 | } 8 | 9 | req2 = { 10 | "method": "GET", 11 | "uri": uri("/second"), 12 | "version": (1, 1), 13 | "headers": [], 14 | "body": b"" 15 | } 16 | 17 | request = [req1, req2] 18 | -------------------------------------------------------------------------------- /tests/requests/valid/023.http: -------------------------------------------------------------------------------- 1 | POST /two_chunks_mult_zero_end HTTP/1.1\r\n 2 | Transfer-Encoding: chunked\r\n 3 | \r\n 4 | 5\r\n 5 | hello\r\n 6 | 6\r\n 7 | world\r\n 8 | 000\r\n 9 | \r\n 10 | GET /second HTTP/1.1\r\n 11 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/023.py: -------------------------------------------------------------------------------- 1 | req1 = { 2 | "method": "POST", 3 | "uri": uri("/two_chunks_mult_zero_end"), 4 | "version": (1, 1), 5 | "headers": [ 6 | ("TRANSFER-ENCODING", "chunked") 7 | ], 8 | "body": b"hello world" 9 | } 10 | 11 | req2 = { 12 | "method": "GET", 13 | "uri": uri("/second"), 14 | "version": (1, 1), 15 | "headers": [], 16 | "body": b"" 17 | } 18 | 19 | request = [req1, req2] 20 | -------------------------------------------------------------------------------- /tests/requests/valid/025.http: -------------------------------------------------------------------------------- 1 | POST /chunked HTTP/1.1\r\n 2 | Transfer-Encoding: gzip\r\n 3 | Transfer-Encoding: chunked\r\n 4 | \r\n 5 | 5\r\n 6 | hello\r\n 7 | 6\r\n 8 | world\r\n 9 | 0\r\n 10 | \r\n 11 | -------------------------------------------------------------------------------- /tests/requests/valid/025.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "POST", 3 | "uri": uri("/chunked"), 4 | "version": (1, 1), 5 | "headers": [ 6 | ('TRANSFER-ENCODING', 'gzip'), 7 | ('TRANSFER-ENCODING', 'chunked') 8 | ], 9 | "body": b"hello world" 10 | } 11 | -------------------------------------------------------------------------------- /tests/requests/valid/025_line.http: -------------------------------------------------------------------------------- 1 | POST /chunked HTTP/1.1\r\n 2 | Transfer-Encoding: gzip,chunked\r\n 3 | \r\n 4 | 5\r\n 5 | hello\r\n 6 | 6\r\n 7 | world\r\n 8 | 0\r\n 9 | \r\n 10 | -------------------------------------------------------------------------------- /tests/requests/valid/025_line.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "POST", 3 | "uri": uri("/chunked"), 4 | "version": (1, 1), 5 | "headers": [ 6 | ('TRANSFER-ENCODING', 'gzip,chunked') 7 | 8 | ], 9 | "body": b"hello world" 10 | } 11 | -------------------------------------------------------------------------------- /tests/requests/valid/027.http: -------------------------------------------------------------------------------- 1 | GET /à%20k HTTP/1.0\r\n 2 | \r\n 3 | -------------------------------------------------------------------------------- /tests/requests/valid/027.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "GET", 3 | "uri": uri("/\xc3\xa0%20k"), 4 | "version": (1, 0), 5 | "headers": [ 6 | ], 7 | "body": '' 8 | } 9 | -------------------------------------------------------------------------------- /tests/requests/valid/028.http: -------------------------------------------------------------------------------- 1 | GET /stuff/here?foo=bar HTTP/1.1\r\n 2 | Content-Length : 3\r\n 3 | \r\n 4 | xyz -------------------------------------------------------------------------------- /tests/requests/valid/028.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | 3 | cfg = Config() 4 | cfg.set("strip_header_spaces", True) 5 | 6 | request = { 7 | "method": "GET", 8 | "uri": uri("/stuff/here?foo=bar"), 9 | "version": (1, 1), 10 | "headers": [ 11 | ("CONTENT-LENGTH", "3"), 12 | ], 13 | "body": b"xyz" 14 | } -------------------------------------------------------------------------------- /tests/requests/valid/029.http: -------------------------------------------------------------------------------- 1 | GET /stuff/here?foo=bar HTTP/1.1\r\n 2 | Transfer-Encoding: identity\r\n 3 | Transfer-Encoding: chunked\r\n 4 | \r\n 5 | 5\r\n 6 | hello\r\n 7 | 000\r\n 8 | -------------------------------------------------------------------------------- /tests/requests/valid/029.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | 3 | cfg = Config() 4 | 5 | request = { 6 | "method": "GET", 7 | "uri": uri("/stuff/here?foo=bar"), 8 | "version": (1, 1), 9 | "headers": [ 10 | ('TRANSFER-ENCODING', 'identity'), 11 | ('TRANSFER-ENCODING', 'chunked'), 12 | ], 13 | "body": b"hello" 14 | } 15 | -------------------------------------------------------------------------------- /tests/requests/valid/030.http: -------------------------------------------------------------------------------- 1 | GET /stuff/here?foo=bar HTTP/1.1\r\n 2 | Transfer-Encoding: identity\r\n 3 | Transfer-Encoding: chunked\r\n 4 | \r\n 5 | 5\r\n 6 | hello\r\n 7 | 000\r\n 8 | -------------------------------------------------------------------------------- /tests/requests/valid/030.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | 3 | cfg = Config() 4 | 5 | request = { 6 | "method": "GET", 7 | "uri": uri("/stuff/here?foo=bar"), 8 | "version": (1, 1), 9 | "headers": [ 10 | ('TRANSFER-ENCODING', 'identity'), 11 | ('TRANSFER-ENCODING', 'chunked') 12 | ], 13 | "body": b"hello" 14 | } 15 | -------------------------------------------------------------------------------- /tests/requests/valid/031.http: -------------------------------------------------------------------------------- 1 | -BLARGH /foo HTTP/1.1\r\n 2 | \r\n 3 | -------------------------------------------------------------------------------- /tests/requests/valid/031.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "-BLARGH", 3 | "uri": uri("/foo"), 4 | "version": (1, 1), 5 | "headers": [], 6 | "body": b"" 7 | } 8 | -------------------------------------------------------------------------------- /tests/requests/valid/031compat.http: -------------------------------------------------------------------------------- 1 | -blargh /foo HTTP/1.1\r\n 2 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/031compat.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | 3 | cfg = Config() 4 | cfg.set("permit_unconventional_http_method", True) 5 | cfg.set("casefold_http_method", True) 6 | 7 | request = { 8 | "method": "-BLARGH", 9 | "uri": uri("/foo"), 10 | "version": (1, 1), 11 | "headers": [], 12 | "body": b"" 13 | } 14 | -------------------------------------------------------------------------------- /tests/requests/valid/031compat2.http: -------------------------------------------------------------------------------- 1 | -blargh /foo HTTP/1.1\r\n 2 | \r\n 3 | -------------------------------------------------------------------------------- /tests/requests/valid/031compat2.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | 3 | cfg = Config() 4 | cfg.set("permit_unconventional_http_method", True) 5 | 6 | request = { 7 | "method": "-blargh", 8 | "uri": uri("/foo"), 9 | "version": (1, 1), 10 | "headers": [], 11 | "body": b"" 12 | } 13 | -------------------------------------------------------------------------------- /tests/requests/valid/040.http: -------------------------------------------------------------------------------- 1 | GET /keep/same/as?invalid/040 HTTP/1.0\r\n 2 | Transfer_Encoding: tricked\r\n 3 | Content-Length: 7\r\n 4 | Content_Length: -1E23\r\n 5 | \r\n 6 | tricked\r\n 7 | -------------------------------------------------------------------------------- /tests/requests/valid/040.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "GET", 3 | "uri": uri("/keep/same/as?invalid/040"), 4 | "version": (1, 0), 5 | "headers": [ 6 | ("CONTENT-LENGTH", "7") 7 | ], 8 | "body": b'tricked' 9 | } 10 | -------------------------------------------------------------------------------- /tests/requests/valid/040_compat.http: -------------------------------------------------------------------------------- 1 | GET /keep/same/as?invalid/040 HTTP/1.0\r\n 2 | Transfer_Encoding: tricked\r\n 3 | Content-Length: 7\r\n 4 | Content_Length: -1E23\r\n 5 | \r\n 6 | tricked\r\n 7 | -------------------------------------------------------------------------------- /tests/requests/valid/040_compat.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | 3 | cfg = Config() 4 | cfg.set("header_map", "dangerous") 5 | 6 | request = { 7 | "method": "GET", 8 | "uri": uri("/keep/same/as?invalid/040"), 9 | "version": (1, 0), 10 | "headers": [ 11 | ("TRANSFER_ENCODING", "tricked"), 12 | ("CONTENT-LENGTH", "7"), 13 | ("CONTENT_LENGTH", "-1E23"), 14 | ], 15 | "body": b'tricked' 16 | } 17 | -------------------------------------------------------------------------------- /tests/requests/valid/100.http: -------------------------------------------------------------------------------- 1 | GET ///keeping_slashes HTTP/1.1\r\n 2 | \r\n 3 | -------------------------------------------------------------------------------- /tests/requests/valid/100.py: -------------------------------------------------------------------------------- 1 | request = { 2 | "method": "GET", 3 | "uri": uri("///keeping_slashes"), 4 | "version": (1, 1), 5 | "headers": [], 6 | "body": b"" 7 | } 8 | -------------------------------------------------------------------------------- /tests/requests/valid/compat_obs_fold.http: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1\r\n 2 | Long: one\r\n 3 | two\r\n 4 | Host: localhost\r\n 5 | \r\n 6 | -------------------------------------------------------------------------------- /tests/requests/valid/compat_obs_fold.py: -------------------------------------------------------------------------------- 1 | from gunicorn.http.errors import ObsoleteFolding 2 | from gunicorn.config import Config 3 | 4 | cfg = Config() 5 | cfg.set('permit_obsolete_folding', True) 6 | 7 | request = { 8 | "method": "GET", 9 | "uri": uri("/"), 10 | "version": (1, 1), 11 | "headers": [ 12 | ("LONG", "one two"), 13 | ("HOST", "localhost"), 14 | ], 15 | "body": b"" 16 | } 17 | -------------------------------------------------------------------------------- /tests/requests/valid/compat_obs_fold_huge.http: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1\r\n 2 | X-SSL-Cert: -----BEGIN CERTIFICATE-----\r\n 3 | MIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n 4 | ETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n 5 | AkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n 6 | dWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n 7 | SzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n 8 | BAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n 9 | BQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n 10 | W51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n 11 | gW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n 12 | 0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n 13 | u2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n 14 | wgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n 15 | 1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n 16 | BglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n 17 | VR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n 18 | loCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n 19 | aWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n 20 | 9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n 21 | IjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n 22 | BgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n 23 | cHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4Qg\r\n 24 | EDBDAWLmh0dHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC\r\n 25 | 5jcmwwPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n 26 | Y3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n 27 | XCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n 28 | UO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n 29 | hTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n 30 | wTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n 31 | Yhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n 32 | RA==\r\n 33 | -----END CERTIFICATE-----\r\n 34 | \r\n -------------------------------------------------------------------------------- /tests/requests/valid/compat_obs_fold_huge.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | 3 | cfg = Config() 4 | cfg.set('permit_obsolete_folding', True) 5 | 6 | certificate = """-----BEGIN CERTIFICATE----- 7 | MIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx 8 | ETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT 9 | AkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu 10 | dWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV 11 | SzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV 12 | BAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB 13 | BQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF 14 | W51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR 15 | gW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL 16 | 0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP 17 | u2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR 18 | wgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG 19 | 1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs 20 | BglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD 21 | VR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj 22 | loCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj 23 | aWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG 24 | 9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE 25 | IjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO 26 | BgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1 27 | cHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4Qg 28 | EDBDAWLmh0dHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC 29 | 5jcmwwPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv 30 | Y3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3 31 | XCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8 32 | UO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk 33 | hTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK 34 | wTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu 35 | Yhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3 36 | RA== 37 | -----END CERTIFICATE-----""".replace("\n", "") 38 | 39 | request = { 40 | "method": "GET", 41 | "uri": uri("/"), 42 | "version": (1, 1), 43 | "headers": [("X-SSL-CERT", certificate)], 44 | "body": b"" 45 | } 46 | -------------------------------------------------------------------------------- /tests/requests/valid/padding_01.http: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1\r\n 2 | Host: localhost\r\n 3 | Name: \t value \t \r\n 4 | \r\n 5 | -------------------------------------------------------------------------------- /tests/requests/valid/padding_01.py: -------------------------------------------------------------------------------- 1 | 2 | request = { 3 | "method": "GET", 4 | "uri": uri("/"), 5 | "version": (1, 1), 6 | "headers": [ 7 | ("HOST", "localhost"), 8 | ("NAME", "value") 9 | ], 10 | "body": b"", 11 | } 12 | -------------------------------------------------------------------------------- /tests/requests/valid/pp_01.http: -------------------------------------------------------------------------------- 1 | PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\n 2 | GET /stuff/here?foo=bar HTTP/1.0\r\n 3 | Server: http://127.0.0.1:5984\r\n 4 | Content-Type: application/json\r\n 5 | Content-Length: 14\r\n 6 | \r\n 7 | {"nom": "nom"} 8 | -------------------------------------------------------------------------------- /tests/requests/valid/pp_01.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | 3 | cfg = Config() 4 | cfg.set('proxy_protocol', True) 5 | 6 | request = { 7 | "method": "GET", 8 | "uri": uri("/stuff/here?foo=bar"), 9 | "version": (1, 0), 10 | "headers": [ 11 | ("SERVER", "http://127.0.0.1:5984"), 12 | ("CONTENT-TYPE", "application/json"), 13 | ("CONTENT-LENGTH", "14") 14 | ], 15 | "body": b'{"nom": "nom"}' 16 | } 17 | -------------------------------------------------------------------------------- /tests/requests/valid/pp_02.http: -------------------------------------------------------------------------------- 1 | PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\n 2 | GET /stuff/here?foo=bar HTTP/1.1\r\n 3 | Server: http://127.0.0.1:5984\r\n 4 | Content-Type: application/json\r\n 5 | Content-Length: 14\r\n 6 | Connection: keep-alive\r\n 7 | \r\n 8 | {"nom": "nom"} 9 | POST /post_chunked_all_your_base HTTP/1.1\r\n 10 | Transfer-Encoding: chunked\r\n 11 | \r\n 12 | 1e\r\n 13 | all your base are belong to us\r\n 14 | 0\r\n 15 | \r\n 16 | -------------------------------------------------------------------------------- /tests/requests/valid/pp_02.py: -------------------------------------------------------------------------------- 1 | from gunicorn.config import Config 2 | 3 | cfg = Config() 4 | cfg.set("proxy_protocol", True) 5 | 6 | req1 = { 7 | "method": "GET", 8 | "uri": uri("/stuff/here?foo=bar"), 9 | "version": (1, 1), 10 | "headers": [ 11 | ("SERVER", "http://127.0.0.1:5984"), 12 | ("CONTENT-TYPE", "application/json"), 13 | ("CONTENT-LENGTH", "14"), 14 | ("CONNECTION", "keep-alive") 15 | ], 16 | "body": b'{"nom": "nom"}' 17 | } 18 | 19 | 20 | req2 = { 21 | "method": "POST", 22 | "uri": uri("/post_chunked_all_your_base"), 23 | "version": (1, 1), 24 | "headers": [ 25 | ("TRANSFER-ENCODING", "chunked"), 26 | ], 27 | "body": b"all your base are belong to us" 28 | } 29 | 30 | request = [req1, req2] 31 | -------------------------------------------------------------------------------- /tests/support.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import sys 3 | import unittest 4 | import platform 5 | from wsgiref.validate import validator 6 | 7 | HOST = "127.0.0.1" 8 | 9 | 10 | def create_app(name="World", count=1): 11 | message = (('Hello, %s!\n' % name) * count).encode("utf8") 12 | length = str(len(message)) 13 | 14 | @validator 15 | def app(environ, start_response): 16 | """Simplest possible application object""" 17 | 18 | status = '200 OK' 19 | 20 | response_headers = [ 21 | ('Content-type', 'text/plain'), 22 | ('Content-Length', length), 23 | ] 24 | start_response(status, response_headers) 25 | return iter([message]) 26 | 27 | return app 28 | 29 | 30 | app = application = create_app() 31 | none_app = None 32 | 33 | 34 | def error_factory(): 35 | raise TypeError("inner") 36 | 37 | 38 | def requires_mac_ver(*min_version): 39 | """Decorator raising SkipTest if the OS is Mac OS X and the OS X 40 | version if less than min_version. 41 | 42 | For example, @requires_mac_ver(10, 5) raises SkipTest if the OS X version 43 | is lesser than 10.5. 44 | """ 45 | def decorator(func): 46 | @functools.wraps(func) 47 | def wrapper(*args, **kw): 48 | if sys.platform == 'darwin': 49 | version_txt = platform.mac_ver()[0] 50 | try: 51 | version = tuple(map(int, version_txt.split('.'))) 52 | except ValueError: 53 | pass 54 | else: 55 | if version < min_version: 56 | min_version_txt = '.'.join(map(str, min_version)) 57 | raise unittest.SkipTest( 58 | "Mac OS X %s or higher required, not %s" 59 | % (min_version_txt, version_txt)) 60 | return func(*args, **kw) 61 | wrapper.min_version = min_version 62 | return wrapper 63 | return decorator 64 | -------------------------------------------------------------------------------- /tests/t.py: -------------------------------------------------------------------------------- 1 | # Copyright 2009 Paul J. Davis 2 | # 3 | # This file is part of gunicorn released under the MIT license. 4 | # See the NOTICE for more information. 5 | 6 | import io 7 | import os 8 | import tempfile 9 | 10 | dirname = os.path.dirname(__file__) 11 | 12 | from gunicorn.http.parser import RequestParser 13 | 14 | 15 | def data_source(fname): 16 | buf = io.BytesIO() 17 | with open(fname) as handle: 18 | for line in handle: 19 | line = line.rstrip("\n").replace("\\r\\n", "\r\n") 20 | buf.write(line.encode('latin1')) 21 | return buf 22 | 23 | 24 | class request: 25 | def __init__(self, name): 26 | self.fname = os.path.join(dirname, "requests", name) 27 | 28 | def __call__(self, func): 29 | def run(): 30 | src = data_source(self.fname) 31 | func(src, RequestParser(src, None, None)) 32 | run.func_name = func.func_name 33 | return run 34 | 35 | 36 | class FakeSocket: 37 | 38 | def __init__(self, data): 39 | self.tmp = tempfile.TemporaryFile() 40 | if data: 41 | self.tmp.write(data.getvalue()) 42 | self.tmp.flush() 43 | self.tmp.seek(0) 44 | 45 | def fileno(self): 46 | return self.tmp.fileno() 47 | 48 | def len(self): 49 | return self.tmp.len 50 | 51 | def recv(self, length=None): 52 | return self.tmp.read(length) 53 | 54 | def recv_into(self, buf, length): 55 | tmp_buffer = self.tmp.read(length) 56 | v = len(tmp_buffer) 57 | for i, c in enumerate(tmp_buffer): 58 | buf[i] = c 59 | return v 60 | 61 | def send(self, data): 62 | self.tmp.write(data) 63 | self.tmp.flush() 64 | 65 | def seek(self, offset, whence=0): 66 | self.tmp.seek(offset, whence) 67 | -------------------------------------------------------------------------------- /tests/test_invalid_requests.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | import glob 6 | import os 7 | 8 | import pytest 9 | 10 | import treq 11 | 12 | dirname = os.path.dirname(__file__) 13 | reqdir = os.path.join(dirname, "requests", "invalid") 14 | httpfiles = glob.glob(os.path.join(reqdir, "*.http")) 15 | 16 | 17 | @pytest.mark.parametrize("fname", httpfiles) 18 | def test_http_parser(fname): 19 | env = treq.load_py(os.path.splitext(fname)[0] + ".py") 20 | 21 | expect = env["request"] 22 | cfg = env["cfg"] 23 | req = treq.badrequest(fname) 24 | 25 | with pytest.raises(expect): 26 | req.check(cfg) 27 | -------------------------------------------------------------------------------- /tests/test_logger.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from types import SimpleNamespace 3 | 4 | import pytest 5 | 6 | from gunicorn.config import Config 7 | from gunicorn.glogging import Logger 8 | 9 | 10 | def test_atoms_defaults(): 11 | response = SimpleNamespace( 12 | status='200', response_length=1024, 13 | headers=(('Content-Type', 'application/json'),), sent=1024, 14 | ) 15 | request = SimpleNamespace(headers=(('Accept', 'application/json'),)) 16 | environ = { 17 | 'REQUEST_METHOD': 'GET', 'RAW_URI': '/my/path?foo=bar', 18 | 'PATH_INFO': '/my/path', 'QUERY_STRING': 'foo=bar', 19 | 'SERVER_PROTOCOL': 'HTTP/1.1', 20 | } 21 | logger = Logger(Config()) 22 | atoms = logger.atoms(response, request, environ, datetime.timedelta(seconds=1)) 23 | assert isinstance(atoms, dict) 24 | assert atoms['r'] == 'GET /my/path?foo=bar HTTP/1.1' 25 | assert atoms['m'] == 'GET' 26 | assert atoms['U'] == '/my/path' 27 | assert atoms['q'] == 'foo=bar' 28 | assert atoms['H'] == 'HTTP/1.1' 29 | assert atoms['b'] == '1024' 30 | assert atoms['B'] == 1024 31 | assert atoms['{accept}i'] == 'application/json' 32 | assert atoms['{content-type}o'] == 'application/json' 33 | 34 | 35 | def test_atoms_zero_bytes(): 36 | response = SimpleNamespace( 37 | status='200', response_length=0, 38 | headers=(('Content-Type', 'application/json'),), sent=0, 39 | ) 40 | request = SimpleNamespace(headers=(('Accept', 'application/json'),)) 41 | environ = { 42 | 'REQUEST_METHOD': 'GET', 'RAW_URI': '/my/path?foo=bar', 43 | 'PATH_INFO': '/my/path', 'QUERY_STRING': 'foo=bar', 44 | 'SERVER_PROTOCOL': 'HTTP/1.1', 45 | } 46 | logger = Logger(Config()) 47 | atoms = logger.atoms(response, request, environ, datetime.timedelta(seconds=1)) 48 | assert atoms['b'] == '0' 49 | assert atoms['B'] == 0 50 | 51 | 52 | @pytest.mark.parametrize('auth', [ 53 | # auth type is case in-sensitive 54 | 'Basic YnJrMHY6', 55 | 'basic YnJrMHY6', 56 | 'BASIC YnJrMHY6', 57 | ]) 58 | def test_get_username_from_basic_auth_header(auth): 59 | request = SimpleNamespace(headers=()) 60 | response = SimpleNamespace( 61 | status='200', response_length=1024, sent=1024, 62 | headers=(('Content-Type', 'text/plain'),), 63 | ) 64 | environ = { 65 | 'REQUEST_METHOD': 'GET', 'RAW_URI': '/my/path?foo=bar', 66 | 'PATH_INFO': '/my/path', 'QUERY_STRING': 'foo=bar', 67 | 'SERVER_PROTOCOL': 'HTTP/1.1', 68 | 'HTTP_AUTHORIZATION': auth, 69 | } 70 | logger = Logger(Config()) 71 | atoms = logger.atoms(response, request, environ, datetime.timedelta(seconds=1)) 72 | assert atoms['u'] == 'brk0v' 73 | 74 | 75 | def test_get_username_handles_malformed_basic_auth_header(): 76 | """Should catch a malformed auth header""" 77 | request = SimpleNamespace(headers=()) 78 | response = SimpleNamespace( 79 | status='200', response_length=1024, sent=1024, 80 | headers=(('Content-Type', 'text/plain'),), 81 | ) 82 | environ = { 83 | 'REQUEST_METHOD': 'GET', 'RAW_URI': '/my/path?foo=bar', 84 | 'PATH_INFO': '/my/path', 'QUERY_STRING': 'foo=bar', 85 | 'SERVER_PROTOCOL': 'HTTP/1.1', 86 | 'HTTP_AUTHORIZATION': 'Basic ixsTtkKzIpVTncfQjbBcnoRNoDfbnaXG', 87 | } 88 | logger = Logger(Config()) 89 | 90 | atoms = logger.atoms(response, request, environ, datetime.timedelta(seconds=1)) 91 | assert atoms['u'] == '-' 92 | -------------------------------------------------------------------------------- /tests/test_pidfile.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | import errno 6 | from unittest import mock 7 | 8 | import gunicorn.pidfile 9 | 10 | 11 | def builtin(name): 12 | return 'builtins.{}'.format(name) 13 | 14 | 15 | @mock.patch(builtin('open'), new_callable=mock.mock_open) 16 | def test_validate_no_file(_open): 17 | pidfile = gunicorn.pidfile.Pidfile('test.pid') 18 | _open.side_effect = IOError(errno.ENOENT) 19 | assert pidfile.validate() is None 20 | 21 | 22 | @mock.patch(builtin('open'), new_callable=mock.mock_open, read_data='1') 23 | @mock.patch('os.kill') 24 | def test_validate_file_pid_exists(kill, _open): 25 | pidfile = gunicorn.pidfile.Pidfile('test.pid') 26 | assert pidfile.validate() == 1 27 | assert kill.called 28 | 29 | 30 | @mock.patch(builtin('open'), new_callable=mock.mock_open, read_data='a') 31 | def test_validate_file_pid_malformed(_open): 32 | pidfile = gunicorn.pidfile.Pidfile('test.pid') 33 | assert pidfile.validate() is None 34 | 35 | 36 | @mock.patch(builtin('open'), new_callable=mock.mock_open, read_data='1') 37 | @mock.patch('os.kill') 38 | def test_validate_file_pid_exists_kill_exception(kill, _open): 39 | pidfile = gunicorn.pidfile.Pidfile('test.pid') 40 | kill.side_effect = OSError(errno.EPERM) 41 | assert pidfile.validate() == 1 42 | 43 | 44 | @mock.patch(builtin('open'), new_callable=mock.mock_open, read_data='1') 45 | @mock.patch('os.kill') 46 | def test_validate_file_pid_does_not_exist(kill, _open): 47 | pidfile = gunicorn.pidfile.Pidfile('test.pid') 48 | kill.side_effect = OSError(errno.ESRCH) 49 | assert pidfile.validate() is None 50 | -------------------------------------------------------------------------------- /tests/test_reload.py: -------------------------------------------------------------------------------- 1 | import unittest.mock as mock 2 | 3 | from gunicorn.app.base import Application 4 | from gunicorn.workers.base import Worker 5 | from gunicorn.reloader import reloader_engines 6 | 7 | 8 | class ReloadApp(Application): 9 | def __init__(self): 10 | super().__init__("no usage", prog="gunicorn_test") 11 | 12 | def do_load_config(self): 13 | self.load_default_config() 14 | self.cfg.set('reload', True) 15 | self.cfg.set('reload_engine', 'poll') 16 | 17 | 18 | class SyntaxErrorApp(ReloadApp): 19 | def wsgi(self): 20 | error = SyntaxError('invalid syntax') 21 | error.filename = 'syntax_error_filename' 22 | raise error 23 | 24 | 25 | class MyWorker(Worker): 26 | def run(self): 27 | pass 28 | 29 | 30 | def test_reload_on_syntax_error(): 31 | """ 32 | Test that reloading works if the application has a syntax error. 33 | """ 34 | reloader = mock.Mock() 35 | reloader_engines['poll'] = lambda *args, **kw: reloader 36 | 37 | app = SyntaxErrorApp() 38 | cfg = app.cfg 39 | log = mock.Mock() 40 | worker = MyWorker(age=0, ppid=0, sockets=[], app=app, timeout=0, cfg=cfg, log=log) 41 | 42 | worker.init_process() 43 | reloader.start.assert_called_with() 44 | reloader.add_extra_file.assert_called_with('syntax_error_filename') 45 | 46 | 47 | def test_start_reloader_after_load_wsgi(): 48 | """ 49 | Check that the reloader is started after the wsgi app has been loaded. 50 | """ 51 | reloader = mock.Mock() 52 | reloader_engines['poll'] = lambda *args, **kw: reloader 53 | 54 | app = ReloadApp() 55 | cfg = app.cfg 56 | log = mock.Mock() 57 | worker = MyWorker(age=0, ppid=0, sockets=[], app=app, timeout=0, cfg=cfg, log=log) 58 | 59 | worker.load_wsgi = mock.Mock() 60 | mock_parent = mock.Mock() 61 | mock_parent.attach_mock(worker.load_wsgi, 'load_wsgi') 62 | mock_parent.attach_mock(reloader.start, 'reloader_start') 63 | 64 | worker.init_process() 65 | mock_parent.assert_has_calls([ 66 | mock.call.load_wsgi(), 67 | mock.call.reloader_start(), 68 | ]) 69 | -------------------------------------------------------------------------------- /tests/test_sock.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | from unittest import mock 6 | 7 | from gunicorn import sock 8 | 9 | 10 | @mock.patch('os.stat') 11 | def test_create_sockets_unix_bytes(stat): 12 | conf = mock.Mock(address=[b'127.0.0.1:8000']) 13 | log = mock.Mock() 14 | with mock.patch.object(sock.UnixSocket, '__init__', lambda *args: None): 15 | listeners = sock.create_sockets(conf, log) 16 | assert len(listeners) == 1 17 | print(type(listeners[0])) 18 | assert isinstance(listeners[0], sock.UnixSocket) 19 | 20 | 21 | @mock.patch('os.stat') 22 | def test_create_sockets_unix_strings(stat): 23 | conf = mock.Mock(address=['127.0.0.1:8000']) 24 | log = mock.Mock() 25 | with mock.patch.object(sock.UnixSocket, '__init__', lambda *args: None): 26 | listeners = sock.create_sockets(conf, log) 27 | assert len(listeners) == 1 28 | assert isinstance(listeners[0], sock.UnixSocket) 29 | 30 | 31 | def test_socket_close(): 32 | listener1 = mock.Mock() 33 | listener1.getsockname.return_value = ('127.0.0.1', '80') 34 | listener2 = mock.Mock() 35 | listener2.getsockname.return_value = ('192.168.2.5', '80') 36 | sock.close_sockets([listener1, listener2]) 37 | listener1.close.assert_called_with() 38 | listener2.close.assert_called_with() 39 | 40 | 41 | @mock.patch('os.unlink') 42 | def test_unix_socket_close_unlink(unlink): 43 | listener = mock.Mock() 44 | listener.getsockname.return_value = '/var/run/test.sock' 45 | sock.close_sockets([listener]) 46 | listener.close.assert_called_with() 47 | unlink.assert_called_once_with('/var/run/test.sock') 48 | 49 | 50 | @mock.patch('os.unlink') 51 | def test_unix_socket_close_without_unlink(unlink): 52 | listener = mock.Mock() 53 | listener.getsockname.return_value = '/var/run/test.sock' 54 | sock.close_sockets([listener], False) 55 | listener.close.assert_called_with() 56 | assert not unlink.called, 'unlink should not have been called' 57 | -------------------------------------------------------------------------------- /tests/test_ssl.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Dariusz Suchojad 2 | # 3 | # This file is part of gunicorn released under the MIT license. 4 | # See the NOTICE for more information. 5 | 6 | import pytest 7 | 8 | from gunicorn.config import ( 9 | KeyFile, CertFile, CACerts, SuppressRaggedEOFs, 10 | DoHandshakeOnConnect, Setting, Ciphers, 11 | ) 12 | 13 | ssl = pytest.importorskip('ssl') 14 | 15 | 16 | def test_keyfile(): 17 | assert issubclass(KeyFile, Setting) 18 | assert KeyFile.name == 'keyfile' 19 | assert KeyFile.section == 'SSL' 20 | assert KeyFile.cli == ['--keyfile'] 21 | assert KeyFile.meta == 'FILE' 22 | assert KeyFile.default is None 23 | 24 | 25 | def test_certfile(): 26 | assert issubclass(CertFile, Setting) 27 | assert CertFile.name == 'certfile' 28 | assert CertFile.section == 'SSL' 29 | assert CertFile.cli == ['--certfile'] 30 | assert CertFile.default is None 31 | 32 | 33 | def test_cacerts(): 34 | assert issubclass(CACerts, Setting) 35 | assert CACerts.name == 'ca_certs' 36 | assert CACerts.section == 'SSL' 37 | assert CACerts.cli == ['--ca-certs'] 38 | assert CACerts.meta == 'FILE' 39 | assert CACerts.default is None 40 | 41 | 42 | def test_suppress_ragged_eofs(): 43 | assert issubclass(SuppressRaggedEOFs, Setting) 44 | assert SuppressRaggedEOFs.name == 'suppress_ragged_eofs' 45 | assert SuppressRaggedEOFs.section == 'SSL' 46 | assert SuppressRaggedEOFs.cli == ['--suppress-ragged-eofs'] 47 | assert SuppressRaggedEOFs.action == 'store_true' 48 | assert SuppressRaggedEOFs.default is True 49 | 50 | 51 | def test_do_handshake_on_connect(): 52 | assert issubclass(DoHandshakeOnConnect, Setting) 53 | assert DoHandshakeOnConnect.name == 'do_handshake_on_connect' 54 | assert DoHandshakeOnConnect.section == 'SSL' 55 | assert DoHandshakeOnConnect.cli == ['--do-handshake-on-connect'] 56 | assert DoHandshakeOnConnect.action == 'store_true' 57 | assert DoHandshakeOnConnect.default is False 58 | 59 | 60 | def test_ciphers(): 61 | assert issubclass(Ciphers, Setting) 62 | assert Ciphers.name == 'ciphers' 63 | assert Ciphers.section == 'SSL' 64 | assert Ciphers.cli == ['--ciphers'] 65 | assert Ciphers.default is None 66 | -------------------------------------------------------------------------------- /tests/test_systemd.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | from contextlib import contextmanager 6 | import os 7 | from unittest import mock 8 | 9 | import pytest 10 | 11 | from gunicorn import systemd 12 | 13 | 14 | @contextmanager 15 | def check_environ(unset=True): 16 | """ 17 | A context manager that asserts post-conditions of ``listen_fds`` at exit. 18 | 19 | This helper is used to ease checking of the test post-conditions for the 20 | systemd socket activation tests that parametrize the call argument. 21 | """ 22 | 23 | with mock.patch.dict(os.environ): 24 | old_fds = os.environ.get('LISTEN_FDS', None) 25 | old_pid = os.environ.get('LISTEN_PID', None) 26 | 27 | yield 28 | 29 | if unset: 30 | assert 'LISTEN_FDS' not in os.environ, \ 31 | "LISTEN_FDS should have been unset" 32 | assert 'LISTEN_PID' not in os.environ, \ 33 | "LISTEN_PID should have been unset" 34 | else: 35 | new_fds = os.environ.get('LISTEN_FDS', None) 36 | new_pid = os.environ.get('LISTEN_PID', None) 37 | assert new_fds == old_fds, \ 38 | "LISTEN_FDS should not have been changed" 39 | assert new_pid == old_pid, \ 40 | "LISTEN_PID should not have been changed" 41 | 42 | 43 | @pytest.mark.parametrize("unset", [True, False]) 44 | def test_listen_fds_ignores_wrong_pid(unset): 45 | with mock.patch.dict(os.environ): 46 | os.environ['LISTEN_FDS'] = str(5) 47 | os.environ['LISTEN_PID'] = str(1) 48 | with check_environ(False): # early exit — never changes the environment 49 | assert systemd.listen_fds(unset) == 0, \ 50 | "should ignore listen fds not intended for this pid" 51 | 52 | 53 | @pytest.mark.parametrize("unset", [True, False]) 54 | def test_listen_fds_returns_count(unset): 55 | with mock.patch.dict(os.environ): 56 | os.environ['LISTEN_FDS'] = str(5) 57 | os.environ['LISTEN_PID'] = str(os.getpid()) 58 | with check_environ(unset): 59 | assert systemd.listen_fds(unset) == 5, \ 60 | "should return the correct count of fds" 61 | -------------------------------------------------------------------------------- /tests/test_valid_requests.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | import glob 6 | import os 7 | 8 | import pytest 9 | 10 | import treq 11 | 12 | dirname = os.path.dirname(__file__) 13 | reqdir = os.path.join(dirname, "requests", "valid") 14 | httpfiles = glob.glob(os.path.join(reqdir, "*.http")) 15 | 16 | 17 | @pytest.mark.parametrize("fname", httpfiles) 18 | def test_http_parser(fname): 19 | env = treq.load_py(os.path.splitext(fname)[0] + ".py") 20 | 21 | expect = env['request'] 22 | cfg = env['cfg'] 23 | req = treq.request(fname, expect) 24 | 25 | for case in req.gen_cases(cfg): 26 | case[0](*case[1:]) 27 | -------------------------------------------------------------------------------- /tests/workers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/gunicorn/a86ea1e4e6c271d1cd1823c7e14490123f9238fe/tests/workers/__init__.py -------------------------------------------------------------------------------- /tests/workers/test_geventlet.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | import pytest 6 | import sys 7 | 8 | def test_import(): 9 | 10 | try: 11 | import eventlet 12 | except AttributeError: 13 | if (3,13) > sys.version_info >= (3, 12): 14 | pytest.skip("Ignoring eventlet failures on Python 3.12") 15 | raise 16 | __import__('gunicorn.workers.geventlet') 17 | -------------------------------------------------------------------------------- /tests/workers/test_ggevent.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of gunicorn released under the MIT license. 3 | # See the NOTICE for more information. 4 | 5 | def test_import(): 6 | __import__('gunicorn.workers.ggevent') 7 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py{37,38,39,310,311,312,py3}, 4 | lint, 5 | docs-lint, 6 | pycodestyle, 7 | run-entrypoint, 8 | run-module, 9 | 10 | [testenv] 11 | package = editable 12 | commands = pytest --cov=gunicorn {posargs} 13 | deps = 14 | -rrequirements_test.txt 15 | 16 | [testenv:run-entrypoint] 17 | package = wheel 18 | deps = 19 | # entry point: console script (provided by setuptools from pyproject.toml) 20 | commands = python -c 'import subprocess; cmd_out = subprocess.check_output(["gunicorn", "--version"])[:79].decode("utf-8", errors="replace"); print(cmd_out); assert cmd_out.startswith("gunicorn ")' 21 | 22 | [testenv:run-module] 23 | package = wheel 24 | deps = 25 | # runpy (provided by module.__main__) 26 | commands = python -c 'import sys,subprocess; cmd_out = subprocess.check_output([sys.executable, "-m", "gunicorn", "--version"])[:79].decode("utf-8", errors="replace"); print(cmd_out); assert cmd_out.startswith("gunicorn ")' 27 | 28 | [testenv:lint] 29 | no_package = true 30 | commands = 31 | pylint -j0 \ 32 | --max-line-length=120 \ 33 | gunicorn \ 34 | tests/test_arbiter.py \ 35 | tests/test_config.py \ 36 | tests/test_http.py \ 37 | tests/test_invalid_requests.py \ 38 | tests/test_logger.py \ 39 | tests/test_pidfile.py \ 40 | tests/test_sock.py \ 41 | tests/test_ssl.py \ 42 | tests/test_statsd.py \ 43 | tests/test_systemd.py \ 44 | tests/test_util.py \ 45 | tests/test_valid_requests.py 46 | deps = 47 | pylint==2.17.4 48 | 49 | [testenv:docs-lint] 50 | no_package = true 51 | allowlist_externals = 52 | rst-lint 53 | bash 54 | grep 55 | deps = 56 | restructuredtext_lint 57 | pygments 58 | commands = 59 | rst-lint README.rst docs/README.rst 60 | bash -c "(set -o pipefail; rst-lint --encoding utf-8 docs/source/*.rst | grep -v 'Unknown interpreted text role\|Unknown directive type'); test $? == 1" 61 | 62 | [testenv:pycodestyle] 63 | no_package = true 64 | commands = 65 | pycodestyle gunicorn 66 | deps = 67 | pycodestyle 68 | 69 | [pycodestyle] 70 | max-line-length = 120 71 | ignore = E129,W503,W504,W606 72 | --------------------------------------------------------------------------------