pax_global_header00006660000000000000000000000064140115732200014505gustar00rootroot0000000000000052 comment=61ccfd6c38d477a908e0f376757bbb884438053a gunicorn-20.1.0/000077500000000000000000000000001401157322000134115ustar00rootroot00000000000000gunicorn-20.1.0/.gitignore000077500000000000000000000004131401157322000154020ustar00rootroot00000000000000*.egg *.egg-info *.pyc *.so .coverage .pytest_cache .tox __pycache__ build coverage.xml dist examples/frameworks/django/testing/testdb.sql examples/frameworks/pylonstest/PasteScript* examples/frameworks/pylonstest/pylonstest.egg-info/ MANIFEST nohup.out setuptools-* gunicorn-20.1.0/.pylintrc000066400000000000000000000021541401157322000152600ustar00rootroot00000000000000[MASTER] ignore= build, docs, examples, scripts, _compat.py, _gaiohttp.py, [MESSAGES CONTROL] disable= attribute-defined-outside-init, bad-continuation, bad-mcs-classmethod-argument, bare-except, broad-except, duplicate-bases, duplicate-code, eval-used, fixme, import-error, import-outside-toplevel, import-self, inconsistent-return-statements, invalid-name, misplaced-comparison-constant, missing-docstring, no-else-return, no-member, no-self-argument, no-self-use, no-staticmethod-decorator, not-callable, protected-access, raise-missing-from, redefined-outer-name, too-few-public-methods, too-many-arguments, too-many-branches, too-many-instance-attributes, too-many-lines, too-many-locals, too-many-nested-blocks, too-many-public-methods, too-many-statements, wrong-import-position, wrong-import-order, ungrouped-imports, unused-argument, useless-object-inheritance, useless-import-alias, comparison-with-callable, try-except-raise, gunicorn-20.1.0/.travis.yml000066400000000000000000000014661401157322000155310ustar00rootroot00000000000000language: python matrix: include: - python: 3.8 env: TOXENV=lint - python: 3.8 env: TOXENV=docs-lint - python: 3.5 env: TOXENV=py35 - python: 3.6 env: TOXENV=py36 - python: 3.7 env: TOXENV=py37 - python: 3.8 env: TOXENV=py38 - python: 3.9 env: TOXENV=py39 - python: pypy3 env: - TOXENV=pypy3 # Embedded c-ares takes a long time to build and # as-of 2020-01-04 there are no PyPy3 manylinux # wheels for gevent on PyPI. - GEVENTSETUP_EMBED_CARES=no dist: xenial install: pip install -U tox coverage # TODO: https://github.com/tox-dev/tox/issues/149 script: tox --recreate after_success: - if [ -f .coverage ]; then coverage report ; fi cache: directories: - .tox - $HOME/.cache/pip gunicorn-20.1.0/CONTRIBUTING.md000066400000000000000000000160361401157322000156500ustar00rootroot00000000000000# Contributing to Gunicorn Want to hack on Gunicorn? Awesome! Here are instructions to get you started. They are probably not perfect, please let us know if anything feels wrong or incomplete. ## Contribution guidelines ### Pull requests are always welcome We are always thrilled to receive pull requests, and do our best to process them as fast as possible. Not sure if that typo is worth a pull request? Do it! We will appreciate it. If your pull request is not accepted on the first try, don't be discouraged! If there's a problem with the implementation, hopefully you received feedback on what to improve. We're trying very hard to keep Gunicorn lean and focused. We don't want it to do everything for everybody. This means that we might decide against incorporating a new feature. However, there might be a way to implement that feature *on top of* Gunicorn. ### Discuss your design on the mailing list We recommend discussing your plans [on the mailing list](http://gunicorn.org/#community) before starting to code - especially for more ambitious contributions. This gives other contributors a chance to point you in the right direction, give feedback on your design, and maybe point out if someone else is working on the same thing. ### Create issues... Any significant improvement should be documented as [a github issue](https://github.com/benoitc/gunicorn/issues) before anybody starts working on it. ### ...but check for existing issues first! Please take a moment to check that an issue doesn't already exist documenting your bug report or improvement proposal. If it does, it never hurts to add a quick "+1" or "I have this problem too". This will help prioritize the most common problems and requests. ### Conventions Don't comment on closed issues or PRs, instead open a new issue and link it to the old one. Fork the repo and make changes on your fork in a feature branch: - If it's a bugfix branch, name it XXX-something where XXX is the number of the issue - If it's a feature branch, create an enhancement issue to announce your intentions, and name it XXX-something where XXX is the number of the issue. Submit unit tests for your changes. Python has a great test framework built in; use it! Take a look at existing tests for inspiration. Run the full test suite on your branch before submitting a pull request. Make sure you include relevant updates or additions to documentation when creating or modifying features. If you are adding a new configuration option or updating an existing one, please do it in `gunicorn/config.py`, then run `make -C docs html` to update `docs/source/settings.rst`. Write clean code. Pull requests descriptions should be as clear as possible and include a reference to all the issues that they address. Code review comments may be added to your pull request. Discuss, then make the suggested modifications and push additional commits to your feature branch. Be sure to post a comment after pushing. The new commits will show up in the pull request automatically, but the reviewers will not be notified unless you comment. Before the pull request is merged, make sure that you squash your commits into logical units of work using `git rebase -i` and `git push -f`. After every commit the test suite should be passing. Include documentation changes in the same commit so that a revert would remove all traces of the feature or fix. Commits that fix or close an issue should include a reference like `Closes #XXX` or `Fixes #XXX`, which will automatically close the issue when merged. Add your name to the THANKS file, but make sure the list is sorted and your name and email address match your git configuration. The THANKS file is regenerated occasionally from the git commit history, so a mismatch may result in your changes being overwritten. ## Decision process ### How are decisions made? Short answer: with pull requests to the gunicorn repository. Gunicorn is an open-source project under the MIT License with an open design philosophy. This means that the repository is the source of truth for EVERY aspect of the project, including its philosophy, design, roadmap and APIs. *If it's part of the project, it's in the repo. It's in the repo, it's part of the project.* As a result, all decisions can be expressed as changes to the repository. An implementation change is a change to the source code. An API change is a change to the API specification. A philosophy change is a change to the relevant documentation. And so on. All decisions affecting gunicorn, big and small, follow the same 3 steps: * Step 1: Open a pull request. Anyone can do this. * Step 2: Discuss the pull request. Anyone can do this. * Step 3: Accept or refuse a pull request. The relevant maintainer does this (see below "Who decides what?") ### Who decides what? So all decisions are pull requests, and the relevant maintainer makes the decision by accepting or refusing the pull request. But how do we identify the relevant maintainer for a given pull request? Gunicorn follows the timeless, highly efficient and totally unfair system known as [Benevolent dictator for life](http://en.wikipedia.org/wiki/Benevolent_Dictator_for_Life), with Benoit Chesneau (aka benoitc), in the role of BDFL. This means that all decisions are made by default by me. Since making every decision myself would be highly unscalable, in practice decisions are spread across multiple maintainers. The relevant maintainer for a pull request is assigned in 3 steps: * Step 1: Determine the subdirectory affected by the pull request. This might be src/registry, docs/source/api, or any other part of the repo. * Step 2: Find the MAINTAINERS file which affects this directory. If the directory itself does not have a MAINTAINERS file, work your way up the the repo hierarchy until you find one. * Step 3: The first maintainer listed is the primary maintainer who is assigned the Pull Request. The primary maintainer can reassign a Pull Request to other listed maintainers. ### I'm a maintainer, should I make pull requests too? Primary maintainers are not required to create pull requests when changing their own subdirectory, but secondary maintainers are. ### Who assigns maintainers? benoitc. ### How can I become a maintainer? * Step 1: learn the component inside out * Step 2: make yourself useful by contributing code, bugfixes, support etc. * Step 3: volunteer on the irc channel (#gunicorn@freenode) Don't forget: being a maintainer is a time investment. Make sure you will have time to make yourself available. You don't have to be a maintainer to make a difference on the project! ### What are a maintainer's responsibility? It is every maintainer's responsibility to: * 1) Expose a clear roadmap for improving their component. * 2) Deliver prompt feedback and decisions on pull requests. * 3) Be available to anyone with questions, bug reports, criticism etc. on their component. This includes irc, github requests and the mailing list. * 4) Make sure their component respects the philosophy, design and roadmap of the project. ### How is this process changed? Just like everything else: by making a pull request :) gunicorn-20.1.0/LICENSE000066400000000000000000000021601401157322000144150ustar00rootroot000000000000002009-2018 (c) Benoît Chesneau 2009-2015 (c) Paul J. Davis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gunicorn-20.1.0/MAINTAINERS000066400000000000000000000011761401157322000151130ustar00rootroot00000000000000Core maintainers ================ Benoit Chesneau Konstantin Kapustin Randall Leeds Berker Peksağ Jason Madden Brett Randall Alumni ====== This list contains maintainers that are no longer active on the project. It is thanks to these people that the project has become what it is today. Thank you! Paul J. Davis Kenneth Reitz Nikolay Kim Andrew Svetlov Stéphane Wirtel gunicorn-20.1.0/MANIFEST.in000066400000000000000000000005041401157322000151460ustar00rootroot00000000000000include .gitignore include LICENSE include NOTICE include README.rst include THANKS include requirements_dev.txt include requirements_test.txt recursive-include tests * recursive-include examples * recursive-include docs * recursive-include examples/frameworks * recursive-exclude * __pycache__ recursive-exclude * *.py[co] gunicorn-20.1.0/Makefile000066400000000000000000000005651401157322000150570ustar00rootroot00000000000000build: virtualenv venv venv/bin/pip install -e . venv/bin/pip install -r requirements_dev.txt test: venv/bin/python setup.py test coverage: venv/bin/python setup.py test --cov clean: @rm -rf .Python MANIFEST build dist venv* *.egg-info *.egg @find . -type f -name "*.py[co]" -delete @find . -type d -name "__pycache__" -delete .PHONY: build clean coverage test gunicorn-20.1.0/NOTICE000066400000000000000000000073011401157322000143160ustar00rootroot00000000000000Gunicorn 2009-2018 (c) Benoît Chesneau 2009-2015 (c) Paul J. Davis Gunicorn is released under the MIT license. See the LICENSE file for the complete license. gunicorn.logging_config ----------------------- Copyright 2001-2005 by Vinay Sajip. All Rights Reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Vinay Sajip not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. gunicorn.debug -------------- Based on eventlet.debug module under MIT license: Unless otherwise noted, the files in Eventlet are under the following MIT license: Copyright (c) 2005-2006, Bob Ippolito Copyright (c) 2007-2010, Linden Research, Inc. Copyright (c) 2008-2010, Eventlet Contributors (see Eventlet AUTHORS) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gunicorn.reloader ----------------- Based on greins.reloader module under MIT license: 2010 (c) Meebo, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. util/unlink.py -------------- backport frop python3 Lib/test/support.py gunicorn-20.1.0/README.rst000066400000000000000000000033421401157322000151020ustar00rootroot00000000000000Gunicorn -------- .. image:: https://img.shields.io/pypi/v/gunicorn.svg?style=flat :alt: PyPI version :target: https://pypi.python.org/pypi/gunicorn .. image:: https://img.shields.io/pypi/pyversions/gunicorn.svg :alt: Supported Python versions :target: https://pypi.python.org/pypi/gunicorn .. image:: https://travis-ci.org/benoitc/gunicorn.svg?branch=master :alt: Build Status :target: https://travis-ci.org/benoitc/gunicorn Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It's a pre-fork worker model ported from Ruby's Unicorn_ project. The Gunicorn server is broadly compatible with various web frameworks, simply implemented, light on server resource usage, and fairly speedy. Feel free to join us in `#gunicorn`_ on Freenode_. Documentation ------------- The documentation is hosted at https://docs.gunicorn.org. Installation ------------ Gunicorn requires **Python 3.x >= 3.5**. Install from PyPI:: $ pip install gunicorn Usage ----- Basic usage:: $ gunicorn [OPTIONS] APP_MODULE Where ``APP_MODULE`` is of the pattern ``$(MODULE_NAME):$(VARIABLE_NAME)``. The module name can be a full dotted path. The variable name refers to a WSGI callable that should be found in the specified module. Example with test app:: $ cd examples $ gunicorn --workers=2 test:app Contributing ------------ See `our complete contributor's guide `_ for more details. License ------- Gunicorn is released under the MIT License. See the LICENSE_ file for more details. .. _Unicorn: https://bogomips.org/unicorn/ .. _`#gunicorn`: https://webchat.freenode.net/?channels=gunicorn .. _Freenode: https://freenode.net/ .. _LICENSE: https://github.com/benoitc/gunicorn/blob/master/LICENSE gunicorn-20.1.0/THANKS000066400000000000000000000147561401157322000143410ustar00rootroot00000000000000Gunicorn THANKS =============== A number of people have contributed to Gunicorn by reporting problems, suggesting improvements or submitting changes. Some of these people are: 414nch4n Aaron Kavlie aartur Adnane Belmadiaf Adrien CLERC Alasdair Nicol Alex Conrad Alex Gaynor Alex Robbins Alexandre Zani Alexis Le-Quoc Anand Chitipothu Andreas Stührk Andrew Burdo Andrew Svetlov Anil V Antoine Girard Anton Vlasenko Bartosz Oler Ben Cochran Ben Oswald Benjamin Gilbert Benoit Chesneau Berker Peksag bninja Bob Hagemann Bobby Beckmann Brett Randall Brian Rosner Bruno Bigras Caleb Brown Chris Adams Chris Forbes Chris Lamb Chris Streeter Christoph Heer Christos Stavrakakis CMGS Curt Micol Dan Callaghan Dan Sully Daniel Quinn Dariusz Suchojad David Black David Vincelli David Wolever Denis Bilenko Diego Oliveira Dima Barsky Djoume Salvetti Dmitry Medvinsky Dustin Ingram Ed Morley Eric Florenzano Eric Shull Eugene Obukhov Evan Mezeske Florian Apolloner Gaurav Kumar George Kollias George Notaras German Larrain Graham Dumpleton Graham Dumpleton Greg McGuire Greg Taylor Hasan Ramezani Hebert J Hobson Lane Hugo van Kemenade Igor Petrov INADA Naoki Jakub Paweł Głazik Jan-Philip Gehrcke Jannis Leidel Jason Madden jean-philippe serafin Jeremy Volkman Jeryn Mathew Jet Sun Jim Garrison Johan Bergström John Hensley Jonas Haag Jonas Nockert Jorge Niedbalski Jorge Niedbalski R Justin Quick keakon Keegan Carruthers-Smith Kenneth Reitz Kevin Gessner Kevin Littlejohn Kevin Luikens Kirill Zaborsky Konstantin Kapustin kracekumar Kristian Glass Kristian Øllegaard Krzysztof Urbaniak Kyle Kelley Kyle Mulka Lars Hansson Leonardo Santagada Levi Gross Łukasz Kucharski Mahmoud Hashemi Malthe Borch Marc Abramowitz Marc Abramowitz Mark Adams Matt Behrens Matt Billenstein Matt Good Matt Robenolt Maxim Kamenkov Mazdak Rezvani Michael Schurter Mieszko Mike Tigas Moriyoshi Koizumi mpaolini Neil Chintomby Neil Williams Nick Pillitteri Nik Nyby Nikolay Kim Oliver Bristow Oliver Tonnhofer Omer Katz PA Parent Paul Davis Paul J. Davis Paul Smith Phil Schanely Philip Cristiano Philipp Saveliev Prateek Singh Paudel py Qiangning Hong Randall Leeds Randall Leeds Randall Leeds Raphaël Slinckx Rhys Powell Rik Ronan Amicel Ryan Peck Saeed Gharedaghi Sergey Rublev Shane Reustle shouse-cars sib Simon Lundmark Stephane Wirtel Stephen DiCato Stephen Holsapple Steven Cummings Sébastien Fievet Talha Malik TedWantsMore Thomas Grainger Thomas Steinacher Travis Cline Travis Swicegood Trey Long W. Trevor King Wojtek Wolfgang Schnerring WoLpH wong2 WooParadog Xie Shi Yue Du zakdances Emile Fugulin gunicorn-20.1.0/appveyor.yml000066400000000000000000000015721401157322000160060ustar00rootroot00000000000000version: '{branch}.{build}' environment: matrix: - TOXENV: lint PYTHON: "C:\\Python37-x64" - TOXENV: py35 PYTHON: "C:\\Python35-x64" - TOXENV: py36 PYTHON: "C:\\Python36-x64" - TOXENV: py37 PYTHON: "C:\\Python37-x64" - TOXENV: py38 PYTHON: "C:\\Python38-x64" - TOXENV: py39 PYTHON: "C:\\Python39-x64" matrix: allow_failures: - TOXENV: py35 - TOXENV: py36 - TOXENV: py37 - TOXENV: py38 - TOXENV: py39 init: SET "PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" install: - pip install tox build: off test_script: tox cache: # Not including the .tox directory since it takes longer to download/extract # the cache archive than for tox to clean install from the pip cache. - '%LOCALAPPDATA%\pip\Cache -> tox.ini' notifications: - provider: Email on_build_success: false on_build_status_changed: false gunicorn-20.1.0/docs/000077500000000000000000000000001401157322000143415ustar00rootroot00000000000000gunicorn-20.1.0/docs/Makefile000066400000000000000000000132371401157322000160070ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. PYTHON = python SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html htmlview dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " htmlview to open the index page built by the html target in your browser" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." htmlview: html $(PYTHON) -c "import webbrowser; webbrowser.open('build/html/index.html')" dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Gunicorn.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Gunicorn.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Gunicorn" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Gunicorn" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." gunicorn-20.1.0/docs/README.rst000066400000000000000000000004401401157322000160260ustar00rootroot00000000000000Generate Documentation ====================== Requirements ------------ To generate documentation you need to install: - Python >= 3.4 - Sphinx (http://sphinx-doc.org/) Generate html ------------- :: $ make html The command generates html document inside ``build/html`` dir. gunicorn-20.1.0/docs/gunicorn_ext.py000077500000000000000000000056251401157322000174320ustar00rootroot00000000000000import os import inspect from docutils import nodes, utils import gunicorn.config as guncfg HEAD = """\ .. Please update gunicorn/config.py instead. .. _settings: Settings ======== This is an exhaustive list of settings for Gunicorn. Some settings are only able to be set from a configuration file. The setting name is what should be used in the configuration file. The command line arguments are listed as well for reference on setting at the command line. .. note:: Settings can be specified by using environment variable ``GUNICORN_CMD_ARGS``. All available command line arguments can be used. For example, to specify the bind address and number of workers:: $ GUNICORN_CMD_ARGS="--bind=127.0.0.1 --workers=3" gunicorn app:app .. versionadded:: 19.7 """ ISSUE_URI = 'https://github.com/benoitc/gunicorn/issues/%s' PULL_REQUEST_URI = 'https://github.com/benoitc/gunicorn/pull/%s' def format_settings(app): settings_file = os.path.join(app.srcdir, "settings.rst") ret = [] known_settings = sorted(guncfg.KNOWN_SETTINGS, key=lambda s: s.section) for i, s in enumerate(known_settings): if i == 0 or s.section != known_settings[i - 1].section: ret.append("%s\n%s\n\n" % (s.section, "-" * len(s.section))) ret.append(fmt_setting(s)) with open(settings_file, 'w') as settings: settings.write(HEAD) settings.write(''.join(ret)) def fmt_setting(s): if callable(s.default): val = inspect.getsource(s.default) val = "\n".join(" %s" % line for line in val.splitlines()) val = "\n\n.. code-block:: python\n\n" + val elif s.default == '': val = "``''``" else: val = "``%r``" % s.default if s.cli and s.meta: cli = " or ".join("``%s %s``" % (arg, s.meta) for arg in s.cli) elif s.cli: cli = " or ".join("``%s``" % arg for arg in s.cli) else: cli = "" out = [] out.append(".. _%s:\n" % s.name.replace("_", "-")) out.append("``%s``" % s.name) out.append("~" * (len(s.name) + 4)) out.append("") if s.cli: out.append("**Command line:** %s" % cli) out.append("") out.append("**Default:** %s" % val) out.append("") out.append(s.desc) out.append("") out.append("") return "\n".join(out) def issue_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): issue = utils.unescape(text) text = 'issue ' + issue refnode = nodes.reference(text, text, refuri=ISSUE_URI % issue) return [refnode], [] def pull_request_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): issue = utils.unescape(text) text = 'pull request ' + issue refnode = nodes.reference(text, text, refuri=PULL_REQUEST_URI % issue) return [refnode], [] def setup(app): app.connect('builder-inited', format_settings) app.add_role('issue', issue_role) app.add_role('pr', pull_request_role) gunicorn-20.1.0/docs/logo/000077500000000000000000000000001401157322000153015ustar00rootroot00000000000000gunicorn-20.1.0/docs/logo/gunicorn.png000066400000000000000000000520561401157322000176430ustar00rootroot00000000000000PNG  IHDR :=bKGD pHYs B(xtIME  5a} IDATxyx3#rb';RZ@-[3N( [KYm@JKKq t/;E@ll&P@[--[:N,[9,fFIh 52 ZVل>f^V9}yAAgέ" # 6&Gz=̴Whxfq?AA2}ê&)&eʷ5 t)̏Jqԩ@su<`ODD9   $ ̈́l:HO5cx + XSAALo>E׀J|*qK% Xbhzz_ցٙ|  "GMUs44*^DWvEMs#EWqUAAZRV \ ۙ1>{ݦ~h~A9G뉹cN]a I(T:88݈#{Vʉ @93V*&URUpXA LFUK y] d22h-ڸ$'>0>Ate!v |]ק8p`[a^f~$ɗ:::k !?Y5o``ͺ |(@pw PTVV&Lp 3cf^(3aѢEHo y-7?e\]ouBd̙{y Fm$2sK4} #A!?|ތC0gΉ5 `0X(e̬P=XŷȪ ׂ3UW_/~3Άą|FӴ0ӓAVF9=mrɪqj(紵IRA!9455)/Ny[w+{lRc=IsFd?F.~%A䟱W(*c ]?WrMX`v5f-3|4M󄶶s#|ENcBPq9IkMRAAe`|au)7NҥK/5lx:%"f0!MMMxS244t>Cl?3_,YGW/C!dY] y *ނ4mԡ?L?#Hݍ]f"Гně}IS 򬒰 _+*As_>5?Γn"cR9PίʑbQAAU,&nuBmmmOfKJJ6˒|~wY1>'ljEAyV!&pߖ* ljE#n9uJm)+"`qE|P+sv.gTϫsNgMR`>/#9Ei,rWQE/ <'9v|YUD-zzB'gg'/$N;?iڥ(t*XTO~ǐ lMD\]fڎg9,X^T0']*0a"cbQAAUD7Z{%lH$t8~Բ3܌Trx<`q/EAy%|RbU dib|e2sFc_n|x!H_sb>{Ģ |>7 g-Hc4 ]ǧ1|~<88ub%AAIL'%>a$ѵDb9e$H X3欎ײpW0ciĠaן&L$7YdеT41>("ȅQMKSAA}T1[Dcl}m[AH3ز^kYLbz%GkZCLS9T!4PK1'Eoτ>Wa:7\MPnhxF1 ͉J4T^ r"AD/DoN T6 PGcc 2 5 n0b/S[[[(+nmY֤,kPp+ N~y:l~D^+~(ב"U[xk= 40.@]H!1㺮)sNg7hVl'1D0rUKlEQrCCPi۱>B<)`dC\RܳpR/`dUڋLu .0 ISEYRWWwh6&Fq"ju~MMMi713lbfo* 9t6v^ liG̗tae Maz68`'&DQ7888=Mq:yF2&TnȢL6+T #%`np[L_N3fpe:c`,`0xR*o6Onmz{{pE@fk[8>H=NsB@P9pu!6>pKb.Mq[d(_;Eeߪ/J slvjIIbEymmm=,RNY:/G1v^sǢ %Xc._7u~*q@20jnntk\kjjr} 4m{`'yX$sMMMJ__߽0p}(̸ (ʣJreDԮzu;NG,+Z )P|C"G{qG4l7s9+LXHffMaҥ'mt:\$d3g!Ԕ 0CCC|뺾?(ˡ0Úe2nH>9y'&6"{BiSp)9F p-Iaf'c _?# AAs@ooo+%.ӨiZCׇBsR-sG l23jk"]@B.l،g/DndSH퀿z^cƋ@ 8d]ˉ!x?XѱKu  {{{/& = p<8E난?+[zg\*~7X}}}S655)>3V\ӘD,3:?[p>j;ڋF`A) {3b"HF&ӵw2Qk|˶XUֹ|6gocކaL&w%2̋5~ kgMAps`B+'::#<8؅gj |s78!a"8ۉz Zh6@=p*xO{`p] OٴE}}}1DRRRp8<0q7v7Qf~_ FiJDQ>{_ 6X(Je[[>TYYr/,M<`&sBCCC1\ƝE)A"Dž 6ܪ:ĩɪ(1 盓gP(T[R mY։Xlx=.fƢhc XUU#wA{ g~^8^3 ϟFO̙30MfyʲKǖ8uf৆alqD m]~z37C8%ta I5ʮfc~4͆X,vKKGDy' Yq^ Bhh{{{3_墠84ͿՍO]zI1̕h依vIۚߐxr{}H-~?0~9Z1)Z[[3 AQi#3 8ib<5ءթI߉[҈H0a˅,a'z0 c")51Uf>r& wh@ j0fWϫ*Fћ9ղnMӞ4@ 0as?Yu1ߘ٩UKƨ8/;&"Dt[ryO/e,BQj0#7d2y ݹ&&": <νeT}˲Fkap,ަ}3Z1=>,D"CaF갗)ZKFqSeS崞d{K.݅Ouo4>$9Mkt?%Se'd$i8F=,Y3׶;ihn3u%/efo F"[_0,˺Wp\{{2ft̙Ǜ 6R ^H$h~~i4IvAV]%dEXzn!4J$` P83/+E{yKG"~:(;z&\]]H$r/5;TXHЮui64a,soQ3}|j}e4aƕ_,'ARdy\S7Lo>ņ èp\?Jch‘<DDAhϰK~A(*M 1s]&CR f5L q[3:::rafRƕh.wxxh4zi:yL&@\TT~.DlW x*_|qy"Lޙ:+beYǹX7s<4M[r7nw+iRF a! ~ddrmzdiqt4]:Q=scʕ+Wf>,sIi=3]Ԧm'8^tѢE:]͇iwg6} 9c .:'Q!]E?o D"L*籐BU7Ij)e ah6lٖzG05T.`+nr\Ap@36)y5 E}}}b$ox700P\\\<ٲ<@9QeY _Q~˲2s}Gq;vrBxb9+1?]^t$l x<[׫LkwօO mI6x/ *- < `(~D"7w nr0'dz,Ƥ4#XHyr<ijdb[kCPq< @(/L&AvM[Ȋ+]^IAz3+6l<|o 1O<1W|dA̠"ƅR0(t)L=c +ͼX^!#ƶRQ FmuM0s"}'Ha>o]>=KHӴ˲`/7n9ҳ#U"ȿ;Bm$6l1gNW+EEE";kT JnBgy;0ĝ8b n @>1Ks<3fj.OyfJury<'3ݬXbll,k|zTUn&b/8ɳKJJ<9 Oyէx1—7CȅO~LN[m.Ά &LO,˲#ȷqNlGFEqM4 RylнYJ&]C)UUU7;.9N{<A>FjU7A~ J\yvS1$ KY1e1seН l+vX5 aSkڣׯn}UwEQ*<x=99<9 %-E5 p$Y%bC ׀f۷  :tF(*+p ~ns=}OH}1s7aqnT^݃$|n a'< ^Z>5-5樅K*i53gܻY(*BePzNnԉў2dWLu]'kYfڿc dcV8鎓W#gK-E?r0(AZfVJR*r aEwEEEsQw]ׯh6Ω2h"~ihwm5"OrORҟ(54\4$[m] BCA\dL^-qIeeevPh"ty1 cgmS5M&4fQDbI)GS'B0v'AS\&b !Mw(65{٢XۉQe߂ZbkNGtapp8Dg;(M.ʐ?bO! ϶[E>b...D:-"'0Qsgj [4W_,`k^*X} wRO 'B`"Z-Nu~3\RRVZ"f=$=L6"|4Gpiv(R5M)g}k7Ųy<0sv[گe=n0y"=Gj iL q#qhV@`fzȾZ>ز߭..R>ac "sGGt]d5M3wV\/{8PEy>+ܖ] v%#"ȽEUKR _maVusj٤x*8D `5B(1z?31T:tmH`oC+ CDO{ 7 AjЬYF_YY c$+U##H= 2󛚦] &*(9 4,%MI[)"zNӴәM4ODtFd_{? <=Z631h&"VIJ9=xOfN'26PۓCs(axl~@%jMWw3hv3kDt!68%]]bP*xM2Dh▃ΊT1L5rxh`iCCCwwwmmmK4M{#Ep>3GӴntƢheo[u]}aoNj 5f!_cRyW"Ngx!PW}v:0%B C?Xrܵf{dTX@9>낼M-C-B r04U;$.,Ҿ1 7ZUUn>a<0yNp] iDOf~_jU IvN܈i4\5?4m@}E%ӽIT6Uԛ2|>0|%عwGc]b;;5tN qI>W$<;l2~o0\?Xr0;"> B_tpq̼3W`$ɼV XUzƣ- i|*.Hw%]A^4mLm x.ڻTeb"=E鎥oneYĮ|y̤_™U=sm`SnOP_?_.w^_0޵Z5_hLnQkI; yش"ZƘu/3/΃!a"=\~^XC':.X5RŽ,΋:$RnE5w4:>KD"3ja|)' ME}"鹉̳f]DHg`-**:97~b XMqeQQ7q5-q>t|N |F <ٿGD?"KwdmX5o/csbx wSIm.s4iGxYᴨ&R9tο~O+I^ɓ0[ҽxѢE*p5iīE{gP>ߧsN͑#8_\Hrv%@oc8Pi5Uգj66\掎***]w /?k֬/e Dt.p8, X>n㝇6\%XE˙`W⺓+7[ngc\^{sk_g#=g3>E/{ &KUoxKKD9MMMox*GzhFȗl4 #*[@/)[7?. :ї:{sTZ 5[ ',t#]hRK}vyW!η.pk< &ܼ[c3_d^i|@ld_@kkP(4#gN[d2wttC9 O QU)^$0qFH ]׿̭I&gbcs[!'b+Clw_`9|uj[s{>J~.lf[U·Ax֔芊\o]׫_gF)_pd4+m0 ca0 Af층5~dmwQ嵛bh4ZODAlX6xɄ@kʔ)clƕ'r0*E ,'anEp~#NϕRUe<9N |?;~u}i 9`Q^֝zݫ2 d2y,\"'wpx0YF83_Df;N>H=L̷f6f9c#ȫaʲ- ."j$Fy.AB>l3uȢf.Q`J :FSb610ka'f455yBe{k#Cbؐa0nVUT&c-yX4ABFˉh0+od2$/>a[(rt$DDp_yBbm] ]}dށkWsN _ 4W'O1b_$`< $r~?p[`\ӒSx&핦i5Ko6 \lW^^~<܉1!%[}" (wN"0 i21cCmQZI͟f#ȿ\^[[S"pH$؃=D"aMڙԙÉD# 2h4gt/\ɻT; @stz1H: |"dKv*Z]#َrMEKE<@mT&VZ3|Ic4XcU4~?Ɨ½h*1p BPi<0*y11LDo29VZZ.' 3g424ƧH^g===e'8")~K Hx#JtE!v(%Ks:OuZɉf;O5wպocO䶋Vr)SăiNl:~\w4M{7}H$reBׯ?LUݙy23O&&̼ZQ̼5u7ϟ lD({؆y0`=wH$qi.MvUe?4'2|W1rjEYmYևUAAᅬ FE.U-T5r`Ν<-ȧbjQ=L֧nOMKMz@UW9]qqr̘7jDyOlU% m6k+*H$:ß|ss]xiAnYZ+`d5YCb!o2l2_- [Êa@V3YY\ϝ+R;vj%>ǣ C^@K`~ ( -s?pW ARX.mʥiGxcD-Z<xpk  B^PpYVJ&tu*AbtS4/˲=iV|;:TNIIAAajƪ#QX}VbS1%A0³=TK4A/QL@@e<[vzti&QW8  l輏YX٪5kkz8<&66 &ۘcYVCE)-AyOHNG@u4v0] 2;UT AQlVL#"[P>;!ߩX$-Ay`'=. z,k`Qal7`>^qD'f|_^',A+t:!\[DE=lCqQB鿰*Ai_ ^qo͊5SsY,kL*++;y4AIIɯ  wbGXa[Wn0Ȫ,Q1U-US<%bpZǿ9=(+NCD ʘZ'ldAA^t4t,at(uwd`&'iXc5/>I:UŮ Yġf꺞Ӈ% ]`<DdNAEE1Mk MK1_2<[V' ˚A](эܟޅ{.??h^wȆbsMNdfT3e2* /H/l]ٱc>t豎9]VMm31>un@YS'<`) ?(<+s555xϦ/FAAA^ v4v~WbT)hb*f\"JJU6 a%QT bJk-L&sD O͕WUUMRU5 `[EDXAsNL8[+_(f]-Ӳ1!+h!]A$[r3>SAD@ 0x}m^QQ=}AD LslSbl3&특dLYѯDimdYO: מQ'uJCYLD4M;ګu0 l":CNA(,|b=55-Ug2+7#9PDzI"W<ӰC [ Xn[\^bϞ={Ԣ\U$<0Lwχ0|CExZk8zIjv3`+܈>df-AUHLL===࠺6(CCCࠢ(oV,^W˲a5L*`>wfV@Q7nY&|>:44Y>6OMu[]!7ͱ/&_e1R'>6" ֘n,9]wYH$,"R6T]ٲ,Ӳ,SQ(9<<%%%xoigBcƬYK$&(MEQ>WAyNpB;:2ofw%_hIVEHMB]@mPyjHAJXDBBB1H'ġEiӛKnڵ|<ǘr#vvg/7e˖-Z)ڔsNDZ^\bQZk2c20V`ui?>;XW"w`FMN tԹ//߿syReUJYNDsN Ȝs✳A,܂̜ks眍8okkˆҡ;36Cu::;::Zhsd)iιQpsan(f9}[iKT[lFUTo7 " @D$"EZ#"$I0 q |1S&ץMc~y=<=/J'x<E+ED]i6 y]kIHM}𩆎v8P.h0e*5 |Yg70ߑ v8rH `Z{NrCCC>$9T*Ux< >;(lڃ]דW뎎"*V܊|uD(ytm{JD"[)ulRyp%8}uJ^=sY7!.kdfFW!PzKF#'e%";yf&*/ˮgZT߉HcJ ߿kDS7yqQ|~M쌉脈1c###3x< ygr%˲TmeKmo7f/{G?՛8if!ofD$QJ֞|׺jQ%"7 +tIx<z %~kV*EQTsfNa|cћ7N_?}x}*ˊku--\@mrjLBQxn" I1ιR*>w\QӳM)yHD{\pRDN8:K^=B?d|h֭, vyQHD:X'R/䛫ⅼq09gh qb1I4ihzwsn]KD[D\[#0s2:<dz)|%>vttj5d搈<ϣy9ɋ{!҄\Dr3; fy L$&󬵵KD~:[x image/svg+xml g gunicorn-20.1.0/docs/make.bat000066400000000000000000000117651401157322000157600ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source set I18NSPHINXOPTS=%SPHINXOPTS% source if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Gunicorn.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Gunicorn.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end gunicorn-20.1.0/docs/site/000077500000000000000000000000001401157322000153055ustar00rootroot00000000000000gunicorn-20.1.0/docs/site/.nojekyll000066400000000000000000000000001401157322000171230ustar00rootroot00000000000000gunicorn-20.1.0/docs/site/CNAME000066400000000000000000000000151401157322000160470ustar00rootroot00000000000000gunicorn.org gunicorn-20.1.0/docs/site/community.html000066400000000000000000000005241401157322000202200ustar00rootroot00000000000000 Green Unicorn - Community

Redirecting to here

gunicorn-20.1.0/docs/site/configuration.html000066400000000000000000000004601401157322000210420ustar00rootroot00000000000000 Green Unicorn - Configuration

Redirecting to here

gunicorn-20.1.0/docs/site/configure.html000066400000000000000000000004541401157322000201570ustar00rootroot00000000000000 Green Unicorn - Configure

Redirecting to here

gunicorn-20.1.0/docs/site/css/000077500000000000000000000000001401157322000160755ustar00rootroot00000000000000gunicorn-20.1.0/docs/site/css/style.css000066400000000000000000000150171401157322000177530ustar00rootroot00000000000000html,body { margin: 0; padding: 0; } h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,cite, code,del,dfn,em,img,q,s,samp,small,strike,strong,sub,sup,tt,var, dd,dl,dt,li,ol,ul,fieldset,form,label,legend,button,table,caption, tbody,tfoot,thead,tr,th,td { margin: 0; padding: 0; border: 0; font: inherit; vertical-align: baseline; } ol,ul { list-style: none; } html { overflow-y: scroll; font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } a:hover, a:active, a:focus { outline: 0; } img { border: 0; -ms-interpolation-mode: bicubic; } body { background: #F8F8F3; margin: 0; font: 14px/1.4 "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, "Lucida Grande", sans-serif; color: #67686B; height: auto; } a, a:hover { text-decoration: none; } .clearall { clear: both; display: block; overflow: hidden; visibility: hidden; width: 0; height: 0; } .logo-wrapper { border-bottom: 1px solid #2A8729; } .latest { width: 150px; top: 0; display: block; float: right; font-weight: bold; } .logo-div { width: 1000px; margin: 0 auto; padding: 5px; height: 72px; } .logo { width: 250px; margin: 0 auto; height: 119px; background: url(../images/logo-bottom.png) no-repeat bottom center; position: relative; z-index: 99999; } .banner-wrapper { background: url(../images/banner-bg.jpg) repeat; display: block; width: 100%; min-height: 365px; margin-top: 1px; margin-bottom: 1px; } .banner { width: 1000px; margin: 0 auto; padding: 15px; } .title { width: 250px; margin: 0 auto; margin-top: 32px; text-align:center; } .banner h1 { font-size: 20px; color: #FFF; margin: 15px 10px 0; padding: 5px 40px; text-align: center; line-height: 28px; } .greenbutton { background: url(../images/greenbutton.jpg) repeat-x; height: 54px; width: 224px; line-height: 54px; display: inline-block; text-align: center; border-radius: 3px; border: solid 1px #1D692D; color: #fff; font-size: 22px; letter-spacing: 1px; text-shadow: 1px 1px 1px #000; } .greenbutton:hover { background: url(../images/greenbutton.jpg) repeat-x bottom; } .redbutton { background: url(../images/redbutton.jpg) repeat-x; height: 54px; width: 224px; line-height: 54px; display: inline-block; text-align: center; border-radius: 3px; border: solid 1px #7D180A; color: #fff; font-size: 22px; letter-spacing: 1px; text-shadow: 1px 1px 1px #000; } .redbutton:hover { background: url(../images/redbutton.jpg) repeat-x bottom; } .banner-button { width: 460px; margin: 0 auto; margin-top: 30px; } .banner-link { width: 250px; margin: 0 auto; margin-top: 15px; padding: 5px; text-align: center; } .banner-link a { color: #fff; font-weight: 700; letter-spacing: 1px; } .banner-link a:hover { color: #000; } .mid-wrapper { width: 100%; border-top: 1px solid #2A8729; padding-top: 15px; } .tabs { width: 1000px; margin: 0 auto; padding: 3px; margin-top: 5px; margin-bottom: 25px; } .tab-bar li { width: 230px; padding: 3px; text-align: center; float: left; margin-right: 5px; margin-left: 6px; } .tab-bar li a { display: inline-block; } .tab-bar li a:hover > p, .tab-bar li a:hover > h2 { color: #1D692D; } .tab-bar li a p, .tab-bar li a h2 { color: #404028; margin-top: 8px; line-height: 1.2; } .tab-bar li a h2 { font-weight: 700; text-transform: uppercase; } .withborder { background: url(../images/separator.jpg) no-repeat; } .gabout, .gcommunity, .gdownloads, .gdocuments { height: 80px; width: 230px; padding-top: 118px; } .gabout { background: url(../images/about.jpg) no-repeat 50% 0; } .gcommunity { background: url(../images/community.jpg) no-repeat 50% 0; } .gdocuments { background: url(../images/documents.jpg) no-repeat 50% 0; } .gdownloads { background: url(../images/downloads.jpg) no-repeat 50% 0; } .tabs li.active a, .gabout:hover, .gcommunity:hover, .gdocuments:hover, .gdownloads:hover { background-position: 50% -220px; } .tabs div { display:none; } .tabs div.active { display: block; } .tab-box { color: #3F3F27; border: 1px solid #DDDDD5; padding: 25px 35px; position: relative; margin-top: 20px; border-radius: 3px; } .tab-box h1 { font-size: 28px; color: #2A8729; } .tab-box p { margin: 0 0 9px; } .tab-box ul { padding-left: 40px; } .tab-box li { list-style: disc; margin: 0 0 9px; } .tab-box a, .latest a { color: #3F3F27; text-decoration: underline; } .tab-box a:hover, .latest a:hover { color: #1D692D; } .arrow { background: url(../images/arrow.png) no-repeat; position: absolute; left: 115px; top: -7px; height: 10px; width: 20px; } pre { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 14px; color: #333333; display: block; padding: 8.5px; margin: 0 0 9px; font-size: 14px; line-height: 18px; word-break: break-all; word-wrap: break-word; white-space: pre; white-space: pre-wrap; background-color: #EEFFCC; border-top: 1px solid #A9CC99; border-bottom: 1px solid #A9CC99; } .user-wrapper { background: url(../images/banner-bg.jpg) repeat; height: 110px; } .users { width: 1000px; padding: 20px 5px; margin: 0 auto; color: #fff; } .users h3 { font-size: 12px; margin-left: 5px; padding-top: 15px; } .users h2 { font-size: 26px; margin-left: 5px; } .users .left-details { width: 120px; float: left; height: 66px; background: url(../images/footer-arrow.png) no-repeat top right; padding-right: 15px; text-align: right; } .users .company-logos { float: left; width: 820px; height: 70px; margin-left: 20px; } .users .company-logos a img { float: left; border: solid 1px #004000; margin: 0 6px; } .users .company-logos a:hover img { border: solid 1px #000; } .footer { background-color: #F8F8F3; display: block; height: 70px; } .footer .footer-wp { margin: 0 auto; padding: 15px 5px; width: 930px; background: url(../images/footer-logo.jpg) no-repeat 0 50%; padding-left: 70px; } .footer-wp a { color: #3F3F27; text-decoration: underline; } .footer-wp a:hover { color: #1D692D; } gunicorn-20.1.0/docs/site/deploy.html000066400000000000000000000005271401157322000174730ustar00rootroot00000000000000 Green Unicorn - Deployment

Redirecting to here

gunicorn-20.1.0/docs/site/deployment.html000066400000000000000000000005261401157322000203560ustar00rootroot00000000000000 Green Unicorn - Deployment

Redirecting to here

gunicorn-20.1.0/docs/site/design.html000066400000000000000000000004511401157322000174440ustar00rootroot00000000000000 Green Unicorn - Design

Redirecting to here

gunicorn-20.1.0/docs/site/faq.html000066400000000000000000000004461401157322000167460ustar00rootroot00000000000000 Green Unicorn - FAQ

Redirecting to here

gunicorn-20.1.0/docs/site/images/000077500000000000000000000000001401157322000165525ustar00rootroot00000000000000gunicorn-20.1.0/docs/site/images/about.jpg000066400000000000000000000422171401157322000203740ustar00rootroot00000000000000JFIFHHCCf 7!" #1$2Ab%'BQRa3CG !1"AQ2aq#BRb3rCS$4s%Tc ?a@@@@@@@@@@[OX/ڭJ#{V*ՃsJ93(6i%ս#3q6*o9#A῟?fS"#"1?(22nYynEvilX+8 C(mݝNݬ띯UaO<:rGPY@{vruяTEsŤȐyFnqz7ޭ~;TM>^Mݾʲed?ȗ     hb)YdWB֭,B 77fnv[S3LSm._'&K_.;Gy qSyJzfp;[[j z=b3[JqN<-Y~_=ߒl9Mmy4m|̸MqBQG2יV^˞u ?=ԲZ&g'xFVQC)0^%Zm0@Ӆݯ)֖2o-hcitč/|_zݍ[^%JrqByD*+bN "<%\du Ƣ+}L@     bq:w7j51)2IU`1̒f7wig(.RxHY*"o~W~fTǟ|Nr - V)U'v°Wƪ̺‡͇-3D]6ߓ&_bޜ}q!:iJqR Z31{&B<\$g9oo/}HD ߻ ۖh^?ekJ@Nb8e]7x좛O)׊x`7!f׷N _cunNivl T9?Ӳ`̓N=B7t)V VkdegH~E:< ,`n<~MV7fEGS/Ģ?R?    ܅^ov7o&|Gk,~ݷUmD6w*jxҤZ0Ǐ[ޢ^>/1r2K$X-4sZ0w~ cǶ{߼|*  q ^Ɂ{o:U(Y@o;;>jī9IWU_Sٷ8yvk?u.3#O16(dWLgf-k"?3J.wIE5$5#Q@@@@+e/\+`Uu;dU!@eW4Qۭ:Jl:YB~ɢTVtL#V_n9)_GD1fRR)=QȤB;O/ٔ.OMp>H'b) ]Dowy~ ~,P"KچLj1*z#dv]ړ5RəRi]9k濽xlAl#24:]9ūK~ДͭfTC[(Noe$9h8NT׳Z_1e/%)y≹\@@@@V~VΖɱ1qXjjwr5c&H^6޷O&!Aμ4*뿻x^#_UpK;`-o^nr>>@ "c1y.4Jix;{rȕriv?!T9eq Th Ofbg$3wç:N4v}/9 {' VR"U&,h3ԪȤIumsjto4_hpDɓi{txbXxqfYز,ci?cGusF4:V H坱36[7RN˫o+-8me1~yi}U)e 7?!x(/"q&٢1򹏂lu u yHc3n/bЩKyc_}P"ۈ^/,!=wqcNKcK9Z; "hŁ$[ڋ5%E㫍EG?wdA@@@@ztW( j :݈ $p$aniCȄjy rmsmLB` Xp bc=*\{#$kŌ*RUw]oȌMӜ;NIBoxOe[svW-l  %j 07v6bYJR*r|Ϳo&Y+R` &;Gj` x Hpw?niCDTa(?E+6`     XYK- mˎp.yx-_qfee^RxLk*޲RLER>珿( ,N?kT-+pW= \3 Q6cxHo.g^ZTJj =BY-1 8`L7Zw=@<iw/Y*Ē-,|[B +oU{ 1zI/s(IٚKZx}2;+UCҮ\x`-ѸI | Co_5'ٶ*LES"VةܹHNLҫsY,yG?!Yx̱={B;S[R7Š$?}L@@@rRѥ){6.a6ljyRɫyXݙ7"@~*jJY~-5v =Z:Z#a/R(rN?+h6d ׈%,X =VE)FVi{=`gU<?dB45 H^%RZ6qNM;<]S*O81nwZGW#&{BIɋ/(B?{Rϴk)f?[JizkAcL R g)wnʔHJߣ[cЮ И {.֡H>Pճ@.IȄF8E'&K@`G`[0H 1e7o5kƐ-#4Zm&KU%7/ĥ/w]@@@@~6+VЇb A,$8Ivv` N6׻>ӧKGJ!1&623\ ,].wԤuQoP"U?s*;2gҋggo+s  q~Wf ʄiVG*!ztK9Ո"q%ڛR_F/iU~˞p+_e5~kɩbƼ%>ӕ"ڶnݣ.!hiF1 HWM     ?ux['diYKݯa2޵3ǻrAI@qSMT3c%2]{O̦[,}l+m?!_kx4}> v]xbl ږܭ`!9Up;'R/K.44 PD,}B@? Dy|#OJאMը]n.ތsѷ6+Yҕ{˚!կViGTqwM|E+W>|9HuB̅'-2#lщ*yF`GMhͩ4Ml[UѬ迏i,xIz+l\ީcʵ)|>Ygdd?æ:_~:2y?pnG( y;N?6xG}V];ѴQSo& ^5OGUvHG~s x&]~?l'SncVJG`|Θ h. 6@1Z$,veB^֥(J[,Ml׽,^jTWvVN|~Vػz';۱/KB|0 (ŵJb՟6D ȕ6}k[[fxVQH*M{ٞPu:]k{G[Ws)(v]즱MZn0/в=M{bo71v+F *3J9.R==vF\ @@@@@C&4LE#d6}7n7@Âl9r@/\Dm(6mYWٶ]AOrEΥIFEfS7Q[$}0Ӕ)SjӍ*T*'9(B)51NRi(KsF=G~֟zy9K|+8Xi0эNj'4'-%KKUkvcw]wg4b}R{ƭLAMyND2.IUq4Qa4)J|^ ԯ:w^ugvSڝ(JqW4wsjWr{zӥ4(QiӇ#bbhҳ4-;cybj9s-{ XIVcZdf~,ZŭXֶZ޴}*NH'/F{Z(U_ cg~Qmn)=jeW^ Vke x(C# K5rҺ2IJR~˫K?}Ÿ,iS꿀{[ٵ?#l$8kH]., ső(簆hP _&uN*:'M-mm֗Q涫%B{ƥ9'q}3SִmSN]SU)m(/xT5(U.'F[b@@@@Tu={/LM2ZM/u RXmL5'hu,M_k؊Yx9O:zyd^(@^1x"5R2ye؁W\V^W~юo;J{*k.d闋kF TuN5Y VB);V^`GNh2ӧ6@@?4-bs.Jm~6^(J]w1(|fn5,a}:sQ-Jj4{[jV>J#BXbJ;FօwU*PR}mN\(F_VR㍎Ův̏ӱ/6a>8 vpE)D~OMn-mΨ=џ*    >g1Xz<5MO ^05N>"9w)2t0yoPק|Bl'7 ЉwRj{ҸKV4ڍivpZ?]&V7=::ƛ 5iЭӷus(E;O=ކy+ ^4h\}VU /%rQx FqT/Tً'Wögq>ʥcuRS&2%N2RڔdT駺ktú?v;Dֆl00;DXڱR_,6g#y6\ƤY{07NSMNݲ7~kU*n2cZ{7ܼ#.!+Um:խx]ГW9?eB@@@cy}g:Fr|QZݠ9Prm4{kL01 x±,>+2:ve'S=:em}jpx*VQW4Yu6Yk·tsn9)SufǦj*s-7Hɇ@KX{fqoخ)ηƟ˄{Jkj ǪʭEGQRe9o>Q<=>FTW1+^)ʚ3C ,?gg4.QJ! M=K{c#z;nW4ݡ*03d1wBcmCQҭ׫֩(ԄTJRtmqZku҉i[}.W] Z⻚g T9v3%,&Oo.qXW"{_[vw_&,ӀX)bu*7 9L\9L}oD֣avԨQTM֍;hɬJҋXi6W^Hi|ccXawgTQvq 6'qQ(8I.OchضWɸg5r'Aşյu0PR<[ !D(iVkyN϶B8C5nNEw{}Fn-mk[TOQ8mT>IղѭѧNhA^f.c_64|XSwaV15,7i=#TZSZ4SHЩ4u٦G~zpq uInqLzfɯ/p\5VAuUOxg>ۉɸiRz>7nB    576.:yP%z _K˜0bzչ)S?@/[ʿjv7Q^j5aJ M~1ѭ{ZW| 8,Vi{^QU?Fu'}+'@n79r}D x}v#֖Ѿ+㰕cXa\fUH1JbGg𥖱T*k4h]:hu(8RnVJuju| Y鶕8Eoק6)ҕ*Zae#u]T}="b5岻uߧz:ZMoYh AS2D`gXp:5nVeN9(NRq]^_D|c}zVy>Ε+hEՔaysq4Y}"e֧ÜxS3ǹ,f,5h͛OF,/;S "DO)+zd]>歜XխI&8ef]מ3vz&R^T)n9G9P}8m{^xصK[il7q9$Fab`F1GbmVUeVGr춎3V1Niwwkүi^wqi8ʌ;YMϱy$\x#5`c1x5d5OZ'cWٲЄslv'$ZqL^go VO?}gV^YχJ pumq{kjJu޾,{cŷM@@@Y9A3>2 t Ã3wB6cԯӡ2!EV4jT#,niNkj쯭mg%Sini46.-jmqJT%.hjI4,vg5l&;`s]s9@([ xiw 1wٗ+Pkq^ִyk[֩B~JSp%p]{ ˫\YVVIR+/nx l<-'J_Wyq>m^#;CLqe Y, `&'ֶ B:u hN,^S9}}%uljo Z:M*םIsb%o®[yy03GK#?CWԳ nrpkeb[Kk٫Y[e-myJ̳('t+pOյK[:UTԶRI%Ț뗒.euu8c܆yb+/ $#ebcW~7pijJc9㮷j6!EHXҐ}ܳzK4צe*RXN3I2ݯEzVcs^MbJ%ҕ+ycƑ5b@@@ozvŒZxk#3t L^X1(J>XsQ%_O GkẤ0Oxïԍ3Z5AG¥-T/;%ZyK1}"hq v( }\)^B<.%asFJ*iRgPX( ky/~L4l1Yףm~`>.^Y*v#xʱ?[y}оOQ\mr,rɦ׉ҸSѭuK;GW[Ź{JJQR[w[X܈+/nۭ[! zL]1ـ8@ќa'3jQM$)%ǼFHqITdfM'$Iƥ$tӨ9)ڮAQrF;w6Ny{n*͊Jk+fjoh_XJ38\ɬJ/4LǕen𤸛h2T5uek9gT#]N8?+m|G;zlf*msID^*#y9@jȏ$OѴbK۪_2?^$X    |Zk0qڄ#kxEb$jƅ[uWhBhڬLڅ RUni7T_΃R>1IPBIN.2\ŵcsэŭ)QJ}% y5ބ:u"ԌjAp9>ǥ]xۅ}G>L zl5!< Vxrsk*RKZО65Vܒg~+zuwkO=W )Z\U)Ī0   W8cypkƸCfћ] v G3|dF1kkc bn1Zm{oV76{o.ZՇX5$,=/Okiڥ;ZEg ,JX>:g o'מ.MnAң}.Gι[z4ⶈ"Zx!O!1`*5k N94W.w:G>b(/VRPۥg̜_WѧRB]cM˗cͩG}F zTj߶cQ 2$`DQAsf p4&vvgeqn2MI6k 5M=O5 (JP\e(5(<8=Of韚(@נhUrT\@#Td};g.ꞽAZ8hTbB2%iAiW0qRgr0yiggU=žxJ-3K+z9EԦ泶|.o_+跦~0?$Onǐ3^~`-Zx 49OPt,q*loA}VƓJ|죔Vi.Z{˒QS|\gSp 'cZ] J|]ԂiTs˙rRaJ48GNWʍxUqFVPE W cf.zI-#>}j     ٸ_.G3 NjX#(8LȂA jڌjȁNf'T^EAΎ\X9s&HHpu?XbH,NtxUU=x|ޏk"lÚf[]kO6V棅Q>oZ,UҭWk*{{?VyBOiߺ~<nO)yr*%bDlrzy0ndP]ףM.nNQI< wpO])?5oN56ܯ{ErΧ*R)? +EC}n!ۼ=ɰI#_ =|ZFBUv%4\Iۼў<{Koo\sĘ_`5ܣB hP񄇤(:hPϟMZIY ^ '[hIENDB`gunicorn-20.1.0/docs/site/images/banner-bg.jpg000066400000000000000000000011431401157322000211060ustar00rootroot00000000000000JFIFHHCC &"#!$%B ,!123A"#Q4BTCRSab ?˹˸.s{S?VA 3_*"v78f{$0IDt8֤xDDpW]ժ+;G]v-D|[gunicorn-20.1.0/docs/site/images/community.jpg000066400000000000000000000363511401157322000213100ustar00rootroot00000000000000JFIFHHCCf 7 ! "#1A2%QaB$&34CRVC !1"AQ2aqBR#3SbrC$4cD ?a@@@@@@@@@@s<lke"r4,90c93wu,R2J`%zx0{PrIGb-yEr2X/(P)/Sal{:l/n^A7_&ሱXqwvxMшA̝wvBiu%p<Foܥj6/%Ҧ7+j؍b6'?^㱦g7(h^ 6W 68~9WN~,,k=5^ل+1dziSH@cm,HO8Zxֆz:2'g;sR{r&y?̳xYBkg3b$k8ݧg ̀)+!FK3ġ{z]3i`˯?_I:ֵZp\`YZbi 0!DHJQ].쮓Me<Ѯ:     <&ﺶ4q'3ƕMo\6XwՒ?O@}l_g(yGNNL$-7dRŔq䟲d/mc']#[9PV9`{BA12q[R+x=nɂ%s|qmWB/vlع`֭1lZd=67!sR!bJD)I)LRy;o;ݷLp k^sPȶOqC-,#cX^j8qϴ"<'*o1ד^+!Դo0Zכbo\"u/W d.JI)!\{нW=J5xԥ>>k$k-'-if16{,ԴC$$HĠ4&BR{S;@@@@t{a2yURZ&xwϷE\ IhjrGd;D+e.KH~Ê'f.>XV FZNI?bgkjp^'_y Pg!k "o7 #ga00#bعINOyuP@@J\Svt>B-fߍISgfD%VoY^ŷx˯E^\Μ%KdGlv=R0l`b `_f!];E< fNe( >s tv~]z%),?U @@@YΘk)D VUn Z+Fsiy26'}}Ƨ 9FU ]ywdVXkKFJ_k+R'S#ٹ&=Cf.43qG/ӄ%M)^ WY?͒gq-솳ѻ珱;8dffr^+*~2}_$GSzR{bKU20,YQEqɝ;y?"*~e_ەcfk}`,l*7(ZL;1Jefwd2_eS^a%Qx&x.?óvvvS"~  f ۓ/3i!|x8vbem̄s'^)̄ylb-FyW R'<~+, $   8HF9?F1n37W@b+ḯ [Jʹ,Fw91# B(ìZaj4c.eyJ_v3+/"@ ?GpVnF=_nf~Ps԰uo !Rhţ|8̥O!I)Nr~.;N*0WDB]Y-FTՋ]3ڰyY8xַJD7-)7VoǯOF[VI ɾKc!PS>:,wa26~34&y!wowArٯiyhecJ@]n 8 tVgco_H^ـ䃲TjQQn ] Ӱ7<ЃB|4=[W|?>W"+@@@@GXXv`zuu0C! ^.8vW?3\6W`ĝũ)~5g̨1_"@  g-۱u;F60V:5Iۧ{u;ݣ~%)GİߣKY u $B`gy;n~3i,HR=zWXk' B9kV%/Jџ`m%>֣y?La㍉N|")v.>øvnfnYFJ4W_5+rx{]YJ]  oc'or#,׏o)}J) S   W)eKO@a @q"ndiKakϚs;'8F}ÝḣA,aRR8˿Y+.|V@NEh9eolCcUqв /_t:tnq}so51'Z?6^ڝ~>/%U|?؃0@@ ["$!JRI1pgIF<'hŝq[zNҰvN_"ើjdO;]fҫ_ -[3>.ѱFog3SU{7q)-O<ڃ?).YuC"5@  >-_ TkE+UX`ēDa23;Yo]Ah8o3 C\[<$ R#TW0@XkkDIXXuz8Ongz͊| jC >9N%ː쿒W%f]IfD!%.0담ᐄ8ol^OH @@@@}qLSqϊ&cWOf53<)TLFq"ҝ%'Qt~.^E7#F 8g!&Qc ќ'n쨓HJ<>S3`B}F ӀsftdhR1r@+|-;f9#.0=aUMW$(A`%}6v!-c1v'Kq]Wʙu(WZNM^2o$uwNtc9^KxSd%r}'] =G,>S'gb]<ٛ4y}!#6Y#y;1     M],+(9REp`HyXǞ8揮B̦|3old5m'IijVv1k1wW4hscj1Jߩo'r: ,~>rUCz|i-FoB, {>yGj_|sxK< k>H5xnq_*RN_r<>>2Kc88ۇoTnCVdAX- t]"[GXT-S?q~ (_:VQw.K4/m1 ?]1Y;MT:2q{c{jZ@ɚ2jF)iƚ=f*IkEVQi'yKXzJE-+ 5nR߮UgYWc̳.w7RroETw&o Z恂^gԣ-^qFIl s絥R8ַN*)5!5 e=%.wPSn48K8jLjR0?sFk1jK~t;e0    As)gpBI1 9;F0Y)I٢R9>j%W&NT2t.[dʑ@za^>1(:,D!JՌN[XT=q=9╼eRrMmWUӴ+*uJ֖3:ړ-:pYZN2Msw/zkśu;RZvas>)K)CVwIץ#Q[4s[{pP`gzukx[u8UszdյUؽ*xg֎}TԗZ_ub1lY+y-f2\Y<ȇzOjɧ'yL,);ּZz^Jf:g*&9)?{f;ky^VJl2@-p~ wJ[|@G ͆jيi0&emKJifmSNҩe_}'lu+ \=\.?ͯ-' m]Svδ=foZt3b:1Zj35::} z<´.3r9۹6۶'㝶-sbLN:Vs^1F9?'uIڬfz& J#,),<¤^t7ouN JI'2SU@@@@J=Hs雖߸eK[^)[;}\);v*솤._h@a5}VN-IwU9lsNX55u]FF{RYmJތ>Uj+W5IӅIGUOQޥMv0%M/GJ@tlhC_7hEҵZwo,S;E!s_kn|ڶ}7N=9{J[(qqfź/8Pa&YҖ;Vj1usU(SyXC˄@C>2g6o-^}դs:yM;\>bIؼOJ*9i-mN>{py-E] [hZksU-.r}عn݌^}[j太A[ȬkVa*Աb"͟ q{n—7$L)'(jH F7vMs'TjCSɿQҔ^c%^Oēb@@@@eKZ<{f8Bݍf3kxW+NQQԣ K8wr.&yCR{ӗ,۵m()Jb#oq|]>sYrS8󔬦K?ȩB?ɼb5svZܔjdiQxJun%PywbYm$ԒkF>@@@^k8y`ʎ7\!vr^ĉq&vw{UkdA_Kѵ ri:uɯrkQ|ҢJ-|$SzBe%Rģqy}_P!tR57\qP@ &-ӯ-s͌4"@u1ݳ4 9s^p2:u*N^:6U`&5oÜCyMUuRTSkN EgCq,nBD9B.2u3(R'G;?oUFMlo,/m;ZoXGo{jRtF<$lnmSoN(':VF4 n5||<5-J֧NYMjˈ+ߦk1 h_Bj:[ msϹ^ T,m,K]j|=f-`b]_]A9 )5as>F6?vKe ֱӷjzFeF\"I1<$Qģ )A5:rtx5%u] jQT(IZqk 4ή@@@@UtoNڛl.Wݩ.vf`0`'<#®?0D%~-3.4t{Eԝ_&FǞU$g/ ӵTLITF q᜼amjS;xCsvʛ>>v4 9?^ _pxƾ+Zo#5/pNQ.lԓTa&ԋޕXx(6pОxNݖ'״_Wr\CO\n>9|0L~DްJ->K]h5䛞wS71 }-G~oМYNӚr~Nrd=zԄ@@@ڑZAf 4ߣ984?wPjk9W*QP݋h-uc[h"EYz'fhQ+J[|WͿsRz-*R*W;m;ikSH5r"V _`O}o/wQOO|OO&|(:\ul9d%\YcNmp8EXiwo;y)RBpR_M4ג;B-]#N J2n-A)BX8I8/t2H7o]^CŞ.nbw v kvW'Yv;e!E^O=ae׹T9ӚTe˞*Ϩ^^9/N燳>֕,7<'/="ckF5EY/[h.x?^:?K{p5K2")?~9^:4iЀ 6g^<*0K"# xBYC$bBġ]cFс~)MF558x}zi9=G+ e64JS#i<>?j^ITVwm {b@@@@kWp~7Ks{'Kso;;c+F DPSe[kJXTTsק{Iv=~ZM8jԽ:òG>"`_#Xܻ-ܬ2*;}ܖCW )yD0[W쩹OJVRYF1NPe9⽼nݾqj4.ssJޯ+TgN\V{D]eli޿lǶQn[%x0)e,VKɜ/T-IVUխq-)ݓxj>x%'W35H3GNqswҥ; 1$ ϴ9eb~x<󜷸 ҟ)1T9CI hR2w-S hA;}ZTnJ [Jy^ܜjTrKfmiq=ZUBYNƏ2ڬ v</?Oyn&ۿNwܯxYJ`Ɓqږ@Oᥭ29CQJKCiڄ[넹`[Ђ4(y ]!]ϯڴmNG-PXYnOdbs[YLQhA你l@S1qiyHq ԒZxj5t->N}[녴#˷/*Ԟ8 2GڨjwK1Tn֗Y:'?\|?zS2tmQ8`D(u7,\_Fad ?Ipu@)J.2VUnMmyq:{ƮSM$vGe?FIp򶾯Z=}p$Y.^A@@@@y^ l>6xf֝Jjg^W5aNx^%|ݠO&&Bp uiBkXԆKlӊ;rF>9褵ץ]Zԥk&p%nk|zZZJT6kWg1Ih%Zޝl= i+9;ݙ23şYNZ;;km483k:ݍ=NnR$7ˉӐ1^Fqٶ;rIrqc;m+mzruZ;9rROsq^q= U)r>FoEKԭ{JSSV<@{0s<ȺGw} nB#.ZjJ&G$v^iu5K?ĺNI9?t#?rfOF+k:k}wF5$g4Uy4՗pλ<\#Ym_^n;HZbsRӵFVw_jr$MQ%$*0mE? VPX~+>>h*4iя-+z4ӏͧJ XE"uWd@@@@/bxC[^v{-wX2b W?c'emq⦢jAeR*FQ{Ic|K#5#:U#¤\e%(5E%Ogj şMٟ)YpC5gg;XG֩rXl\QF({cל4IhZkdUŅMڝF# Q8^ k6J ΙUNOߵVYW;X/:xЀ  5K⣘+Ɠ/qT<{'sd}ͱvsg`L`hBq>IEkA5;M1Iu S5\_=e(_B).zW{IմTgyy$iMR^fzOR0[DU|7[1M[{JQbmnXAB+7΄o/'S   vi3Lgggn(|KsÃvv3|_ǴJ5ӿK+Kfz%%̭iΝ]o:X|}~a0 xE{ab)r]s.^mŷeiJ-T,)ӧB+F)/?=s*@@@@Yc6 ]6f̀|(JDH@8f3ApBQRN2YO}UHjutnQ 玩qxsgV?-9cPTc%:IkI;ljRLW?Z*7+*^}EE\&FOz'ݗXO~}w&g7Ɠ?;ÇZ36sifQ c}𞯢9TGlMu(r\wKmD99c=q N-yE:%RG|z7i,c]^)֫Ԩ̞ [7l:n s` Q I0#)Iߣ31sQb&I%o"ztUi҄TaN\9=c)IIe;ӷ7sϟiv"f'2"+3ī.Kc^Ib<wu*w:=ģh}ٽMPYS[jZ^"V^Ρr*{;J,؋8Wul\N>(kb͟&õE,ndc1 bB6V4-(ҡF;kj -i<{$rmMM-eegPӭ[CJya:&9ɹnS99;"@@@@@esMɟGFlL<ƟZpƛ}U>mRS}׶|mI8)&O~i<#ȹ6YCo۰HaBA^` pWts{VIe=K-BNutoQʾ?VolonӠn]{,McѧkRZll&)6R.sܲ'1&8{ɜoJŲDL^$ОK qvќZER1BP+2KXoU.ju߽%w^lg'L=ӗx! .U%,yw'ԱWgk,Թ[=?X"\#|oo.]GHєa<*Z4ҡ$o5Ս4)ǔx?Aa;A%-GU/֝%BʜIF1 yߛ zE8R>w(ŗf"b#,Y0N#x~L;JǨX[YKuoT*),,8lxs[[KպyMJs{NMų\|Qgš{f&[`firodG q']nOogo=¨@                         ?gunicorn-20.1.0/docs/site/images/documents.jpg000066400000000000000000000422361401157322000212640ustar00rootroot00000000000000JFIFHHCCf J! "1#A$Q234V%57Rabquv&8FSP!1"AQ2aqBR#Sbr3Tc$%56fstu&'4 ?a@@@@@@@@@@Lp@W)v;hRmPyyA1RQ^0Y?>֩.Lc;|UnEB,&z,K?زnYy}D܇b\0c!CMaJ1ndn :UB~G$혺3699~NЌAWZ_T9_0P"(se 2ݤUH@@@@@olNSٛ^KFFK9t`QNfd14Nq̟.$I /'L,]6i/>L(eБG?7FкKc=kB p'Viwا#Vk{2u´F.1]#}wMA6ˏPBV%( f)f9{/d[y>e w<QqS =ѥ(qNzu/{fn#i%qZ]jK{r"ܑm7/٧]_/%=npHJ[6,S&+fbnurԎH9F.AbF3\aN?6qg!ʾ YY K/3O jƴByc.SBFW̑I+=%EOOl%0[$TZ,fgY9Rla24|mXyH-ZIkr$PfrQv!{IazU |[?'Efb$ i (P iBpQvvweqtJ}@@@@fl5ӫJ3[.hҩk>FcSX"c%?JA]^Ʉ4#r][DRu!.|&O1yp&#Y&>R/:Jr䇟cɍfIԭ+u?F~;//>4Zfܻ`),Yh=,e1YH)&2'yIZ7&> M?䣓]yˤ.T#ynIu=&PО%M/kcrIqƼU`(7N1%`zNl1IdF02HB&\lQ:-Q/w<:|׃d(Tc- >f˸f [2gR98qZ5$yO)@@@@Rsv2j]R۴On1E>e g11CPb%Rn﨔Ȼ:0I%O+ flYȦCQ<㵪tkʩ}KUY@@UY̦%8zӬe(Ls@qN`4p,d9/4d%(5>4@mc6n]ŻrL'Bw~i].0`JTÓQm%:u~{9NK_l$$    0&^,-wIː٪yU54yR{VMv弲xeٮk!2Yܩ \Gyd[a*k$[3'҄(LZqvK|H}:^Ok/bd>[&ͯzV qVW\JQro ɑՙJ'(/Q_I qy=4SHѩ?G6)Ͼbws<2r#<<yNp){RuFoW@@@@,hĀO)xn(LR 8̒l=rv+ÿȚhIJw-9~9Sža\.K3y0b #yK"H/j/sg^юo ݟoruP+ϬN?AˇM_~yV;O? ?Ot:))c.ߐ?#2_V"QԠ  7`*&KxUd% Ffj~c$eR;ǧ{l4{[SS)~%)@@@$"QSn$% &K:Y@!,eis21̳\_O#OZ/TѲK6KeL1 :];/w2Nײ|ֱˎ9!͟wO,FN-]]wfws*~}9!&h6/|TW`DV(1g߯EBbk&O>< %6.3}"|‘ф۬ٺ7ٞde kgNiByl(+xɟgovTeKD@@y~3Ƙ<>JٝòwDċ4_aE]yoROٱ U @@@@}T7YY\Uqܩ [MJ.Řœ:lC*ыZֆdE^<y~'OoS^٣j+-[jٮI0!EHIP^.Mcg[4 @)U"8eni ynϕ#|;4XАܟ7IrNALRXOWnoZ͗H6u?螵/; lSoR.KF`zt$Œ p4&v aC>8\F3#6|yU`rޔ|BxN4x%J̰ia/"y Uvʼuϴ,b@ aNAѼOBpx,@D&ʵ)\SV;eo |Mq;קʎan9{^DaCnBpln*6w_޲S旵Qet]"%uldT@@@@@0V9inX8"7%YLZ2tĎIÞ.9NJkZ5dS9gV4K;/'÷EWgFx]l;;<맳pjcMF|53U|<=R`  kG j- 0W",bERNQyNNы;2]&qiZ$xr;Uiָ'`L^d\^`#q|AB~Kg]퍷/r~?D_    ڕˮvM4>Q굼I=bxeV%uNnU!RK >! ~>wczAy Hxv nGrN1PIQ79Vh>asV3:y^IIyyKuC0& U*72VCV불RIb/FD$ߧW2M$}݂Y2mRrl7+,u\qV`g̛kKF/B._$gjC˺Ojtf8'?ra]uK:?ϕ2yUhDz?}o˫ܨ3 xkz_qNq|Γn%+3q &|X."&,]ICoUn<|%Mo|v1nֱRd5{A%sqx% 7E6Tq̘sLX<&_2AǼߐ#U |wI>;7^e/f2R:$=9҂w?_cGXYٳk;¹Xu%^~[UXey轩#3̒G-_EH7[RKU :'0#?"ލ{p yNN^rí%𦾎m{7翹{?$_k8JDGfyj݂?Qj-}qV E3W3UՙC4[?s6Fuz~JhS=K }KN@                                                                                                           v=[+.V2vqT`knx rU{ IԄ6oEo'IaNBH7<0r돥Ћ?<^Ǔ,;#bysgc{qN^J|R~M;c%:LMORM\ѷ^Nqu v)>'~뽰m)-^6WJ#hq;1k\׾ڽajn<խ\/ۭE̥MץԪ]I}:9U~Ǭ\iݞh9{ys^8}ϪilF66Rģ[e_jK/;vWG|BslxLQ{`G+ 9#,|h2ּiX-f&kDF~~C`ükas+g0E{=M4*X4jv6.!iiOi\Ow9ovYI %ɼ7[,uMWM+KZX>iӧ՞%*q+xO/xiKz% yqFG!Xk;+mc MX\s[{p[z}];~3慺}!V/x֏^5]BUmxvK>R1Z;58xEUo_g6ləg#dm岷bBn2Ӝjծ*Jz+՛֜Tܤj.+\Ty^֨V9?|)*@  iYE8W,k06}F;M8.NNVS{w:|8Oz`36G Z-6\l ީi;dqi tJ390gweGvٔeo4WP~!%O*Ï4kx[IwSDB@A/ovL20wPV:2^w_ԝպlB'ݭYwkܼm.i REMa՞|GzKjt-_r=˛dž2tmïQZI  >^(r܉9R c46-Jw7>iRyɵJHT6 ʼncGA;9iwr)JO_N~n\yJR\'.TwU%+9UN~.vS(lЩo7Nr{_Q'K%Nfj ,W'ItwGfvPM4W(     j \K|8s;kjMWn6Kr֘8=r%5+Pw0N2 \]+E.˖>ѧYR>SK_ΥMˇOR7 +9'(^f .\rBx 6Ǿ TmE'8'i5-/)uO芟 J*]|5w_5_ ϴ,&YK%duk c9b;BvomuÈjׯRSfSQ6[%Eͥ;{+:\]^;zyJP6YyoI'm߅ׅrK1ꁵk9b]i_27ؑgi/G$(uqBXklۤ>~&?E<-ET!-iRvj<υlNJKO%G f28l>&*9IQ ӹVL388Pksx*PM)JQ\gǸÄ*]]U,;9Aԣ^4:2 PaD<4sCیb5"*+Y-Zb^Y޿s!\rL5*d([w%Y6=3j;}f0ORHQ۬%BAzBq;â(ũE>jn;NO-5IG9 ũFqMI|J##ͺi*n̉x+w+{þn PmqS՚Ȍ2Z-'P{IR#GoO]Ǟ֋}k\gbJiq5 VԨJʂ~yNjܸv4%.Ƿ\(Ӕj$@|UNJw+gڟZ!`+,-lf3=1uƶBlVZqueԪǶNJ_)6Z Kj*5RQ^iRN)%fO5zFæ/Krzǔc5A|>pQTD!e(Xk ---xEe *=^4w>μ{J]*km7Υ XzBV:nӒRTW2]ևeF{%ړ#=BSykY}nqŚV=AVhqfWH[?m.(Fƍz߬u%+%ZҶkwum7KmkЌƕYSNXK-G/ /r"__;{\ֶOLhk+]{.(yeduνxhf5(רѡqokF*:U9\7Jpxo5&mC-,Vxh<9US{8j-(+Y43oro e66f )w\p=T+&J}'E\+t=R˞~2]|oߓ }-G~M(ԓyrvs:Sܽ쟋f6    >er0H9"4_.<2?k´bЩ :!I]'tb23LWtVNnmNr״xaiSvR^c/:\@M 7 8 ]#/^F] :qhlۆ2fǀ6p#iE^f-C[~H]ZԌ*5% {.;ע*>p<3N \Nq]ۊrg%Imk%NAN,yڸ<U9&='K!8Vǧ:~3j}gVv/ԫtnHWUiԧeNXmm .QJPNS:.WpޛrZ3jѧ.Mi.Zt8P89TjKBn8Xm$if17h֋mZкN8MMTB6sRm+~{KIK0}oBacK?3X% iǴhV\_UT* rW|*]6>i=L Y_zBẖ:S 9\S]Ӣ֬5-_i)?0d/?s[5 Y :fYɛ7h] |@QÞ0$%{]?=<8M.Bx4(~Sd1} no~hF?8zě8{+l=ES:ҷm8{=<^Vt0    e>- Â1ʇwoFJ '$b;hMP( zMZ%^ZOΔx|+{i.ׇM&V/wyJ4ĹA簀􎝲Ŧ*K1bdQmy(M&l/9L'#iն\n`#LyVd]Nzәe[ 4?cv>TJW6E%ҝ5-r~ivzsCR z.qUjTS>O9|Ob J΋=d]c5Djyl /"PR Z&,[zպTBv)NCt~&mZƺsk^|RH8ak8}ej<DB̖ HȤ7tP6s,JyԳ&d[{7>?cX-?>S Cxim/zȫ;Ϳ`,.L5o^T,5fcO^ Bq1!81c[蔤/Rs^\J/|Q$=GkvJV={XKcl7     G|Ixhe*@7q^#D~!s^V kN0dl67~8tN:]|ֱ5J^2tNmQ*.1\Ҧ(\iSI.qYeVҝ8/&h^z|G꼃zorzL^>Wy6+l0EW_hj;J^EE\IQXFONQFyw9;ܸXѢLޡjYgXx@go;! jJj{7ԡYh9JO,O!iԩ^WlrS=YciWԇKW4P MJѡV۶:i ,ڲrɠ W̦1&p'hŝMr!Nrj1S'QmnөZ)Q:jIB:qNrxa)JOeo -c6s3.N&0yǾd~YO] cSIxdD2>-/_N[,mvlO)vےU#8?N*evj>]Bu۟7XU*2jz%jMg-cZhEruBd73d1vm@6Ofw+jAM Z4mQomoZ!O|Km)K-mrr=+eegZQ[CFyaV&9ɹԛ)JNO?+     ƙ.5Ʒ{QXo_>a^`_rtEsbS|$z.7)&[$Q[>Ϳk 8K;wbK=$ rM *`%3ܼ;lÃ3ڿ VN"{FCyz)Ne4ڞ/GrV99ymϣn*t\qKԦ*tSm9ͻپQ;;(U<ۧA38E\bFwpF=$:eCWvՂ^NtEzKhW~l''AwB]YP^NT&{cOkŷ8eXUi4'l?m̄Z\"=8]$.=e}zcԗa1 pN%8i2z4<HGm }oUcmaWX zY֓Ivo"YOFM9'uwҥNΓg5Rxͩ3 25MFǭ:})~cDŽ+ xM!/V'xf$Pzֆ-c{YRR|vTK;n}=sv$bdJ:^;Q[[O ;YT}Tʭ̗vJ}wHkF5õ[BŚLvbn߱A­(Dz{YfN0ed/r N@                         ?gunicorn-20.1.0/docs/site/images/downloads.jpg000066400000000000000000000371321401157322000212540ustar00rootroot00000000000000JFIFHHCCf H ! "#1$AB2346CVbgq&QWacuvL!1AQ"aq2BRb#r3CST4$Ucs ?aH#.I1c[u [ /(}H ͳߊO}|ps2%|%_aefBϏ(\7 >^b6^N(uymer4df&b+>5w]"*V\uwT puI58\{ߴI"<23Jx2 E2ǛJ/܉8ٙ2%rBjKxך{@|gC"}!BҞ*SeGʜu (BK?߂/!M][-w;gHvrm>Yq+#mNTͩ-Ñ?۱-$Y(Y y\ oVkKcmZԜdq8U=\x劕7L~ė[ZF,}~wKs~Wo.Fn2TH7qv=S[Y*qLg'4?FdFR<%%^pge/Yg? B>? Iu|$zxm*)ruuhukr$ 'GduAV$̲n_MzR!¼UmnL= XpM)n ^u-pmwS_DrI$ Ou|>j//>]$߃e56ͭ@IsZD҈!d 3#Ez H8ɰ&ԡy ^]|v\7G$i~DCQX1a fh5jK<;kK2@ ;("L)C(z4.֕|<r2 \[s)XE,S/ ؽKK\/2mIu 1a;Ύ|O4Q V¾uA{7/w&]۹[|4t£'ǘJkbm(Rym)LA%r O1jS̯Ԃ*^+C"|b76C#4JBM\K,JqIB{Ԕ3ƅSUrVZS%Ǣ/51%P qoI$?n?݆t(eF7;_UO*x'{!Ru9e:j["SjrI\)Ff-cpҺ,א~2JDo,d @vpv8 [t]]dI2_'](Bڗ׶~qTcʀd̎fORwtM OiXWjLS$9 ۆr*S$4~bᶛeD}&'Hꇵw?*dܕ_ؠ@)?a4Y1;ROuLg)9-;¸2R~1?6ݾГc_s)ǓKmk2m~[ bsOnTٟ ; $59ifki&4(K~\w%Ȫ]SLj}lIY?ʤ$'y}Xy i!)%^LU==°~xױRݟs јW''m)y{{T|YI8=zmOfNH)w9Q4"35yjH˱]R%}cDmJEIcuYG}|IזTqRDf/VHGT<T5Yw/)ST˧8ŕ@_` |nȲsjs\ Cþ>B7Y٦sSo۷/K&C4~e_;$uQEDVQfؒWVْk!-e5’~qjJZ\ξ&Rjq~ urDE4H>FX[hcR_R_.^ևחb -̰yu 䧚}Q%>GiYl;8ME'sosֿY+mں)qMjũFͿ_K 2jcOKPTڷW҆򥿧^Z͹%*'%2ZӨSn6!֓BдhZD*#%$Ȍ@Z MXÁ~]sշ0(εt#uX6fJn,HN5Ĵn4ڒՒ/,j_V?JKK˯eTUQC ".e[y$<. "-m[6{Șel9!d5aMqMkȔh춟aFMɌ9ؤ.E=O-a5,r#VKIplG+ZtTNʝamiJ\.r0KrQ!ŝt+SmMl[)Jߗ6]CVl24V2J\&\\vƧRF-)k!\s{.+M.ERc&1OA(6-^S& ή-TXɥScm\u[jp85Obε[ ZL1>J[j)8# &{O~Gy<--Ȼlokڼ7{x|<7J[NC0 lq-0P2fLGM} qodOQ{_ lױs&ϗu7,wEqP#4l]_AeZIe GFQDDOBǪ$r&<+JZۋ3>$b)9rI$%6Yjc*HYDSI#SM?:]ㄻBdzmq6u<E鬶Vu\uxQK7M Iv&g\nLMTq>_;Ex\6kxŖK`cIrDȋ-9=;]kY> -\؆tZ;VO=erฎPd]B1;&6|KVEm mDK[?Ċmx)`V06u^,Ϋ!G>Ģ"FIUe=H.8me^moښ^8$wKM~/êWSL>} j[dYK󿨟Qճme5%M+\殱=>3+3% B\4YɭL{rF3?XYޓ;z93[5mv5Sorĉ?陗QtϖMsc>vZXǿBufiQ<~'jGxٹnƩ겏/,([*3o䙝bRMjv?%}P^LE}-|&ێ;"ѤԖ^ECL^vﳗK:d^]_[+gGYLOJj/T$,LʗVک~[5n}{ۘ$,ƻ)&mYEYT-?mZzߪZiZͅ78mɀlIHmWOQ*TfDztOHcռF::yGǦ=ݛqJ]);Uc=KeKEQ*+UoIJO3j&h14%Ķ:mKbGڲ=12N4$3'!b|i1Qq3ʦo'9U5/5]|OxFJH-W7&KUˊ2]2g\liq arYkX={Gj]^nF meq쭶+Q)M7䙯3] PMqƽ)lPd${nm$BSU 2d!A9Jsa&I6&r.vӓg~>;%ț{1^yJd7:%"AvjOO*k3g'ߺRM2K yC/\[vΝ(OT$eh)㴷۾;!F58o\Qg5\IJNT#([pm5ɹEzNv3\Uv_,“.G8oʻb벍P{NvR~zJ6"KwGjG[c]u]^he!me`TcCu|5Jo-OCv4ۘfi*0zEiٸ8ؙQǷuReJ2Tv>Z~6]rVW5Q_8Y\ԫzМes\^h/TiU nx:h.c"7_=WZefj O dq[s*#}Eųig 'A7U~4}TkiFm5%\Eoc%gT5u^4B{o"vr ;o՘&e<ԈĆC>̼҉m:Ө4ZIhZ )&FDb)(iMsM542hPg 8-J2jQ{Ei53Hv\O4ٓN]-ǒQGۢW/)t?PmqcpdmgQ> 7ܝֿfU1.<|_EJ8\CWQL[ߪ{TXj*GnE"iWqmIC yWt'9l)m&l'Wlu]3*hӏa_lHbˆ+p)p-E6uF7Ն穽rEu5JGX?tfu<ӱ֓m Ku-pٻwٝ_]Nksf\ktGwۖSҗbAڍGK񩾬l+iٕMzJ}eNE^V=vJ͜j dۖmDyC47E8Ӓ|h՛_(yKWvj6L6mGs*9KB̥\$nFyNzVv+z7!Zy.*lLAWѯ;)Ey.ٿM/KZś L+Ü40릃ZL㺓%')>q.kHKj\DzK/YGcTzg(=W6rʟ/BgPhCnz 9ԷӣYVJzm7RIR&;C:Ҵ-$dbVMʫjg]8(sM>i7]uYΛmevABpvqdM=:{}DiHAջJd=U­`k~֙|4Cg{M +%\Q2']cI3>ʮ'x}*pҲGշnnie%&h< \͖VTkŋ}5[i˭d#CUڭKT7$t!vK;(5 0gtIg u]r4*Ŝ0:Rq(J_m'6)P4!iwk1rKxi)?@}i4)yM=MAuJV>Gn@5=/}L1]1.6y)KbGy)emx~j,,{Tn?vok^VĖ_aGg>fS:}ZgdGXqNz^]?:Ӄ={R ŒỸ ~TW_$.,GPFSMuM_nWsT\VJsφ9lbfoF}:YԷNvθ;'*vFNj/h<Y|c*-/44Zo>֮|~H.łwc\eYki|']{%/\ev.^-IΜ|gɧUY8N> j:^~eM8-_VELĴ׎vL[n5;PuLJeQ6 WUMz|Ѷ3uJ0r]#ٟL:l[_i%:| w_[W*T"*oh XOSthds>Ҿ/a:B㤴WW׻eיjeе7-eev׵f/.\0ƋnZ[9llH03=ԻYPϗq$X}{I UUSo7?rkMU{[f(MG2+i;![:򕽽d uGyXKލ:&~nNi).q}믟yd!*ֽ߮cyVX{0rVU<몮uUgy8s(ߤ^*mi}2;cn=/#8I_W%ɭ5){zȲKh4p!Uk]yh{9G+-WV#rO7euۗD|zp`xuye&vi;َx=VLOEǜ6:jO\jJsיEضE~ 뒌﷓-5,M7Pn;#omڎERR[K>*I4qEX>Uy$E@q+|j%12Ib"W#qȿݍu[+*EN Ŀ3+&<ybmJ|\Q{?Dž6UҎuog=Ƨ2%OZV # 4Jv}[lyйj]bQF\we_}8Dz^:Vo.  Fݸd·J[u|Q&)nFr$õo(wvw<=.zk7[W3rjX56wC,Uwf(=jtE4SZ[;STn+9\2ҫˋIt2%d6\[E:kjݹ*Y]>2QY6q|'gw[[ w|1ju,C g0?j'o>EJ'+&VbL&M\fvKKzbYI!!KG4=v~3\FĞpńeѸJ3/=~;) +c Ry8хM7^,Pd,I [IaQZDI:n_aR ;-ZrE:"GFI:RշRyKU|Yݵ>ogtR.ŖĶ}*uOmtK-xʿ Z~MUE,m+OLQMK}ONcAqYt6 2;n!v>iM~T[%kgG5N0/5ײ' itdtצhf4ӍΩg䭺]BuIVnvZdt[!}_.Mm5~k$5Jy{j6<=a1QYSĘhU9 49_sM1Kho%}TDSg5U-)ީc[= ܵq$)b~WY>O#6Jz+B+ ՍG~|S|w7e%_i?RVd"FDʦ1M%} lj,~~t_FMJ\YT 8[Q_\c(8*E~&26>hN2u]bL}\zO-giMk-ab.U4]#Jyx5S/2Y.sʹ9}Wgs+u}x&(Zi[_^+Qgvӱԥju[\\W 2M++k]T5v t%WEVSz,"BN:♃v2#yeX85;-K+ 9F/1hZht2m7yfWMi}dk3p ԸvX.)i/y[54RmN>bŏ!~"|$g:?Aqt|1}zy rqoq(Gy(0ssh_x=(d*6e%fEoÿ PuL+N.R+&јYd?"I=[]8,}y?O_Zbҭ~:cgc[ KǗT\vqi֟[-SLS[҅_6quλkq%-xC铰fޞ]zՇ\\8q0sj̓iܜn4QB9 ICE-'v;'H5<.mղKtǍnq>d=cFzm4ra-KdZ򲬊iYf>T# jw\\wD,xPj,84faPMHO BR.gI$tF|@`lG2"e|Iܶ/([&yIZP FNuFO~q_潏tE=ȩ{&썧-d8Wj)zoDHLAOSⳫS旪l/|\8Jj(2u* SXރtKzva2!pyJKL&SOW.)L3Ne0lRu'ߑWmv(p"><[-~7Y{}O5\|^8ijpKJg%)H8jm& r&'OevJ-I9]K&i)}K"nі瑪G~QldiВ!G彶sZN#8ͼ ;t|f@!+|xB]㫆5}~ MgU+c9e 5xJ̓z<솅(ۋU r͹It;*iǦ^ALYE)S]Z#rkhQ>LHJ.9j5sR]'bXi7\$!Tgunicorn-20.1.0/docs/site/images/favicon.png000066400000000000000000000033531401157322000207110ustar00rootroot00000000000000PNG  IHDR sRGB pHYs  tIMEi}IDATHǵVmW~33] @-Z"bbĚ4i-jX5UkLlHchRH _ h|B]~ܙ3<-,"?I&sޯ}0v{a^|iU[^m_2k $`=04նC;'ͼ:I$U Wz/3aG5H ={潧v= CH1w.u%q/5%  H$i]圕ְohHN2'I8 fuEH,9tPHErT$H#`$I8'@^ޑɁaV $ Qtn@Hx=+~Ϭs pQֺm_6/m]6sE x,U wb-{zW6NYæs'ݰɕkQ3l`?uQ4PfdcO%:&L Qˉm&YK.\F֣?Ӓ_zhLU F#h$/_ʛӛ]a|۵NRH3\##B@P=;$0wsRI J"@ݞ"xCAb#3I6܈$K6+圧EV7Hj}'C8Q0SBQm^D+9IHCr °]?O>`]QvH]vu=\ žZPcxTb.@T$(jq4p` Ο`ZZ\xxԦ31 zp[+YggV}mmvT<տo\y?&?uxg>ks|w|g/'=' e6WJeO_C ]̥?3k_d oջ5#{ ȃɐsܺaɆfA8i={TTNia$iԲML4ƼuO<}ޑ#,;l0>jgfN$b1*圙4ŏHϿO]>;^[#ɍq zi$C9VԦ/ԳH.[e~aFWj'>9&n*U0efL:#  s#hע!s^_E]3>'J;mEށ^ҐD S7eSԈkҴUƃ>xTr5km.CCpjg}gۦ4ASPqrYMNw{Mo, ,+ %iYk#_.ȧAI=p7.Om朻rrl"'}I(%k83&TIIpB$@YBSia˼ 8 W;4lvˬ8,0886ysZI_V&KC/99H,+W)=/J'?~i%8 By#N1>1r 7I#@YM2j:IENDB`gunicorn-20.1.0/docs/site/images/footer-arrow.png000066400000000000000000000003771401157322000217150ustar00rootroot00000000000000PNG  IHDRB21sBIT|d pHYs  ~tEXtSoftwareAdobe FireworksONtEXtCreation Time07/17/12ؘ[IDAT8cxA1@ bSCqlڣ~{ȁ."K\[U!<8" IENDB`gunicorn-20.1.0/docs/site/images/footer-logo.jpg000066400000000000000000000047031401157322000215140ustar00rootroot00000000000000JFIFHHCC+@ 0 !"12A#$%3BQUab< !1A"Qaq2Bb#3Rr$4S ?q'}&F@cs䛘X$˅+e:,1l}`0wy0RUn'L]iXq٣j*q]7Yn'LjXc% G#n9LFV\G[R_PyIK KL#`)e%m~>zQ8Cq={D>[Zw9v!nXoWaFHoa~R$LcˎY* [u瑷]ܕ&t{qQEQEgYwORqъLpիEO0SRF]#J*wY^)bxy#~ULIDP SLFꩩ_Nu9Z%O=Iv(ӄW8fYѓk'KHѬZ٬K)9ҭFηt+IJbE*{7i;p }+p*񼑽xlTFMᄊ`|ֱH#qc=`?R)1֙~v44S ҅k*?uXhua_xUcו,WG$o>U>{[SJd+ljF ؝ 8r3奒R#(@~Ė^l* n Al5EsEVQk7r(6L}bhV͉"ġ2&-'OE(ن vQڛ7T-[GEtQ]4.dGJwjU-A %)_ۥ $#KnrcoכoZSL9vE2[]$巏m'Wn2Y=ڰ ibŇe ,i5 ;,Fα"'BM3Ta4J')]奻 ɤ"LRXSA:vʎ^XL>6Y!ȧv6`s+\rjT\[}oUP]wG_\_zv_P$yY+?&wXS1J`_2oz3 0eE3[K Ksz^S\9h3;zC+JjUiReO?}j 0׿ @4%}A7$,V|.[%Xp9uVedmQ[ CT75F v&lh?n?'f>X~t֐4I F7@yRLufgDPr97Eiu'aJm"ٮIe}Ȩ[VlK{hVϐ7 V;}V7iY]1&*iNT 9ZzWrRD!OV׼yq:3#~]JNG6IH,  {Trkr5grK+ ^S;u-M@&ƺHdrڕ 7e_r={κU]QEBsEk\4vH*+*ZPQ0Q/5:yG[x8 L/طRxF A4GG֐X|5c`X:V#VŞbb쑢IJ ҩ la dn5,AV L[)-)ĉdeqP~Ģɇ]Z7rƠ0SDZթEI;;,I#wJgbuݡP1XX1lMN:N'$gunicorn-20.1.0/docs/site/images/greenbutton.jpg000066400000000000000000000005171401157322000216130ustar00rootroot00000000000000JFIFHHC       C  lS ?ֱyg8k Q `##gunicorn-20.1.0/docs/site/images/gunicorn.png000066400000000000000000000030211401157322000211000ustar00rootroot00000000000000PNG  IHDRNsr?sBIT|d pHYsuuDwftEXtSoftwarewww.inkscape.org<IDATXk\e9]n׮b)@!A뭡 11HQ4qްް ^bAC@(h hf Je)^PZHZNwvٶh?O&y/K0ní4{K ZoʐDHxaV7jOq2B?|>3 X׉!5M{#Lf1nf ۧ {܎b`J_Wz$ʴBKaߓƾ"Je3C0tap"|P0zGwcsᬍ85u_5-NwFYMscNe0aF D2e7Rf|_۱G b57p r`f,. $_[OMkiLKʏ=]/&(4j]O ,sDD,jc`"!)="d:㿅ᜎ?-BTkjRY` aeLEqq7r΄ՋSp0'dߋ1q혯y+iN+NZ,2f0fqG܄miѩ<&Iei8mM㖊%BYy٩߾hcD&`:/bIypXi{f ikf٨5(;֎_6j[ąW0\9:1h%e;A狌{Gj7j1$"RNiDf/2U\^LyxaǸ4+?FD_+ƋRy'4j/V%xwּfD6Q$h)cyh,+2A+|ӿ';^mwn(]TPt FFF@(((|||<+zVE؍?Z>xΧhEZ`6ruVUe7l˶]O2(]eY_P(,,spr( {yrXleYez>o>IENDB`gunicorn-20.1.0/docs/site/images/large_gunicorn.png000066400000000000000000000527041401157322000222660ustar00rootroot00000000000000PNG  IHDR^_sBIT|d pHYstEXtSoftwarewww.inkscape.org< IDATxwUOBH&ADRDpF<EttE(* !MiR8ҤK;HIǝOwa&윹3j``4Zx)KI/d1c1' Qi8 &7 k+$i46K7c1czYbuPF'eI>5hit#p,_:c1cEU8f?? %RFっ$ڬ1c1Rhe;qfITiabuT7ic1cbuPFCW'ggI>F2buZX܍1c1)*)Vis`a> ,ɯ-%aPN\Gǀǧ?,ɧc1cb N]CG&ѽþ;;kB?ͯUI\|Ec1cG4 .C`>|*p%eq+U+^(^eI\c1cL'jShn``O`>eW%#NہUC^óg) WVܨMc1cWbuPF _fnSwf` qK4J?]q>c1-Vi"py (;;K<`mj`׵x,o:c1cTbuPF0ēN;it swÁó$\u2c1Әb NQ֏t%B|vfita׮>m1c1hT:(NqMvMR<,_ڗty:{7F,ɧV1c1O5Xm< \+\ϒ\`O׫Vc1cL]h#/"(\q[dN|:c߫: c1cLb Nqcn pIqY$1c1gAq|8tzŏ$EIc1cG4tzY$1c1gAq- tlp鄪1c1/ViQ৸9*NdI1c1)Vi: `siU$D1c1k([fIy`ciEV1c14 8X\;Y[uc1Ƙ*@FFZq:MfwU1c1X:8VN֨8WYu"cL?EUE9g'T*s3ƘnY:b`si$vIc1BDfZ>Ō 1xc4 h$?$1Ƙ^'"k>⧼ oKcnmu 4q-VucL/+iP׋G"2.HbC{d>/U'a1P02ƘXBoWG|>NmNc5"2p0s>#c)YFF:y X!K7NS.YXXbN`5'0wv-M`<,sC:䍩99po+ccL0T@Uu (p(I٘&""`C`\r \ \OykLc !6c!i407C%U'bGD>|WNy9p:UZo(࿴PUۨ9c7V!NyWΣ>%2Ddv8wU}d MDm|U2Plc¶mhO__Չc'"_^KFp#"G=dLH441xa݀6w 4q}$1D~`oUNkV`W[uBjKUcLY:4kԝ#N8tZ53]پd (걽cd:8֫: cDd.9 X|:! 9FD ,"cDٖt*Vݕ7ƘؙաYڽQqq%䪓1Ƽ, ? D"0xMIفqyfgu??1&_׭X:+VX1ƼGD6Íy: w5;pZ*^>٬WSpKBeƘZbuhVshF%U'b 8 wӇ;p0^ګ;VAŪ0}cbgVfŪ?c4s_TXQ=?`` U=eB@UoWpg.ChϨ͌6U YL cf+Cbկ?:c `cUhGU>'"RlreVU1g+CzO4 y3Y'yP*TS3OWkOهpUCm:ZӒ? xBuP- _Sޞb> !D.ʪ}_ezXC{zLIqY+c;Xg3bNleTNUMl+6Ƙ X4p~FU1JD1c} ![Y5"ڪj3ύ1akVf: 7p.`=mVYSu2=ps}xr^l -tv ߉)U=RDmSOUc֨u>#q} 8K %c\{q0`cL$Ru"ehn\W[xS:xxxx=j́+ڹΙXC*V{ߍT83`'G |lKHa 8p5p"peGD{OTN"r=9UuTFDI`épׯƘ&mC{9p݇,ɯHp|m嫫-UL4Xx{ Sw.xi`W8NNΒ 2s{<έ/Vgŝ3'9czC{.pgIH%qm[a)Òuq >Ktdhf\4Հū̫ Kit%n,'W BD6b@qWÊUc11XZb,/h$4:BG߈hvNi!_ BQ8N͒ܶ́Y=ƛ; ]Pꒁc1&+VX]?%0\ꣀit#@Fy |-:5 p 8~mZ{ƦMP2WCk1ƘXB4Fgts*o=SwkitaM#mh>`/~].Xp[FeI~a }s7<#>Xj%"q,^SU^SEy8`<N2F* 9"23c%?-f W׼;04zW,09qm{a;jm|x77`Q:{55o5ѕyOq=bW=9%1MCȲkύyؤ% 5Yz_/YzJ 0qӾT'&tBD> $"ů 3No<^aFU}|NDV|\x-;EӋhP<45n'~'v>>buq] 1D#Yi8Oc75pHoUi]UuM1%" .T/z Ɍo:NkD \\kf=mOSn;ҩ{q]iINneف<\\ճ7j p p2pe]"2̍iETDŽ $8U}`c7dR;4vz)UYLqlozb7j4X5͂;'uFȒcZBU< q͐77ឨCbc w n4HD=kX~x=#_V>u+K7?WT-l kbbWc"r pjתp+|,ڶr^m q߱VQ?.f}7~+"W ikx58ĭzSe4K1S߼bVq D:䊟M"\BÊ˴HDV`'|:o7e"{WDd%ɀpS$B[8xPD.KfDd=.\Nd䇈;v"aF2Ί{{'[:<:'K Rlr1vh몓1# 1Gp&|{e1@Dƈq~Nl{E$|ƛH}8л1p$"[VL;Dd!IqgR9p("0B52czV<;4ŀE i'Ȓ!>~):Ɣma8Ț|W*ʪHJpsl"򍒯"2VD~E> +"U'3]D~k: LUD()+*dUUD[ڙ!p[:S<ĸ Kr X5U OF;e?BU瓯DdWY* TD0pE6Ev}^zwֱ  V7B*.VEd8j~ZBD>;Z:讳S{X5U[ %NFmSĬZ'؈LD~W4.[Oq[ }5!+X\ó[DdŪ$"뭲dũbZQmP}7",׫a:sl?5LU$|κHN%y^V:87N_iT'},Yϱ2.V'㭩 ȀeҪJQzlOƪu8,"Cq5u(VB4 =;?|ݛqcdV4ybYl@{[$:@JS( cfd\NaQGbvU l*$"V*6:a|]DTue q]W8I*"'G{:/fzF5+,"umx5𧡚.5z$7s>>`,}Q.SkSiNՉ`2޾{T--lՉ^:MmLEHi"!R+e]3ZmS_wlSVC p&4Jl8}OYچ,͒|]܌O@X-Њ괞 ߘv-[aݪD#.(|xzsfTSs'7^D䛸㺜k+BVn\."u3} s̳+Xԧp[ߧ4CȒ,ɿ%",ɗɒ|,ɏ̒xkӮYqQu"},TZR=\91wgqWDȐ(VΠ% %@A-F4"괿/o&dIjoUpi+VM]>N_i]0+8Z9cGN&DdUQ}Ed߀Mdwkv,v9={wfԌlܸwY\V 83N^ V+:0??!">"{-91Z蹑_"&pByTHwPٝ_/'"ĺ8lZCqFbɬX5M-mSkH39nV3ߩ:DdQp`e_4L |}`cVS5} ū d(K8^:cڰoFgI~qՉ?⿫DU}s\=O_OLo w˪:Pl=_x r%_'b{ޟ>r"p(|'ׅ(^eؓ:dYHf4'q_ϧnkkq`sZv` ?pNpt;7!3 ;ˀUu|` {MJg-b&`ŪiQ q%KU'T9s1_TDNlǼ`oUrFQU_ĝ) 7.$̸WFHߡ,@U:O,攮lU> ϶CYd47p~}̆k|53_9r jUU=ePշpv]9l{X>)"3Yl|"k¸'TH8 w6nqw= 8&~WlDU "1p:w' Z"0njUt8/xe~EH`?)r+nrq!+[ n5 xN3\@۫j[Ez\;tHĀ9jYjj8v:^/oE||OU4k@ǶSNKU3P7WRU^1j#6nV^Oq+ ,iPNQճpA l݊HuU'ZTU=zzk[lV&;2NN3: 8QD:)"3[.}mOU)ifziT>ݧ4Cx*"sL!ʪz~ @^kp|tzOnz$Tܿld4@5fja`}U}CqۃCX݊fb4قAU'Tu*n2ĹE$"$p) }TWbf+s}po;ePUu'\Jr7~YU5UwG<(v;W'S]TK7uG\V6U'늆 {2灇Eo"iqDd&E =]kOLD!nՐ^6Q#Fvc-}ըS">=Էg TGU!{σZ%69RU(|0fŪiYqML`z1~فD^DDN+D-ؐ3ܹyeM˪ރ;L?θy W2U=h˫Q!mBgXiސ0[_KK*O!yn+V1U"NVD?P3peqsv,ٕV9k30~sJxZ޳^U x(@3R۪cyM5 @xkj^ux8Ɋum܍XO!Lo0[Цu:n4OJ!" HUq;Vt]X%YOYXs3Ud1[FUj܈I IDAT!wz U1 lWuBU>k_J#pCWPڎg]:U :*ٺ?+=BUo 4sQޚXỰՆUKޓJR p}ڪ6h%]1w0ح5{ m+&9"q1[Pm]z\Uo }⡞ưٚCΘV;+dJQ?dMcݥt<ǫbl&Þx("2ǐQEdߝ |w$xRՉE"r1p"K }7bb1(sU^zD>ϫNϗfSEdT_ U}x8^'Xm8FT1߳$:Sp Up>0?@w4DѸm{䬏2?O?}V6csW*F'\Gh ;\TMS3%_bL,9ޥ5ՂcЀ],>gy> +V=n(N@1u,yI*Y wޯՎ^V D٫UucM|U|M/Vߨ`7H+V&N࿕1uw ">Y#+le)X}YU&^W>&vhzZ*jhCVe*NŘ|&N5NWS}7G:WܚʪSvr׃*VoSYڞƿGZq \ [u.ƔVW=@o+"#:Xe+X}mbg4cYm'q0D`֊1fF8|~Y[&:cVUf:UUO+>PTe^+91pjM|⁞8:cp1 G;]|R"RvƦZ֍ؠ<*Ʈ0试U]fm{|>*s,EӷCN,oXeŘ!|9K,ɯ4iMwMFzJW5gZfAfMX{t>yǗ8NJy{b4ɧŘa%k'Knn8s_ !ƷD{*+eob=T'"8.Xm\yNmejbeᚷY|2݋h/X#%S[gd_`k, p=MD>y Y&n"p|g״3k^;$\87u?pAYuleev+VDF?:cZtP!K4Vk@qg)^uCDN<݀ZVuOc|Uȼڄ1V(L^#zNjmX1wXj/ qյ8uNFv.>ʪ+n WV<*seuk%U3V:~W˷xwx3?`j48ض\խ|P/34:)iH>W2,1VŪ)Ǖc$" դyN95/W@mIg Grc\B~ս<66k2*߭xs54%hQ*UbLj 8`q{MWOD*U5 9RȼC.MT~ܼb_1^SݎFK;iuy{xX-AFPu.t,j;Bճ#P۪ΣF|je<^UIo򽺺9fpǐcpgbOv~ x!X ,Np i8c|hF4K7§4}WQu ,VWS;L޲c1}]<?>9D 4ia9iH{D Q=EUQu 47fRc "=b2@ոJ?iGlGD wX"y։zX,NpbUbL ktyoL`ZCU'0mch߾,9杞Q {ϔ+E*[m?/\+" tg<ǜ 8SDvwH"afOŭ֚>eŪkU1'eI*aΜl=IUT'fCU -?+V|+tQfTu*\$";TAwu"mN$lQ7pxYPm_sQk7 0K 8fōwȒyy:h٦u~otYZiXd^3N4K򶊛,ɧi7`?Y8TvlHk7mOSf{FM~o{mVsTy,=lDz ݁D1XY8(@N/ ϩ!tEv?SծWsEd>`K`{`{?QD6vXPS$l-"㾆B:XU'ѫ$x_u.q, , HUfi`/:{~"UpVC\WV ӁoAR2_;,ÜXNէqM "V6TWDd 4k5ĥJkP"s//5 `'yzpN铋O  ԍ?s;`u^Uo&!"ڐ+^Db\ggU^DdNJָe'qIXRFnh V\I qO-3k&K򩸕'4 &ޏ,֛%q݈Fd;i,'Ѵ:9lVC+=Pr9­jwi!w, dׁ9(O*vPa\SsyyϹq.D91> \/"?P㺌;˸⡍=pCwyu хUi(+VO7Ɲ 1Eԫ@U;-VGYs_l 9nO%qsZ rU%"V1CjMU+p+9e۫fjСS_ڛf7 (:˥'>ƧĪẐx4@N;}ޞ5T۪Nn;xu7쬪W+V; w}ų$'K'=ļC|*5v{xwXY7J*?hv>!K;{1N*9{f ,TQ1buTAPgVNڜXu.zVUx~t}k݉[=[:K$1kЃc &#e+XOXȒP\c*,HggB~oHƻJ6%U}V9sQ=0WUbj{sx`KUz3nY|hY\[^ |Kہ-.Un_V&_9TU)UΩ+V;^_$?D/ធ8V|*:W䓀ps`67#eſFFOkGDDg_ͿcŸݽDUU'aIU@wb"P*T3vF<} 'T/+V;&%`,ɿ% 3/Ȓep1Kp]8?YF_lsN`N}?@̺+0[U3g{u8*#0TR`#9^57ʲ.vVsqۧ{cb3WeIsUpJF)ZH]fo4jyyoc8Dd#kfv\jgz E퀦n@frՉS[O;w͟ yf@M$`{ LbMEvF@ EQgva8(:U 28U, ˌ,+t8dfໞcY7toU^*"\$u7n^?l4ꃸs<}]Ŷ &쮪__UVo@q% b撮ӳ[q۷RZ8j"~p7Mvh瘵#"~=WMi1bSqʀuU#Gn> 48 ZU}Nd航ގ{Ф_wkۙi[ @˲$??@VI9zy+pVV"pW>v>!K򻁟ycܟmHwe(,<{lqw4Һ|:p RUk[UQ]*pSu2yXmbu*mɒm<RqYuPוU$kskd+vhv>!KÀxq{݆]~>⨧8 :IUw>x/8kkLT>U" ?@ģV=RUOpۦmLz.ywRuXհ)seQCY?lmEgf$5}vZyueբۀ%5w<2iv?)KwYrzx1nTu [v#I z5R2TU=װET;e*n'TuU=+zn潥xm|1yđ;Sȑ-6jmɒ/qFE~~R?Z]jFgIR|9)wVph>:_e衪w}p3 x뀓3TzרƇ2_L\1Oo_P 11"2-;.^Kxr\|>z)V9q9uDbZ,:W r 7yӮ#}50Pz wz_kqI9+)z8vsYq͂;GF4X.K;8d,ɷ2F]^#:gsz|!"Kk۸L>)McqEfuA`MwkṬ1qpyk^um&tLVVX{ISn>E^1t%8=m[F43ط;,_86=iܤ<|l #O.[3Tw"xHknt 33kN`:nk௏wӍis=Q'"cpDZk,nt<`#Jj %sv1S%\,N ѱgiNo8+:}EU 39T.?<c1KVY>1O{VjBv^,[u@; |k=h{P*tr>#c1tϊ,VWy%=@Z¥(Yɲse]Ylvq"8Q})IDATn+ت1S+V)K7qg|ǃ tZ:6ZF)rh7'lѹWx1m'ݽ h c1nX<&K geu m)[iuř`$ 7=䘗]Kz/nP{/ySpdaVл 1&+V;X]=Nz?UJ:YBm\bS=9FDV+(Y8FG'#c1djg|7x^m \Y~p,6^ ~`qɸݚXDY`ۨ~2cOVv*qղ`˒"izW+͒,>3ڃ> f_Sq!|XW)W""n]|AU1c|b3OydFuan!WVoّ˒\ӥ7=b5_u,"_Kvm`+UKRc Q] 槁۵,'f0Mje `,+ݢ%ָ|_ӨVEP/P՗pc9TBD"ԛž1Ȓ6ђv-K򧀝.ȮqY?(v[;S<<2U=8cqu"Ǹ-"?7{T3cLpVv.88V kY 8"<]'g1ƘRRu 8ɒUu.%{m5cάv1pBFl͟ewp+p|n U,ɧy 8fqg6 `U+T1ƘfS8YZvN(D3_mi{B=Wr~yZZUUN( #U}ꄌ1AF[gt3٧ xzWkؚ7 ,K ҩ86iwӉX\W9+NLJ\wU1c?AGt]KV+~ <1^[sXeCq_GSU}SUpP_cLbՓ,ōu).+B_9VРGp㐚nOqǴAUWo߇N!XLUwW{Nc1aXQut eٳN.o BYc *YbZń}kp3S\Ӥ5ThU}⼌1,U'k$O4z8eIhݸ7Gq6Oh,/+W`jQwpgy2m|nNuZWc1}Ɗ$?'Ǹqg|yx^dI>1Nˀ/x %|\)Vi$.NWy |? |XwuTk'M/obܪ;\d& !ʰ@Hhdi˪ʪKRU>v HPKURPR`x?jihvHAYR҆]Bv!,Ix|=̐xl|?5}s19~xǧ@DDDj~v*{l{U~aP7B\ޮʞ!w Ã{[#Ȏ(v+4RC}E <~0 5&""!İGsO;4Cc{б б|7-KAm<`aR|V? DK+Fov5 P6u0W,?0pWs>Bujˣ! 񾇚6u4W, ?_n ߟ# *D[nj!Y!@*CDDDD:+Ox;ύ;YhXG!T?3(ޟaRauמ.p ;W,n֛~ʿCPBV PgB}IDDDDԁau@e7W,)vm>zPdg(5{v ùb3HY%"""O \|7~4wvvV9'Z1ƈ^1""⽇H$@sE8ֲs}_xMs<K烽.V`cW eX~6cо]O/{5NEsșz:… zQ ճʰJDDDDV{kɁ&Ib(9㽷"bTc5U}u 'O8aDU=11[[Wʕmw~w~I3c"\V:,\o_U\z^վ x7FȮ?6۾\8UZ'"vks[kUթs˗/rG1QmʰzɨVEQEι*#UD$G"j 0fY. 5tcIي[9}v[%|'ThC9qWvr\MdF =ZvC`BU' Ь(J5UUcs=_>|!hV &oOl2CJ\Mea/?3eAu.DX2ı  '"S/0}TcUkmefff1&"""%:;;+`Ne @){RGǹ`SѶrV+9sK= 2?F8Gshvau9T#>)JMU(ᵰDDDD}={T{>;;ɍ㊻*o7&{1&cC0ȣs#w)ı-"4MT*9#GAFdADYd Q` ]upI=;e2 &t^klaY'x ^ADDDCRyhx#OܗFJzNhUQgCϠ=?30{1Sҳ͛7{:'hy9wVD~3_ҪW QˊճcϾVEWrWC~Yd7̵dm{(w`tΝyW6IDDD4Lz*Ǫ:m٦-u?$pѣ_km2A5t![ Q%BjV2,r WeC<ꠏeww dI 5 {)H=U"1^x?CDDDJcکFpBݏ`և7?D?7?7A_MHo xTiC ⽯#Gp :Y1viN kwy?ovaUU'O+6@cOd5ρ#,+kmX,1mjYXXɫjAU "RPP|c9ҾVF<|ҽW+n+& _XVUj:疌1՗^zi%""")ɓ'l6 m6 g$QkwGd{6QXU&bȒi_o8sLqg1YUm2"E=fVa lw@a5E=xm!4F=bcL ޱcGr~DDDDt5w}73Vm#"Uv=lY>HQ~H)!4UHĪ>9&7jaϛZe2y#}dT5RHD"UmVmlh Yhfs]{[US}:66>ڦ !DZqYk㽷j1{oEĨUU#"VU vkwS׭r{3D:^UWuUc SU'"^U($r@DDDDŰ:N8a$S'{H 4Mo־{\ 4Uc_}WZn_q sc{XVsY 4OXNeMmux͵Xw۬-"ki޷7j6a9UUcZ֪jǚdTDŋѣG6–'kyIENDB`gunicorn-20.1.0/docs/site/images/logo-bottom.png000066400000000000000000000061111401157322000215210ustar00rootroot00000000000000PNG  IHDR>**7 sRGBbKGD pHYs  ~tIMEx IDATxypSm1xemRp I6&=$; $mLwNNCKˑH "0 ezrjfg%hfxޓ,afcvVYߝQҺAF]Wc{yu د?:Q> 3?|,I5F)ɦ#/ݲ3Eö)df=F{ֆG Cj#lsN?qg kD/f|C?AK_1EdFAMze7H]eOK&FP=m۩±kV}#m|]wC qT Bh`· |'/^R55g|C~ީ}{Ѷ۔3ܟ Dsy7/Mhhxr>Gr$qu*Y'_ת^gܒ._ҮEv Jö  KbG f6G~Qg|KCNr @?7hvgv}LaŪzGb+iYy']˦-|N1li}hH2ņw+zf{>}+\6{ 5gqk༰zџ[zqND]gs)n|_KH/O=|-x:R0kK󖈣VJuj]S fYiA+o}wL b6ZϒRq —۪gyֳ0{a3$,X!(t7{ Sf-Z?7M'-Ccibierq0*8]Nvq]Oj^3aoТ`8_U[sƜ|ۦO'IE+-V,VQj F Y,s'y`ڷM} \#p&7LV`Ήk/~v֚fb%ǜÝcd'pf﨧aaa+l'MMwݟj)ԺZ~*[@5Nrz͍j1ږsFGOQ%)lc8ɜ-fQ/f%@(0Fu+G]UN{JRd'eFQ~9OegkIsNh,[2+'|Yj`p/w4w5Kyj՚6Ѻ|.54S*TG ~9]N>qpZ9օc5q܉wKƤikEZ|Qi,`)p $u{N_=uF!#4~š^Qg$')Hnd(xk.w~w.d4O9oFƌ3OB wb4E+ Y*G]5헴m1}]:߫dEL)0LXFDc`9f6$R̤L䔼ns ; t!V<Wg0zs7NSкdWQxjoF|]s&o3_t}g=r-|n[^qԙ=2sS)UhP:6`z^ØK̈X uƛ*z]<.!q #G,0>,& 0~Yns]ڛ׫A?L1+D d< ܢ=R0pcn׈,C_x7#QsDԺL- >A! _ s}bIENDB`gunicorn-20.1.0/docs/site/images/logo.jpg000066400000000000000000000237761401157322000202330ustar00rootroot00000000000000JFIFHHCCG   B  !" $36a#%&1257AWwx89BIQ E  !1AQ"2Baq#3RSr$CDEbc ?tN8DN8DN8DN8DN8DN8DN8DN8DN8DN8DN8DN8DN8DN8DN8DN8DN8DN8DN8DN8DN8DN8DN8DN8E旃$!#_.;lCB&12xQHkm3s! p6X709cwo1T#& %,N,S\dQu;nvӮ^vWK̵\bF\˧uY_%BDOGtVy&[(\iHAds9Ӟ,'R8汣iA;z'Mnb͍R `ޔTw~vʝHj7Ԩ[VT#@rnSo #2'=0]M#kUP5ϧ<:i"loZGɿi ?&Z}[Pm.Z9OAS|bWy&|c,8̍٧`W=@3CbQ .aIKF#6`O.ʈ,L%tZkPVS`;%  l G$1= ZxGYhԨO1 ]ρZs p; L"ۖB 8GK<|M:J6iq$"4V}Z\mJB!1 !v!!&ȐtqvP $3#DpII䔓N8DvF.GJ̖ͬݎ5b:dpki@Sk1 ő1"I[7`ZOb+5ιo%0''<{hb=. +O0[w yn'"]!`<(%g⎗!|s?#IB~yzݝEN8DN8DN8DN8E} ;sݱU ^ӳ'oHE|r36<%:2G:'Fd,&&̋UNxu=>D3Gb8#C�.E:!kzN0[խO-–G pbrJa c|mh 鍒bb?SApehxײg5ᒼW\&Y!I8$-*iٱRy&X@oc)oAZ!tesW١*` =ݼ\IBͮn3HMBaZӖ$>#)pnR0ȑgy/UzʵM埍h~ng?C=ѶNy]6f|1ж|z_`KBqTGJNQg, [q9Mץa߳Z|{w7o=۪ ~[2Y Մ{3$\*-WJ+>RZŋֵLOIoMY~pR IJB~XqNIye2+]~I8r)ԵyX-~Sȴ Oi=y;tkm?*. NrY͆m5.8:kC"IjP⸌̌BLH1LiY!i ?NZ8go޽ei7` F-AU_Ss 14(B*k1Ӟ,gk>ي慛@Ű6פXB9.+aTfa åGeעu֬{󍅟L:,Yo]/MܵKG urBu"an`096xqZXLPדǞ=}K#M-ڰ _K/n½[{ f~@J >ܔ76?#3>s9ulyy"SsvcxNܚWQ h(vYW烃u* > ^RT)8ͫ B/ϥG^7=z7tkaK0EF WgAy%Ӧb3oIX ]5Dkۍڕe ȋ < rCC,n(όl̋YO/Ju쎲ܦKϬɾsc<,[އ/1Sx[..̋`6NԺhThR gdݫTaDJ302 91q%Hebw4.6~Q.óXL[e»)ת10cPEgeQUl!12(,hzdFܕ $|:V\vh*V8Yâta4bͲJDj"t4R؊jk$ӏhX9ӞLN8DN8EƼ[**efxۭWdu<% K !M>ZL[Se ]_T=+A=b<0EI@9>G'l*nRūu(Rj1ݳ^ظcjhdCRn|?W\w@2aL3{lKravM$1!zs\SHSVS/.|Oq.Q8| 9X,'w|e L@NVs&&>drwv|g8ZӄNNtNү}VzH"$ npZ DBiCq>8TcA6>[Foh7(JQ7020)@$n2sNkMhIn~Ej>A+6ތc~]=W6fJq>6K%Mqabx"o)Rg?]iZ}_NU|r[,$ә qIB*qk4_59FEX ۧ6$Ieċ>,3G lwL-ECjfDi1J~;-m<˨Sn!iRUs ˋ_O(hY}TpQ2_,ltߨdjŞl̒!vbӣ|YV*HnvGkCF-dS 9fF =/ y" _jyGp/I<ĝ̿o-:٭/GuGq B\SsTJ\"p)1Hm XMF!'9D슸?f ^4~UU/?o}'_?:(ON4m{6ja08IȚ"?*|m52=J1&hbrc+džq_ ~ҭLQH;7 =N(XQ 7!Bpv!gQWmE^r"u퉚֝ėË&٤ l*y76("툓ՏlP<m:ֳ݂هřؒT#r:[`r]s2E7+3> Ga̶3g3(]6}JIQm7mMU6*hɧkm(@3}OTB6ͧtn(j dN\%˟:TCM7aƥ01LI# 8 F^y^<0_DSJouZn*f9ݟC)"{MƠ3 ꁨ?p=GPK؄lR72 c;<Ҽ\gi'ut]w ^Ҫv H3Nh SK f[!,GBԜrBl _ ?¾aŭi:v{3=CQ~YWv՗ͷahwZ'l<2j>P0`(M1Ɵ++e0ϸj~Bޣ5*hi@v%e3n>g'gz3/{k.-ŋ uim؊awQ]xإt_|35VlFV'erm@*$gNK Bz3 m"n}zQM\ G@NU?m6BhǵE?(;H?@E[XKd5 ROvARh/N-G i-JˢvezzSzݭ)֫eR~_/ šӻ۽ou;! ՇF"buH2 "@SV:M 06]s1a>C0e`R+O׾">8fZeDmb\lu}J! X>Y \>(ɄJen'll"NurOޱv `//w=>"ϪLw.*F]dk0 ɈL cfo.?Hje޵OQs|CH G\mNhi ~W@%N2/;!3[ϺeN-J̿?EՏ,8DN8D+*s.]H94l[ _[p.=Y7f%%Hة.;)o𯈱ڒMِ%A-tS{MYsqhFVu?T%]9#7&}CmvrE DN35F;83ūWAH `C1,Dapl]|,HPokTUxxmR@0xX䌇 #ZA1WUjUklK9%ť-QIGh͊]/ǵNXf͵C ݵ@ԕg'e 珋T3%Y2uo9@؎9=7<=MruԬizpfBWQgHl~mCus i!)gD-wcrz%L/'x`k-g՜aFr۸Z_M [ivzs7~˵i7LX藪 6cG#¢[7Ǥe755JTDVbR^#ׇ>bUμ$$ 쯈iqqa$q a63,UN)XRIaXd^1o8o Y{ָѲ]-B;Yi4!qyiiɲ1. q'?tJ'S)ư= +5 ,cy"~&ZpchDQu#4Ŷev`^ʳ!q_2.7Hzlyn սL5En36Y(dw"Yl4NDBe>_e9 z1;2)n5%]?֑Uœ 2^ U4W!44CQymNu]Z]>G\FՔmZ, G+>3dG2$Hf>rn-g˞|N_b?5W`gߟdR{Rfŷn_o||zƲUŁr:erY'& 9-Z.X˟g2/(\:[^l" 'hAlcs\HFu=O2L'd_=-.sh%5f)C5zv0 %cRC &]i+'tR^mU Qv}*)0!ʊ6K!(_BLYݟ,KZu[WP,X'e_24-VRc EmJj2iJFf7)mh9neJ CB.G/ HyfbQ' d:ߟy"$CЋ.&_a_쟴> o}G?ѿ|/2ϞsµҚ qqSVQhX$n:f4y(!-21.zDE;ҋ~ ߎ?_큟~KI}KC~7ݹ}]N8D^\ʕ]FsIc\mgEէ>~z>26>I=?oֺ潰*5y[hJIƑHʍ_NìЬajhYF\څ$Y' όԡmF[H\+0b-I˘L7uYzn_dzm~߱]G]ݽI◢u(brݑka/"&#!C N4LcGTgsJM_u[gΤurW#K,#yYI2Mwq S_n֛fmF]BZ1idG+VeDB#)YrmwmqIFu6j(ADZ]1\ё^]"mYD>!,1odLPͶ ۰N7!뻦;-8nn.Ỻ7GO{Pm匰p]1G- w람zLWF QDb$J"|F8Rn!i>Sg.n:|LZCIoch.lHsK&Ƿty?^4~k Q%Y<6s&BÙ?^۝٥ʬkڋtH=ȊH_d{)9!KnזǸy-X7C⺔kr*eRE,G+IfB?GV+q 2mB<];ss ,/0;qgK9ubuл(HB*@Ke)Ba^Q &<3eWFq~XghgɆ(Fv qǪWZ%alAb0g`:DS>il?mZN|px 9Hom~v\B%'8ą5 3U6֧>bgrrؐ"KwuK&ͭ^ϲjHimEc !/QuTfBk[ǨD|tElQƃZFTpGhy[,_22]B<ʛ`6VfF'm '"p'"p'"p'"gunicorn-20.1.0/docs/site/images/logo.png000066400000000000000000000162571401157322000202330ustar00rootroot00000000000000PNG  IHDR;qdsBIT|d pHYs  {tEXtSoftwarewww.inkscape.org<,IDATx|e?d ^)b C$dgBȩX;r(x*)  kTPRiL ZBA~ zP~`J[&;ٸ<3Mk^If_|=̐$4g֠$u4i2q ?LBp/xrFIf Wl1uKeM4* Lb +|?Lݲ%g2lvWݲp[/U)4i2%ġ pZS niZSVXI& *Q"IG~`%zG1.Gx\?6`m<@r_$8!bo=U .nDO*.]axġfQ]\`-S.o@m:Aq29D4Pf0~_WyeDt_|f.DfԂD2g78. "X۝ptvvVK@k* 7|KIhP阮a|rt{BpM,T&8 IeiguxI%D@f%;yf̼6}~UU?=KD<=#3-3U7GhֶaÆDt%j!fE?c+4B4B'1%3y j{=̿0ocߨaL\:bʔ)p4Bp4M-Ie 'OGư(9j~l۞;v#yЈQl`ED,0h_u<"33ۦiZpfHDklѼ5h$zt+ 3_0t:k׮suf۶aFh:(b05)?OD/3s8 kf<*E8fZHGrgčB\.w!H%"hZǸ;f̟>eqM#f#>%NcxU0d&S/L A``Ngԭq5Q,O#MWQŷ-3/uL&3ܹ\.J ޲EYf6S$3b֛n9n#ϚӍ9`\kֽo(4MTcawQX3@۶t:\4\-[nYfG @wwwmM-\0=PkN4[kX #t[78owr/Mz}@Ӵ9hqaZ/ÙIBDlvD@i*3Svk6[1r&a4wI宒kiuͫ8N#$kvI}RshgfPQ֩TP"Z )pR@@DBܖ~=/E$3 X8 "<'s+b\VWW"|6QIQPU| s$z}C0\#*{ά`Sd&!O$3LnT'Ff^ * 8g g * 8/;p\gIǯcx.Oaf>KUՏ "`yY8)|QUD_#L3Lc棉i%ޓtz 3j]v,ɹ1PUU=ZA Ep2~U *̗\{{{Amx32qf sK'r@RRb"^=:y k 7hIkkֆw+I!VZ5d@ ""[φADKu]_צHDi0@0եN{Ʊ'$wDѹl&MQBp|iVF"$+f \hay_)uDlx< g.f\l4==g;juK}0.~g+Var,$VobTrZrF<|vؘV5 C5 pj^oѨnݽk̙]ypj$W,"\i۶>CrX,iݽZP83_ yQGWQ!IݷW2fyaua, YJӠ?\"mWUu$VfB:BYx? 3%zNر@0f~-@S;q|zoͣFUՄmۏ2ht'c# -skp4ꐎEQꖓTQ5W $D"TC?jeƑ$Z G7Q#R FӸiFNf~(yo1"̛lReCKKK_&WN|a)u{|śW/QOXYUۚ"9[AKD3+URYS}G2 g7tl"a8NZ%׻ᵆ㫪z,{ 0v~t:'H)" 0x*2ꥊ%E9f" {o e(ʁ>b|>f%$8t$&ߘ }dd|f*)BJdCٱc688)9GR'!1mBshF8+U>ACݏp7^u?CA|ϧYL"̦I i0/SժjmDio5f ᎀnNك] ĊŢ m0󉲦o^ZQZ-5}c"8đpd>'~T8f66ֶSU?RUokvvWWqҡ&#Z„RBD܉ոuSe-s_Zu*uY"a,iHfs2?`CU~UU4M9J]u7B38,{x\??0 DCCQ,X!yD*zZUUݯ {t7%NOB\s5 T=mpf.̴!4IBd&Q 3;}0yDt⡶mRӴ~ɓ'oD7'RBR#Dk.)!f>X,>񥨪:Y Ndam3wR!R##p2yR,?hw/dA~y653MDZvd9fdG2ms`KLb5oN:6Y%)Hd:18>翣fΜyMy Uwww@±D,ɤPfh5McKB_z/=.xaѱpR|>tWWžU4*?zgV!cn2a0Ӕ[g;;(Eq/PN\JWٹaC ޅk`*oR\ m_D΃WeJ!c5lUU-7|ji5?2Wq15,zNx "z[ JMnp&3d**xf&Wyiw3N21sw,yŊ\D#X,@}BV@Pd!Sd&w̞|[fMB廦n}q-A S Hf*9n/p;4u+T>Me F۶O}AӴBflf g_Zn5 cQѶw(R<ꨣDc"ڏ;vl˻UUgQanSvs7kZO8tUDӈ駩[F2<m3S8q9UJm %8. z[0NU(^!h!!V"78ũڂJ;MTD0rP׍**gG/wr2giQm ƒ{W6}$O.t$p#nVep΁c/q\~c^T(o3Y`$dq%ܦɞX !I)aփWp~zS0T0g 7Յ!Y4iRN#s$Np_2,d;߀c27I̫f@rBq+&[xu4Q0~ZӴUeN-~aԔ[YU I}oմ[i铙Ր+CԭU1 WX.2u*ډ1Az-UMaRˉBD3G"׊H$ն)28|ȅf9E۶w=F4ѱ#'<- ۛ+Ȝj1u ^oTIMpV&~.(0Ey3*8ެ Bh8)48Lzޜ W&5unfȼY2uӛLQM )n]co ʘd3ui8)ڮc%N}5)}ʐ V921T X{qL6fE9IP֭[2s#b1 BD!D9D薾W]{|YDD$ fvi3f:D6OB0eClޠ'~e˖-.BBlEQlb6|T0,Xpm2gрiDr9m7ׯFV"jB !Z@+32s 3 fFADCN|`y_ks%^|Jd} VJ- Flsr؋fz˗=W,1929'6j8hҥ,5 DT 3Z[[ {nMܘ+cؙ'F$ȜZgDԓfE;MDУ>:@6 @k4Tz0BXn,p̻! #6a#lI3_ 0D9W"lvP.<.]j(;y+yW\qonE9^QMf潉ho8y"*6 !)t45 6eo\/yߋ=Wml"&el2M4H b#6nn\F$f`?f7.y(&ME4=300:}h85\iZI y(sZT~-GF߷@LP',{T6Dwl QUɻصknwIR{X,*JD%Ɛ=b5ʏJׂˮ|tc[mSL}GAתde f.(0s^Qhmm-,^5ihj]|9͞=;200PRF9 ۶#"B5ZVB3[Fvε/Y]kB YPJ%  MD(ԩS 4'u5"IENDB`gunicorn-20.1.0/docs/site/images/redbutton.jpg000066400000000000000000000011011401157322000212530ustar00rootroot00000000000000JFIFHHCCl* QRab!1A 4 aQ!b25AVqv ?\q՗gxꫥ*ڳ&9c[v7xuMW÷mX(<@9Όq ӞB".'^xؤk3oT-w`wGD{q`wGDص}fbǘp)buHɩ 60ʟ6\&)U ";U=AWjUSsvH.$iSi!gunicorn-20.1.0/docs/site/images/separator.jpg000066400000000000000000000006701401157322000212570ustar00rootroot00000000000000JFIFHHCC "aQR$b"Qaqr ?sD+7פZ6 Vl7Љr&zK<O_l _ &jm5z a ۑN\;<"pU 7¸vVW+j~^!]#ʡۑgunicorn-20.1.0/docs/site/images/title.png000066400000000000000000000065721401157322000204130ustar00rootroot00000000000000PNG  IHDRO: # sBIT|d pHYs  ~tEXtCreation Time07/17/12ؘtEXtSoftwareAdobe Fireworks CS4Ӡ IDATxu:?k+V`V`(Du*]\\X2I E;G 0| 0 %Ocv}EDl.$Y+K*@Ƙ6rhJ3AJw~[[cLSJ3j%-*!{đ9Ub7߽.XU"r,3d GL;ЏXU;ojSđ9 kFbHwUtS;cL `yvOmD"T*1vQ'cZӐ1&Ŏ}3Xa_o9#̉EdOy'*my s&^sUJsƘ؛uaJ{"3cˍ/H?ۚcyKN̖gwzpn޲,Zc iCVpn֦mډpul"[E$aؠ2O#sQ (ۍ/W74^~;đn2ѩu&{D-ͣ;2. v'w,NY΄ bl&3VY"=;@E} !:gYs JXG΂눚 `e> o/A[an$9e{ҁ.h;jnj3n5Qf1& heYrnik}pe4#k9~ؾV5l.N(mfkN#޵Kx4(qgr'{76~kYg%Dd)a5D*Ss֕2O}m&"{ʾ[!FD@Y"n=+m{MQqov e|F]y ;zM[?%FM%_nQ{oo-genדtLJ(fM~O>c=\bJ+l=g}JJD7sk+!X6$v{gUj.׵X P%`;{JZ!K:bcUj_ʜ]'Mu|fM )risN7Ez#.+%vJߋM׈+-ہ0Ҕ)y)_*޴w-[rI@4"s2o1dG/H\g4OV ߙ5yE['Nf'^ԊS7aM}Vͼ:Bx;/!CMt3w^ ]&އb)ʩt zlC\TsM{W"Kqe9mSrr7-,Dk5dyM,W"rGey'T_5y|c!"?g>%YH jC&6 f0rJY7?\Ěuκ/=u,씲jۼ=Kw]L. lh a8;]_b}!SܝSq5;#yC9Md9~{VDcLfYbL[ؗoc mrs Ws@6c e(T+9An7@]"?ǻrc̹yDЖ2or&lǹ߰m[b4nyki?rctpB=EC)rZ2{"rKucTFBs%s|_D7ׁ^fN;o:Xb=i C0cؖOkdpVJT4~ 2Sggs<A-}t?wSdo9-7JFaM,֕ssfylƢʜ֮];Ifm芷GXjZ8)Ggz6d%{l|w}"/iȮ> }J9?dOͤ*?g=k9&x2 > n.3s%2 wuʞ9ZE9~ʹrFG:=9NM.]Zr5^?㟞Wf>wT)8S.m_\YϔHM3h&m:U*%> tf/\pL\a&N}$RI,n؎ oh޳WR@=07U9RNo]`_jEyfJK{Ok)we݃, ]SQi}5&=!?ZWi ݸV!mWԿ#dP=Mۺ:|C'/NUYQƘ]in_.#Rn[3Urtm˶ĀUldt6igud%E!wPXr,3͝-X:ӚƥZr\t'!<3&iyE;k|c[gi?찦Ρ90; 1U1ۮ9,i+x}SQʲ (툁%N= 9)bE־4>p& EN{7oQo}fBMWJ\}65 N֟a_SY/Ja}9}*Y|G'얓 ~/ztĥ1/vF+tD;C~ k#/ԯFaXHdx{3hQ8y!$b >rhňȜ)Z'=ྃW=vFHG"%~]ʙ)]*,8GLYy{Jja$>*qdμ䒱oYמ-aU,!=aJ3o xh>Q#sr/3SF;x6}.#E%̙ʈ-csE\G!>c:p$G#)RqH3^%.㲀$꤂qGLW{)ۈarE2oFȮlvaMuheR0n 3un徚`roeipw}]WM̒ p a؅L;H`GPr ޜmo~a\JGZlB&q\jef| H?$xT'abug%]m1F#v$?oP`0 x;~m&lu>b,E>)=L8?Ė*ѵD꧖gv:}w_w[: }J0d-}`Wܿghnwׅ5t(r5=}mtTʻ^^NW)PUS/ݹ:uFSSړicT=fVā'cL {97[Gxonr^Š^[ɪU \αسX(L@̐XCBrV!TdyְG|E&U SB "VkɈ@;XIo9LwkȦGݘ醴R`0 +:[(RraOxS#<",8F,(^?p867}v>q@ Vt-hNn {[~S0lq4mp #/71 P^y="IL^aa%* |<)b/nzQ<1#'o]v&{𨨽~Ӳ`mGuלWSkකn"~.kE(a}e{wQu4כNhc 60u`0 dܣzXtҕAr!DcVȃ::{1і^.NFݠᩚ0Ø: :IЩvxGA&ǧ_r'KɱVfKi^ɉ$l{ձdiZ^[эnէG+[^ŽڃZ3Y|I=%10r8 #ȌlM} l:j[Y^Ƈ(Sac+s(U0 `0 `0 `0 `0 gunicorn-20.1.0/docs/site/index.html000066400000000000000000000172561401157322000173150ustar00rootroot00000000000000 Gunicorn - Python WSGI HTTP Server for UNIX
Latest version: 20.0.4

Installation

Here's a quick rundown on how to get started with Gunicorn. For more details read the documentation.

  $ pip install gunicorn
  $ cat myapp.py
    def app(environ, start_response):
        data = b"Hello, World!\n"
        start_response("200 OK", [
            ("Content-Type", "text/plain"),
            ("Content-Length", str(len(data)))
        ])
        return iter([data])
  $ gunicorn -w 4 myapp:app
  [2014-09-10 10:22:28 +0000] [30869] [INFO] Listening at: http://127.0.0.1:8000 (30869)
  [2014-09-10 10:22:28 +0000] [30869] [INFO] Using worker: sync
  [2014-09-10 10:22:28 +0000] [30874] [INFO] Booting worker with pid: 30874
  [2014-09-10 10:22:28 +0000] [30875] [INFO] Booting worker with pid: 30875
  [2014-09-10 10:22:28 +0000] [30876] [INFO] Booting worker with pid: 30876
  [2014-09-10 10:22:28 +0000] [30877] [INFO] Booting worker with pid: 30877

Deployment

Gunicorn is a WSGI HTTP server. It is best to use Gunicorn behind an HTTP proxy server. We strongly advise you to use nginx.

Here's an example to help you get started with using nginx:

  server {
    listen 80;
    server_name example.org;
    access_log  /var/log/nginx/example.log;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
  }

Nginx is set up as reverse proxy server to a Gunicorn server running on localhost port 8000.

Read the full documentation at docs.gunicorn.org

Project Management

Gunicorn uses GitHub for the project management. GitHub issues are used for 3 different purposes:

Project maintenance guidelines are avaible on the wiki

Irc

The Gunicorn channel is on the Freenode IRC network. You can chat with the community on the #gunicorn channel.

Issue Tracking

Bug reports, enhancement requests and tasks generally go in the Github issue tracker.

Security Issues

The security mailing list is a place to report security issues. Only developers are subscribed to it. To post a message to the list use the address security@gunicorn.org

Documentation

You can read more comprehensive documentation at docs.gunicorn.org.

The contents are:

gunicorn-20.1.0/docs/site/install.html000066400000000000000000000004521401157322000176420ustar00rootroot00000000000000 Green Unicorn - Install

Redirecting to here

gunicorn-20.1.0/docs/site/installation.html000066400000000000000000000004521401157322000206750ustar00rootroot00000000000000 Green Unicorn - Install

Redirecting to here

gunicorn-20.1.0/docs/site/js/000077500000000000000000000000001401157322000157215ustar00rootroot00000000000000gunicorn-20.1.0/docs/site/js/main.js000077500000000000000000000027071401157322000172140ustar00rootroot00000000000000$(document).ready(function() { Tabs.init(); }); var Tabs = { init: function(){ var activateTab = function ($tab) { var // this links tabs set $tabs = $tab.parents('.tabs'), // currently active tab activeTab = { 'tab' : $tabs.find('ul').children('li.active'), 'content' : $tabs.find('div[data-tab].active') }, // newly clicked tab newTab = { 'tab' : $tab.parent('li'), 'content' : $tabs.find('[data-tab=' + $tab.attr('href').replace('#', '') + ']') }, x, y; // remove active class from tab and content for (x in activeTab) { activeTab[x].removeClass('active'); } // add active class to tab and content for (y in newTab) { newTab[y].addClass('active'); } }; // hook up tab links $(document).on('click', '.tabs ul li a', function(e) { activateTab($(this)); //alert($(this)); }); // hook up initial load active tab if (window.location.hash) { var $activeTab = $('a[href="' + window.location.hash + '"]'); if ($activeTab.length && $activeTab.parents('.tabs').length) { activateTab($activeTab); } } } };gunicorn-20.1.0/docs/site/news.html000066400000000000000000000004471401157322000171540ustar00rootroot00000000000000 Green Unicorn - News

Redirecting to here

gunicorn-20.1.0/docs/site/run.html000066400000000000000000000004461401157322000170030ustar00rootroot00000000000000 Green Unicorn - Run

Redirecting to here

gunicorn-20.1.0/docs/site/sitemap.xml000066400000000000000000000037061401157322000174770ustar00rootroot00000000000000 http://gunicorn.org/ 2019-11-27T00:02:48+01:00 1.0 http://gunicorn.org/community.html 2012-10-04T00:43:15+05:45 0.5 http://gunicorn.org/configuration.html 2012-10-04T00:43:15+05:45 0.5 http://gunicorn.org/configure.html 2012-10-04T00:43:15+05:45 0.5 http://gunicorn.org/deploy.html 2012-10-04T00:43:15+05:45 0.5 http://gunicorn.org/deployment.html 2012-10-04T00:43:15+05:45 0.5 http://gunicorn.org/design.html 2012-10-04T00:43:15+05:45 0.5 http://gunicorn.org/faq.html 2012-10-04T00:43:15+05:45 0.5 http://gunicorn.org/install.html 2012-10-04T00:43:15+05:45 0.5 http://gunicorn.org/installation.html 2012-10-04T00:43:15+05:45 0.5 http://gunicorn.org/news.html 2012-10-04T00:43:15+05:45 0.5 http://gunicorn.org/run.html 2012-10-04T00:43:15+05:45 0.5 http://gunicorn.org/tuning.html 2012-10-04T00:43:15+05:45 0.5 http://gunicorn.org/usage.html 2012-10-04T00:43:15+05:45 0.5 gunicorn-20.1.0/docs/site/tuning.html000066400000000000000000000004461401157322000175030ustar00rootroot00000000000000 Green Unicorn - FAQ

Redirecting to here

gunicorn-20.1.0/docs/site/usage.html000066400000000000000000000004461401157322000173030ustar00rootroot00000000000000 Green Unicorn - Run

Redirecting to here

gunicorn-20.1.0/docs/sitemap_gen.py000066400000000000000000000040501401157322000172050ustar00rootroot00000000000000import os import subprocess from xml.etree import ElementTree def main(): generate( site_path=os.path.join(os.path.dirname(__file__), 'site'), special_priorities={'index.html': 1.0}) def generate(site_path, special_priorities, directory_index='index.html'): urlset = ElementTree.Element('urlset', xmlns='http://www.sitemaps.org/schemas/sitemap/0.9') urlset.text = '\n ' for root, dirs, filenames in os.walk(site_path): for filename in filenames: if filename.endswith('.html'): absolute_filepath = os.path.join(root, filename) relative_path = os.path.relpath(absolute_filepath, site_path) relative_url = os.path.dirname(relative_path) if filename == directory_index else relative_path last_modification = subprocess.check_output( ['git', 'log', '-1', '--pretty="%cI"', absolute_filepath]).decode('ascii').strip('\n"') url_element = ElementTree.SubElement(urlset, 'url') loc_element = ElementTree.SubElement(url_element, 'loc') loc_element.text = 'http://gunicorn.org/' + relative_url lastmod_element = ElementTree.SubElement(url_element, 'lastmod') lastmod_element.text = last_modification priority_element = ElementTree.SubElement(url_element, 'priority') priority_element.text = str(special_priorities.get(relative_path, 0.5)) url_element.tail = priority_element.tail = '\n ' url_element.text = loc_element.tail = lastmod_element.tail = '\n ' # We sort the url nodes instead of the filenames because # filenames might be altered by the directory_index option urlset[:] = sorted([url for url in urlset], key=lambda url: url[0].text) urlset.tail = urlset[-1].tail = '\n' with open(os.path.join(site_path, 'sitemap.xml'), 'wb') as sitemap_file: ElementTree.ElementTree(urlset).write(sitemap_file, encoding='UTF-8', xml_declaration=True) if __name__ == '__main__': main() gunicorn-20.1.0/docs/source/000077500000000000000000000000001401157322000156415ustar00rootroot00000000000000gunicorn-20.1.0/docs/source/2010-news.rst000066400000000000000000000147631401157322000177420ustar00rootroot00000000000000Changelog - 2010 ================ 0.12.0 / 2010-12-22 ------------------- - Add support for logging configuration using a ini file. It uses the standard Python logging's module Configuration file format and allows anyone to use his custom file handler - Add IPV6 support - Add multidomain application example - Improve gunicorn_django command when importing settings module using DJANGO_SETTINGS_MODULE environment variable - Send appropriate error status on http parsing - Fix pidfile, set permissions so other user can read it and use it. - Fix temporary file leaking - Fix setpgrp issue, can now be launched via ubuntu upstart - Set the number of workers to zero on WINCH 0.11.2 / 2010-10-30 ------------------- * Add SERVER_SOFTWARE to the os.environ * Add support for django settings environment variable * Add support for logging configuration in Paster ini-files * Improve arbiter notification in asynchronous workers * Display the right error when a worker can't be used * Fix Django support * Fix HUP with Paster applications * Fix readline in wsgi.input 0.11.1 / 2010-09-02 ------------------- * Implement max-requests feature to prevent memory leaks. * Added 'worker_exit' server hook. * Reseed the random number generator after fork(). * Improve Eventlet worker. * Fix Django command `run_gunicorn`. * Fix the default proc name internal setting. * Workaround to prevent Gevent worker to segfault on MacOSX. 0.11.0 / 2010-08-12 ------------------- * Improve dramatically performances of Gevent and Eventlet workers * Optimize HTTP parsing * Drop Server and Date headers in start_response when provided. * Fix latency issue in async workers 0.10.1 / 2010-08-06 ------------------- * Improve gevent's workers. Add "egg:gunicorn#gevent_wsgi" worker using `gevent.wsgi `_ and "egg:gunicorn#gevent_pywsgi" worker using `gevent.pywsgi `_ . **"egg:gunicorn#gevent"** using our own HTTP parser is still here and is **recommended** for normal uses. Use the "gevent.wsgi" parser if you need really fast connections and don't need streaming, keepalive or ssl. * Add pre/post request hooks * Exit more quietly * Fix gevent dns issue 0.10.0 / 2010-07-08 ------------------- * New HTTP parser. * New HUP behaviour. Re-reads the configuration and then reloads all worker processes without changing the master process id. Helpful for code reloading and monitoring applications like supervisord and runit. * Added a preload configuration parameter. By default, application code is now loaded after a worker forks. This couple with the new HUP handling can be used for dev servers to do hot code reloading. Using the preload flag can help a bit in small memory VM's. * Allow people to pass command line arguments to WSGI applications. See: `examples/alt_spec.py `_ * Added an example gevent reloader configuration: `examples/example_gevent_reloader.py `_. * New gevent worker "egg:gunicorn#gevent2", working with gevent.wsgi. * Internal refactoring and various bug fixes. * New documentation website. 0.9.1 / 2010-05-26 ------------------ * Support https via X-Forwarded-Protocol or X-Forwarded-Ssl headers * Fix configuration * Remove -d options which was used instead of -D for daemon. * Fix umask in unix socket 0.9.0 / 2010-05-24 ------------------ * Added *when_ready* hook. Called just after the server is started * Added *preload* setting. Load application code before the worker processes are forked. * Refactored Config * Fix pidfile * Fix QUIT/HUP in async workers * Fix reexec * Documentation improvements 0.8.1 / 2010-04-29 ------------------ * Fix builtins import in config * Fix installation with pip * Fix Tornado WSGI support * Delay application loading until after processing all configuration 0.8.0 / 2010-04-22 ------------------ * Refactored Worker management for better async support. Now use the -k option to set the type of request processing to use * Added support for Tornado_ 0.7.2 / 2010-04-15 ------------------ * Added --spew option to help debugging (installs a system trace hook) * Some fixes in async arbiters * Fix a bug in start_response on error 0.7.1 / 2010-04-01 ------------------ * Fix bug when responses have no body. 0.7.0 / 2010-03-26 ------------------ * Added support for Eventlet_ and Gevent_ based workers. * Added Websockets_ support * Fix Chunked Encoding * Fix SIGWINCH on OpenBSD_ * Fix `PEP 333`_ compliance for the write callable. 0.6.5 / 2010-03-11 ------------------ * Fix pidfile handling * Fix Exception Error 0.6.4 / 2010-03-08 ------------------ * Use cStringIO for performance when possible. * Fix worker freeze when a remote connection closes unexpectedly. 0.6.3 / 2010-03-07 ------------------ * Make HTTP parsing faster. * Various bug fixes 0.6.2 / 2010-03-01 ------------------ * Added support for chunked response. * Added proc_name option to the config file. * Improved the HTTP parser. It now uses buffers instead of strings to store temporary data. * Improved performance when sending responses. * Workers are now murdered by age (the oldest is killed first). 0.6.1 / 2010-02-24 ------------------ * Added gunicorn config file support for Django admin command * Fix gunicorn config file. -c was broken. * Removed TTIN/TTOU from workers which blocked other signals. 0.6.0 / 2010-02-22 ------------------ * Added setproctitle support * Change privilege switch behavior. We now work like NGINX, master keeps the permissions, new uid/gid permissions are only set for workers. 0.5.1 / 2010-02-22 ------------------ * Fix umask * Added Debian packaging 0.5.0 / 2010-02-20 ------------------ * Added `configuration file `_ handler. * Added support for pre/post fork hooks * Added support for before_exec hook * Added support for unix sockets * Added launch of workers processes under different user/group * Added umask option * Added SCRIPT_NAME support * Better support of some exotic settings for Django projects * Better support of Paste-compatible applications * Some refactoring to make the code easier to hack * Allow multiple keys in request and response headers .. _Tornado: http://www.tornadoweb.org/ .. _`PEP 333`: https://www.python.org/dev/peps/pep-0333/ .. _Eventlet: http://eventlet.net/ .. _Gevent: http://www.gevent.org/ .. _OpenBSD: https://www.openbsd.org/ .. _Websockets: https://html.spec.whatwg.org/multipage/web-sockets.html gunicorn-20.1.0/docs/source/2011-news.rst000066400000000000000000000040541401157322000177330ustar00rootroot00000000000000Changelog - 2011 ================ 0.13.4 / 2011-09-23 ------------------- - fix util.closerange function used to prevent leaking fds on python 2.5 (typo) 0.13.3 / 2011-09-19 ------------------- - refactor gevent worker - prevent leaking fds on reexec - fix inverted request_time computation 0.13.2 / 2011-09-17 ------------------- - Add support for Tornado 2.0 in tornado worker - Improve access logs: allows customisation of the log format & add request time - Logger module is now pluggable - Improve graceful shutdown in Python versions >= 2.6 - Fix post_request root arity for compatibility - Fix sendfile support - Fix Django reloading 0.13.1 / 2011-08-22 ------------------- - Fix unix socket. log argument was missing. 0.13.0 / 2011-08-22 ------------------- - Improve logging: allows file-reopening and add access log file compatible with the `apache combined log format `_ - Add the possibility to set custom SSL headers. X-Forwarded-Protocol and X-Forwarded-SSL are still the default - New `on_reload` hook to customize how gunicorn spawn new workers on SIGHUP - Handle projects with relative path in django_gunicorn command - Preserve path parameters in PATH_INFO - post_request hook now accepts the environ as argument. - When stopping the arbiter, close the listener asap. - Fix Django command `run_gunicorn` in settings reloading - Fix Tornado_ worker exiting - Fix the use of sendfile in wsgi.file_wrapper 0.12.2 / 2011-05-18 ------------------- - Add wsgi.file_wrapper optimised for FreeBSD, Linux & MacOSX (use sendfile if available) - Fix django run_gunicorn command. Make sure we reload the application code. - Fix django localisation - Compatible with gevent 0.14dev 0.12.1 / 2011-03-23 ------------------- - Add "on_starting" hook. This hook can be used to set anything before the arbiter really start - Support bdist_rpm in setup - Improve content-length handling (pep 3333) - Improve Django support - Fix daemonizing (#142) - Fix ipv6 handling .. _Tornado: http://www.tornadoweb.org/ gunicorn-20.1.0/docs/source/2012-news.rst000066400000000000000000000101271401157322000177320ustar00rootroot00000000000000Changelog - 2012 ================ 0.17.0 / 2012-12-25 ------------------- - allows gunicorn to bind to multiple address - add SSL support - add syslog support - add nworkers_changed hook - add response arg for post_request hook - parse command line with argparse (replace deprecated optparse) - fix PWD detection in arbiter - miscellaneous PEP8 fixes 0.16.1 / 2012-11-19 ------------------- - Fix packaging 0.16.0 / 2012-11-19 ------------------- - **Added support for Python 3.2 & 3.3** - Expose --pythonpath command to all gunicorn commands - Honor $PORT environment variable, useful for deployment on heroku - Removed support for Python 2.5 - Make sure we reopen the logs on the console - Fix django settings module detection from path - Reverted timeout for client socket. Fix issue on blocking issues. - Fixed gevent worker 0.15.0 / 2012-10-18 ------------------- - new documentation site on http://docs.gunicorn.org - new website on http://gunicorn.org - add `haproxy PROXY protocol `_ support - add ForwardedAllowIPS option: allows to filter Front-end's IPs allowed to handle X-Forwarded-* headers. - add callable hooks for paster config - add x-forwarded-proto as secure scheme default (Heroku is using this) - allows gunicorn to load a pre-compiled application - support file reopening & reexec for all loggers - initialize the logging config file with defaults. - set timeout for client socket (slow client DoS). - NoMoreData, ChunkMissingTerminator, InvalidChunkSize are now IOError exceptions - fix graceful shutdown in gevent - fix limit request line check 0.14.6 / 2012-07-26 ------------------- - fix gevent & subproces - fix request line length check - fix keepalive = 0 - fix tornado worker 0.14.5 / 2012-06-24 -------------------- - fix logging during daemonisation 0.14.4 / 2012-06-24 ------------------- - new --graceful-timeout option - fix multiple issues with request limit - more fixes in django settings resolutions - fix gevent.core import - fix keepalive=0 in eventlet worker - fix handle_error display with the unix worker - fix tornado.wsgi.WSGIApplication calling error - **breaking change**: take the control on graceful reload back. graceful can't be overrided anymore using the on_reload function. 0.14.3 / 2012-05-15 ------------------- - improvement: performance of http.body.Body.readline() - improvement: log HTTP errors in access log like Apache - improvement: display traceback when the worker fails to boot - improvement: makes gunicorn work with gevent 1.0 - examples: websocket example now supports hybi13 - fix: reopen log files after initialization - fix: websockets support - fix: django1.4 support - fix: only load the paster application 1 time 0.14.2 / 2012-03-16 ------------------- - add validate_class validator: allows to use a class or a method to initialize the app during in-code configuration - add support for max_requests in tornado worker - add support for disabling x_forwarded_for_header in tornado worker - gevent_wsgi is now an alias of gevent_pywsgi - Fix gevent_pywsgi worker 0.14.1 / 2012-03-02 ------------------- - fixing source archive, reducing its size 0.14.0 / 2012-02-27 ------------------- - check if Request line is too large: You can now pass the parameter ``--limit-request-line`` or set the ``limit_request_line`` in your configuration file to set the max size of the request line in bytes. - limit the number of headers fields and their size. Add ``--limit-request-field`` and ``limit-request-field-size`` settings - add ``p`` variable to the log access format to log pidfile - add ``{HeaderName}o`` variable to the logo access format to log the response header HeaderName - request header is now logged with the variable ``{HeaderName}i`` in the access log file - improve error logging - support logging.configFile - support django 1.4 in both gunicorn_django & run_gunicorn command - improve reload in django run_gunicorn command (should just work now) - allows people to set the ``X-Forwarded-For`` header key and disable it by setting an empty string. - fix support of Tornado - many other fixes. gunicorn-20.1.0/docs/source/2013-news.rst000066400000000000000000000063061401157322000177370ustar00rootroot00000000000000Changelog - 2013 ================ 18.0 / 2013-08-26 ----------------- - new: add ``-e/--env`` command line argument to pass an environment variables to gunicorn - new: add ``--chdir`` command line argument to specified directory before apps loading. - new: add wsgi.file_wrapper support in async workers - new: add ``--paste`` command line argument to set the paster config file - deprecated: the command ``gunicorn_django`` is now deprecated. You should now run your application with the WSGI interface installed with your project (see https://docs.djangoproject.com/en/1.4/howto/deployment/wsgi/gunicorn/) for more infos. - deprecated: the command ``gunicorn_paste`` is deprecated. You now should use the new ``--paste`` argument to set the configuration file of your paster application. - fix: Removes unmatched leading quote from the beginning of the default access log format string - fix: null timeout - fix: gevent worker - fix: don't reload the paster app when using pserve - fix: after closing for error do not keep alive the connection - fix: responses 1xx, 204 and 304 should not force the connection to be closed 17.5 / 2013-07-03 ------------------ - new: add signals documentation - new: add post_worker_init hook for workers - new: try to use gunicorn.conf.py in current folder as the default config file. - fix graceful timeout with the Eventlet worker - fix: don't raise an error when closing the socket if already closed - fix: fix --settings parameter for django application and try to find the django settings when using the ``gunicorn`` command. - fix: give the initial global_conf to paster application - fix: fix 'Expect: 100-continue' support on Python 3 New versionning: ++++++++++++++++ With this release, the versionning of Gunicorn is changing. Gunicorn is stable since a long time and there is no point to release a "1.0" now. It should have been done since a long time. 0.17 really meant it was the 17th stable version. From the beginning we have only 2 kind of releases: major release: releases with major changes or huge features added services releases: fixes and minor features added So from now we will apply the following versionning ``.``. For example ``17.5`` is a service release. 0.17.4 / 2013-04-24 ------------------- - fix unix socket address parsing 0.17.3 / 2013-04-23 ------------------- - add systemd sockets support - add ``python -m gunicorn.app.wsgiapp`` support - improve logger class inheritance - exit when the config file isn't found - add the -R option to enable stdio inheritance in daemon mode - don't close file descriptors > 3 in daemon mode - improve STDOUT/STDERR logging - fix pythonpath option - fix pidfile creation on Python 3 - fix gevent worker exit - fix ipv6 detection when the platform isn't supporting it 0.17.2 / 2013-01-07 ------------------- - optimize readline - make imports errors more visible when loading an app or a logging class - fix tornado worker: don't pass ssl options if there are none - fix PEP3333: accept only bytetrings in the response body - fix support on CYGWIN platforms 0.17.1 / 2013-01-05 ------------------- - add syslog facility name setting - fix ``--version`` command line argument - fix wsgi url_scheme for https gunicorn-20.1.0/docs/source/2014-news.rst000066400000000000000000000152621401157322000177410ustar00rootroot00000000000000================ Changelog - 2014 ================ .. note:: Please see :doc:`news` for the latest changes. 19.1.1 / 2014-08-16 =================== Changes ------- Core ++++ - fix :issue:`835`: display correct pid of already running instance - fix :pr:`833`: fix `PyTest` class in setup.py. Logging +++++++ - fix :issue:`838`: statsd logger, send statsd timing metrics in milliseconds - fix :issue:`839`: statsd logger, allows for empty log message while pushing metrics and restore worker number in DEBUG logs - fix :issue:`850`: add timezone to logging - fix :issue:`853`: Respect logger_class setting unless statsd is on AioHttp worker ++++++++++++++ - fix :issue:`830` make sure gaiohttp worker is shipped with gunicorn. 19.1 / 2014-07-26 ================= Changes ------- Core ++++ - fix :issue:`785`: handle binary type address given to a client socket address - fix graceful shutdown. make sure QUIT and TERMS signals are switched everywhere. - :issue:`799`: fix support loading config from module - :issue:`805`: fix check for file-like objects - fix :issue:`815`: args validation in WSGIApplication.init - fix :issue:`787`: check if we load a pyc file or not. Tornado worker ++++++++++++++ - fix :issue:`771`: support tornado 4.0 - fix :issue:`783`: x_headers error. The x-forwarded-headers option has been removed in `c4873681299212d6082cd9902740eef18c2f14f1 `_. The discussion is available on :pr:`633`. AioHttp worker ++++++++++++++ - fix: fetch all body in input. fix :issue:`803` - fix: don't install the worker if python < 3.3 - fix :issue:`822`: Support UNIX sockets in gaiohttp worker Async worker ++++++++++++ - fix :issue:`790`: StopIteration shouldn't be catched at this level. Logging +++++++ - add statsd logging handler fix :issue:`748` Paster ++++++ - fix :issue:`809`: Set global logging configuration from a Paste config. Extra +++++ - fix RuntimeError in gunicorn.reloader (:issue:`807`) Documentation +++++++++++++ - update faq: put a note on how `watch logs in the console `_ since many people asked for it. 19.0 / 2014-06-12 ================= Gunicorn 19.0 is a major release with new features and fixes. This version improve a lot the usage of Gunicorn with python 3 by adding `two new workers `_ to it: `gthread` a fully threaded async worker using futures and `gaiohttp` a worker using asyncio. Breaking Changes ---------------- Switch QUIT and TERM signals ++++++++++++++++++++++++++++ With this change, when gunicorn receives a QUIT all the workers are killed immediately and exit and TERM is used for the graceful shutdown. Note: the old behaviour was based on the NGINX but the new one is more correct according the following doc: https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html also it is complying with the way the signals are sent by heroku: https://devcenter.heroku.com/articles/python-faq#what-constraints-exist-when-developing-applications-on-heroku Deprecations ++++++++++++ `run_gunicorn`, `gunicorn_django` and `gunicorn_paster` are now completely deprecated and will be removed in the next release. Use the `gunicorn` command instead. Changes ------- core ++++ - add aiohttp worker named `gaiohttp` using asyncio. Full async worker on python 3. - fix HTTP-violating excess whitespace in write_error output - fix: try to log what happened in the worker after a timeout, add a `worker_abort` hook on SIGABRT signal. - fix: save listener socket name in workers so we can handle buffered keep-alive requests after the listener has closed. - add on_exit hook called just before exiting gunicorn. - add support for python 3.4 - fix: do not swallow unexpected errors when reaping - fix: remove incompatible SSL option with python 2.6 - add new async gthread worker and `--threads` options allows to set multiple threads to listen on connection - deprecate `gunicorn_django` and `gunicorn_paster` - switch QUIT and TERM signal - reap workers in SIGCHLD handler - add universal wheel support - use `email.utils.formatdate` in gunicorn.util.http_date - deprecate the `--debug` option - fix: log exceptions that occur after response start … - allows loading of applications from `.pyc` files (#693) - fix: issue #691, raw_env config file parsing - use a dynamic timeout to wait for the optimal time. (Reduce power usage) - fix python3 support when notifying the arbiter - add: honor $WEB_CONCURRENCY environment variable. Useful for heroku setups. - add: include tz offset in access log - add: include access logs in the syslog handler. - add --reload option for code reloading - add the capability to load `gunicorn.base.Application` without the loading of the arguments of the command line. It allows you to :ref:`embed gunicorn in your own application `. - improve: set wsgi.multithread to True for async workers - fix logging: make sure to redirect wsgi.errors when needed - add: syslog logging can now be done to a unix socket - fix logging: don't try to redirect stdout/stderr to the logfile. - fix logging: don't propagate log - improve logging: file option can be overriden by the gunicorn options `--error-logfile` and `--access-logfile` if they are given. - fix: don't override SERVER_* by the Host header - fix: handle_error - add more option to configure SSL - fix: sendfile with SSL - add: worker_int callback (to react on SIGTERM) - fix: don't depend on entry point for internal classes, now absolute modules path can be given. - fix: Error messages are now encoded in latin1 - fix: request line length check - improvement: proxy_allow_ips: Allow proxy protocol if "*" specified - fix: run worker's `setup` method before setting num_workers - fix: FileWrapper inherit from `object` now - fix: Error messages are now encoded in latin1 - fix: don't spam the console on SIGWINCH. - fix: logging -don't stringify T and D logging atoms (#621) - add support for the latest django version - deprecate `run_gunicorn` django option - fix: sys imported twice gevent worker +++++++++++++ - fix: make sure to stop all listeners - fix: monkey patching is now done in the worker - fix: "global name 'hub' is not defined" - fix: reinit `hub` on old versions of gevent - support gevent 1.0 - fix: add subprocess in monkey patching - fix: add support for multiple listener eventlet worker +++++++++++++++ - fix: merge duplicate EventletWorker.init_process method (fixes #657) - fix: missing errno import for eventlet sendfile patch - fix: add support for multiple listener tornado worker ++++++++++++++ - add graceful stop support gunicorn-20.1.0/docs/source/2015-news.rst000066400000000000000000000131061401157322000177350ustar00rootroot00000000000000================ Changelog - 2015 ================ .. note:: Please see :doc:`news` for the latest changes. 19.4.3 / 2015/12/30 =================== - fix: don't check if a file is writable using os.stat with SELINUX (:issue:`1171`) 19.4.2 / 2015/12/29 =================== Core ++++ - improvement: handle HaltServer in manage_workers (:issue:`1095`) - fix: Do not rely on sendfile sending requested count (:issue:`1155`) - fix: claridy --no-sendfile default (:issue:`1156`) - fix: LoggingCatch sendfile failure from no file descriptor (:issue:`1160`) Logging +++++++ - fix: Always send access log to syslog if syslog is on - fix: check auth before trying to own a file (:issue:`1157`) Documentation +++++++++++++ - fix: Fix Slowloris broken link. (:issue:`1142`) - Tweak markup in faq.rst Testing +++++++ - fix: gaiohttp test (:issue:`1164`) 19.4.1 / 2015/11/25 =================== - fix tornado worker (:issue:`1154`) 19.4.0 / 2015/11/20 =================== Core ++++ - fix: make sure that a user is able to access to the logs after dropping a privilege (:issue:`1116`) - improvement: inherit the `Exception` class where it needs to be (:issue:`997`) - fix: make sure headers are always encoded as latin1 RFC 2616 (:issue:`1102`) - improvement: reduce arbiter noise (:issue:`1078`) - fix: don't close the unix socket when the worker exit (:issue:`1088`) - improvement: Make last logged worker count an explicit instance var (:issue:`1078`) - improvement: prefix config file with its type (:issue:`836`) - improvement: pidfile handing (:issue:`1042`) - fix: catch OSError as well as ValueError on race condition (:issue:`1052`) - improve support of ipv6 by backporting urlparse.urlsplit from Python 2.7 to Python 2.6. - fix: raise InvalidRequestLine when the line contains malicious data (:issue:`1023`) - fix: fix argument to disable sendfile - fix: add gthread to the list of supported workers (:issue:`1011`) - improvement: retry socket binding up to five times upon EADDRNOTAVAIL (:issue:`1004`) - **breaking change**: only honor headers that can be encoded in ascii to comply to the RFC 7230 (See :issue:`1151`). Logging +++++++ - add new parameters to access log (:issue:`1132`) - fix: make sure that files handles are correctly reopened on HUP (:issue:`627`) - include request URL in error message (:issue:`1071`) - get username in access logs (:issue:`1069`) - fix statsd logging support on Python 3 (:issue:`1010`) Testing +++++++ - use last version of mock. - many fixes in Travis CI support - miscellaneous improvements in tests Thread worker +++++++++++++ - fix: Fix self.nr usage in ThreadedWorker so that auto restart works as expected (:issue:`1031`) Gevent worker +++++++++++++ - fix quit signal handling (:issue:`1128`) - add support for Python 3 (:issue:`1066`) - fix: make graceful shutdown thread-safe (:issue:`1032`) Tornado worker ++++++++++++++ - fix ssl options (:issue:`1146`, :issue:`1135`) - don't check timeout when stopping gracefully (:issue:`1106`) AIOHttp worker ++++++++++++++ - add SSL support (:issue:`1105`) Documentation +++++++++++++ - fix link to proc name setting (:issue:`1144`) - fix worker class documentation (:issue:`1141`, :issue:`1104`) - clarify graceful timeout documentation (:issue:`1137`) - don't duplicate NGINX config files examples (:issue:`1050`, :issue:`1048`) - add `web.py` framework example (:issue:`1117`) - update Debian/Ubuntu installations instructions (:issue:`1112`) - clarify `pythonpath` setting description (:issue:`1080`) - tweak some example for python3 - clarify `sendfile` documentation - miscellaneous typos in source code comments (thanks!) - clarify why REMOTE_ADD may not be the user's IP address (:issue:`1037`) Misc ++++ - fix: reloader should survive SyntaxError (:issue:`994`) - fix: expose the reloader class to the worker. 19.3.0 / 2015/03/06 =================== Core ++++ - fix: :issue:`978` make sure a listener is inheritable - add `check_config` class method to workers - fix: :issue:`983` fix select timeout in sync worker with multiple connections - allows workers to access to the reloader. close :issue:`984` - raise TypeError instead of AssertionError Logging +++++++ - make Logger.loglevel a class attribute Documentation +++++++++++++ - fix: :issue:`988` fix syntax errors in examples/gunicorn_rc 19.2.1 / 2015/02/4 ================== Logging +++++++ - expose loglevel in the Logger class AsyncIO worker (gaiohttp) +++++++++++++++++++++++++ - fix :issue:`977` fix initial crash Documentation +++++++++++++ - document security mailing-list in the contributing page. 19.2 / 2015/01/30 ================= Core ++++ - optimize the sync workers when listening on a single interface - add `--sendfile` settings to enable/disable sendfile. fix :issue:`856` . - add the selectors module to the code base. :issue:`886` - add `--max-requests-jitter` setting to set the maximum jitter to add to the max-requests setting. - fix :issue:`899` propagate proxy_protocol_info to keep-alive requests - fix :issue:`863` worker timeout: dynamic timeout has been removed - fix: Avoid world writable file Logging +++++++ - fix :issue:`941` set logconfig default to paster more trivially - add statsd-prefix config setting: set the prefix to use when emitting statsd metrics - :issue:`832` log to console by default Thread Worker +++++++++++++ - fix :issue:`908` make sure the worker can continue to accept requests Eventlet Worker +++++++++++++++ - fix :issue:`867` Fix eventlet shutdown to actively shut down the workers. Documentation +++++++++++++ Many improvements and fixes have been done, see the detailed changelog for more information. gunicorn-20.1.0/docs/source/2016-news.rst000066400000000000000000000055371401157322000177470ustar00rootroot00000000000000================ Changelog - 2016 ================ .. note:: Please see :doc:`news` for the latest changes 19.6.0 / 2016/05/21 =================== Core & Logging ++++++++++++++ - improvement of the binary upgrade behaviour using USR2: remove file locking (:issue:`1270`) - add the ``--capture-output`` setting to capture stdout/stderr tot the log file (:issue:`1271`) - Allow disabling ``sendfile()`` via the ``SENDFILE`` environment variable (:issue:`1252`) - fix reload under pycharm (:issue:`1129`) Workers +++++++ - fix: make sure to remove the signal from the worker pipe (:issue:`1269`) - fix: **gthread** worker, handle removed socket in the select loop (:issue:`1258`) 19.5.0 / 2016/05/10 =================== Core ++++ - fix: Ensure response to HEAD request won't have message body - fix: lock domain socket and remove on last arbiter exit (:issue:`1220`) - improvement: use EnvironmentError instead of socket.error (:issue:`939`) - add: new ``FORWARDED_ALLOW_IPS`` environment variable (:issue:`1205`) - fix: infinite recursion when destroying sockets (:issue:`1219`) - fix: close sockets on shutdown (:issue:`922`) - fix: clean up sys.exc_info calls to drop circular refs (:issue:`1228`) - fix: do post_worker_init after load_wsgi (:issue:`1248`) Workers +++++++ - fix access logging in gaiohttp worker (:issue:`1193`) - eventlet: handle QUIT in a new coroutine (:issue:`1217`) - gevent: remove obsolete exception clauses in run (:issue:`1218`) - tornado: fix extra "Server" response header (:issue:`1246`) - fix: unblock the wait loop under python 3.5 in sync worker (:issue:`1256`) Logging +++++++ - fix: log message for listener reloading (:issue:`1181`) - Let logging module handle traceback printing (:issue:`1201`) - improvement: Allow configuring logger_class with statsd_host (:issue:`1188`) - fix: traceback formatting (:issue:`1235`) - fix: print error logs on stderr and access logs on stdout (:issue:`1184`) Documentation +++++++++++++ - Simplify installation instructions in gunicorn.org (:issue:`1072`) - Fix URL and default worker type in example_config (:issue:`1209`) - update django doc url to 1.8 lts (:issue:`1213`) - fix: miscellaneous wording corrections (:issue:`1216`) - Add PSF License Agreement of selectors.py to NOTICE (:issue: `1226`) - document LOGGING overriding (:issue:`1051`) - put a note that error logs are only errors from Gunicorn (:issue:`1124`) - add a note about the requirements of the threads workers under python 2.x (:issue:`1200`) - add access_log_format to config example (:issue:`1251`) Tests +++++ - Use more pytest.raises() in test_http.py 19.4.5 / 2016/01/05 =================== - fix: NameError fileno in gunicorn.http.wsgi (:issue:`1178`) 19.4.4 / 2016/01/04 =================== - fix: check if a fileobject can be used with sendfile(2) (:issue:`1174`) - doc: be more descriptive in errorlog option (:issue:`1173`) gunicorn-20.1.0/docs/source/2017-news.rst000066400000000000000000000040641401157322000177420ustar00rootroot00000000000000================ Changelog - 2017 ================ .. note:: Please see :doc:`news` for the latest changes 19.7.1 / 2017/03/21 =================== - fix: continue if SO_REUSEPORT seems to be available but fails (:issue:`1480`) - fix: support non-decimal values for the umask command line option (:issue:`1325`) 19.7.0 / 2017/03/01 =================== - The previously deprecated ``gunicorn_django`` command has been removed. Use the :ref:`gunicorn-cmd` command-line interface instead. - The previously deprecated ``django_settings`` setting has been removed. Use the :ref:`raw-env` setting instead. - The default value of :ref:`ssl-version` has been changed from ``ssl.PROTOCOL_TLSv1`` to ``ssl.PROTOCOL_SSLv23``. - fix: initialize the group access list when initgroups is set (:issue:`1297`) - add environment variables to gunicorn access log format (:issue:`1291`) - add --paste-global-conf option (:issue:`1304`) - fix: print access logs to STDOUT (:issue:`1184`) - remove upper limit on max header size config (:issue:`1313`) - fix: print original exception on AppImportError (:issue:`1334`) - use SO_REUSEPORT if available (:issue:`1344`) - `fix leak `_ of duplicate file descriptor for bound sockets. - add --reload-engine option, support inotify and other backends (:issue:`1368`, :issue:`1459`) - fix: reject request with invalid HTTP versions - add ``child_exit`` callback (:issue:`1394`) - add support for eventlets _AlreadyHandled object (:issue:`1406`) - format boot tracebacks properly with reloader (:issue:`1408`) - refactor socket activation and fd inheritance for better support of SystemD (:issue:`1310`) - fix: o fds are given by default in gunicorn (:issue:`1423`) - add ability to pass settings to GUNICORN_CMD_ARGS environment variable which helps in container world (:issue:`1385`) - fix: catch access denied to pid file (:issue:`1091`) - many additions and improvements to the documentation Breaking Change +++++++++++++++ - **Python 2.6.0** is the last supported version gunicorn-20.1.0/docs/source/2018-news.rst000066400000000000000000000056141401157322000177450ustar00rootroot00000000000000================ Changelog - 2018 ================ .. note:: Please see :doc:`news` for the latest changes 19.9.0 / 2018/07/03 =================== - fix: address a regression that prevented syslog support from working (:issue:`1668`, :pr:`1773`) - fix: correctly set `REMOTE_ADDR` on versions of Python 3 affected by `Python Issue 30205 `_ (:issue:`1755`, :pr:`1796`) - fix: show zero response length correctly in access log (:pr:`1787`) - fix: prevent raising :exc:`AttributeError` when ``--reload`` is not passed in case of a :exc:`SyntaxError` raised from the WSGI application. (:issue:`1805`, :pr:`1806`) - The internal module ``gunicorn.workers.async`` was renamed to ``gunicorn.workers.base_async`` since ``async`` is now a reserved word in Python 3.7. (:pr:`1527`) 19.8.1 / 2018/04/30 =================== - fix: secure scheme headers when bound to a unix socket (:issue:`1766`, :pr:`1767`) 19.8.0 / 2018/04/28 =================== - Eventlet 0.21.0 support (:issue:`1584`) - Tornado 5 support (:issue:`1728`, :pr:`1752`) - support watching additional files with ``--reload-extra-file`` (:pr:`1527`) - support configuring logging with a dictionary with ``--logging-config-dict`` (:issue:`1087`, :pr:`1110`, :pr:`1602`) - add support for the ``--config`` flag in the ``GUNICORN_CMD_ARGS`` environment variable (:issue:`1576`, :pr:`1581`) - disable ``SO_REUSEPORT`` by default and add the ``--reuse-port`` setting (:issue:`1553`, :issue:`1603`, :pr:`1669`) - fix: installing `inotify` on MacOS no longer breaks the reloader (:issue:`1540`, :pr:`1541`) - fix: do not throw ``TypeError`` when ``SO_REUSEPORT`` is not available (:issue:`1501`, :pr:`1491`) - fix: properly decode HTTP paths containing certain non-ASCII characters (:issue:`1577`, :pr:`1578`) - fix: remove whitespace when logging header values under gevent (:pr:`1607`) - fix: close unlinked temporary files (:issue:`1327`, :pr:`1428`) - fix: parse ``--umask=0`` correctly (:issue:`1622`, :pr:`1632`) - fix: allow loading applications using relative file paths (:issue:`1349`, :pr:`1481`) - fix: force blocking mode on the gevent sockets (:issue:`880`, :pr:`1616`) - fix: preserve leading `/` in request path (:issue:`1512`, :pr:`1511`) - fix: forbid contradictory secure scheme headers - fix: handle malformed basic authentication headers in access log (:issue:`1683`, :pr:`1684`) - fix: defer handling of ``USR1`` signal to a new greenlet under gevent (:issue:`1645`, :pr:`1651`) - fix: the threaded worker would sometimes close the wrong keep-alive connection under Python 2 (:issue:`1698`, :pr:`1699`) - fix: re-open log files on ``USR1`` signal using ``handler._open`` to support subclasses of ``FileHandler`` (:issue:`1739`, :pr:`1742`) - deprecation: the ``gaiohttp`` worker is deprecated, see the :ref:`worker-class` documentation for more information (:issue:`1338`, :pr:`1418`, :pr:`1569`)gunicorn-20.1.0/docs/source/2019-news.rst000066400000000000000000000112621401157322000177420ustar00rootroot00000000000000================ Changelog - 2019 ================ .. note:: Please see :doc:`news` for the latest changes 20.0.4 / 2019/11/26 =================== - fix binding a socket using the file descriptor - remove support for the `bdist_rpm` build 20.0.3 / 2019/11/24 =================== - fixed load of a config file without a Python extension - fixed `socketfromfd.fromfd` when defaults are not set .. note:: we now warn when we load a config file without Python Extension 20.0.2 / 2019/11/23 =================== - fix changelog 20.0.1 / 2019/11/23 =================== - fixed the way the config module is loaded. `__file__` is now available - fixed `wsgi.input_terminated`. It is always true. - use the highest protocol version of openssl by default - only support Python >= 3.5 - added `__repr__` method to `Config` instance - fixed support of AIX platform and musl libc in `socketfromfd.fromfd` function - fixed support of applications loaded from a factory function - fixed chunked encoding support to prevent any `request smuggling `_ - Capture os.sendfile before patching in gevent and eventlet workers. fix `RecursionError`. - removed locking in reloader when adding new files - load the WSGI application before the loader to pick up all files .. note:: this release add official support for applications loaded from a factory function as documented in Flask and other places. 19.10.0 / 2019/11/23 ==================== - unblock select loop during reload of a sync worker - security fix: http desync attack - handle `wsgi.input_terminated` - added support for str and bytes in unix socket addresses - fixed `max_requests` setting - headers values are now encoded as LATN1, not ASCII - fixed `InotifyReloadeder`: handle `module.__file__` is None - fixed compatibility with tornado 6 - fixed root logging - Prevent removalof unix sockets from `reuse_port` - Clear tornado ioloop before os.fork - Miscellaneous fixes and improvement for linting using Pylint 20.0 / 2019/10/30 ================= - Fixed `fdopen` `RuntimeWarning` in Python 3.8 - Added check and exception for str type on value in Response process_headers method. - Ensure WSGI header value is string before conducting regex search on it. - Added pypy3 to list of tested environments - Grouped `StopIteration` and `KeyboardInterrupt` exceptions with same body together in Arbiter.run() - Added `setproctitle` module to `extras_require` in setup.py - Avoid unnecessary chown of temporary files - Logging: Handle auth type case insensitively - Removed `util.import_module` - Removed fallback for `types.SimpleNamespace` in tests utils - Use `SourceFileLoader` instead instead of `execfile_` - Use `importlib` instead of `__import__` and eval` - Fixed eventlet patching - Added optional `datadog `_ tags for statsd metrics - Header values now are encoded using latin-1, not ascii. - Rewritten `parse_address` util added test - Removed redundant super() arguments - Simplify `futures` import in gthread module - Fixed worker_connections` setting to also affects the Gthread worker type - Fixed setting max_requests - Bump minimum Eventlet and Gevent versions to 0.24 and 1.4 - Use Python default SSL cipher list by default - handle `wsgi.input_terminated` extension - Simplify Paste Deployment documentation - Fix root logging: root and logger are same level. - Fixed typo in ssl_version documentation - Documented systemd deployement unit examples - Added systemd sd_notify support - Fixed typo in gthread.py - Added `tornado `_ 5 and 6 support - Declare our setuptools dependency - Added support to `--bind` to open file descriptors - Document how to serve WSGI app modules from Gunicorn - Provide guidance on X-Forwarded-For access log in documentation - Add support for named constants in the `--ssl-version` flag - Clarify log format usage of header & environment in documentation - Fixed systemd documentation to properly setup gunicorn unix socket - Prevent removal unix socket for reuse_port - Fix `ResourceWarning` when reading a Python config module - Remove unnecessary call to dict keys method - Support str and bytes for UNIX socket addresses - fixed `InotifyReloadeder`: handle `module.__file__` is None - `/dev/shm` as a convenient alternative to making your own tmpfs mount in fchmod FAQ - fix examples to work on python3 - Fix typo in `--max-requests` documentation - Clear tornado ioloop before os.fork - Miscellaneous fixes and improvement for linting using Pylint Breaking Change +++++++++++++++ - Removed gaiohttp worker - Drop support for Python 2.x - Drop support for EOL Python 3.2 and 3.3 - Drop support for Paste Deploy server blocks gunicorn-20.1.0/docs/source/2020-news.rst000066400000000000000000000035351401157322000177360ustar00rootroot00000000000000================ Changelog - 2020 ================ .. note:: Please see :doc:`news` for the latest changes Unreleased ========== - document WEB_CONCURRENCY is set by, at least, Heroku - capture peername from accept: Avoid calls to getpeername by capturing the peer name returned by accept - log a warning when a worker was terminated due to a signal - fix tornado usage with latest versions of Django - add support for python -m gunicorn - fix systemd socket activation example - allows to set wsgi application in configg file using `wsgi_app` - document `--timeout = 0` - always close a connection when the number of requests exceeds the max requests - Disable keepalive during graceful shutdown - kill tasks in the gthread workers during upgrade - fix latency in gevent worker when accepting new requests - fix file watcher: handle errors when new worker reboot and ensure the list of files is kept - document the default name and path of the configuration file - document how variable impact configuration - document the `$PORT` environment variable - added milliseconds option to request_time in access_log - added PIP requirements to be used for example - remove version from the Server header - fix sendfile: use `socket.sendfile` instead of `os.sendfile` - reloader: use absolute path to prevent empty to prevent0 `InotifyError` when a file is added to the working directory - Add --print-config option to print the resolved settings at startup. - remove the `--log-dict-config` CLI flag because it never had a working format (the `logconfig_dict` setting in configuration files continues to work) ** Breaking changes ** - minimum version is Python 3.5 - remove version from the Server header ** Documentation ** ** Others ** - miscellaneous changes in the code base to be a better citizen with Python 3 - remove dead code - fix documentation generation gunicorn-20.1.0/docs/source/2021-news.rst000066400000000000000000000035571401157322000177430ustar00rootroot00000000000000================ Changelog - 2021 ================ .. note:: Please see :doc:`news` for the latest changes 20.1.0 - 2021-02-12 =================== - document WEB_CONCURRENCY is set by, at least, Heroku - capture peername from accept: Avoid calls to getpeername by capturing the peer name returned by accept - log a warning when a worker was terminated due to a signal - fix tornado usage with latest versions of Django - add support for python -m gunicorn - fix systemd socket activation example - allows to set wsgi application in configg file using `wsgi_app` - document `--timeout = 0` - always close a connection when the number of requests exceeds the max requests - Disable keepalive during graceful shutdown - kill tasks in the gthread workers during upgrade - fix latency in gevent worker when accepting new requests - fix file watcher: handle errors when new worker reboot and ensure the list of files is kept - document the default name and path of the configuration file - document how variable impact configuration - document the `$PORT` environment variable - added milliseconds option to request_time in access_log - added PIP requirements to be used for example - remove version from the Server header - fix sendfile: use `socket.sendfile` instead of `os.sendfile` - reloader: use absolute path to prevent empty to prevent0 `InotifyError` when a file is added to the working directory - Add --print-config option to print the resolved settings at startup. - remove the `--log-dict-config` CLI flag because it never had a working format (the `logconfig_dict` setting in configuration files continues to work) ** Breaking changes ** - minimum version is Python 3.5 - remove version from the Server header ** Documentation ** ** Others ** - miscellaneous changes in the code base to be a better citizen with Python 3 - remove dead code - fix documentation generation gunicorn-20.1.0/docs/source/_static/000077500000000000000000000000001401157322000172675ustar00rootroot00000000000000gunicorn-20.1.0/docs/source/_static/gunicorn.png000066400000000000000000000340761401157322000216330ustar00rootroot00000000000000PNG  IHDRXsbKGD pHYs B(xtIME  /s IDATx}y|\eyLnA6!,Mfs IAWG^IVJd&bA(P7PD@)ݒy?t2[2SfΜw<@F.>mD[#_CEtA 8Gg#@HI¾v5Ed+oW >rMծQ)J>x> j y \y NXt]W)F~ Bd =!i4֢Y4zj]5 jueW00 jy'C'WL`Y殗\d™@~.O&cD$?! Lctjņ`H`dɈϋ659(<d!M!#oEjMS4ߔGDAETBJ-˨Px5Zim,=F9@ ˘0s;[: L%4dm-)Vd-߯; nd(_U 0SE7ȓS&<>tBcRΗE\4W+(Lr_ߴeźoՑ\3\ duJ9D0cXfM߿-0H+"{ $ț<X,'RV./;V+}/'x"1r%C@ DN7' |l4M8 /0L&9$/tdֈ]MGGGO T9_{@sĉ!>X /ܩ@ۜDp&D& #yxE `tF}_i旓dJD~ bMN"r.0MeYS12h5p|UGB>,exf7;QF<G}}zD䇞ZP(f޶,˗/T(T!SY=B.e2vcӂ]XdEbI{E<***Jop۶lBdt:Rj_q#  sŪUmL= e(2"9oQi%9əgkEFw?3?m``E"r)ɰm`% Υmہa?BӻLC]] ԆbO'l oeYց8 1}/ZqnCDh8w8Ŷ?3{υKob FŌ! Chk4Ì;{j $:QFvI%9=yjjjKkkHѲ"H0qg7<f0cʕZˌHjB9h; m۰, ."7I Q1=v̺p=s/#!ɻD0P!@Q mLӻ7&Z[0|0M"2]RX,h)ohhhZ/2y86}OZDDZio3=@FFtTt@(? B+Yfh4X,Py wmO0%NG)g"4wp/!E3?x@jƟ`F>\=_k}X1AǽJ2<ij7|EE*= )>2!M T鵿ex߭Gc\8~4xL[}&HC CJ=b%hFZ\s D¬{6~2q=0ֶF $E)HPoĹ"l<}t#x-ypᶆz*1:L d?$( ǔq|%Y8By[L)vO""m~=qL}HD>]RX,H}}=ۃNC1*КP‚E@fwXi ђk;L]/ڷ2ӱj`7Ek w^L.;X5 1)x2WЙԍ \,8^{k5#PWVZ1=HVJ/rK zP<;7M@q8Y\ KWTs?-_@%s۵_"A8Lړ]h[䁴/QP0҈JѶhkHkĥr_"m d_O}җSd P|FaCe:5iw jǮz+RQ+ts ɤSbR㢮:@Yc)2{G? 5Oh[%r\ǧ-s|F&;Id;(^YJ;:yh#K-1y4ȑs8lٲmC(5^ԘdYl.g_ 2h=SN9?8'h-gI$ޔyCJ)'؈;DBP5'"؛!"[Iw"g+- i?Z}}J3H DdM$_{TTTT|^D,3\{̙ŋe}TD%yCm78*Oڶ}Kd"r`!HZk5jD6"JK. \Q"=:$meZD s^P"rig=?x?F@ty% e= 3g,dߩeYSшږ$&۶g`EZ?/P ?_Mi :a(7o<UmXU+7 `!%pޢF:˗/Y$ r)٭X )c8%gP=!"D"qyQG5Ų-"rBe=bŊVi&"-(c6K?RE0,lpT AT֗M"xg8Ր.H9ʞU~t X%!VsW V0;/+8묳" D㼼Spb&LRw4{l̟?B'Nc7LU wDd9ɫ4X`;vLܯ2MS)6x}$$WH()(MW^ŗP8Q_FF/,*Gfy?oW8#($K$+"#m/;Ws[3LlϟuMի~[K.EcctzSdz y}!J{l۞f,qض;v*"/xԅt|*""vOPio|*m>GǹqO;q۶qg|ˁCٜNbꊊ 7]XDB;rav8mmq!yh($WϚ5j0;۶睸e˖Ǚ_V3P{T۶oٶ &`QJӊpt9C2/NUs?G'x<ɏp'DD䘛nײV`g5k kYJzNx$$8η3JPl_bK`6VZE=ńm!yT9_0؈$8zGcc#l"8vF/]?dȳq%("4I׶t8q#I~;]\sj"t'öOd[mPhJ<D޵dXX,K/"pCfQ8OHHsf˲h˲6# m"yn8RC-um'cKψjHHm>F){ޕ)۶Op`my۶mYJy.Z޾\Q_E'm%SM(I@"xצk۽^+@ș#" ڟ|h4 ۶"AoI$}zEd "2!KJ.;Y]]=ȣXa@ ˲毩8+"Pbg8(Ote+jfI-[_J|jG<Ȥ8}G1pǯOHͫUe+(bl =ٚX,ѿm]v,`55Āg[P!6v~aB|mO۶?Hc<۶g>|RYWȜOHSUu?vҺ: ׮GyXuAV`8SqN27$I62g+U?2Z i DD\Rww0[GO6xpCk-*r5-4e"2 2菴?˗/Qco#-El%y!*6k˾z*_# 7_1O[Pj0XlYڲ,S'g(66ܫ0>;I-4<򨽃 r2E'oF:eY֟uq?q 2g$.Ql>s@>>"# p׿rr o0϶A|'oqE K.Zhkd5D>+$ hc;ޒ#_Ls G.eY_bg^P^0znUQ /IIa^2% xq[5]»v C[@]mC}B0@6}x$;gAjF%! *hC u|h =v% tS}TH`744鼐sH9dHG GA|gȉNDF[F.BҊWmXՍ Ѹ;K(%2*z|qǕtFV T0Fx(_+?I}$ (垉1 k05_yjS՜+GE l_1b:u.i? A=!O k,Q 4_/YvXY0Rg3~% 馛z< "䈆QB0f' R˗/;{l o0Co_8j^*ݐk~VAUPؚKc~|.+G*UWDJ^,k=Ї`RjzR*vED{Q&$"I"?W@wwwҲ<`]oo:;;;QI]%ܷ^PD#chz&bGN%;FGA[m)wG3dm3+i+"m$2yo<m[[E՜k_5,-[4#(.y8#D3gX߾98&;L| P(t--% #g:~~6'x""s[#O|'̻q,뗶m_<_|Ų} Lx1oªYu/"eUep<(ܰB TR>NʧHEn|J@ mf wF.0F_Et\-.R 6q&+|8GVVUUm5k> BNJK.iWxq9s˼|m&]q]x Cqv]7-"Y4n [bEZDPH&"ݖe+ʬY0MD.fkאH81fѣ5_04ȜCV<yTMO@x[DiJ*ED D>(Z3IDAT=~(+$S`CȾ"rG<7\*"T/2DzhDUX 㸮RD3"rjG ;.$8ځEG=ׄ$BcCxL9qjb^篓=\M]\PK?]vLJH[-7+*puZmgkE WTeYH&_E/^իWk_(%o @dwwQ{!H¶x󋎎WDZY|(Afu;~41{4"NI$>]wUAΉAJ&B$֣%}kʨ]5 W|KFr3:I| ;C935g455a)2aFʹe͉'ٶ턋mhoo7y=aLDŽΧ(\8a-r6 DqpV/;-E΂GbIP>in؂.Bmk- #|\._9?{lopy;l=`Lkvm$5f U0&ٶ"nglzo7:Q#~"?Σ,O$JvQ}ll{sdQZԑn?ޫ*]NQZV3gh[[-E:M;[:~PUt.`.He*&Lt?Զ[u&_!Gs8gsvMLHf۶=3ّJ3'$6oxSDxƶYbwDž.!mvhoo1~i8 WD%9R"d.>L&W_[olˆ $ܱ) Zq~_~9A590(' ͓xs! ɳ2 -Wu XL\H͉" \CYG!6yƻ괠Wvk*B poTLRwW3gĊ+`Y֥$a[&9 CMS䂸(\_r̙w.@nXrw-7AuF#":*#ΏgZ=ibEVo.-^H4'.{G>(.TL w'|W[" (897Z펰@8( g"0k{gc&DSEm/ DD1g }%1|aۡGJCTxy5<(& BNd$ZBN `8cz" =Ā 83"dIn-+VWW"@L F}1n Zsuuz=މG, ֗[;|!@sw8`LJov A GjZ _AJdxe~Ta&NUƃ"Z,DYs%๹G ~e}EH\t)D"63t]@40&P/qPSϩ ?m([i8ڋ*:²G98("? ~j4kDd1rG.StTo߾=he,-d9hD3Ns_7TPCu_%枾1qOۿP(ԛJJֺ7 %ֽW4PP"t4'.F߫~T8ns]Ďn&ݲFzMN~>q?b @".7&vwwOZO2 caSԄt:-"d2 a}# B69tv; sxltHoNmʙ, .]^d\E.BD "R6=zݡ\;4H"t: o9LҥZwBm~ֹ+\7˲XT"JY-"wض*w|0MINE DX,`x3O?=gRj/D$kd2I0`@kO%s b5|?OUv"OpQnpT "JtHqCI&[r*17HV"9#vm""i)i)`KEE[s.uٶ4wDȟE1E$? #^0D/o{_)W$/mgY[Ȏ;[n?{Ȥd2I}O|$0X^4v݈ _yO=YMNb_~aF"a FN '&S^eDƱ <$L߮InRjW^έފ:oǹazx_SKXmǚ nݺSR$9ٹ"= y#IjrEvOSTKw1"2}u.!J Vm+9 P[-D>|Ϳ;sLep~?|ڶL&$"@ސw!@FՄ}@#HCϛrZ C-Ŵ AAmTP  16TA{qCզCCܣD[(;U 46U*5vya~WGǩ$y MP\6il!;TmDŽB;=sZ(c8so*&L0# p]wR?s7Qe{G"H/\w`<}/B*6Wӯxm+ $=~S>vsjVѳ ;5T*%/IY1JVP݊l2߶]fHxZfm۶cxm}}"-I@ IϷ~6?jζg  H<@"kNEP($ZkI&US]ם""HN Btz›c@|URBi M-WzWRg4"m$5tzTZkMk4sLX8DHEdE(d?77H*" "i_!ӻjUa]]]`dxe*݆( TX@T[ )*z{{+=]r0[gHsR#L 6zmIL NHuݤRd:NHOeee:LzP)pw?a 0:R<3e۫RHRJe\RJiCDDkmd47DaH:Q5].RkQ/PJ!J~Bv4końZR)mu!aMp8]$:;vГ'O۷oJ)}O`_. GitHub issues are used for 3 different purposes: * `Bug tracker `_ : to check latest bug * `Forum `_ : Stackoverflow-style questions about Gunicorn usage * `Mailing list `_ : Discussion of Gunicorn development, new features and project management. Project maintenance guidelines are avaible on the `wiki `_ . IRC === The Gunicorn channel is on the `Freenode `_ IRC network. You can chat with other on `#gunicorn channel `_. Issue Tracking ============== Bug reports, enhancement requests and tasks generally go in the `Github issue tracker `_. Security Issues =============== The security mailing list is a place to report security issues. Only developers are subscribed to it. To post a message to the list use the address to `security@gunicorn.org `_ . gunicorn-20.1.0/docs/source/conf.py000066400000000000000000000033331401157322000171420ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Gunicorn documentation build configuration file # import os import sys import time DOCS_DIR = os.path.abspath(os.path.dirname(__file__)) on_rtd = os.environ.get('READTHEDOCS', None) == 'True' # for gunicorn_ext.py sys.path.append(os.path.join(DOCS_DIR, os.pardir)) sys.path.insert(0, os.path.join(DOCS_DIR, os.pardir, os.pardir)) extensions = ['gunicorn_ext'] templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' # General information about the project. project = 'Gunicorn' copyright = '2009-%s, Benoit Chesneau' % time.strftime('%Y') # gunicorn version import gunicorn release = version = gunicorn.__version__ exclude_patterns = [] pygments_style = 'sphinx' # -- Options for HTML output --------------------------------------------------- if not on_rtd: # only import and set the theme if we're building docs locally try: import sphinx_rtd_theme except ImportError: html_theme = 'default' else: html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] else: html_theme = 'default' html_static_path = ['_static'] htmlhelp_basename = 'Gunicorndoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { } latex_documents = [ ('index', 'Gunicorn.tex', 'Gunicorn Documentation', 'Benoit Chesneau', 'manual'), ] # -- Options for manual page output -------------------------------------------- man_pages = [ ('index', 'gunicorn', 'Gunicorn Documentation', ['Benoit Chesneau'], 1) ] texinfo_documents = [ ('index', 'Gunicorn', 'Gunicorn Documentation', 'Benoit Chesneau', 'Gunicorn', 'One line description of project.', 'Miscellaneous'), ] gunicorn-20.1.0/docs/source/configure.rst000066400000000000000000000074111401157322000203570ustar00rootroot00000000000000.. _configuration: ====================== Configuration Overview ====================== Gunicorn reads configuration information from five places. Gunicorn first reads environment variables for some configuration :ref:`settings `. Gunicorn then reads configuration from a framework specific configuration file. Currently this only affects Paster applications. The third source of configuration information is an optional configuration file ``gunicorn.conf.py`` searched in the current working directory or specified using a command line argument. Anything specified in this configuration file will override any framework specific settings. The fourth place of configuration information are command line arguments stored in an environment variable named ``GUNICORN_CMD_ARGS``. Lastly, the command line arguments used to invoke Gunicorn are the final place considered for configuration settings. If an option is specified on the command line, this is the value that will be used. When a configuration file is specified in the command line arguments and in the ``GUNICORN_CMD_ARGS`` environment variable, only the configuration file specified on the command line is used. Once again, in order of least to most authoritative: 1. Environment Variables 2. Framework Settings 3. Configuration File 4. ``GUNICORN_CMD_ARGS`` 5. Command Line .. note:: To print your resolved configuration when using the command line or the configuration file you can run the following command:: $ gunicorn --print-config APP_MODULE To check your resolved configuration when using the command line or the configuration file you can run the following command:: $ gunicorn --check-config APP_MODULE It also allows you to know if your application can be launched. Command Line ============ If an option is specified on the command line, it overrides all other values that may have been specified in the app specific settings, or in the optional configuration file. Not all Gunicorn settings are available to be set from the command line. To see the full list of command line settings you can do the usual:: $ gunicorn -h There is also a ``--version`` flag available to the command line scripts that isn't mentioned in the list of :ref:`settings `. Configuration File ================== The configuration file should be a valid Python source file with a **python extension** (e.g. `gunicorn.conf.py`). It only needs to be readable from the file system. More specifically, it does not have to be on the module path (sys.path, PYTHONPATH). Any Python is valid. Just consider that this will be run every time you start Gunicorn (including when you signal Gunicorn to reload). To set a parameter, just assign to it. There's no special syntax. The values you provide will be used for the configuration values. For instance:: import multiprocessing bind = "127.0.0.1:8000" workers = multiprocessing.cpu_count() * 2 + 1 All the settings are mentioned in the :ref:`settings ` list. Framework Settings ================== Currently, only Paster applications have access to framework specific settings. If you have ideas for providing settings to WSGI applications or pulling information from Django's settings.py feel free to open an issue_ to let us know. .. _issue: https://github.com/benoitc/gunicorn/issues Paster Applications ------------------- In your INI file, you can specify to use Gunicorn as the server like such: .. code-block:: ini [server:main] use = egg:gunicorn#main host = 192.168.0.1 port = 80 workers = 2 proc_name = brim Any parameters that Gunicorn knows about will automatically be inserted into the base configuration. Remember that these will be overridden by the config file and/or the command line. gunicorn-20.1.0/docs/source/custom.rst000066400000000000000000000036071401157322000177130ustar00rootroot00000000000000.. _custom: ================== Custom Application ================== .. versionadded:: 19.0 Sometimes, you want to integrate Gunicorn with your WSGI application. In this case, you can inherit from :class:`gunicorn.app.base.BaseApplication`. Here is a small example where we create a very small WSGI app and load it with a custom Application: .. literalinclude:: ../../examples/standalone_app.py :start-after: # See the NOTICE for more information :lines: 2- Direct Usage of Existing WSGI Apps ---------------------------------- If necessary, you can run Gunicorn straight from Python, allowing you to specify a WSGI-compatible application at runtime. This can be handy for rolling deploys or in the case of using PEX files to deploy your application, as the app and Gunicorn can be bundled in the same PEX file. Gunicorn has this functionality built-in as a first class citizen known as :class:`gunicorn.app.wsgiapp`. This can be used to run WSGI-compatible app instances such as those produced by Flask or Django. Assuming your WSGI API package is *exampleapi*, and your application instance is *app*, this is all you need to get going:: gunicorn.app.wsgiapp exampleapi:app This command will work with any Gunicorn CLI parameters or a config file - just pass them along as if you're directly giving them to Gunicorn: .. code-block:: bash # Custom parameters $ python gunicorn.app.wsgiapp exampleapi:app --bind=0.0.0.0:8081 --workers=4 # Using a config file $ python gunicorn.app.wsgiapp exampleapi:app -c config.py Note for those using PEX: use ``-c gunicorn`` as your entry at build time, and your compiled app should work with the entry point passed to it at run time. .. code-block:: bash # Generic pex build command via bash from root of exampleapi project $ pex . -v -c gunicorn -o compiledapp.pex # Running it ./compiledapp.pex exampleapi:app -c gunicorn_config.py gunicorn-20.1.0/docs/source/deploy.rst000066400000000000000000000265041401157322000176760ustar00rootroot00000000000000================== Deploying Gunicorn ================== We strongly recommend using Gunicorn behind a proxy server. Nginx Configuration =================== Although there are many HTTP proxies available, we strongly advise that you use Nginx_. If you choose another proxy server you need to make sure that it buffers slow clients when you use default Gunicorn workers. Without this buffering Gunicorn will be easily susceptible to denial-of-service attacks. You can use Hey_ to check if your proxy is behaving properly. An `example configuration`_ file for fast clients with Nginx_: .. literalinclude:: ../../examples/nginx.conf :language: nginx :caption: **nginx.conf** If you want to be able to handle streaming request/responses or other fancy features like Comet, Long polling, or Web sockets, you need to turn off the proxy buffering. **When you do this** you must run with one of the async worker classes. To turn off buffering, you only need to add ``proxy_buffering off;`` to your ``location`` block:: ... location @proxy_to_app { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_buffering off; proxy_pass http://app_server; } ... It is recommended to pass protocol information to Gunicorn. Many web frameworks use this information to generate URLs. Without this information, the application may mistakenly generate 'http' URLs in 'https' responses, leading to mixed content warnings or broken applications. To configure Nginx to pass an appropriate header, add a ``proxy_set_header`` directive to your ``location`` block:: ... proxy_set_header X-Forwarded-Proto $scheme; ... If you are running Nginx on a different host than Gunicorn you need to tell Gunicorn to trust the ``X-Forwarded-*`` headers sent by Nginx. By default, Gunicorn will only trust these headers if the connection comes from localhost. This is to prevent a malicious client from forging these headers:: $ gunicorn -w 3 --forwarded-allow-ips="10.170.3.217,10.170.3.220" test:app When the Gunicorn host is completely firewalled from the external network such that all connections come from a trusted proxy (e.g. Heroku) this value can be set to '*'. Using this value is **potentially dangerous** if connections to Gunicorn may come from untrusted proxies or directly from clients since the application may be tricked into serving SSL-only content over an insecure connection. Gunicorn 19 introduced a breaking change concerning how ``REMOTE_ADDR`` is handled. Previous to Gunicorn 19 this was set to the value of ``X-Forwarded-For`` if received from a trusted proxy. However, this was not in compliance with :rfc:`3875` which is why the ``REMOTE_ADDR`` is now the IP address of **the proxy** and **not the actual user**. To have access logs indicate **the actual user** IP when proxied, set :ref:`access-log-format` with a format which includes ``X-Forwarded-For``. For example, this format uses ``X-Forwarded-For`` in place of ``REMOTE_ADDR``:: %({x-forwarded-for}i)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" It is also worth noting that the ``REMOTE_ADDR`` will be completely empty if you bind Gunicorn to a UNIX socket and not a TCP ``host:port`` tuple. Using Virtualenv ================ To serve an app from a Virtualenv_ it is generally easiest to just install Gunicorn directly into the Virtualenv. This will create a set of Gunicorn scripts for that Virtualenv which can be used to run applications normally. If you have Virtualenv installed, you should be able to do something like this:: $ mkdir ~/venvs/ $ virtualenv ~/venvs/webapp $ source ~/venvs/webapp/bin/activate $ pip install gunicorn $ deactivate Then you just need to use one of the three Gunicorn scripts that was installed into ``~/venvs/webapp/bin``. Note: You can force the installation of Gunicorn in your Virtualenv by passing ``-I`` or ``--ignore-installed`` option to pip:: $ source ~/venvs/webapp/bin/activate $ pip install -I gunicorn Monitoring ========== .. note:: Make sure that when using either of these service monitors you do not enable the Gunicorn's daemon mode. These monitors expect that the process they launch will be the process they need to monitor. Daemonizing will fork-exec which creates an unmonitored process and generally just confuses the monitor services. Gaffer ------ Using Gafferd and gaffer ++++++++++++++++++++++++ Gaffer_ can be used to monitor Gunicorn. A simple configuration is:: [process:gunicorn] cmd = gunicorn -w 3 test:app cwd = /path/to/project Then you can easily manage Gunicorn using Gaffer_. Using a Procfile ++++++++++++++++ Create a ``Procfile`` in your project:: gunicorn = gunicorn -w 3 test:app You can launch any other applications that should be launched at the same time. Then you can start your Gunicorn application using Gaffer_:: gaffer start If gafferd is launched you can also load your Procfile in it directly:: gaffer load All your applications will be then supervised by gafferd. Runit ----- A popular method for deploying Gunicorn is to have it monitored by runit_. Here is an `example service`_ definition:: #!/bin/sh GUNICORN=/usr/local/bin/gunicorn ROOT=/path/to/project PID=/var/run/gunicorn.pid APP=main:application if [ -f $PID ]; then rm $PID; fi cd $ROOT exec $GUNICORN -c $ROOT/gunicorn.conf.py --pid=$PID $APP Save this as ``/etc/sv/[app_name]/run``, and make it executable (``chmod u+x /etc/sv/[app_name]/run``). Then run ``ln -s /etc/sv/[app_name] /etc/service/[app_name]``. If runit is installed, Gunicorn should start running automatically as soon as you create the symlink. If it doesn't start automatically, run the script directly to troubleshoot. Supervisor ---------- Another useful tool to monitor and control Gunicorn is Supervisor_. A `simple configuration`_ is:: [program:gunicorn] command=/path/to/gunicorn main:application -c /path/to/gunicorn.conf.py directory=/path/to/project user=nobody autostart=true autorestart=true redirect_stderr=true Upstart ------- Using Gunicorn with upstart is simple. In this example we will run the app "myapp" from a virtualenv. All errors will go to ``/var/log/upstart/myapp.log``. **/etc/init/myapp.conf**:: description "myapp" start on (filesystem) stop on runlevel [016] respawn setuid nobody setgid nogroup chdir /path/to/app/directory exec /path/to/virtualenv/bin/gunicorn myapp:app Systemd ------- A tool that is starting to be common on linux systems is Systemd_. It is a system services manager that allows for strict process management, resources and permissions control. Below are configurations files and instructions for using systemd to create a unix socket for incoming Gunicorn requests. Systemd will listen on this socket and start gunicorn automatically in response to traffic. Later in this section are instructions for configuring Nginx to forward web traffic to the newly created unix socket: **/etc/systemd/system/gunicorn.service**:: [Unit] Description=gunicorn daemon Requires=gunicorn.socket After=network.target [Service] Type=notify # the specific user that our service will run as User=someuser Group=someuser # another option for an even more restricted service is # DynamicUser=yes # see http://0pointer.net/blog/dynamic-users-with-systemd.html RuntimeDirectory=gunicorn WorkingDirectory=/home/someuser/applicationroot ExecStart=/usr/bin/gunicorn applicationname.wsgi ExecReload=/bin/kill -s HUP $MAINPID KillMode=mixed TimeoutStopSec=5 PrivateTmp=true [Install] WantedBy=multi-user.target **/etc/systemd/system/gunicorn.socket**:: [Unit] Description=gunicorn socket [Socket] ListenStream=/run/gunicorn.sock # Our service won't need permissions for the socket, since it # inherits the file descriptor by socket activation # only the nginx daemon will need access to the socket SocketUser=www-data # Optionally restrict the socket permissions even more. # SocketMode=600 [Install] WantedBy=sockets.target Next enable and start the socket (it will autostart at boot too):: systemctl enable --now gunicorn.socket Now let's see if the nginx daemon will be able to connect to the socket. Running ``sudo -u www-data curl --unix-socket /run/gunicorn.sock http``, our Gunicorn service will be automatically started and you should see some HTML from your server in the terminal. .. note:: systemd employs cgroups to track the processes of a service, so it doesn't need pid files. In the rare case that you need to find out the service main pid, you can use ``systemctl show --value -p MainPID gunicorn.service``, but if you only want to send a signal an even better option is ``systemctl kill -s HUP gunicorn.service``. .. note:: ``www-data`` is the default nginx user in debian, other distributions use different users (for example: ``http`` or ``nginx``). Check your distro to know what to put for the socket user, and for the sudo command. You must now configure your web proxy to send traffic to the new Gunicorn socket. Edit your ``nginx.conf`` to include the following: **/etc/nginx/nginx.conf**:: user www-data; ... http { server { listen 8000; server_name 127.0.0.1; location / { proxy_pass http://unix:/run/gunicorn.sock; } } } ... .. note:: The listen and server_name used here are configured for a local machine. In a production server you will most likely listen on port 80, and use your URL as the server_name. Now make sure you enable the nginx service so it automatically starts at boot:: systemctl enable nginx.service Either reboot, or start Nginx with the following command:: systemctl start nginx Now you should be able to test Nginx with Gunicorn by visiting http://127.0.0.1:8000/ in any web browser. Systemd is now set up. Logging ======= Logging can be configured by using various flags detailed in the `configuration documentation`_ or by creating a `logging configuration file`_. Send the ``USR1`` signal to rotate logs if you are using the logrotate utility:: kill -USR1 $(cat /var/run/gunicorn.pid) .. note:: Overriding the ``LOGGING`` dictionary requires to set ``disable_existing_loggers: False`` to not interfere with the Gunicorn logging. .. warning:: Gunicorn error log is here to log errors from Gunicorn, not from another application. .. _Nginx: https://nginx.org/ .. _Hey: https://github.com/rakyll/hey .. _`example configuration`: https://github.com/benoitc/gunicorn/blob/master/examples/nginx.conf .. _runit: http://smarden.org/runit/ .. _`example service`: https://github.com/benoitc/gunicorn/blob/master/examples/gunicorn_rc .. _Supervisor: http://supervisord.org/ .. _`simple configuration`: https://github.com/benoitc/gunicorn/blob/master/examples/supervisor.conf .. _`configuration documentation`: http://docs.gunicorn.org/en/latest/settings.html#logging .. _`logging configuration file`: https://github.com/benoitc/gunicorn/blob/master/examples/logging.conf .. _Virtualenv: https://pypi.python.org/pypi/virtualenv .. _Systemd: https://www.freedesktop.org/wiki/Software/systemd/ .. _Gaffer: https://gaffer.readthedocs.io/ gunicorn-20.1.0/docs/source/design.rst000066400000000000000000000144661401157322000176570ustar00rootroot00000000000000 .. _design: ====== Design ====== A brief description of the architecture of Gunicorn. Server Model ============ Gunicorn is based on the pre-fork worker model. This means that there is a central master process that manages a set of worker processes. The master never knows anything about individual clients. All requests and responses are handled completely by worker processes. Master ------ The master process is a simple loop that listens for various process signals and reacts accordingly. It manages the list of running workers by listening for signals like TTIN, TTOU, and CHLD. TTIN and TTOU tell the master to increase or decrease the number of running workers. CHLD indicates that a child process has terminated, in this case the master process automatically restarts the failed worker. Sync Workers ------------ The most basic and the default worker type is a synchronous worker class that handles a single request at a time. This model is the simplest to reason about as any errors will affect at most a single request. Though as we describe below only processing a single request at a time requires some assumptions about how applications are programmed. ``sync`` worker does not support persistent connections - each connection is closed after response has been sent (even if you manually add ``Keep-Alive`` or ``Connection: keep-alive`` header in your application). Async Workers ------------- The asynchronous workers available are based on Greenlets_ (via Eventlet_ and Gevent_). Greenlets are an implementation of cooperative multi-threading for Python. In general, an application should be able to make use of these worker classes with no changes. For full greenlet support applications might need to be adapted. When using, e.g., Gevent_ and Psycopg_ it makes sense to ensure psycogreen_ is installed and `setup `_. Other applications might not be compatible at all as they, e.g., rely on the original unpatched behavior. Tornado Workers --------------- There's also a Tornado worker class. It can be used to write applications using the Tornado framework. Although the Tornado workers are capable of serving a WSGI application, this is not a recommended configuration. .. _asyncio-workers: AsyncIO Workers --------------- These workers are compatible with Python 3. The worker `gthread` is a threaded worker. It accepts connections in the main loop, accepted connections are added to the thread pool as a connection job. On keepalive connections are put back in the loop waiting for an event. If no event happen after the keep alive timeout, the connection is closed. You can port also your application to use aiohttp_'s ``web.Application`` API and use the ``aiohttp.worker.GunicornWebWorker`` worker. Choosing a Worker Type ====================== The default synchronous workers assume that your application is resource-bound in terms of CPU and network bandwidth. Generally this means that your application shouldn't do anything that takes an undefined amount of time. An example of something that takes an undefined amount of time is a request to the internet. At some point the external network will fail in such a way that clients will pile up on your servers. So, in this sense, any web application which makes outgoing requests to APIs will benefit from an asynchronous worker. This resource bound assumption is why we require a buffering proxy in front of a default configuration Gunicorn. If you exposed synchronous workers to the internet, a DOS attack would be trivial by creating a load that trickles data to the servers. For the curious, Hey_ is an example of this type of load. Some examples of behavior requiring asynchronous workers: * Applications making long blocking calls (Ie, external web services) * Serving requests directly to the internet * Streaming requests and responses * Long polling * Web sockets * Comet How Many Workers? ================= DO NOT scale the number of workers to the number of clients you expect to have. Gunicorn should only need 4-12 worker processes to handle hundreds or thousands of requests per second. Gunicorn relies on the operating system to provide all of the load balancing when handling requests. Generally we recommend ``(2 x $num_cores) + 1`` as the number of workers to start off with. While not overly scientific, the formula is based on the assumption that for a given core, one worker will be reading or writing from the socket while the other worker is processing a request. Obviously, your particular hardware and application are going to affect the optimal number of workers. Our recommendation is to start with the above guess and tune using TTIN and TTOU signals while the application is under load. Always remember, there is such a thing as too many workers. After a point your worker processes will start thrashing system resources decreasing the throughput of the entire system. How Many Threads? =================== Since Gunicorn 19, a threads option can be used to process requests in multiple threads. Using threads assumes use of the gthread worker. One benefit from threads is that requests can take longer than the worker timeout while notifying the master process that it is not frozen and should not be killed. Depending on the system, using multiple threads, multiple worker processes, or some mixture, may yield the best results. For example, CPython may not perform as well as Jython when using threads, as threading is implemented differently by each. Using threads instead of processes is a good way to reduce the memory footprint of Gunicorn, while still allowing for application upgrades using the reload signal, as the application code will be shared among workers but loaded only in the worker processes (unlike when using the preload setting, which loads the code in the master process). .. note:: Under Python 2.x, you need to install the 'futures' package to use this feature. .. _Greenlets: https://github.com/python-greenlet/greenlet .. _Eventlet: http://eventlet.net/ .. _Gevent: http://www.gevent.org/ .. _Hey: https://github.com/rakyll/hey .. _aiohttp: https://docs.aiohttp.org/en/stable/deployment.html#nginx-gunicorn .. _`example`: https://github.com/benoitc/gunicorn/blob/master/examples/frameworks/flaskapp_aiohttp_wsgi.py .. _Psycopg: http://initd.org/psycopg/ .. _psycogreen: https://bitbucket.org/dvarrazzo/psycogreen gunicorn-20.1.0/docs/source/faq.rst000066400000000000000000000203221401157322000171410ustar00rootroot00000000000000.. _faq: === FAQ === WSGI Bits ========= How do I set SCRIPT_NAME? ------------------------- By default ``SCRIPT_NAME`` is an empty string. The value could be set by setting ``SCRIPT_NAME`` in the environment or as an HTTP header. Server Stuff ============ How do I reload my application in Gunicorn? ------------------------------------------- You can gracefully reload by sending HUP signal to gunicorn:: $ kill -HUP masterpid How might I test a proxy configuration? --------------------------------------- The Hey_ program is a great way to test that your proxy is correctly buffering responses for the synchronous workers:: $ hey -n 10000 -c 100 http://127.0.0.1:5000/ This runs a benchmark of 10000 requests with 100 running concurrently. How can I name processes? ------------------------- If you install the Python package setproctitle_ Gunicorn will set the process names to something a bit more meaningful. This will affect the output you see in tools like ``ps`` and ``top``. This helps for distinguishing the master process as well as between masters when running more than one app on a single machine. See the proc_name_ setting for more information. Why is there no HTTP Keep-Alive? -------------------------------- The default Sync workers are designed to run behind Nginx which only uses HTTP/1.0 with its upstream servers. If you want to deploy Gunicorn to handle unbuffered requests (ie, serving requests directly from the internet) you should use one of the async workers. .. _Hey: https://github.com/rakyll/hey .. _setproctitle: https://pypi.python.org/pypi/setproctitle .. _proc_name: settings.html#proc-name Worker Processes ================ How do I know which type of worker to use? ------------------------------------------ Read the :ref:`design` page for help on the various worker types. What types of workers are there? -------------------------------- Check out the configuration docs for worker_class_. How can I figure out the best number of worker processes? --------------------------------------------------------- Here is our recommendation for tuning the `number of workers`_. How can I change the number of workers dynamically? --------------------------------------------------- TTIN and TTOU signals can be sent to the master to increase or decrease the number of workers. To increase the worker count by one:: $ kill -TTIN $masterpid To decrease the worker count by one:: $ kill -TTOU $masterpid Does Gunicorn suffer from the thundering herd problem? ------------------------------------------------------ The thundering herd problem occurs when many sleeping request handlers, which may be either threads or processes, wake up at the same time to handle a new request. Since only one handler will receive the request, the others will have been awakened for no reason, wasting CPU cycles. At this time, Gunicorn does not implement any IPC solution for coordinating between worker processes. You may experience high load due to this problem when using many workers or threads. However `a work has been started `_ to remove this issue. .. _worker_class: settings.html#worker-class .. _`number of workers`: design.html#how-many-workers Why I don't see any logs in the console? ---------------------------------------- In version 19.0, Gunicorn doesn't log by default in the console. To watch the logs in the console you need to use the option ``--log-file=-``. In version 19.2, Gunicorn logs to the console by default again. Kernel Parameters ================= When dealing with large numbers of concurrent connections there are a handful of kernel parameters that you might need to adjust. Generally these should only affect sites with a very large concurrent load. These parameters are not specific to Gunicorn, they would apply to any sort of network server you may be running. These commands are for Linux. Your particular OS may have slightly different parameters. How can I increase the maximum number of file descriptors? ---------------------------------------------------------- One of the first settings that usually needs to be bumped is the maximum number of open file descriptors for a given process. For the confused out there, remember that Unices treat sockets as files. :: $ sudo ulimit -n 2048 How can I increase the maximum socket backlog? ---------------------------------------------- Listening sockets have an associated queue of incoming connections that are waiting to be accepted. If you happen to have a stampede of clients that fill up this queue new connections will eventually start getting dropped. :: $ sudo sysctl -w net.core.somaxconn="2048" How can I disable the use of ``sendfile()`` ------------------------------------------- Disabling the use ``sendfile()`` can be done by using the ``--no-sendfile`` setting or by setting the environment variable ``SENDFILE`` to 0. Troubleshooting =============== How do I fix Django reporting an ``ImproperlyConfigured`` error? ---------------------------------------------------------------- With asynchronous workers, creating URLs with the ``reverse`` function of ``django.core.urlresolvers`` may fail. Use ``reverse_lazy`` instead. .. _blocking-os-fchmod: How do I avoid Gunicorn excessively blocking in ``os.fchmod``? -------------------------------------------------------------- The current heartbeat system involves calling ``os.fchmod`` on temporary file handlers and may block a worker for arbitrary time if the directory is on a disk-backed filesystem. For example, by default ``/tmp`` is not mounted as ``tmpfs`` in Ubuntu; in AWS an EBS root instance volume may sometimes hang for half a minute and during this time Gunicorn workers may completely block in ``os.fchmod``. ``os.fchmod`` may introduce extra delays if the disk gets full. Also Gunicorn may refuse to start if it can't create the files when the disk is full. Currently to avoid these problems you can use a ``tmpfs`` mount (for a new directory or for ``/tmp``) and pass its path to ``--worker-tmp-dir``. First, check whether your ``/tmp`` is disk-backed or RAM-backed:: $ df /tmp Filesystem 1K-blocks Used Available Use% Mounted on /dev/xvda1 ... ... ... ... / No luck. If you are using Fedora or Ubuntu, you should already have a ``tmpfs`` mount at ``/dev/shm``:: $ df /dev/shm Filesystem 1K-blocks Used Available Use% Mounted on tmpfs ... ... ... ... /dev/shm In this case you can set ``--worker-tmp-dir /dev/shm``, otherwise you can create a new ``tmpfs`` mount:: sudo cp /etc/fstab /etc/fstab.orig sudo mkdir /mem echo 'tmpfs /mem tmpfs defaults,size=64m,mode=1777,noatime,comment=for-gunicorn 0 0' | sudo tee -a /etc/fstab sudo mount /mem Check the result:: $ df /mem Filesystem 1K-blocks Used Available Use% Mounted on tmpfs 65536 0 65536 0% /mem Now you can set ``--worker-tmp-dir /mem``. Why are Workers Silently Killed? -------------------------------------------------------------- A sometimes subtle problem to debug is when a worker process is killed and there is little logging information about what happened. If you use a reverse proxy like NGINX you might see 502 returned to a client. In the gunicorn logs you might simply see ``[35] [INFO] Booting worker with pid: 35`` It's completely normal for workers to be killed and startup, for example due to max-requests setting. Ordinarily gunicorn will capture any signals and log something. This particular failure case is usually due to a SIGKILL being received, as it's not possible to catch this signal silence is usually a common side effect! A common cause of SIGKILL is when OOM killer terminates a process due to low memory condition. This is increasingly common in container deployments where memory limits are enforced by cgroups, you'll usually see evidence of this from dmesg:: dmesg | grep gunicorn Memory cgroup out of memory: Kill process 24534 (gunicorn) score 1506 or sacrifice child Killed process 24534 (gunicorn) total-vm:1016648kB, anon-rss:550160kB, file-rss:25824kB, shmem-rss:0kB In these instances adjusting the memory limit is usually your best bet, it's also possible to configure OOM not to send SIGKILL by default. gunicorn-20.1.0/docs/source/index.rst000066400000000000000000000020201401157322000174740ustar00rootroot00000000000000====================== Gunicorn - WSGI server ====================== .. image:: _static/gunicorn.png :Website: http://gunicorn.org :Source code: https://github.com/benoitc/gunicorn :Issue tracker: https://github.com/benoitc/gunicorn/issues :IRC: ``#gunicorn`` on Freenode :Usage questions: https://github.com/benoitc/gunicorn/issues Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It's a pre-fork worker model ported from Ruby's Unicorn project. The Gunicorn server is broadly compatible with various web frameworks, simply implemented, light on server resources, and fairly speedy. Features -------- * Natively supports WSGI, Django, and Paster * Automatic worker process management * Simple Python configuration * Multiple worker configurations * Various server hooks for extensibility * Compatible with Python 3.x >= 3.5 Contents -------- .. toctree:: :maxdepth: 2 install run configure settings instrumentation deploy signals custom design faq community news gunicorn-20.1.0/docs/source/install.rst000066400000000000000000000126571401157322000200540ustar00rootroot00000000000000============ Installation ============ .. highlight:: bash :Requirements: **Python 3.x >= 3.5** To install the latest released version of Gunicorn:: $ pip install gunicorn From Source =========== You can install Gunicorn from source just as you would install any other Python package:: $ pip install git+https://github.com/benoitc/gunicorn.git This will allow you to keep up to date with development on GitHub:: $ pip install -U git+https://github.com/benoitc/gunicorn.git Async Workers ============= You may also want to install Eventlet_ or Gevent_ if you expect that your application code may need to pause for extended periods of time during request processing. Check out the `design docs`_ for more information on when you'll want to consider one of the alternate worker types. :: $ pip install greenlet # Required for both $ pip install eventlet # For eventlet workers $ pip install gunicorn[eventlet] # Or, using extra $ pip install gevent # For gevent workers $ pip install gunicorn[gevent] # Or, using extra .. note:: Both require ``greenlet``, which should get installed automatically. If its installation fails, you probably need to install the Python headers. These headers are available in most package managers. On Ubuntu the package name for ``apt-get`` is ``python-dev``. Gevent_ also requires that ``libevent`` 1.4.x or 2.0.4 is installed. This could be a more recent version than what is available in your package manager. If Gevent_ fails to build even with libevent_ installed, this is the most likely reason. Extra Packages ============== Some Gunicorn options require additional packages. You can use the ``[extra]`` syntax to install these at the same time as Gunicorn. Most extra packages are needed for alternate worker types. See the `design docs`_ for more information on when you'll want to consider an alternate worker type. * ``gunicorn[eventlet]`` - Eventlet-based greenlets workers * ``gunicorn[gevent]`` - Gevent-based greenlets workers * ``gunicorn[gthread]`` - Threaded workers * ``gunicorn[tornado]`` - Tornado-based workers, not recommended If you are running more than one instance of Gunicorn, the :ref:`proc-name` setting will help distinguish between them in tools like ``ps`` and ``top``. * ``gunicorn[setproctitle]`` - Enables setting the process name Multiple extras can be combined, like ``pip install gunicorn[gevent,setproctitle]``. Debian GNU/Linux ================ If you are using Debian GNU/Linux it is recommended that you use system packages to install Gunicorn except maybe when you want to use different versions of Gunicorn with virtualenv. This has a number of advantages: * Zero-effort installation: Automatically starts multiple Gunicorn instances based on configurations defined in ``/etc/gunicorn.d``. * Sensible default locations for logs (``/var/log/gunicorn``). Logs can be automatically rotated and compressed using ``logrotate``. * Improved security: Can easily run each Gunicorn instance with a dedicated UNIX user/group. * Sensible upgrade path: Upgrades to newer versions result in less downtime, handle conflicting changes in configuration options, and can be quickly rolled back in case of incompatibility. The package can also be purged entirely from the system in seconds. stable ("buster") ------------------ The version of Gunicorn in the Debian_ "stable" distribution is 19.9.0 (December 2020). You can install it using:: $ sudo apt-get install gunicorn3 You can also use the most recent version 20.0.4 (December 2020) by using `Debian Backports`_. First, copy the following line to your ``/etc/apt/sources.list``:: deb http://ftp.debian.org/debian buster-backports main Then, update your local package lists:: $ sudo apt-get update You can then install the latest version using:: $ sudo apt-get -t buster-backports install gunicorn oldstable ("stretch") --------------------- While Debian releases newer than Stretch will give you gunicorn with Python 3 support no matter if you install the gunicorn or gunicorn3 package for Stretch you specifically have to install gunicorn3 to get Python 3 support. The version of Gunicorn in the Debian_ "oldstable" distribution is 19.6.0 (December 2020). You can install it using:: $ sudo apt-get install gunicorn3 You can also use the most recent version 19.7.1 (December 2020) by using `Debian Backports`_. First, copy the following line to your ``/etc/apt/sources.list``:: deb http://ftp.debian.org/debian stretch-backports main Then, update your local package lists:: $ sudo apt-get update You can then install the latest version using:: $ sudo apt-get -t stretch-backports install gunicorn3 Testing ("bullseye") / Unstable ("sid") --------------------------------------- "bullseye" and "sid" contain the latest released version of Gunicorn 20.0.4 (December 2020). You can install it in the usual way:: $ sudo apt-get install gunicorn Ubuntu ====== Ubuntu_ 20.04 LTS (Focal Fossa) or later contains the Gunicorn package by default 20.0.4 (December 2020) so that you can install it in the usual way:: $ sudo apt-get update $ sudo apt-get install gunicorn .. _`design docs`: design.html .. _Eventlet: http://eventlet.net .. _Gevent: http://www.gevent.org/ .. _libevent: http://libevent.org/ .. _Debian: https://www.debian.org/ .. _`Debian Backports`: https://backports.debian.org/ .. _Ubuntu: https://www.ubuntu.com/ gunicorn-20.1.0/docs/source/instrumentation.rst000066400000000000000000000024551401157322000216440ustar00rootroot00000000000000.. _instrumentation: =============== Instrumentation =============== .. versionadded:: 19.1 Gunicorn provides an optional instrumentation of the arbiter and workers using the statsD_ protocol over UDP. Thanks to the ``gunicorn.instrument.statsd`` module, Gunicorn becomes a statsD client. The use of UDP cleanly isolates Gunicorn from the receiving end of the statsD metrics so that instrumentation does not cause Gunicorn to be held up by a slow statsD consumer. To use statsD, just tell Gunicorn where the statsD server is: .. code-block:: bash $ gunicorn --statsd-host=localhost:8125 --statsd-prefix=service.app ... The ``Statsd`` logger overrides ``gunicorn.glogging.Logger`` to track all requests. The following metrics are generated: * ``gunicorn.requests``: request rate per second * ``gunicorn.request.duration``: histogram of request duration (in millisecond) * ``gunicorn.workers``: number of workers managed by the arbiter (gauge) * ``gunicorn.log.critical``: rate of critical log messages * ``gunicorn.log.error``: rate of error log messages * ``gunicorn.log.warning``: rate of warning log messages * ``gunicorn.log.exception``: rate of exceptional log messages See the statsd-host_ setting for more information. .. _statsd-host: settings.html#statsd-host .. _statsD: https://github.com/etsy/statsd gunicorn-20.1.0/docs/source/news.rst000066400000000000000000000037251401157322000173560ustar00rootroot00000000000000========= Changelog ========= 20.1.0 - 2021-02-12 =================== - document WEB_CONCURRENCY is set by, at least, Heroku - capture peername from accept: Avoid calls to getpeername by capturing the peer name returned by accept - log a warning when a worker was terminated due to a signal - fix tornado usage with latest versions of Django - add support for python -m gunicorn - fix systemd socket activation example - allows to set wsgi application in configg file using ``wsgi_app`` - document ``--timeout = 0`` - always close a connection when the number of requests exceeds the max requests - Disable keepalive during graceful shutdown - kill tasks in the gthread workers during upgrade - fix latency in gevent worker when accepting new requests - fix file watcher: handle errors when new worker reboot and ensure the list of files is kept - document the default name and path of the configuration file - document how variable impact configuration - document the ``$PORT`` environment variable - added milliseconds option to request_time in access_log - added PIP requirements to be used for example - remove version from the Server header - fix sendfile: use ``socket.sendfile`` instead of ``os.sendfile`` - reloader: use absolute path to prevent empty to prevent0 ``InotifyError`` when a file is added to the working directory - Add --print-config option to print the resolved settings at startup. - remove the ``--log-dict-config`` CLI flag because it never had a working format (the ``logconfig_dict`` setting in configuration files continues to work) ** Breaking changes ** - minimum version is Python 3.5 - remove version from the Server header ** Others ** - miscellaneous changes in the code base to be a better citizen with Python 3 - remove dead code - fix documentation generation History ======= .. toctree:: :titlesonly: 2020-news 2019-news 2018-news 2017-news 2016-news 2015-news 2014-news 2013-news 2012-news 2011-news 2010-news gunicorn-20.1.0/docs/source/run.rst000066400000000000000000000145251401157322000172060ustar00rootroot00000000000000================ Running Gunicorn ================ .. highlight:: bash You can run Gunicorn by using commands or integrate with popular frameworks like Django, Pyramid, or TurboGears. For deploying Gunicorn in production see :doc:`deploy`. Commands ======== After installing Gunicorn you will have access to the command line script ``gunicorn``. .. _gunicorn-cmd: gunicorn -------- Basic usage:: $ gunicorn [OPTIONS] [WSGI_APP] Where ``WSGI_APP`` is of the pattern ``$(MODULE_NAME):$(VARIABLE_NAME)``. The module name can be a full dotted path. The variable name refers to a WSGI callable that should be found in the specified module. .. versionchanged:: 20.1.0 ``WSGI_APP`` is optional if it is defined in a :ref:`config` file. Example with the test app: .. code-block:: python def app(environ, start_response): """Simplest possible application object""" data = b'Hello, World!\n' status = '200 OK' response_headers = [ ('Content-type', 'text/plain'), ('Content-Length', str(len(data))) ] start_response(status, response_headers) return iter([data]) You can now run the app with the following command: .. code-block:: text $ gunicorn --workers=2 test:app The variable name can also be a function call. In that case the name will be imported from the module, then called to get the application object. This is commonly referred to as the "application factory" pattern. .. code-block:: python def create_app(): app = FrameworkApp() ... return app .. code-block:: text $ gunicorn --workers=2 'test:create_app()' Positional and keyword arguments can also be passed, but it is recommended to load configuration from environment variables rather than the command line. Commonly Used Arguments ^^^^^^^^^^^^^^^^^^^^^^^ * ``-c CONFIG, --config=CONFIG`` - Specify a config file in the form ``$(PATH)``, ``file:$(PATH)``, or ``python:$(MODULE_NAME)``. * ``-b BIND, --bind=BIND`` - Specify a server socket to bind. Server sockets can be any of ``$(HOST)``, ``$(HOST):$(PORT)``, ``fd://$(FD)``, or ``unix:$(PATH)``. An IP is a valid ``$(HOST)``. * ``-w WORKERS, --workers=WORKERS`` - The number of worker processes. This number should generally be between 2-4 workers per core in the server. Check the :ref:`faq` for ideas on tuning this parameter. * ``-k WORKERCLASS, --worker-class=WORKERCLASS`` - The type of worker process to run. You'll definitely want to read the production page for the implications of this parameter. You can set this to ``$(NAME)`` where ``$(NAME)`` is one of ``sync``, ``eventlet``, ``gevent``, ``tornado``, ``gthread``. ``sync`` is the default. See the :ref:`worker-class` documentation for more information. * ``-n APP_NAME, --name=APP_NAME`` - If setproctitle_ is installed you can adjust the name of Gunicorn process as they appear in the process system table (which affects tools like ``ps`` and ``top``). Settings can be specified by using environment variable :ref:`GUNICORN_CMD_ARGS `. See :ref:`configuration` and :ref:`settings` for detailed usage. .. _setproctitle: https://pypi.python.org/pypi/setproctitle Integration =========== Gunicorn also provides integration for Django and Paste Deploy applications. Django ------ Gunicorn will look for a WSGI callable named ``application`` if not specified. So for a typical Django project, invoking Gunicorn would look like:: $ gunicorn myproject.wsgi .. note:: This requires that your project be on the Python path; the simplest way to ensure that is to run this command from the same directory as your ``manage.py`` file. You can use the `--env `_ option to set the path to load the settings. In case you need it you can also add your application path to ``PYTHONPATH`` using the `--pythonpath `_ option:: $ gunicorn --env DJANGO_SETTINGS_MODULE=myproject.settings myproject.wsgi Paste Deployment ---------------- Frameworks such as Pyramid and Turbogears are typically configured using Paste Deployment configuration files. If you would like to use these files with Gunicorn, there are two approaches. As a server runner, Gunicorn can serve your application using the commands from your framework, such as ``pserve`` or ``gearbox``. To use Gunicorn with these commands, specify it as a server in your configuration file: .. code-block:: ini [server:main] use = egg:gunicorn#main host = 127.0.0.1 port = 8080 workers = 3 This approach is the quickest way to get started with Gunicorn, but there are some limitations. Gunicorn will have no control over how the application is loaded, so settings such as reload_ will have no effect and Gunicorn will be unable to hot upgrade a running application. Using the daemon_ option may confuse your command line tool. Instead, use the built-in support for these features provided by that tool. For example, run ``pserve --reload`` instead of specifying ``reload = True`` in the server configuration block. For advanced configuration of Gunicorn, such as `Server Hooks`_ specifying a Gunicorn configuration file using the ``config`` key is supported. To use the full power of Gunicorn's reloading and hot code upgrades, use the `paste option`_ to run your application instead. When used this way, Gunicorn will use the application defined by the PasteDeploy configuration file, but Gunicorn will not use any server configuration defined in the file. Instead, `configure gunicorn`_. For example:: $ gunicorn --paste development.ini -b :8080 --chdir /path/to/project Or use a different application:: $ gunicorn --paste development.ini#admin -b :8080 --chdir /path/to/project With both approaches, Gunicorn will use any loggers section found in Paste Deployment configuration file, unless instructed otherwise by specifying additional `logging settings`_. .. _reload: http://docs.gunicorn.org/en/latest/settings.html#reload .. _daemon: http://docs.gunicorn.org/en/latest/settings.html#daemon .. _Server Hooks: http://docs.gunicorn.org/en/latest/settings.html#server-hooks .. _paste option: http://docs.gunicorn.org/en/latest/settings.html#paste .. _configure gunicorn: http://docs.gunicorn.org/en/latest/configure.html .. _logging settings: http://docs.gunicorn.org/en/latest/settings.html#logging gunicorn-20.1.0/docs/source/settings.rst000066400000000000000000000777441401157322000202560ustar00rootroot00000000000000.. Please update gunicorn/config.py instead. .. _settings: Settings ======== This is an exhaustive list of settings for Gunicorn. Some settings are only able to be set from a configuration file. The setting name is what should be used in the configuration file. The command line arguments are listed as well for reference on setting at the command line. .. note:: Settings can be specified by using environment variable ``GUNICORN_CMD_ARGS``. All available command line arguments can be used. For example, to specify the bind address and number of workers:: $ GUNICORN_CMD_ARGS="--bind=127.0.0.1 --workers=3" gunicorn app:app .. versionadded:: 19.7 Config File ----------- .. _config: ``config`` ~~~~~~~~~~ **Command line:** ``-c CONFIG`` or ``--config CONFIG`` **Default:** ``'./gunicorn.conf.py'`` The Gunicorn config file. A string of the form ``PATH``, ``file:PATH``, or ``python:MODULE_NAME``. Only has an effect when specified on the command line or as part of an application specific configuration. By default, a file named ``gunicorn.conf.py`` will be read from the same directory where gunicorn is being run. .. versionchanged:: 19.4 Loading the config from a Python module requires the ``python:`` prefix. .. _wsgi-app: ``wsgi_app`` ~~~~~~~~~~~~ **Default:** ``None`` A WSGI application path in pattern ``$(MODULE_NAME):$(VARIABLE_NAME)``. .. versionadded:: 20.1.0 Debugging --------- .. _reload: ``reload`` ~~~~~~~~~~ **Command line:** ``--reload`` **Default:** ``False`` Restart workers when code changes. This setting is intended for development. It will cause workers to be restarted whenever application code changes. The reloader is incompatible with application preloading. When using a paste configuration be sure that the server block does not import any application code or the reload will not work as designed. The default behavior is to attempt inotify with a fallback to file system polling. Generally, inotify should be preferred if available because it consumes less system resources. .. note:: In order to use the inotify reloader, you must have the ``inotify`` package installed. .. _reload-engine: ``reload_engine`` ~~~~~~~~~~~~~~~~~ **Command line:** ``--reload-engine STRING`` **Default:** ``'auto'`` The implementation that should be used to power :ref:`reload`. Valid engines are: * ``'auto'`` * ``'poll'`` * ``'inotify'`` (requires inotify) .. versionadded:: 19.7 .. _reload-extra-files: ``reload_extra_files`` ~~~~~~~~~~~~~~~~~~~~~~ **Command line:** ``--reload-extra-file FILES`` **Default:** ``[]`` Extends :ref:`reload` option to also watch and reload on additional files (e.g., templates, configurations, specifications, etc.). .. versionadded:: 19.8 .. _spew: ``spew`` ~~~~~~~~ **Command line:** ``--spew`` **Default:** ``False`` Install a trace function that spews every line executed by the server. This is the nuclear option. .. _check-config: ``check_config`` ~~~~~~~~~~~~~~~~ **Command line:** ``--check-config`` **Default:** ``False`` Check the configuration and exit. The exit status is 0 if the configuration is correct, and 1 if the configuration is incorrect. .. _print-config: ``print_config`` ~~~~~~~~~~~~~~~~ **Command line:** ``--print-config`` **Default:** ``False`` Print the configuration settings as fully resolved. Implies :ref:`check-config`. Logging ------- .. _accesslog: ``accesslog`` ~~~~~~~~~~~~~ **Command line:** ``--access-logfile FILE`` **Default:** ``None`` The Access log file to write to. ``'-'`` means log to stdout. .. _disable-redirect-access-to-syslog: ``disable_redirect_access_to_syslog`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ **Command line:** ``--disable-redirect-access-to-syslog`` **Default:** ``False`` Disable redirect access logs to syslog. .. versionadded:: 19.8 .. _access-log-format: ``access_log_format`` ~~~~~~~~~~~~~~~~~~~~~ **Command line:** ``--access-logformat STRING`` **Default:** ``'%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'`` The access log format. =========== =========== Identifier Description =========== =========== h remote address l ``'-'`` u user name t date of the request r status line (e.g. ``GET / HTTP/1.1``) m request method U URL path without query string q query string H protocol s status B response length b response length or ``'-'`` (CLF format) f referer a user agent T request time in seconds M request time in milliseconds D request time in microseconds L request time in decimal seconds p process ID {header}i request header {header}o response header {variable}e environment variable =========== =========== Use lowercase for header and environment variable names, and put ``{...}x`` names inside ``%(...)s``. For example:: %({x-forwarded-for}i)s .. _errorlog: ``errorlog`` ~~~~~~~~~~~~ **Command line:** ``--error-logfile FILE`` or ``--log-file FILE`` **Default:** ``'-'`` The Error log file to write to. Using ``'-'`` for FILE makes gunicorn log to stderr. .. versionchanged:: 19.2 Log to stderr by default. .. _loglevel: ``loglevel`` ~~~~~~~~~~~~ **Command line:** ``--log-level LEVEL`` **Default:** ``'info'`` The granularity of Error log outputs. Valid level names are: * ``'debug'`` * ``'info'`` * ``'warning'`` * ``'error'`` * ``'critical'`` .. _capture-output: ``capture_output`` ~~~~~~~~~~~~~~~~~~ **Command line:** ``--capture-output`` **Default:** ``False`` Redirect stdout/stderr to specified file in :ref:`errorlog`. .. versionadded:: 19.6 .. _logger-class: ``logger_class`` ~~~~~~~~~~~~~~~~ **Command line:** ``--logger-class STRING`` **Default:** ``'gunicorn.glogging.Logger'`` The logger you want to use to log events in Gunicorn. The default class (``gunicorn.glogging.Logger``) handles most normal usages in logging. It provides error and access logging. You can provide your own logger by giving Gunicorn a Python path to a class that quacks like ``gunicorn.glogging.Logger``. .. _logconfig: ``logconfig`` ~~~~~~~~~~~~~ **Command line:** ``--log-config FILE`` **Default:** ``None`` The log config file to use. Gunicorn uses the standard Python logging module's Configuration file format. .. _logconfig-dict: ``logconfig_dict`` ~~~~~~~~~~~~~~~~~~ **Command line:** ``--log-config-dict`` **Default:** ``{}`` The log config dictionary to use, using the standard Python logging module's dictionary configuration format. This option takes precedence over the :ref:`logconfig` option, which uses the older file configuration format. Format: https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig .. versionadded:: 19.8 .. _syslog-addr: ``syslog_addr`` ~~~~~~~~~~~~~~~ **Command line:** ``--log-syslog-to SYSLOG_ADDR`` **Default:** ``'unix:///var/run/syslog'`` Address to send syslog messages. Address is a string of the form: * ``unix://PATH#TYPE`` : for unix domain socket. ``TYPE`` can be ``stream`` for the stream driver or ``dgram`` for the dgram driver. ``stream`` is the default. * ``udp://HOST:PORT`` : for UDP sockets * ``tcp://HOST:PORT`` : for TCP sockets .. _syslog: ``syslog`` ~~~~~~~~~~ **Command line:** ``--log-syslog`` **Default:** ``False`` Send *Gunicorn* logs to syslog. .. versionchanged:: 19.8 You can now disable sending access logs by using the :ref:`disable-redirect-access-to-syslog` setting. .. _syslog-prefix: ``syslog_prefix`` ~~~~~~~~~~~~~~~~~ **Command line:** ``--log-syslog-prefix SYSLOG_PREFIX`` **Default:** ``None`` Makes Gunicorn use the parameter as program-name in the syslog entries. All entries will be prefixed by ``gunicorn.``. By default the program name is the name of the process. .. _syslog-facility: ``syslog_facility`` ~~~~~~~~~~~~~~~~~~~ **Command line:** ``--log-syslog-facility SYSLOG_FACILITY`` **Default:** ``'user'`` Syslog facility name .. _enable-stdio-inheritance: ``enable_stdio_inheritance`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ **Command line:** ``-R`` or ``--enable-stdio-inheritance`` **Default:** ``False`` Enable stdio inheritance. Enable inheritance for stdio file descriptors in daemon mode. Note: To disable the Python stdout buffering, you can to set the user environment variable ``PYTHONUNBUFFERED`` . .. _statsd-host: ``statsd_host`` ~~~~~~~~~~~~~~~ **Command line:** ``--statsd-host STATSD_ADDR`` **Default:** ``None`` ``host:port`` of the statsd server to log to. .. versionadded:: 19.1 .. _dogstatsd-tags: ``dogstatsd_tags`` ~~~~~~~~~~~~~~~~~~ **Command line:** ``--dogstatsd-tags DOGSTATSD_TAGS`` **Default:** ``''`` A comma-delimited list of datadog statsd (dogstatsd) tags to append to statsd metrics. .. versionadded:: 20 .. _statsd-prefix: ``statsd_prefix`` ~~~~~~~~~~~~~~~~~ **Command line:** ``--statsd-prefix STATSD_PREFIX`` **Default:** ``''`` Prefix to use when emitting statsd metrics (a trailing ``.`` is added, if not provided). .. versionadded:: 19.2 Process Naming -------------- .. _proc-name: ``proc_name`` ~~~~~~~~~~~~~ **Command line:** ``-n STRING`` or ``--name STRING`` **Default:** ``None`` A base to use with setproctitle for process naming. This affects things like ``ps`` and ``top``. If you're going to be running more than one instance of Gunicorn you'll probably want to set a name to tell them apart. This requires that you install the setproctitle module. If not set, the *default_proc_name* setting will be used. .. _default-proc-name: ``default_proc_name`` ~~~~~~~~~~~~~~~~~~~~~ **Default:** ``'gunicorn'`` Internal setting that is adjusted for each type of application. SSL --- .. _keyfile: ``keyfile`` ~~~~~~~~~~~ **Command line:** ``--keyfile FILE`` **Default:** ``None`` SSL key file .. _certfile: ``certfile`` ~~~~~~~~~~~~ **Command line:** ``--certfile FILE`` **Default:** ``None`` SSL certificate file .. _ssl-version: ``ssl_version`` ~~~~~~~~~~~~~~~ **Command line:** ``--ssl-version`` **Default:** ``<_SSLMethod.PROTOCOL_TLS: 2>`` SSL version to use. ============= ============ --ssl-version Description ============= ============ SSLv3 SSLv3 is not-secure and is strongly discouraged. SSLv23 Alias for TLS. Deprecated in Python 3.6, use TLS. TLS Negotiate highest possible version between client/server. Can yield SSL. (Python 3.6+) TLSv1 TLS 1.0 TLSv1_1 TLS 1.1 (Python 3.4+) TLSv1_2 TLS 1.2 (Python 3.4+) TLS_SERVER Auto-negotiate the highest protocol version like TLS, but only support server-side SSLSocket connections. (Python 3.6+) ============= ============ .. versionchanged:: 19.7 The default value has been changed from ``ssl.PROTOCOL_TLSv1`` to ``ssl.PROTOCOL_SSLv23``. .. versionchanged:: 20.0 This setting now accepts string names based on ``ssl.PROTOCOL_`` constants. .. _cert-reqs: ``cert_reqs`` ~~~~~~~~~~~~~ **Command line:** ``--cert-reqs`` **Default:** ```` Whether client certificate is required (see stdlib ssl module's) .. _ca-certs: ``ca_certs`` ~~~~~~~~~~~~ **Command line:** ``--ca-certs FILE`` **Default:** ``None`` CA certificates file .. _suppress-ragged-eofs: ``suppress_ragged_eofs`` ~~~~~~~~~~~~~~~~~~~~~~~~ **Command line:** ``--suppress-ragged-eofs`` **Default:** ``True`` Suppress ragged EOFs (see stdlib ssl module's) .. _do-handshake-on-connect: ``do_handshake_on_connect`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ **Command line:** ``--do-handshake-on-connect`` **Default:** ``False`` Whether to perform SSL handshake on socket connect (see stdlib ssl module's) .. _ciphers: ``ciphers`` ~~~~~~~~~~~ **Command line:** ``--ciphers`` **Default:** ``None`` SSL Cipher suite to use, in the format of an OpenSSL cipher list. By default we use the default cipher list from Python's ``ssl`` module, which contains ciphers considered strong at the time of each Python release. As a recommended alternative, the Open Web App Security Project (OWASP) offers `a vetted set of strong cipher strings rated A+ to C- `_. OWASP provides details on user-agent compatibility at each security level. See the `OpenSSL Cipher List Format Documentation `_ for details on the format of an OpenSSL cipher list. Security -------- .. _limit-request-line: ``limit_request_line`` ~~~~~~~~~~~~~~~~~~~~~~ **Command line:** ``--limit-request-line INT`` **Default:** ``4094`` The maximum size of HTTP request line in bytes. This parameter is used to limit the allowed size of a client's HTTP request-line. Since the request-line consists of the HTTP method, URI, and protocol version, this directive places a restriction on the length of a request-URI allowed for a request on the server. A server needs this value to be large enough to hold any of its resource names, including any information that might be passed in the query part of a GET request. Value is a number from 0 (unlimited) to 8190. This parameter can be used to prevent any DDOS attack. .. _limit-request-fields: ``limit_request_fields`` ~~~~~~~~~~~~~~~~~~~~~~~~ **Command line:** ``--limit-request-fields INT`` **Default:** ``100`` Limit the number of HTTP headers fields in a request. This parameter is used to limit the number of headers in a request to prevent DDOS attack. Used with the *limit_request_field_size* it allows more safety. By default this value is 100 and can't be larger than 32768. .. _limit-request-field-size: ``limit_request_field_size`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ **Command line:** ``--limit-request-field_size INT`` **Default:** ``8190`` Limit the allowed size of an HTTP request header field. Value is a positive number or 0. Setting it to 0 will allow unlimited header field sizes. .. warning:: Setting this parameter to a very high or unlimited value can open up for DDOS attacks. Server Hooks ------------ .. _on-starting: ``on_starting`` ~~~~~~~~~~~~~~~ **Default:** .. code-block:: python def on_starting(server): pass Called just before the master process is initialized. The callable needs to accept a single instance variable for the Arbiter. .. _on-reload: ``on_reload`` ~~~~~~~~~~~~~ **Default:** .. code-block:: python def on_reload(server): pass Called to recycle workers during a reload via SIGHUP. The callable needs to accept a single instance variable for the Arbiter. .. _when-ready: ``when_ready`` ~~~~~~~~~~~~~~ **Default:** .. code-block:: python def when_ready(server): pass Called just after the server is started. The callable needs to accept a single instance variable for the Arbiter. .. _pre-fork: ``pre_fork`` ~~~~~~~~~~~~ **Default:** .. code-block:: python def pre_fork(server, worker): pass Called just before a worker is forked. The callable needs to accept two instance variables for the Arbiter and new Worker. .. _post-fork: ``post_fork`` ~~~~~~~~~~~~~ **Default:** .. code-block:: python def post_fork(server, worker): pass Called just after a worker has been forked. The callable needs to accept two instance variables for the Arbiter and new Worker. .. _post-worker-init: ``post_worker_init`` ~~~~~~~~~~~~~~~~~~~~ **Default:** .. code-block:: python def post_worker_init(worker): pass Called just after a worker has initialized the application. The callable needs to accept one instance variable for the initialized Worker. .. _worker-int: ``worker_int`` ~~~~~~~~~~~~~~ **Default:** .. code-block:: python def worker_int(worker): pass Called just after a worker exited on SIGINT or SIGQUIT. The callable needs to accept one instance variable for the initialized Worker. .. _worker-abort: ``worker_abort`` ~~~~~~~~~~~~~~~~ **Default:** .. code-block:: python def worker_abort(worker): pass Called when a worker received the SIGABRT signal. This call generally happens on timeout. The callable needs to accept one instance variable for the initialized Worker. .. _pre-exec: ``pre_exec`` ~~~~~~~~~~~~ **Default:** .. code-block:: python def pre_exec(server): pass Called just before a new master process is forked. The callable needs to accept a single instance variable for the Arbiter. .. _pre-request: ``pre_request`` ~~~~~~~~~~~~~~~ **Default:** .. code-block:: python def pre_request(worker, req): worker.log.debug("%s %s" % (req.method, req.path)) Called just before a worker processes the request. The callable needs to accept two instance variables for the Worker and the Request. .. _post-request: ``post_request`` ~~~~~~~~~~~~~~~~ **Default:** .. code-block:: python def post_request(worker, req, environ, resp): pass Called after a worker processes the request. The callable needs to accept two instance variables for the Worker and the Request. .. _child-exit: ``child_exit`` ~~~~~~~~~~~~~~ **Default:** .. code-block:: python def child_exit(server, worker): pass Called just after a worker has been exited, in the master process. The callable needs to accept two instance variables for the Arbiter and the just-exited Worker. .. versionadded:: 19.7 .. _worker-exit: ``worker_exit`` ~~~~~~~~~~~~~~~ **Default:** .. code-block:: python def worker_exit(server, worker): pass Called just after a worker has been exited, in the worker process. The callable needs to accept two instance variables for the Arbiter and the just-exited Worker. .. _nworkers-changed: ``nworkers_changed`` ~~~~~~~~~~~~~~~~~~~~ **Default:** .. code-block:: python def nworkers_changed(server, new_value, old_value): pass Called just after *num_workers* has been changed. The callable needs to accept an instance variable of the Arbiter and two integers of number of workers after and before change. If the number of workers is set for the first time, *old_value* would be ``None``. .. _on-exit: ``on_exit`` ~~~~~~~~~~~ **Default:** .. code-block:: python def on_exit(server): pass Called just before exiting Gunicorn. The callable needs to accept a single instance variable for the Arbiter. Server Mechanics ---------------- .. _preload-app: ``preload_app`` ~~~~~~~~~~~~~~~ **Command line:** ``--preload`` **Default:** ``False`` Load application code before the worker processes are forked. By preloading an application you can save some RAM resources as well as speed up server boot times. Although, if you defer application loading to each worker process, you can reload your application code easily by restarting workers. .. _sendfile: ``sendfile`` ~~~~~~~~~~~~ **Command line:** ``--no-sendfile`` **Default:** ``None`` Disables the use of ``sendfile()``. If not set, the value of the ``SENDFILE`` environment variable is used to enable or disable its usage. .. versionadded:: 19.2 .. versionchanged:: 19.4 Swapped ``--sendfile`` with ``--no-sendfile`` to actually allow disabling. .. versionchanged:: 19.6 added support for the ``SENDFILE`` environment variable .. _reuse-port: ``reuse_port`` ~~~~~~~~~~~~~~ **Command line:** ``--reuse-port`` **Default:** ``False`` Set the ``SO_REUSEPORT`` flag on the listening socket. .. versionadded:: 19.8 .. _chdir: ``chdir`` ~~~~~~~~~ **Command line:** ``--chdir`` **Default:** ``'/Users/chainz/Documents/Projects/gunicorn/docs'`` Change directory to specified directory before loading apps. .. _daemon: ``daemon`` ~~~~~~~~~~ **Command line:** ``-D`` or ``--daemon`` **Default:** ``False`` Daemonize the Gunicorn process. Detaches the server from the controlling terminal and enters the background. .. _raw-env: ``raw_env`` ~~~~~~~~~~~ **Command line:** ``-e ENV`` or ``--env ENV`` **Default:** ``[]`` Set environment variables in the execution environment. Should be a list of strings in the ``key=value`` format. For example on the command line: .. code-block:: console $ gunicorn -b 127.0.0.1:8000 --env FOO=1 test:app Or in the configuration file: .. code-block:: python raw_env = ["FOO=1"] .. _pidfile: ``pidfile`` ~~~~~~~~~~~ **Command line:** ``-p FILE`` or ``--pid FILE`` **Default:** ``None`` A filename to use for the PID file. If not set, no PID file will be written. .. _worker-tmp-dir: ``worker_tmp_dir`` ~~~~~~~~~~~~~~~~~~ **Command line:** ``--worker-tmp-dir DIR`` **Default:** ``None`` A directory to use for the worker heartbeat temporary file. If not set, the default temporary directory will be used. .. note:: The current heartbeat system involves calling ``os.fchmod`` on temporary file handlers and may block a worker for arbitrary time if the directory is on a disk-backed filesystem. See :ref:`blocking-os-fchmod` for more detailed information and a solution for avoiding this problem. .. _user: ``user`` ~~~~~~~~ **Command line:** ``-u USER`` or ``--user USER`` **Default:** ``501`` Switch worker processes to run as this user. A valid user id (as an integer) or the name of a user that can be retrieved with a call to ``pwd.getpwnam(value)`` or ``None`` to not change the worker process user. .. _group: ``group`` ~~~~~~~~~ **Command line:** ``-g GROUP`` or ``--group GROUP`` **Default:** ``20`` Switch worker process to run as this group. A valid group id (as an integer) or the name of a user that can be retrieved with a call to ``pwd.getgrnam(value)`` or ``None`` to not change the worker processes group. .. _umask: ``umask`` ~~~~~~~~~ **Command line:** ``-m INT`` or ``--umask INT`` **Default:** ``0`` A bit mask for the file mode on files written by Gunicorn. Note that this affects unix socket permissions. A valid value for the ``os.umask(mode)`` call or a string compatible with ``int(value, 0)`` (``0`` means Python guesses the base, so values like ``0``, ``0xFF``, ``0022`` are valid for decimal, hex, and octal representations) .. _initgroups: ``initgroups`` ~~~~~~~~~~~~~~ **Command line:** ``--initgroups`` **Default:** ``False`` If true, set the worker process's group access list with all of the groups of which the specified username is a member, plus the specified group id. .. versionadded:: 19.7 .. _tmp-upload-dir: ``tmp_upload_dir`` ~~~~~~~~~~~~~~~~~~ **Default:** ``None`` Directory to store temporary request data as they are read. This may disappear in the near future. This path should be writable by the process permissions set for Gunicorn workers. If not specified, Gunicorn will choose a system generated temporary directory. .. _secure-scheme-headers: ``secure_scheme_headers`` ~~~~~~~~~~~~~~~~~~~~~~~~~ **Default:** ``{'X-FORWARDED-PROTOCOL': 'ssl', 'X-FORWARDED-PROTO': 'https', 'X-FORWARDED-SSL': 'on'}`` A dictionary containing headers and values that the front-end proxy uses to indicate HTTPS requests. These tell Gunicorn to set ``wsgi.url_scheme`` to ``https``, so your application can tell that the request is secure. The dictionary should map upper-case header names to exact string values. The value comparisons are case-sensitive, unlike the header names, so make sure they're exactly what your front-end proxy sends when handling HTTPS requests. It is important that your front-end proxy configuration ensures that the headers defined here can not be passed directly from the client. .. _forwarded-allow-ips: ``forwarded_allow_ips`` ~~~~~~~~~~~~~~~~~~~~~~~ **Command line:** ``--forwarded-allow-ips STRING`` **Default:** ``'127.0.0.1'`` Front-end's IPs from which allowed to handle set secure headers. (comma separate). Set to ``*`` to disable checking of Front-end IPs (useful for setups where you don't know in advance the IP address of Front-end, but you still trust the environment). By default, the value of the ``FORWARDED_ALLOW_IPS`` environment variable. If it is not defined, the default is ``"127.0.0.1"``. .. _pythonpath: ``pythonpath`` ~~~~~~~~~~~~~~ **Command line:** ``--pythonpath STRING`` **Default:** ``None`` A comma-separated list of directories to add to the Python path. e.g. ``'/home/djangoprojects/myproject,/home/python/mylibrary'``. .. _paste: ``paste`` ~~~~~~~~~ **Command line:** ``--paste STRING`` or ``--paster STRING`` **Default:** ``None`` Load a PasteDeploy config file. The argument may contain a ``#`` symbol followed by the name of an app section from the config file, e.g. ``production.ini#admin``. At this time, using alternate server blocks is not supported. Use the command line arguments to control server configuration instead. .. _proxy-protocol: ``proxy_protocol`` ~~~~~~~~~~~~~~~~~~ **Command line:** ``--proxy-protocol`` **Default:** ``False`` Enable detect PROXY protocol (PROXY mode). Allow using HTTP and Proxy together. It may be useful for work with stunnel as HTTPS frontend and Gunicorn as HTTP server. PROXY protocol: http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt Example for stunnel config:: [https] protocol = proxy accept = 443 connect = 80 cert = /etc/ssl/certs/stunnel.pem key = /etc/ssl/certs/stunnel.key .. _proxy-allow-ips: ``proxy_allow_ips`` ~~~~~~~~~~~~~~~~~~~ **Command line:** ``--proxy-allow-from`` **Default:** ``'127.0.0.1'`` Front-end's IPs from which allowed accept proxy requests (comma separate). Set to ``*`` to disable checking of Front-end IPs (useful for setups where you don't know in advance the IP address of Front-end, but you still trust the environment) .. _raw-paste-global-conf: ``raw_paste_global_conf`` ~~~~~~~~~~~~~~~~~~~~~~~~~ **Command line:** ``--paste-global CONF`` **Default:** ``[]`` Set a PasteDeploy global config variable in ``key=value`` form. The option can be specified multiple times. The variables are passed to the the PasteDeploy entrypoint. Example:: $ gunicorn -b 127.0.0.1:8000 --paste development.ini --paste-global FOO=1 --paste-global BAR=2 .. versionadded:: 19.7 .. _strip-header-spaces: ``strip_header_spaces`` ~~~~~~~~~~~~~~~~~~~~~~~ **Command line:** ``--strip-header-spaces`` **Default:** ``False`` Strip spaces present between the header name and the the ``:``. This is known to induce vulnerabilities and is not compliant with the HTTP/1.1 standard. See https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn. Use with care and only if necessary. Server Socket ------------- .. _bind: ``bind`` ~~~~~~~~ **Command line:** ``-b ADDRESS`` or ``--bind ADDRESS`` **Default:** ``['127.0.0.1:8000']`` The socket to bind. A string of the form: ``HOST``, ``HOST:PORT``, ``unix:PATH``, ``fd://FD``. An IP is a valid ``HOST``. .. versionchanged:: 20.0 Support for ``fd://FD`` got added. Multiple addresses can be bound. ex.:: $ gunicorn -b 127.0.0.1:8000 -b [::1]:8000 test:app will bind the `test:app` application on localhost both on ipv6 and ipv4 interfaces. If the ``PORT`` environment variable is defined, the default is ``['0.0.0.0:$PORT']``. If it is not defined, the default is ``['127.0.0.1:8000']``. .. _backlog: ``backlog`` ~~~~~~~~~~~ **Command line:** ``--backlog INT`` **Default:** ``2048`` The maximum number of pending connections. This refers to the number of clients that can be waiting to be served. Exceeding this number results in the client getting an error when attempting to connect. It should only affect servers under significant load. Must be a positive integer. Generally set in the 64-2048 range. Worker Processes ---------------- .. _workers: ``workers`` ~~~~~~~~~~~ **Command line:** ``-w INT`` or ``--workers INT`` **Default:** ``1`` The number of worker processes for handling requests. A positive integer generally in the ``2-4 x $(NUM_CORES)`` range. You'll want to vary this a bit to find the best for your particular application's work load. By default, the value of the ``WEB_CONCURRENCY`` environment variable. If it is not defined, the default is ``1``. .. _worker-class: ``worker_class`` ~~~~~~~~~~~~~~~~ **Command line:** ``-k STRING`` or ``--worker-class STRING`` **Default:** ``'sync'`` The type of workers to use. The default class (``sync``) should handle most "normal" types of workloads. You'll want to read :doc:`design` for information on when you might want to choose one of the other worker classes. Required libraries may be installed using setuptools' ``extras_require`` feature. A string referring to one of the following bundled classes: * ``sync`` * ``eventlet`` - Requires eventlet >= 0.24.1 (or install it via ``pip install gunicorn[eventlet]``) * ``gevent`` - Requires gevent >= 1.4 (or install it via ``pip install gunicorn[gevent]``) * ``tornado`` - Requires tornado >= 0.2 (or install it via ``pip install gunicorn[tornado]``) * ``gthread`` - Python 2 requires the futures package to be installed (or install it via ``pip install gunicorn[gthread]``) Optionally, you can provide your own worker by giving Gunicorn a Python path to a subclass of ``gunicorn.workers.base.Worker``. This alternative syntax will load the gevent class: ``gunicorn.workers.ggevent.GeventWorker``. .. _threads: ``threads`` ~~~~~~~~~~~ **Command line:** ``--threads INT`` **Default:** ``1`` The number of worker threads for handling requests. Run each worker with the specified number of threads. A positive integer generally in the ``2-4 x $(NUM_CORES)`` range. You'll want to vary this a bit to find the best for your particular application's work load. If it is not defined, the default is ``1``. This setting only affects the Gthread worker type. .. note:: If you try to use the ``sync`` worker type and set the ``threads`` setting to more than 1, the ``gthread`` worker type will be used instead. .. _worker-connections: ``worker_connections`` ~~~~~~~~~~~~~~~~~~~~~~ **Command line:** ``--worker-connections INT`` **Default:** ``1000`` The maximum number of simultaneous clients. This setting only affects the Eventlet and Gevent worker types. .. _max-requests: ``max_requests`` ~~~~~~~~~~~~~~~~ **Command line:** ``--max-requests INT`` **Default:** ``0`` The maximum number of requests a worker will process before restarting. Any value greater than zero will limit the number of requests a worker will process before automatically restarting. This is a simple method to help limit the damage of memory leaks. If this is set to zero (the default) then the automatic worker restarts are disabled. .. _max-requests-jitter: ``max_requests_jitter`` ~~~~~~~~~~~~~~~~~~~~~~~ **Command line:** ``--max-requests-jitter INT`` **Default:** ``0`` The maximum jitter to add to the *max_requests* setting. The jitter causes the restart per worker to be randomized by ``randint(0, max_requests_jitter)``. This is intended to stagger worker restarts to avoid all workers restarting at the same time. .. versionadded:: 19.2 .. _timeout: ``timeout`` ~~~~~~~~~~~ **Command line:** ``-t INT`` or ``--timeout INT`` **Default:** ``30`` Workers silent for more than this many seconds are killed and restarted. Value is a positive number or 0. Setting it to 0 has the effect of infinite timeouts by disabling timeouts for all workers entirely. Generally, the default of thirty seconds should suffice. Only set this noticeably higher if you're sure of the repercussions for sync workers. For the non sync workers it just means that the worker process is still communicating and is not tied to the length of time required to handle a single request. .. _graceful-timeout: ``graceful_timeout`` ~~~~~~~~~~~~~~~~~~~~ **Command line:** ``--graceful-timeout INT`` **Default:** ``30`` Timeout for graceful workers restart. After receiving a restart signal, workers have this much time to finish serving requests. Workers still alive after the timeout (starting from the receipt of the restart signal) are force killed. .. _keepalive: ``keepalive`` ~~~~~~~~~~~~~ **Command line:** ``--keep-alive INT`` **Default:** ``2`` The number of seconds to wait for requests on a Keep-Alive connection. Generally set in the 1-5 seconds range for servers with direct connection to the client (e.g. when you don't have separate load balancer). When Gunicorn is deployed behind a load balancer, it often makes sense to set this to a higher value. .. note:: ``sync`` worker does not support persistent connections and will ignore this option. gunicorn-20.1.0/docs/source/signals.rst000066400000000000000000000124541401157322000200410ustar00rootroot00000000000000.. _signals: =============== Signal Handling =============== A brief description of the signals handled by Gunicorn. We also document the signals used internally by Gunicorn to communicate with the workers. Master process ============== - ``QUIT``, ``INT``: Quick shutdown - ``TERM``: Graceful shutdown. Waits for workers to finish their current requests up to the :ref:`graceful-timeout`. - ``HUP``: Reload the configuration, start the new worker processes with a new configuration and gracefully shutdown older workers. If the application is not preloaded (using the :ref:`preload-app` option), Gunicorn will also load the new version of it. - ``TTIN``: Increment the number of processes by one - ``TTOU``: Decrement the number of processes by one - ``USR1``: Reopen the log files - ``USR2``: Upgrade Gunicorn on the fly. A separate ``TERM`` signal should be used to kill the old master process. This signal can also be used to use the new versions of pre-loaded applications. See :ref:`binary-upgrade` for more information. - ``WINCH``: Gracefully shutdown the worker processes when Gunicorn is daemonized. Worker process ============== Sending signals directly to the worker processes should not normally be needed. If the master process is running, any exited worker will be automatically respawned. - ``QUIT``, ``INT``: Quick shutdown - ``TERM``: Graceful shutdown - ``USR1``: Reopen the log files Reload the configuration ======================== The ``HUP`` signal can be used to reload the Gunicorn configuration on the fly. :: 2013-06-29 06:26:55 [20682] [INFO] Handling signal: hup 2013-06-29 06:26:55 [20682] [INFO] Hang up: Master 2013-06-29 06:26:55 [20703] [INFO] Booting worker with pid: 20703 2013-06-29 06:26:55 [20702] [INFO] Booting worker with pid: 20702 2013-06-29 06:26:55 [20688] [INFO] Worker exiting (pid: 20688) 2013-06-29 06:26:55 [20687] [INFO] Worker exiting (pid: 20687) 2013-06-29 06:26:55 [20689] [INFO] Worker exiting (pid: 20689) 2013-06-29 06:26:55 [20704] [INFO] Booting worker with pid: 20704 Sending a ``HUP`` signal will reload the configuration, start the new worker processes with a new configuration and gracefully shutdown older workers. If the application is not preloaded (using the :ref:`preload-app` option), Gunicorn will also load the new version of it. .. _binary-upgrade: Upgrading to a new binary on the fly ==================================== .. versionchanged:: 19.6.0 PID file naming format has been changed from ``.pid.oldbin`` to ``.pid.2``. If you need to replace the Gunicorn binary with a new one (when upgrading to a new version or adding/removing server modules), you can do it without any service downtime - no incoming requests will be lost. Preloaded applications will also be reloaded. First, replace the old binary with a new one, then send a ``USR2`` signal to the current master process. It executes a new binary whose PID file is postfixed with ``.2`` (e.g. ``/var/run/gunicorn.pid.2``), which in turn starts a new master process and new worker processes:: PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 20844 benoitc 20 0 54808 11m 3352 S 0.0 0.1 0:00.36 gunicorn: master [test:app] 20849 benoitc 20 0 54808 9.9m 1500 S 0.0 0.1 0:00.02 gunicorn: worker [test:app] 20850 benoitc 20 0 54808 9.9m 1500 S 0.0 0.1 0:00.01 gunicorn: worker [test:app] 20851 benoitc 20 0 54808 9.9m 1500 S 0.0 0.1 0:00.01 gunicorn: worker [test:app] 20854 benoitc 20 0 55748 12m 3348 S 0.0 0.2 0:00.35 gunicorn: master [test:app] 20859 benoitc 20 0 55748 11m 1500 S 0.0 0.1 0:00.01 gunicorn: worker [test:app] 20860 benoitc 20 0 55748 11m 1500 S 0.0 0.1 0:00.00 gunicorn: worker [test:app] 20861 benoitc 20 0 55748 11m 1500 S 0.0 0.1 0:00.01 gunicorn: worker [test:app] At this point, two instances of Gunicorn are running, handling the incoming requests together. To phase the old instance out, you have to send a ``WINCH`` signal to the old master process, and its worker processes will start to gracefully shut down. At this point you can still revert to the old process since it hasn't closed its listen sockets yet, by following these steps: - Send a ``HUP`` signal to the old master process - it will start the worker processes without reloading a configuration file - Send a ``TERM`` signal to the new master process to gracefully shut down its worker processes - Send a ``QUIT`` signal to the new master process to force it quit If for some reason the new worker processes do not quit, send a ``KILL`` signal to them after the new master process quits, and everything will back to exactly as before the upgrade attempt. If the update is successful and you want to keep the new master process, send a ``TERM`` signal to the old master process to leave only the new server running:: PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 20854 benoitc 20 0 55748 12m 3348 S 0.0 0.2 0:00.45 gunicorn: master [test:app] 20859 benoitc 20 0 55748 11m 1500 S 0.0 0.1 0:00.02 gunicorn: worker [test:app] 20860 benoitc 20 0 55748 11m 1500 S 0.0 0.1 0:00.02 gunicorn: worker [test:app] 20861 benoitc 20 0 55748 11m 1500 S 0.0 0.1 0:00.01 gunicorn: worker [test:app] gunicorn-20.1.0/examples/000077500000000000000000000000001401157322000152275ustar00rootroot00000000000000gunicorn-20.1.0/examples/alt_spec.py000066400000000000000000000013421401157322000173730ustar00rootroot00000000000000# -*- coding: utf-8 - # # An example of how to pass information from the command line to # a WSGI app. Only applies to the native WSGI workers used by # Gunicorn sync (default) workers. # # $ gunicorn 'alt_spec:load(arg)' # # Single quoting is generally necessary for shell escape semantics. # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. def load(arg): def app(environ, start_response): data = b'Hello, %s!\n' % arg status = '200 OK' response_headers = [ ('Content-type', 'text/plain'), ('Content-Length', str(len(data))) ] start_response(status, response_headers) return iter([data]) return app gunicorn-20.1.0/examples/bad.py000066400000000000000000000003141401157322000163250ustar00rootroot00000000000000import tempfile files = [] def app(environ, start_response): files.append(tempfile.mkstemp()) start_response('200 OK', [('Content-type', 'text/plain'), ('Content-length', '2')]) return ['ok'] gunicorn-20.1.0/examples/boot_fail.py000066400000000000000000000001541401157322000175370ustar00rootroot00000000000000 raise RuntimeError("Bad app!") def app(environ, start_response): assert 1 == 2, "Shouldn't get here." gunicorn-20.1.0/examples/deep/000077500000000000000000000000001401157322000161445ustar00rootroot00000000000000gunicorn-20.1.0/examples/deep/__init__.py000066400000000000000000000000001401157322000202430ustar00rootroot00000000000000gunicorn-20.1.0/examples/deep/test.py000066400000000000000000000012231401157322000174730ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. # # Example code from Eventlet sources from wsgiref.validate import validator from gunicorn import __version__ @validator def app(environ, start_response): """Simplest possible application object""" data = b'Hello, World!\n' status = '200 OK' response_headers = [ ('Content-type', 'text/plain'), ('Content-Length', str(len(data))), ('X-Gunicorn-Version', __version__), ('Foo', 'B\u00e5r'), # Foo: Bår ] start_response(status, response_headers) return iter([data]) gunicorn-20.1.0/examples/echo.py000066400000000000000000000012441401157322000165200ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. # # Example code from Eventlet sources from gunicorn import __version__ def app(environ, start_response): """Simplest possible application object""" if environ['REQUEST_METHOD'].upper() != 'POST': data = b'Hello, World!\n' else: data = environ['wsgi.input'].read() status = '200 OK' response_headers = [ ('Content-type', 'text/plain'), ('Content-Length', str(len(data))), ('X-Gunicorn-Version', __version__) ] start_response(status, response_headers) return iter([data]) gunicorn-20.1.0/examples/example_config.py000066400000000000000000000150431401157322000205640ustar00rootroot00000000000000# Sample Gunicorn configuration file. # # Server socket # # bind - The socket to bind. # # A string of the form: 'HOST', 'HOST:PORT', 'unix:PATH'. # An IP is a valid HOST. # # backlog - The number of pending connections. This refers # to the number of clients that can be waiting to be # served. Exceeding this number results in the client # getting an error when attempting to connect. It should # only affect servers under significant load. # # Must be a positive integer. Generally set in the 64-2048 # range. # bind = '127.0.0.1:8000' backlog = 2048 # # Worker processes # # workers - The number of worker processes that this server # should keep alive for handling requests. # # A positive integer generally in the 2-4 x $(NUM_CORES) # range. You'll want to vary this a bit to find the best # for your particular application's work load. # # worker_class - The type of workers to use. The default # sync class should handle most 'normal' types of work # loads. You'll want to read # http://docs.gunicorn.org/en/latest/design.html#choosing-a-worker-type # for information on when you might want to choose one # of the other worker classes. # # A string referring to a Python path to a subclass of # gunicorn.workers.base.Worker. The default provided values # can be seen at # http://docs.gunicorn.org/en/latest/settings.html#worker-class # # worker_connections - For the eventlet and gevent worker classes # this limits the maximum number of simultaneous clients that # a single process can handle. # # A positive integer generally set to around 1000. # # timeout - If a worker does not notify the master process in this # number of seconds it is killed and a new worker is spawned # to replace it. # # Generally set to thirty seconds. Only set this noticeably # higher if you're sure of the repercussions for sync workers. # For the non sync workers it just means that the worker # process is still communicating and is not tied to the length # of time required to handle a single request. # # keepalive - The number of seconds to wait for the next request # on a Keep-Alive HTTP connection. # # A positive integer. Generally set in the 1-5 seconds range. # workers = 1 worker_class = 'sync' worker_connections = 1000 timeout = 30 keepalive = 2 # # spew - Install a trace function that spews every line of Python # that is executed when running the server. This is the # nuclear option. # # True or False # spew = False # # Server mechanics # # daemon - Detach the main Gunicorn process from the controlling # terminal with a standard fork/fork sequence. # # True or False # # raw_env - Pass environment variables to the execution environment. # # pidfile - The path to a pid file to write # # A path string or None to not write a pid file. # # user - Switch worker processes to run as this user. # # A valid user id (as an integer) or the name of a user that # can be retrieved with a call to pwd.getpwnam(value) or None # to not change the worker process user. # # group - Switch worker process to run as this group. # # A valid group id (as an integer) or the name of a user that # can be retrieved with a call to pwd.getgrnam(value) or None # to change the worker processes group. # # umask - A mask for file permissions written by Gunicorn. Note that # this affects unix socket permissions. # # A valid value for the os.umask(mode) call or a string # compatible with int(value, 0) (0 means Python guesses # the base, so values like "0", "0xFF", "0022" are valid # for decimal, hex, and octal representations) # # tmp_upload_dir - A directory to store temporary request data when # requests are read. This will most likely be disappearing soon. # # A path to a directory where the process owner can write. Or # None to signal that Python should choose one on its own. # daemon = False raw_env = [ 'DJANGO_SECRET_KEY=something', 'SPAM=eggs', ] pidfile = None umask = 0 user = None group = None tmp_upload_dir = None # # Logging # # logfile - The path to a log file to write to. # # A path string. "-" means log to stdout. # # loglevel - The granularity of log output # # A string of "debug", "info", "warning", "error", "critical" # errorlog = '-' loglevel = 'info' accesslog = '-' access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' # # Process naming # # proc_name - A base to use with setproctitle to change the way # that Gunicorn processes are reported in the system process # table. This affects things like 'ps' and 'top'. If you're # going to be running more than one instance of Gunicorn you'll # probably want to set a name to tell them apart. This requires # that you install the setproctitle module. # # A string or None to choose a default of something like 'gunicorn'. # proc_name = None # # Server hooks # # post_fork - Called just after a worker has been forked. # # A callable that takes a server and worker instance # as arguments. # # pre_fork - Called just prior to forking the worker subprocess. # # A callable that accepts the same arguments as after_fork # # pre_exec - Called just prior to forking off a secondary # master process during things like config reloading. # # A callable that takes a server instance as the sole argument. # def post_fork(server, worker): server.log.info("Worker spawned (pid: %s)", worker.pid) def pre_fork(server, worker): pass def pre_exec(server): server.log.info("Forked child, re-executing.") def when_ready(server): server.log.info("Server is ready. Spawning workers") def worker_int(worker): worker.log.info("worker received INT or QUIT signal") ## get traceback info import threading, sys, traceback id2name = {th.ident: th.name for th in threading.enumerate()} code = [] for threadId, stack in sys._current_frames().items(): code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,""), threadId)) for filename, lineno, name, line in traceback.extract_stack(stack): code.append('File: "%s", line %d, in %s' % (filename, lineno, name)) if line: code.append(" %s" % (line.strip())) worker.log.debug("\n".join(code)) def worker_abort(worker): worker.log.info("worker received SIGABRT signal") gunicorn-20.1.0/examples/frameworks/000077500000000000000000000000001401157322000174075ustar00rootroot00000000000000gunicorn-20.1.0/examples/frameworks/cherryapp.py000066400000000000000000000003061401157322000217550ustar00rootroot00000000000000import cherrypy class Root(object): @cherrypy.expose def index(self): return 'Hello World!' cherrypy.config.update({'environment': 'embedded'}) app = cherrypy.tree.mount(Root()) gunicorn-20.1.0/examples/frameworks/django/000077500000000000000000000000001401157322000206515ustar00rootroot00000000000000gunicorn-20.1.0/examples/frameworks/django/README000066400000000000000000000000741401157322000215320ustar00rootroot00000000000000Applications to test Django support: testing -> Django 1.4 gunicorn-20.1.0/examples/frameworks/django/testing/000077500000000000000000000000001401157322000223265ustar00rootroot00000000000000gunicorn-20.1.0/examples/frameworks/django/testing/manage.py000077500000000000000000000003641401157322000241360ustar00rootroot00000000000000#!/usr/bin/env python import os, sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testing.settings") from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) gunicorn-20.1.0/examples/frameworks/django/testing/testing/000077500000000000000000000000001401157322000240035ustar00rootroot00000000000000gunicorn-20.1.0/examples/frameworks/django/testing/testing/__init__.py000066400000000000000000000000001401157322000261020ustar00rootroot00000000000000gunicorn-20.1.0/examples/frameworks/django/testing/testing/apps/000077500000000000000000000000001401157322000247465ustar00rootroot00000000000000gunicorn-20.1.0/examples/frameworks/django/testing/testing/apps/__init__.py000066400000000000000000000000001401157322000270450ustar00rootroot00000000000000gunicorn-20.1.0/examples/frameworks/django/testing/testing/apps/someapp/000077500000000000000000000000001401157322000264125ustar00rootroot00000000000000gunicorn-20.1.0/examples/frameworks/django/testing/testing/apps/someapp/__init__.py000077500000000000000000000000001401157322000305140ustar00rootroot00000000000000gunicorn-20.1.0/examples/frameworks/django/testing/testing/apps/someapp/middleware.py000066400000000000000000000011631401157322000311020ustar00rootroot00000000000000from multiprocessing import Process, Queue import requests def child_process(queue): while True: print(queue.get()) requests.get('http://requestb.in/15s95oz1') class GunicornSubProcessTestMiddleware(object): def __init__(self): super().__init__() self.queue = Queue() self.process = Process(target=child_process, args=(self.queue,)) self.process.start() def process_request(self, request): self.queue.put(('REQUEST',)) def process_response(self, request, response): self.queue.put(('RESPONSE', response.status_code)) return response gunicorn-20.1.0/examples/frameworks/django/testing/testing/apps/someapp/models.py000066400000000000000000000000001401157322000302350ustar00rootroot00000000000000gunicorn-20.1.0/examples/frameworks/django/testing/testing/apps/someapp/templates/000077500000000000000000000000001401157322000304105ustar00rootroot00000000000000gunicorn-20.1.0/examples/frameworks/django/testing/testing/apps/someapp/templates/base.html000066400000000000000000000013671401157322000322170ustar00rootroot00000000000000 gunicorn django example app

test app

{% block content %}{% endblock %}
gunicorn-20.1.0/examples/frameworks/django/testing/testing/apps/someapp/templates/home.html000066400000000000000000000007271401157322000322340ustar00rootroot00000000000000{% extends "base.html" %} {% block content %}
{% csrf_token %} {{ form.as_table }}

Got

{% if subject %}

subject:
{{ subject}}

message:
{{ message }}

size:
{{ size }}

{% endif %} {% endblock content %} gunicorn-20.1.0/examples/frameworks/django/testing/testing/apps/someapp/tests.py000077500000000000000000000010011401157322000301210ustar00rootroot00000000000000""" This file demonstrates two different styles of tests (one doctest and one unittest). These will both pass when you run "manage.py test". Replace these with more appropriate tests for your application. """ from django.test import TestCase class SimpleTest(TestCase): def test_basic_addition(self): """ Tests that 1 + 1 always equals 2. """ self.failUnlessEqual(1 + 1, 2) __test__ = {"doctest": """ Another way to test that 1 + 1 is equal to 2. >>> 1 + 1 == 2 True """} gunicorn-20.1.0/examples/frameworks/django/testing/testing/apps/someapp/urls.py000066400000000000000000000002051401157322000277460ustar00rootroot00000000000000from django.conf.urls import url from . import views urlpatterns = [ url(r'^acsv$', views.acsv), url(r'^$', views.home), ] gunicorn-20.1.0/examples/frameworks/django/testing/testing/apps/someapp/views.py000077500000000000000000000030141401157322000301220ustar00rootroot00000000000000import csv import io import os from django import forms from django.http import HttpResponse from django.shortcuts import render from django.template import RequestContext class MsgForm(forms.Form): subject = forms.CharField(max_length=100) message = forms.CharField() f = forms.FileField() def home(request): from django.conf import settings print(settings.SOME_VALUE) subject = None message = None size = 0 print(request.META) if request.POST: form = MsgForm(request.POST, request.FILES) print(request.FILES) if form.is_valid(): subject = form.cleaned_data['subject'] message = form.cleaned_data['message'] f = request.FILES['f'] if not hasattr(f, "fileno"): size = len(f.read()) else: try: size = int(os.fstat(f.fileno())[6]) except io.UnsupportedOperation: size = len(f.read()) else: form = MsgForm() return render(request, 'home.html', { 'form': form, 'subject': subject, 'message': message, 'size': size }) def acsv(request): rows = [ {'a': 1, 'b': 2}, {'a': 3, 'b': 3} ] response = HttpResponse(mimetype='text/csv') response['Content-Disposition'] = 'attachment; filename=report.csv' writer = csv.writer(response) writer.writerow(['a', 'b']) for r in rows: writer.writerow([r['a'], r['b']]) return response gunicorn-20.1.0/examples/frameworks/django/testing/testing/settings.py000066400000000000000000000131421401157322000262160ustar00rootroot00000000000000# Django settings for testing project. DEBUG = True TEMPLATE_DEBUG = DEBUG ADMINS = ( # ('Your Name', 'your_email@example.com'), ) MANAGERS = ADMINS DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 'NAME': 'testdb.sql', # Or path to database file if using sqlite3. 'USER': '', # Not used with sqlite3. 'PASSWORD': '', # Not used with sqlite3. 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 'PORT': '', # Set to empty string for default. Not used with sqlite3. } } # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # although not all choices may be available on all operating systems. # On Unix systems, a value of None will cause Django to use the same # timezone as the operating system. # If running in a Windows environment this must be set to the same as your # system time zone. TIME_ZONE = 'America/Chicago' # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html LANGUAGE_CODE = 'en-us' SITE_ID = 1 # If you set this to False, Django will make some optimizations so as not # to load the internationalization machinery. USE_I18N = True # If you set this to False, Django will not format dates, numbers and # calendars according to the current locale. USE_L10N = True # If you set this to False, Django will not use timezone-aware datetimes. USE_TZ = True # Absolute filesystem path to the directory that will hold user-uploaded files. # Example: "/home/media/media.lawrence.com/media/" MEDIA_ROOT = '' # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash. # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" MEDIA_URL = '' # Absolute path to the directory static files should be collected to. # Don't put anything in this directory yourself; store your static files # in apps' "static/" subdirectories and in STATICFILES_DIRS. # Example: "/home/media/media.lawrence.com/static/" STATIC_ROOT = '' # URL prefix for static files. # Example: "http://media.lawrence.com/static/" STATIC_URL = '/static/' # Additional locations of static files STATICFILES_DIRS = ( # Put strings here, like "/home/html/static" or "C:/www/django/static". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. ) # List of finder classes that know how to find static files in # various locations. STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', # 'django.contrib.staticfiles.finders.DefaultStorageFinder', ) # Make this unique, and don't share it with anybody. SECRET_KEY = 'what' # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', # 'django.template.loaders.eggs.Loader', ) MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', # Uncomment the next line for simple clickjacking protection: # 'django.middleware.clickjacking.XFrameOptionsMiddleware', # uncomment the next line to test multiprocessing #'testing.apps.someapp.middleware.GunicornSubProcessTestMiddleware', ) ROOT_URLCONF = 'testing.urls' # Python dotted path to the WSGI application used by Django's runserver. WSGI_APPLICATION = 'testing.wsgi.application' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { # ... some options here ... }, }, ] TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. ) INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', # Uncomment the next line to enable the admin: 'django.contrib.admin', # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', 'testing.apps.someapp', 'gunicorn' ) # A sample logging configuration. The only tangible logging # performed by this configuration is to send an email to # the site admins on every HTTP 500 error when DEBUG=False. # See http://docs.djangoproject.com/en/dev/topics/logging for # more details on how to customize your logging configuration. LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'filters': { 'require_debug_false': { '()': 'django.utils.log.RequireDebugFalse' } }, 'handlers': { 'mail_admins': { 'level': 'ERROR', 'filters': ['require_debug_false'], 'class': 'django.utils.log.AdminEmailHandler' } }, 'loggers': { 'django.request': { 'handlers': ['mail_admins'], 'level': 'ERROR', 'propagate': True, }, } } SOME_VALUE = "test on reload" gunicorn-20.1.0/examples/frameworks/django/testing/testing/urls.py000066400000000000000000000011011401157322000253330ustar00rootroot00000000000000from django.conf.urls import include, url # Uncomment the next two lines to enable the admin: from django.contrib import admin admin.autodiscover() urlpatterns = [ # Examples: # url(r'^$', 'testing.views.home', name='home'), # url(r'^testing/', include('testing.foo.urls')), # Uncomment the admin/doc line below to enable admin documentation: # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: url(r'^admin/', admin.site.urls), url(r'^', include("testing.apps.someapp.urls")), ] gunicorn-20.1.0/examples/frameworks/django/testing/testing/wsgi.py000066400000000000000000000025171401157322000253330ustar00rootroot00000000000000""" WSGI config for testing project. This module contains the WSGI application used by Django's development server and any production WSGI deployments. It should expose a module-level variable named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover this application via the ``WSGI_APPLICATION`` setting. Usually you will have the standard Django WSGI application here, but it also might make sense to replace the whole Django WSGI application with a custom one that later delegates to the Django one. For example, you could introduce WSGI middleware here, or combine a Django application with an application of another framework. """ import os import sys # make sure the current project is in PYTHONPATH sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) # set the environment settings os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testing.settings") # This application object is used by any WSGI server configured to use this # file. This includes Django's development server, if the WSGI_APPLICATION # setting points here. from django.core.wsgi import get_wsgi_application application = get_wsgi_application() # Apply WSGI middleware here. # from helloworld.wsgi import HelloWorldApplication # application = HelloWorldApplication(application) gunicorn-20.1.0/examples/frameworks/flask_sendfile.py000066400000000000000000000004431401157322000227330ustar00rootroot00000000000000import io from flask import Flask, send_file app = Flask(__name__) @app.route('/') def index(): buf = io.BytesIO() buf.write(b'hello world') buf.seek(0) return send_file(buf, attachment_filename="testing.txt", as_attachment=True) gunicorn-20.1.0/examples/frameworks/flaskapp.py000066400000000000000000000002231401157322000215570ustar00rootroot00000000000000# Run with: # # $ gunicorn flaskapp:app # from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" gunicorn-20.1.0/examples/frameworks/flaskapp_aiohttp_wsgi.py000066400000000000000000000007651401157322000243530ustar00rootroot00000000000000# Example command to run the example: # # $ gunicorn flaskapp_aiohttp_wsgi:aioapp -k aiohttp.worker.GunicornWebWorker # from aiohttp import web from aiohttp_wsgi import WSGIHandler from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return 'Hello, world!' def make_aiohttp_app(app): wsgi_handler = WSGIHandler(app) aioapp = web.Application() aioapp.router.add_route('*', '/{path_info:.*}', wsgi_handler) return aioapp aioapp = make_aiohttp_app(app) gunicorn-20.1.0/examples/frameworks/pyramidapp.py000066400000000000000000000005221401157322000221260ustar00rootroot00000000000000from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') def goodbye_world(request): return Response('Goodbye world!') config = Configurator() config.add_view(hello_world) config.add_view(goodbye_world, name='goodbye') app = config.make_wsgi_app() gunicorn-20.1.0/examples/frameworks/requirements.txt000066400000000000000000000002261401157322000226730ustar00rootroot00000000000000-r requirements_flaskapp.txt -r requirements_cherryapp.txt -r requirements_pyramidapp.txt -r requirements_tornadoapp.txt -r requirements_webpyapp.txt gunicorn-20.1.0/examples/frameworks/requirements_cherryapp.txt000066400000000000000000000000111401157322000247400ustar00rootroot00000000000000cherrypy gunicorn-20.1.0/examples/frameworks/requirements_flaskapp.txt000066400000000000000000000000061401157322000245500ustar00rootroot00000000000000flask gunicorn-20.1.0/examples/frameworks/requirements_pyramidapp.txt000066400000000000000000000000101401157322000251100ustar00rootroot00000000000000pyramid gunicorn-20.1.0/examples/frameworks/requirements_tornadoapp.txt000066400000000000000000000000121401157322000251130ustar00rootroot00000000000000tornado<6 gunicorn-20.1.0/examples/frameworks/requirements_webpyapp.txt000066400000000000000000000000071401157322000245770ustar00rootroot00000000000000web-py gunicorn-20.1.0/examples/frameworks/tornadoapp.py000066400000000000000000000015511401157322000221320ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. # # Run with: # # $ gunicorn -k tornado tornadoapp:app # from datetime import timedelta from tornado.web import Application, RequestHandler, asynchronous from tornado.ioloop import IOLoop class MainHandler(RequestHandler): def get(self): self.write("Hello, world") class LongPollHandler(RequestHandler): @asynchronous def get(self): lines = ['line 1\n', 'line 2\n'] def send(): try: self.write(lines.pop(0)) self.flush() except: self.finish() else: IOLoop.instance().add_timeout(timedelta(0, 20), send) send() app = Application([ (r"/", MainHandler), (r"/longpoll", LongPollHandler) ]) gunicorn-20.1.0/examples/frameworks/webpyapp.py000066400000000000000000000003051401157322000216060ustar00rootroot00000000000000# Run with # # $ gunicorn webpyapp:app # import web urls = ( '/', 'index' ) class index: def GET(self): return "Hello, world!" app = web.application(urls, globals()).wsgifunc() gunicorn-20.1.0/examples/gunicorn_rc000077500000000000000000000003271401157322000174670ustar00rootroot00000000000000#!/bin/sh GUNICORN=/usr/local/bin/gunicorn ROOT=/path/to/project PID=/var/run/gunicorn.pid APP=main:application if [ -f $PID ]; then rm $PID; fi cd $ROOT exec $GUNICORN -c $ROOT/gunicorn.conf.py --pid=$PID $APP gunicorn-20.1.0/examples/hello.txt000066400000000000000000000000151401157322000170670ustar00rootroot00000000000000Hello world! gunicorn-20.1.0/examples/log_app.ini000066400000000000000000000007001401157322000173460ustar00rootroot00000000000000[app:main] paste.app_factory = log_app:app_factory [server:main] use = egg:gunicorn#main host = 127.0.0.1 port = 8080 workers = 3 [loggers] keys=root [handlers] keys=console [formatters] keys=default [logger_root] level=INFO qualname=root handlers=console [handler_console] class=StreamHandler formatter=default args=(sys.stdout, ) [formatter_default] format=[%(asctime)s] [%(levelname)-7s] - %(process)d:%(name)s:%(funcName)s - %(message)s gunicorn-20.1.0/examples/log_app.py000066400000000000000000000005641401157322000172270ustar00rootroot00000000000000import logging log = logging.getLogger(__name__) log.addHandler(logging.StreamHandler()) def app_factory(global_options, **local_options): return app def app(environ, start_response): start_response("200 OK", []) log.debug("Hello Debug!") log.info("Hello Info!") log.warn("Hello Warn!") log.error("Hello Error!") return [b"Hello World!\n"] gunicorn-20.1.0/examples/logging.conf000066400000000000000000000015111401157322000175220ustar00rootroot00000000000000[loggers] keys=root, gunicorn.error, gunicorn.access [handlers] keys=console, error_file, access_file [formatters] keys=generic, access [logger_root] level=INFO handlers=console [logger_gunicorn.error] level=INFO handlers=error_file propagate=1 qualname=gunicorn.error [logger_gunicorn.access] level=INFO handlers=access_file propagate=0 qualname=gunicorn.access [handler_console] class=StreamHandler formatter=generic args=(sys.stdout, ) [handler_error_file] class=logging.FileHandler formatter=generic args=('/tmp/gunicorn.error.log',) [handler_access_file] class=logging.FileHandler formatter=access args=('/tmp/gunicorn.access.log',) [formatter_generic] format=%(asctime)s [%(process)d] [%(levelname)s] %(message)s datefmt=%Y-%m-%d %H:%M:%S class=logging.Formatter [formatter_access] format=%(message)s class=logging.Formatter gunicorn-20.1.0/examples/longpoll.py000066400000000000000000000013411401157322000174260ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import sys import time class TestIter(object): def __iter__(self): lines = [b'line 1\n', b'line 2\n'] for line in lines: yield line time.sleep(20) def app(environ, start_response): """Application which cooperatively pauses 20 seconds (needed to surpass normal timeouts) before responding""" status = '200 OK' response_headers = [ ('Content-type', 'text/plain'), ('Transfer-Encoding', "chunked"), ] sys.stdout.write('request received') sys.stdout.flush() start_response(status, response_headers) return TestIter() gunicorn-20.1.0/examples/multiapp.py000066400000000000000000000026761401157322000174470ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. # # Run this application with: # # $ gunicorn multiapp:app # # And then visit: # # http://127.0.0.1:8000/app1url # http://127.0.0.1:8000/app2url # http://127.0.0.1:8000/this_is_a_404 # try: from routes import Mapper except ImportError: print("This example requires Routes to be installed") # Obviously you'd import your app callables # from different places... from test import app as app1 from test import app as app2 class Application(object): def __init__(self): self.map = Mapper() self.map.connect('app1', '/app1url', app=app1) self.map.connect('app2', '/app2url', app=app2) def __call__(self, environ, start_response): match = self.map.routematch(environ=environ) if not match: return self.error404(environ, start_response) return match[0]['app'](environ, start_response) def error404(self, environ, start_response): html = b"""\ 404 - Not Found

404 - Not Found

""" headers = [ ('Content-Type', 'text/html'), ('Content-Length', str(len(html))) ] start_response('404 Not Found', headers) return [html] app = Application() gunicorn-20.1.0/examples/multidomainapp.py000066400000000000000000000017411401157322000206270ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import re class SubDomainApp: """WSGI application to delegate requests based on domain name. """ def __init__(self, mapping): self.mapping = mapping def __call__(self, environ, start_response): host = environ.get("HTTP_HOST", "") host = host.split(":")[0] # strip port for pattern, app in self.mapping: if re.match("^" + pattern + "$", host): return app(environ, start_response) else: start_response("404 Not Found", []) return [b""] def hello(environ, start_response): start_response("200 OK", [("Content-Type", "text/plain")]) return [b"Hello, world\n"] def bye(environ, start_response): start_response("200 OK", [("Content-Type", "text/plain")]) return [b"Goodbye!\n"] app = SubDomainApp([ ("localhost", hello), (".*", bye) ]) gunicorn-20.1.0/examples/nginx.conf000066400000000000000000000036741401157322000172330ustar00rootroot00000000000000worker_processes 1; user nobody nogroup; # 'user nobody nobody;' for systems with 'nobody' as a group instead error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; # increase if you have lots of clients accept_mutex off; # set to 'on' if nginx worker_processes > 1 # 'use epoll;' to enable for Linux 2.6+ # 'use kqueue;' to enable for FreeBSD, OSX } http { include mime.types; # fallback in case we can't determine a type default_type application/octet-stream; access_log /var/log/nginx/access.log combined; sendfile on; upstream app_server { # fail_timeout=0 means we always retry an upstream even if it failed # to return a good HTTP response # for UNIX domain socket setups server unix:/tmp/gunicorn.sock fail_timeout=0; # for a TCP configuration # server 192.168.0.7:8000 fail_timeout=0; } server { # if no Host match, close the connection to prevent host spoofing listen 80 default_server; return 444; } server { # use 'listen 80 deferred;' for Linux # use 'listen 80 accept_filter=httpready;' for FreeBSD listen 80; client_max_body_size 4G; # set the correct host(s) for your site server_name example.com www.example.com; keepalive_timeout 5; # path for static files root /path/to/app/current/public; location / { # checks for static file, if not found proxy to app try_files $uri @proxy_to_app; } location @proxy_to_app { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; # we don't want nginx trying to do something clever with # redirects, we set the Host: header above already. proxy_redirect off; proxy_pass http://app_server; } error_page 500 502 503 504 /500.html; location = /500.html { root /path/to/app/current/public; } } } gunicorn-20.1.0/examples/read_django_settings.py000066400000000000000000000006231401157322000217570ustar00rootroot00000000000000""" Use this config file in your script like this: $ gunicorn project_name.wsgi:application -c read_django_settings.py """ settings_dict = {} with open('frameworks/django/testing/testing/settings.py') as f: exec(f.read(), settings_dict) loglevel = 'warning' proc_name = 'web-project' workers = 1 if settings_dict['DEBUG']: loglevel = 'debug' reload = True proc_name += '_debug' gunicorn-20.1.0/examples/readline_app.py000066400000000000000000000017671401157322000202370ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. # # Simple example of readline, reading from a stream then echoing the response # # Usage: # # Launch a server with the app in a terminal # # $ gunicorn -w3 readline_app:app # # Then in another terminal launch the following command: # # $ curl -XPOST -d'test\r\ntest2\r\n' -H"Transfer-Encoding: Chunked" http://localhost:8000 from gunicorn import __version__ def app(environ, start_response): """Simplest possible application object""" status = '200 OK' response_headers = [ ('Content-type', 'text/plain'), ('Transfer-Encoding', "chunked"), ('X-Gunicorn-Version', __version__) ] start_response(status, response_headers) body = environ['wsgi.input'] lines = [] while True: line = body.readline() if line == b"": break print(line) lines.append(line) return iter(lines) gunicorn-20.1.0/examples/sendfile.py000066400000000000000000000011101401157322000173630ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. # # Example code from Eventlet sources import os from wsgiref.validate import validator @validator def app(environ, start_response): """Simplest possible application object""" status = '200 OK' fname = os.path.join(os.path.dirname(__file__), "hello.txt") f = open(fname, 'rb') response_headers = [ ('Content-type', 'text/plain'), ] start_response(status, response_headers) return environ['wsgi.file_wrapper'](f) gunicorn-20.1.0/examples/server.crt000066400000000000000000000023511401157322000172500ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDdDCCAlwCCQC3MfdcOMwt6DANBgkqhkiG9w0BAQUFADB8MQswCQYDVQQGEwJG UjERMA8GA1UECBMIUGljYXJkaWUxDjAMBgNVBAcTBUNyZWlsMREwDwYDVQQKEwhn dW5pY29ybjEVMBMGA1UEAxMMZ3VuaWNvcm4ub3JnMSAwHgYJKoZIhvcNAQkBFhF1 c2VyQGd1bmljb3JuLm9yZzAeFw0xMjEyMTQwODI2MDJaFw0xMzEyMTQwODI2MDJa MHwxCzAJBgNVBAYTAkZSMREwDwYDVQQIEwhQaWNhcmRpZTEOMAwGA1UEBxMFQ3Jl aWwxETAPBgNVBAoTCGd1bmljb3JuMRUwEwYDVQQDEwxndW5pY29ybi5vcmcxIDAe BgkqhkiG9w0BCQEWEXVzZXJAZ3VuaWNvcm4ub3JnMIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEAy9RQSiGpB+HyjMpRCEfV9M/4g7gXq/qRizxDspJujoBz SW0d4FqMHaSRX2QOA+euhtlOYTgsvWZcyv5cvDfL1CtrNWSVBrlo7wIy5tg60Z3A JnWT/Zxj4WIqkPwdglB1sRBsI1Fn0o6nJu4HekZedXDK6fua4lOPfsQG84EhRQKS Mz2o7Nesk8/UMjb+5WoRmG7mxrpe0/OYlnydqzqwHUQ+I5CHl1kOhePo9ZBTFMA5 Ece8kGQs37rFCEy92xCYHDgp+CjjyYbeBskF3o0/a88K2bt8J7uXkn4h14HjtFHq fYnqn60cwyIx3T/uMUh6EmhKQezaw60xyIivmjH8tQIDAQABMA0GCSqGSIb3DQEB BQUAA4IBAQAKu7kzTAqONFI1qC6mnwAixSd7ml6RtyQRiIWjg4FyTJmS2NMlqUSI CiV1g1+pv2cy9amld8hoO17ISYFZqMoRxJgD5GuN4y1lUefFe95GHI9loubIJZlR 5KlZEvCiaAQoGvYiacf4BNkljyrwgPVM5e71dGon7jyghmV6yUaUL6+1J8BU/KYg jz8RtMtptqkwKPKQVfuDcr/eoH6uZwPRbyfqSui8SuMz3Df6Dnx1hOtlQRJC6eNo U9L3jkmCsbbMNBAz6iQjyFHFa9iqzwL7nvqZTryjmI5Dpn+BnT7Q5cduK+N5vt4+ RjNVrz/l6+nR68B5GO96zUTV3/KrEmFr -----END CERTIFICATE----- gunicorn-20.1.0/examples/server.key000066400000000000000000000032171401157322000172520ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAy9RQSiGpB+HyjMpRCEfV9M/4g7gXq/qRizxDspJujoBzSW0d 4FqMHaSRX2QOA+euhtlOYTgsvWZcyv5cvDfL1CtrNWSVBrlo7wIy5tg60Z3AJnWT /Zxj4WIqkPwdglB1sRBsI1Fn0o6nJu4HekZedXDK6fua4lOPfsQG84EhRQKSMz2o 7Nesk8/UMjb+5WoRmG7mxrpe0/OYlnydqzqwHUQ+I5CHl1kOhePo9ZBTFMA5Ece8 kGQs37rFCEy92xCYHDgp+CjjyYbeBskF3o0/a88K2bt8J7uXkn4h14HjtFHqfYnq n60cwyIx3T/uMUh6EmhKQezaw60xyIivmjH8tQIDAQABAoIBAQDFzhTc3C2daLhp yS06S/xmyCz0JwNR8qir5qAL++8ue5ll+G61+yle2wX4/LBdOck1NE3MKye/5kbG +HImdj9od3pjJmk5TVV4HToorE7ofZ6rtA8aX1rOruWALiq0/EA6xSUsYSPQQoAU V4sKLqAceIly6Kk2WsE21CWqyfXvcQOtfBYmFwmPCImWecJLypeheEpz1U2EYl65 u6b0NsXeODrLXEAEFjdb6UBJtzRtTJ/OnbDvghu9xMjlT0Pj+inoAv/ePZB8bmvH XGhZo7dzgsDZ+eys7XnbeggUOhFImzCjO1f3pIVXWThGDgKIrpc9Evac2Q3AjTFY NV9HBV9BAoGBAOyWq7HDgeCEu54orPAmdkO4j/HFX+262BTQoVCg4OX3Iv9A/lH4 lpVGrFlK0qJF9jb7mjDmXP2LW0fwzyHe42DGFbZkKdfiMBuE+qoPeAV9s+SjE4H3 l3tHoAOFUt2wITcHK4EYjoLMAgrbRNiv9t/gqiMm1oIb3fkUbpOoGG3LAoGBANyN kLop3JfN1Kzto7gJq/tLS21joexTU+s4EJ+a4Q8KH1d47DLI+4119Ot+NWwi9X3S sbOKZOjfrGw9+HPI64i7hD9HPSK58IUbsfVR9vPlPei45inRfi6s7+EUzKifOKZU o1ecpOSPYQHZtDToGcQCTS0IFwMXHgrkP380We9/AoGBAI1wljyz8RVUxQWMs7bu h4187TFRGkR5i20GPSqCw3E4CkgnhuNihkO/+JF5VeuFf+jnCgtp7PX3Nh8QLATH x4+3XIup3goeQzxwh5rbnJlLyRxLEgKFDp6490SjlCLMhU7sjmmjUK+JXz82TzZs HF9DZPOW6G7oUg/y0xibSd95AoGAXpDEcU3pq50xh0QNYqei+gh6uthxYScJYF2V oxmBTjWE4riSbeQHF8xvy1k+BrOmluB0GQtJ4R+minK3yM1pUCM2vPsKl40qN6h8 UTdnr4OnW9WLunp8o/66i8OjTNmYLJk1wCcF/IoNigGSZuztv0FNXfWOCGEtHHZp U11bAnkCgYEAoU0sdFL3IfmxnNQ9CDmgXdJM0SpUm4ECd2jM/fRdgLelL+WislCF gHjZw+3mplIzqQ9DMwRkjbaIxP0H92OopOBIqmShWUuzWw/Dj0L8PGe/7skcwsGD /VLEkGzrxJwP4kokUu1kvLOqHM429JXsb8wO16iMQAB93yUZ+X8PGfQ= -----END RSA PRIVATE KEY----- gunicorn-20.1.0/examples/slowclient.py000066400000000000000000000011341401157322000177630ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import sys import time def app(environ, start_response): """Application which cooperatively pauses 10 seconds before responding""" data = b'Hello, World!\n' status = '200 OK' response_headers = [ ('Content-type', 'text/plain'), ('Content-Length', str(len(data))), ] sys.stdout.write('request received, pausing 10 seconds') sys.stdout.flush() time.sleep(10) start_response(status, response_headers) return iter([data]) gunicorn-20.1.0/examples/standalone_app.py000066400000000000000000000024731401157322000205770ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # An example of a standalone application using the internal API of Gunicorn. # # $ python standalone_app.py # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import multiprocessing import gunicorn.app.base def number_of_workers(): return (multiprocessing.cpu_count() * 2) + 1 def handler_app(environ, start_response): response_body = b'Works fine' status = '200 OK' response_headers = [ ('Content-Type', 'text/plain'), ] start_response(status, response_headers) return [response_body] class StandaloneApplication(gunicorn.app.base.BaseApplication): def __init__(self, app, options=None): self.options = options or {} self.application = app super().__init__() def load_config(self): config = {key: value for key, value in self.options.items() if key in self.cfg.settings and value is not None} for key, value in config.items(): self.cfg.set(key.lower(), value) def load(self): return self.application if __name__ == '__main__': options = { 'bind': '%s:%s' % ('127.0.0.1', '8080'), 'workers': number_of_workers(), } StandaloneApplication(handler_app, options).run() gunicorn-20.1.0/examples/supervisor.conf000066400000000000000000000002661401157322000203230ustar00rootroot00000000000000[program:gunicorn] command=/usr/local/bin/gunicorn main:application -c /path/to/project/gunicorn.conf.py directory=/path/to/project user=nobody autorestart=true redirect_stderr=true gunicorn-20.1.0/examples/test.py000066400000000000000000000012231401157322000165560ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. # # Example code from Eventlet sources from wsgiref.validate import validator from gunicorn import __version__ @validator def app(environ, start_response): """Simplest possible application object""" data = b'Hello, World!\n' status = '200 OK' response_headers = [ ('Content-type', 'text/plain'), ('Content-Length', str(len(data))), ('X-Gunicorn-Version', __version__), ('Foo', 'B\u00e5r'), # Foo: Bår ] start_response(status, response_headers) return iter([data]) gunicorn-20.1.0/examples/timeout.py000066400000000000000000000011541401157322000172700ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import sys import time def app(environ, start_response): """Application which pauses 35 seconds before responding. the worker will timeout in default case.""" data = b'Hello, World!\n' status = '200 OK' response_headers = [ ('Content-type', 'text/plain'), ('Content-Length', str(len(data))), ] sys.stdout.write('request will timeout') sys.stdout.flush() time.sleep(35) start_response(status, response_headers) return iter([data]) gunicorn-20.1.0/examples/websocket/000077500000000000000000000000001401157322000172155ustar00rootroot00000000000000gunicorn-20.1.0/examples/websocket/gevent_websocket.py000066400000000000000000000365041401157322000231350ustar00rootroot00000000000000 import collections import errno import re import hashlib import base64 from base64 import b64encode, b64decode import socket import struct import logging from socket import error as SocketError import gevent from gunicorn.workers.base_async import ALREADY_HANDLED logger = logging.getLogger(__name__) WS_KEY = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11" class WebSocketWSGI(object): def __init__(self, handler): self.handler = handler def verify_client(self, ws): pass def _get_key_value(self, key_value): if not key_value: return key_number = int(re.sub("\\D", "", key_value)) spaces = re.subn(" ", "", key_value)[1] if key_number % spaces != 0: return part = key_number / spaces return part def __call__(self, environ, start_response): if not (environ.get('HTTP_CONNECTION').find('Upgrade') != -1 and environ['HTTP_UPGRADE'].lower() == 'websocket'): # need to check a few more things here for true compliance start_response('400 Bad Request', [('Connection','close')]) return [] sock = environ['gunicorn.socket'] version = environ.get('HTTP_SEC_WEBSOCKET_VERSION') ws = WebSocket(sock, environ, version) handshake_reply = ("HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n") key = environ.get('HTTP_SEC_WEBSOCKET_KEY') if key: ws_key = base64.b64decode(key) if len(ws_key) != 16: start_response('400 Bad Request', [('Connection','close')]) return [] protocols = [] subprotocols = environ.get('HTTP_SEC_WEBSOCKET_PROTOCOL') ws_protocols = [] if subprotocols: for s in subprotocols.split(','): s = s.strip() if s in protocols: ws_protocols.append(s) if ws_protocols: handshake_reply += 'Sec-WebSocket-Protocol: %s\r\n' % ', '.join(ws_protocols) exts = [] extensions = environ.get('HTTP_SEC_WEBSOCKET_EXTENSIONS') ws_extensions = [] if extensions: for ext in extensions.split(','): ext = ext.strip() if ext in exts: ws_extensions.append(ext) if ws_extensions: handshake_reply += 'Sec-WebSocket-Extensions: %s\r\n' % ', '.join(ws_extensions) key_hash = hashlib.sha1() key_hash.update(key.encode()) key_hash.update(WS_KEY) handshake_reply += ( "Sec-WebSocket-Origin: %s\r\n" "Sec-WebSocket-Location: ws://%s%s\r\n" "Sec-WebSocket-Version: %s\r\n" "Sec-WebSocket-Accept: %s\r\n\r\n" % ( environ.get('HTTP_ORIGIN'), environ.get('HTTP_HOST'), ws.path, version, base64.b64encode(key_hash.digest()).decode() )) else: handshake_reply += ( "WebSocket-Origin: %s\r\n" "WebSocket-Location: ws://%s%s\r\n\r\n" % ( environ.get('HTTP_ORIGIN'), environ.get('HTTP_HOST'), ws.path)) sock.sendall(handshake_reply.encode()) try: self.handler(ws) except BrokenPipeError: pass else: raise # use this undocumented feature of grainbows to ensure that it # doesn't barf on the fact that we didn't call start_response return ALREADY_HANDLED class WebSocket(object): """A websocket object that handles the details of serialization/deserialization to the socket. The primary way to interact with a :class:`WebSocket` object is to call :meth:`send` and :meth:`wait` in order to pass messages back and forth with the browser. Also available are the following properties: path The path value of the request. This is the same as the WSGI PATH_INFO variable, but more convenient. protocol The value of the Websocket-Protocol header. origin The value of the 'Origin' header. environ The full WSGI environment for this request. """ def __init__(self, sock, environ, version=76): """ :param socket: The eventlet socket :type socket: :class:`eventlet.greenio.GreenSocket` :param environ: The wsgi environment :param version: The WebSocket spec version to follow (default is 76) """ self.socket = sock self.origin = environ.get('HTTP_ORIGIN') self.protocol = environ.get('HTTP_WEBSOCKET_PROTOCOL') self.path = environ.get('PATH_INFO') self.environ = environ self.version = version self.websocket_closed = False self._buf = "" self._msgs = collections.deque() #self._sendlock = semaphore.Semaphore() @staticmethod def encode_hybi(buf, opcode, base64=False): """ Encode a HyBi style WebSocket frame. Optional opcode: 0x0 - continuation 0x1 - text frame (base64 encode buf) 0x2 - binary frame (use raw buf) 0x8 - connection close 0x9 - ping 0xA - pong """ if base64: buf = b64encode(buf) else: buf = buf.encode() b1 = 0x80 | (opcode & 0x0f) # FIN + opcode payload_len = len(buf) if payload_len <= 125: header = struct.pack('>BB', b1, payload_len) elif payload_len > 125 and payload_len < 65536: header = struct.pack('>BBH', b1, 126, payload_len) elif payload_len >= 65536: header = struct.pack('>BBQ', b1, 127, payload_len) #print("Encoded: %s" % repr(header + buf)) return header + buf, len(header), 0 @staticmethod def decode_hybi(buf, base64=False): """ Decode HyBi style WebSocket packets. Returns: {'fin' : 0_or_1, 'opcode' : number, 'mask' : 32_bit_number, 'hlen' : header_bytes_number, 'length' : payload_bytes_number, 'payload' : decoded_buffer, 'left' : bytes_left_number, 'close_code' : number, 'close_reason' : string} """ f = {'fin' : 0, 'opcode' : 0, 'mask' : 0, 'hlen' : 2, 'length' : 0, 'payload' : None, 'left' : 0, 'close_code' : None, 'close_reason' : None} blen = len(buf) f['left'] = blen if blen < f['hlen']: return f # Incomplete frame header b1, b2 = struct.unpack_from(">BB", buf) f['opcode'] = b1 & 0x0f f['fin'] = (b1 & 0x80) >> 7 has_mask = (b2 & 0x80) >> 7 f['length'] = b2 & 0x7f if f['length'] == 126: f['hlen'] = 4 if blen < f['hlen']: return f # Incomplete frame header (f['length'],) = struct.unpack_from('>xxH', buf) elif f['length'] == 127: f['hlen'] = 10 if blen < f['hlen']: return f # Incomplete frame header (f['length'],) = struct.unpack_from('>xxQ', buf) full_len = f['hlen'] + has_mask * 4 + f['length'] if blen < full_len: # Incomplete frame return f # Incomplete frame header # Number of bytes that are part of the next frame(s) f['left'] = blen - full_len # Process 1 frame if has_mask: # unmask payload f['mask'] = buf[f['hlen']:f['hlen']+4] b = c = '' if f['length'] >= 4: data = struct.unpack('= 2: f['close_code'] = struct.unpack_from(">H", f['payload']) if f['length'] > 3: f['close_reason'] = f['payload'][2:] return f @staticmethod def _pack_message(message): """Pack the message inside ``00`` and ``FF`` As per the dataframing section (5.3) for the websocket spec """ if isinstance(message, str): message = message.encode('utf-8') packed = "\x00%s\xFF" % message return packed def _parse_messages(self): """ Parses for messages in the buffer *buf*. It is assumed that the buffer contains the start character for a message, but that it may contain only part of the rest of the message. Returns an array of messages, and the buffer remainder that didn't contain any full messages.""" msgs = [] end_idx = 0 buf = self._buf while buf: if self.version in ['7', '8', '13']: frame = self.decode_hybi(buf, base64=False) #print("Received buf: %s, frame: %s" % (repr(buf), frame)) if frame['payload'] == None: break else: if frame['opcode'] == 0x8: # connection close self.websocket_closed = True break #elif frame['opcode'] == 0x1: else: msgs.append(frame['payload']); #msgs.append(frame['payload'].decode('utf-8', 'replace')); #buf = buf[-frame['left']:] if frame['left']: buf = buf[-frame['left']:] else: buf = '' else: frame_type = ord(buf[0]) if frame_type == 0: # Normal message. end_idx = buf.find("\xFF") if end_idx == -1: #pragma NO COVER break msgs.append(buf[1:end_idx].decode('utf-8', 'replace')) buf = buf[end_idx+1:] elif frame_type == 255: # Closing handshake. assert ord(buf[1]) == 0, "Unexpected closing handshake: %r" % buf self.websocket_closed = True break else: raise ValueError("Don't understand how to parse this type of message: %r" % buf) self._buf = buf return msgs def send(self, message): """Send a message to the browser. *message* should be convertible to a string; unicode objects should be encodable as utf-8. Raises socket.error with errno of 32 (broken pipe) if the socket has already been closed by the client.""" if self.version in ['7', '8', '13']: packed, lenhead, lentail = self.encode_hybi(message, opcode=0x01, base64=False) else: packed = self._pack_message(message) # if two greenthreads are trying to send at the same time # on the same socket, sendlock prevents interleaving and corruption #self._sendlock.acquire() try: self.socket.sendall(packed) finally: pass #self._sendlock.release() def wait(self): """Waits for and deserializes messages. Returns a single message; the oldest not yet processed. If the client has already closed the connection, returns None. This is different from normal socket behavior because the empty string is a valid websocket message.""" while not self._msgs: # Websocket might be closed already. if self.websocket_closed: return None # no parsed messages, must mean buf needs more data delta = self.socket.recv(8096) if delta == b'': return None self._buf += delta msgs = self._parse_messages() self._msgs.extend(msgs) return self._msgs.popleft() def _send_closing_frame(self, ignore_send_errors=False): """Sends the closing frame to the client, if required.""" if self.version in ['7', '8', '13'] and not self.websocket_closed: msg = '' #if code != None: # msg = struct.pack(">H%ds" % (len(reason)), code) buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False) self.socket.sendall(buf) self.websocket_closed = True elif self.version == 76 and not self.websocket_closed: try: self.socket.sendall(b"\xff\x00") except SocketError: # Sometimes, like when the remote side cuts off the connection, # we don't care about this. if not ignore_send_errors: #pragma NO COVER raise self.websocket_closed = True def close(self): """Forcibly close the websocket; generally it is preferable to return from the handler method.""" self._send_closing_frame() self.socket.shutdown(True) self.socket.close() # demo app import os import random def handle(ws): """ This is the websocket handler function. Note that we can dispatch based on path in here, too.""" if ws.path == '/echo': while True: m = ws.wait() if m is None: break ws.send(m) elif ws.path == '/data': for i in range(10000): ws.send("0 %s %s\n" % (i, random.random())) gevent.sleep(0.1) wsapp = WebSocketWSGI(handle) def app(environ, start_response): """ This resolves to the web page or the websocket depending on the path.""" if environ['PATH_INFO'] == '/' or environ['PATH_INFO'] == "": data = open(os.path.join( os.path.dirname(__file__), 'websocket.html')).read() data = data % environ start_response('200 OK', [('Content-Type', 'text/html'), ('Content-Length', str(len(data)))]) return [data.encode()] else: return wsapp(environ, start_response) gunicorn-20.1.0/examples/websocket/websocket.html000066400000000000000000000024501401157322000220720ustar00rootroot00000000000000

Plot

gunicorn-20.1.0/examples/websocket/websocket.py000066400000000000000000000365611401157322000215700ustar00rootroot00000000000000 import collections import errno import re from hashlib import md5, sha1 import base64 from base64 import b64encode, b64decode import socket import struct import logging from socket import error as SocketError import eventlet from gunicorn.workers.base_async import ALREADY_HANDLED from eventlet import pools logger = logging.getLogger(__name__) WS_KEY = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11" class WebSocketWSGI(object): def __init__(self, handler): self.handler = handler def verify_client(self, ws): pass def _get_key_value(self, key_value): if not key_value: return key_number = int(re.sub("\\D", "", key_value)) spaces = re.subn(" ", "", key_value)[1] if key_number % spaces != 0: return part = key_number / spaces return part def __call__(self, environ, start_response): if not (environ.get('HTTP_CONNECTION').find('Upgrade') != -1 and environ['HTTP_UPGRADE'].lower() == 'websocket'): # need to check a few more things here for true compliance start_response('400 Bad Request', [('Connection','close')]) return [] sock = environ['gunicorn.socket'] version = environ.get('HTTP_SEC_WEBSOCKET_VERSION') ws = WebSocket(sock, environ, version) handshake_reply = ("HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n") key = environ.get('HTTP_SEC_WEBSOCKET_KEY') if key: ws_key = base64.b64decode(key) if len(ws_key) != 16: start_response('400 Bad Request', [('Connection','close')]) return [] protocols = [] subprotocols = environ.get('HTTP_SEC_WEBSOCKET_PROTOCOL') ws_protocols = [] if subprotocols: for s in subprotocols.split(','): s = s.strip() if s in protocols: ws_protocols.append(s) if ws_protocols: handshake_reply += 'Sec-WebSocket-Protocol: %s\r\n' % ', '.join(ws_protocols) exts = [] extensions = environ.get('HTTP_SEC_WEBSOCKET_EXTENSIONS') ws_extensions = [] if extensions: for ext in extensions.split(','): ext = ext.strip() if ext in exts: ws_extensions.append(ext) if ws_extensions: handshake_reply += 'Sec-WebSocket-Extensions: %s\r\n' % ', '.join(ws_extensions) key_hash = sha1() key_hash.update(key.encode()) key_hash.update(WS_KEY) handshake_reply += ( "Sec-WebSocket-Origin: %s\r\n" "Sec-WebSocket-Location: ws://%s%s\r\n" "Sec-WebSocket-Version: %s\r\n" "Sec-WebSocket-Accept: %s\r\n\r\n" % ( environ.get('HTTP_ORIGIN'), environ.get('HTTP_HOST'), ws.path, version, base64.b64encode(key_hash.digest()).decode() )) else: handshake_reply += ( "WebSocket-Origin: %s\r\n" "WebSocket-Location: ws://%s%s\r\n\r\n" % ( environ.get('HTTP_ORIGIN'), environ.get('HTTP_HOST'), ws.path)) sock.sendall(handshake_reply.encode()) try: self.handler(ws) except BrokenPipeError: pass else: raise # use this undocumented feature of grainbows to ensure that it # doesn't barf on the fact that we didn't call start_response return ALREADY_HANDLED class WebSocket(object): """A websocket object that handles the details of serialization/deserialization to the socket. The primary way to interact with a :class:`WebSocket` object is to call :meth:`send` and :meth:`wait` in order to pass messages back and forth with the browser. Also available are the following properties: path The path value of the request. This is the same as the WSGI PATH_INFO variable, but more convenient. protocol The value of the Websocket-Protocol header. origin The value of the 'Origin' header. environ The full WSGI environment for this request. """ def __init__(self, sock, environ, version=76): """ :param socket: The eventlet socket :type socket: :class:`eventlet.greenio.GreenSocket` :param environ: The wsgi environment :param version: The WebSocket spec version to follow (default is 76) """ self.socket = sock self.origin = environ.get('HTTP_ORIGIN') self.protocol = environ.get('HTTP_WEBSOCKET_PROTOCOL') self.path = environ.get('PATH_INFO') self.environ = environ self.version = version self.websocket_closed = False self._buf = "" self._msgs = collections.deque() self._sendlock = pools.TokenPool(1) @staticmethod def encode_hybi(buf, opcode, base64=False): """ Encode a HyBi style WebSocket frame. Optional opcode: 0x0 - continuation 0x1 - text frame (base64 encode buf) 0x2 - binary frame (use raw buf) 0x8 - connection close 0x9 - ping 0xA - pong """ if base64: buf = b64encode(buf) else: buf = buf.encode() b1 = 0x80 | (opcode & 0x0f) # FIN + opcode payload_len = len(buf) if payload_len <= 125: header = struct.pack('>BB', b1, payload_len) elif payload_len > 125 and payload_len < 65536: header = struct.pack('>BBH', b1, 126, payload_len) elif payload_len >= 65536: header = struct.pack('>BBQ', b1, 127, payload_len) #print("Encoded: %s" % repr(header + buf)) return header + buf, len(header), 0 @staticmethod def decode_hybi(buf, base64=False): """ Decode HyBi style WebSocket packets. Returns: {'fin' : 0_or_1, 'opcode' : number, 'mask' : 32_bit_number, 'hlen' : header_bytes_number, 'length' : payload_bytes_number, 'payload' : decoded_buffer, 'left' : bytes_left_number, 'close_code' : number, 'close_reason' : string} """ f = {'fin' : 0, 'opcode' : 0, 'mask' : 0, 'hlen' : 2, 'length' : 0, 'payload' : None, 'left' : 0, 'close_code' : None, 'close_reason' : None} blen = len(buf) f['left'] = blen if blen < f['hlen']: return f # Incomplete frame header b1, b2 = struct.unpack_from(">BB", buf) f['opcode'] = b1 & 0x0f f['fin'] = (b1 & 0x80) >> 7 has_mask = (b2 & 0x80) >> 7 f['length'] = b2 & 0x7f if f['length'] == 126: f['hlen'] = 4 if blen < f['hlen']: return f # Incomplete frame header (f['length'],) = struct.unpack_from('>xxH', buf) elif f['length'] == 127: f['hlen'] = 10 if blen < f['hlen']: return f # Incomplete frame header (f['length'],) = struct.unpack_from('>xxQ', buf) full_len = f['hlen'] + has_mask * 4 + f['length'] if blen < full_len: # Incomplete frame return f # Incomplete frame header # Number of bytes that are part of the next frame(s) f['left'] = blen - full_len # Process 1 frame if has_mask: # unmask payload f['mask'] = buf[f['hlen']:f['hlen']+4] b = c = '' if f['length'] >= 4: data = struct.unpack('= 2: f['close_code'] = struct.unpack_from(">H", f['payload']) if f['length'] > 3: f['close_reason'] = f['payload'][2:] return f @staticmethod def _pack_message(message): """Pack the message inside ``00`` and ``FF`` As per the dataframing section (5.3) for the websocket spec """ if isinstance(message, str): message = message.encode('utf-8') packed = "\x00%s\xFF" % message return packed def _parse_messages(self): """ Parses for messages in the buffer *buf*. It is assumed that the buffer contains the start character for a message, but that it may contain only part of the rest of the message. Returns an array of messages, and the buffer remainder that didn't contain any full messages.""" msgs = [] end_idx = 0 buf = self._buf while buf: if self.version in ['7', '8', '13']: frame = self.decode_hybi(buf, base64=False) #print("Received buf: %s, frame: %s" % (repr(buf), frame)) if frame['payload'] == None: break else: if frame['opcode'] == 0x8: # connection close self.websocket_closed = True break #elif frame['opcode'] == 0x1: else: msgs.append(frame['payload']); #msgs.append(frame['payload'].decode('utf-8', 'replace')); #buf = buf[-frame['left']:] if frame['left']: buf = buf[-frame['left']:] else: buf = '' else: frame_type = ord(buf[0]) if frame_type == 0: # Normal message. end_idx = buf.find("\xFF") if end_idx == -1: #pragma NO COVER break msgs.append(buf[1:end_idx].decode('utf-8', 'replace')) buf = buf[end_idx+1:] elif frame_type == 255: # Closing handshake. assert ord(buf[1]) == 0, "Unexpected closing handshake: %r" % buf self.websocket_closed = True break else: raise ValueError("Don't understand how to parse this type of message: %r" % buf) self._buf = buf return msgs def send(self, message): """Send a message to the browser. *message* should be convertible to a string; unicode objects should be encodable as utf-8. Raises socket.error with errno of 32 (broken pipe) if the socket has already been closed by the client.""" if self.version in ['7', '8', '13']: packed, lenhead, lentail = self.encode_hybi(message, opcode=0x01, base64=False) else: packed = self._pack_message(message) # if two greenthreads are trying to send at the same time # on the same socket, sendlock prevents interleaving and corruption #self._sendlock.acquire() t = self._sendlock.get() try: self.socket.sendall(packed) finally: self._sendlock.put(t) def wait(self): """Waits for and deserializes messages. Returns a single message; the oldest not yet processed. If the client has already closed the connection, returns None. This is different from normal socket behavior because the empty string is a valid websocket message.""" while not self._msgs: # Websocket might be closed already. if self.websocket_closed: return None # no parsed messages, must mean buf needs more data delta = self.socket.recv(8096) if delta == b'': return None self._buf += delta msgs = self._parse_messages() self._msgs.extend(msgs) return self._msgs.popleft() def _send_closing_frame(self, ignore_send_errors=False): """Sends the closing frame to the client, if required.""" if self.version in ['7', '8', '13'] and not self.websocket_closed: msg = '' #if code != None: # msg = struct.pack(">H%ds" % (len(reason)), code) buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False) self.socket.sendall(buf) self.websocket_closed = True elif self.version == 76 and not self.websocket_closed: try: self.socket.sendall(b"\xff\x00") except SocketError: # Sometimes, like when the remote side cuts off the connection, # we don't care about this. if not ignore_send_errors: #pragma NO COVER raise self.websocket_closed = True def close(self): """Forcibly close the websocket; generally it is preferable to return from the handler method.""" self._send_closing_frame() self.socket.shutdown(True) self.socket.close() # demo app import os import random def handle(ws): """ This is the websocket handler function. Note that we can dispatch based on path in here, too.""" if ws.path == '/echo': while True: m = ws.wait() if m is None: break ws.send(m) elif ws.path == '/data': for i in range(10000): ws.send("0 %s %s\n" % (i, random.random())) eventlet.sleep(0.1) wsapp = WebSocketWSGI(handle) def app(environ, start_response): """ This resolves to the web page or the websocket depending on the path.""" if environ['PATH_INFO'] == '/' or environ['PATH_INFO'] == "": data = open(os.path.join( os.path.dirname(__file__), 'websocket.html')).read() data = data % environ start_response('200 OK', [('Content-Type', 'text/html'), ('Content-Length', str(len(data)))]) return [data.encode()] else: return wsapp(environ, start_response) gunicorn-20.1.0/examples/when_ready.conf.py000066400000000000000000000017741401157322000206630ustar00rootroot00000000000000import signal import commands import threading import time max_mem = 100000 class MemoryWatch(threading.Thread): def __init__(self, server, max_mem): super().__init__() self.daemon = True self.server = server self.max_mem = max_mem self.timeout = server.timeout / 2 def memory_usage(self, pid): try: out = commands.getoutput("ps -o rss -p %s" % pid) except IOError: return -1 used_mem = sum(int(x) for x in out.split('\n')[1:]) return used_mem def run(self): while True: for (pid, worker) in list(self.server.WORKERS.items()): if self.memory_usage(pid) > self.max_mem: self.server.log.info("Pid %s killed (memory usage > %s)", pid, self.max_mem) self.server.kill_worker(pid, signal.SIGTERM) time.sleep(self.timeout) def when_ready(server): mw = MemoryWatch(server, max_mem) mw.start() gunicorn-20.1.0/gunicorn/000077500000000000000000000000001401157322000152355ustar00rootroot00000000000000gunicorn-20.1.0/gunicorn/__init__.py000066400000000000000000000004271401157322000173510ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. version_info = (20, 1, 0) __version__ = ".".join([str(v) for v in version_info]) SERVER = "gunicorn" SERVER_SOFTWARE = "%s/%s" % (SERVER, __version__) gunicorn-20.1.0/gunicorn/__main__.py000066400000000000000000000002531401157322000173270ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. from gunicorn.app.wsgiapp import run run() gunicorn-20.1.0/gunicorn/app/000077500000000000000000000000001401157322000160155ustar00rootroot00000000000000gunicorn-20.1.0/gunicorn/app/__init__.py000066400000000000000000000001771401157322000201330ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. gunicorn-20.1.0/gunicorn/app/base.py000066400000000000000000000157561401157322000173170ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import importlib.util import importlib.machinery import os import sys import traceback from gunicorn import util from gunicorn.arbiter import Arbiter from gunicorn.config import Config, get_default_config_file from gunicorn import debug class BaseApplication(object): """ An application interface for configuring and loading the various necessities for any given web framework. """ def __init__(self, usage=None, prog=None): self.usage = usage self.cfg = None self.callable = None self.prog = prog self.logger = None self.do_load_config() def do_load_config(self): """ Loads the configuration """ try: self.load_default_config() self.load_config() except Exception as e: print("\nError: %s" % str(e), file=sys.stderr) sys.stderr.flush() sys.exit(1) def load_default_config(self): # init configuration self.cfg = Config(self.usage, prog=self.prog) def init(self, parser, opts, args): raise NotImplementedError def load(self): raise NotImplementedError def load_config(self): """ This method is used to load the configuration from one or several input(s). Custom Command line, configuration file. You have to override this method in your class. """ raise NotImplementedError def reload(self): self.do_load_config() if self.cfg.spew: debug.spew() def wsgi(self): if self.callable is None: self.callable = self.load() return self.callable def run(self): try: Arbiter(self).run() except RuntimeError as e: print("\nError: %s\n" % e, file=sys.stderr) sys.stderr.flush() sys.exit(1) class Application(BaseApplication): # 'init' and 'load' methods are implemented by WSGIApplication. # pylint: disable=abstract-method def chdir(self): # chdir to the configured path before loading, # default is the current dir os.chdir(self.cfg.chdir) # add the path to sys.path if self.cfg.chdir not in sys.path: sys.path.insert(0, self.cfg.chdir) def get_config_from_filename(self, filename): if not os.path.exists(filename): raise RuntimeError("%r doesn't exist" % filename) ext = os.path.splitext(filename)[1] try: module_name = '__config__' if ext in [".py", ".pyc"]: spec = importlib.util.spec_from_file_location(module_name, filename) else: msg = "configuration file should have a valid Python extension.\n" util.warn(msg) loader_ = importlib.machinery.SourceFileLoader(module_name, filename) spec = importlib.util.spec_from_file_location(module_name, filename, loader=loader_) mod = importlib.util.module_from_spec(spec) sys.modules[module_name] = mod spec.loader.exec_module(mod) except Exception: print("Failed to read config file: %s" % filename, file=sys.stderr) traceback.print_exc() sys.stderr.flush() sys.exit(1) return vars(mod) def get_config_from_module_name(self, module_name): return vars(importlib.import_module(module_name)) def load_config_from_module_name_or_filename(self, location): """ Loads the configuration file: the file is a python file, otherwise raise an RuntimeError Exception or stop the process if the configuration file contains a syntax error. """ if location.startswith("python:"): module_name = location[len("python:"):] cfg = self.get_config_from_module_name(module_name) else: if location.startswith("file:"): filename = location[len("file:"):] else: filename = location cfg = self.get_config_from_filename(filename) for k, v in cfg.items(): # Ignore unknown names if k not in self.cfg.settings: continue try: self.cfg.set(k.lower(), v) except Exception: print("Invalid value for %s: %s\n" % (k, v), file=sys.stderr) sys.stderr.flush() raise return cfg def load_config_from_file(self, filename): return self.load_config_from_module_name_or_filename(location=filename) def load_config(self): # parse console args parser = self.cfg.parser() args = parser.parse_args() # optional settings from apps cfg = self.init(parser, args, args.args) # set up import paths and follow symlinks self.chdir() # Load up the any app specific configuration if cfg: for k, v in cfg.items(): self.cfg.set(k.lower(), v) env_args = parser.parse_args(self.cfg.get_cmd_args_from_env()) if args.config: self.load_config_from_file(args.config) elif env_args.config: self.load_config_from_file(env_args.config) else: default_config = get_default_config_file() if default_config is not None: self.load_config_from_file(default_config) # Load up environment configuration for k, v in vars(env_args).items(): if v is None: continue if k == "args": continue self.cfg.set(k.lower(), v) # Lastly, update the configuration with any command line settings. for k, v in vars(args).items(): if v is None: continue if k == "args": continue self.cfg.set(k.lower(), v) # current directory might be changed by the config now # set up import paths and follow symlinks self.chdir() def run(self): if self.cfg.print_config: print(self.cfg) if self.cfg.print_config or self.cfg.check_config: try: self.load() except Exception: msg = "\nError while loading the application:\n" print(msg, file=sys.stderr) traceback.print_exc() sys.stderr.flush() sys.exit(1) sys.exit(0) if self.cfg.spew: debug.spew() if self.cfg.daemon: util.daemonize(self.cfg.enable_stdio_inheritance) # set python paths if self.cfg.pythonpath: paths = self.cfg.pythonpath.split(",") for path in paths: pythonpath = os.path.abspath(path) if pythonpath not in sys.path: sys.path.insert(0, pythonpath) super().run() gunicorn-20.1.0/gunicorn/app/pasterapp.py000066400000000000000000000037661401157322000204020ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import configparser import os from paste.deploy import loadapp from gunicorn.app.wsgiapp import WSGIApplication from gunicorn.config import get_default_config_file def get_wsgi_app(config_uri, name=None, defaults=None): if ':' not in config_uri: config_uri = "config:%s" % config_uri return loadapp( config_uri, name=name, relative_to=os.getcwd(), global_conf=defaults, ) def has_logging_config(config_file): parser = configparser.ConfigParser() parser.read([config_file]) return parser.has_section('loggers') def serve(app, global_conf, **local_conf): """\ A Paste Deployment server runner. Example configuration: [server:main] use = egg:gunicorn#main host = 127.0.0.1 port = 5000 """ config_file = global_conf['__file__'] gunicorn_config_file = local_conf.pop('config', None) host = local_conf.pop('host', '') port = local_conf.pop('port', '') if host and port: local_conf['bind'] = '%s:%s' % (host, port) elif host: local_conf['bind'] = host.split(',') class PasterServerApplication(WSGIApplication): def load_config(self): self.cfg.set("default_proc_name", config_file) if has_logging_config(config_file): self.cfg.set("logconfig", config_file) if gunicorn_config_file: self.load_config_from_file(gunicorn_config_file) else: default_gunicorn_config_file = get_default_config_file() if default_gunicorn_config_file is not None: self.load_config_from_file(default_gunicorn_config_file) for k, v in local_conf.items(): if v is not None: self.cfg.set(k.lower(), v) def load(self): return app PasterServerApplication().run() gunicorn-20.1.0/gunicorn/app/wsgiapp.py000066400000000000000000000036061401157322000200460ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import os from gunicorn.errors import ConfigError from gunicorn.app.base import Application from gunicorn import util class WSGIApplication(Application): def init(self, parser, opts, args): self.app_uri = None if opts.paste: from .pasterapp import has_logging_config config_uri = os.path.abspath(opts.paste) config_file = config_uri.split('#')[0] if not os.path.exists(config_file): raise ConfigError("%r not found" % config_file) self.cfg.set("default_proc_name", config_file) self.app_uri = config_uri if has_logging_config(config_file): self.cfg.set("logconfig", config_file) return if len(args) > 0: self.cfg.set("default_proc_name", args[0]) self.app_uri = args[0] def load_config(self): super().load_config() if self.app_uri is None: if self.cfg.wsgi_app is not None: self.app_uri = self.cfg.wsgi_app else: raise ConfigError("No application module specified.") def load_wsgiapp(self): return util.import_app(self.app_uri) def load_pasteapp(self): from .pasterapp import get_wsgi_app return get_wsgi_app(self.app_uri, defaults=self.cfg.paste_global_conf) def load(self): if self.cfg.paste is not None: return self.load_pasteapp() else: return self.load_wsgiapp() def run(): """\ The ``gunicorn`` command line runner for launching Gunicorn with generic WSGI applications. """ from gunicorn.app.wsgiapp import WSGIApplication WSGIApplication("%(prog)s [OPTIONS] [APP_MODULE]").run() if __name__ == '__main__': run() gunicorn-20.1.0/gunicorn/arbiter.py000066400000000000000000000500511401157322000172400ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import errno import os import random import select import signal import sys import time import traceback from gunicorn.errors import HaltServer, AppImportError from gunicorn.pidfile import Pidfile from gunicorn import sock, systemd, util from gunicorn import __version__, SERVER_SOFTWARE class Arbiter(object): """ Arbiter maintain the workers processes alive. It launches or kills them if needed. It also manages application reloading via SIGHUP/USR2. """ # A flag indicating if a worker failed to # to boot. If a worker process exist with # this error code, the arbiter will terminate. WORKER_BOOT_ERROR = 3 # A flag indicating if an application failed to be loaded APP_LOAD_ERROR = 4 START_CTX = {} LISTENERS = [] WORKERS = {} PIPE = [] # I love dynamic languages SIG_QUEUE = [] SIGNALS = [getattr(signal, "SIG%s" % x) for x in "HUP QUIT INT TERM TTIN TTOU USR1 USR2 WINCH".split()] SIG_NAMES = dict( (getattr(signal, name), name[3:].lower()) for name in dir(signal) if name[:3] == "SIG" and name[3] != "_" ) def __init__(self, app): os.environ["SERVER_SOFTWARE"] = SERVER_SOFTWARE self._num_workers = None self._last_logged_active_worker_count = None self.log = None self.setup(app) self.pidfile = None self.systemd = False self.worker_age = 0 self.reexec_pid = 0 self.master_pid = 0 self.master_name = "Master" cwd = util.getcwd() args = sys.argv[:] args.insert(0, sys.executable) # init start context self.START_CTX = { "args": args, "cwd": cwd, 0: sys.executable } def _get_num_workers(self): return self._num_workers def _set_num_workers(self, value): old_value = self._num_workers self._num_workers = value self.cfg.nworkers_changed(self, value, old_value) num_workers = property(_get_num_workers, _set_num_workers) def setup(self, app): self.app = app self.cfg = app.cfg if self.log is None: self.log = self.cfg.logger_class(app.cfg) # reopen files if 'GUNICORN_FD' in os.environ: self.log.reopen_files() self.worker_class = self.cfg.worker_class self.address = self.cfg.address self.num_workers = self.cfg.workers self.timeout = self.cfg.timeout self.proc_name = self.cfg.proc_name self.log.debug('Current configuration:\n{0}'.format( '\n'.join( ' {0}: {1}'.format(config, value.value) for config, value in sorted(self.cfg.settings.items(), key=lambda setting: setting[1])))) # set enviroment' variables if self.cfg.env: for k, v in self.cfg.env.items(): os.environ[k] = v if self.cfg.preload_app: self.app.wsgi() def start(self): """\ Initialize the arbiter. Start listening and set pidfile if needed. """ self.log.info("Starting gunicorn %s", __version__) if 'GUNICORN_PID' in os.environ: self.master_pid = int(os.environ.get('GUNICORN_PID')) self.proc_name = self.proc_name + ".2" self.master_name = "Master.2" self.pid = os.getpid() if self.cfg.pidfile is not None: pidname = self.cfg.pidfile if self.master_pid != 0: pidname += ".2" self.pidfile = Pidfile(pidname) self.pidfile.create(self.pid) self.cfg.on_starting(self) self.init_signals() if not self.LISTENERS: fds = None listen_fds = systemd.listen_fds() if listen_fds: self.systemd = True fds = range(systemd.SD_LISTEN_FDS_START, systemd.SD_LISTEN_FDS_START + listen_fds) elif self.master_pid: fds = [] for fd in os.environ.pop('GUNICORN_FD').split(','): fds.append(int(fd)) self.LISTENERS = sock.create_sockets(self.cfg, self.log, fds) listeners_str = ",".join([str(l) for l in self.LISTENERS]) self.log.debug("Arbiter booted") self.log.info("Listening at: %s (%s)", listeners_str, self.pid) self.log.info("Using worker: %s", self.cfg.worker_class_str) systemd.sd_notify("READY=1\nSTATUS=Gunicorn arbiter booted", self.log) # check worker class requirements if hasattr(self.worker_class, "check_config"): self.worker_class.check_config(self.cfg, self.log) self.cfg.when_ready(self) def init_signals(self): """\ Initialize master signal handling. Most of the signals are queued. Child signals only wake up the master. """ # close old PIPE for p in self.PIPE: os.close(p) # initialize the pipe self.PIPE = pair = os.pipe() for p in pair: util.set_non_blocking(p) util.close_on_exec(p) self.log.close_on_exec() # initialize all signals for s in self.SIGNALS: signal.signal(s, self.signal) signal.signal(signal.SIGCHLD, self.handle_chld) def signal(self, sig, frame): if len(self.SIG_QUEUE) < 5: self.SIG_QUEUE.append(sig) self.wakeup() def run(self): "Main master loop." self.start() util._setproctitle("master [%s]" % self.proc_name) try: self.manage_workers() while True: self.maybe_promote_master() sig = self.SIG_QUEUE.pop(0) if self.SIG_QUEUE else None if sig is None: self.sleep() self.murder_workers() self.manage_workers() continue if sig not in self.SIG_NAMES: self.log.info("Ignoring unknown signal: %s", sig) continue signame = self.SIG_NAMES.get(sig) handler = getattr(self, "handle_%s" % signame, None) if not handler: self.log.error("Unhandled signal: %s", signame) continue self.log.info("Handling signal: %s", signame) handler() self.wakeup() except (StopIteration, KeyboardInterrupt): self.halt() except HaltServer as inst: self.halt(reason=inst.reason, exit_status=inst.exit_status) except SystemExit: raise except Exception: self.log.info("Unhandled exception in main loop", exc_info=True) self.stop(False) if self.pidfile is not None: self.pidfile.unlink() sys.exit(-1) def handle_chld(self, sig, frame): "SIGCHLD handling" self.reap_workers() self.wakeup() def handle_hup(self): """\ HUP handling. - Reload configuration - Start the new worker processes with a new configuration - Gracefully shutdown the old worker processes """ self.log.info("Hang up: %s", self.master_name) self.reload() def handle_term(self): "SIGTERM handling" raise StopIteration def handle_int(self): "SIGINT handling" self.stop(False) raise StopIteration def handle_quit(self): "SIGQUIT handling" self.stop(False) raise StopIteration def handle_ttin(self): """\ SIGTTIN handling. Increases the number of workers by one. """ self.num_workers += 1 self.manage_workers() def handle_ttou(self): """\ SIGTTOU handling. Decreases the number of workers by one. """ if self.num_workers <= 1: return self.num_workers -= 1 self.manage_workers() def handle_usr1(self): """\ SIGUSR1 handling. Kill all workers by sending them a SIGUSR1 """ self.log.reopen_files() self.kill_workers(signal.SIGUSR1) def handle_usr2(self): """\ SIGUSR2 handling. Creates a new arbiter/worker set as a fork of the current arbiter without affecting old workers. Use this to do live deployment with the ability to backout a change. """ self.reexec() def handle_winch(self): """SIGWINCH handling""" if self.cfg.daemon: self.log.info("graceful stop of workers") self.num_workers = 0 self.kill_workers(signal.SIGTERM) else: self.log.debug("SIGWINCH ignored. Not daemonized") def maybe_promote_master(self): if self.master_pid == 0: return if self.master_pid != os.getppid(): self.log.info("Master has been promoted.") # reset master infos self.master_name = "Master" self.master_pid = 0 self.proc_name = self.cfg.proc_name del os.environ['GUNICORN_PID'] # rename the pidfile if self.pidfile is not None: self.pidfile.rename(self.cfg.pidfile) # reset proctitle util._setproctitle("master [%s]" % self.proc_name) def wakeup(self): """\ Wake up the arbiter by writing to the PIPE """ try: os.write(self.PIPE[1], b'.') except IOError as e: if e.errno not in [errno.EAGAIN, errno.EINTR]: raise def halt(self, reason=None, exit_status=0): """ halt arbiter """ self.stop() self.log.info("Shutting down: %s", self.master_name) if reason is not None: self.log.info("Reason: %s", reason) if self.pidfile is not None: self.pidfile.unlink() self.cfg.on_exit(self) sys.exit(exit_status) def sleep(self): """\ Sleep until PIPE is readable or we timeout. A readable PIPE means a signal occurred. """ try: ready = select.select([self.PIPE[0]], [], [], 1.0) if not ready[0]: return while os.read(self.PIPE[0], 1): pass except (select.error, OSError) as e: # TODO: select.error is a subclass of OSError since Python 3.3. error_number = getattr(e, 'errno', e.args[0]) if error_number not in [errno.EAGAIN, errno.EINTR]: raise except KeyboardInterrupt: sys.exit() def stop(self, graceful=True): """\ Stop workers :attr graceful: boolean, If True (the default) workers will be killed gracefully (ie. trying to wait for the current connection) """ unlink = ( self.reexec_pid == self.master_pid == 0 and not self.systemd and not self.cfg.reuse_port ) sock.close_sockets(self.LISTENERS, unlink) self.LISTENERS = [] sig = signal.SIGTERM if not graceful: sig = signal.SIGQUIT limit = time.time() + self.cfg.graceful_timeout # instruct the workers to exit self.kill_workers(sig) # wait until the graceful timeout while self.WORKERS and time.time() < limit: time.sleep(0.1) self.kill_workers(signal.SIGKILL) def reexec(self): """\ Relaunch the master and workers. """ if self.reexec_pid != 0: self.log.warning("USR2 signal ignored. Child exists.") return if self.master_pid != 0: self.log.warning("USR2 signal ignored. Parent exists.") return master_pid = os.getpid() self.reexec_pid = os.fork() if self.reexec_pid != 0: return self.cfg.pre_exec(self) environ = self.cfg.env_orig.copy() environ['GUNICORN_PID'] = str(master_pid) if self.systemd: environ['LISTEN_PID'] = str(os.getpid()) environ['LISTEN_FDS'] = str(len(self.LISTENERS)) else: environ['GUNICORN_FD'] = ','.join( str(l.fileno()) for l in self.LISTENERS) os.chdir(self.START_CTX['cwd']) # exec the process using the original environment os.execvpe(self.START_CTX[0], self.START_CTX['args'], environ) def reload(self): old_address = self.cfg.address # reset old environment for k in self.cfg.env: if k in self.cfg.env_orig: # reset the key to the value it had before # we launched gunicorn os.environ[k] = self.cfg.env_orig[k] else: # delete the value set by gunicorn try: del os.environ[k] except KeyError: pass # reload conf self.app.reload() self.setup(self.app) # reopen log files self.log.reopen_files() # do we need to change listener ? if old_address != self.cfg.address: # close all listeners for l in self.LISTENERS: l.close() # init new listeners self.LISTENERS = sock.create_sockets(self.cfg, self.log) listeners_str = ",".join([str(l) for l in self.LISTENERS]) self.log.info("Listening at: %s", listeners_str) # do some actions on reload self.cfg.on_reload(self) # unlink pidfile if self.pidfile is not None: self.pidfile.unlink() # create new pidfile if self.cfg.pidfile is not None: self.pidfile = Pidfile(self.cfg.pidfile) self.pidfile.create(self.pid) # set new proc_name util._setproctitle("master [%s]" % self.proc_name) # spawn new workers for _ in range(self.cfg.workers): self.spawn_worker() # manage workers self.manage_workers() def murder_workers(self): """\ Kill unused/idle workers """ if not self.timeout: return workers = list(self.WORKERS.items()) for (pid, worker) in workers: try: if time.time() - worker.tmp.last_update() <= self.timeout: continue except (OSError, ValueError): continue if not worker.aborted: self.log.critical("WORKER TIMEOUT (pid:%s)", pid) worker.aborted = True self.kill_worker(pid, signal.SIGABRT) else: self.kill_worker(pid, signal.SIGKILL) def reap_workers(self): """\ Reap workers to avoid zombie processes """ try: while True: wpid, status = os.waitpid(-1, os.WNOHANG) if not wpid: break if self.reexec_pid == wpid: self.reexec_pid = 0 else: # A worker was terminated. If the termination reason was # that it could not boot, we'll shut it down to avoid # infinite start/stop cycles. exitcode = status >> 8 if exitcode == self.WORKER_BOOT_ERROR: reason = "Worker failed to boot." raise HaltServer(reason, self.WORKER_BOOT_ERROR) if exitcode == self.APP_LOAD_ERROR: reason = "App failed to load." raise HaltServer(reason, self.APP_LOAD_ERROR) if os.WIFSIGNALED(status): self.log.warning( "Worker with pid %s was terminated due to signal %s", wpid, os.WTERMSIG(status) ) worker = self.WORKERS.pop(wpid, None) if not worker: continue worker.tmp.close() self.cfg.child_exit(self, worker) except OSError as e: if e.errno != errno.ECHILD: raise def manage_workers(self): """\ Maintain the number of workers by spawning or killing as required. """ if len(self.WORKERS) < self.num_workers: self.spawn_workers() workers = self.WORKERS.items() workers = sorted(workers, key=lambda w: w[1].age) while len(workers) > self.num_workers: (pid, _) = workers.pop(0) self.kill_worker(pid, signal.SIGTERM) active_worker_count = len(workers) if self._last_logged_active_worker_count != active_worker_count: self._last_logged_active_worker_count = active_worker_count self.log.debug("{0} workers".format(active_worker_count), extra={"metric": "gunicorn.workers", "value": active_worker_count, "mtype": "gauge"}) def spawn_worker(self): self.worker_age += 1 worker = self.worker_class(self.worker_age, self.pid, self.LISTENERS, self.app, self.timeout / 2.0, self.cfg, self.log) self.cfg.pre_fork(self, worker) pid = os.fork() if pid != 0: worker.pid = pid self.WORKERS[pid] = worker return pid # Do not inherit the temporary files of other workers for sibling in self.WORKERS.values(): sibling.tmp.close() # Process Child worker.pid = os.getpid() try: util._setproctitle("worker [%s]" % self.proc_name) self.log.info("Booting worker with pid: %s", worker.pid) self.cfg.post_fork(self, worker) worker.init_process() sys.exit(0) except SystemExit: raise except AppImportError as e: self.log.debug("Exception while loading the application", exc_info=True) print("%s" % e, file=sys.stderr) sys.stderr.flush() sys.exit(self.APP_LOAD_ERROR) except Exception: self.log.exception("Exception in worker process") if not worker.booted: sys.exit(self.WORKER_BOOT_ERROR) sys.exit(-1) finally: self.log.info("Worker exiting (pid: %s)", worker.pid) try: worker.tmp.close() self.cfg.worker_exit(self, worker) except Exception: self.log.warning("Exception during worker exit:\n%s", traceback.format_exc()) def spawn_workers(self): """\ Spawn new workers as needed. This is where a worker process leaves the main loop of the master process. """ for _ in range(self.num_workers - len(self.WORKERS)): self.spawn_worker() time.sleep(0.1 * random.random()) def kill_workers(self, sig): """\ Kill all workers with the signal `sig` :attr sig: `signal.SIG*` value """ worker_pids = list(self.WORKERS.keys()) for pid in worker_pids: self.kill_worker(pid, sig) def kill_worker(self, pid, sig): """\ Kill a worker :attr pid: int, worker pid :attr sig: `signal.SIG*` value """ try: os.kill(pid, sig) except OSError as e: if e.errno == errno.ESRCH: try: worker = self.WORKERS.pop(pid) worker.tmp.close() self.cfg.worker_exit(self, worker) return except (KeyError, OSError): return raise gunicorn-20.1.0/gunicorn/config.py000066400000000000000000001624141401157322000170640ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. # Please remember to run "make -C docs html" after update "desc" attributes. import argparse import copy import grp import inspect import os import pwd import re import shlex import ssl import sys import textwrap from gunicorn import __version__, util from gunicorn.errors import ConfigError from gunicorn.reloader import reloader_engines KNOWN_SETTINGS = [] PLATFORM = sys.platform def make_settings(ignore=None): settings = {} ignore = ignore or () for s in KNOWN_SETTINGS: setting = s() if setting.name in ignore: continue settings[setting.name] = setting.copy() return settings def auto_int(_, x): # for compatible with octal numbers in python3 if re.match(r'0(\d)', x, re.IGNORECASE): x = x.replace('0', '0o', 1) return int(x, 0) class Config(object): def __init__(self, usage=None, prog=None): self.settings = make_settings() self.usage = usage self.prog = prog or os.path.basename(sys.argv[0]) self.env_orig = os.environ.copy() def __str__(self): lines = [] kmax = max(len(k) for k in self.settings) for k in sorted(self.settings): v = self.settings[k].value if callable(v): v = "<{}()>".format(v.__qualname__) lines.append("{k:{kmax}} = {v}".format(k=k, v=v, kmax=kmax)) return "\n".join(lines) def __getattr__(self, name): if name not in self.settings: raise AttributeError("No configuration setting for: %s" % name) return self.settings[name].get() def __setattr__(self, name, value): if name != "settings" and name in self.settings: raise AttributeError("Invalid access!") super().__setattr__(name, value) def set(self, name, value): if name not in self.settings: raise AttributeError("No configuration setting for: %s" % name) self.settings[name].set(value) def get_cmd_args_from_env(self): if 'GUNICORN_CMD_ARGS' in self.env_orig: return shlex.split(self.env_orig['GUNICORN_CMD_ARGS']) return [] def parser(self): kwargs = { "usage": self.usage, "prog": self.prog } parser = argparse.ArgumentParser(**kwargs) parser.add_argument("-v", "--version", action="version", default=argparse.SUPPRESS, version="%(prog)s (version " + __version__ + ")\n", help="show program's version number and exit") parser.add_argument("args", nargs="*", help=argparse.SUPPRESS) keys = sorted(self.settings, key=self.settings.__getitem__) for k in keys: self.settings[k].add_option(parser) return parser @property def worker_class_str(self): uri = self.settings['worker_class'].get() # are we using a threaded worker? is_sync = uri.endswith('SyncWorker') or uri == 'sync' if is_sync and self.threads > 1: return "threads" return uri @property def worker_class(self): uri = self.settings['worker_class'].get() # are we using a threaded worker? is_sync = uri.endswith('SyncWorker') or uri == 'sync' if is_sync and self.threads > 1: uri = "gunicorn.workers.gthread.ThreadWorker" worker_class = util.load_class(uri) if hasattr(worker_class, "setup"): worker_class.setup() return worker_class @property def address(self): s = self.settings['bind'].get() return [util.parse_address(util.bytes_to_str(bind)) for bind in s] @property def uid(self): return self.settings['user'].get() @property def gid(self): return self.settings['group'].get() @property def proc_name(self): pn = self.settings['proc_name'].get() if pn is not None: return pn else: return self.settings['default_proc_name'].get() @property def logger_class(self): uri = self.settings['logger_class'].get() if uri == "simple": # support the default uri = LoggerClass.default # if default logger is in use, and statsd is on, automagically switch # to the statsd logger if uri == LoggerClass.default: if 'statsd_host' in self.settings and self.settings['statsd_host'].value is not None: uri = "gunicorn.instrument.statsd.Statsd" logger_class = util.load_class( uri, default="gunicorn.glogging.Logger", section="gunicorn.loggers") if hasattr(logger_class, "install"): logger_class.install() return logger_class @property def is_ssl(self): return self.certfile or self.keyfile @property def ssl_options(self): opts = {} for name, value in self.settings.items(): if value.section == 'SSL': opts[name] = value.get() return opts @property def env(self): raw_env = self.settings['raw_env'].get() env = {} if not raw_env: return env for e in raw_env: s = util.bytes_to_str(e) try: k, v = s.split('=', 1) except ValueError: raise RuntimeError("environment setting %r invalid" % s) env[k] = v return env @property def sendfile(self): if self.settings['sendfile'].get() is not None: return False if 'SENDFILE' in os.environ: sendfile = os.environ['SENDFILE'].lower() return sendfile in ['y', '1', 'yes', 'true'] return True @property def reuse_port(self): return self.settings['reuse_port'].get() @property def paste_global_conf(self): raw_global_conf = self.settings['raw_paste_global_conf'].get() if raw_global_conf is None: return None global_conf = {} for e in raw_global_conf: s = util.bytes_to_str(e) try: k, v = re.split(r'(?" % ( self.__class__.__module__, self.__class__.__name__, id(self), self.value, ) Setting = SettingMeta('Setting', (Setting,), {}) def validate_bool(val): if val is None: return if isinstance(val, bool): return val if not isinstance(val, str): raise TypeError("Invalid type for casting: %s" % val) if val.lower().strip() == "true": return True elif val.lower().strip() == "false": return False else: raise ValueError("Invalid boolean: %s" % val) def validate_dict(val): if not isinstance(val, dict): raise TypeError("Value is not a dictionary: %s " % val) return val def validate_pos_int(val): if not isinstance(val, int): val = int(val, 0) else: # Booleans are ints! val = int(val) if val < 0: raise ValueError("Value must be positive: %s" % val) return val def validate_ssl_version(val): ssl_versions = {} for protocol in [p for p in dir(ssl) if p.startswith("PROTOCOL_")]: ssl_versions[protocol[9:]] = getattr(ssl, protocol) if val in ssl_versions: # string matching PROTOCOL_... return ssl_versions[val] try: intval = validate_pos_int(val) if intval in ssl_versions.values(): # positive int matching a protocol int constant return intval except (ValueError, TypeError): # negative integer or not an integer # drop this in favour of the more descriptive ValueError below pass raise ValueError("Invalid ssl_version: %s. Valid options: %s" % (val, ', '.join(ssl_versions))) def validate_string(val): if val is None: return None if not isinstance(val, str): raise TypeError("Not a string: %s" % val) return val.strip() def validate_file_exists(val): if val is None: return None if not os.path.exists(val): raise ValueError("File %s does not exists." % val) return val def validate_list_string(val): if not val: return [] # legacy syntax if isinstance(val, str): val = [val] return [validate_string(v) for v in val] def validate_list_of_existing_files(val): return [validate_file_exists(v) for v in validate_list_string(val)] def validate_string_to_list(val): val = validate_string(val) if not val: return [] return [v.strip() for v in val.split(",") if v] def validate_class(val): if inspect.isfunction(val) or inspect.ismethod(val): val = val() if inspect.isclass(val): return val return validate_string(val) def validate_callable(arity): def _validate_callable(val): if isinstance(val, str): try: mod_name, obj_name = val.rsplit(".", 1) except ValueError: raise TypeError("Value '%s' is not import string. " "Format: module[.submodules...].object" % val) try: mod = __import__(mod_name, fromlist=[obj_name]) val = getattr(mod, obj_name) except ImportError as e: raise TypeError(str(e)) except AttributeError: raise TypeError("Can not load '%s' from '%s'" "" % (obj_name, mod_name)) if not callable(val): raise TypeError("Value is not callable: %s" % val) if arity != -1 and arity != util.get_arity(val): raise TypeError("Value must have an arity of: %s" % arity) return val return _validate_callable def validate_user(val): if val is None: return os.geteuid() if isinstance(val, int): return val elif val.isdigit(): return int(val) else: try: return pwd.getpwnam(val).pw_uid except KeyError: raise ConfigError("No such user: '%s'" % val) def validate_group(val): if val is None: return os.getegid() if isinstance(val, int): return val elif val.isdigit(): return int(val) else: try: return grp.getgrnam(val).gr_gid except KeyError: raise ConfigError("No such group: '%s'" % val) def validate_post_request(val): val = validate_callable(-1)(val) largs = util.get_arity(val) if largs == 4: return val elif largs == 3: return lambda worker, req, env, _r: val(worker, req, env) elif largs == 2: return lambda worker, req, _e, _r: val(worker, req) else: raise TypeError("Value must have an arity of: 4") def validate_chdir(val): # valid if the value is a string val = validate_string(val) # transform relative paths path = os.path.abspath(os.path.normpath(os.path.join(util.getcwd(), val))) # test if the path exists if not os.path.exists(path): raise ConfigError("can't chdir to %r" % val) return path def validate_hostport(val): val = validate_string(val) if val is None: return None elements = val.split(":") if len(elements) == 2: return (elements[0], int(elements[1])) else: raise TypeError("Value must consist of: hostname:port") def validate_reload_engine(val): if val not in reloader_engines: raise ConfigError("Invalid reload_engine: %r" % val) return val def get_default_config_file(): config_path = os.path.join(os.path.abspath(os.getcwd()), 'gunicorn.conf.py') if os.path.exists(config_path): return config_path return None class ConfigFile(Setting): name = "config" section = "Config File" cli = ["-c", "--config"] meta = "CONFIG" validator = validate_string default = "./gunicorn.conf.py" desc = """\ The Gunicorn config file. A string of the form ``PATH``, ``file:PATH``, or ``python:MODULE_NAME``. Only has an effect when specified on the command line or as part of an application specific configuration. By default, a file named ``gunicorn.conf.py`` will be read from the same directory where gunicorn is being run. .. versionchanged:: 19.4 Loading the config from a Python module requires the ``python:`` prefix. """ class WSGIApp(Setting): name = "wsgi_app" section = "Config File" meta = "STRING" validator = validate_string default = None desc = """\ A WSGI application path in pattern ``$(MODULE_NAME):$(VARIABLE_NAME)``. .. versionadded:: 20.1.0 """ class Bind(Setting): name = "bind" action = "append" section = "Server Socket" cli = ["-b", "--bind"] meta = "ADDRESS" validator = validate_list_string if 'PORT' in os.environ: default = ['0.0.0.0:{0}'.format(os.environ.get('PORT'))] else: default = ['127.0.0.1:8000'] desc = """\ The socket to bind. A string of the form: ``HOST``, ``HOST:PORT``, ``unix:PATH``, ``fd://FD``. An IP is a valid ``HOST``. .. versionchanged:: 20.0 Support for ``fd://FD`` got added. Multiple addresses can be bound. ex.:: $ gunicorn -b 127.0.0.1:8000 -b [::1]:8000 test:app will bind the `test:app` application on localhost both on ipv6 and ipv4 interfaces. If the ``PORT`` environment variable is defined, the default is ``['0.0.0.0:$PORT']``. If it is not defined, the default is ``['127.0.0.1:8000']``. """ class Backlog(Setting): name = "backlog" section = "Server Socket" cli = ["--backlog"] meta = "INT" validator = validate_pos_int type = int default = 2048 desc = """\ The maximum number of pending connections. This refers to the number of clients that can be waiting to be served. Exceeding this number results in the client getting an error when attempting to connect. It should only affect servers under significant load. Must be a positive integer. Generally set in the 64-2048 range. """ class Workers(Setting): name = "workers" section = "Worker Processes" cli = ["-w", "--workers"] meta = "INT" validator = validate_pos_int type = int default = int(os.environ.get("WEB_CONCURRENCY", 1)) desc = """\ The number of worker processes for handling requests. A positive integer generally in the ``2-4 x $(NUM_CORES)`` range. You'll want to vary this a bit to find the best for your particular application's work load. By default, the value of the ``WEB_CONCURRENCY`` environment variable, which is set by some Platform-as-a-Service providers such as Heroku. If it is not defined, the default is ``1``. """ class WorkerClass(Setting): name = "worker_class" section = "Worker Processes" cli = ["-k", "--worker-class"] meta = "STRING" validator = validate_class default = "sync" desc = """\ The type of workers to use. The default class (``sync``) should handle most "normal" types of workloads. You'll want to read :doc:`design` for information on when you might want to choose one of the other worker classes. Required libraries may be installed using setuptools' ``extras_require`` feature. A string referring to one of the following bundled classes: * ``sync`` * ``eventlet`` - Requires eventlet >= 0.24.1 (or install it via ``pip install gunicorn[eventlet]``) * ``gevent`` - Requires gevent >= 1.4 (or install it via ``pip install gunicorn[gevent]``) * ``tornado`` - Requires tornado >= 0.2 (or install it via ``pip install gunicorn[tornado]``) * ``gthread`` - Python 2 requires the futures package to be installed (or install it via ``pip install gunicorn[gthread]``) Optionally, you can provide your own worker by giving Gunicorn a Python path to a subclass of ``gunicorn.workers.base.Worker``. This alternative syntax will load the gevent class: ``gunicorn.workers.ggevent.GeventWorker``. """ class WorkerThreads(Setting): name = "threads" section = "Worker Processes" cli = ["--threads"] meta = "INT" validator = validate_pos_int type = int default = 1 desc = """\ The number of worker threads for handling requests. Run each worker with the specified number of threads. A positive integer generally in the ``2-4 x $(NUM_CORES)`` range. You'll want to vary this a bit to find the best for your particular application's work load. If it is not defined, the default is ``1``. This setting only affects the Gthread worker type. .. note:: If you try to use the ``sync`` worker type and set the ``threads`` setting to more than 1, the ``gthread`` worker type will be used instead. """ class WorkerConnections(Setting): name = "worker_connections" section = "Worker Processes" cli = ["--worker-connections"] meta = "INT" validator = validate_pos_int type = int default = 1000 desc = """\ The maximum number of simultaneous clients. This setting only affects the Eventlet and Gevent worker types. """ class MaxRequests(Setting): name = "max_requests" section = "Worker Processes" cli = ["--max-requests"] meta = "INT" validator = validate_pos_int type = int default = 0 desc = """\ The maximum number of requests a worker will process before restarting. Any value greater than zero will limit the number of requests a worker will process before automatically restarting. This is a simple method to help limit the damage of memory leaks. If this is set to zero (the default) then the automatic worker restarts are disabled. """ class MaxRequestsJitter(Setting): name = "max_requests_jitter" section = "Worker Processes" cli = ["--max-requests-jitter"] meta = "INT" validator = validate_pos_int type = int default = 0 desc = """\ The maximum jitter to add to the *max_requests* setting. The jitter causes the restart per worker to be randomized by ``randint(0, max_requests_jitter)``. This is intended to stagger worker restarts to avoid all workers restarting at the same time. .. versionadded:: 19.2 """ class Timeout(Setting): name = "timeout" section = "Worker Processes" cli = ["-t", "--timeout"] meta = "INT" validator = validate_pos_int type = int default = 30 desc = """\ Workers silent for more than this many seconds are killed and restarted. Value is a positive number or 0. Setting it to 0 has the effect of infinite timeouts by disabling timeouts for all workers entirely. Generally, the default of thirty seconds should suffice. Only set this noticeably higher if you're sure of the repercussions for sync workers. For the non sync workers it just means that the worker process is still communicating and is not tied to the length of time required to handle a single request. """ class GracefulTimeout(Setting): name = "graceful_timeout" section = "Worker Processes" cli = ["--graceful-timeout"] meta = "INT" validator = validate_pos_int type = int default = 30 desc = """\ Timeout for graceful workers restart. After receiving a restart signal, workers have this much time to finish serving requests. Workers still alive after the timeout (starting from the receipt of the restart signal) are force killed. """ class Keepalive(Setting): name = "keepalive" section = "Worker Processes" cli = ["--keep-alive"] meta = "INT" validator = validate_pos_int type = int default = 2 desc = """\ The number of seconds to wait for requests on a Keep-Alive connection. Generally set in the 1-5 seconds range for servers with direct connection to the client (e.g. when you don't have separate load balancer). When Gunicorn is deployed behind a load balancer, it often makes sense to set this to a higher value. .. note:: ``sync`` worker does not support persistent connections and will ignore this option. """ class LimitRequestLine(Setting): name = "limit_request_line" section = "Security" cli = ["--limit-request-line"] meta = "INT" validator = validate_pos_int type = int default = 4094 desc = """\ The maximum size of HTTP request line in bytes. This parameter is used to limit the allowed size of a client's HTTP request-line. Since the request-line consists of the HTTP method, URI, and protocol version, this directive places a restriction on the length of a request-URI allowed for a request on the server. A server needs this value to be large enough to hold any of its resource names, including any information that might be passed in the query part of a GET request. Value is a number from 0 (unlimited) to 8190. This parameter can be used to prevent any DDOS attack. """ class LimitRequestFields(Setting): name = "limit_request_fields" section = "Security" cli = ["--limit-request-fields"] meta = "INT" validator = validate_pos_int type = int default = 100 desc = """\ Limit the number of HTTP headers fields in a request. This parameter is used to limit the number of headers in a request to prevent DDOS attack. Used with the *limit_request_field_size* it allows more safety. By default this value is 100 and can't be larger than 32768. """ class LimitRequestFieldSize(Setting): name = "limit_request_field_size" section = "Security" cli = ["--limit-request-field_size"] meta = "INT" validator = validate_pos_int type = int default = 8190 desc = """\ Limit the allowed size of an HTTP request header field. Value is a positive number or 0. Setting it to 0 will allow unlimited header field sizes. .. warning:: Setting this parameter to a very high or unlimited value can open up for DDOS attacks. """ class Reload(Setting): name = "reload" section = 'Debugging' cli = ['--reload'] validator = validate_bool action = 'store_true' default = False desc = '''\ Restart workers when code changes. This setting is intended for development. It will cause workers to be restarted whenever application code changes. The reloader is incompatible with application preloading. When using a paste configuration be sure that the server block does not import any application code or the reload will not work as designed. The default behavior is to attempt inotify with a fallback to file system polling. Generally, inotify should be preferred if available because it consumes less system resources. .. note:: In order to use the inotify reloader, you must have the ``inotify`` package installed. ''' class ReloadEngine(Setting): name = "reload_engine" section = "Debugging" cli = ["--reload-engine"] meta = "STRING" validator = validate_reload_engine default = "auto" desc = """\ The implementation that should be used to power :ref:`reload`. Valid engines are: * ``'auto'`` * ``'poll'`` * ``'inotify'`` (requires inotify) .. versionadded:: 19.7 """ class ReloadExtraFiles(Setting): name = "reload_extra_files" action = "append" section = "Debugging" cli = ["--reload-extra-file"] meta = "FILES" validator = validate_list_of_existing_files default = [] desc = """\ Extends :ref:`reload` option to also watch and reload on additional files (e.g., templates, configurations, specifications, etc.). .. versionadded:: 19.8 """ class Spew(Setting): name = "spew" section = "Debugging" cli = ["--spew"] validator = validate_bool action = "store_true" default = False desc = """\ Install a trace function that spews every line executed by the server. This is the nuclear option. """ class ConfigCheck(Setting): name = "check_config" section = "Debugging" cli = ["--check-config"] validator = validate_bool action = "store_true" default = False desc = """\ Check the configuration and exit. The exit status is 0 if the configuration is correct, and 1 if the configuration is incorrect. """ class PrintConfig(Setting): name = "print_config" section = "Debugging" cli = ["--print-config"] validator = validate_bool action = "store_true" default = False desc = """\ Print the configuration settings as fully resolved. Implies :ref:`check-config`. """ class PreloadApp(Setting): name = "preload_app" section = "Server Mechanics" cli = ["--preload"] validator = validate_bool action = "store_true" default = False desc = """\ Load application code before the worker processes are forked. By preloading an application you can save some RAM resources as well as speed up server boot times. Although, if you defer application loading to each worker process, you can reload your application code easily by restarting workers. """ class Sendfile(Setting): name = "sendfile" section = "Server Mechanics" cli = ["--no-sendfile"] validator = validate_bool action = "store_const" const = False desc = """\ Disables the use of ``sendfile()``. If not set, the value of the ``SENDFILE`` environment variable is used to enable or disable its usage. .. versionadded:: 19.2 .. versionchanged:: 19.4 Swapped ``--sendfile`` with ``--no-sendfile`` to actually allow disabling. .. versionchanged:: 19.6 added support for the ``SENDFILE`` environment variable """ class ReusePort(Setting): name = "reuse_port" section = "Server Mechanics" cli = ["--reuse-port"] validator = validate_bool action = "store_true" default = False desc = """\ Set the ``SO_REUSEPORT`` flag on the listening socket. .. versionadded:: 19.8 """ class Chdir(Setting): name = "chdir" section = "Server Mechanics" cli = ["--chdir"] validator = validate_chdir default = util.getcwd() desc = """\ Change directory to specified directory before loading apps. """ class Daemon(Setting): name = "daemon" section = "Server Mechanics" cli = ["-D", "--daemon"] validator = validate_bool action = "store_true" default = False desc = """\ Daemonize the Gunicorn process. Detaches the server from the controlling terminal and enters the background. """ class Env(Setting): name = "raw_env" action = "append" section = "Server Mechanics" cli = ["-e", "--env"] meta = "ENV" validator = validate_list_string default = [] desc = """\ Set environment variables in the execution environment. Should be a list of strings in the ``key=value`` format. For example on the command line: .. code-block:: console $ gunicorn -b 127.0.0.1:8000 --env FOO=1 test:app Or in the configuration file: .. code-block:: python raw_env = ["FOO=1"] """ class Pidfile(Setting): name = "pidfile" section = "Server Mechanics" cli = ["-p", "--pid"] meta = "FILE" validator = validate_string default = None desc = """\ A filename to use for the PID file. If not set, no PID file will be written. """ class WorkerTmpDir(Setting): name = "worker_tmp_dir" section = "Server Mechanics" cli = ["--worker-tmp-dir"] meta = "DIR" validator = validate_string default = None desc = """\ A directory to use for the worker heartbeat temporary file. If not set, the default temporary directory will be used. .. note:: The current heartbeat system involves calling ``os.fchmod`` on temporary file handlers and may block a worker for arbitrary time if the directory is on a disk-backed filesystem. See :ref:`blocking-os-fchmod` for more detailed information and a solution for avoiding this problem. """ class User(Setting): name = "user" section = "Server Mechanics" cli = ["-u", "--user"] meta = "USER" validator = validate_user default = os.geteuid() desc = """\ Switch worker processes to run as this user. A valid user id (as an integer) or the name of a user that can be retrieved with a call to ``pwd.getpwnam(value)`` or ``None`` to not change the worker process user. """ class Group(Setting): name = "group" section = "Server Mechanics" cli = ["-g", "--group"] meta = "GROUP" validator = validate_group default = os.getegid() desc = """\ Switch worker process to run as this group. A valid group id (as an integer) or the name of a user that can be retrieved with a call to ``pwd.getgrnam(value)`` or ``None`` to not change the worker processes group. """ class Umask(Setting): name = "umask" section = "Server Mechanics" cli = ["-m", "--umask"] meta = "INT" validator = validate_pos_int type = auto_int default = 0 desc = """\ A bit mask for the file mode on files written by Gunicorn. Note that this affects unix socket permissions. A valid value for the ``os.umask(mode)`` call or a string compatible with ``int(value, 0)`` (``0`` means Python guesses the base, so values like ``0``, ``0xFF``, ``0022`` are valid for decimal, hex, and octal representations) """ class Initgroups(Setting): name = "initgroups" section = "Server Mechanics" cli = ["--initgroups"] validator = validate_bool action = 'store_true' default = False desc = """\ If true, set the worker process's group access list with all of the groups of which the specified username is a member, plus the specified group id. .. versionadded:: 19.7 """ class TmpUploadDir(Setting): name = "tmp_upload_dir" section = "Server Mechanics" meta = "DIR" validator = validate_string default = None desc = """\ Directory to store temporary request data as they are read. This may disappear in the near future. This path should be writable by the process permissions set for Gunicorn workers. If not specified, Gunicorn will choose a system generated temporary directory. """ class SecureSchemeHeader(Setting): name = "secure_scheme_headers" section = "Server Mechanics" validator = validate_dict default = { "X-FORWARDED-PROTOCOL": "ssl", "X-FORWARDED-PROTO": "https", "X-FORWARDED-SSL": "on" } desc = """\ A dictionary containing headers and values that the front-end proxy uses to indicate HTTPS requests. These tell Gunicorn to set ``wsgi.url_scheme`` to ``https``, so your application can tell that the request is secure. The dictionary should map upper-case header names to exact string values. The value comparisons are case-sensitive, unlike the header names, so make sure they're exactly what your front-end proxy sends when handling HTTPS requests. It is important that your front-end proxy configuration ensures that the headers defined here can not be passed directly from the client. """ class ForwardedAllowIPS(Setting): name = "forwarded_allow_ips" section = "Server Mechanics" cli = ["--forwarded-allow-ips"] meta = "STRING" validator = validate_string_to_list default = os.environ.get("FORWARDED_ALLOW_IPS", "127.0.0.1") desc = """\ Front-end's IPs from which allowed to handle set secure headers. (comma separate). Set to ``*`` to disable checking of Front-end IPs (useful for setups where you don't know in advance the IP address of Front-end, but you still trust the environment). By default, the value of the ``FORWARDED_ALLOW_IPS`` environment variable. If it is not defined, the default is ``"127.0.0.1"``. """ class AccessLog(Setting): name = "accesslog" section = "Logging" cli = ["--access-logfile"] meta = "FILE" validator = validate_string default = None desc = """\ The Access log file to write to. ``'-'`` means log to stdout. """ class DisableRedirectAccessToSyslog(Setting): name = "disable_redirect_access_to_syslog" section = "Logging" cli = ["--disable-redirect-access-to-syslog"] validator = validate_bool action = 'store_true' default = False desc = """\ Disable redirect access logs to syslog. .. versionadded:: 19.8 """ class AccessLogFormat(Setting): name = "access_log_format" section = "Logging" cli = ["--access-logformat"] meta = "STRING" validator = validate_string default = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' desc = """\ The access log format. =========== =========== Identifier Description =========== =========== h remote address l ``'-'`` u user name t date of the request r status line (e.g. ``GET / HTTP/1.1``) m request method U URL path without query string q query string H protocol s status B response length b response length or ``'-'`` (CLF format) f referer a user agent T request time in seconds M request time in milliseconds D request time in microseconds L request time in decimal seconds p process ID {header}i request header {header}o response header {variable}e environment variable =========== =========== Use lowercase for header and environment variable names, and put ``{...}x`` names inside ``%(...)s``. For example:: %({x-forwarded-for}i)s """ class ErrorLog(Setting): name = "errorlog" section = "Logging" cli = ["--error-logfile", "--log-file"] meta = "FILE" validator = validate_string default = '-' desc = """\ The Error log file to write to. Using ``'-'`` for FILE makes gunicorn log to stderr. .. versionchanged:: 19.2 Log to stderr by default. """ class Loglevel(Setting): name = "loglevel" section = "Logging" cli = ["--log-level"] meta = "LEVEL" validator = validate_string default = "info" desc = """\ The granularity of Error log outputs. Valid level names are: * ``'debug'`` * ``'info'`` * ``'warning'`` * ``'error'`` * ``'critical'`` """ class CaptureOutput(Setting): name = "capture_output" section = "Logging" cli = ["--capture-output"] validator = validate_bool action = 'store_true' default = False desc = """\ Redirect stdout/stderr to specified file in :ref:`errorlog`. .. versionadded:: 19.6 """ class LoggerClass(Setting): name = "logger_class" section = "Logging" cli = ["--logger-class"] meta = "STRING" validator = validate_class default = "gunicorn.glogging.Logger" desc = """\ The logger you want to use to log events in Gunicorn. The default class (``gunicorn.glogging.Logger``) handles most normal usages in logging. It provides error and access logging. You can provide your own logger by giving Gunicorn a Python path to a class that quacks like ``gunicorn.glogging.Logger``. """ class LogConfig(Setting): name = "logconfig" section = "Logging" cli = ["--log-config"] meta = "FILE" validator = validate_string default = None desc = """\ The log config file to use. Gunicorn uses the standard Python logging module's Configuration file format. """ class LogConfigDict(Setting): name = "logconfig_dict" section = "Logging" validator = validate_dict default = {} desc = """\ The log config dictionary to use, using the standard Python logging module's dictionary configuration format. This option takes precedence over the :ref:`logconfig` option, which uses the older file configuration format. Format: https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig .. versionadded:: 19.8 """ class SyslogTo(Setting): name = "syslog_addr" section = "Logging" cli = ["--log-syslog-to"] meta = "SYSLOG_ADDR" validator = validate_string if PLATFORM == "darwin": default = "unix:///var/run/syslog" elif PLATFORM in ('freebsd', 'dragonfly', ): default = "unix:///var/run/log" elif PLATFORM == "openbsd": default = "unix:///dev/log" else: default = "udp://localhost:514" desc = """\ Address to send syslog messages. Address is a string of the form: * ``unix://PATH#TYPE`` : for unix domain socket. ``TYPE`` can be ``stream`` for the stream driver or ``dgram`` for the dgram driver. ``stream`` is the default. * ``udp://HOST:PORT`` : for UDP sockets * ``tcp://HOST:PORT`` : for TCP sockets """ class Syslog(Setting): name = "syslog" section = "Logging" cli = ["--log-syslog"] validator = validate_bool action = 'store_true' default = False desc = """\ Send *Gunicorn* logs to syslog. .. versionchanged:: 19.8 You can now disable sending access logs by using the :ref:`disable-redirect-access-to-syslog` setting. """ class SyslogPrefix(Setting): name = "syslog_prefix" section = "Logging" cli = ["--log-syslog-prefix"] meta = "SYSLOG_PREFIX" validator = validate_string default = None desc = """\ Makes Gunicorn use the parameter as program-name in the syslog entries. All entries will be prefixed by ``gunicorn.``. By default the program name is the name of the process. """ class SyslogFacility(Setting): name = "syslog_facility" section = "Logging" cli = ["--log-syslog-facility"] meta = "SYSLOG_FACILITY" validator = validate_string default = "user" desc = """\ Syslog facility name """ class EnableStdioInheritance(Setting): name = "enable_stdio_inheritance" section = "Logging" cli = ["-R", "--enable-stdio-inheritance"] validator = validate_bool default = False action = "store_true" desc = """\ Enable stdio inheritance. Enable inheritance for stdio file descriptors in daemon mode. Note: To disable the Python stdout buffering, you can to set the user environment variable ``PYTHONUNBUFFERED`` . """ # statsD monitoring class StatsdHost(Setting): name = "statsd_host" section = "Logging" cli = ["--statsd-host"] meta = "STATSD_ADDR" default = None validator = validate_hostport desc = """\ ``host:port`` of the statsd server to log to. .. versionadded:: 19.1 """ # Datadog Statsd (dogstatsd) tags. https://docs.datadoghq.com/developers/dogstatsd/ class DogstatsdTags(Setting): name = "dogstatsd_tags" section = "Logging" cli = ["--dogstatsd-tags"] meta = "DOGSTATSD_TAGS" default = "" validator = validate_string desc = """\ A comma-delimited list of datadog statsd (dogstatsd) tags to append to statsd metrics. .. versionadded:: 20 """ class StatsdPrefix(Setting): name = "statsd_prefix" section = "Logging" cli = ["--statsd-prefix"] meta = "STATSD_PREFIX" default = "" validator = validate_string desc = """\ Prefix to use when emitting statsd metrics (a trailing ``.`` is added, if not provided). .. versionadded:: 19.2 """ class Procname(Setting): name = "proc_name" section = "Process Naming" cli = ["-n", "--name"] meta = "STRING" validator = validate_string default = None desc = """\ A base to use with setproctitle for process naming. This affects things like ``ps`` and ``top``. If you're going to be running more than one instance of Gunicorn you'll probably want to set a name to tell them apart. This requires that you install the setproctitle module. If not set, the *default_proc_name* setting will be used. """ class DefaultProcName(Setting): name = "default_proc_name" section = "Process Naming" validator = validate_string default = "gunicorn" desc = """\ Internal setting that is adjusted for each type of application. """ class PythonPath(Setting): name = "pythonpath" section = "Server Mechanics" cli = ["--pythonpath"] meta = "STRING" validator = validate_string default = None desc = """\ A comma-separated list of directories to add to the Python path. e.g. ``'/home/djangoprojects/myproject,/home/python/mylibrary'``. """ class Paste(Setting): name = "paste" section = "Server Mechanics" cli = ["--paste", "--paster"] meta = "STRING" validator = validate_string default = None desc = """\ Load a PasteDeploy config file. The argument may contain a ``#`` symbol followed by the name of an app section from the config file, e.g. ``production.ini#admin``. At this time, using alternate server blocks is not supported. Use the command line arguments to control server configuration instead. """ class OnStarting(Setting): name = "on_starting" section = "Server Hooks" validator = validate_callable(1) type = callable def on_starting(server): pass default = staticmethod(on_starting) desc = """\ Called just before the master process is initialized. The callable needs to accept a single instance variable for the Arbiter. """ class OnReload(Setting): name = "on_reload" section = "Server Hooks" validator = validate_callable(1) type = callable def on_reload(server): pass default = staticmethod(on_reload) desc = """\ Called to recycle workers during a reload via SIGHUP. The callable needs to accept a single instance variable for the Arbiter. """ class WhenReady(Setting): name = "when_ready" section = "Server Hooks" validator = validate_callable(1) type = callable def when_ready(server): pass default = staticmethod(when_ready) desc = """\ Called just after the server is started. The callable needs to accept a single instance variable for the Arbiter. """ class Prefork(Setting): name = "pre_fork" section = "Server Hooks" validator = validate_callable(2) type = callable def pre_fork(server, worker): pass default = staticmethod(pre_fork) desc = """\ Called just before a worker is forked. The callable needs to accept two instance variables for the Arbiter and new Worker. """ class Postfork(Setting): name = "post_fork" section = "Server Hooks" validator = validate_callable(2) type = callable def post_fork(server, worker): pass default = staticmethod(post_fork) desc = """\ Called just after a worker has been forked. The callable needs to accept two instance variables for the Arbiter and new Worker. """ class PostWorkerInit(Setting): name = "post_worker_init" section = "Server Hooks" validator = validate_callable(1) type = callable def post_worker_init(worker): pass default = staticmethod(post_worker_init) desc = """\ Called just after a worker has initialized the application. The callable needs to accept one instance variable for the initialized Worker. """ class WorkerInt(Setting): name = "worker_int" section = "Server Hooks" validator = validate_callable(1) type = callable def worker_int(worker): pass default = staticmethod(worker_int) desc = """\ Called just after a worker exited on SIGINT or SIGQUIT. The callable needs to accept one instance variable for the initialized Worker. """ class WorkerAbort(Setting): name = "worker_abort" section = "Server Hooks" validator = validate_callable(1) type = callable def worker_abort(worker): pass default = staticmethod(worker_abort) desc = """\ Called when a worker received the SIGABRT signal. This call generally happens on timeout. The callable needs to accept one instance variable for the initialized Worker. """ class PreExec(Setting): name = "pre_exec" section = "Server Hooks" validator = validate_callable(1) type = callable def pre_exec(server): pass default = staticmethod(pre_exec) desc = """\ Called just before a new master process is forked. The callable needs to accept a single instance variable for the Arbiter. """ class PreRequest(Setting): name = "pre_request" section = "Server Hooks" validator = validate_callable(2) type = callable def pre_request(worker, req): worker.log.debug("%s %s" % (req.method, req.path)) default = staticmethod(pre_request) desc = """\ Called just before a worker processes the request. The callable needs to accept two instance variables for the Worker and the Request. """ class PostRequest(Setting): name = "post_request" section = "Server Hooks" validator = validate_post_request type = callable def post_request(worker, req, environ, resp): pass default = staticmethod(post_request) desc = """\ Called after a worker processes the request. The callable needs to accept two instance variables for the Worker and the Request. """ class ChildExit(Setting): name = "child_exit" section = "Server Hooks" validator = validate_callable(2) type = callable def child_exit(server, worker): pass default = staticmethod(child_exit) desc = """\ Called just after a worker has been exited, in the master process. The callable needs to accept two instance variables for the Arbiter and the just-exited Worker. .. versionadded:: 19.7 """ class WorkerExit(Setting): name = "worker_exit" section = "Server Hooks" validator = validate_callable(2) type = callable def worker_exit(server, worker): pass default = staticmethod(worker_exit) desc = """\ Called just after a worker has been exited, in the worker process. The callable needs to accept two instance variables for the Arbiter and the just-exited Worker. """ class NumWorkersChanged(Setting): name = "nworkers_changed" section = "Server Hooks" validator = validate_callable(3) type = callable def nworkers_changed(server, new_value, old_value): pass default = staticmethod(nworkers_changed) desc = """\ Called just after *num_workers* has been changed. The callable needs to accept an instance variable of the Arbiter and two integers of number of workers after and before change. If the number of workers is set for the first time, *old_value* would be ``None``. """ class OnExit(Setting): name = "on_exit" section = "Server Hooks" validator = validate_callable(1) def on_exit(server): pass default = staticmethod(on_exit) desc = """\ Called just before exiting Gunicorn. The callable needs to accept a single instance variable for the Arbiter. """ class ProxyProtocol(Setting): name = "proxy_protocol" section = "Server Mechanics" cli = ["--proxy-protocol"] validator = validate_bool default = False action = "store_true" desc = """\ Enable detect PROXY protocol (PROXY mode). Allow using HTTP and Proxy together. It may be useful for work with stunnel as HTTPS frontend and Gunicorn as HTTP server. PROXY protocol: http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt Example for stunnel config:: [https] protocol = proxy accept = 443 connect = 80 cert = /etc/ssl/certs/stunnel.pem key = /etc/ssl/certs/stunnel.key """ class ProxyAllowFrom(Setting): name = "proxy_allow_ips" section = "Server Mechanics" cli = ["--proxy-allow-from"] validator = validate_string_to_list default = "127.0.0.1" desc = """\ Front-end's IPs from which allowed accept proxy requests (comma separate). Set to ``*`` to disable checking of Front-end IPs (useful for setups where you don't know in advance the IP address of Front-end, but you still trust the environment) """ class KeyFile(Setting): name = "keyfile" section = "SSL" cli = ["--keyfile"] meta = "FILE" validator = validate_string default = None desc = """\ SSL key file """ class CertFile(Setting): name = "certfile" section = "SSL" cli = ["--certfile"] meta = "FILE" validator = validate_string default = None desc = """\ SSL certificate file """ class SSLVersion(Setting): name = "ssl_version" section = "SSL" cli = ["--ssl-version"] validator = validate_ssl_version if hasattr(ssl, "PROTOCOL_TLS"): default = ssl.PROTOCOL_TLS else: default = ssl.PROTOCOL_SSLv23 desc = """\ SSL version to use (see stdlib ssl module's) .. versionchanged:: 20.0.1 The default value has been changed from ``ssl.PROTOCOL_SSLv23`` to ``ssl.PROTOCOL_TLS`` when Python >= 3.6 . """ default = ssl.PROTOCOL_SSLv23 desc = """\ SSL version to use. ============= ============ --ssl-version Description ============= ============ SSLv3 SSLv3 is not-secure and is strongly discouraged. SSLv23 Alias for TLS. Deprecated in Python 3.6, use TLS. TLS Negotiate highest possible version between client/server. Can yield SSL. (Python 3.6+) TLSv1 TLS 1.0 TLSv1_1 TLS 1.1 (Python 3.4+) TLSv1_2 TLS 1.2 (Python 3.4+) TLS_SERVER Auto-negotiate the highest protocol version like TLS, but only support server-side SSLSocket connections. (Python 3.6+) ============= ============ .. versionchanged:: 19.7 The default value has been changed from ``ssl.PROTOCOL_TLSv1`` to ``ssl.PROTOCOL_SSLv23``. .. versionchanged:: 20.0 This setting now accepts string names based on ``ssl.PROTOCOL_`` constants. """ class CertReqs(Setting): name = "cert_reqs" section = "SSL" cli = ["--cert-reqs"] validator = validate_pos_int default = ssl.CERT_NONE desc = """\ Whether client certificate is required (see stdlib ssl module's) """ class CACerts(Setting): name = "ca_certs" section = "SSL" cli = ["--ca-certs"] meta = "FILE" validator = validate_string default = None desc = """\ CA certificates file """ class SuppressRaggedEOFs(Setting): name = "suppress_ragged_eofs" section = "SSL" cli = ["--suppress-ragged-eofs"] action = "store_true" default = True validator = validate_bool desc = """\ Suppress ragged EOFs (see stdlib ssl module's) """ class DoHandshakeOnConnect(Setting): name = "do_handshake_on_connect" section = "SSL" cli = ["--do-handshake-on-connect"] validator = validate_bool action = "store_true" default = False desc = """\ Whether to perform SSL handshake on socket connect (see stdlib ssl module's) """ class Ciphers(Setting): name = "ciphers" section = "SSL" cli = ["--ciphers"] validator = validate_string default = None desc = """\ SSL Cipher suite to use, in the format of an OpenSSL cipher list. By default we use the default cipher list from Python's ``ssl`` module, which contains ciphers considered strong at the time of each Python release. As a recommended alternative, the Open Web App Security Project (OWASP) offers `a vetted set of strong cipher strings rated A+ to C- `_. OWASP provides details on user-agent compatibility at each security level. See the `OpenSSL Cipher List Format Documentation `_ for details on the format of an OpenSSL cipher list. """ class PasteGlobalConf(Setting): name = "raw_paste_global_conf" action = "append" section = "Server Mechanics" cli = ["--paste-global"] meta = "CONF" validator = validate_list_string default = [] desc = """\ Set a PasteDeploy global config variable in ``key=value`` form. The option can be specified multiple times. The variables are passed to the the PasteDeploy entrypoint. Example:: $ gunicorn -b 127.0.0.1:8000 --paste development.ini --paste-global FOO=1 --paste-global BAR=2 .. versionadded:: 19.7 """ class StripHeaderSpaces(Setting): name = "strip_header_spaces" section = "Server Mechanics" cli = ["--strip-header-spaces"] validator = validate_bool action = "store_true" default = False desc = """\ Strip spaces present between the header name and the the ``:``. This is known to induce vulnerabilities and is not compliant with the HTTP/1.1 standard. See https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn. Use with care and only if necessary. """ gunicorn-20.1.0/gunicorn/debug.py000066400000000000000000000043611401157322000167010ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. """The debug module contains utilities and functions for better debugging Gunicorn.""" import sys import linecache import re import inspect __all__ = ['spew', 'unspew'] _token_spliter = re.compile(r'\W+') class Spew(object): def __init__(self, trace_names=None, show_values=True): self.trace_names = trace_names self.show_values = show_values def __call__(self, frame, event, arg): if event == 'line': lineno = frame.f_lineno if '__file__' in frame.f_globals: filename = frame.f_globals['__file__'] if (filename.endswith('.pyc') or filename.endswith('.pyo')): filename = filename[:-1] name = frame.f_globals['__name__'] line = linecache.getline(filename, lineno) else: name = '[unknown]' try: src = inspect.getsourcelines(frame) line = src[lineno] except IOError: line = 'Unknown code named [%s]. VM instruction #%d' % ( frame.f_code.co_name, frame.f_lasti) if self.trace_names is None or name in self.trace_names: print('%s:%s: %s' % (name, lineno, line.rstrip())) if not self.show_values: return self details = [] tokens = _token_spliter.split(line) for tok in tokens: if tok in frame.f_globals: details.append('%s=%r' % (tok, frame.f_globals[tok])) if tok in frame.f_locals: details.append('%s=%r' % (tok, frame.f_locals[tok])) if details: print("\t%s" % ' '.join(details)) return self def spew(trace_names=None, show_values=False): """Install a trace hook which writes incredibly detailed logs about what code is being executed to stdout. """ sys.settrace(Spew(trace_names, show_values)) def unspew(): """Remove the trace hook installed by spew. """ sys.settrace(None) gunicorn-20.1.0/gunicorn/errors.py000066400000000000000000000016271401157322000171310ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. # We don't need to call super() in __init__ methods of our # BaseException and Exception classes because we also define # our own __str__ methods so there is no need to pass 'message' # to the base class to get a meaningful output from 'str(exc)'. # pylint: disable=super-init-not-called # we inherit from BaseException here to make sure to not be caught # at application level class HaltServer(BaseException): def __init__(self, reason, exit_status=1): self.reason = reason self.exit_status = exit_status def __str__(self): return "" % (self.reason, self.exit_status) class ConfigError(Exception): """ Exception raised on config error """ class AppImportError(Exception): """ Exception raised when loading an application """ gunicorn-20.1.0/gunicorn/glogging.py000066400000000000000000000351011401157322000174040ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import base64 import binascii import time import logging logging.Logger.manager.emittedNoHandlerWarning = 1 # noqa from logging.config import dictConfig from logging.config import fileConfig import os import socket import sys import threading import traceback from gunicorn import util # syslog facility codes SYSLOG_FACILITIES = { "auth": 4, "authpriv": 10, "cron": 9, "daemon": 3, "ftp": 11, "kern": 0, "lpr": 6, "mail": 2, "news": 7, "security": 4, # DEPRECATED "syslog": 5, "user": 1, "uucp": 8, "local0": 16, "local1": 17, "local2": 18, "local3": 19, "local4": 20, "local5": 21, "local6": 22, "local7": 23 } CONFIG_DEFAULTS = dict( version=1, disable_existing_loggers=False, root={"level": "INFO", "handlers": ["console"]}, loggers={ "gunicorn.error": { "level": "INFO", "handlers": ["error_console"], "propagate": True, "qualname": "gunicorn.error" }, "gunicorn.access": { "level": "INFO", "handlers": ["console"], "propagate": True, "qualname": "gunicorn.access" } }, handlers={ "console": { "class": "logging.StreamHandler", "formatter": "generic", "stream": "ext://sys.stdout" }, "error_console": { "class": "logging.StreamHandler", "formatter": "generic", "stream": "ext://sys.stderr" }, }, formatters={ "generic": { "format": "%(asctime)s [%(process)d] [%(levelname)s] %(message)s", "datefmt": "[%Y-%m-%d %H:%M:%S %z]", "class": "logging.Formatter" } } ) def loggers(): """ get list of all loggers """ root = logging.root existing = root.manager.loggerDict.keys() return [logging.getLogger(name) for name in existing] class SafeAtoms(dict): def __init__(self, atoms): dict.__init__(self) for key, value in atoms.items(): if isinstance(value, str): self[key] = value.replace('"', '\\"') else: self[key] = value def __getitem__(self, k): if k.startswith("{"): kl = k.lower() if kl in self: return super().__getitem__(kl) else: return "-" if k in self: return super().__getitem__(k) else: return '-' def parse_syslog_address(addr): # unix domain socket type depends on backend # SysLogHandler will try both when given None if addr.startswith("unix://"): sock_type = None # set socket type only if explicitly requested parts = addr.split("#", 1) if len(parts) == 2: addr = parts[0] if parts[1] == "dgram": sock_type = socket.SOCK_DGRAM return (sock_type, addr.split("unix://")[1]) if addr.startswith("udp://"): addr = addr.split("udp://")[1] socktype = socket.SOCK_DGRAM elif addr.startswith("tcp://"): addr = addr.split("tcp://")[1] socktype = socket.SOCK_STREAM else: raise RuntimeError("invalid syslog address") if '[' in addr and ']' in addr: host = addr.split(']')[0][1:].lower() elif ':' in addr: host = addr.split(':')[0].lower() elif addr == "": host = "localhost" else: host = addr.lower() addr = addr.split(']')[-1] if ":" in addr: port = addr.split(':', 1)[1] if not port.isdigit(): raise RuntimeError("%r is not a valid port number." % port) port = int(port) else: port = 514 return (socktype, (host, port)) class Logger(object): LOG_LEVELS = { "critical": logging.CRITICAL, "error": logging.ERROR, "warning": logging.WARNING, "info": logging.INFO, "debug": logging.DEBUG } loglevel = logging.INFO error_fmt = r"%(asctime)s [%(process)d] [%(levelname)s] %(message)s" datefmt = r"[%Y-%m-%d %H:%M:%S %z]" access_fmt = "%(message)s" syslog_fmt = "[%(process)d] %(message)s" atoms_wrapper_class = SafeAtoms def __init__(self, cfg): self.error_log = logging.getLogger("gunicorn.error") self.error_log.propagate = False self.access_log = logging.getLogger("gunicorn.access") self.access_log.propagate = False self.error_handlers = [] self.access_handlers = [] self.logfile = None self.lock = threading.Lock() self.cfg = cfg self.setup(cfg) def setup(self, cfg): self.loglevel = self.LOG_LEVELS.get(cfg.loglevel.lower(), logging.INFO) self.error_log.setLevel(self.loglevel) self.access_log.setLevel(logging.INFO) # set gunicorn.error handler if self.cfg.capture_output and cfg.errorlog != "-": for stream in sys.stdout, sys.stderr: stream.flush() self.logfile = open(cfg.errorlog, 'a+') os.dup2(self.logfile.fileno(), sys.stdout.fileno()) os.dup2(self.logfile.fileno(), sys.stderr.fileno()) self._set_handler(self.error_log, cfg.errorlog, logging.Formatter(self.error_fmt, self.datefmt)) # set gunicorn.access handler if cfg.accesslog is not None: self._set_handler( self.access_log, cfg.accesslog, fmt=logging.Formatter(self.access_fmt), stream=sys.stdout ) # set syslog handler if cfg.syslog: self._set_syslog_handler( self.error_log, cfg, self.syslog_fmt, "error" ) if not cfg.disable_redirect_access_to_syslog: self._set_syslog_handler( self.access_log, cfg, self.syslog_fmt, "access" ) if cfg.logconfig_dict: config = CONFIG_DEFAULTS.copy() config.update(cfg.logconfig_dict) try: dictConfig(config) except ( AttributeError, ImportError, ValueError, TypeError ) as exc: raise RuntimeError(str(exc)) elif cfg.logconfig: if os.path.exists(cfg.logconfig): defaults = CONFIG_DEFAULTS.copy() defaults['__file__'] = cfg.logconfig defaults['here'] = os.path.dirname(cfg.logconfig) fileConfig(cfg.logconfig, defaults=defaults, disable_existing_loggers=False) else: msg = "Error: log config '%s' not found" raise RuntimeError(msg % cfg.logconfig) def critical(self, msg, *args, **kwargs): self.error_log.critical(msg, *args, **kwargs) def error(self, msg, *args, **kwargs): self.error_log.error(msg, *args, **kwargs) def warning(self, msg, *args, **kwargs): self.error_log.warning(msg, *args, **kwargs) def info(self, msg, *args, **kwargs): self.error_log.info(msg, *args, **kwargs) def debug(self, msg, *args, **kwargs): self.error_log.debug(msg, *args, **kwargs) def exception(self, msg, *args, **kwargs): self.error_log.exception(msg, *args, **kwargs) def log(self, lvl, msg, *args, **kwargs): if isinstance(lvl, str): lvl = self.LOG_LEVELS.get(lvl.lower(), logging.INFO) self.error_log.log(lvl, msg, *args, **kwargs) def atoms(self, resp, req, environ, request_time): """ Gets atoms for log formating. """ status = resp.status if isinstance(status, str): status = status.split(None, 1)[0] atoms = { 'h': environ.get('REMOTE_ADDR', '-'), 'l': '-', 'u': self._get_user(environ) or '-', 't': self.now(), 'r': "%s %s %s" % (environ['REQUEST_METHOD'], environ['RAW_URI'], environ["SERVER_PROTOCOL"]), 's': status, 'm': environ.get('REQUEST_METHOD'), 'U': environ.get('PATH_INFO'), 'q': environ.get('QUERY_STRING'), 'H': environ.get('SERVER_PROTOCOL'), 'b': getattr(resp, 'sent', None) is not None and str(resp.sent) or '-', 'B': getattr(resp, 'sent', None), 'f': environ.get('HTTP_REFERER', '-'), 'a': environ.get('HTTP_USER_AGENT', '-'), 'T': request_time.seconds, 'D': (request_time.seconds * 1000000) + request_time.microseconds, 'M': (request_time.seconds * 1000) + int(request_time.microseconds/1000), 'L': "%d.%06d" % (request_time.seconds, request_time.microseconds), 'p': "<%s>" % os.getpid() } # add request headers if hasattr(req, 'headers'): req_headers = req.headers else: req_headers = req if hasattr(req_headers, "items"): req_headers = req_headers.items() atoms.update({"{%s}i" % k.lower(): v for k, v in req_headers}) resp_headers = resp.headers if hasattr(resp_headers, "items"): resp_headers = resp_headers.items() # add response headers atoms.update({"{%s}o" % k.lower(): v for k, v in resp_headers}) # add environ variables environ_variables = environ.items() atoms.update({"{%s}e" % k.lower(): v for k, v in environ_variables}) return atoms def access(self, resp, req, environ, request_time): """ See http://httpd.apache.org/docs/2.0/logs.html#combined for format details """ if not (self.cfg.accesslog or self.cfg.logconfig or self.cfg.logconfig_dict or (self.cfg.syslog and not self.cfg.disable_redirect_access_to_syslog)): return # wrap atoms: # - make sure atoms will be test case insensitively # - if atom doesn't exist replace it by '-' safe_atoms = self.atoms_wrapper_class( self.atoms(resp, req, environ, request_time) ) try: self.access_log.info(self.cfg.access_log_format, safe_atoms) except Exception: self.error(traceback.format_exc()) def now(self): """ return date in Apache Common Log Format """ return time.strftime('[%d/%b/%Y:%H:%M:%S %z]') def reopen_files(self): if self.cfg.capture_output and self.cfg.errorlog != "-": for stream in sys.stdout, sys.stderr: stream.flush() with self.lock: if self.logfile is not None: self.logfile.close() self.logfile = open(self.cfg.errorlog, 'a+') os.dup2(self.logfile.fileno(), sys.stdout.fileno()) os.dup2(self.logfile.fileno(), sys.stderr.fileno()) for log in loggers(): for handler in log.handlers: if isinstance(handler, logging.FileHandler): handler.acquire() try: if handler.stream: handler.close() handler.stream = handler._open() finally: handler.release() def close_on_exec(self): for log in loggers(): for handler in log.handlers: if isinstance(handler, logging.FileHandler): handler.acquire() try: if handler.stream: util.close_on_exec(handler.stream.fileno()) finally: handler.release() def _get_gunicorn_handler(self, log): for h in log.handlers: if getattr(h, "_gunicorn", False): return h def _set_handler(self, log, output, fmt, stream=None): # remove previous gunicorn log handler h = self._get_gunicorn_handler(log) if h: log.handlers.remove(h) if output is not None: if output == "-": h = logging.StreamHandler(stream) else: util.check_is_writeable(output) h = logging.FileHandler(output) # make sure the user can reopen the file try: os.chown(h.baseFilename, self.cfg.user, self.cfg.group) except OSError: # it's probably OK there, we assume the user has given # /dev/null as a parameter. pass h.setFormatter(fmt) h._gunicorn = True log.addHandler(h) def _set_syslog_handler(self, log, cfg, fmt, name): # setup format prefix = cfg.syslog_prefix or cfg.proc_name.replace(":", ".") prefix = "gunicorn.%s.%s" % (prefix, name) # set format fmt = logging.Formatter(r"%s: %s" % (prefix, fmt)) # syslog facility try: facility = SYSLOG_FACILITIES[cfg.syslog_facility.lower()] except KeyError: raise RuntimeError("unknown facility name") # parse syslog address socktype, addr = parse_syslog_address(cfg.syslog_addr) # finally setup the syslog handler h = logging.handlers.SysLogHandler(address=addr, facility=facility, socktype=socktype) h.setFormatter(fmt) h._gunicorn = True log.addHandler(h) def _get_user(self, environ): user = None http_auth = environ.get("HTTP_AUTHORIZATION") if http_auth and http_auth.lower().startswith('basic'): auth = http_auth.split(" ", 1) if len(auth) == 2: try: # b64decode doesn't accept unicode in Python < 3.3 # so we need to convert it to a byte string auth = base64.b64decode(auth[1].strip().encode('utf-8')) # b64decode returns a byte string auth = auth.decode('utf-8') auth = auth.split(":", 1) except (TypeError, binascii.Error, UnicodeDecodeError) as exc: self.debug("Couldn't get username: %s", exc) return user if len(auth) == 2: user = auth[0] return user gunicorn-20.1.0/gunicorn/http/000077500000000000000000000000001401157322000162145ustar00rootroot00000000000000gunicorn-20.1.0/gunicorn/http/__init__.py000066400000000000000000000004251401157322000203260ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. from gunicorn.http.message import Message, Request from gunicorn.http.parser import RequestParser __all__ = ['Message', 'Request', 'RequestParser'] gunicorn-20.1.0/gunicorn/http/body.py000066400000000000000000000162011401157322000175230ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import io import sys from gunicorn.http.errors import (NoMoreData, ChunkMissingTerminator, InvalidChunkSize) class ChunkedReader(object): def __init__(self, req, unreader): self.req = req self.parser = self.parse_chunked(unreader) self.buf = io.BytesIO() def read(self, size): if not isinstance(size, int): raise TypeError("size must be an integral type") if size < 0: raise ValueError("Size must be positive.") if size == 0: return b"" if self.parser: while self.buf.tell() < size: try: self.buf.write(next(self.parser)) except StopIteration: self.parser = None break data = self.buf.getvalue() ret, rest = data[:size], data[size:] self.buf = io.BytesIO() self.buf.write(rest) return ret def parse_trailers(self, unreader, data): buf = io.BytesIO() buf.write(data) idx = buf.getvalue().find(b"\r\n\r\n") done = buf.getvalue()[:2] == b"\r\n" while idx < 0 and not done: self.get_data(unreader, buf) idx = buf.getvalue().find(b"\r\n\r\n") done = buf.getvalue()[:2] == b"\r\n" if done: unreader.unread(buf.getvalue()[2:]) return b"" self.req.trailers = self.req.parse_headers(buf.getvalue()[:idx]) unreader.unread(buf.getvalue()[idx + 4:]) def parse_chunked(self, unreader): (size, rest) = self.parse_chunk_size(unreader) while size > 0: while size > len(rest): size -= len(rest) yield rest rest = unreader.read() if not rest: raise NoMoreData() yield rest[:size] # Remove \r\n after chunk rest = rest[size:] while len(rest) < 2: rest += unreader.read() if rest[:2] != b'\r\n': raise ChunkMissingTerminator(rest[:2]) (size, rest) = self.parse_chunk_size(unreader, data=rest[2:]) def parse_chunk_size(self, unreader, data=None): buf = io.BytesIO() if data is not None: buf.write(data) idx = buf.getvalue().find(b"\r\n") while idx < 0: self.get_data(unreader, buf) idx = buf.getvalue().find(b"\r\n") data = buf.getvalue() line, rest_chunk = data[:idx], data[idx + 2:] chunk_size = line.split(b";", 1)[0].strip() try: chunk_size = int(chunk_size, 16) except ValueError: raise InvalidChunkSize(chunk_size) if chunk_size == 0: try: self.parse_trailers(unreader, rest_chunk) except NoMoreData: pass return (0, None) return (chunk_size, rest_chunk) def get_data(self, unreader, buf): data = unreader.read() if not data: raise NoMoreData() buf.write(data) class LengthReader(object): def __init__(self, unreader, length): self.unreader = unreader self.length = length def read(self, size): if not isinstance(size, int): raise TypeError("size must be an integral type") size = min(self.length, size) if size < 0: raise ValueError("Size must be positive.") if size == 0: return b"" buf = io.BytesIO() data = self.unreader.read() while data: buf.write(data) if buf.tell() >= size: break data = self.unreader.read() buf = buf.getvalue() ret, rest = buf[:size], buf[size:] self.unreader.unread(rest) self.length -= size return ret class EOFReader(object): def __init__(self, unreader): self.unreader = unreader self.buf = io.BytesIO() self.finished = False def read(self, size): if not isinstance(size, int): raise TypeError("size must be an integral type") if size < 0: raise ValueError("Size must be positive.") if size == 0: return b"" if self.finished: data = self.buf.getvalue() ret, rest = data[:size], data[size:] self.buf = io.BytesIO() self.buf.write(rest) return ret data = self.unreader.read() while data: self.buf.write(data) if self.buf.tell() > size: break data = self.unreader.read() if not data: self.finished = True data = self.buf.getvalue() ret, rest = data[:size], data[size:] self.buf = io.BytesIO() self.buf.write(rest) return ret class Body(object): def __init__(self, reader): self.reader = reader self.buf = io.BytesIO() def __iter__(self): return self def __next__(self): ret = self.readline() if not ret: raise StopIteration() return ret next = __next__ def getsize(self, size): if size is None: return sys.maxsize elif not isinstance(size, int): raise TypeError("size must be an integral type") elif size < 0: return sys.maxsize return size def read(self, size=None): size = self.getsize(size) if size == 0: return b"" if size < self.buf.tell(): data = self.buf.getvalue() ret, rest = data[:size], data[size:] self.buf = io.BytesIO() self.buf.write(rest) return ret while size > self.buf.tell(): data = self.reader.read(1024) if not data: break self.buf.write(data) data = self.buf.getvalue() ret, rest = data[:size], data[size:] self.buf = io.BytesIO() self.buf.write(rest) return ret def readline(self, size=None): size = self.getsize(size) if size == 0: return b"" data = self.buf.getvalue() self.buf = io.BytesIO() ret = [] while 1: idx = data.find(b"\n", 0, size) idx = idx + 1 if idx >= 0 else size if len(data) >= size else 0 if idx: ret.append(data[:idx]) self.buf.write(data[idx:]) break ret.append(data) size -= len(data) data = self.reader.read(min(1024, size)) if not data: break return b"".join(ret) def readlines(self, size=None): ret = [] data = self.read() while data: pos = data.find(b"\n") if pos < 0: ret.append(data) data = b"" else: line, data = data[:pos + 1], data[pos + 1:] ret.append(line) return ret gunicorn-20.1.0/gunicorn/http/errors.py000066400000000000000000000054421401157322000201070ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. # We don't need to call super() in __init__ methods of our # BaseException and Exception classes because we also define # our own __str__ methods so there is no need to pass 'message' # to the base class to get a meaningful output from 'str(exc)'. # pylint: disable=super-init-not-called class ParseException(Exception): pass class NoMoreData(IOError): def __init__(self, buf=None): self.buf = buf def __str__(self): return "No more data after: %r" % self.buf class InvalidRequestLine(ParseException): def __init__(self, req): self.req = req self.code = 400 def __str__(self): return "Invalid HTTP request line: %r" % self.req class InvalidRequestMethod(ParseException): def __init__(self, method): self.method = method def __str__(self): return "Invalid HTTP method: %r" % self.method class InvalidHTTPVersion(ParseException): def __init__(self, version): self.version = version def __str__(self): return "Invalid HTTP Version: %r" % self.version class InvalidHeader(ParseException): def __init__(self, hdr, req=None): self.hdr = hdr self.req = req def __str__(self): return "Invalid HTTP Header: %r" % self.hdr class InvalidHeaderName(ParseException): def __init__(self, hdr): self.hdr = hdr def __str__(self): return "Invalid HTTP header name: %r" % self.hdr class InvalidChunkSize(IOError): def __init__(self, data): self.data = data def __str__(self): return "Invalid chunk size: %r" % self.data class ChunkMissingTerminator(IOError): def __init__(self, term): self.term = term def __str__(self): return "Invalid chunk terminator is not '\\r\\n': %r" % self.term class LimitRequestLine(ParseException): def __init__(self, size, max_size): self.size = size self.max_size = max_size def __str__(self): return "Request Line is too large (%s > %s)" % (self.size, self.max_size) class LimitRequestHeaders(ParseException): def __init__(self, msg): self.msg = msg def __str__(self): return self.msg class InvalidProxyLine(ParseException): def __init__(self, line): self.line = line self.code = 400 def __str__(self): return "Invalid PROXY line: %r" % self.line class ForbiddenProxyRequest(ParseException): def __init__(self, host): self.host = host self.code = 403 def __str__(self): return "Proxy request from %r not allowed" % self.host class InvalidSchemeHeaders(ParseException): def __str__(self): return "Contradictory scheme headers" gunicorn-20.1.0/gunicorn/http/message.py000066400000000000000000000267571401157322000202330ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import io import re import socket from gunicorn.http.body import ChunkedReader, LengthReader, EOFReader, Body from gunicorn.http.errors import ( InvalidHeader, InvalidHeaderName, NoMoreData, InvalidRequestLine, InvalidRequestMethod, InvalidHTTPVersion, LimitRequestLine, LimitRequestHeaders, ) from gunicorn.http.errors import InvalidProxyLine, ForbiddenProxyRequest from gunicorn.http.errors import InvalidSchemeHeaders from gunicorn.util import bytes_to_str, split_request_uri MAX_REQUEST_LINE = 8190 MAX_HEADERS = 32768 DEFAULT_MAX_HEADERFIELD_SIZE = 8190 HEADER_RE = re.compile(r"[\x00-\x1F\x7F()<>@,;:\[\]={} \t\\\"]") METH_RE = re.compile(r"[A-Z0-9$-_.]{3,20}") VERSION_RE = re.compile(r"HTTP/(\d+)\.(\d+)") class Message(object): def __init__(self, cfg, unreader, peer_addr): self.cfg = cfg self.unreader = unreader self.peer_addr = peer_addr self.version = None self.headers = [] self.trailers = [] self.body = None self.scheme = "https" if cfg.is_ssl else "http" # set headers limits self.limit_request_fields = cfg.limit_request_fields if (self.limit_request_fields <= 0 or self.limit_request_fields > MAX_HEADERS): self.limit_request_fields = MAX_HEADERS self.limit_request_field_size = cfg.limit_request_field_size if self.limit_request_field_size < 0: self.limit_request_field_size = DEFAULT_MAX_HEADERFIELD_SIZE # set max header buffer size max_header_field_size = self.limit_request_field_size or DEFAULT_MAX_HEADERFIELD_SIZE self.max_buffer_headers = self.limit_request_fields * \ (max_header_field_size + 2) + 4 unused = self.parse(self.unreader) self.unreader.unread(unused) self.set_body_reader() def parse(self, unreader): raise NotImplementedError() def parse_headers(self, data): cfg = self.cfg headers = [] # Split lines on \r\n keeping the \r\n on each line lines = [bytes_to_str(line) + "\r\n" for line in data.split(b"\r\n")] # handle scheme headers scheme_header = False secure_scheme_headers = {} if ('*' in cfg.forwarded_allow_ips or not isinstance(self.peer_addr, tuple) or self.peer_addr[0] in cfg.forwarded_allow_ips): secure_scheme_headers = cfg.secure_scheme_headers # Parse headers into key/value pairs paying attention # to continuation lines. while lines: if len(headers) >= self.limit_request_fields: raise LimitRequestHeaders("limit request headers fields") # Parse initial header name : value pair. curr = lines.pop(0) header_length = len(curr) if curr.find(":") < 0: raise InvalidHeader(curr.strip()) name, value = curr.split(":", 1) if self.cfg.strip_header_spaces: name = name.rstrip(" \t").upper() else: name = name.upper() if HEADER_RE.search(name): raise InvalidHeaderName(name) name, value = name.strip(), [value.lstrip()] # Consume value continuation lines while lines and lines[0].startswith((" ", "\t")): curr = lines.pop(0) header_length += len(curr) if header_length > self.limit_request_field_size > 0: raise LimitRequestHeaders("limit request headers " "fields size") value.append(curr) value = ''.join(value).rstrip() if header_length > self.limit_request_field_size > 0: raise LimitRequestHeaders("limit request headers fields size") if name in secure_scheme_headers: secure = value == secure_scheme_headers[name] scheme = "https" if secure else "http" if scheme_header: if scheme != self.scheme: raise InvalidSchemeHeaders() else: scheme_header = True self.scheme = scheme headers.append((name, value)) return headers def set_body_reader(self): chunked = False content_length = None for (name, value) in self.headers: if name == "CONTENT-LENGTH": if content_length is not None: raise InvalidHeader("CONTENT-LENGTH", req=self) content_length = value elif name == "TRANSFER-ENCODING": if value.lower() == "chunked": chunked = True if chunked: self.body = Body(ChunkedReader(self, self.unreader)) elif content_length is not None: try: content_length = int(content_length) except ValueError: raise InvalidHeader("CONTENT-LENGTH", req=self) if content_length < 0: raise InvalidHeader("CONTENT-LENGTH", req=self) self.body = Body(LengthReader(self.unreader, content_length)) else: self.body = Body(EOFReader(self.unreader)) def should_close(self): for (h, v) in self.headers: if h == "CONNECTION": v = v.lower().strip() if v == "close": return True elif v == "keep-alive": return False break return self.version <= (1, 0) class Request(Message): def __init__(self, cfg, unreader, peer_addr, req_number=1): self.method = None self.uri = None self.path = None self.query = None self.fragment = None # get max request line size self.limit_request_line = cfg.limit_request_line if (self.limit_request_line < 0 or self.limit_request_line >= MAX_REQUEST_LINE): self.limit_request_line = MAX_REQUEST_LINE self.req_number = req_number self.proxy_protocol_info = None super().__init__(cfg, unreader, peer_addr) def get_data(self, unreader, buf, stop=False): data = unreader.read() if not data: if stop: raise StopIteration() raise NoMoreData(buf.getvalue()) buf.write(data) def parse(self, unreader): buf = io.BytesIO() self.get_data(unreader, buf, stop=True) # get request line line, rbuf = self.read_line(unreader, buf, self.limit_request_line) # proxy protocol if self.proxy_protocol(bytes_to_str(line)): # get next request line buf = io.BytesIO() buf.write(rbuf) line, rbuf = self.read_line(unreader, buf, self.limit_request_line) self.parse_request_line(line) buf = io.BytesIO() buf.write(rbuf) # Headers data = buf.getvalue() idx = data.find(b"\r\n\r\n") done = data[:2] == b"\r\n" while True: idx = data.find(b"\r\n\r\n") done = data[:2] == b"\r\n" if idx < 0 and not done: self.get_data(unreader, buf) data = buf.getvalue() if len(data) > self.max_buffer_headers: raise LimitRequestHeaders("max buffer headers") else: break if done: self.unreader.unread(data[2:]) return b"" self.headers = self.parse_headers(data[:idx]) ret = data[idx + 4:] buf = None return ret def read_line(self, unreader, buf, limit=0): data = buf.getvalue() while True: idx = data.find(b"\r\n") if idx >= 0: # check if the request line is too large if idx > limit > 0: raise LimitRequestLine(idx, limit) break if len(data) - 2 > limit > 0: raise LimitRequestLine(len(data), limit) self.get_data(unreader, buf) data = buf.getvalue() return (data[:idx], # request line, data[idx + 2:]) # residue in the buffer, skip \r\n def proxy_protocol(self, line): """\ Detect, check and parse proxy protocol. :raises: ForbiddenProxyRequest, InvalidProxyLine. :return: True for proxy protocol line else False """ if not self.cfg.proxy_protocol: return False if self.req_number != 1: return False if not line.startswith("PROXY"): return False self.proxy_protocol_access_check() self.parse_proxy_protocol(line) return True def proxy_protocol_access_check(self): # check in allow list if ("*" not in self.cfg.proxy_allow_ips and isinstance(self.peer_addr, tuple) and self.peer_addr[0] not in self.cfg.proxy_allow_ips): raise ForbiddenProxyRequest(self.peer_addr[0]) def parse_proxy_protocol(self, line): bits = line.split() if len(bits) != 6: raise InvalidProxyLine(line) # Extract data proto = bits[1] s_addr = bits[2] d_addr = bits[3] # Validation if proto not in ["TCP4", "TCP6"]: raise InvalidProxyLine("protocol '%s' not supported" % proto) if proto == "TCP4": try: socket.inet_pton(socket.AF_INET, s_addr) socket.inet_pton(socket.AF_INET, d_addr) except socket.error: raise InvalidProxyLine(line) elif proto == "TCP6": try: socket.inet_pton(socket.AF_INET6, s_addr) socket.inet_pton(socket.AF_INET6, d_addr) except socket.error: raise InvalidProxyLine(line) try: s_port = int(bits[4]) d_port = int(bits[5]) except ValueError: raise InvalidProxyLine("invalid port %s" % line) if not ((0 <= s_port <= 65535) and (0 <= d_port <= 65535)): raise InvalidProxyLine("invalid port %s" % line) # Set data self.proxy_protocol_info = { "proxy_protocol": proto, "client_addr": s_addr, "client_port": s_port, "proxy_addr": d_addr, "proxy_port": d_port } def parse_request_line(self, line_bytes): bits = [bytes_to_str(bit) for bit in line_bytes.split(None, 2)] if len(bits) != 3: raise InvalidRequestLine(bytes_to_str(line_bytes)) # Method if not METH_RE.match(bits[0]): raise InvalidRequestMethod(bits[0]) self.method = bits[0].upper() # URI self.uri = bits[1] try: parts = split_request_uri(self.uri) except ValueError: raise InvalidRequestLine(bytes_to_str(line_bytes)) self.path = parts.path or "" self.query = parts.query or "" self.fragment = parts.fragment or "" # Version match = VERSION_RE.match(bits[2]) if match is None: raise InvalidHTTPVersion(bits[2]) self.version = (int(match.group(1)), int(match.group(2))) def set_body_reader(self): super().set_body_reader() if isinstance(self.body.reader, EOFReader): self.body = Body(LengthReader(self.unreader, 0)) gunicorn-20.1.0/gunicorn/http/parser.py000066400000000000000000000025241401157322000200650ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. from gunicorn.http.message import Request from gunicorn.http.unreader import SocketUnreader, IterUnreader class Parser(object): mesg_class = None def __init__(self, cfg, source, source_addr): self.cfg = cfg if hasattr(source, "recv"): self.unreader = SocketUnreader(source) else: self.unreader = IterUnreader(source) self.mesg = None self.source_addr = source_addr # request counter (for keepalive connetions) self.req_count = 0 def __iter__(self): return self def __next__(self): # Stop if HTTP dictates a stop. if self.mesg and self.mesg.should_close(): raise StopIteration() # Discard any unread body of the previous message if self.mesg: data = self.mesg.body.read(8192) while data: data = self.mesg.body.read(8192) # Parse the next request self.req_count += 1 self.mesg = self.mesg_class(self.cfg, self.unreader, self.source_addr, self.req_count) if not self.mesg: raise StopIteration() return self.mesg next = __next__ class RequestParser(Parser): mesg_class = Request gunicorn-20.1.0/gunicorn/http/unreader.py000066400000000000000000000036271401157322000204030ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import io import os # Classes that can undo reading data from # a given type of data source. class Unreader(object): def __init__(self): self.buf = io.BytesIO() def chunk(self): raise NotImplementedError() def read(self, size=None): if size is not None and not isinstance(size, int): raise TypeError("size parameter must be an int or long.") if size is not None: if size == 0: return b"" if size < 0: size = None self.buf.seek(0, os.SEEK_END) if size is None and self.buf.tell(): ret = self.buf.getvalue() self.buf = io.BytesIO() return ret if size is None: d = self.chunk() return d while self.buf.tell() < size: chunk = self.chunk() if not chunk: ret = self.buf.getvalue() self.buf = io.BytesIO() return ret self.buf.write(chunk) data = self.buf.getvalue() self.buf = io.BytesIO() self.buf.write(data[size:]) return data[:size] def unread(self, data): self.buf.seek(0, os.SEEK_END) self.buf.write(data) class SocketUnreader(Unreader): def __init__(self, sock, max_chunk=8192): super().__init__() self.sock = sock self.mxchunk = max_chunk def chunk(self): return self.sock.recv(self.mxchunk) class IterUnreader(Unreader): def __init__(self, iterable): super().__init__() self.iter = iter(iterable) def chunk(self): if not self.iter: return b"" try: return next(self.iter) except StopIteration: self.iter = None return b"" gunicorn-20.1.0/gunicorn/http/wsgi.py000066400000000000000000000300501401157322000175350ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import io import logging import os import re import sys from gunicorn.http.message import HEADER_RE from gunicorn.http.errors import InvalidHeader, InvalidHeaderName from gunicorn import SERVER_SOFTWARE, SERVER import gunicorn.util as util # Send files in at most 1GB blocks as some operating systems can have problems # with sending files in blocks over 2GB. BLKSIZE = 0x3FFFFFFF HEADER_VALUE_RE = re.compile(r'[\x00-\x1F\x7F]') log = logging.getLogger(__name__) class FileWrapper(object): def __init__(self, filelike, blksize=8192): self.filelike = filelike self.blksize = blksize if hasattr(filelike, 'close'): self.close = filelike.close def __getitem__(self, key): data = self.filelike.read(self.blksize) if data: return data raise IndexError class WSGIErrorsWrapper(io.RawIOBase): def __init__(self, cfg): # There is no public __init__ method for RawIOBase so # we don't need to call super() in the __init__ method. # pylint: disable=super-init-not-called errorlog = logging.getLogger("gunicorn.error") handlers = errorlog.handlers self.streams = [] if cfg.errorlog == "-": self.streams.append(sys.stderr) handlers = handlers[1:] for h in handlers: if hasattr(h, "stream"): self.streams.append(h.stream) def write(self, data): for stream in self.streams: try: stream.write(data) except UnicodeError: stream.write(data.encode("UTF-8")) stream.flush() def base_environ(cfg): return { "wsgi.errors": WSGIErrorsWrapper(cfg), "wsgi.version": (1, 0), "wsgi.multithread": False, "wsgi.multiprocess": (cfg.workers > 1), "wsgi.run_once": False, "wsgi.file_wrapper": FileWrapper, "wsgi.input_terminated": True, "SERVER_SOFTWARE": SERVER_SOFTWARE, } def default_environ(req, sock, cfg): env = base_environ(cfg) env.update({ "wsgi.input": req.body, "gunicorn.socket": sock, "REQUEST_METHOD": req.method, "QUERY_STRING": req.query, "RAW_URI": req.uri, "SERVER_PROTOCOL": "HTTP/%s" % ".".join([str(v) for v in req.version]) }) return env def proxy_environ(req): info = req.proxy_protocol_info if not info: return {} return { "PROXY_PROTOCOL": info["proxy_protocol"], "REMOTE_ADDR": info["client_addr"], "REMOTE_PORT": str(info["client_port"]), "PROXY_ADDR": info["proxy_addr"], "PROXY_PORT": str(info["proxy_port"]), } def create(req, sock, client, server, cfg): resp = Response(req, sock, cfg) # set initial environ environ = default_environ(req, sock, cfg) # default variables host = None script_name = os.environ.get("SCRIPT_NAME", "") # add the headers to the environ for hdr_name, hdr_value in req.headers: if hdr_name == "EXPECT": # handle expect if hdr_value.lower() == "100-continue": sock.send(b"HTTP/1.1 100 Continue\r\n\r\n") elif hdr_name == 'HOST': host = hdr_value elif hdr_name == "SCRIPT_NAME": script_name = hdr_value elif hdr_name == "CONTENT-TYPE": environ['CONTENT_TYPE'] = hdr_value continue elif hdr_name == "CONTENT-LENGTH": environ['CONTENT_LENGTH'] = hdr_value continue key = 'HTTP_' + hdr_name.replace('-', '_') if key in environ: hdr_value = "%s,%s" % (environ[key], hdr_value) environ[key] = hdr_value # set the url scheme environ['wsgi.url_scheme'] = req.scheme # set the REMOTE_* keys in environ # authors should be aware that REMOTE_HOST and REMOTE_ADDR # may not qualify the remote addr: # http://www.ietf.org/rfc/rfc3875 if isinstance(client, str): environ['REMOTE_ADDR'] = client elif isinstance(client, bytes): environ['REMOTE_ADDR'] = client.decode() else: environ['REMOTE_ADDR'] = client[0] environ['REMOTE_PORT'] = str(client[1]) # handle the SERVER_* # Normally only the application should use the Host header but since the # WSGI spec doesn't support unix sockets, we are using it to create # viable SERVER_* if possible. if isinstance(server, str): server = server.split(":") if len(server) == 1: # unix socket if host: server = host.split(':') if len(server) == 1: if req.scheme == "http": server.append(80) elif req.scheme == "https": server.append(443) else: server.append('') else: # no host header given which means that we are not behind a # proxy, so append an empty port. server.append('') environ['SERVER_NAME'] = server[0] environ['SERVER_PORT'] = str(server[1]) # set the path and script name path_info = req.path if script_name: path_info = path_info.split(script_name, 1)[1] environ['PATH_INFO'] = util.unquote_to_wsgi_str(path_info) environ['SCRIPT_NAME'] = script_name # override the environ with the correct remote and server address if # we are behind a proxy using the proxy protocol. environ.update(proxy_environ(req)) return resp, environ class Response(object): def __init__(self, req, sock, cfg): self.req = req self.sock = sock self.version = SERVER self.status = None self.chunked = False self.must_close = False self.headers = [] self.headers_sent = False self.response_length = None self.sent = 0 self.upgrade = False self.cfg = cfg def force_close(self): self.must_close = True def should_close(self): if self.must_close or self.req.should_close(): return True if self.response_length is not None or self.chunked: return False if self.req.method == 'HEAD': return False if self.status_code < 200 or self.status_code in (204, 304): return False return True def start_response(self, status, headers, exc_info=None): if exc_info: try: if self.status and self.headers_sent: util.reraise(exc_info[0], exc_info[1], exc_info[2]) finally: exc_info = None elif self.status is not None: raise AssertionError("Response headers already set!") self.status = status # get the status code from the response here so we can use it to check # the need for the connection header later without parsing the string # each time. try: self.status_code = int(self.status.split()[0]) except ValueError: self.status_code = None self.process_headers(headers) self.chunked = self.is_chunked() return self.write def process_headers(self, headers): for name, value in headers: if not isinstance(name, str): raise TypeError('%r is not a string' % name) if HEADER_RE.search(name): raise InvalidHeaderName('%r' % name) if not isinstance(value, str): raise TypeError('%r is not a string' % value) if HEADER_VALUE_RE.search(value): raise InvalidHeader('%r' % value) value = value.strip() lname = name.lower().strip() if lname == "content-length": self.response_length = int(value) elif util.is_hoppish(name): if lname == "connection": # handle websocket if value.lower().strip() == "upgrade": self.upgrade = True elif lname == "upgrade": if value.lower().strip() == "websocket": self.headers.append((name.strip(), value)) # ignore hopbyhop headers continue self.headers.append((name.strip(), value)) def is_chunked(self): # Only use chunked responses when the client is # speaking HTTP/1.1 or newer and there was # no Content-Length header set. if self.response_length is not None: return False elif self.req.version <= (1, 0): return False elif self.req.method == 'HEAD': # Responses to a HEAD request MUST NOT contain a response body. return False elif self.status_code in (204, 304): # Do not use chunked responses when the response is guaranteed to # not have a response body. return False return True def default_headers(self): # set the connection header if self.upgrade: connection = "upgrade" elif self.should_close(): connection = "close" else: connection = "keep-alive" headers = [ "HTTP/%s.%s %s\r\n" % (self.req.version[0], self.req.version[1], self.status), "Server: %s\r\n" % self.version, "Date: %s\r\n" % util.http_date(), "Connection: %s\r\n" % connection ] if self.chunked: headers.append("Transfer-Encoding: chunked\r\n") return headers def send_headers(self): if self.headers_sent: return tosend = self.default_headers() tosend.extend(["%s: %s\r\n" % (k, v) for k, v in self.headers]) header_str = "%s\r\n" % "".join(tosend) util.write(self.sock, util.to_bytestring(header_str, "latin-1")) self.headers_sent = True def write(self, arg): self.send_headers() if not isinstance(arg, bytes): raise TypeError('%r is not a byte' % arg) arglen = len(arg) tosend = arglen if self.response_length is not None: if self.sent >= self.response_length: # Never write more than self.response_length bytes return tosend = min(self.response_length - self.sent, tosend) if tosend < arglen: arg = arg[:tosend] # Sending an empty chunk signals the end of the # response and prematurely closes the response if self.chunked and tosend == 0: return self.sent += tosend util.write(self.sock, arg, self.chunked) def can_sendfile(self): return self.cfg.sendfile is not False def sendfile(self, respiter): if self.cfg.is_ssl or not self.can_sendfile(): return False if not util.has_fileno(respiter.filelike): return False fileno = respiter.filelike.fileno() try: offset = os.lseek(fileno, 0, os.SEEK_CUR) if self.response_length is None: filesize = os.fstat(fileno).st_size nbytes = filesize - offset else: nbytes = self.response_length except (OSError, io.UnsupportedOperation): return False self.send_headers() if self.is_chunked(): chunk_size = "%X\r\n" % nbytes self.sock.sendall(chunk_size.encode('utf-8')) self.sock.sendfile(respiter.filelike, count=nbytes) if self.is_chunked(): self.sock.sendall(b"\r\n") os.lseek(fileno, offset, os.SEEK_SET) return True def write_file(self, respiter): if not self.sendfile(respiter): for item in respiter: self.write(item) def close(self): if not self.headers_sent: self.send_headers() if self.chunked: util.write_chunk(self.sock, b"") gunicorn-20.1.0/gunicorn/instrument/000077500000000000000000000000001401157322000174455ustar00rootroot00000000000000gunicorn-20.1.0/gunicorn/instrument/__init__.py000066400000000000000000000000001401157322000215440ustar00rootroot00000000000000gunicorn-20.1.0/gunicorn/instrument/statsd.py000066400000000000000000000110311401157322000213150ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. "Bare-bones implementation of statsD's protocol, client-side" import logging import socket from re import sub from gunicorn.glogging import Logger # Instrumentation constants METRIC_VAR = "metric" VALUE_VAR = "value" MTYPE_VAR = "mtype" GAUGE_TYPE = "gauge" COUNTER_TYPE = "counter" HISTOGRAM_TYPE = "histogram" class Statsd(Logger): """statsD-based instrumentation, that passes as a logger """ def __init__(self, cfg): """host, port: statsD server """ Logger.__init__(self, cfg) self.prefix = sub(r"^(.+[^.]+)\.*$", "\\g<1>.", cfg.statsd_prefix) try: host, port = cfg.statsd_host self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock.connect((host, int(port))) except Exception: self.sock = None self.dogstatsd_tags = cfg.dogstatsd_tags # Log errors and warnings def critical(self, msg, *args, **kwargs): Logger.critical(self, msg, *args, **kwargs) self.increment("gunicorn.log.critical", 1) def error(self, msg, *args, **kwargs): Logger.error(self, msg, *args, **kwargs) self.increment("gunicorn.log.error", 1) def warning(self, msg, *args, **kwargs): Logger.warning(self, msg, *args, **kwargs) self.increment("gunicorn.log.warning", 1) def exception(self, msg, *args, **kwargs): Logger.exception(self, msg, *args, **kwargs) self.increment("gunicorn.log.exception", 1) # Special treatment for info, the most common log level def info(self, msg, *args, **kwargs): self.log(logging.INFO, msg, *args, **kwargs) # skip the run-of-the-mill logs def debug(self, msg, *args, **kwargs): self.log(logging.DEBUG, msg, *args, **kwargs) def log(self, lvl, msg, *args, **kwargs): """Log a given statistic if metric, value and type are present """ try: extra = kwargs.get("extra", None) if extra is not None: metric = extra.get(METRIC_VAR, None) value = extra.get(VALUE_VAR, None) typ = extra.get(MTYPE_VAR, None) if metric and value and typ: if typ == GAUGE_TYPE: self.gauge(metric, value) elif typ == COUNTER_TYPE: self.increment(metric, value) elif typ == HISTOGRAM_TYPE: self.histogram(metric, value) else: pass # Log to parent logger only if there is something to say if msg: Logger.log(self, lvl, msg, *args, **kwargs) except Exception: Logger.warning(self, "Failed to log to statsd", exc_info=True) # access logging def access(self, resp, req, environ, request_time): """Measure request duration request_time is a datetime.timedelta """ Logger.access(self, resp, req, environ, request_time) duration_in_ms = request_time.seconds * 1000 + float(request_time.microseconds) / 10 ** 3 status = resp.status if isinstance(status, str): status = int(status.split(None, 1)[0]) self.histogram("gunicorn.request.duration", duration_in_ms) self.increment("gunicorn.requests", 1) self.increment("gunicorn.request.status.%d" % status, 1) # statsD methods # you can use those directly if you want def gauge(self, name, value): self._sock_send("{0}{1}:{2}|g".format(self.prefix, name, value)) def increment(self, name, value, sampling_rate=1.0): self._sock_send("{0}{1}:{2}|c|@{3}".format(self.prefix, name, value, sampling_rate)) def decrement(self, name, value, sampling_rate=1.0): self._sock_send("{0}{1}:-{2}|c|@{3}".format(self.prefix, name, value, sampling_rate)) def histogram(self, name, value): self._sock_send("{0}{1}:{2}|ms".format(self.prefix, name, value)) def _sock_send(self, msg): try: if isinstance(msg, str): msg = msg.encode("ascii") # http://docs.datadoghq.com/guides/dogstatsd/#datagram-format if self.dogstatsd_tags: msg = msg + b"|#" + self.dogstatsd_tags.encode('ascii') if self.sock: self.sock.send(msg) except Exception: Logger.warning(self, "Error sending message to statsd", exc_info=True) gunicorn-20.1.0/gunicorn/pidfile.py000066400000000000000000000044771401157322000172370ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import errno import os import tempfile class Pidfile(object): """\ Manage a PID file. If a specific name is provided it and '"%s.oldpid" % name' will be used. Otherwise we create a temp file using os.mkstemp. """ def __init__(self, fname): self.fname = fname self.pid = None def create(self, pid): oldpid = self.validate() if oldpid: if oldpid == os.getpid(): return msg = "Already running on PID %s (or pid file '%s' is stale)" raise RuntimeError(msg % (oldpid, self.fname)) self.pid = pid # Write pidfile fdir = os.path.dirname(self.fname) if fdir and not os.path.isdir(fdir): raise RuntimeError("%s doesn't exist. Can't create pidfile." % fdir) fd, fname = tempfile.mkstemp(dir=fdir) os.write(fd, ("%s\n" % self.pid).encode('utf-8')) if self.fname: os.rename(fname, self.fname) else: self.fname = fname os.close(fd) # set permissions to -rw-r--r-- os.chmod(self.fname, 420) def rename(self, path): self.unlink() self.fname = path self.create(self.pid) def unlink(self): """ delete pidfile""" try: with open(self.fname, "r") as f: pid1 = int(f.read() or 0) if pid1 == self.pid: os.unlink(self.fname) except Exception: pass def validate(self): """ Validate pidfile and make it stale if needed""" if not self.fname: return try: with open(self.fname, "r") as f: try: wpid = int(f.read()) except ValueError: return try: os.kill(wpid, 0) return wpid except OSError as e: if e.args[0] == errno.EPERM: return wpid if e.args[0] == errno.ESRCH: return raise except IOError as e: if e.args[0] == errno.ENOENT: return raise gunicorn-20.1.0/gunicorn/reloader.py000066400000000000000000000073011401157322000174050ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. # pylint: disable=no-else-continue import os import os.path import re import sys import time import threading COMPILED_EXT_RE = re.compile(r'py[co]$') class Reloader(threading.Thread): def __init__(self, extra_files=None, interval=1, callback=None): super().__init__() self.setDaemon(True) self._extra_files = set(extra_files or ()) self._interval = interval self._callback = callback def add_extra_file(self, filename): self._extra_files.add(filename) def get_files(self): fnames = [ COMPILED_EXT_RE.sub('py', module.__file__) for module in tuple(sys.modules.values()) if getattr(module, '__file__', None) ] fnames.extend(self._extra_files) return fnames def run(self): mtimes = {} while True: for filename in self.get_files(): try: mtime = os.stat(filename).st_mtime except OSError: continue old_time = mtimes.get(filename) if old_time is None: mtimes[filename] = mtime continue elif mtime > old_time: if self._callback: self._callback(filename) time.sleep(self._interval) has_inotify = False if sys.platform.startswith('linux'): try: from inotify.adapters import Inotify import inotify.constants has_inotify = True except ImportError: pass if has_inotify: class InotifyReloader(threading.Thread): event_mask = (inotify.constants.IN_CREATE | inotify.constants.IN_DELETE | inotify.constants.IN_DELETE_SELF | inotify.constants.IN_MODIFY | inotify.constants.IN_MOVE_SELF | inotify.constants.IN_MOVED_FROM | inotify.constants.IN_MOVED_TO) def __init__(self, extra_files=None, callback=None): super().__init__() self.setDaemon(True) self._callback = callback self._dirs = set() self._watcher = Inotify() for extra_file in extra_files: self.add_extra_file(extra_file) def add_extra_file(self, filename): dirname = os.path.dirname(filename) if dirname in self._dirs: return self._watcher.add_watch(dirname, mask=self.event_mask) self._dirs.add(dirname) def get_dirs(self): fnames = [ os.path.dirname(os.path.abspath(COMPILED_EXT_RE.sub('py', module.__file__))) for module in tuple(sys.modules.values()) if getattr(module, '__file__', None) ] return set(fnames) def run(self): self._dirs = self.get_dirs() for dirname in self._dirs: if os.path.isdir(dirname): self._watcher.add_watch(dirname, mask=self.event_mask) for event in self._watcher.event_gen(): if event is None: continue filename = event[3] self._callback(filename) else: class InotifyReloader(object): def __init__(self, callback=None): raise ImportError('You must have the inotify module installed to ' 'use the inotify reloader') preferred_reloader = InotifyReloader if has_inotify else Reloader reloader_engines = { 'auto': preferred_reloader, 'poll': Reloader, 'inotify': InotifyReloader, } gunicorn-20.1.0/gunicorn/sock.py000066400000000000000000000137361401157322000165600ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import errno import os import socket import stat import sys import time from gunicorn import util class BaseSocket(object): def __init__(self, address, conf, log, fd=None): self.log = log self.conf = conf self.cfg_addr = address if fd is None: sock = socket.socket(self.FAMILY, socket.SOCK_STREAM) bound = False else: sock = socket.fromfd(fd, self.FAMILY, socket.SOCK_STREAM) os.close(fd) bound = True self.sock = self.set_options(sock, bound=bound) def __str__(self): return "" % self.sock.fileno() def __getattr__(self, name): return getattr(self.sock, name) def set_options(self, sock, bound=False): sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) if (self.conf.reuse_port and hasattr(socket, 'SO_REUSEPORT')): # pragma: no cover try: sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) except socket.error as err: if err.errno not in (errno.ENOPROTOOPT, errno.EINVAL): raise if not bound: self.bind(sock) sock.setblocking(0) # make sure that the socket can be inherited if hasattr(sock, "set_inheritable"): sock.set_inheritable(True) sock.listen(self.conf.backlog) return sock def bind(self, sock): sock.bind(self.cfg_addr) def close(self): if self.sock is None: return try: self.sock.close() except socket.error as e: self.log.info("Error while closing socket %s", str(e)) self.sock = None class TCPSocket(BaseSocket): FAMILY = socket.AF_INET def __str__(self): if self.conf.is_ssl: scheme = "https" else: scheme = "http" addr = self.sock.getsockname() return "%s://%s:%d" % (scheme, addr[0], addr[1]) def set_options(self, sock, bound=False): sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) return super().set_options(sock, bound=bound) class TCP6Socket(TCPSocket): FAMILY = socket.AF_INET6 def __str__(self): (host, port, _, _) = self.sock.getsockname() return "http://[%s]:%d" % (host, port) class UnixSocket(BaseSocket): FAMILY = socket.AF_UNIX def __init__(self, addr, conf, log, fd=None): if fd is None: try: st = os.stat(addr) except OSError as e: if e.args[0] != errno.ENOENT: raise else: if stat.S_ISSOCK(st.st_mode): os.remove(addr) else: raise ValueError("%r is not a socket" % addr) super().__init__(addr, conf, log, fd=fd) def __str__(self): return "unix:%s" % self.cfg_addr def bind(self, sock): old_umask = os.umask(self.conf.umask) sock.bind(self.cfg_addr) util.chown(self.cfg_addr, self.conf.uid, self.conf.gid) os.umask(old_umask) def _sock_type(addr): if isinstance(addr, tuple): if util.is_ipv6(addr[0]): sock_type = TCP6Socket else: sock_type = TCPSocket elif isinstance(addr, (str, bytes)): sock_type = UnixSocket else: raise TypeError("Unable to create socket from: %r" % addr) return sock_type def create_sockets(conf, log, fds=None): """ Create a new socket for the configured addresses or file descriptors. If a configured address is a tuple then a TCP socket is created. If it is a string, a Unix socket is created. Otherwise, a TypeError is raised. """ listeners = [] # get it only once addr = conf.address fdaddr = [bind for bind in addr if isinstance(bind, int)] if fds: fdaddr += list(fds) laddr = [bind for bind in addr if not isinstance(bind, int)] # check ssl config early to raise the error on startup # only the certfile is needed since it can contains the keyfile if conf.certfile and not os.path.exists(conf.certfile): raise ValueError('certfile "%s" does not exist' % conf.certfile) if conf.keyfile and not os.path.exists(conf.keyfile): raise ValueError('keyfile "%s" does not exist' % conf.keyfile) # sockets are already bound if fdaddr: for fd in fdaddr: sock = socket.fromfd(fd, socket.AF_UNIX, socket.SOCK_STREAM) sock_name = sock.getsockname() sock_type = _sock_type(sock_name) listener = sock_type(sock_name, conf, log, fd=fd) listeners.append(listener) return listeners # no sockets is bound, first initialization of gunicorn in this env. for addr in laddr: sock_type = _sock_type(addr) sock = None for i in range(5): try: sock = sock_type(addr, conf, log) except socket.error as e: if e.args[0] == errno.EADDRINUSE: log.error("Connection in use: %s", str(addr)) if e.args[0] == errno.EADDRNOTAVAIL: log.error("Invalid address: %s", str(addr)) if i < 5: msg = "connection to {addr} failed: {error}" log.debug(msg.format(addr=str(addr), error=str(e))) log.error("Retrying in 1 second.") time.sleep(1) else: break if sock is None: log.error("Can't connect to %s", str(addr)) sys.exit(1) listeners.append(sock) return listeners def close_sockets(listeners, unlink=True): for sock in listeners: sock_name = sock.getsockname() sock.close() if unlink and _sock_type(sock_name) is UnixSocket: os.unlink(sock_name) gunicorn-20.1.0/gunicorn/systemd.py000066400000000000000000000047171401157322000173100ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import os import socket SD_LISTEN_FDS_START = 3 def listen_fds(unset_environment=True): """ Get the number of sockets inherited from systemd socket activation. :param unset_environment: clear systemd environment variables unless False :type unset_environment: bool :return: the number of sockets to inherit from systemd socket activation :rtype: int Returns zero immediately if $LISTEN_PID is not set to the current pid. Otherwise, returns the number of systemd activation sockets specified by $LISTEN_FDS. When $LISTEN_PID matches the current pid, unsets the environment variables unless the ``unset_environment`` flag is ``False``. .. note:: Unlike the sd_listen_fds C function, this implementation does not set the FD_CLOEXEC flag because the gunicorn arbiter never needs to do this. .. seealso:: ``_ """ fds = int(os.environ.get('LISTEN_FDS', 0)) listen_pid = int(os.environ.get('LISTEN_PID', 0)) if listen_pid != os.getpid(): return 0 if unset_environment: os.environ.pop('LISTEN_PID', None) os.environ.pop('LISTEN_FDS', None) return fds def sd_notify(state, logger, unset_environment=False): """Send a notification to systemd. state is a string; see the man page of sd_notify (http://www.freedesktop.org/software/systemd/man/sd_notify.html) for a description of the allowable values. If the unset_environment parameter is True, sd_notify() will unset the $NOTIFY_SOCKET environment variable before returning (regardless of whether the function call itself succeeded or not). Further calls to sd_notify() will then fail, but the variable is no longer inherited by child processes. """ addr = os.environ.get('NOTIFY_SOCKET') if addr is None: # not run in a service, just a noop return try: sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM | socket.SOCK_CLOEXEC) if addr[0] == '@': addr = '\0' + addr[1:] sock.connect(addr) sock.sendall(state.encode('utf-8')) except: logger.debug("Exception while invoking sd_notify()", exc_info=True) finally: if unset_environment: os.environ.pop('NOTIFY_SOCKET') sock.close() gunicorn-20.1.0/gunicorn/util.py000066400000000000000000000441241401157322000165710ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import ast import email.utils import errno import fcntl import html import importlib import inspect import io import logging import os import pwd import random import re import socket import sys import textwrap import time import traceback import warnings import pkg_resources from gunicorn.errors import AppImportError from gunicorn.workers import SUPPORTED_WORKERS import urllib.parse REDIRECT_TO = getattr(os, 'devnull', '/dev/null') # Server and Date aren't technically hop-by-hop # headers, but they are in the purview of the # origin server which the WSGI spec says we should # act like. So we drop them and add our own. # # In the future, concatenation server header values # might be better, but nothing else does it and # dropping them is easier. hop_headers = set(""" connection keep-alive proxy-authenticate proxy-authorization te trailers transfer-encoding upgrade server date """.split()) try: from setproctitle import setproctitle def _setproctitle(title): setproctitle("gunicorn: %s" % title) except ImportError: def _setproctitle(title): pass def load_class(uri, default="gunicorn.workers.sync.SyncWorker", section="gunicorn.workers"): if inspect.isclass(uri): return uri if uri.startswith("egg:"): # uses entry points entry_str = uri.split("egg:")[1] try: dist, name = entry_str.rsplit("#", 1) except ValueError: dist = entry_str name = default try: return pkg_resources.load_entry_point(dist, section, name) except Exception: exc = traceback.format_exc() msg = "class uri %r invalid or not found: \n\n[%s]" raise RuntimeError(msg % (uri, exc)) else: components = uri.split('.') if len(components) == 1: while True: if uri.startswith("#"): uri = uri[1:] if uri in SUPPORTED_WORKERS: components = SUPPORTED_WORKERS[uri].split(".") break try: return pkg_resources.load_entry_point( "gunicorn", section, uri ) except Exception: exc = traceback.format_exc() msg = "class uri %r invalid or not found: \n\n[%s]" raise RuntimeError(msg % (uri, exc)) klass = components.pop(-1) try: mod = importlib.import_module('.'.join(components)) except: exc = traceback.format_exc() msg = "class uri %r invalid or not found: \n\n[%s]" raise RuntimeError(msg % (uri, exc)) return getattr(mod, klass) positionals = ( inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD, ) def get_arity(f): sig = inspect.signature(f) arity = 0 for param in sig.parameters.values(): if param.kind in positionals: arity += 1 return arity def get_username(uid): """ get the username for a user id""" return pwd.getpwuid(uid).pw_name def set_owner_process(uid, gid, initgroups=False): """ set user and group of workers processes """ if gid: if uid: try: username = get_username(uid) except KeyError: initgroups = False # versions of python < 2.6.2 don't manage unsigned int for # groups like on osx or fedora gid = abs(gid) & 0x7FFFFFFF if initgroups: os.initgroups(username, gid) elif gid != os.getgid(): os.setgid(gid) if uid: os.setuid(uid) def chown(path, uid, gid): os.chown(path, uid, gid) if sys.platform.startswith("win"): def _waitfor(func, pathname, waitall=False): # Perform the operation func(pathname) # Now setup the wait loop if waitall: dirname = pathname else: dirname, name = os.path.split(pathname) dirname = dirname or '.' # Check for `pathname` to be removed from the filesystem. # The exponential backoff of the timeout amounts to a total # of ~1 second after which the deletion is probably an error # anyway. # Testing on a i7@4.3GHz shows that usually only 1 iteration is # required when contention occurs. timeout = 0.001 while timeout < 1.0: # Note we are only testing for the existence of the file(s) in # the contents of the directory regardless of any security or # access rights. If we have made it this far, we have sufficient # permissions to do that much using Python's equivalent of the # Windows API FindFirstFile. # Other Windows APIs can fail or give incorrect results when # dealing with files that are pending deletion. L = os.listdir(dirname) if not L if waitall else name in L: return # Increase the timeout and try again time.sleep(timeout) timeout *= 2 warnings.warn('tests may fail, delete still pending for ' + pathname, RuntimeWarning, stacklevel=4) def _unlink(filename): _waitfor(os.unlink, filename) else: _unlink = os.unlink def unlink(filename): try: _unlink(filename) except OSError as error: # The filename need not exist. if error.errno not in (errno.ENOENT, errno.ENOTDIR): raise def is_ipv6(addr): try: socket.inet_pton(socket.AF_INET6, addr) except socket.error: # not a valid address return False except ValueError: # ipv6 not supported on this platform return False return True def parse_address(netloc, default_port='8000'): if re.match(r'unix:(//)?', netloc): return re.split(r'unix:(//)?', netloc)[-1] if netloc.startswith("fd://"): fd = netloc[5:] try: return int(fd) except ValueError: raise RuntimeError("%r is not a valid file descriptor." % fd) from None if netloc.startswith("tcp://"): netloc = netloc.split("tcp://")[1] host, port = netloc, default_port if '[' in netloc and ']' in netloc: host = netloc.split(']')[0][1:] port = (netloc.split(']:') + [default_port])[1] elif ':' in netloc: host, port = (netloc.split(':') + [default_port])[:2] elif netloc == "": host, port = "0.0.0.0", default_port try: port = int(port) except ValueError: raise RuntimeError("%r is not a valid port number." % port) return host.lower(), port def close_on_exec(fd): flags = fcntl.fcntl(fd, fcntl.F_GETFD) flags |= fcntl.FD_CLOEXEC fcntl.fcntl(fd, fcntl.F_SETFD, flags) def set_non_blocking(fd): flags = fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK fcntl.fcntl(fd, fcntl.F_SETFL, flags) def close(sock): try: sock.close() except socket.error: pass try: from os import closerange except ImportError: def closerange(fd_low, fd_high): # Iterate through and close all file descriptors. for fd in range(fd_low, fd_high): try: os.close(fd) except OSError: # ERROR, fd wasn't open to begin with (ignored) pass def write_chunk(sock, data): if isinstance(data, str): data = data.encode('utf-8') chunk_size = "%X\r\n" % len(data) chunk = b"".join([chunk_size.encode('utf-8'), data, b"\r\n"]) sock.sendall(chunk) def write(sock, data, chunked=False): if chunked: return write_chunk(sock, data) sock.sendall(data) def write_nonblock(sock, data, chunked=False): timeout = sock.gettimeout() if timeout != 0.0: try: sock.setblocking(0) return write(sock, data, chunked) finally: sock.setblocking(1) else: return write(sock, data, chunked) def write_error(sock, status_int, reason, mesg): html_error = textwrap.dedent("""\ %(reason)s

%(reason)s

%(mesg)s """) % {"reason": reason, "mesg": html.escape(mesg)} http = textwrap.dedent("""\ HTTP/1.1 %s %s\r Connection: close\r Content-Type: text/html\r Content-Length: %d\r \r %s""") % (str(status_int), reason, len(html_error), html_error) write_nonblock(sock, http.encode('latin1')) def _called_with_wrong_args(f): """Check whether calling a function raised a ``TypeError`` because the call failed or because something in the function raised the error. :param f: The function that was called. :return: ``True`` if the call failed. """ tb = sys.exc_info()[2] try: while tb is not None: if tb.tb_frame.f_code is f.__code__: # In the function, it was called successfully. return False tb = tb.tb_next # Didn't reach the function. return True finally: # Delete tb to break a circular reference in Python 2. # https://docs.python.org/2/library/sys.html#sys.exc_info del tb def import_app(module): parts = module.split(":", 1) if len(parts) == 1: obj = "application" else: module, obj = parts[0], parts[1] try: mod = importlib.import_module(module) except ImportError: if module.endswith(".py") and os.path.exists(module): msg = "Failed to find application, did you mean '%s:%s'?" raise ImportError(msg % (module.rsplit(".", 1)[0], obj)) raise # Parse obj as a single expression to determine if it's a valid # attribute name or function call. try: expression = ast.parse(obj, mode="eval").body except SyntaxError: raise AppImportError( "Failed to parse %r as an attribute name or function call." % obj ) if isinstance(expression, ast.Name): name = expression.id args = kwargs = None elif isinstance(expression, ast.Call): # Ensure the function name is an attribute name only. if not isinstance(expression.func, ast.Name): raise AppImportError("Function reference must be a simple name: %r" % obj) name = expression.func.id # Parse the positional and keyword arguments as literals. try: args = [ast.literal_eval(arg) for arg in expression.args] kwargs = {kw.arg: ast.literal_eval(kw.value) for kw in expression.keywords} except ValueError: # literal_eval gives cryptic error messages, show a generic # message with the full expression instead. raise AppImportError( "Failed to parse arguments as literal values: %r" % obj ) else: raise AppImportError( "Failed to parse %r as an attribute name or function call." % obj ) is_debug = logging.root.level == logging.DEBUG try: app = getattr(mod, name) except AttributeError: if is_debug: traceback.print_exception(*sys.exc_info()) raise AppImportError("Failed to find attribute %r in %r." % (name, module)) # If the expression was a function call, call the retrieved object # to get the real application. if args is not None: try: app = app(*args, **kwargs) except TypeError as e: # If the TypeError was due to bad arguments to the factory # function, show Python's nice error message without a # traceback. if _called_with_wrong_args(app): raise AppImportError( "".join(traceback.format_exception_only(TypeError, e)).strip() ) # Otherwise it was raised from within the function, show the # full traceback. raise if app is None: raise AppImportError("Failed to find application object: %r" % obj) if not callable(app): raise AppImportError("Application object must be callable.") return app def getcwd(): # get current path, try to use PWD env first try: a = os.stat(os.environ['PWD']) b = os.stat(os.getcwd()) if a.st_ino == b.st_ino and a.st_dev == b.st_dev: cwd = os.environ['PWD'] else: cwd = os.getcwd() except Exception: cwd = os.getcwd() return cwd def http_date(timestamp=None): """Return the current date and time formatted for a message header.""" if timestamp is None: timestamp = time.time() s = email.utils.formatdate(timestamp, localtime=False, usegmt=True) return s def is_hoppish(header): return header.lower().strip() in hop_headers def daemonize(enable_stdio_inheritance=False): """\ Standard daemonization of a process. http://www.svbug.com/documentation/comp.unix.programmer-FAQ/faq_2.html#SEC16 """ if 'GUNICORN_FD' not in os.environ: if os.fork(): os._exit(0) os.setsid() if os.fork(): os._exit(0) os.umask(0o22) # In both the following any file descriptors above stdin # stdout and stderr are left untouched. The inheritance # option simply allows one to have output go to a file # specified by way of shell redirection when not wanting # to use --error-log option. if not enable_stdio_inheritance: # Remap all of stdin, stdout and stderr on to # /dev/null. The expectation is that users have # specified the --error-log option. closerange(0, 3) fd_null = os.open(REDIRECT_TO, os.O_RDWR) if fd_null != 0: os.dup2(fd_null, 0) os.dup2(fd_null, 1) os.dup2(fd_null, 2) else: fd_null = os.open(REDIRECT_TO, os.O_RDWR) # Always redirect stdin to /dev/null as we would # never expect to need to read interactive input. if fd_null != 0: os.close(0) os.dup2(fd_null, 0) # If stdout and stderr are still connected to # their original file descriptors we check to see # if they are associated with terminal devices. # When they are we map them to /dev/null so that # are still detached from any controlling terminal # properly. If not we preserve them as they are. # # If stdin and stdout were not hooked up to the # original file descriptors, then all bets are # off and all we can really do is leave them as # they were. # # This will allow 'gunicorn ... > output.log 2>&1' # to work with stdout/stderr going to the file # as expected. # # Note that if using --error-log option, the log # file specified through shell redirection will # only be used up until the log file specified # by the option takes over. As it replaces stdout # and stderr at the file descriptor level, then # anything using stdout or stderr, including having # cached a reference to them, will still work. def redirect(stream, fd_expect): try: fd = stream.fileno() if fd == fd_expect and stream.isatty(): os.close(fd) os.dup2(fd_null, fd) except AttributeError: pass redirect(sys.stdout, 1) redirect(sys.stderr, 2) def seed(): try: random.seed(os.urandom(64)) except NotImplementedError: random.seed('%s.%s' % (time.time(), os.getpid())) def check_is_writeable(path): try: f = open(path, 'a') except IOError as e: raise RuntimeError("Error: '%s' isn't writable [%r]" % (path, e)) f.close() def to_bytestring(value, encoding="utf8"): """Converts a string argument to a byte string""" if isinstance(value, bytes): return value if not isinstance(value, str): raise TypeError('%r is not a string' % value) return value.encode(encoding) def has_fileno(obj): if not hasattr(obj, "fileno"): return False # check BytesIO case and maybe others try: obj.fileno() except (AttributeError, IOError, io.UnsupportedOperation): return False return True def warn(msg): print("!!!", file=sys.stderr) lines = msg.splitlines() for i, line in enumerate(lines): if i == 0: line = "WARNING: %s" % line print("!!! %s" % line, file=sys.stderr) print("!!!\n", file=sys.stderr) sys.stderr.flush() def make_fail_app(msg): msg = to_bytestring(msg) def app(environ, start_response): start_response("500 Internal Server Error", [ ("Content-Type", "text/plain"), ("Content-Length", str(len(msg))) ]) return [msg] return app def split_request_uri(uri): if uri.startswith("//"): # When the path starts with //, urlsplit considers it as a # relative uri while the RFC says we should consider it as abs_path # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 # We use temporary dot prefix to workaround this behaviour parts = urllib.parse.urlsplit("." + uri) return parts._replace(path=parts.path[1:]) return urllib.parse.urlsplit(uri) # From six.reraise def reraise(tp, value, tb=None): try: if value is None: value = tp() if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value finally: value = None tb = None def bytes_to_str(b): if isinstance(b, str): return b return str(b, 'latin1') def unquote_to_wsgi_str(string): return urllib.parse.unquote_to_bytes(string).decode('latin-1') gunicorn-20.1.0/gunicorn/workers/000077500000000000000000000000001401157322000167315ustar00rootroot00000000000000gunicorn-20.1.0/gunicorn/workers/__init__.py000066400000000000000000000011221401157322000210360ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. # supported gunicorn workers. SUPPORTED_WORKERS = { "sync": "gunicorn.workers.sync.SyncWorker", "eventlet": "gunicorn.workers.geventlet.EventletWorker", "gevent": "gunicorn.workers.ggevent.GeventWorker", "gevent_wsgi": "gunicorn.workers.ggevent.GeventPyWSGIWorker", "gevent_pywsgi": "gunicorn.workers.ggevent.GeventPyWSGIWorker", "tornado": "gunicorn.workers.gtornado.TornadoWorker", "gthread": "gunicorn.workers.gthread.ThreadWorker", } gunicorn-20.1.0/gunicorn/workers/base.py000066400000000000000000000216171401157322000202240ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import io import os import signal import sys import time import traceback from datetime import datetime from random import randint from ssl import SSLError from gunicorn import util from gunicorn.http.errors import ( ForbiddenProxyRequest, InvalidHeader, InvalidHeaderName, InvalidHTTPVersion, InvalidProxyLine, InvalidRequestLine, InvalidRequestMethod, InvalidSchemeHeaders, LimitRequestHeaders, LimitRequestLine, ) from gunicorn.http.wsgi import Response, default_environ from gunicorn.reloader import reloader_engines from gunicorn.workers.workertmp import WorkerTmp class Worker(object): SIGNALS = [getattr(signal, "SIG%s" % x) for x in ( "ABRT HUP QUIT INT TERM USR1 USR2 WINCH CHLD".split() )] PIPE = [] def __init__(self, age, ppid, sockets, app, timeout, cfg, log): """\ This is called pre-fork so it shouldn't do anything to the current process. If there's a need to make process wide changes you'll want to do that in ``self.init_process()``. """ self.age = age self.pid = "[booting]" self.ppid = ppid self.sockets = sockets self.app = app self.timeout = timeout self.cfg = cfg self.booted = False self.aborted = False self.reloader = None self.nr = 0 if cfg.max_requests > 0: jitter = randint(0, cfg.max_requests_jitter) self.max_requests = cfg.max_requests + jitter else: self.max_requests = sys.maxsize self.alive = True self.log = log self.tmp = WorkerTmp(cfg) def __str__(self): return "" % self.pid def notify(self): """\ Your worker subclass must arrange to have this method called once every ``self.timeout`` seconds. If you fail in accomplishing this task, the master process will murder your workers. """ self.tmp.notify() def run(self): """\ This is the mainloop of a worker process. You should override this method in a subclass to provide the intended behaviour for your particular evil schemes. """ raise NotImplementedError() def init_process(self): """\ If you override this method in a subclass, the last statement in the function should be to call this method with super().init_process() so that the ``run()`` loop is initiated. """ # set environment' variables if self.cfg.env: for k, v in self.cfg.env.items(): os.environ[k] = v util.set_owner_process(self.cfg.uid, self.cfg.gid, initgroups=self.cfg.initgroups) # Reseed the random number generator util.seed() # For waking ourselves up self.PIPE = os.pipe() for p in self.PIPE: util.set_non_blocking(p) util.close_on_exec(p) # Prevent fd inheritance for s in self.sockets: util.close_on_exec(s) util.close_on_exec(self.tmp.fileno()) self.wait_fds = self.sockets + [self.PIPE[0]] self.log.close_on_exec() self.init_signals() # start the reloader if self.cfg.reload: def changed(fname): self.log.info("Worker reloading: %s modified", fname) self.alive = False os.write(self.PIPE[1], b"1") self.cfg.worker_int(self) time.sleep(0.1) sys.exit(0) reloader_cls = reloader_engines[self.cfg.reload_engine] self.reloader = reloader_cls(extra_files=self.cfg.reload_extra_files, callback=changed) self.load_wsgi() if self.reloader: self.reloader.start() self.cfg.post_worker_init(self) # Enter main run loop self.booted = True self.run() def load_wsgi(self): try: self.wsgi = self.app.wsgi() except SyntaxError as e: if not self.cfg.reload: raise self.log.exception(e) # fix from PR #1228 # storing the traceback into exc_tb will create a circular reference. # per https://docs.python.org/2/library/sys.html#sys.exc_info warning, # delete the traceback after use. try: _, exc_val, exc_tb = sys.exc_info() self.reloader.add_extra_file(exc_val.filename) tb_string = io.StringIO() traceback.print_tb(exc_tb, file=tb_string) self.wsgi = util.make_fail_app(tb_string.getvalue()) finally: del exc_tb def init_signals(self): # reset signaling for s in self.SIGNALS: signal.signal(s, signal.SIG_DFL) # init new signaling signal.signal(signal.SIGQUIT, self.handle_quit) signal.signal(signal.SIGTERM, self.handle_exit) signal.signal(signal.SIGINT, self.handle_quit) signal.signal(signal.SIGWINCH, self.handle_winch) signal.signal(signal.SIGUSR1, self.handle_usr1) signal.signal(signal.SIGABRT, self.handle_abort) # Don't let SIGTERM and SIGUSR1 disturb active requests # by interrupting system calls signal.siginterrupt(signal.SIGTERM, False) signal.siginterrupt(signal.SIGUSR1, False) if hasattr(signal, 'set_wakeup_fd'): signal.set_wakeup_fd(self.PIPE[1]) def handle_usr1(self, sig, frame): self.log.reopen_files() def handle_exit(self, sig, frame): self.alive = False def handle_quit(self, sig, frame): self.alive = False # worker_int callback self.cfg.worker_int(self) time.sleep(0.1) sys.exit(0) def handle_abort(self, sig, frame): self.alive = False self.cfg.worker_abort(self) sys.exit(1) def handle_error(self, req, client, addr, exc): request_start = datetime.now() addr = addr or ('', -1) # unix socket case if isinstance(exc, ( InvalidRequestLine, InvalidRequestMethod, InvalidHTTPVersion, InvalidHeader, InvalidHeaderName, LimitRequestLine, LimitRequestHeaders, InvalidProxyLine, ForbiddenProxyRequest, InvalidSchemeHeaders, SSLError, )): status_int = 400 reason = "Bad Request" if isinstance(exc, InvalidRequestLine): mesg = "Invalid Request Line '%s'" % str(exc) elif isinstance(exc, InvalidRequestMethod): mesg = "Invalid Method '%s'" % str(exc) elif isinstance(exc, InvalidHTTPVersion): mesg = "Invalid HTTP Version '%s'" % str(exc) elif isinstance(exc, (InvalidHeaderName, InvalidHeader,)): mesg = "%s" % str(exc) if not req and hasattr(exc, "req"): req = exc.req # for access log elif isinstance(exc, LimitRequestLine): mesg = "%s" % str(exc) elif isinstance(exc, LimitRequestHeaders): mesg = "Error parsing headers: '%s'" % str(exc) elif isinstance(exc, InvalidProxyLine): mesg = "'%s'" % str(exc) elif isinstance(exc, ForbiddenProxyRequest): reason = "Forbidden" mesg = "Request forbidden" status_int = 403 elif isinstance(exc, InvalidSchemeHeaders): mesg = "%s" % str(exc) elif isinstance(exc, SSLError): reason = "Forbidden" mesg = "'%s'" % str(exc) status_int = 403 msg = "Invalid request from ip={ip}: {error}" self.log.debug(msg.format(ip=addr[0], error=str(exc))) else: if hasattr(req, "uri"): self.log.exception("Error handling request %s", req.uri) status_int = 500 reason = "Internal Server Error" mesg = "" if req is not None: request_time = datetime.now() - request_start environ = default_environ(req, client, self.cfg) environ['REMOTE_ADDR'] = addr[0] environ['REMOTE_PORT'] = str(addr[1]) resp = Response(req, client, self.cfg) resp.status = "%s %s" % (status_int, reason) resp.response_length = len(mesg) self.log.access(resp, req, environ, request_time) try: util.write_error(client, status_int, reason, mesg) except Exception: self.log.debug("Failed to send error message.") def handle_winch(self, sig, fname): # Ignore SIGWINCH in worker. Fixes a crash on OpenBSD. self.log.debug("worker: SIGWINCH ignored.") gunicorn-20.1.0/gunicorn/workers/base_async.py000066400000000000000000000130751401157322000214200ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. from datetime import datetime import errno import socket import ssl import sys import gunicorn.http as http import gunicorn.http.wsgi as wsgi import gunicorn.util as util import gunicorn.workers.base as base ALREADY_HANDLED = object() class AsyncWorker(base.Worker): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.worker_connections = self.cfg.worker_connections def timeout_ctx(self): raise NotImplementedError() def is_already_handled(self, respiter): # some workers will need to overload this function to raise a StopIteration return respiter == ALREADY_HANDLED def handle(self, listener, client, addr): req = None try: parser = http.RequestParser(self.cfg, client, addr) try: listener_name = listener.getsockname() if not self.cfg.keepalive: req = next(parser) self.handle_request(listener_name, req, client, addr) else: # keepalive loop proxy_protocol_info = {} while True: req = None with self.timeout_ctx(): req = next(parser) if not req: break if req.proxy_protocol_info: proxy_protocol_info = req.proxy_protocol_info else: req.proxy_protocol_info = proxy_protocol_info self.handle_request(listener_name, req, client, addr) except http.errors.NoMoreData as e: self.log.debug("Ignored premature client disconnection. %s", e) except StopIteration as e: self.log.debug("Closing connection. %s", e) except ssl.SSLError: # pass to next try-except level util.reraise(*sys.exc_info()) except EnvironmentError: # pass to next try-except level util.reraise(*sys.exc_info()) except Exception as e: self.handle_error(req, client, addr, e) except ssl.SSLError as e: if e.args[0] == ssl.SSL_ERROR_EOF: self.log.debug("ssl connection closed") client.close() else: self.log.debug("Error processing SSL request.") self.handle_error(req, client, addr, e) except EnvironmentError as e: if e.errno not in (errno.EPIPE, errno.ECONNRESET, errno.ENOTCONN): self.log.exception("Socket error processing request.") else: if e.errno == errno.ECONNRESET: self.log.debug("Ignoring connection reset") elif e.errno == errno.ENOTCONN: self.log.debug("Ignoring socket not connected") else: self.log.debug("Ignoring EPIPE") except Exception as e: self.handle_error(req, client, addr, e) finally: util.close(client) def handle_request(self, listener_name, req, sock, addr): request_start = datetime.now() environ = {} resp = None try: self.cfg.pre_request(self, req) resp, environ = wsgi.create(req, sock, addr, listener_name, self.cfg) environ["wsgi.multithread"] = True self.nr += 1 if self.nr >= self.max_requests: if self.alive: self.log.info("Autorestarting worker after current request.") self.alive = False if not self.alive or not self.cfg.keepalive: resp.force_close() respiter = self.wsgi(environ, resp.start_response) if self.is_already_handled(respiter): return False try: if isinstance(respiter, environ['wsgi.file_wrapper']): resp.write_file(respiter) else: for item in respiter: resp.write(item) resp.close() request_time = datetime.now() - request_start self.log.access(resp, req, environ, request_time) finally: if hasattr(respiter, "close"): respiter.close() if resp.should_close(): raise StopIteration() except StopIteration: raise except EnvironmentError: # If the original exception was a socket.error we delegate # handling it to the caller (where handle() might ignore it) util.reraise(*sys.exc_info()) except Exception: if resp and resp.headers_sent: # If the requests have already been sent, we should close the # connection to indicate the error. self.log.exception("Error handling request") try: sock.shutdown(socket.SHUT_RDWR) sock.close() except EnvironmentError: pass raise StopIteration() raise finally: try: self.cfg.post_request(self, req, environ, resp) except Exception: self.log.exception("Exception in post_request hook") return True gunicorn-20.1.0/gunicorn/workers/geventlet.py000066400000000000000000000131211401157322000212760ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. from functools import partial import sys try: import eventlet except ImportError: raise RuntimeError("eventlet worker requires eventlet 0.24.1 or higher") else: from pkg_resources import parse_version if parse_version(eventlet.__version__) < parse_version('0.24.1'): raise RuntimeError("eventlet worker requires eventlet 0.24.1 or higher") from eventlet import hubs, greenthread from eventlet.greenio import GreenSocket from eventlet.wsgi import ALREADY_HANDLED as EVENTLET_ALREADY_HANDLED import greenlet from gunicorn.workers.base_async import AsyncWorker def _eventlet_socket_sendfile(self, file, offset=0, count=None): # Based on the implementation in gevent which in turn is slightly # modified from the standard library implementation. if self.gettimeout() == 0: raise ValueError("non-blocking sockets are not supported") if offset: file.seek(offset) blocksize = min(count, 8192) if count else 8192 total_sent = 0 # localize variable access to minimize overhead file_read = file.read sock_send = self.send try: while True: if count: blocksize = min(count - total_sent, blocksize) if blocksize <= 0: break data = memoryview(file_read(blocksize)) if not data: break # EOF while True: try: sent = sock_send(data) except BlockingIOError: continue else: total_sent += sent if sent < len(data): data = data[sent:] else: break return total_sent finally: if total_sent > 0 and hasattr(file, 'seek'): file.seek(offset + total_sent) def _eventlet_serve(sock, handle, concurrency): """ Serve requests forever. This code is nearly identical to ``eventlet.convenience.serve`` except that it attempts to join the pool at the end, which allows for gunicorn graceful shutdowns. """ pool = eventlet.greenpool.GreenPool(concurrency) server_gt = eventlet.greenthread.getcurrent() while True: try: conn, addr = sock.accept() gt = pool.spawn(handle, conn, addr) gt.link(_eventlet_stop, server_gt, conn) conn, addr, gt = None, None, None except eventlet.StopServe: sock.close() pool.waitall() return def _eventlet_stop(client, server, conn): """ Stop a greenlet handling a request and close its connection. This code is lifted from eventlet so as not to depend on undocumented functions in the library. """ try: try: client.wait() finally: conn.close() except greenlet.GreenletExit: pass except Exception: greenthread.kill(server, *sys.exc_info()) def patch_sendfile(): # As of eventlet 0.25.1, GreenSocket.sendfile doesn't exist, # meaning the native implementations of socket.sendfile will be used. # If os.sendfile exists, it will attempt to use that, failing explicitly # if the socket is in non-blocking mode, which the underlying # socket object /is/. Even the regular _sendfile_use_send will # fail in that way; plus, it would use the underlying socket.send which isn't # properly cooperative. So we have to monkey-patch a working socket.sendfile() # into GreenSocket; in this method, `self.send` will be the GreenSocket's # send method which is properly cooperative. if not hasattr(GreenSocket, 'sendfile'): GreenSocket.sendfile = _eventlet_socket_sendfile class EventletWorker(AsyncWorker): def patch(self): hubs.use_hub() eventlet.monkey_patch() patch_sendfile() def is_already_handled(self, respiter): if respiter == EVENTLET_ALREADY_HANDLED: raise StopIteration() return super().is_already_handled(respiter) def init_process(self): self.patch() super().init_process() def handle_quit(self, sig, frame): eventlet.spawn(super().handle_quit, sig, frame) def handle_usr1(self, sig, frame): eventlet.spawn(super().handle_usr1, sig, frame) def timeout_ctx(self): return eventlet.Timeout(self.cfg.keepalive or None, False) def handle(self, listener, client, addr): if self.cfg.is_ssl: client = eventlet.wrap_ssl(client, server_side=True, **self.cfg.ssl_options) super().handle(listener, client, addr) def run(self): acceptors = [] for sock in self.sockets: gsock = GreenSocket(sock) gsock.setblocking(1) hfun = partial(self.handle, gsock) acceptor = eventlet.spawn(_eventlet_serve, gsock, hfun, self.worker_connections) acceptors.append(acceptor) eventlet.sleep(0.0) while self.alive: self.notify() eventlet.sleep(1.0) self.notify() try: with eventlet.Timeout(self.cfg.graceful_timeout) as t: for a in acceptors: a.kill(eventlet.StopServe()) for a in acceptors: a.wait() except eventlet.Timeout as te: if te != t: raise for a in acceptors: a.kill() gunicorn-20.1.0/gunicorn/workers/ggevent.py000066400000000000000000000131451401157322000207460ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import os import sys from datetime import datetime from functools import partial import time try: import gevent except ImportError: raise RuntimeError("gevent worker requires gevent 1.4 or higher") else: from pkg_resources import parse_version if parse_version(gevent.__version__) < parse_version('1.4'): raise RuntimeError("gevent worker requires gevent 1.4 or higher") from gevent.pool import Pool from gevent.server import StreamServer from gevent import hub, monkey, socket, pywsgi import gunicorn from gunicorn.http.wsgi import base_environ from gunicorn.workers.base_async import AsyncWorker VERSION = "gevent/%s gunicorn/%s" % (gevent.__version__, gunicorn.__version__) class GeventWorker(AsyncWorker): server_class = None wsgi_handler = None def patch(self): monkey.patch_all() # patch sockets sockets = [] for s in self.sockets: sockets.append(socket.socket(s.FAMILY, socket.SOCK_STREAM, fileno=s.sock.fileno())) self.sockets = sockets def notify(self): super().notify() if self.ppid != os.getppid(): self.log.info("Parent changed, shutting down: %s", self) sys.exit(0) def timeout_ctx(self): return gevent.Timeout(self.cfg.keepalive, False) def run(self): servers = [] ssl_args = {} if self.cfg.is_ssl: ssl_args = dict(server_side=True, **self.cfg.ssl_options) for s in self.sockets: s.setblocking(1) pool = Pool(self.worker_connections) if self.server_class is not None: environ = base_environ(self.cfg) environ.update({ "wsgi.multithread": True, "SERVER_SOFTWARE": VERSION, }) server = self.server_class( s, application=self.wsgi, spawn=pool, log=self.log, handler_class=self.wsgi_handler, environ=environ, **ssl_args) else: hfun = partial(self.handle, s) server = StreamServer(s, handle=hfun, spawn=pool, **ssl_args) if self.cfg.workers > 1: server.max_accept = 1 server.start() servers.append(server) while self.alive: self.notify() gevent.sleep(1.0) try: # Stop accepting requests for server in servers: if hasattr(server, 'close'): # gevent 1.0 server.close() if hasattr(server, 'kill'): # gevent < 1.0 server.kill() # Handle current requests until graceful_timeout ts = time.time() while time.time() - ts <= self.cfg.graceful_timeout: accepting = 0 for server in servers: if server.pool.free_count() != server.pool.size: accepting += 1 # if no server is accepting a connection, we can exit if not accepting: return self.notify() gevent.sleep(1.0) # Force kill all active the handlers self.log.warning("Worker graceful timeout (pid:%s)" % self.pid) for server in servers: server.stop(timeout=1) except Exception: pass def handle(self, listener, client, addr): # Connected socket timeout defaults to socket.getdefaulttimeout(). # This forces to blocking mode. client.setblocking(1) super().handle(listener, client, addr) def handle_request(self, listener_name, req, sock, addr): try: super().handle_request(listener_name, req, sock, addr) except gevent.GreenletExit: pass except SystemExit: pass def handle_quit(self, sig, frame): # Move this out of the signal handler so we can use # blocking calls. See #1126 gevent.spawn(super().handle_quit, sig, frame) def handle_usr1(self, sig, frame): # Make the gevent workers handle the usr1 signal # by deferring to a new greenlet. See #1645 gevent.spawn(super().handle_usr1, sig, frame) def init_process(self): self.patch() hub.reinit() super().init_process() class GeventResponse(object): status = None headers = None sent = None def __init__(self, status, headers, clength): self.status = status self.headers = headers self.sent = clength class PyWSGIHandler(pywsgi.WSGIHandler): def log_request(self): start = datetime.fromtimestamp(self.time_start) finish = datetime.fromtimestamp(self.time_finish) response_time = finish - start resp_headers = getattr(self, 'response_headers', {}) resp = GeventResponse(self.status, resp_headers, self.response_length) if hasattr(self, 'headers'): req_headers = self.headers.items() else: req_headers = [] self.server.log.access(resp, req_headers, self.environ, response_time) def get_environ(self): env = super().get_environ() env['gunicorn.sock'] = self.socket env['RAW_URI'] = self.path return env class PyWSGIServer(pywsgi.WSGIServer): pass class GeventPyWSGIWorker(GeventWorker): "The Gevent StreamServer based workers." server_class = PyWSGIServer wsgi_handler = PyWSGIHandler gunicorn-20.1.0/gunicorn/workers/gthread.py000066400000000000000000000276421401157322000207340ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. # design: # A threaded worker accepts connections in the main loop, accepted # connections are added to the thread pool as a connection job. # Keepalive connections are put back in the loop waiting for an event. # If no event happen after the keep alive timeout, the connection is # closed. # pylint: disable=no-else-break import concurrent.futures as futures import errno import os import selectors import socket import ssl import sys import time from collections import deque from datetime import datetime from functools import partial from threading import RLock from . import base from .. import http from .. import util from ..http import wsgi class TConn(object): def __init__(self, cfg, sock, client, server): self.cfg = cfg self.sock = sock self.client = client self.server = server self.timeout = None self.parser = None # set the socket to non blocking self.sock.setblocking(False) def init(self): self.sock.setblocking(True) if self.parser is None: # wrap the socket if needed if self.cfg.is_ssl: self.sock = ssl.wrap_socket(self.sock, server_side=True, **self.cfg.ssl_options) # initialize the parser self.parser = http.RequestParser(self.cfg, self.sock, self.client) def set_timeout(self): # set the timeout self.timeout = time.time() + self.cfg.keepalive def close(self): util.close(self.sock) class ThreadWorker(base.Worker): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.worker_connections = self.cfg.worker_connections self.max_keepalived = self.cfg.worker_connections - self.cfg.threads # initialise the pool self.tpool = None self.poller = None self._lock = None self.futures = deque() self._keep = deque() self.nr_conns = 0 @classmethod def check_config(cls, cfg, log): max_keepalived = cfg.worker_connections - cfg.threads if max_keepalived <= 0 and cfg.keepalive: log.warning("No keepalived connections can be handled. " + "Check the number of worker connections and threads.") def init_process(self): self.tpool = self.get_thread_pool() self.poller = selectors.DefaultSelector() self._lock = RLock() super().init_process() def get_thread_pool(self): """Override this method to customize how the thread pool is created""" return futures.ThreadPoolExecutor(max_workers=self.cfg.threads) def handle_quit(self, sig, frame): self.alive = False # worker_int callback self.cfg.worker_int(self) self.tpool.shutdown(False) time.sleep(0.1) sys.exit(0) def _wrap_future(self, fs, conn): fs.conn = conn self.futures.append(fs) fs.add_done_callback(self.finish_request) def enqueue_req(self, conn): conn.init() # submit the connection to a worker fs = self.tpool.submit(self.handle, conn) self._wrap_future(fs, conn) def accept(self, server, listener): try: sock, client = listener.accept() # initialize the connection object conn = TConn(self.cfg, sock, client, server) self.nr_conns += 1 # enqueue the job self.enqueue_req(conn) except EnvironmentError as e: if e.errno not in (errno.EAGAIN, errno.ECONNABORTED, errno.EWOULDBLOCK): raise def reuse_connection(self, conn, client): with self._lock: # unregister the client from the poller self.poller.unregister(client) # remove the connection from keepalive try: self._keep.remove(conn) except ValueError: # race condition return # submit the connection to a worker self.enqueue_req(conn) def murder_keepalived(self): now = time.time() while True: with self._lock: try: # remove the connection from the queue conn = self._keep.popleft() except IndexError: break delta = conn.timeout - now if delta > 0: # add the connection back to the queue with self._lock: self._keep.appendleft(conn) break else: self.nr_conns -= 1 # remove the socket from the poller with self._lock: try: self.poller.unregister(conn.sock) except EnvironmentError as e: if e.errno != errno.EBADF: raise except KeyError: # already removed by the system, continue pass # close the socket conn.close() def is_parent_alive(self): # If our parent changed then we shut down. if self.ppid != os.getppid(): self.log.info("Parent changed, shutting down: %s", self) return False return True def run(self): # init listeners, add them to the event loop for sock in self.sockets: sock.setblocking(False) # a race condition during graceful shutdown may make the listener # name unavailable in the request handler so capture it once here server = sock.getsockname() acceptor = partial(self.accept, server) self.poller.register(sock, selectors.EVENT_READ, acceptor) while self.alive: # notify the arbiter we are alive self.notify() # can we accept more connections? if self.nr_conns < self.worker_connections: # wait for an event events = self.poller.select(1.0) for key, _ in events: callback = key.data callback(key.fileobj) # check (but do not wait) for finished requests result = futures.wait(self.futures, timeout=0, return_when=futures.FIRST_COMPLETED) else: # wait for a request to finish result = futures.wait(self.futures, timeout=1.0, return_when=futures.FIRST_COMPLETED) # clean up finished requests for fut in result.done: self.futures.remove(fut) if not self.is_parent_alive(): break # handle keepalive timeouts self.murder_keepalived() self.tpool.shutdown(False) self.poller.close() for s in self.sockets: s.close() futures.wait(self.futures, timeout=self.cfg.graceful_timeout) def finish_request(self, fs): if fs.cancelled(): self.nr_conns -= 1 fs.conn.close() return try: (keepalive, conn) = fs.result() # if the connection should be kept alived add it # to the eventloop and record it if keepalive and self.alive: # flag the socket as non blocked conn.sock.setblocking(False) # register the connection conn.set_timeout() with self._lock: self._keep.append(conn) # add the socket to the event loop self.poller.register(conn.sock, selectors.EVENT_READ, partial(self.reuse_connection, conn)) else: self.nr_conns -= 1 conn.close() except Exception: # an exception happened, make sure to close the # socket. self.nr_conns -= 1 fs.conn.close() def handle(self, conn): keepalive = False req = None try: req = next(conn.parser) if not req: return (False, conn) # handle the request keepalive = self.handle_request(req, conn) if keepalive: return (keepalive, conn) except http.errors.NoMoreData as e: self.log.debug("Ignored premature client disconnection. %s", e) except StopIteration as e: self.log.debug("Closing connection. %s", e) except ssl.SSLError as e: if e.args[0] == ssl.SSL_ERROR_EOF: self.log.debug("ssl connection closed") conn.sock.close() else: self.log.debug("Error processing SSL request.") self.handle_error(req, conn.sock, conn.client, e) except EnvironmentError as e: if e.errno not in (errno.EPIPE, errno.ECONNRESET, errno.ENOTCONN): self.log.exception("Socket error processing request.") else: if e.errno == errno.ECONNRESET: self.log.debug("Ignoring connection reset") elif e.errno == errno.ENOTCONN: self.log.debug("Ignoring socket not connected") else: self.log.debug("Ignoring connection epipe") except Exception as e: self.handle_error(req, conn.sock, conn.client, e) return (False, conn) def handle_request(self, req, conn): environ = {} resp = None try: self.cfg.pre_request(self, req) request_start = datetime.now() resp, environ = wsgi.create(req, conn.sock, conn.client, conn.server, self.cfg) environ["wsgi.multithread"] = True self.nr += 1 if self.nr >= self.max_requests: if self.alive: self.log.info("Autorestarting worker after current request.") self.alive = False resp.force_close() if not self.alive or not self.cfg.keepalive: resp.force_close() elif len(self._keep) >= self.max_keepalived: resp.force_close() respiter = self.wsgi(environ, resp.start_response) try: if isinstance(respiter, environ['wsgi.file_wrapper']): resp.write_file(respiter) else: for item in respiter: resp.write(item) resp.close() request_time = datetime.now() - request_start self.log.access(resp, req, environ, request_time) finally: if hasattr(respiter, "close"): respiter.close() if resp.should_close(): self.log.debug("Closing connection.") return False except EnvironmentError: # pass to next try-except level util.reraise(*sys.exc_info()) except Exception: if resp and resp.headers_sent: # If the requests have already been sent, we should close the # connection to indicate the error. self.log.exception("Error handling request") try: conn.sock.shutdown(socket.SHUT_RDWR) conn.sock.close() except EnvironmentError: pass raise StopIteration() raise finally: try: self.cfg.post_request(self, req, environ, resp) except Exception: self.log.exception("Exception in post_request hook") return True gunicorn-20.1.0/gunicorn/workers/gtornado.py000066400000000000000000000135441401157322000211270ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import copy import os import sys try: import tornado except ImportError: raise RuntimeError("You need tornado installed to use this worker.") import tornado.web import tornado.httpserver from tornado.ioloop import IOLoop, PeriodicCallback from tornado.wsgi import WSGIContainer from gunicorn.workers.base import Worker from gunicorn import __version__ as gversion # Tornado 5.0 updated its IOLoop, and the `io_loop` arguments to many # Tornado functions have been removed in Tornado 5.0. Also, they no # longer store PeriodCallbacks in ioloop._callbacks. Instead we store # them on our side, and use stop() on them when stopping the worker. # See https://www.tornadoweb.org/en/stable/releases/v5.0.0.html#backwards-compatibility-notes # for more details. TORNADO5 = tornado.version_info >= (5, 0, 0) class TornadoWorker(Worker): @classmethod def setup(cls): web = sys.modules.pop("tornado.web") old_clear = web.RequestHandler.clear def clear(self): old_clear(self) if "Gunicorn" not in self._headers["Server"]: self._headers["Server"] += " (Gunicorn/%s)" % gversion web.RequestHandler.clear = clear sys.modules["tornado.web"] = web def handle_exit(self, sig, frame): if self.alive: super().handle_exit(sig, frame) def handle_request(self): self.nr += 1 if self.alive and self.nr >= self.max_requests: self.log.info("Autorestarting worker after current request.") self.alive = False def watchdog(self): if self.alive: self.notify() if self.ppid != os.getppid(): self.log.info("Parent changed, shutting down: %s", self) self.alive = False def heartbeat(self): if not self.alive: if self.server_alive: if hasattr(self, 'server'): try: self.server.stop() except Exception: pass self.server_alive = False else: if TORNADO5: for callback in self.callbacks: callback.stop() self.ioloop.stop() else: if not self.ioloop._callbacks: self.ioloop.stop() def init_process(self): # IOLoop cannot survive a fork or be shared across processes # in any way. When multiple processes are being used, each process # should create its own IOLoop. We should clear current IOLoop # if exists before os.fork. IOLoop.clear_current() super().init_process() def run(self): self.ioloop = IOLoop.instance() self.alive = True self.server_alive = False if TORNADO5: self.callbacks = [] self.callbacks.append(PeriodicCallback(self.watchdog, 1000)) self.callbacks.append(PeriodicCallback(self.heartbeat, 1000)) for callback in self.callbacks: callback.start() else: PeriodicCallback(self.watchdog, 1000, io_loop=self.ioloop).start() PeriodicCallback(self.heartbeat, 1000, io_loop=self.ioloop).start() # Assume the app is a WSGI callable if its not an # instance of tornado.web.Application or is an # instance of tornado.wsgi.WSGIApplication app = self.wsgi if tornado.version_info[0] < 6: if not isinstance(app, tornado.web.Application) or \ isinstance(app, tornado.wsgi.WSGIApplication): app = WSGIContainer(app) elif not isinstance(app, WSGIContainer): app = WSGIContainer(app) # Monkey-patching HTTPConnection.finish to count the # number of requests being handled by Tornado. This # will help gunicorn shutdown the worker if max_requests # is exceeded. httpserver = sys.modules["tornado.httpserver"] if hasattr(httpserver, 'HTTPConnection'): old_connection_finish = httpserver.HTTPConnection.finish def finish(other): self.handle_request() old_connection_finish(other) httpserver.HTTPConnection.finish = finish sys.modules["tornado.httpserver"] = httpserver server_class = tornado.httpserver.HTTPServer else: class _HTTPServer(tornado.httpserver.HTTPServer): def on_close(instance, server_conn): self.handle_request() super(_HTTPServer, instance).on_close(server_conn) server_class = _HTTPServer if self.cfg.is_ssl: _ssl_opt = copy.deepcopy(self.cfg.ssl_options) # tornado refuses initialization if ssl_options contains following # options del _ssl_opt["do_handshake_on_connect"] del _ssl_opt["suppress_ragged_eofs"] if TORNADO5: server = server_class(app, ssl_options=_ssl_opt) else: server = server_class(app, io_loop=self.ioloop, ssl_options=_ssl_opt) else: if TORNADO5: server = server_class(app) else: server = server_class(app, io_loop=self.ioloop) self.server = server self.server_alive = True for s in self.sockets: s.setblocking(0) if hasattr(server, "add_socket"): # tornado > 2.0 server.add_socket(s) elif hasattr(server, "_sockets"): # tornado 2.0 server._sockets[s.fileno()] = s server.no_keep_alive = self.cfg.keepalive <= 0 server.start(num_processes=1) self.ioloop.start() gunicorn-20.1.0/gunicorn/workers/sync.py000066400000000000000000000162371401157322000202700ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. # from datetime import datetime import errno import os import select import socket import ssl import sys import gunicorn.http as http import gunicorn.http.wsgi as wsgi import gunicorn.util as util import gunicorn.workers.base as base class StopWaiting(Exception): """ exception raised to stop waiting for a connection """ class SyncWorker(base.Worker): def accept(self, listener): client, addr = listener.accept() client.setblocking(1) util.close_on_exec(client) self.handle(listener, client, addr) def wait(self, timeout): try: self.notify() ret = select.select(self.wait_fds, [], [], timeout) if ret[0]: if self.PIPE[0] in ret[0]: os.read(self.PIPE[0], 1) return ret[0] except select.error as e: if e.args[0] == errno.EINTR: return self.sockets if e.args[0] == errno.EBADF: if self.nr < 0: return self.sockets else: raise StopWaiting raise def is_parent_alive(self): # If our parent changed then we shut down. if self.ppid != os.getppid(): self.log.info("Parent changed, shutting down: %s", self) return False return True def run_for_one(self, timeout): listener = self.sockets[0] while self.alive: self.notify() # Accept a connection. If we get an error telling us # that no connection is waiting we fall down to the # select which is where we'll wait for a bit for new # workers to come give us some love. try: self.accept(listener) # Keep processing clients until no one is waiting. This # prevents the need to select() for every client that we # process. continue except EnvironmentError as e: if e.errno not in (errno.EAGAIN, errno.ECONNABORTED, errno.EWOULDBLOCK): raise if not self.is_parent_alive(): return try: self.wait(timeout) except StopWaiting: return def run_for_multiple(self, timeout): while self.alive: self.notify() try: ready = self.wait(timeout) except StopWaiting: return if ready is not None: for listener in ready: if listener == self.PIPE[0]: continue try: self.accept(listener) except EnvironmentError as e: if e.errno not in (errno.EAGAIN, errno.ECONNABORTED, errno.EWOULDBLOCK): raise if not self.is_parent_alive(): return def run(self): # if no timeout is given the worker will never wait and will # use the CPU for nothing. This minimal timeout prevent it. timeout = self.timeout or 0.5 # self.socket appears to lose its blocking status after # we fork in the arbiter. Reset it here. for s in self.sockets: s.setblocking(0) if len(self.sockets) > 1: self.run_for_multiple(timeout) else: self.run_for_one(timeout) def handle(self, listener, client, addr): req = None try: if self.cfg.is_ssl: client = ssl.wrap_socket(client, server_side=True, **self.cfg.ssl_options) parser = http.RequestParser(self.cfg, client, addr) req = next(parser) self.handle_request(listener, req, client, addr) except http.errors.NoMoreData as e: self.log.debug("Ignored premature client disconnection. %s", e) except StopIteration as e: self.log.debug("Closing connection. %s", e) except ssl.SSLError as e: if e.args[0] == ssl.SSL_ERROR_EOF: self.log.debug("ssl connection closed") client.close() else: self.log.debug("Error processing SSL request.") self.handle_error(req, client, addr, e) except EnvironmentError as e: if e.errno not in (errno.EPIPE, errno.ECONNRESET, errno.ENOTCONN): self.log.exception("Socket error processing request.") else: if e.errno == errno.ECONNRESET: self.log.debug("Ignoring connection reset") elif e.errno == errno.ENOTCONN: self.log.debug("Ignoring socket not connected") else: self.log.debug("Ignoring EPIPE") except Exception as e: self.handle_error(req, client, addr, e) finally: util.close(client) def handle_request(self, listener, req, client, addr): environ = {} resp = None try: self.cfg.pre_request(self, req) request_start = datetime.now() resp, environ = wsgi.create(req, client, addr, listener.getsockname(), self.cfg) # Force the connection closed until someone shows # a buffering proxy that supports Keep-Alive to # the backend. resp.force_close() self.nr += 1 if self.nr >= self.max_requests: self.log.info("Autorestarting worker after current request.") self.alive = False respiter = self.wsgi(environ, resp.start_response) try: if isinstance(respiter, environ['wsgi.file_wrapper']): resp.write_file(respiter) else: for item in respiter: resp.write(item) resp.close() request_time = datetime.now() - request_start self.log.access(resp, req, environ, request_time) finally: if hasattr(respiter, "close"): respiter.close() except EnvironmentError: # pass to next try-except level util.reraise(*sys.exc_info()) except Exception: if resp and resp.headers_sent: # If the requests have already been sent, we should close the # connection to indicate the error. self.log.exception("Error handling request") try: client.shutdown(socket.SHUT_RDWR) client.close() except EnvironmentError: pass raise StopIteration() raise finally: try: self.cfg.post_request(self, req, environ, resp) except Exception: self.log.exception("Exception in post_request hook") gunicorn-20.1.0/gunicorn/workers/workertmp.py000066400000000000000000000031611401157322000213360ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import os import platform import tempfile from gunicorn import util PLATFORM = platform.system() IS_CYGWIN = PLATFORM.startswith('CYGWIN') class WorkerTmp(object): def __init__(self, cfg): old_umask = os.umask(cfg.umask) fdir = cfg.worker_tmp_dir if fdir and not os.path.isdir(fdir): raise RuntimeError("%s doesn't exist. Can't create workertmp." % fdir) fd, name = tempfile.mkstemp(prefix="wgunicorn-", dir=fdir) os.umask(old_umask) # change the owner and group of the file if the worker will run as # a different user or group, so that the worker can modify the file if cfg.uid != os.geteuid() or cfg.gid != os.getegid(): util.chown(name, cfg.uid, cfg.gid) # unlink the file so we don't leak tempory files try: if not IS_CYGWIN: util.unlink(name) # In Python 3.8, open() emits RuntimeWarning if buffering=1 for binary mode. # Because we never write to this file, pass 0 to switch buffering off. self._tmp = os.fdopen(fd, 'w+b', 0) except Exception: os.close(fd) raise self.spinner = 0 def notify(self): self.spinner = (self.spinner + 1) % 2 os.fchmod(self._tmp.fileno(), self.spinner) def last_update(self): return os.fstat(self._tmp.fileno()).st_ctime def fileno(self): return self._tmp.fileno() def close(self): return self._tmp.close() gunicorn-20.1.0/requirements_dev.txt000066400000000000000000000000621401157322000175310ustar00rootroot00000000000000-r requirements_test.txt sphinx sphinx_rtd_theme gunicorn-20.1.0/requirements_test.txt000066400000000000000000000000631401157322000177330ustar00rootroot00000000000000aiohttp gevent eventlet coverage pytest pytest-cov gunicorn-20.1.0/scripts/000077500000000000000000000000001401157322000151005ustar00rootroot00000000000000gunicorn-20.1.0/scripts/update_thanks.py000066400000000000000000000020701401157322000203030ustar00rootroot00000000000000#!/usr/bin/env python # Usage: git log --format="%an <%ae>" | python update_thanks.py # You will get a result.txt file, you can work with the file (update, remove, ...) # # Install # ======= # pip install validate_email pyDNS # import sys from validate_email import validate_email from email.utils import parseaddr import DNS.Base addresses = set() bad_addresses = set() collection = [] lines = list(reversed(sys.stdin.readlines())) for author in map(str.strip, lines): realname, email_address = parseaddr(author) if email_address not in addresses: if email_address in bad_addresses: continue else: try: value = validate_email(email_address) if value: addresses.add(email_address) collection.append(author) else: bad_addresses.add(email_address) except DNS.Base.TimeoutError: bad_addresses.add(email_address) with open('result.txt', 'w') as output: output.write('\n'.join(collection)) gunicorn-20.1.0/setup.cfg000066400000000000000000000002441401157322000152320ustar00rootroot00000000000000[tool:pytest] norecursedirs = examples lib local src testpaths = tests/ addopts = --assert=plain --cov=gunicorn --cov-report=xml [metadata] license_file = LICENSE gunicorn-20.1.0/setup.py000066400000000000000000000071021401157322000151230ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import os import sys from setuptools import setup, find_packages from setuptools.command.test import test as TestCommand from gunicorn import __version__ CLASSIFIERS = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Other Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: MacOS :: MacOS X', 'Operating System :: POSIX', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Internet', 'Topic :: Utilities', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: WSGI', 'Topic :: Internet :: WWW/HTTP :: WSGI :: Server', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content'] # read long description with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as f: long_description = f.read() # read dev requirements fname = os.path.join(os.path.dirname(__file__), 'requirements_test.txt') with open(fname) as f: tests_require = [l.strip() for l in f.readlines()] class PyTestCommand(TestCommand): user_options = [ ("cov", None, "measure coverage") ] def initialize_options(self): TestCommand.initialize_options(self) self.cov = None def finalize_options(self): TestCommand.finalize_options(self) self.test_args = ['tests'] if self.cov: self.test_args += ['--cov', 'gunicorn'] self.test_suite = True def run_tests(self): import pytest errno = pytest.main(self.test_args) sys.exit(errno) install_requires = [ # We depend on functioning pkg_resources.working_set.add_entry() and # pkg_resources.load_entry_point(). These both work as of 3.0 which # is the first version to support Python 3.4 which we require as a # floor. 'setuptools>=3.0', ] extras_require = { 'gevent': ['gevent>=1.4.0'], 'eventlet': ['eventlet>=0.24.1'], 'tornado': ['tornado>=0.2'], 'gthread': [], 'setproctitle': ['setproctitle'], } setup( name='gunicorn', version=__version__, description='WSGI HTTP Server for UNIX', long_description=long_description, author='Benoit Chesneau', author_email='benoitc@e-engura.com', license='MIT', url='https://gunicorn.org', project_urls={ 'Documentation': 'https://docs.gunicorn.org', 'Homepage': 'https://gunicorn.org', 'Issue tracker': 'https://github.com/benoitc/gunicorn/issues', 'Source code': 'https://github.com/benoitc/gunicorn', }, python_requires='>=3.5', install_requires=install_requires, classifiers=CLASSIFIERS, zip_safe=False, packages=find_packages(exclude=['examples', 'tests']), include_package_data=True, tests_require=tests_require, cmdclass={'test': PyTestCommand}, entry_points=""" [console_scripts] gunicorn=gunicorn.app.wsgiapp:run [paste.server_runner] main=gunicorn.app.pasterapp:serve """, extras_require=extras_require, ) gunicorn-20.1.0/tests/000077500000000000000000000000001401157322000145535ustar00rootroot00000000000000gunicorn-20.1.0/tests/config/000077500000000000000000000000001401157322000160205ustar00rootroot00000000000000gunicorn-20.1.0/tests/config/__init__.py000066400000000000000000000000001401157322000201170ustar00rootroot00000000000000gunicorn-20.1.0/tests/config/test_cfg.py000066400000000000000000000001301401157322000201620ustar00rootroot00000000000000bind = "unix:/tmp/bar/baz" workers = 3 proc_name = "fooey" default_proc_name = "blurgh" gunicorn-20.1.0/tests/config/test_cfg_alt.py000066400000000000000000000000301401157322000210210ustar00rootroot00000000000000proc_name = "not-fooey" gunicorn-20.1.0/tests/config/test_cfg_with_wsgi_app.py000066400000000000000000000000271401157322000231130ustar00rootroot00000000000000wsgi_app = "app1:app1" gunicorn-20.1.0/tests/requests/000077500000000000000000000000001401157322000164265ustar00rootroot00000000000000gunicorn-20.1.0/tests/requests/invalid/000077500000000000000000000000001401157322000200545ustar00rootroot00000000000000gunicorn-20.1.0/tests/requests/invalid/001.http000066400000000000000000000000351401157322000212530ustar00rootroot00000000000000GET /foo/bar HTTP/1.0\r\n bazgunicorn-20.1.0/tests/requests/invalid/001.py000066400000000000000000000001001401157322000207150ustar00rootroot00000000000000from gunicorn.http.errors import NoMoreData request = NoMoreDatagunicorn-20.1.0/tests/requests/invalid/002.http000066400000000000000000000000251401157322000212530ustar00rootroot00000000000000GET HTTP/1.1\r\n \r\ngunicorn-20.1.0/tests/requests/invalid/002.py000066400000000000000000000001211401157322000207210ustar00rootroot00000000000000from gunicorn.http.errors import InvalidRequestLine request = InvalidRequestLine gunicorn-20.1.0/tests/requests/invalid/003.http000066400000000000000000000000361401157322000212560ustar00rootroot00000000000000-blargh /foo HTTP/1.1\r\n \r\ngunicorn-20.1.0/tests/requests/invalid/003.py000066400000000000000000000001241401157322000207250ustar00rootroot00000000000000from gunicorn.http.errors import InvalidRequestMethod request = InvalidRequestMethodgunicorn-20.1.0/tests/requests/invalid/004.http000066400000000000000000000000311401157322000212520ustar00rootroot00000000000000GET /foo FTP/1.1\r\n \r\ngunicorn-20.1.0/tests/requests/invalid/004.py000066400000000000000000000001201401157322000207220ustar00rootroot00000000000000from gunicorn.http.errors import InvalidHTTPVersion request = InvalidHTTPVersiongunicorn-20.1.0/tests/requests/invalid/005.http000066400000000000000000000000511401157322000212550ustar00rootroot00000000000000GET /foo HTTP/1.1\r\n ba\0z: bar\r\n \r\ngunicorn-20.1.0/tests/requests/invalid/005.py000066400000000000000000000001161401157322000207300ustar00rootroot00000000000000from gunicorn.http.errors import InvalidHeaderName request = InvalidHeaderNamegunicorn-20.1.0/tests/requests/invalid/006.http000066400000000000000000000100321401157322000212560ustar00rootroot00000000000000PUT /q=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 HTTP/1.0\r\n \r\n gunicorn-20.1.0/tests/requests/invalid/006.py000066400000000000000000000001151401157322000207300ustar00rootroot00000000000000from gunicorn.http.errors import LimitRequestLine request = LimitRequestLine gunicorn-20.1.0/tests/requests/invalid/007.http000066400000000000000000000202461401157322000212670ustar00rootroot00000000000000PUT /stuff/here?foo=bar HTTP/1.0\r\n Server: http://127.0.0.1:5984\r\n Content-Type: application/json\r\n Content-Length: 14\r\n Someheader: 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\r\n \r\n {"nom": "nom"} gunicorn-20.1.0/tests/requests/invalid/007.py000066400000000000000000000001231401157322000207300ustar00rootroot00000000000000from gunicorn.http.errors import LimitRequestHeaders request = LimitRequestHeaders gunicorn-20.1.0/tests/requests/invalid/008.http000066400000000000000000000302021401157322000212610ustar00rootroot00000000000000PUT /stuff/here?foo=bar HTTP/1.0\r\n Server: http://127.0.0.1:5984\r\n Content-Type: application/json\r\n Someheader: 08aP8931Ltyl9nqyJvjMaRCOgDV3uONtAdHABjoZUG6KAP6h3Vh97O3GJjjovXYgNdrhxc7TriXoAmeehZMJx88EyhcPXO0f09Nvd128SZnxZ2r5jFDELkn26reKRysODSLBZLfjU3vxLzLXKWeFOFJKcZYRH9V7hC98DDS4ZsS7weUksBuK6m86aLNHHHB0Xbyxv1TiDbOWYIzKxV0eZKyk0CaDLDiR0CRuMOf4rwBeuHoMrumzafrFI5iL72ANQZmOvKdk1qQeXkRqEG11YU0kF7f1hSlmgiIgg5maWiBsA9sAg36IIXZMWwJF63zpMgAyjTT8l4pQhSBfhY2xbGAWmLGpyd1rlBm0O5LCoKpnQuTACm2azi0x6a1Qbry9flQBO4jHge2dXiD1si6Gh5q8fZu8ZQ7LLWii2u4rGB7E4XlhnClrCHg5vJmjYf2AItYPA0ogsiIdEEQGpzMJPqrp8Icn5kAAimWF1aCYaDjcdSgWI48PnoxlzIHX50EPFcPOSLecjkstD9z66H554sUXfWn3Mk9lnOUlse6nx0u1YClFK4UFXp98ru9eBBr7pkAsfZ34yPskayGyXPPyzWyBfVd28UuvdEG47SMdyqEpX0rFdk67fAYij0PWMK79mDmGAS37O821o18XUbu0GQjsqAGVMN9LDIAliD9QqtlwdEnplKkUyyZ7GAFJCFffgzppU9CjA2FbPX6ZjTOi4sPoYEyhyeQKVqAe9keYeDpU2qDwq83XEDQUKvP0w48GyavSmdBcrMXjUsu0PfdYpSaKwarrUB3i93HgoQB3ZJIR4lW6iPRTmm28OEKq2MIJGAoTXxCZYM5UacRldlqQOj6JkYz6y7ppWOjJ9yiCUEenuvfcItgmw9HIgGA59JxO8NDLEZLSONfuIgiV7wjsJnxuTOlU4vkjV7fTuOeU91xez7UKhaTqqEW3XBUSLjhKi3IkZg7ukrGZTWPhijFv2EZwEWDAyLlHvZB4X738zGJUlEX1k52EHwrKVKdLfePcaOjAGKsongHBFYxYC8vBBLuKm9RWexKCT14M25pCGloJXZ4OpBRfDQA2kobLUcEXEpzqRBPGN2JdNSBOFlUtUxWKnnPBM6r9S356l3k1o9zTIPeoIitWRjASs4A0iwYc8p5vv5Kt8KtsmW7Xv8dlU8HbZHsy3LI7O9BpUH8cJubqdEhooKABkx71pdcsZGhZb6epyTiPyvOhdJ7tNtFy3KQOameqTgGyd53Z42eZ0AjaOEvnzermi2E0xo3MMHFhB74TFtNAI3ppxxyqknc1mzUqZ49Wi8YPBg9ids6IgZvddBQYvwEozkmyGAkatQtt9TD4LjU3TyyUlhNG21q7CzEEl8NNsVrV6QyHsfw7E5w7XcoT7OQkBYoZwHIAjfekehnpc2llRtRY5m43fPVasmsVazOR36DRSLZJPHAqUDO0LInu9mgP57Mnz9CgylEmdE2aaYs426rnTFR3G3CfjLofHfjaLOkAegr4W3jx6MNMMOMZw2u46YTCnlfbBK6ZA1UYeAH1DIQJykcSQESinC8HpYIJt9A8g7UT0awzRP1F9nHa3wDnaAHndQYKMrjzlWo8ejQ0XHWgHhqnWHgW4h9sOnJckH00CYK1fHUKASJ3D8kOKax6uplexfz6BCvAoL9zm5TjeB1yxrpLp9NjjTWSKG2HOZhPkGpdEqU4mjnN2AkUVACPGos5YLBmTnSrdOEGZJDlAvJOUt800Mu3BYc1MiDIB6LMSSV5RsIUDFOzNletGQoq4G3yHZmx78uEse5vUTPFF3KT8LCrssqdIU9H97Npgf6N5j8arQ7ykLzN459jJaUzpGIo6uowPnUSatDf9GAvAmWNvsVTz6bYiAV71C7QF0C7UolYIQY6DHJEHejgX2YMEovWNLPL50eeC51h4DdPNv5G4ZdNtQTRVybYBZMpetGDiFmXN0JKa1sKHOSZxdrhKjxDIhrYVyCcRUMQ0sjGGHFuOcRszr6E5igEMtsebHQ3KYiGd5B27LikpUHhk61rgZlulHdMoS6YgQs6SV6UMVNku6sCw529xhUciDwRMhsbAjDlahYbrGa3NryxyV5LrXONGGKCchCqv7vDMdAtPrVr8M2vL5MySQAC3g90iugGQcLH3hCf9f1Kn5X0hM4KZTfwOPJhlfJsMRNhssiDoXaycUvOUS58266yPDlitPIAzO03XClm4EDPXGIwcwiFr7FcDo3tQIMZVy87i48Zb80s3zAYRiBIS0vO3RKGx3OGN5zid2B7MfnfLzvpvgZoirHhAqXffnym5abpZNzGuo5GowTRA2Ptk4Ve2JFoHACWpD6HiGnRZ9QVOmPICoQrSUQw45Jlk9onKJz5Erhnx0943Uno6tMJ5jbrWBNiIO7i04xzRBgujeiAJvuQkVDX2QLKRxZ7s6rhdfOaq6R6uL108gEzzlXOLqTTJXgM63rcUWNbE7wsIXcCFSF59LLJ7G5Qea33suxdDX6DcK4a0VMZoxmWPtCi1dAT9ggJqc2Sh7mkAqizaB16RXZvSydchpdVj6s4qn4ivr0HKHdAstX0XZ0FFU6lOiNmU3vasMg2uaVG8tyuG8N8VsuXIOQs7xtFxDhilYb8MQ9vES9pWfWPSXFlJAq4XKPY8a0JOIx57EQuWHo3uWgRTIRThvZP9YYzSnjGIHwjS8JeppICHofADXZhJ0uDQaQs7MiXEALpGmT3W6w0G3tBdZcuTDkWx1HsT5jd9jQeJpgD2VxdKh8U4Q3vANTAuwBXLJ2P0stS8Q72JWgNPwKYTY9cPoaGZlUFGgVsq8CdEFH9yW0c27G5s5sfHsyep6t4VxIHHMOX2GmMRyGxDI33am1J7ZmJ1NyXiwkHxtPH5QBpU2PMu2Guf3xIxlk3snMkMAsGO0vYfqO9tdIgdxMYO3HZTYv99OXaHcNQ5u0pRZZyVrNOIPurkEOdJy0nowPemIgUuHWh8vQCuDZav1m35AOl6ftSFuChSm5KstEWnC7q8mJ0juJEBkCRmQphP3V1pqiDjz6YA90qEe7MA3nzT0nHG8A1hWlqcPVPNz4qWNF6Fq1ub4075aXO0H7Krb6rhWGb3ZRPjpb4BKN8jGFQrBUMZprtjAJ67BnfmYgE0mmGLV2QP10gYS1T06kBRyrtp7he6wsPiBPJ7wxPLHNUN2SGQHBTSKagndM99fuaga5Sw9OT8Fzdo7xUJXfhJ97gUnNDrknal0B00NMNvajZeQQTJyBsVSwBZtZ45ZCcq1idc7GWC0MITSk58cIVkSPXbrERUaygyY13dPeEVzjVi9aVJwUF6eJu1s8u3FCJqp2GoWIItwvZO69asX75fekFkmFpNavxM0X0dZC01TTPpV6E6PJoIfW8C06CKNHV7Gk2mkTWGSwUG4xD2L3G3XarodHDcmumFJX9Xviv0rvm38SCtin6OpjH8MHYDrj1OxTJbC2VclJxv73z2BDBquosKOik0fmgbPZN0FUTmjBEwHTvqd5QHTwb3nOpEz3X6YCF0lrcrQc0uhyr7gBGBs86nUBWFRp1LKjIRVTVXDipajqNDTQGNZtzvR9MUf1yJJV07inbrlPOENd7rHpKCrJtoZXOkDqInaIqoMCG3DVd353BGmZNJEKOa3DnL7fb9zwuHlvHAfCco7ZS4wAV87trWkp6skXux9v5WhkumbUyGq4ia6DM1PuqqnFfBTAWDzJsnggAJrzr8O7JbDtaXwcW9sqaOb0S6NvnUDZqiNdDQPMDOKvXRJJJQdf1FSrPCCSPEEWO1SeVwictj7rTbpWGRoukwhgJALys95pGGOQxCPzRGrtVFnGcsLN1CwI3wLbmDnNKUv3KpOLEOPRxQXeXuJRIiYCFum44c0wNr731DvHn3YEJMH4iwFONl1rolEL4w6KFUOCq7ekrE5iyUt1V32PNtuUshXRjOYjBval29JMH5GoqZlGhCczzHMA61cmuzqdFwiPCB9yzqvJTg8TqMNvwKJztFIQK4mc5Ev5rRVSozD796AVRKT8rZF39IA1kmCLdXqz7CCC8x4QjjDpxjKCXP5HkWf9mp2FNBjE3pAeaEc6Vk2ENLlW8WVCe08aP8931Ltyl9nqyJvjMaRCOgDV3uONtAdHABjoZUG6KAP6h3Vh97O3GJjjovXYgNdrhxc7TriXoAmeehZMJx88EyhcPXO0f09Nvd128SZnxZ2r5jFDELkn26reKRysODSLBZLfjU3vxLzLXKWeFOFJKcZYRH9V7hC98DDS4ZsS7weUksBuK6m86aLNHHHB0Xbyxv1TiDbOWYIzKxV0eZKyk0CaDLDiR0CRuMOf4rwBeuHoMrumzafrFI5iL72ANQZmOvKdk1qQeXkRqEG11YU0kF7f1hSlmgiIgg5maWiBsA9sAg36IIXZMWwJF63zpMgAyjTT8l4pQhSBfhY2xbGAWmLGpyd1rlBm0O5LCoKpnQuTACm2azi0x6a1Qbry9flQBO4jHge2dXiD1si6Gh5q8fZu8ZQ7LLWii2u4rGB7E4XlhnClrCHg5vJmjYf2AItYPA0ogsiIdEEQGpzMJPqrp8Icn5kAAimWF1aCYaDjcdSgWI48PnoxlzIHX50EPFcPOSLecjkstD9z66H554sUXfWn3Mk9lnOUlse6nx0u1YClFK4UFXp98ru9eBBr7pkAsfZ34yPskayGyXPPyzWyBfVd28UuvdEG47SMdyqEpX0rFdk67fAYij0PWMK79mDmGAS37O821o18XUbu0GQjsqAGVMN9LDIAliD9QqtlwdEnplKkUyyZ7GAFJCFffgzppU9CjA2FbPX6ZjTOi4sPoYEyhyeQKVqAe9keYeDpU2qDwq83XEDQUKvP0w48GyavSmdBcrMXjUsu0PfdYpSaKwarrUB3i93HgoQB3ZJIR4lW6iPRTmm28OEKq2MIJGAoTXxCZYM5UacRldlqQOj6JkYz6y7ppWOjJ9yiCUEenuvfcItgmw9HIgGA59JxO8NDLEZLSONfuIgiV7wjsJnxuTOlU4vkjV7fTuOeU91xez7UKhaTqqEW3XBUSLjhKi3IkZg7ukrGZTWPhijFv2EZwEWDAyLlHvZB4X738zGJUlEX1k52EHwrKVKdLfePcaOjAGKsongHBFYxYC8vBBLuKm9RWexKCT14M25pCGloJXZ4OpBRfDQA2kobLUcEXEpzqRBPGN2JdNSBOFlUtUxWKnnPBM6r9S356l3k1o9zTIPeoIitWRjASs4A0iwYc8p5vv5Kt8KtsmW7Xv8dlU8HbZHsy3LI7O9BpUH8cJubqdEhooKABkx71pdcsZGhZb6epyTiPyvOhdJ7tNtFy3KQOameqTgGyd53Z42eZ0AjaOEvnzermi2E0xo3MMHFhB74TFtNAI3ppxxyqknc1mzUqZ49Wi8YPBg9ids6IgZvddBQYvwEozkmyGAkatQtt9TD4LjU3TyyUlhNG21q7CzEEl8NNsVrV6QyHsfw7E5w7XcoT7OQkBYoZwHIAjfekehnpc2llRtRY5m43fPVasmsVazOR36DRSLZJPHAqUDO0LInu9mgP57Mnz9CgylEmdE2aaYs426rnTFR3G3CfjLofHfjaLOkAegr4W3jx6MNMMOMZw2u46YTCnlfbBK6ZA1UYeAH1DIQJykcSQESinC8HpYIJt9A8g7UT0awzRP1F9nHa3wDnaAHndQYKMrjzlWo8ejQ0XHWgHhqnWHgW4h9sOnJckH00CYK1fHUKASJ3D8kOKax6uplexfz6BCvAoL9zm5TjeB1yxrpLp9NjjTWSKG2HOZhPkGpdEqU4mjnN2AkUVACPGos5YLBmTnSrdOEGZJDlAvJOUt800Mu3BYc1MiDIB6LMSSV5RsIUDFOzNletGQoq4G3yHZmx78uEse5vUTPFF3KT8LCrssqdIU9H97Npgf6N5j8arQ7ykLzN459jJaUzpGIo6uowPnUSatDf9GAvAmWNvsVTz6bYiAV71C7QF0C7UolYIQY6DHJEHejgX2YMEovWNLPL50eeC51h4DdPNv5G4ZdNtQTRVybYBZMpetGDiFmXN0JKa1sKHOSZxdrhKjxDIhrYVyCcRUMQ0sjGGHFuOcRszr6E5igEMtsebHQ3KYiGd5B27LikpUHhk61rgZlulHdMoS6YgQs6SV6UMVNku6sCw529xhUciDwRMhsbAjDlahYbrGa3NryxyV5LrXONGGKCchCqv7vDMdAtPrVr8M2vL5MySQAC3g90iugGQcLH3hCf9f1Kn5X0hM4KZTfwOPJhlfJsMRNhssiDoXaycUvOUS58266yPDlitPIAzO03XClm4EDPXGIwcwiFr7FcDo3tQIMZVy87i48Zb80s3zAYRiBIS0vO3RKGx3OGN5zid2B7MfnfLzvpvgZoirHhAqXffnym5abpZNzGuo5GowTRA2Ptk4Ve2JFoHACWpD6HiGnRZ9QVOmPICoQrSUQw45Jlk9onKJz5Erhnx0943Uno6tMJ5jbrWBNiIO7i04xzRBgujeiAJvuQkVDX2QLKRxZ7s6rhdfOaq6R6uL108gEzzlXOLqTTJXgM63rcUWNbE7wsIXcCFSF59LLJ7G5Qea33suxdDX6DcK4a0VMZoxmWPtCi1dAT9ggJqc2Sh7mkAqizaB16RXZvSydchpdVj6s4qn4ivr0HKHdAstX0XZ0FFU6lOiNmU3vasMg2uaVG8tyuG8N8VsuXIOQs7xtFxDhilYb8MQ9vES9pWfWPSXFlJAq4XKPY8a0JOIx57EQuWHo3uWgRTIRThvZP9YYzSnjGIHwjS8JeppICHofADXZhJ0uDQaQs7MiXEALpGmT3W6w0G3tBdZcuTDkWx1HsT5jd9jQeJpgD2VxdKh8U4Q3vANTAuwBXLJ2P0stS8Q72JWgNPwKYTY9cPoaGZlUFGgVsq8CdEFH9yW0c27G5s5sfHsyep6t4VxIHHMOX2GmMRyGxDI33am1J7ZmJ1NyXiwkHxtPH5QBpU2PMu2Guf3xIxlk3snMkMAsGO0vYfqO9tdIgdxMYO3HZTYv99OXaHcNQ5u0pRZZyVrNOIPurkEOdJy0nowPemIgUuHWh8vQCuDZav1m35AOl6ftSFuChSm5KstEWnC7q8mJ0juJEBkCRmQphP3V1pqiDjz6YA90qEe7MA3nzT0nHG8A1hWlqcPVPNz4qWNF6Fq1ub4075aXO0H7Krb6rhWGb3ZRPjpb4BKN8jGFQrBUMZprtjAJ67BnfmYgE0mmGLV2QP10gYS1T06kBRyrtp7he6wsPiBPJ7wxPLHNUN2SGQHBTSKagndM99fuaga5Sw9OT8Fzdo7xUJXfhJ97gUnNDrknal0B00NMNvajZeQQTJyBsVSwBZtZ45ZCcq1idc7GWC0MITSk58cIVkSPXbrERUaygyY13dPeEVzjVi9aVJwUF6eJu1s8u3FCJqp2GoWIItwvZO69asX75fekFkmFpNavxM0X0dZC01TTPpV6E6PJoIfW8C06CKNHV7Gk2mkTWGSwUG4xD2L3G3XarodHDcmumFJX9Xviv0rvm38SCtin6OpjH8MHYDrj1OxTJbC2VclJxv73z2BDBquosKOik0fmgbPZN0FUTmjBEwHTvqd5QHTwb3nOpEz3X6YCF0lrcrQc0uhyr7gBGBs86nUBWFRp1LKjIRVTVXDipajqNDTQGNZtzvR9MUf1yJJV07inbrlPOENd7rHpKCrJtoZXOkDqInaIqoMCG3DVd353BGmZNJEKOa3DnL7fb9zwuHlvHAfCco7ZS4wAV87trWkp6skXux9v5WhkumbUyGq4ia6DM1PuqqnFfBTAWDzJsnggAJrzr8O7JbDtaXwcW9sqaOb0S6NvnUDZqiNdDQPMDOKvXRJJJQdf1FSrPCCSPEEWO1SeVwictj7rTbpWGRoukwhgJALys95pGGOQxCPzRGrtVFnGcsLN1CwI3wLbmDnNKUv3KpOLEOPRxQXeXuJRIiYCFum44c0wNr731DvHn3YEJMH4iwFONl1rolEL4w6KFUOCq7ekrE5iyUt1V32PNtuUshXRjOYjBval29JMH5GoqZlGhCczzHMA61cmuzqdFwiPCB9yzqvJTg8TqMNvwKJztFIQK4mc5Ev5rRVSozD796AVRKT8rZF39IA1kmCLdXqz7CCC8x4QjjDpxjKCXP5HkWf9mp2FNBjE3pAeaE\r\n Someheader: 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\r\n \r\n gunicorn-20.1.0/tests/requests/invalid/008.py000066400000000000000000000001231401157322000207310ustar00rootroot00000000000000from gunicorn.http.errors import LimitRequestHeaders request = LimitRequestHeaders gunicorn-20.1.0/tests/requests/invalid/009.http000066400000000000000000000034451401157322000212730ustar00rootroot00000000000000PUT /stuff/here?foo=bar HTTP/1.0\r\n Server: http://127.0.0.1:5984\r\n Content-Type: application/json\r\n Content-Length: 14\r\n header0: 0\r\n header1: 1\r\n header2: 2\r\n header3: 3\r\n header4: 4\r\n header5: 5\r\n header6: 6\r\n header7: 7\r\n header8: 8\r\n header9: 9\r\n header10: 10\r\n header11: 11\r\n header12: 12\r\n header13: 13\r\n header14: 14\r\n header15: 15\r\n header16: 16\r\n header17: 17\r\n header18: 18\r\n header19: 19\r\n header20: 20\r\n header21: 21\r\n header22: 22\r\n header23: 23\r\n header24: 24\r\n header25: 25\r\n header26: 26\r\n header27: 27\r\n header28: 28\r\n header29: 29\r\n header30: 30\r\n header31: 31\r\n header32: 32\r\n header33: 33\r\n header34: 34\r\n header35: 35\r\n header36: 36\r\n header37: 37\r\n header38: 38\r\n header39: 39\r\n header40: 40\r\n header41: 41\r\n header42: 42\r\n header43: 43\r\n header44: 44\r\n header45: 45\r\n header46: 46\r\n header47: 47\r\n header48: 48\r\n header49: 49\r\n header50: 50\r\n header51: 51\r\n header52: 52\r\n header53: 53\r\n header54: 54\r\n header55: 55\r\n header56: 56\r\n header57: 57\r\n header58: 58\r\n header59: 59\r\n header60: 60\r\n header61: 61\r\n header62: 62\r\n header63: 63\r\n header64: 64\r\n header65: 65\r\n header66: 66\r\n header67: 67\r\n header68: 68\r\n header69: 69\r\n header70: 70\r\n header71: 71\r\n header72: 72\r\n header73: 73\r\n header74: 74\r\n header75: 75\r\n header76: 76\r\n header77: 77\r\n header78: 78\r\n header79: 79\r\n header80: 80\r\n header81: 81\r\n header82: 82\r\n header83: 83\r\n header84: 84\r\n header85: 85\r\n header86: 86\r\n header87: 87\r\n header88: 88\r\n header89: 89\r\n header90: 90\r\n header91: 91\r\n header92: 92\r\n header93: 93\r\n header94: 94\r\n header95: 95\r\n header96: 96\r\n header97: 97\r\n header98: 98\r\n header99: 99\r\n \r\n {"nom": "nom"} gunicorn-20.1.0/tests/requests/invalid/009.py000066400000000000000000000001231401157322000207320ustar00rootroot00000000000000from gunicorn.http.errors import LimitRequestHeaders request = LimitRequestHeaders gunicorn-20.1.0/tests/requests/invalid/010.http000066400000000000000000000000541401157322000212540ustar00rootroot00000000000000GET /test HTTP/1.1\r\n Accept: */*\r\n \r\n gunicorn-20.1.0/tests/requests/invalid/010.py000066400000000000000000000002561401157322000207310ustar00rootroot00000000000000from gunicorn.config import Config from gunicorn.http.errors import LimitRequestHeaders request = LimitRequestHeaders cfg = Config() cfg.set('limit_request_field_size', 10) gunicorn-20.1.0/tests/requests/invalid/011.http000066400000000000000000000002511401157322000212540ustar00rootroot00000000000000GET /test HTTP/1.1\r\n 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 Host: 0.0.0.0=5000\r\n Accept: */*\r\n \r\n gunicorn-20.1.0/tests/requests/invalid/011.py000066400000000000000000000002511401157322000207250ustar00rootroot00000000000000from gunicorn.config import Config from gunicorn.http.errors import LimitRequestHeaders request = LimitRequestHeaders cfg = Config() cfg.set('limit_request_fields', 2) gunicorn-20.1.0/tests/requests/invalid/012.http000066400000000000000000000002511401157322000212550ustar00rootroot00000000000000GET /test HTTP/1.1\r\n 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 Host: 0.0.0.0=5000\r\n Accept: */*\r\n \r\n gunicorn-20.1.0/tests/requests/invalid/012.py000066400000000000000000000002561401157322000207330ustar00rootroot00000000000000from gunicorn.config import Config from gunicorn.http.errors import LimitRequestHeaders request = LimitRequestHeaders cfg = Config() cfg.set('limit_request_field_size', 98) gunicorn-20.1.0/tests/requests/invalid/013.http000066400000000000000000000000611401157322000212550ustar00rootroot00000000000000GET /test HTTP/1.1\r\n Accept:\r\n */*\r\n \r\n gunicorn-20.1.0/tests/requests/invalid/013.py000066400000000000000000000002561401157322000207340ustar00rootroot00000000000000from gunicorn.config import Config from gunicorn.http.errors import LimitRequestHeaders request = LimitRequestHeaders cfg = Config() cfg.set('limit_request_field_size', 14) gunicorn-20.1.0/tests/requests/invalid/014.http000066400000000000000000000001161401157322000212570ustar00rootroot00000000000000PUT /stuff/here?foo=bar HTTP/1.0\r\n CONTENT-LENGTH: -1\r\n \r\n {"test": "-1}gunicorn-20.1.0/tests/requests/invalid/014.py000066400000000000000000000001101401157322000207220ustar00rootroot00000000000000from gunicorn.http.errors import InvalidHeader request = InvalidHeader gunicorn-20.1.0/tests/requests/invalid/015.http000066400000000000000000000001301401157322000212540ustar00rootroot00000000000000POST /stuff/here?foo=bar HTTP/1.0\r\n CONTENT-LENGTH: bla-bla-bla\r\n \r\n {"test": "-1}gunicorn-20.1.0/tests/requests/invalid/015.py000066400000000000000000000001101401157322000207230ustar00rootroot00000000000000from gunicorn.http.errors import InvalidHeader request = InvalidHeader gunicorn-20.1.0/tests/requests/invalid/016.http000066400000000000000000000000411401157322000212560ustar00rootroot00000000000000PUT s://]ufd/: HTTP/1.1\r\n \r\n gunicorn-20.1.0/tests/requests/invalid/016.py000066400000000000000000000001221401157322000207270ustar00rootroot00000000000000from gunicorn.http.errors import InvalidRequestLine request = InvalidRequestLine gunicorn-20.1.0/tests/requests/invalid/017.http000066400000000000000000000200361401157322000212650ustar00rootroot00000000000000GET /test HTTP/1.1\r\n Long-header: 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345\r\n \r\n gunicorn-20.1.0/tests/requests/invalid/017.py000066400000000000000000000002061401157322000207330ustar00rootroot00000000000000from gunicorn.config import Config from gunicorn.http.errors import LimitRequestHeaders cfg = Config() request = LimitRequestHeaders gunicorn-20.1.0/tests/requests/invalid/018.http000066400000000000000000000000601401157322000212610ustar00rootroot00000000000000GET /test HTTP/111\r\n Host: localhost\r\n \r\n gunicorn-20.1.0/tests/requests/invalid/018.py000066400000000000000000000001211401157322000207300ustar00rootroot00000000000000from gunicorn.http.errors import InvalidHTTPVersion request = InvalidHTTPVersion gunicorn-20.1.0/tests/requests/invalid/019.http000066400000000000000000000001221401157322000212610ustar00rootroot00000000000000GET /test HTTP/1.1\r\n X-Forwarded-Proto: https\r\n X-Forwarded-Ssl: off\r\n \r\n gunicorn-20.1.0/tests/requests/invalid/019.py000066400000000000000000000002541401157322000207400ustar00rootroot00000000000000from gunicorn.config import Config from gunicorn.http.errors import InvalidSchemeHeaders request = InvalidSchemeHeaders cfg = Config() cfg.set('forwarded_allow_ips', '*') gunicorn-20.1.0/tests/requests/invalid/020.http000066400000000000000000000001051401157322000212520ustar00rootroot00000000000000GET /stuff/here?foo=bar HTTP/1.1\r\n Content-Length : 3\r\n \r\n xyz gunicorn-20.1.0/tests/requests/invalid/020.py000066400000000000000000000002021401157322000207210ustar00rootroot00000000000000from gunicorn.config import Config from gunicorn.http.errors import InvalidHeaderName cfg = Config() request = InvalidHeaderName gunicorn-20.1.0/tests/requests/invalid/021.http000066400000000000000000000001321401157322000212530ustar00rootroot00000000000000GET /stuff/here?foo=bar HTTP/1.1\r\n Content-Length: 3\r\n Content-Length: 2\r\n \r\n xyz gunicorn-20.1.0/tests/requests/invalid/021.py000066400000000000000000000001721401157322000207300ustar00rootroot00000000000000from gunicorn.config import Config from gunicorn.http.errors import InvalidHeader cfg = Config() request = InvalidHeader gunicorn-20.1.0/tests/requests/invalid/pp_01.http000066400000000000000000000000451401157322000216730ustar00rootroot00000000000000PROXY TCP4 192.168.0.1 192.16...\r\n gunicorn-20.1.0/tests/requests/invalid/pp_01.py000066400000000000000000000002411401157322000213420ustar00rootroot00000000000000from gunicorn.config import Config from gunicorn.http.errors import InvalidProxyLine cfg = Config() cfg.set("proxy_protocol", True) request = InvalidProxyLine gunicorn-20.1.0/tests/requests/invalid/pp_02.http000066400000000000000000000000651401157322000216760ustar00rootroot00000000000000PROXY TCP4 192.168.0.1 192.168.0.11 65iii 100000\r\n gunicorn-20.1.0/tests/requests/invalid/pp_02.py000066400000000000000000000002411401157322000213430ustar00rootroot00000000000000from gunicorn.config import Config from gunicorn.http.errors import InvalidProxyLine cfg = Config() cfg.set('proxy_protocol', True) request = InvalidProxyLine gunicorn-20.1.0/tests/requests/valid/000077500000000000000000000000001401157322000175255ustar00rootroot00000000000000gunicorn-20.1.0/tests/requests/valid/001.http000066400000000000000000000002251401157322000207250ustar00rootroot00000000000000PUT /stuff/here?foo=bar HTTP/1.0\r\n Server: http://127.0.0.1:5984\r\n Content-Type: application/json\r\n Content-Length: 14\r\n \r\n {"nom": "nom"} gunicorn-20.1.0/tests/requests/valid/001.py000066400000000000000000000004231401157322000203760ustar00rootroot00000000000000request = { "method": "PUT", "uri": uri("/stuff/here?foo=bar"), "version": (1, 0), "headers": [ ("SERVER", "http://127.0.0.1:5984"), ("CONTENT-TYPE", "application/json"), ("CONTENT-LENGTH", "14") ], "body": b'{"nom": "nom"}' } gunicorn-20.1.0/tests/requests/valid/002.http000066400000000000000000000002501401157322000207240ustar00rootroot00000000000000GET /test HTTP/1.1\r\n 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 Host: 0.0.0.0=5000\r\n Accept: */*\r\n \r\ngunicorn-20.1.0/tests/requests/valid/002.py000066400000000000000000000004501401157322000203770ustar00rootroot00000000000000request = { "method": "GET", "uri": uri("/test"), "version": (1, 1), "headers": [ ("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"), ("HOST", "0.0.0.0=5000"), ("ACCEPT", "*/*") ], "body": b"" } gunicorn-20.1.0/tests/requests/valid/003.http000066400000000000000000000006121401157322000207270ustar00rootroot00000000000000GET /favicon.ico HTTP/1.1\r\n Host: 0.0.0.0=5000\r\n User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n Accept-Language: en-us,en;q=0.5\r\n Accept-Encoding: gzip,deflate\r\n Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n Keep-Alive: 300\r\n Connection: keep-alive\r\n \r\ngunicorn-20.1.0/tests/requests/valid/003.py000066400000000000000000000011011401157322000203720ustar00rootroot00000000000000request = { "method": "GET", "uri": uri("/favicon.ico"), "version": (1, 1), "headers": [ ("HOST", "0.0.0.0=5000"), ("USER-AGENT", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0"), ("ACCEPT", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), ("ACCEPT-LANGUAGE", "en-us,en;q=0.5"), ("ACCEPT-ENCODING", "gzip,deflate"), ("ACCEPT-CHARSET", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"), ("KEEP-ALIVE", "300"), ("CONNECTION", "keep-alive") ], "body": b"" } gunicorn-20.1.0/tests/requests/valid/004.http000066400000000000000000000000711401157322000207270ustar00rootroot00000000000000GET /silly HTTP/1.1\r\n aaaaaaaaaaaaa:++++++++++\r\n \r\ngunicorn-20.1.0/tests/requests/valid/004.py000066400000000000000000000002441401157322000204020ustar00rootroot00000000000000request = { "method": "GET", "uri": uri("/silly"), "version": (1, 1), "headers": [ ("AAAAAAAAAAAAA", "++++++++++") ], "body": b"" } gunicorn-20.1.0/tests/requests/valid/005.http000066400000000000000000000000761401157322000207350ustar00rootroot00000000000000GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n \r\ngunicorn-20.1.0/tests/requests/valid/005.py000066400000000000000000000002311401157322000203770ustar00rootroot00000000000000request = { "method": "GET", "uri": uri("/forums/1/topics/2375?page=1#posts-17408"), "version": (1, 1), "headers": [], "body": b"" } gunicorn-20.1.0/tests/requests/valid/006.http000066400000000000000000000000631401157322000207320ustar00rootroot00000000000000GET /get_no_headers_no_body/world HTTP/1.1\r\n \r\ngunicorn-20.1.0/tests/requests/valid/006.py000066400000000000000000000002161401157322000204030ustar00rootroot00000000000000request = { "method": "GET", "uri": uri("/get_no_headers_no_body/world"), "version": (1, 1), "headers": [], "body": b"" } gunicorn-20.1.0/tests/requests/valid/007.http000066400000000000000000000000751401157322000207360ustar00rootroot00000000000000GET /get_one_header_no_body HTTP/1.1\r\n Accept: */*\r\n \r\ngunicorn-20.1.0/tests/requests/valid/007.py000066400000000000000000000002471401157322000204100ustar00rootroot00000000000000request = { "method": "GET", "uri": uri("/get_one_header_no_body"), "version": (1, 1), "headers": [ ("ACCEPT", "*/*") ], "body": b"" } gunicorn-20.1.0/tests/requests/valid/008.http000066400000000000000000000001111401157322000207260ustar00rootroot00000000000000GET /unusual_content_length HTTP/1.0\r\n conTENT-Length: 5\r\n \r\n HELLOgunicorn-20.1.0/tests/requests/valid/008.py000066400000000000000000000002621401157322000204060ustar00rootroot00000000000000request = { "method": "GET", "uri": uri("/unusual_content_length"), "version": (1, 0), "headers": [ ("CONTENT-LENGTH", "5") ], "body": b"HELLO" } gunicorn-20.1.0/tests/requests/valid/009.http000066400000000000000000000002111401157322000207300ustar00rootroot00000000000000POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n Accept: */*\r\n Transfer-Encoding: identity\r\n Content-Length: 5\r\n \r\n Worldgunicorn-20.1.0/tests/requests/valid/009.py000066400000000000000000000004101401157322000204020ustar00rootroot00000000000000request = { "method": "POST", "uri": uri("/post_identity_body_world?q=search#hey"), "version": (1, 1), "headers": [ ("ACCEPT", "*/*"), ("TRANSFER-ENCODING", "identity"), ("CONTENT-LENGTH", "5") ], "body": b"World" } gunicorn-20.1.0/tests/requests/valid/010.http000066400000000000000000000002061401157322000207240ustar00rootroot00000000000000POST /post_chunked_all_your_base HTTP/1.1\r\n Transfer-Encoding: chunked\r\n \r\n 1e\r\n all your base are belong to us\r\n 0\r\n \r\ngunicorn-20.1.0/tests/requests/valid/010.py000066400000000000000000000003321401157322000203750ustar00rootroot00000000000000request = { "method": "POST", "uri": uri("/post_chunked_all_your_base"), "version": (1, 1), "headers": [ ("TRANSFER-ENCODING", "chunked"), ], "body": b"all your base are belong to us" } gunicorn-20.1.0/tests/requests/valid/011.http000066400000000000000000000001751401157322000207320ustar00rootroot00000000000000POST /two_chunks_mult_zero_end HTTP/1.1\r\n Transfer-Encoding: chunked\r\n \r\n 5\r\n hello\r\n 6\r\n world\r\n 000\r\n \r\ngunicorn-20.1.0/tests/requests/valid/011.py000066400000000000000000000003041401157322000203750ustar00rootroot00000000000000request = { "method": "POST", "uri": uri("/two_chunks_mult_zero_end"), "version": (1, 1), "headers": [ ("TRANSFER-ENCODING", "chunked") ], "body": b"hello world" } gunicorn-20.1.0/tests/requests/valid/012.http000066400000000000000000000002461401157322000207320ustar00rootroot00000000000000POST /chunked_w_trailing_headers HTTP/1.1\r\n Transfer-Encoding: chunked\r\n \r\n 5\r\n hello\r\n 6\r\n world\r\n 0\r\n Vary: *\r\n Content-Type: text/plain\r\n \r\ngunicorn-20.1.0/tests/requests/valid/012.py000066400000000000000000000004351401157322000204030ustar00rootroot00000000000000request = { "method": "POST", "uri": uri("/chunked_w_trailing_headers"), "version": (1, 1), "headers": [ ("TRANSFER-ENCODING", "chunked") ], "body": b"hello world", "trailers": [ ("VARY", "*"), ("CONTENT-TYPE", "text/plain") ] } gunicorn-20.1.0/tests/requests/valid/013.http000066400000000000000000000002371401157322000207330ustar00rootroot00000000000000POST /chunked_w_extensions HTTP/1.1\r\n Transfer-Encoding: chunked\r\n \r\n 5; some; parameters=stuff\r\n hello\r\n 6; blahblah; blah\r\n world\r\n 0\r\n \r\ngunicorn-20.1.0/tests/requests/valid/013.py000066400000000000000000000003001401157322000203730ustar00rootroot00000000000000request = { "method": "POST", "uri": uri("/chunked_w_extensions"), "version": (1, 1), "headers": [ ("TRANSFER-ENCODING", "chunked") ], "body": b"hello world" } gunicorn-20.1.0/tests/requests/valid/014.http000066400000000000000000000000561401157322000207330ustar00rootroot00000000000000GET /with_"quotes"?foo="bar" HTTP/1.1\r\n \r\ngunicorn-20.1.0/tests/requests/valid/014.py000066400000000000000000000002111401157322000203750ustar00rootroot00000000000000request = { "method": "GET", "uri": uri('/with_"quotes"?foo="bar"'), "version": (1, 1), "headers": [], "body": b"" } gunicorn-20.1.0/tests/requests/valid/015.http000066400000000000000000000001421401157322000207300ustar00rootroot00000000000000GET /test HTTP/1.0\r\n Host: 0.0.0.0:5000\r\n User-Agent: ApacheBench/2.3\r\n Accept: */*\r\n \r\ngunicorn-20.1.0/tests/requests/valid/015.py000066400000000000000000000003421401157322000204030ustar00rootroot00000000000000request = { "method": "GET", "uri": uri("/test"), "version": (1, 0), "headers": [ ("HOST", "0.0.0.0:5000"), ("USER-AGENT", "ApacheBench/2.3"), ("ACCEPT", "*/*") ], "body": b"" } gunicorn-20.1.0/tests/requests/valid/016.http000066400000000000000000000042721401157322000207410ustar00rootroot00000000000000GET / HTTP/1.1\r\n X-SSL-Cert: -----BEGIN CERTIFICATE-----\r\n MIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n ETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n AkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n dWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n SzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n BAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n BQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n W51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n gW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n 0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n u2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n wgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n 1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n BglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n VR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n loCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n aWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n 9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n IjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n BgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n cHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4Qg\r\n EDBDAWLmh0dHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC\r\n 5jcmwwPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n Y3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n XCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n UO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n hTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n wTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n Yhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n RA==\r\n -----END CERTIFICATE-----\r\n \r\ngunicorn-20.1.0/tests/requests/valid/016.py000066400000000000000000000045101401157322000204050ustar00rootroot00000000000000certificate = """-----BEGIN CERTIFICATE-----\r\n MIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n ETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n AkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n dWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n SzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n BAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n BQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n W51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n gW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n 0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n u2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n wgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n 1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n BglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n VR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n loCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n aWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n 9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n IjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n BgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n cHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4Qg\r\n EDBDAWLmh0dHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC\r\n 5jcmwwPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n Y3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n XCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n UO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n hTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n wTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n Yhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n RA==\r\n -----END CERTIFICATE-----""".replace("\n\n", "\n") request = { "method": "GET", "uri": uri("/"), "version": (1, 1), "headers": [("X-SSL-CERT", certificate)], "body": b"" } gunicorn-20.1.0/tests/requests/valid/017.http000066400000000000000000000001331401157322000207320ustar00rootroot00000000000000GET /stuff/here?foo=bar HTTP/1.0\r\n If-Match: bazinga!\r\n If-Match: large-sound\r\n \r\n gunicorn-20.1.0/tests/requests/valid/017.py000066400000000000000000000003171401157322000204070ustar00rootroot00000000000000request = { "method": "GET", "uri": uri("/stuff/here?foo=bar"), "version": (1, 0), "headers": [ ("IF-MATCH", "bazinga!"), ("IF-MATCH", "large-sound") ], "body": b"" } gunicorn-20.1.0/tests/requests/valid/018.http000066400000000000000000000000721401157322000207350ustar00rootroot00000000000000GET /first HTTP/1.1\r\n \r\n GET /second HTTP/1.1\r\n \r\ngunicorn-20.1.0/tests/requests/valid/018.py000066400000000000000000000004021401157322000204030ustar00rootroot00000000000000req1 = { "method": "GET", "uri": uri("/first"), "version": (1, 1), "headers": [], "body": b"" } req2 = { "method": "GET", "uri": uri("/second"), "version": (1, 1), "headers": [], "body": b"" } request = [req1, req2] gunicorn-20.1.0/tests/requests/valid/019.http000066400000000000000000000000721401157322000207360ustar00rootroot00000000000000GET /first HTTP/1.0\r\n \r\n GET /second HTTP/1.1\r\n \r\ngunicorn-20.1.0/tests/requests/valid/019.py000066400000000000000000000001671401157322000204140ustar00rootroot00000000000000request = { "method": "GET", "uri": uri("/first"), "version": (1, 0), "headers": [], "body": b"" } gunicorn-20.1.0/tests/requests/valid/020.http000066400000000000000000000001211401157322000207210ustar00rootroot00000000000000GET /first HTTP/1.0\r\n Content-Length: 24\r\n \r\n GET /second HTTP/1.1\r\n \r\ngunicorn-20.1.0/tests/requests/valid/020.py000066400000000000000000000002531401157322000204000ustar00rootroot00000000000000request = { "method": "GET", "uri": uri("/first"), "version": (1, 0), "headers": [('CONTENT-LENGTH', '24')], "body": b"GET /second HTTP/1.1\r\n\r\n" } gunicorn-20.1.0/tests/requests/valid/021.http000066400000000000000000000001201401157322000207210ustar00rootroot00000000000000GET /first HTTP/1.1\r\n Connection: Close\r\n \r\n GET /second HTTP/1.1\r\n \r\ngunicorn-20.1.0/tests/requests/valid/021.py000066400000000000000000000002161401157322000204000ustar00rootroot00000000000000request = { "method": "GET", "uri": uri("/first"), "version": (1, 1), "headers": [("CONNECTION", "Close")], "body": b"" } gunicorn-20.1.0/tests/requests/valid/022.http000066400000000000000000000001251401157322000207270ustar00rootroot00000000000000GET /first HTTP/1.0\r\n Connection: Keep-Alive\r\n \r\n GET /second HTTP/1.1\r\n \r\ngunicorn-20.1.0/tests/requests/valid/022.py000066400000000000000000000004361401157322000204050ustar00rootroot00000000000000req1 = { "method": "GET", "uri": uri("/first"), "version": (1, 0), "headers": [("CONNECTION", "Keep-Alive")], "body": b"" } req2 = { "method": "GET", "uri": uri("/second"), "version": (1, 1), "headers": [], "body": b"" } request = [req1, req2] gunicorn-20.1.0/tests/requests/valid/023.http000066400000000000000000000002331401157322000207300ustar00rootroot00000000000000POST /two_chunks_mult_zero_end HTTP/1.1\r\n Transfer-Encoding: chunked\r\n \r\n 5\r\n hello\r\n 6\r\n world\r\n 000\r\n \r\n GET /second HTTP/1.1\r\n \r\ngunicorn-20.1.0/tests/requests/valid/023.py000066400000000000000000000005171401157322000204060ustar00rootroot00000000000000req1 = { "method": "POST", "uri": uri("/two_chunks_mult_zero_end"), "version": (1, 1), "headers": [ ("TRANSFER-ENCODING", "chunked") ], "body": b"hello world" } req2 = { "method": "GET", "uri": uri("/second"), "version": (1, 1), "headers": [], "body": b"" } request = [req1, req2] gunicorn-20.1.0/tests/requests/valid/024.http000066400000000000000000000400301401157322000207300ustar00rootroot00000000000000PUT /q=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 HTTP/1.0\r\n Someheader: 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\r\n \r\n gunicorn-20.1.0/tests/requests/valid/024.py000066400000000000000000000403771401157322000204170ustar00rootroot00000000000000from gunicorn.config import Config cfg = Config() cfg.set('limit_request_line', 0) cfg.set('limit_request_field_size', 0) request = { "method": "PUT", "uri": uri("/q=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"), "version": (1, 0), "headers": [ ("SOMEHEADER", "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") ], "body": '' } gunicorn-20.1.0/tests/requests/valid/025.http000066400000000000000000000005541401157322000207400ustar00rootroot00000000000000POST /chunked_cont_h_at_first HTTP/1.1\r\n Content-Length: -1\r\n Transfer-Encoding: chunked\r\n \r\n 5; some; parameters=stuff\r\n hello\r\n 6; blahblah; blah\r\n world\r\n 0\r\n \r\n PUT /chunked_cont_h_at_last HTTP/1.1\r\n Transfer-Encoding: chunked\r\n Content-Length: -1\r\n \r\n 5; some; parameters=stuff\r\n hello\r\n 6; blahblah; blah\r\n world\r\n 0\r\ngunicorn-20.1.0/tests/requests/valid/025.py000066400000000000000000000007341401157322000204110ustar00rootroot00000000000000req1 = { "method": "POST", "uri": uri("/chunked_cont_h_at_first"), "version": (1, 1), "headers": [ ("CONTENT-LENGTH", "-1"), ("TRANSFER-ENCODING", "chunked") ], "body": b"hello world" } req2 = { "method": "PUT", "uri": uri("/chunked_cont_h_at_last"), "version": (1, 1), "headers": [ ("TRANSFER-ENCODING", "chunked"), ("CONTENT-LENGTH", "-1"), ], "body": b"hello world" } request = [req1, req2] gunicorn-20.1.0/tests/requests/valid/026.http000066400000000000000000000200511401157322000207330ustar00rootroot00000000000000GET / HTTP/1.0\r\n Someheader: 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\r\n \r\n gunicorn-20.1.0/tests/requests/valid/026.py000066400000000000000000000204171401157322000204120ustar00rootroot00000000000000from gunicorn.config import Config cfg = Config() cfg.set('limit_request_line', 0) cfg.set('limit_request_field_size', 8210) request = { "method": "GET", "uri": uri("/"), "version": (1, 0), "headers": [ ("SOMEHEADER", "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") ], "body": '' } gunicorn-20.1.0/tests/requests/valid/027.http000066400000000000000000000000361401157322000207350ustar00rootroot00000000000000GET /à%20k HTTP/1.0\r\n \r\n gunicorn-20.1.0/tests/requests/valid/027.py000066400000000000000000000002021401157322000204010ustar00rootroot00000000000000request = { "method": "GET", "uri": uri("/\xc3\xa0%20k"), "version": (1, 0), "headers": [ ], "body": '' } gunicorn-20.1.0/tests/requests/valid/028.http000066400000000000000000000001041401157322000207320ustar00rootroot00000000000000GET /stuff/here?foo=bar HTTP/1.1\r\n Content-Length : 3\r\n \r\n xyzgunicorn-20.1.0/tests/requests/valid/028.py000066400000000000000000000004051401157322000204070ustar00rootroot00000000000000from gunicorn.config import Config cfg = Config() cfg.set("strip_header_spaces", True) request = { "method": "GET", "uri": uri("/stuff/here?foo=bar"), "version": (1, 1), "headers": [ ("CONTENT-LENGTH", "3"), ], "body": b"xyz" }gunicorn-20.1.0/tests/requests/valid/029.http000066400000000000000000000002011401157322000207310ustar00rootroot00000000000000GET /stuff/here?foo=bar HTTP/1.1\r\n Transfer-Encoding: chunked\r\n Transfer-Encoding: identity\r\n \r\n 5\r\n hello\r\n 000\r\n gunicorn-20.1.0/tests/requests/valid/029.py000066400000000000000000000004261401157322000204130ustar00rootroot00000000000000from gunicorn.config import Config cfg = Config() request = { "method": "GET", "uri": uri("/stuff/here?foo=bar"), "version": (1, 1), "headers": [ ('TRANSFER-ENCODING', 'chunked'), ('TRANSFER-ENCODING', 'identity') ], "body": b"hello" } gunicorn-20.1.0/tests/requests/valid/030.http000066400000000000000000000002011401157322000207210ustar00rootroot00000000000000GET /stuff/here?foo=bar HTTP/1.1\r\n Transfer-Encoding: identity\r\n Transfer-Encoding: chunked\r\n \r\n 5\r\n hello\r\n 000\r\n gunicorn-20.1.0/tests/requests/valid/030.py000066400000000000000000000004261401157322000204030ustar00rootroot00000000000000from gunicorn.config import Config cfg = Config() request = { "method": "GET", "uri": uri("/stuff/here?foo=bar"), "version": (1, 1), "headers": [ ('TRANSFER-ENCODING', 'identity'), ('TRANSFER-ENCODING', 'chunked') ], "body": b"hello" } gunicorn-20.1.0/tests/requests/valid/099.http000066400000000000000000000232651401157322000207570ustar00rootroot00000000000000POST /test-form HTTP/1.1\r\n Host: 0.0.0.0:5000\r\n User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0\r\n Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n Accept-Language: en-us,en;q=0.7,el;q=0.3\r\n Accept-Encoding: gzip, deflate\r\n Cookie: csrftoken=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX; sessionid=YYYYYYYYYYYYYYYYYYYYYYYYYYYY\r\n Connection: keep-alive\r\n Content-Type: multipart/form-data; boundary=---------------------------320761477111544\r\n Content-Length: 17914\r\n \r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="csrfmiddlewaretoken"\r\n \r\n XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="_save"\r\n \r\n Save\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="name"\r\n \r\n test.example.org\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="type"\r\n \r\n NATIVE\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="master"\r\n \r\n \r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_dynamiczone_domain-TOTAL_FORMS"\r\n \r\n 1\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_dynamiczone_domain-INITIAL_FORMS"\r\n \r\n 1\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_dynamiczone_domain-MAX_NUM_FORMS"\r\n \r\n 1\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_dynamiczone_domain-0-is_dynamic"\r\n \r\n on\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_dynamiczone_domain-0-id"\r\n \r\n 1\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_dynamiczone_domain-0-domain"\r\n \r\n 2\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_dynamiczone_domain-__prefix__-id"\r\n \r\n \r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_dynamiczone_domain-__prefix__-domain"\r\n \r\n 2\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-TOTAL_FORMS"\r\n \r\n 1\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-INITIAL_FORMS"\r\n \r\n 1\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-MAX_NUM_FORMS"\r\n \r\n 1\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-0-ttl"\r\n \r\n 3600\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-0-primary"\r\n \r\n ns.example.org\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-0-hostmaster"\r\n \r\n hostmaster.test.example.org\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-0-serial"\r\n \r\n 2013121701\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-0-refresh"\r\n \r\n 10800\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-0-retry"\r\n \r\n 3600\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-0-expire"\r\n \r\n 604800\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-0-default_ttl"\r\n \r\n 3600\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-0-id"\r\n \r\n 16\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-0-domain"\r\n \r\n 2\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-ttl"\r\n \r\n \r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-primary"\r\n \r\n \r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-hostmaster"\r\n \r\n \r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-serial"\r\n \r\n 1\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-refresh"\r\n \r\n 10800\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-retry"\r\n \r\n 3600\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-expire"\r\n \r\n 604800\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-default_ttl"\r\n \r\n 3600\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-id"\r\n \r\n \r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-domain"\r\n \r\n 2\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-2-TOTAL_FORMS"\r\n \r\n 0\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-2-INITIAL_FORMS"\r\n \r\n 0\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-2-MAX_NUM_FORMS"\r\n \r\n 1000\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-2-__prefix__-id"\r\n \r\n \r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-2-__prefix__-domain"\r\n \r\n 2\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-2-__prefix__-name"\r\n \r\n \r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-2-__prefix__-ttl"\r\n \r\n \r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-2-__prefix__-content"\r\n \r\n \r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-3-TOTAL_FORMS"\r\n \r\n 0\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-3-INITIAL_FORMS"\r\n \r\n 0\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-3-MAX_NUM_FORMS"\r\n \r\n 1000\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-3-__prefix__-id"\r\n \r\n \r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-3-__prefix__-domain"\r\n \r\n 2\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-3-__prefix__-name"\r\n \r\n \r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-3-__prefix__-ttl"\r\n \r\n \r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-3-__prefix__-prio"\r\n \r\n \r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-3-__prefix__-content"\r\n \r\n \r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-4-TOTAL_FORMS"\r\n \r\n 0\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-4-INITIAL_FORMS"\r\n \r\n 0\r\n ---------------------\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-5-TOTAL_FORMS"\r\n \r\n 0\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-5-INITIAL_FORMS"\r\n \r\n 0\r\n ---------------------\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-6-TOTAL_FORMS"\r\n \r\n 0\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-6-INITIAL_FORMS"\r\n \r\n 0\r\n ---------------------\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-7-TOTAL_FORMS"\r\n \r\n 0\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-7-INITIAL_FORMS"\r\n \r\n 0\r\n ---------------------\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-8-TOTAL_FORMS"\r\n \r\n 0\r\n -----------------------------320761477111544\r\n Content-Disposition: form-data; name="foobar_manager_record_domain-8-INITIAL_FORMS"\r\n \r\n 0\r\n ---------------------\r\ngunicorn-20.1.0/tests/requests/valid/099.py000066400000000000000000000216571401157322000204330ustar00rootroot00000000000000request = { "method": "POST", "uri": uri("/test-form"), "version": (1, 1), "headers": [ ("HOST", "0.0.0.0:5000"), ("USER-AGENT", "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0"), ("ACCEPT", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), ("ACCEPT-LANGUAGE", "en-us,en;q=0.7,el;q=0.3"), ("ACCEPT-ENCODING", "gzip, deflate"), ("COOKIE", "csrftoken=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX; sessionid=YYYYYYYYYYYYYYYYYYYYYYYYYYYY"), ("CONNECTION", "keep-alive"), ("CONTENT-TYPE", "multipart/form-data; boundary=---------------------------320761477111544"), ("CONTENT-LENGTH", "17914"), ], "body": b"""-----------------------------320761477111544 Content-Disposition: form-data; name="csrfmiddlewaretoken" XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -----------------------------320761477111544 Content-Disposition: form-data; name="_save" Save -----------------------------320761477111544 Content-Disposition: form-data; name="name" test.example.org -----------------------------320761477111544 Content-Disposition: form-data; name="type" NATIVE -----------------------------320761477111544 Content-Disposition: form-data; name="master" -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_dynamiczone_domain-TOTAL_FORMS" 1 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_dynamiczone_domain-INITIAL_FORMS" 1 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_dynamiczone_domain-MAX_NUM_FORMS" 1 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_dynamiczone_domain-0-is_dynamic" on -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_dynamiczone_domain-0-id" 1 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_dynamiczone_domain-0-domain" 2 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_dynamiczone_domain-__prefix__-id" -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_dynamiczone_domain-__prefix__-domain" 2 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-TOTAL_FORMS" 1 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-INITIAL_FORMS" 1 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-MAX_NUM_FORMS" 1 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-0-ttl" 3600 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-0-primary" ns.example.org -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-0-hostmaster" hostmaster.test.example.org -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-0-serial" 2013121701 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-0-refresh" 10800 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-0-retry" 3600 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-0-expire" 604800 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-0-default_ttl" 3600 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-0-id" 16 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-0-domain" 2 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-ttl" -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-primary" -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-hostmaster" -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-serial" 1 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-refresh" 10800 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-retry" 3600 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-expire" 604800 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-default_ttl" 3600 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-id" -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-__prefix__-domain" 2 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-2-TOTAL_FORMS" 0 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-2-INITIAL_FORMS" 0 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-2-MAX_NUM_FORMS" 1000 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-2-__prefix__-id" -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-2-__prefix__-domain" 2 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-2-__prefix__-name" -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-2-__prefix__-ttl" -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-2-__prefix__-content" -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-3-TOTAL_FORMS" 0 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-3-INITIAL_FORMS" 0 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-3-MAX_NUM_FORMS" 1000 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-3-__prefix__-id" -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-3-__prefix__-domain" 2 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-3-__prefix__-name" -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-3-__prefix__-ttl" -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-3-__prefix__-prio" -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-3-__prefix__-content" -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-4-TOTAL_FORMS" 0 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-4-INITIAL_FORMS" 0 --------------------- -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-5-TOTAL_FORMS" 0 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-5-INITIAL_FORMS" 0 --------------------- -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-6-TOTAL_FORMS" 0 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-6-INITIAL_FORMS" 0 --------------------- -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-7-TOTAL_FORMS" 0 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-7-INITIAL_FORMS" 0 --------------------- -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-8-TOTAL_FORMS" 0 -----------------------------320761477111544 Content-Disposition: form-data; name="foobar_manager_record_domain-8-INITIAL_FORMS" 0 --------------------- """.decode('utf-8').replace('\n', '\r\n').encode('utf-8'), } gunicorn-20.1.0/tests/requests/valid/100.http000066400000000000000000000000511401157322000207220ustar00rootroot00000000000000GET ///keeping_slashes HTTP/1.1\r\n \r\n gunicorn-20.1.0/tests/requests/valid/100.py000066400000000000000000000002031401157322000203720ustar00rootroot00000000000000request = { "method": "GET", "uri": uri("///keeping_slashes"), "version": (1, 1), "headers": [], "body": b"" } gunicorn-20.1.0/tests/requests/valid/pp_01.http000066400000000000000000000003071401157322000213450ustar00rootroot00000000000000PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\n GET /stuff/here?foo=bar HTTP/1.0\r\n Server: http://127.0.0.1:5984\r\n Content-Type: application/json\r\n Content-Length: 14\r\n \r\n {"nom": "nom"} gunicorn-20.1.0/tests/requests/valid/pp_01.py000066400000000000000000000005471401157322000210240ustar00rootroot00000000000000from gunicorn.config import Config cfg = Config() cfg.set('proxy_protocol', True) request = { "method": "GET", "uri": uri("/stuff/here?foo=bar"), "version": (1, 0), "headers": [ ("SERVER", "http://127.0.0.1:5984"), ("CONTENT-TYPE", "application/json"), ("CONTENT-LENGTH", "14") ], "body": b'{"nom": "nom"}' } gunicorn-20.1.0/tests/requests/valid/pp_02.http000066400000000000000000000005511401157322000213470ustar00rootroot00000000000000PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\n GET /stuff/here?foo=bar HTTP/1.1\r\n Server: http://127.0.0.1:5984\r\n Content-Type: application/json\r\n Content-Length: 14\r\n Connection: keep-alive\r\n \r\n {"nom": "nom"} POST /post_chunked_all_your_base HTTP/1.1\r\n Transfer-Encoding: chunked\r\n \r\n 1e\r\n all your base are belong to us\r\n 0\r\n \r\n gunicorn-20.1.0/tests/requests/valid/pp_02.py000066400000000000000000000011771401157322000210250ustar00rootroot00000000000000from gunicorn.config import Config cfg = Config() cfg.set("proxy_protocol", True) req1 = { "method": "GET", "uri": uri("/stuff/here?foo=bar"), "version": (1, 1), "headers": [ ("SERVER", "http://127.0.0.1:5984"), ("CONTENT-TYPE", "application/json"), ("CONTENT-LENGTH", "14"), ("CONNECTION", "keep-alive") ], "body": b'{"nom": "nom"}' } req2 = { "method": "POST", "uri": uri("/post_chunked_all_your_base"), "version": (1, 1), "headers": [ ("TRANSFER-ENCODING", "chunked"), ], "body": b"all your base are belong to us" } request = [req1, req2] gunicorn-20.1.0/tests/support.py000066400000000000000000000033211401157322000166400ustar00rootroot00000000000000import functools import sys import unittest import platform from wsgiref.validate import validator HOST = "127.0.0.1" def create_app(name="World", count=1): message = (('Hello, %s!\n' % name) * count).encode("utf8") length = str(len(message)) @validator def app(environ, start_response): """Simplest possible application object""" status = '200 OK' response_headers = [ ('Content-type', 'text/plain'), ('Content-Length', length), ] start_response(status, response_headers) return iter([message]) return app app = application = create_app() none_app = None def error_factory(): raise TypeError("inner") def requires_mac_ver(*min_version): """Decorator raising SkipTest if the OS is Mac OS X and the OS X version if less than min_version. For example, @requires_mac_ver(10, 5) raises SkipTest if the OS X version is lesser than 10.5. """ def decorator(func): @functools.wraps(func) def wrapper(*args, **kw): if sys.platform == 'darwin': version_txt = platform.mac_ver()[0] try: version = tuple(map(int, version_txt.split('.'))) except ValueError: pass else: if version < min_version: min_version_txt = '.'.join(map(str, min_version)) raise unittest.SkipTest( "Mac OS X %s or higher required, not %s" % (min_version_txt, version_txt)) return func(*args, **kw) wrapper.min_version = min_version return wrapper return decorator gunicorn-20.1.0/tests/t.py000066400000000000000000000031161401157322000153710ustar00rootroot00000000000000# -*- coding: utf-8 - # Copyright 2009 Paul J. Davis # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import io import os import tempfile dirname = os.path.dirname(__file__) from gunicorn.http.parser import RequestParser def data_source(fname): buf = io.BytesIO() with open(fname) as handle: for line in handle: line = line.rstrip("\n").replace("\\r\\n", "\r\n") buf.write(line.encode('latin1')) return buf class request(object): def __init__(self, name): self.fname = os.path.join(dirname, "requests", name) def __call__(self, func): def run(): src = data_source(self.fname) func(src, RequestParser(src, None, None)) run.func_name = func.func_name return run class FakeSocket(object): def __init__(self, data): self.tmp = tempfile.TemporaryFile() if data: self.tmp.write(data.getvalue()) self.tmp.flush() self.tmp.seek(0) def fileno(self): return self.tmp.fileno() def len(self): return self.tmp.len def recv(self, length=None): return self.tmp.read(length) def recv_into(self, buf, length): tmp_buffer = self.tmp.read(length) v = len(tmp_buffer) for i, c in enumerate(tmp_buffer): buf[i] = c return v def send(self, data): self.tmp.write(data) self.tmp.flush() def seek(self, offset, whence=0): self.tmp.seek(offset, whence) gunicorn-20.1.0/tests/test_arbiter.py000066400000000000000000000137411401157322000176220ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import os import unittest.mock as mock import gunicorn.app.base import gunicorn.arbiter from gunicorn.config import ReusePort class DummyApplication(gunicorn.app.base.BaseApplication): """ Dummy application that has a default configuration. """ def init(self, parser, opts, args): """No-op""" def load(self): """No-op""" def load_config(self): """No-op""" @mock.patch('gunicorn.sock.close_sockets') def test_arbiter_stop_closes_listeners(close_sockets): arbiter = gunicorn.arbiter.Arbiter(DummyApplication()) listener1 = mock.Mock() listener2 = mock.Mock() listeners = [listener1, listener2] arbiter.LISTENERS = listeners arbiter.stop() close_sockets.assert_called_with(listeners, True) @mock.patch('gunicorn.sock.close_sockets') def test_arbiter_stop_child_does_not_unlink_listeners(close_sockets): arbiter = gunicorn.arbiter.Arbiter(DummyApplication()) arbiter.reexec_pid = os.getpid() arbiter.stop() close_sockets.assert_called_with([], False) @mock.patch('gunicorn.sock.close_sockets') def test_arbiter_stop_parent_does_not_unlink_listeners(close_sockets): arbiter = gunicorn.arbiter.Arbiter(DummyApplication()) arbiter.master_pid = os.getppid() arbiter.stop() close_sockets.assert_called_with([], False) @mock.patch('gunicorn.sock.close_sockets') def test_arbiter_stop_does_not_unlink_systemd_listeners(close_sockets): arbiter = gunicorn.arbiter.Arbiter(DummyApplication()) arbiter.systemd = True arbiter.stop() close_sockets.assert_called_with([], False) @mock.patch('gunicorn.sock.close_sockets') def test_arbiter_stop_does_not_unlink_when_using_reuse_port(close_sockets): arbiter = gunicorn.arbiter.Arbiter(DummyApplication()) arbiter.cfg.settings['reuse_port'] = ReusePort() arbiter.cfg.settings['reuse_port'].set(True) arbiter.stop() close_sockets.assert_called_with([], False) @mock.patch('os.getpid') @mock.patch('os.fork') @mock.patch('os.execvpe') def test_arbiter_reexec_passing_systemd_sockets(execvpe, fork, getpid): arbiter = gunicorn.arbiter.Arbiter(DummyApplication()) arbiter.LISTENERS = [mock.Mock(), mock.Mock()] arbiter.systemd = True fork.return_value = 0 getpid.side_effect = [2, 3] arbiter.reexec() environ = execvpe.call_args[0][2] assert environ['GUNICORN_PID'] == '2' assert environ['LISTEN_FDS'] == '2' assert environ['LISTEN_PID'] == '3' @mock.patch('os.getpid') @mock.patch('os.fork') @mock.patch('os.execvpe') def test_arbiter_reexec_passing_gunicorn_sockets(execvpe, fork, getpid): arbiter = gunicorn.arbiter.Arbiter(DummyApplication()) listener1 = mock.Mock() listener2 = mock.Mock() listener1.fileno.return_value = 4 listener2.fileno.return_value = 5 arbiter.LISTENERS = [listener1, listener2] fork.return_value = 0 getpid.side_effect = [2, 3] arbiter.reexec() environ = execvpe.call_args[0][2] assert environ['GUNICORN_FD'] == '4,5' assert environ['GUNICORN_PID'] == '2' @mock.patch('os.fork') def test_arbiter_reexec_limit_parent(fork): arbiter = gunicorn.arbiter.Arbiter(DummyApplication()) arbiter.reexec_pid = ~os.getpid() arbiter.reexec() assert fork.called is False, "should not fork when there is already a child" @mock.patch('os.fork') def test_arbiter_reexec_limit_child(fork): arbiter = gunicorn.arbiter.Arbiter(DummyApplication()) arbiter.master_pid = ~os.getpid() arbiter.reexec() assert fork.called is False, "should not fork when arbiter is a child" @mock.patch('os.fork') def test_arbiter_calls_worker_exit(mock_os_fork): mock_os_fork.return_value = 0 arbiter = gunicorn.arbiter.Arbiter(DummyApplication()) arbiter.cfg.settings['worker_exit'] = mock.Mock() arbiter.pid = None mock_worker = mock.Mock() arbiter.worker_class = mock.Mock(return_value=mock_worker) try: arbiter.spawn_worker() except SystemExit: pass arbiter.cfg.worker_exit.assert_called_with(arbiter, mock_worker) @mock.patch('os.waitpid') def test_arbiter_reap_workers(mock_os_waitpid): mock_os_waitpid.side_effect = [(42, 0), (0, 0)] arbiter = gunicorn.arbiter.Arbiter(DummyApplication()) arbiter.cfg.settings['child_exit'] = mock.Mock() mock_worker = mock.Mock() arbiter.WORKERS = {42: mock_worker} arbiter.reap_workers() mock_worker.tmp.close.assert_called_with() arbiter.cfg.child_exit.assert_called_with(arbiter, mock_worker) class PreloadedAppWithEnvSettings(DummyApplication): """ Simple application that makes use of the 'preload' feature to start the application before spawning worker processes and sets environmental variable configuration settings. """ def load_config(self): """Set the 'preload_app' and 'raw_env' settings in order to verify their interaction below. """ self.cfg.set('raw_env', [ 'SOME_PATH=/tmp/something', 'OTHER_PATH=/tmp/something/else']) self.cfg.set('preload_app', True) def wsgi(self): """Assert that the expected environmental variables are set when the main entry point of this application is called as part of a 'preloaded' application. """ verify_env_vars() return super().wsgi() def verify_env_vars(): assert os.getenv('SOME_PATH') == '/tmp/something' assert os.getenv('OTHER_PATH') == '/tmp/something/else' def test_env_vars_available_during_preload(): """Ensure that configured environmental variables are set during the initial set up of the application (called from the .setup() method of the Arbiter) such that they are available during the initial loading of the WSGI application. """ # Note that we aren't making any assertions here, they are made in the # dummy application object being loaded here instead. gunicorn.arbiter.Arbiter(PreloadedAppWithEnvSettings()) gunicorn-20.1.0/tests/test_config.py000066400000000000000000000342221401157322000174340ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import os import re import sys import pytest from gunicorn import config from gunicorn.app.base import Application from gunicorn.app.wsgiapp import WSGIApplication from gunicorn.errors import ConfigError from gunicorn.workers.sync import SyncWorker from gunicorn import glogging from gunicorn.instrument import statsd dirname = os.path.dirname(__file__) def cfg_module(): return 'config.test_cfg' def alt_cfg_module(): return 'config.test_cfg_alt' def cfg_file(): return os.path.join(dirname, "config", "test_cfg.py") def alt_cfg_file(): return os.path.join(dirname, "config", "test_cfg_alt.py") def cfg_file_with_wsgi_app(): return os.path.join(dirname, "config", "test_cfg_with_wsgi_app.py") def paster_ini(): return os.path.join(dirname, "..", "examples", "frameworks", "pylonstest", "nose.ini") class AltArgs(object): def __init__(self, args=None): self.args = args or [] self.orig = sys.argv def __enter__(self): sys.argv = self.args def __exit__(self, exc_type, exc_inst, traceback): sys.argv = self.orig class NoConfigApp(Application): def __init__(self): super().__init__("no_usage", prog="gunicorn_test") def init(self, parser, opts, args): pass def load(self): pass class WSGIApp(WSGIApplication): def __init__(self): super().__init__("no_usage", prog="gunicorn_test") def load(self): pass def test_defaults(): c = config.Config() for s in config.KNOWN_SETTINGS: assert c.settings[s.name].validator(s.default) == c.settings[s.name].get() def test_property_access(): c = config.Config() for s in config.KNOWN_SETTINGS: getattr(c, s.name) # Class was loaded assert c.worker_class == SyncWorker # logger class was loaded assert c.logger_class == glogging.Logger # Workers defaults to 1 assert c.workers == 1 c.set("workers", 3) assert c.workers == 3 # Address is parsed assert c.address == [("127.0.0.1", 8000)] # User and group defaults assert os.geteuid() == c.uid assert os.getegid() == c.gid # Proc name assert "gunicorn" == c.proc_name # Not a config property pytest.raises(AttributeError, getattr, c, "foo") # Force to be not an error class Baz(object): def get(self): return 3.14 c.settings["foo"] = Baz() assert c.foo == 3.14 # Attempt to set a cfg not via c.set pytest.raises(AttributeError, setattr, c, "proc_name", "baz") # No setting for name pytest.raises(AttributeError, c.set, "baz", "bar") def test_bool_validation(): c = config.Config() assert c.preload_app is False c.set("preload_app", True) assert c.preload_app is True c.set("preload_app", "true") assert c.preload_app is True c.set("preload_app", "false") assert c.preload_app is False pytest.raises(ValueError, c.set, "preload_app", "zilch") pytest.raises(TypeError, c.set, "preload_app", 4) def test_pos_int_validation(): c = config.Config() assert c.workers == 1 c.set("workers", 4) assert c.workers == 4 c.set("workers", "5") assert c.workers == 5 c.set("workers", "0xFF") assert c.workers == 255 c.set("workers", True) assert c.workers == 1 # Yes. That's right... pytest.raises(ValueError, c.set, "workers", -21) pytest.raises(TypeError, c.set, "workers", c) def test_str_validation(): c = config.Config() assert c.proc_name == "gunicorn" c.set("proc_name", " foo ") assert c.proc_name == "foo" pytest.raises(TypeError, c.set, "proc_name", 2) def test_str_to_list_validation(): c = config.Config() assert c.forwarded_allow_ips == ["127.0.0.1"] c.set("forwarded_allow_ips", "127.0.0.1,192.168.0.1") assert c.forwarded_allow_ips == ["127.0.0.1", "192.168.0.1"] c.set("forwarded_allow_ips", "") assert c.forwarded_allow_ips == [] c.set("forwarded_allow_ips", None) assert c.forwarded_allow_ips == [] pytest.raises(TypeError, c.set, "forwarded_allow_ips", 1) def test_callable_validation(): c = config.Config() def func(a, b): pass c.set("pre_fork", func) assert c.pre_fork == func pytest.raises(TypeError, c.set, "pre_fork", 1) pytest.raises(TypeError, c.set, "pre_fork", lambda x: True) def test_reload_engine_validation(): c = config.Config() assert c.reload_engine == "auto" c.set('reload_engine', 'poll') assert c.reload_engine == 'poll' pytest.raises(ConfigError, c.set, "reload_engine", "invalid") def test_callable_validation_for_string(): from os.path import isdir as testfunc assert config.validate_callable(-1)("os.path.isdir") == testfunc # invalid values tests pytest.raises( TypeError, config.validate_callable(-1), "" ) pytest.raises( TypeError, config.validate_callable(-1), "os.path.not_found_func" ) pytest.raises( TypeError, config.validate_callable(-1), "notfoundmodule.func" ) def test_cmd_line(): with AltArgs(["prog_name", "-b", "blargh"]): app = NoConfigApp() assert app.cfg.bind == ["blargh"] with AltArgs(["prog_name", "-w", "3"]): app = NoConfigApp() assert app.cfg.workers == 3 with AltArgs(["prog_name", "--preload"]): app = NoConfigApp() assert app.cfg.preload_app def test_cmd_line_invalid_setting(capsys): with AltArgs(["prog_name", "-q", "bar"]): with pytest.raises(SystemExit): NoConfigApp() _, err = capsys.readouterr() assert "error: unrecognized arguments: -q" in err def test_app_config(): with AltArgs(): app = NoConfigApp() for s in config.KNOWN_SETTINGS: assert app.cfg.settings[s.name].validator(s.default) == app.cfg.settings[s.name].get() def test_load_config(): with AltArgs(["prog_name", "-c", cfg_file()]): app = NoConfigApp() assert app.cfg.bind == ["unix:/tmp/bar/baz"] assert app.cfg.workers == 3 assert app.cfg.proc_name == "fooey" def test_load_config_explicit_file(): with AltArgs(["prog_name", "-c", "file:%s" % cfg_file()]): app = NoConfigApp() assert app.cfg.bind == ["unix:/tmp/bar/baz"] assert app.cfg.workers == 3 assert app.cfg.proc_name == "fooey" def test_load_config_module(): with AltArgs(["prog_name", "-c", "python:%s" % cfg_module()]): app = NoConfigApp() assert app.cfg.bind == ["unix:/tmp/bar/baz"] assert app.cfg.workers == 3 assert app.cfg.proc_name == "fooey" def test_cli_overrides_config(): with AltArgs(["prog_name", "-c", cfg_file(), "-b", "blarney"]): app = NoConfigApp() assert app.cfg.bind == ["blarney"] assert app.cfg.proc_name == "fooey" def test_cli_overrides_config_module(): with AltArgs(["prog_name", "-c", "python:%s" % cfg_module(), "-b", "blarney"]): app = NoConfigApp() assert app.cfg.bind == ["blarney"] assert app.cfg.proc_name == "fooey" @pytest.fixture def create_config_file(request): default_config = os.path.join(os.path.abspath(os.getcwd()), 'gunicorn.conf.py') with open(default_config, 'w+') as default: default.write("bind='0.0.0.0:9090'") def fin(): os.unlink(default_config) request.addfinalizer(fin) return default def test_default_config_file(create_config_file): assert config.get_default_config_file() == create_config_file.name with AltArgs(["prog_name"]): app = NoConfigApp() assert app.cfg.bind == ["0.0.0.0:9090"] def test_post_request(): c = config.Config() def post_request_4(worker, req, environ, resp): return 4 def post_request_3(worker, req, environ): return 3 def post_request_2(worker, req): return 2 c.set("post_request", post_request_4) assert c.post_request(1, 2, 3, 4) == 4 c.set("post_request", post_request_3) assert c.post_request(1, 2, 3, 4) == 3 c.set("post_request", post_request_2) assert c.post_request(1, 2, 3, 4) == 2 def test_nworkers_changed(): c = config.Config() def nworkers_changed_3(server, new_value, old_value): return 3 c.set("nworkers_changed", nworkers_changed_3) assert c.nworkers_changed(1, 2, 3) == 3 def test_statsd_changes_logger(): c = config.Config() assert c.logger_class == glogging.Logger c.set('statsd_host', 'localhost:12345') assert c.logger_class == statsd.Statsd class MyLogger(glogging.Logger): # dummy custom logger class for testing pass def test_always_use_configured_logger(): c = config.Config() c.set('logger_class', __name__ + '.MyLogger') assert c.logger_class == MyLogger c.set('statsd_host', 'localhost:12345') # still uses custom logger over statsd assert c.logger_class == MyLogger def test_load_enviroment_variables_config(monkeypatch): monkeypatch.setenv("GUNICORN_CMD_ARGS", "--workers=4") with AltArgs(): app = NoConfigApp() assert app.cfg.workers == 4 def test_config_file_environment_variable(monkeypatch): monkeypatch.setenv("GUNICORN_CMD_ARGS", "--config=" + alt_cfg_file()) with AltArgs(): app = NoConfigApp() assert app.cfg.proc_name == "not-fooey" assert app.cfg.config == alt_cfg_file() with AltArgs(["prog_name", "--config", cfg_file()]): app = NoConfigApp() assert app.cfg.proc_name == "fooey" assert app.cfg.config == cfg_file() def test_invalid_enviroment_variables_config(monkeypatch, capsys): monkeypatch.setenv("GUNICORN_CMD_ARGS", "--foo=bar") with AltArgs(): with pytest.raises(SystemExit): NoConfigApp() _, err = capsys.readouterr() assert "error: unrecognized arguments: --foo" in err def test_cli_overrides_enviroment_variables_module(monkeypatch): monkeypatch.setenv("GUNICORN_CMD_ARGS", "--workers=4") with AltArgs(["prog_name", "-c", cfg_file(), "--workers", "3"]): app = NoConfigApp() assert app.cfg.workers == 3 @pytest.mark.parametrize("options, expected", [ (["app:app"], 'app:app'), (["-c", cfg_file(), "app:app"], 'app:app'), (["-c", cfg_file_with_wsgi_app(), "app:app"], 'app:app'), (["-c", cfg_file_with_wsgi_app()], 'app1:app1'), ]) def test_wsgi_app_config(options, expected): cmdline = ["prog_name"] cmdline.extend(options) with AltArgs(cmdline): app = WSGIApp() assert app.app_uri == expected @pytest.mark.parametrize("options", [ ([]), (["-c", cfg_file()]), ]) def test_non_wsgi_app(options, capsys): cmdline = ["prog_name"] cmdline.extend(options) with AltArgs(cmdline): with pytest.raises(SystemExit): WSGIApp() _, err = capsys.readouterr() assert "Error: No application module specified." in err @pytest.mark.parametrize("options, expected", [ (["myapp:app"], False), (["--reload", "myapp:app"], True), (["--reload", "--", "myapp:app"], True), (["--reload", "-w 2", "myapp:app"], True), ]) def test_reload(options, expected): cmdline = ["prog_name"] cmdline.extend(options) with AltArgs(cmdline): app = NoConfigApp() assert app.cfg.reload == expected @pytest.mark.parametrize("options, expected", [ (["--umask", "0", "myapp:app"], 0), (["--umask", "0o0", "myapp:app"], 0), (["--umask", "0x0", "myapp:app"], 0), (["--umask", "0xFF", "myapp:app"], 255), (["--umask", "0022", "myapp:app"], 18), ]) def test_umask_config(options, expected): cmdline = ["prog_name"] cmdline.extend(options) with AltArgs(cmdline): app = NoConfigApp() assert app.cfg.umask == expected @pytest.mark.parametrize("options, expected", [ (["--ssl-version", "SSLv23"], 2), (["--ssl-version", "TLSv1"], 3), (["--ssl-version", "2"], 2), (["--ssl-version", "3"], 3), ]) def test_ssl_version_named_constants_python3(options, expected): _test_ssl_version(options, expected) @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6+") @pytest.mark.parametrize("options, expected", [ (["--ssl-version", "TLS"], 2), (["--ssl-version", "TLSv1_1"], 4), (["--ssl-version", "TLSv1_2"], 5), (["--ssl-version", "TLS_SERVER"], 17), ]) def test_ssl_version_named_constants_python36(options, expected): _test_ssl_version(options, expected) @pytest.mark.parametrize("ssl_version", [ "FOO", "-99", "99991234" ]) def test_ssl_version_bad(ssl_version): c = config.Config() with pytest.raises(ValueError) as exc: c.set("ssl_version", ssl_version) assert 'Valid options' in str(exc.value) assert "TLSv" in str(exc.value) def _test_ssl_version(options, expected): cmdline = ["prog_name"] cmdline.extend(options) with AltArgs(cmdline): app = NoConfigApp() assert app.cfg.ssl_version == expected def test_bind_fd(): with AltArgs(["prog_name", "-b", "fd://42"]): app = NoConfigApp() assert app.cfg.bind == ["fd://42"] def test_repr(): c = config.Config() c.set("workers", 5) assert "with value 5" in repr(c.settings['workers']) def test_str(): c = config.Config() o = str(c) # match the first few lines, some different types, but don't go OTT # to avoid needless test fails with changes OUTPUT_MATCH = { 'access_log_format': '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"', 'accesslog': 'None', 'backlog': '2048', 'bind': "['127.0.0.1:8000']", 'capture_output': 'False', 'child_exit': '', } for i, line in enumerate(o.splitlines()): m = re.match(r'^(\w+)\s+= ', line) assert m, "Line {} didn't match expected format: {!r}".format(i, line) key = m.group(1) try: s = OUTPUT_MATCH.pop(key) except KeyError: continue line_re = r'^{}\s+= {}$'.format(key, re.escape(s)) assert re.match(line_re, line), '{!r} != {!r}'.format(line_re, line) if not OUTPUT_MATCH: break else: assert False, 'missing expected setting lines? {}'.format( OUTPUT_MATCH.keys() ) gunicorn-20.1.0/tests/test_http.py000066400000000000000000000153101401157322000171430ustar00rootroot00000000000000# -*- encoding: utf-8 -*- import io import t import pytest import unittest.mock as mock from gunicorn import util from gunicorn.http.body import Body, LengthReader, EOFReader from gunicorn.http.wsgi import Response from gunicorn.http.unreader import Unreader, IterUnreader, SocketUnreader from gunicorn.http.errors import InvalidHeader, InvalidHeaderName def assert_readline(payload, size, expected): body = Body(io.BytesIO(payload)) assert body.readline(size) == expected def test_readline_empty_body(): assert_readline(b"", None, b"") assert_readline(b"", 1, b"") def test_readline_zero_size(): assert_readline(b"abc", 0, b"") assert_readline(b"\n", 0, b"") def test_readline_new_line_before_size(): body = Body(io.BytesIO(b"abc\ndef")) assert body.readline(4) == b"abc\n" assert body.readline() == b"def" def test_readline_new_line_after_size(): body = Body(io.BytesIO(b"abc\ndef")) assert body.readline(2) == b"ab" assert body.readline() == b"c\n" def test_readline_no_new_line(): body = Body(io.BytesIO(b"abcdef")) assert body.readline() == b"abcdef" body = Body(io.BytesIO(b"abcdef")) assert body.readline(2) == b"ab" assert body.readline(2) == b"cd" assert body.readline(2) == b"ef" def test_readline_buffer_loaded(): reader = io.BytesIO(b"abc\ndef") body = Body(reader) body.read(1) # load internal buffer reader.write(b"g\nhi") reader.seek(7) assert body.readline() == b"bc\n" assert body.readline() == b"defg\n" assert body.readline() == b"hi" def test_readline_buffer_loaded_with_size(): body = Body(io.BytesIO(b"abc\ndef")) body.read(1) # load internal buffer assert body.readline(2) == b"bc" assert body.readline(2) == b"\n" assert body.readline(2) == b"de" assert body.readline(2) == b"f" def test_http_header_encoding(): """ tests whether http response headers are USASCII encoded """ mocked_socket = mock.MagicMock() mocked_socket.sendall = mock.MagicMock() mocked_request = mock.MagicMock() response = Response(mocked_request, mocked_socket, None) # set umlaut header value - latin-1 is OK response.headers.append(('foo', 'häder')) response.send_headers() # set a-breve header value - unicode, non-latin-1 fails response = Response(mocked_request, mocked_socket, None) response.headers.append(('apple', 'măr')) with pytest.raises(UnicodeEncodeError): response.send_headers() # build our own header_str to compare against tosend = response.default_headers() tosend.extend(["%s: %s\r\n" % (k, v) for k, v in response.headers]) header_str = "%s\r\n" % "".join(tosend) with pytest.raises(UnicodeEncodeError): mocked_socket.sendall(util.to_bytestring(header_str, "ascii")) def test_http_invalid_response_header(): """ tests whether http response headers are contains control chars """ mocked_socket = mock.MagicMock() mocked_socket.sendall = mock.MagicMock() mocked_request = mock.MagicMock() response = Response(mocked_request, mocked_socket, None) with pytest.raises(InvalidHeader): response.start_response("200 OK", [('foo', 'essai\r\n')]) response = Response(mocked_request, mocked_socket, None) with pytest.raises(InvalidHeaderName): response.start_response("200 OK", [('foo\r\n', 'essai')]) def test_unreader_read_when_size_is_none(): unreader = Unreader() unreader.chunk = mock.MagicMock(side_effect=[b'qwerty', b'123456', b'']) assert unreader.read(size=None) == b'qwerty' assert unreader.read(size=None) == b'123456' assert unreader.read(size=None) == b'' def test_unreader_unread(): unreader = Unreader() unreader.unread(b'hi there') assert b'hi there' in unreader.read() def test_unreader_read_zero_size(): unreader = Unreader() unreader.chunk = mock.MagicMock(side_effect=[b'qwerty', b'asdfgh']) assert unreader.read(size=0) == b'' def test_unreader_read_with_nonzero_size(): unreader = Unreader() unreader.chunk = mock.MagicMock(side_effect=[ b'qwerty', b'asdfgh', b'zxcvbn', b'123456', b'', b'' ]) assert unreader.read(size=5) == b'qwert' assert unreader.read(size=5) == b'yasdf' assert unreader.read(size=5) == b'ghzxc' assert unreader.read(size=5) == b'vbn12' assert unreader.read(size=5) == b'3456' assert unreader.read(size=5) == b'' def test_unreader_raises_excpetion_on_invalid_size(): unreader = Unreader() with pytest.raises(TypeError): unreader.read(size='foobar') with pytest.raises(TypeError): unreader.read(size=3.14) with pytest.raises(TypeError): unreader.read(size=[]) def test_iter_unreader_chunk(): iter_unreader = IterUnreader((b'ab', b'cd', b'ef')) assert iter_unreader.chunk() == b'ab' assert iter_unreader.chunk() == b'cd' assert iter_unreader.chunk() == b'ef' assert iter_unreader.chunk() == b'' assert iter_unreader.chunk() == b'' def test_socket_unreader_chunk(): fake_sock = t.FakeSocket(io.BytesIO(b'Lorem ipsum dolor')) sock_unreader = SocketUnreader(fake_sock, max_chunk=5) assert sock_unreader.chunk() == b'Lorem' assert sock_unreader.chunk() == b' ipsu' assert sock_unreader.chunk() == b'm dol' assert sock_unreader.chunk() == b'or' assert sock_unreader.chunk() == b'' def test_length_reader_read(): unreader = IterUnreader((b'Lorem', b'ipsum', b'dolor', b'sit', b'amet')) reader = LengthReader(unreader, 13) assert reader.read(0) == b'' assert reader.read(5) == b'Lorem' assert reader.read(6) == b'ipsumd' assert reader.read(4) == b'ol' assert reader.read(100) == b'' reader = LengthReader(unreader, 10) assert reader.read(0) == b'' assert reader.read(5) == b'orsit' assert reader.read(5) == b'amet' assert reader.read(100) == b'' def test_length_reader_read_invalid_size(): reader = LengthReader(None, 5) with pytest.raises(TypeError): reader.read('100') with pytest.raises(TypeError): reader.read([100]) with pytest.raises(ValueError): reader.read(-100) def test_eof_reader_read(): unreader = IterUnreader((b'Lorem', b'ipsum', b'dolor', b'sit', b'amet')) reader = EOFReader(unreader) assert reader.read(0) == b'' assert reader.read(5) == b'Lorem' assert reader.read(5) == b'ipsum' assert reader.read(3) == b'dol' assert reader.read(3) == b'ors' assert reader.read(100) == b'itamet' assert reader.read(100) == b'' def test_eof_reader_read_invalid_size(): reader = EOFReader(None) with pytest.raises(TypeError): reader.read('100') with pytest.raises(TypeError): reader.read([100]) with pytest.raises(ValueError): reader.read(-100) gunicorn-20.1.0/tests/test_invalid_requests.py000066400000000000000000000011251401157322000215440ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import glob import os import pytest import treq dirname = os.path.dirname(__file__) reqdir = os.path.join(dirname, "requests", "invalid") httpfiles = glob.glob(os.path.join(reqdir, "*.http")) @pytest.mark.parametrize("fname", httpfiles) def test_http_parser(fname): env = treq.load_py(os.path.splitext(fname)[0] + ".py") expect = env["request"] cfg = env["cfg"] req = treq.badrequest(fname) with pytest.raises(expect): req.check(cfg) gunicorn-20.1.0/tests/test_logger.py000066400000000000000000000061041401157322000174440ustar00rootroot00000000000000import datetime from types import SimpleNamespace import pytest from gunicorn.config import Config from gunicorn.glogging import Logger def test_atoms_defaults(): response = SimpleNamespace( status='200', response_length=1024, headers=(('Content-Type', 'application/json'),), sent=1024, ) request = SimpleNamespace(headers=(('Accept', 'application/json'),)) environ = { 'REQUEST_METHOD': 'GET', 'RAW_URI': '/my/path?foo=bar', 'PATH_INFO': '/my/path', 'QUERY_STRING': 'foo=bar', 'SERVER_PROTOCOL': 'HTTP/1.1', } logger = Logger(Config()) atoms = logger.atoms(response, request, environ, datetime.timedelta(seconds=1)) assert isinstance(atoms, dict) assert atoms['r'] == 'GET /my/path?foo=bar HTTP/1.1' assert atoms['m'] == 'GET' assert atoms['U'] == '/my/path' assert atoms['q'] == 'foo=bar' assert atoms['H'] == 'HTTP/1.1' assert atoms['b'] == '1024' assert atoms['B'] == 1024 assert atoms['{accept}i'] == 'application/json' assert atoms['{content-type}o'] == 'application/json' def test_atoms_zero_bytes(): response = SimpleNamespace( status='200', response_length=0, headers=(('Content-Type', 'application/json'),), sent=0, ) request = SimpleNamespace(headers=(('Accept', 'application/json'),)) environ = { 'REQUEST_METHOD': 'GET', 'RAW_URI': '/my/path?foo=bar', 'PATH_INFO': '/my/path', 'QUERY_STRING': 'foo=bar', 'SERVER_PROTOCOL': 'HTTP/1.1', } logger = Logger(Config()) atoms = logger.atoms(response, request, environ, datetime.timedelta(seconds=1)) assert atoms['b'] == '0' assert atoms['B'] == 0 @pytest.mark.parametrize('auth', [ # auth type is case in-sensitive 'Basic YnJrMHY6', 'basic YnJrMHY6', 'BASIC YnJrMHY6', ]) def test_get_username_from_basic_auth_header(auth): request = SimpleNamespace(headers=()) response = SimpleNamespace( status='200', response_length=1024, sent=1024, headers=(('Content-Type', 'text/plain'),), ) environ = { 'REQUEST_METHOD': 'GET', 'RAW_URI': '/my/path?foo=bar', 'PATH_INFO': '/my/path', 'QUERY_STRING': 'foo=bar', 'SERVER_PROTOCOL': 'HTTP/1.1', 'HTTP_AUTHORIZATION': auth, } logger = Logger(Config()) atoms = logger.atoms(response, request, environ, datetime.timedelta(seconds=1)) assert atoms['u'] == 'brk0v' def test_get_username_handles_malformed_basic_auth_header(): """Should catch a malformed auth header""" request = SimpleNamespace(headers=()) response = SimpleNamespace( status='200', response_length=1024, sent=1024, headers=(('Content-Type', 'text/plain'),), ) environ = { 'REQUEST_METHOD': 'GET', 'RAW_URI': '/my/path?foo=bar', 'PATH_INFO': '/my/path', 'QUERY_STRING': 'foo=bar', 'SERVER_PROTOCOL': 'HTTP/1.1', 'HTTP_AUTHORIZATION': 'Basic ixsTtkKzIpVTncfQjbBcnoRNoDfbnaXG', } logger = Logger(Config()) atoms = logger.atoms(response, request, environ, datetime.timedelta(seconds=1)) assert atoms['u'] == '-' gunicorn-20.1.0/tests/test_pidfile.py000066400000000000000000000027651401157322000176120ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import errno import unittest.mock as mock import gunicorn.pidfile def builtin(name): return 'builtins.{}'.format(name) @mock.patch(builtin('open'), new_callable=mock.mock_open) def test_validate_no_file(_open): pidfile = gunicorn.pidfile.Pidfile('test.pid') _open.side_effect = IOError(errno.ENOENT) assert pidfile.validate() is None @mock.patch(builtin('open'), new_callable=mock.mock_open, read_data='1') @mock.patch('os.kill') def test_validate_file_pid_exists(kill, _open): pidfile = gunicorn.pidfile.Pidfile('test.pid') assert pidfile.validate() == 1 assert kill.called @mock.patch(builtin('open'), new_callable=mock.mock_open, read_data='a') def test_validate_file_pid_malformed(_open): pidfile = gunicorn.pidfile.Pidfile('test.pid') assert pidfile.validate() is None @mock.patch(builtin('open'), new_callable=mock.mock_open, read_data='1') @mock.patch('os.kill') def test_validate_file_pid_exists_kill_exception(kill, _open): pidfile = gunicorn.pidfile.Pidfile('test.pid') kill.side_effect = OSError(errno.EPERM) assert pidfile.validate() == 1 @mock.patch(builtin('open'), new_callable=mock.mock_open, read_data='1') @mock.patch('os.kill') def test_validate_file_pid_does_not_exist(kill, _open): pidfile = gunicorn.pidfile.Pidfile('test.pid') kill.side_effect = OSError(errno.ESRCH) assert pidfile.validate() is None gunicorn-20.1.0/tests/test_reload.py000066400000000000000000000034731401157322000174410ustar00rootroot00000000000000import unittest.mock as mock from gunicorn.app.base import Application from gunicorn.workers.base import Worker from gunicorn.reloader import reloader_engines class ReloadApp(Application): def __init__(self): super().__init__("no usage", prog="gunicorn_test") def do_load_config(self): self.load_default_config() self.cfg.set('reload', True) self.cfg.set('reload_engine', 'poll') class SyntaxErrorApp(ReloadApp): def wsgi(self): error = SyntaxError('invalid syntax') error.filename = 'syntax_error_filename' raise error class MyWorker(Worker): def run(self): pass def test_reload_on_syntax_error(): """ Test that reloading works if the application has a syntax error. """ reloader = mock.Mock() reloader_engines['poll'] = lambda *args, **kw: reloader app = SyntaxErrorApp() cfg = app.cfg log = mock.Mock() worker = MyWorker(age=0, ppid=0, sockets=[], app=app, timeout=0, cfg=cfg, log=log) worker.init_process() reloader.start.assert_called_with() reloader.add_extra_file.assert_called_with('syntax_error_filename') def test_start_reloader_after_load_wsgi(): """ Check that the reloader is started after the wsgi app has been loaded. """ reloader = mock.Mock() reloader_engines['poll'] = lambda *args, **kw: reloader app = ReloadApp() cfg = app.cfg log = mock.Mock() worker = MyWorker(age=0, ppid=0, sockets=[], app=app, timeout=0, cfg=cfg, log=log) worker.load_wsgi = mock.Mock() mock_parent = mock.Mock() mock_parent.attach_mock(worker.load_wsgi, 'load_wsgi') mock_parent.attach_mock(reloader.start, 'reloader_start') worker.init_process() mock_parent.assert_has_calls([ mock.call.load_wsgi(), mock.call.reloader_start(), ]) gunicorn-20.1.0/tests/test_sock.py000066400000000000000000000035261401157322000171310ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import unittest.mock as mock from gunicorn import sock @mock.patch('os.stat') def test_create_sockets_unix_bytes(stat): conf = mock.Mock(address=[b'127.0.0.1:8000']) log = mock.Mock() with mock.patch.object(sock.UnixSocket, '__init__', lambda *args: None): listeners = sock.create_sockets(conf, log) assert len(listeners) == 1 print(type(listeners[0])) assert isinstance(listeners[0], sock.UnixSocket) @mock.patch('os.stat') def test_create_sockets_unix_strings(stat): conf = mock.Mock(address=['127.0.0.1:8000']) log = mock.Mock() with mock.patch.object(sock.UnixSocket, '__init__', lambda *args: None): listeners = sock.create_sockets(conf, log) assert len(listeners) == 1 assert isinstance(listeners[0], sock.UnixSocket) def test_socket_close(): listener1 = mock.Mock() listener1.getsockname.return_value = ('127.0.0.1', '80') listener2 = mock.Mock() listener2.getsockname.return_value = ('192.168.2.5', '80') sock.close_sockets([listener1, listener2]) listener1.close.assert_called_with() listener2.close.assert_called_with() @mock.patch('os.unlink') def test_unix_socket_close_unlink(unlink): listener = mock.Mock() listener.getsockname.return_value = '/var/run/test.sock' sock.close_sockets([listener]) listener.close.assert_called_with() unlink.assert_called_once_with('/var/run/test.sock') @mock.patch('os.unlink') def test_unix_socket_close_without_unlink(unlink): listener = mock.Mock() listener.getsockname.return_value = '/var/run/test.sock' sock.close_sockets([listener], False) listener.close.assert_called_with() assert not unlink.called, 'unlink should not have been called' gunicorn-20.1.0/tests/test_ssl.py000066400000000000000000000043451401157322000167730ustar00rootroot00000000000000# -*- coding: utf-8 - # Copyright 2013 Dariusz Suchojad # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import pytest from gunicorn.config import ( KeyFile, CertFile, SSLVersion, CACerts, SuppressRaggedEOFs, DoHandshakeOnConnect, Setting, Ciphers, ) ssl = pytest.importorskip('ssl') def test_keyfile(): assert issubclass(KeyFile, Setting) assert KeyFile.name == 'keyfile' assert KeyFile.section == 'SSL' assert KeyFile.cli == ['--keyfile'] assert KeyFile.meta == 'FILE' assert KeyFile.default is None def test_certfile(): assert issubclass(CertFile, Setting) assert CertFile.name == 'certfile' assert CertFile.section == 'SSL' assert CertFile.cli == ['--certfile'] assert CertFile.default is None def test_ssl_version(): assert issubclass(SSLVersion, Setting) assert SSLVersion.name == 'ssl_version' assert SSLVersion.section == 'SSL' assert SSLVersion.cli == ['--ssl-version'] assert SSLVersion.default == ssl.PROTOCOL_SSLv23 def test_cacerts(): assert issubclass(CACerts, Setting) assert CACerts.name == 'ca_certs' assert CACerts.section == 'SSL' assert CACerts.cli == ['--ca-certs'] assert CACerts.meta == 'FILE' assert CACerts.default is None def test_suppress_ragged_eofs(): assert issubclass(SuppressRaggedEOFs, Setting) assert SuppressRaggedEOFs.name == 'suppress_ragged_eofs' assert SuppressRaggedEOFs.section == 'SSL' assert SuppressRaggedEOFs.cli == ['--suppress-ragged-eofs'] assert SuppressRaggedEOFs.action == 'store_true' assert SuppressRaggedEOFs.default is True def test_do_handshake_on_connect(): assert issubclass(DoHandshakeOnConnect, Setting) assert DoHandshakeOnConnect.name == 'do_handshake_on_connect' assert DoHandshakeOnConnect.section == 'SSL' assert DoHandshakeOnConnect.cli == ['--do-handshake-on-connect'] assert DoHandshakeOnConnect.action == 'store_true' assert DoHandshakeOnConnect.default is False def test_ciphers(): assert issubclass(Ciphers, Setting) assert Ciphers.name == 'ciphers' assert Ciphers.section == 'SSL' assert Ciphers.cli == ['--ciphers'] assert Ciphers.default is None gunicorn-20.1.0/tests/test_statsd.py000066400000000000000000000105411401157322000174670ustar00rootroot00000000000000import io import logging import os import shutil import socket import tempfile from datetime import timedelta from types import SimpleNamespace from gunicorn.config import Config from gunicorn.instrument.statsd import Statsd class StatsdTestException(Exception): pass class MockSocket(object): "Pretend to be a UDP socket" def __init__(self, failp): self.failp = failp self.msgs = [] # accumulate messages for later inspection def send(self, msg): if self.failp: raise StatsdTestException("Should not interrupt the logger") sock_dir = tempfile.mkdtemp() sock_file = os.path.join(sock_dir, "test.sock") server = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) client = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) try: server.bind(sock_file) client.connect(sock_file) client.send(msg) self.msgs.append(server.recv(1024)) finally: client.close() server.close() shutil.rmtree(sock_dir) def reset(self): self.msgs = [] def test_statsd_fail(): "UDP socket fails" logger = Statsd(Config()) logger.sock = MockSocket(True) logger.info("No impact on logging") logger.debug("No impact on logging") logger.critical("No impact on logging") logger.error("No impact on logging") logger.warning("No impact on logging") logger.exception("No impact on logging") def test_dogstatsd_tags(): c = Config() tags = 'yucatan,libertine:rhubarb' c.set('dogstatsd_tags', tags) logger = Statsd(c) logger.sock = MockSocket(False) logger.info("Twill", extra={"mtype": "gauge", "metric": "barb.westerly", "value": 2}) assert logger.sock.msgs[0] == b"barb.westerly:2|g|#" + tags.encode('ascii') def test_instrument(): logger = Statsd(Config()) # Capture logged messages sio = io.StringIO() logger.error_log.addHandler(logging.StreamHandler(sio)) logger.sock = MockSocket(False) # Regular message logger.info("Blah", extra={"mtype": "gauge", "metric": "gunicorn.test", "value": 666}) assert logger.sock.msgs[0] == b"gunicorn.test:666|g" assert sio.getvalue() == "Blah\n" logger.sock.reset() # Only metrics, no logging logger.info("", extra={"mtype": "gauge", "metric": "gunicorn.test", "value": 666}) assert logger.sock.msgs[0] == b"gunicorn.test:666|g" assert sio.getvalue() == "Blah\n" # log is unchanged logger.sock.reset() # Debug logging also supports metrics logger.debug("", extra={"mtype": "gauge", "metric": "gunicorn.debug", "value": 667}) assert logger.sock.msgs[0] == b"gunicorn.debug:667|g" assert sio.getvalue() == "Blah\n" # log is unchanged logger.sock.reset() logger.critical("Boom") assert logger.sock.msgs[0] == b"gunicorn.log.critical:1|c|@1.0" logger.sock.reset() logger.access(SimpleNamespace(status="200 OK"), None, {}, timedelta(seconds=7)) assert logger.sock.msgs[0] == b"gunicorn.request.duration:7000.0|ms" assert logger.sock.msgs[1] == b"gunicorn.requests:1|c|@1.0" assert logger.sock.msgs[2] == b"gunicorn.request.status.200:1|c|@1.0" def test_prefix(): c = Config() c.set("statsd_prefix", "test.") logger = Statsd(c) logger.sock = MockSocket(False) logger.info("Blah", extra={"mtype": "gauge", "metric": "gunicorn.test", "value": 666}) assert logger.sock.msgs[0] == b"test.gunicorn.test:666|g" def test_prefix_no_dot(): c = Config() c.set("statsd_prefix", "test") logger = Statsd(c) logger.sock = MockSocket(False) logger.info("Blah", extra={"mtype": "gauge", "metric": "gunicorn.test", "value": 666}) assert logger.sock.msgs[0] == b"test.gunicorn.test:666|g" def test_prefix_multiple_dots(): c = Config() c.set("statsd_prefix", "test...") logger = Statsd(c) logger.sock = MockSocket(False) logger.info("Blah", extra={"mtype": "gauge", "metric": "gunicorn.test", "value": 666}) assert logger.sock.msgs[0] == b"test.gunicorn.test:666|g" def test_prefix_nested(): c = Config() c.set("statsd_prefix", "test.asdf.") logger = Statsd(c) logger.sock = MockSocket(False) logger.info("Blah", extra={"mtype": "gauge", "metric": "gunicorn.test", "value": 666}) assert logger.sock.msgs[0] == b"test.asdf.gunicorn.test:666|g" gunicorn-20.1.0/tests/test_systemd.py000066400000000000000000000040041401157322000176520ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. from contextlib import contextmanager import os import unittest.mock as mock import pytest from gunicorn import systemd @contextmanager def check_environ(unset=True): """ A context manager that asserts post-conditions of ``listen_fds`` at exit. This helper is used to ease checking of the test post-conditions for the systemd socket activation tests that parametrize the call argument. """ with mock.patch.dict(os.environ): old_fds = os.environ.get('LISTEN_FDS', None) old_pid = os.environ.get('LISTEN_PID', None) yield if unset: assert 'LISTEN_FDS' not in os.environ, \ "LISTEN_FDS should have been unset" assert 'LISTEN_PID' not in os.environ, \ "LISTEN_PID should have been unset" else: new_fds = os.environ.get('LISTEN_FDS', None) new_pid = os.environ.get('LISTEN_PID', None) assert new_fds == old_fds, \ "LISTEN_FDS should not have been changed" assert new_pid == old_pid, \ "LISTEN_PID should not have been changed" @pytest.mark.parametrize("unset", [True, False]) def test_listen_fds_ignores_wrong_pid(unset): with mock.patch.dict(os.environ): os.environ['LISTEN_FDS'] = str(5) os.environ['LISTEN_PID'] = str(1) with check_environ(False): # early exit — never changes the environment assert systemd.listen_fds(unset) == 0, \ "should ignore listen fds not intended for this pid" @pytest.mark.parametrize("unset", [True, False]) def test_listen_fds_returns_count(unset): with mock.patch.dict(os.environ): os.environ['LISTEN_FDS'] = str(5) os.environ['LISTEN_PID'] = str(os.getpid()) with check_environ(unset): assert systemd.listen_fds(unset) == 5, \ "should return the correct count of fds" gunicorn-20.1.0/tests/test_util.py000066400000000000000000000104171401157322000171440ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import os import pytest from gunicorn import util from gunicorn.errors import AppImportError from urllib.parse import SplitResult @pytest.mark.parametrize('test_input, expected', [ ('unix://var/run/test.sock', 'var/run/test.sock'), ('unix:/var/run/test.sock', '/var/run/test.sock'), ('tcp://localhost', ('localhost', 8000)), ('tcp://localhost:5000', ('localhost', 5000)), ('', ('0.0.0.0', 8000)), ('[::1]:8000', ('::1', 8000)), ('[::1]:5000', ('::1', 5000)), ('[::1]', ('::1', 8000)), ('localhost:8000', ('localhost', 8000)), ('127.0.0.1:8000', ('127.0.0.1', 8000)), ('localhost', ('localhost', 8000)), ('fd://33', 33), ]) def test_parse_address(test_input, expected): assert util.parse_address(test_input) == expected def test_parse_address_invalid(): with pytest.raises(RuntimeError) as exc_info: util.parse_address('127.0.0.1:test') assert "'test' is not a valid port number." in str(exc_info.value) def test_parse_fd_invalid(): with pytest.raises(RuntimeError) as exc_info: util.parse_address('fd://asd') assert "'asd' is not a valid file descriptor." in str(exc_info.value) def test_http_date(): assert util.http_date(1508607753.740316) == 'Sat, 21 Oct 2017 17:42:33 GMT' @pytest.mark.parametrize('test_input, expected', [ ('1200:0000:AB00:1234:0000:2552:7777:1313', True), ('1200::AB00:1234::2552:7777:1313', False), ('21DA:D3:0:2F3B:2AA:FF:FE28:9C5A', True), ('1200:0000:AB00:1234:O000:2552:7777:1313', False), ]) def test_is_ipv6(test_input, expected): assert util.is_ipv6(test_input) == expected def test_warn(capsys): util.warn('test warn') _, err = capsys.readouterr() assert '!!! WARNING: test warn' in err @pytest.mark.parametrize( "value", [ "support", "support:app", "support:create_app()", "support:create_app('Gunicorn', 3)", "support:create_app(count=3)", ], ) def test_import_app_good(value): assert util.import_app(value) @pytest.mark.parametrize( ("value", "exc_type", "msg"), [ ("a:app", ImportError, "No module"), ("support:create_app(", AppImportError, "Failed to parse"), ("support:create.app()", AppImportError, "Function reference"), ("support:create_app(Gunicorn)", AppImportError, "literal values"), ("support:create.app", AppImportError, "attribute name"), ("support:wrong_app", AppImportError, "find attribute"), ("support:error_factory(1)", AppImportError, "error_factory() takes"), ("support:error_factory()", TypeError, "inner"), ("support:none_app", AppImportError, "find application object"), ("support:HOST", AppImportError, "callable"), ], ) def test_import_app_bad(value, exc_type, msg): with pytest.raises(exc_type) as exc_info: util.import_app(value) assert msg in str(exc_info.value) def test_import_app_py_ext(monkeypatch): monkeypatch.chdir(os.path.dirname(__file__)) with pytest.raises(ImportError) as exc_info: util.import_app("support.py") assert "did you mean" in str(exc_info.value) def test_to_bytestring(): assert util.to_bytestring('test_str', 'ascii') == b'test_str' assert util.to_bytestring('test_str®') == b'test_str\xc2\xae' assert util.to_bytestring(b'byte_test_str') == b'byte_test_str' with pytest.raises(TypeError) as exc_info: util.to_bytestring(100) msg = '100 is not a string' assert msg in str(exc_info.value) @pytest.mark.parametrize('test_input, expected', [ ('https://example.org/a/b?c=1#d', SplitResult(scheme='https', netloc='example.org', path='/a/b', query='c=1', fragment='d')), ('a/b?c=1#d', SplitResult(scheme='', netloc='', path='a/b', query='c=1', fragment='d')), ('/a/b?c=1#d', SplitResult(scheme='', netloc='', path='/a/b', query='c=1', fragment='d')), ('//a/b?c=1#d', SplitResult(scheme='', netloc='', path='//a/b', query='c=1', fragment='d')), ('///a/b?c=1#d', SplitResult(scheme='', netloc='', path='///a/b', query='c=1', fragment='d')), ]) def test_split_request_uri(test_input, expected): assert util.split_request_uri(test_input) == expected gunicorn-20.1.0/tests/test_valid_requests.py000066400000000000000000000011401401157322000212120ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. import glob import os import pytest import treq dirname = os.path.dirname(__file__) reqdir = os.path.join(dirname, "requests", "valid") httpfiles = glob.glob(os.path.join(reqdir, "*.http")) @pytest.mark.parametrize("fname", httpfiles) def test_http_parser(fname): env = treq.load_py(os.path.splitext(fname)[0] + ".py") expect = env['request'] cfg = env['cfg'] req = treq.request(fname, expect) for case in req.gen_cases(cfg): case[0](*case[1:]) gunicorn-20.1.0/tests/treq.py000066400000000000000000000223321401157322000161020ustar00rootroot00000000000000# Copyright 2009 Paul J. Davis # # This file is part of the pywebmachine package released # under the MIT license. import inspect import importlib.machinery import os import random import types from gunicorn.config import Config from gunicorn.http.parser import RequestParser from gunicorn.util import split_request_uri dirname = os.path.dirname(__file__) random.seed() def uri(data): ret = {"raw": data} parts = split_request_uri(data) ret["scheme"] = parts.scheme or '' ret["host"] = parts.netloc.rsplit(":", 1)[0] or None ret["port"] = parts.port or 80 ret["path"] = parts.path or '' ret["query"] = parts.query or '' ret["fragment"] = parts.fragment or '' return ret def load_py(fname): module_name = '__config__' mod = types.ModuleType(module_name) setattr(mod, 'uri', uri) setattr(mod, 'cfg', Config()) loader = importlib.machinery.SourceFileLoader(module_name, fname) loader.exec_module(mod) return vars(mod) class request(object): def __init__(self, fname, expect): self.fname = fname self.name = os.path.basename(fname) self.expect = expect if not isinstance(self.expect, list): self.expect = [self.expect] with open(self.fname, 'rb') as handle: self.data = handle.read() self.data = self.data.replace(b"\n", b"").replace(b"\\r\\n", b"\r\n") self.data = self.data.replace(b"\\0", b"\000") # Functions for sending data to the parser. # These functions mock out reading from a # socket or other data source that might # be used in real life. def send_all(self): yield self.data def send_lines(self): lines = self.data pos = lines.find(b"\r\n") while pos > 0: yield lines[:pos+2] lines = lines[pos+2:] pos = lines.find(b"\r\n") if lines: yield lines def send_bytes(self): for d in self.data: yield bytes([d]) def send_random(self): maxs = round(len(self.data) / 10) read = 0 while read < len(self.data): chunk = random.randint(1, maxs) yield self.data[read:read+chunk] read += chunk def send_special_chunks(self): """Meant to test the request line length check. Sends the request data in two chunks, one having a length of 1 byte, which ensures that no CRLF is included, and a second chunk containing the rest of the request data. If the request line length check is not done properly, testing the ``tests/requests/valid/099.http`` request fails with a ``LimitRequestLine`` exception. """ chunk = self.data[:1] read = 0 while read < len(self.data): yield self.data[read:read+len(chunk)] read += len(chunk) chunk = self.data[read:] # These functions define the sizes that the # read functions will read with. def size_all(self): return -1 def size_bytes(self): return 1 def size_small_random(self): return random.randint(1, 4) def size_random(self): return random.randint(1, 4096) # Match a body against various ways of reading # a message. Pass in the request, expected body # and one of the size functions. def szread(self, func, sizes): sz = sizes() data = func(sz) if 0 <= sz < len(data): raise AssertionError("Read more than %d bytes: %s" % (sz, data)) return data def match_read(self, req, body, sizes): data = self.szread(req.body.read, sizes) count = 1000 while body: if body[:len(data)] != data: raise AssertionError("Invalid body data read: %r != %r" % ( data, body[:len(data)])) body = body[len(data):] data = self.szread(req.body.read, sizes) if not data: count -= 1 if count <= 0: raise AssertionError("Unexpected apparent EOF") if body: raise AssertionError("Failed to read entire body: %r" % body) elif data: raise AssertionError("Read beyond expected body: %r" % data) data = req.body.read(sizes()) if data: raise AssertionError("Read after body finished: %r" % data) def match_readline(self, req, body, sizes): data = self.szread(req.body.readline, sizes) count = 1000 while body: if body[:len(data)] != data: raise AssertionError("Invalid data read: %r" % data) if b'\n' in data[:-1]: raise AssertionError("Embedded new line: %r" % data) body = body[len(data):] data = self.szread(req.body.readline, sizes) if not data: count -= 1 if count <= 0: raise AssertionError("Apparent unexpected EOF") if body: raise AssertionError("Failed to read entire body: %r" % body) elif data: raise AssertionError("Read beyond expected body: %r" % data) data = req.body.readline(sizes()) if data: raise AssertionError("Read data after body finished: %r" % data) def match_readlines(self, req, body, sizes): """\ This skips the sizes checks as we don't implement it. """ data = req.body.readlines() for line in data: if b'\n' in line[:-1]: raise AssertionError("Embedded new line: %r" % line) if line != body[:len(line)]: raise AssertionError("Invalid body data read: %r != %r" % ( line, body[:len(line)])) body = body[len(line):] if body: raise AssertionError("Failed to read entire body: %r" % body) data = req.body.readlines(sizes()) if data: raise AssertionError("Read data after body finished: %r" % data) def match_iter(self, req, body, sizes): """\ This skips sizes because there's its not part of the iter api. """ for line in req.body: if b'\n' in line[:-1]: raise AssertionError("Embedded new line: %r" % line) if line != body[:len(line)]: raise AssertionError("Invalid body data read: %r != %r" % ( line, body[:len(line)])) body = body[len(line):] if body: raise AssertionError("Failed to read entire body: %r" % body) try: data = next(iter(req.body)) raise AssertionError("Read data after body finished: %r" % data) except StopIteration: pass # Construct a series of test cases from the permutations of # send, size, and match functions. def gen_cases(self, cfg): def get_funs(p): return [v for k, v in inspect.getmembers(self) if k.startswith(p)] senders = get_funs("send_") sizers = get_funs("size_") matchers = get_funs("match_") cfgs = [ (mt, sz, sn) for mt in matchers for sz in sizers for sn in senders ] ret = [] for (mt, sz, sn) in cfgs: if hasattr(mt, 'funcname'): mtn = mt.func_name[6:] szn = sz.func_name[5:] snn = sn.func_name[5:] else: mtn = mt.__name__[6:] szn = sz.__name__[5:] snn = sn.__name__[5:] def test_req(sn, sz, mt): self.check(cfg, sn, sz, mt) desc = "%s: MT: %s SZ: %s SN: %s" % (self.name, mtn, szn, snn) test_req.description = desc ret.append((test_req, sn, sz, mt)) return ret def check(self, cfg, sender, sizer, matcher): cases = self.expect[:] p = RequestParser(cfg, sender(), None) for req in p: self.same(req, sizer, matcher, cases.pop(0)) assert not cases def same(self, req, sizer, matcher, exp): assert req.method == exp["method"] assert req.uri == exp["uri"]["raw"] assert req.path == exp["uri"]["path"] assert req.query == exp["uri"]["query"] assert req.fragment == exp["uri"]["fragment"] assert req.version == exp["version"] assert req.headers == exp["headers"] matcher(req, exp["body"], sizer) assert req.trailers == exp.get("trailers", []) class badrequest(object): def __init__(self, fname): self.fname = fname self.name = os.path.basename(fname) with open(self.fname) as handle: self.data = handle.read() self.data = self.data.replace("\n", "").replace("\\r\\n", "\r\n") self.data = self.data.replace("\\0", "\000") self.data = self.data.encode('latin1') def send(self): maxs = round(len(self.data) / 10) read = 0 while read < len(self.data): chunk = random.randint(1, maxs) yield self.data[read:read+chunk] read += chunk def check(self, cfg): p = RequestParser(cfg, self.send(), None) next(p) gunicorn-20.1.0/tests/workers/000077500000000000000000000000001401157322000162475ustar00rootroot00000000000000gunicorn-20.1.0/tests/workers/__init__.py000066400000000000000000000000001401157322000203460ustar00rootroot00000000000000gunicorn-20.1.0/tests/workers/test_geventlet.py000066400000000000000000000003001401157322000216460ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. def test_import(): __import__('gunicorn.workers.geventlet') gunicorn-20.1.0/tests/workers/test_ggevent.py000066400000000000000000000002761401157322000213240ustar00rootroot00000000000000# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. def test_import(): __import__('gunicorn.workers.ggevent') gunicorn-20.1.0/tox.ini000066400000000000000000000020261401157322000147240ustar00rootroot00000000000000[tox] envlist = py35, py36, py37, py38, py39, pypy3, lint skipsdist = True [testenv] usedevelop = True commands = py.test --cov=gunicorn {posargs} deps = -rrequirements_test.txt [testenv:lint] commands = pylint -j0 \ gunicorn \ tests/test_arbiter.py \ tests/test_config.py \ tests/test_http.py \ tests/test_invalid_requests.py \ tests/test_logger.py \ tests/test_pidfile.py \ tests/test_sock.py \ tests/test_ssl.py \ tests/test_statsd.py \ tests/test_systemd.py \ tests/test_util.py \ tests/test_valid_requests.py deps = pylint [testenv:docs-lint] whitelist_externals = rst-lint bash grep deps = restructuredtext_lint pygments commands = rst-lint README.rst docs/README.rst bash -c "(set -o pipefail; rst-lint --encoding utf-8 docs/source/*.rst | grep -v 'Unknown interpreted text role\|Unknown directive type'); test $? == 1" [testenv:pycodestyle] commands = pycodestyle gunicorn deps = pycodestyle [pycodestyle] max-line-length = 120 ignore = E129,W503,W504,W606