Merge branch 'master' of github.com:benoitc/gunicorn into 2066-statsd-socket

This commit is contained in:
Wojciech Malinowski 2019-10-26 15:31:16 +02:00
commit 4b91ca1e9f
16 changed files with 72 additions and 28 deletions

View File

@ -16,6 +16,9 @@ matrix:
env: TOXENV=py37 env: TOXENV=py37
dist: xenial dist: xenial
sudo: true sudo: true
- python: pypy3
env: TOXENV=pypy3
dist: xenial
- python: 3.8-dev - python: 3.8-dev
env: TOXENV=py38-dev env: TOXENV=py38-dev
dist: xenial dist: xenial

View File

@ -141,7 +141,7 @@ The relevant maintainer for a pull request is assigned in 3 steps:
* 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 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. The pull request is assigned to him. He may assign it to other listed maintainers, at his discretion. * 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? ### I'm a maintainer, should I make pull requests too?

View File

@ -52,6 +52,12 @@ Example with test app::
$ gunicorn --workers=2 test:app $ gunicorn --workers=2 test:app
Contributing
------------
See `our complete contributor's guide <CONTRIBUTING.md>`_ for more details.
License License
------- -------

View File

@ -13,7 +13,8 @@ Here is a small example where we create a very small WSGI app and load it with
a custom Application: a custom Application:
.. literalinclude:: ../../examples/standalone_app.py .. literalinclude:: ../../examples/standalone_app.py
:lines: 11-60 :start-after: # See the NOTICE for more information
:lines: 2-
Direct Usage of Existing WSGI Apps Direct Usage of Existing WSGI Apps
---------------------------------- ----------------------------------

View File

@ -52,6 +52,28 @@ want to consider one of the alternate worker types.
installed, this is the most likely reason. 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 Debian GNU/Linux
================ ================

View File

@ -5,12 +5,9 @@
# #
# Example code from Eventlet sources # Example code from Eventlet sources
from wsgiref.validate import validator
from gunicorn import __version__ from gunicorn import __version__
@validator
def app(environ, start_response): def app(environ, start_response):
"""Simplest possible application object""" """Simplest possible application object"""

View File

@ -3,6 +3,6 @@
# This file is part of gunicorn released under the MIT license. # This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information. # See the NOTICE for more information.
version_info = (19, 9, 0) version_info = (20, 0, 0)
__version__ = ".".join([str(v) for v in version_info]) __version__ = ".".join([str(v) for v in version_info])
SERVER_SOFTWARE = "gunicorn/%s" % __version__ SERVER_SOFTWARE = "gunicorn/%s" % __version__

View File

@ -223,9 +223,7 @@ class Arbiter(object):
self.log.info("Handling signal: %s", signame) self.log.info("Handling signal: %s", signame)
handler() handler()
self.wakeup() self.wakeup()
except StopIteration: except (StopIteration, KeyboardInterrupt):
self.halt()
except KeyboardInterrupt:
self.halt() self.halt()
except HaltServer as inst: except HaltServer as inst:
self.halt(reason=inst.reason, exit_status=inst.exit_status) self.halt(reason=inst.reason, exit_status=inst.exit_status)

View File

@ -445,7 +445,7 @@ class Logger(object):
def _get_user(self, environ): def _get_user(self, environ):
user = None user = None
http_auth = environ.get("HTTP_AUTHORIZATION") http_auth = environ.get("HTTP_AUTHORIZATION")
if http_auth and http_auth.startswith('Basic'): if http_auth and http_auth.lower().startswith('basic'):
auth = http_auth.split(" ", 1) auth = http_auth.split(" ", 1)
if len(auth) == 2: if len(auth) == 2:
try: try:

View File

@ -7,7 +7,7 @@ import io
import sys import sys
from gunicorn.http.errors import (NoMoreData, ChunkMissingTerminator, from gunicorn.http.errors import (NoMoreData, ChunkMissingTerminator,
InvalidChunkSize) InvalidChunkSize)
class ChunkedReader(object): class ChunkedReader(object):
@ -187,6 +187,7 @@ class Body(object):
if not ret: if not ret:
raise StopIteration() raise StopIteration()
return ret return ret
next = __next__ next = __next__
def getsize(self, size): def getsize(self, size):

View File

@ -253,10 +253,13 @@ class Response(object):
if HEADER_RE.search(name): if HEADER_RE.search(name):
raise InvalidHeaderName('%r' % name) raise InvalidHeaderName('%r' % name)
if not isinstance(value, str):
raise TypeError('%r is not a string' % value)
if HEADER_VALUE_RE.search(value): if HEADER_VALUE_RE.search(value):
raise InvalidHeader('%r' % value) raise InvalidHeader('%r' % value)
value = str(value).strip() value = value.strip()
lname = name.lower().strip() lname = name.lower().strip()
if lname == "content-length": if lname == "content-length":
self.response_length = int(value) self.response_length = int(value)

View File

