mirror of
https://github.com/frappe/gunicorn.git
synced 2026-01-14 11:09:11 +08:00
Merge branch 'master' of github.com:benoitc/gunicorn into 2066-statsd-socket
This commit is contained in:
commit
4b91ca1e9f
@ -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
|
||||||
|
|||||||
@ -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?
|
||||||
|
|||||||
@ -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
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|||||||
@ -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
|
||||||
================
|
================
|
||||||
|
|
||||||
|
|||||||
@ -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"""
|
||||||
|
|
||||||
|
|||||||
@ -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__
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
3
setup.py
3
setup.py
@ -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(
|
||||||
|
|||||||
@ -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))
|
||||||
|
|||||||
@ -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', [
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user