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
dist: xenial
sudo: true
- python: pypy3
env: TOXENV=pypy3
dist: xenial
- python: 3.8-dev
env: TOXENV=py38-dev
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 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?

View File

@ -52,6 +52,12 @@ Example with test app::
$ gunicorn --workers=2 test:app
Contributing
------------
See `our complete contributor's guide <CONTRIBUTING.md>`_ for more details.
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:
.. literalinclude:: ../../examples/standalone_app.py
:lines: 11-60
:start-after: # See the NOTICE for more information
:lines: 2-
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.
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
================

View File

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

View File

@ -3,6 +3,6 @@
# This file is part of gunicorn released under the MIT license.
# 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])
SERVER_SOFTWARE = "gunicorn/%s" % __version__

View File

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

View File

@ -445,7 +445,7 @@ class Logger(object):
def _get_user(self, environ):
user = None
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)
if len(auth) == 2:
try:

View File

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

View File

@ -253,10 +253,13 @@ class Response(object):
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 = str(value).strip()
value = value.strip()
lname = name.lower().strip()
if lname == "content-length":
self.response_length = int(value)

View File

@ -21,11 +21,13 @@ class WorkerTmp(object):
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)
# allows the process to write to the file
util.chown(name, cfg.uid, cfg.gid)
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:

View File

@ -26,6 +26,8 @@ CLASSIFIERS = [
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'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',
@ -78,6 +80,7 @@ extras_require = {
'eventlet': ['eventlet>=0.9.7'],
'tornado': ['tornado>=0.2'],
'gthread': [],
'setproctitle': ['setproctitle'],
}
setup(

View File

@ -1,6 +1,8 @@
import datetime
from types import SimpleNamespace
import pytest
from gunicorn.config import Config
from gunicorn.glogging import Logger
@ -47,7 +49,13 @@ def test_atoms_zero_bytes():
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=())
response = SimpleNamespace(
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',
'PATH_INFO': '/my/path', 'QUERY_STRING': 'foo=bar',
'SERVER_PROTOCOL': 'HTTP/1.1',
'HTTP_AUTHORIZATION': 'Basic YnJrMHY6',
'HTTP_AUTHORIZATION': auth,
}
logger = Logger(Config())
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():
with pytest.raises(RuntimeError) as err:
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(err)
assert "'test' is not a valid port number." in str(exc_info.value)
def test_parse_fd_invalid():
with pytest.raises(RuntimeError) as err:
with pytest.raises(RuntimeError) as exc_info:
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():
@ -63,24 +63,24 @@ def test_warn(capsys):
def test_import_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')
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')
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():
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 err:
with pytest.raises(TypeError) as exc_info:
util.to_bytestring(100)
msg = '100 is not a string'
assert msg in str(err)
assert msg in str(exc_info.value)
@pytest.mark.parametrize('test_input, expected', [

View File

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