@ -21,11 +21,13 @@ class WorkerTmp(object):
if fdir and not os.path.isdir(fdir): if fdir and not os.path.isdir(fdir):
raise RuntimeError("%s doesn't exist. Can't create workertmp." % fdir) raise RuntimeError("%s doesn't exist. Can't create workertmp." % fdir)
fd, name = tempfile.mkstemp(prefix="wgunicorn-", dir=fdir) fd, name = tempfile.mkstemp(prefix="wgunicorn-", dir=fdir)
# allows the process to write to the file
util.chown(name, cfg.uid, cfg.gid)
os.umask(old_umask) 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 # unlink the file so we don't leak tempory files
try: try:
if not IS_CYGWIN: if not IS_CYGWIN:

View File

@ -26,6 +26,8 @@ CLASSIFIERS = [
'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Internet', 'Topic :: Internet',
'Topic :: Utilities', 'Topic :: Utilities',
'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Python Modules',
@ -78,6 +80,7 @@ extras_require = {
'eventlet': ['eventlet>=0.9.7'], 'eventlet': ['eventlet>=0.9.7'],
'tornado': ['tornado>=0.2'], 'tornado': ['tornado>=0.2'],
'gthread': [], 'gthread': [],
'setproctitle': ['setproctitle'],
} }
setup( setup(

View File

@ -1,6 +1,8 @@
import datetime import datetime
from types import SimpleNamespace from types import SimpleNamespace
import pytest
from gunicorn.config import Config from gunicorn.config import Config
from gunicorn.glogging import Logger from gunicorn.glogging import Logger
@ -47,7 +49,13 @@ def test_atoms_zero_bytes():
assert atoms['B'] == 0 assert atoms['B'] == 0
def test_get_username_from_basic_auth_header(): @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=()) request = SimpleNamespace(headers=())
response = SimpleNamespace( response = SimpleNamespace(
status='200', response_length=1024, sent=1024, status='200', response_length=1024, sent=1024,
@ -57,7 +65,7 @@ def test_get_username_from_basic_auth_header():
'REQUEST_METHOD': 'GET', 'RAW_URI': '/my/path?foo=bar', 'REQUEST_METHOD': 'GET', 'RAW_URI': '/my/path?foo=bar',
'PATH_INFO': '/my/path', 'QUERY_STRING': 'foo=bar', 'PATH_INFO': '/my/path', 'QUERY_STRING': 'foo=bar',
'SERVER_PROTOCOL': 'HTTP/1.1', 'SERVER_PROTOCOL': 'HTTP/1.1',
'HTTP_AUTHORIZATION': 'Basic YnJrMHY6', 'HTTP_AUTHORIZATION': auth,
} }
logger = Logger(Config()) logger = Logger(Config())
atoms = logger.atoms(response, request, environ, datetime.timedelta(seconds=1)) atoms = logger.atoms(response, request, environ, datetime.timedelta(seconds=1))

View File

@ -29,15 +29,15 @@ def test_parse_address(test_input, expected):
def test_parse_address_invalid(): def test_parse_address_invalid():
with pytest.raises(RuntimeError) as err: with pytest.raises(RuntimeError) as exc_info:
util.parse_address('127.0.0.1:test') util.parse_address('127.0.0.1:test')
assert "'test' is not a valid port number." in str(err) assert "'test' is not a valid port number." in str(exc_info.value)
def test_parse_fd_invalid(): def test_parse_fd_invalid():
with pytest.raises(RuntimeError) as err: with pytest.raises(RuntimeError) as exc_info:
util.parse_address('fd://asd') util.parse_address('fd://asd')
assert "'asd' is not a valid file descriptor." in str(err) assert "'asd' is not a valid file descriptor." in str(exc_info.value)
def test_http_date(): def test_http_date():
@ -63,24 +63,24 @@ def test_warn(capsys):
def test_import_app(): def test_import_app():
assert util.import_app('support:app') assert util.import_app('support:app')
with pytest.raises(ImportError) as err: with pytest.raises(ImportError) as exc_info:
util.import_app('a:app') util.import_app('a:app')
assert 'No module' in str(err) assert 'No module' in str(exc_info.value)
with pytest.raises(AppImportError) as err: with pytest.raises(AppImportError) as exc_info:
util.import_app('support:wrong_app') util.import_app('support:wrong_app')
msg = "Failed to find application object 'wrong_app' in 'support'" msg = "Failed to find application object 'wrong_app' in 'support'"
assert msg in str(err) assert msg in str(exc_info.value)
def test_to_bytestring(): def test_to_bytestring():
assert util.to_bytestring('test_str', 'ascii') == b'test_str' 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('test_str®') == b'test_str\xc2\xae'
assert util.to_bytestring(b'byte_test_str') == b'byte_test_str' assert util.to_bytestring(b'byte_test_str') == b'byte_test_str'
with pytest.raises(TypeError) as err: with pytest.raises(TypeError) as exc_info:
util.to_bytestring(100) util.to_bytestring(100)
msg = '100 is not a string' msg = '100 is not a string'
assert msg in str(err) assert msg in str(exc_info.value)
@pytest.mark.parametrize('test_input, expected', [ @pytest.mark.parametrize('test_input, expected', [

View File

@ -1,5 +1,5 @@
[tox] [tox]
envlist = py34, py35, py36, py37, py38-dev, pypy, lint envlist = py34, py35, py36, py37, py38-dev, pypy3, lint
skipsdist = True skipsdist = True
[testenv] [testenv]