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
|
||||
dist: xenial
|
||||
sudo: true
|
||||
- python: pypy3
|
||||
env: TOXENV=pypy3
|
||||
dist: xenial
|
||||
- python: 3.8-dev
|
||||
env: TOXENV=py38-dev
|
||||
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 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?
|
||||
|
||||
@ -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
|
||||
-------
|
||||
|
||||
|
||||
@ -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
|
||||
----------------------------------
|
||||
|
||||
@ -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
|
||||
================
|
||||
|
||||
|
||||
@ -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"""
|
||||
|
||||
|
||||
@ -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__
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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:
|
||||
|
||||
3
setup.py
3
setup.py
@ -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(
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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', [
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user