mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-06 13:11:15 -07:00
Update cheroot-8.5.2
This commit is contained in:
parent
4ac151d7de
commit
182e5f553e
25 changed files with 2171 additions and 602 deletions
|
@ -7,12 +7,13 @@ sticking incoming connections onto a Queue::
|
|||
|
||||
server = HTTPServer(...)
|
||||
server.start()
|
||||
-> while True:
|
||||
tick()
|
||||
# This blocks until a request comes in:
|
||||
child = socket.accept()
|
||||
conn = HTTPConnection(child, ...)
|
||||
server.requests.put(conn)
|
||||
-> serve()
|
||||
while ready:
|
||||
_connections.run()
|
||||
while not stop_requested:
|
||||
child = socket.accept() # blocks until a request comes in
|
||||
conn = HTTPConnection(child, ...)
|
||||
server.process_conn(conn) # adds conn to threadpool
|
||||
|
||||
Worker threads are kept in a pool and poll the Queue, popping off and then
|
||||
handling each connection in turn. Each connection can consist of an arbitrary
|
||||
|
@ -58,7 +59,6 @@ And now for a trivial doctest to exercise the test suite
|
|||
|
||||
>>> 'HTTPServer' in globals()
|
||||
True
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
@ -74,6 +74,8 @@ import time
|
|||
import traceback as traceback_
|
||||
import logging
|
||||
import platform
|
||||
import contextlib
|
||||
import threading
|
||||
|
||||
try:
|
||||
from functools import lru_cache
|
||||
|
@ -93,6 +95,7 @@ from .makefile import MakeFile, StreamWriter
|
|||
|
||||
__all__ = (
|
||||
'HTTPRequest', 'HTTPConnection', 'HTTPServer',
|
||||
'HeaderReader', 'DropUnderscoreHeaderReader',
|
||||
'SizeCheckWrapper', 'KnownLengthRFile', 'ChunkedRFile',
|
||||
'Gateway', 'get_ssl_adapter_class',
|
||||
)
|
||||
|
@ -156,7 +159,10 @@ EMPTY = b''
|
|||
ASTERISK = b'*'
|
||||
FORWARD_SLASH = b'/'
|
||||
QUOTED_SLASH = b'%2F'
|
||||
QUOTED_SLASH_REGEX = re.compile(b'(?i)' + QUOTED_SLASH)
|
||||
QUOTED_SLASH_REGEX = re.compile(b''.join((b'(?i)', QUOTED_SLASH)))
|
||||
|
||||
|
||||
_STOPPING_FOR_INTERRUPT = object() # sentinel used during shutdown
|
||||
|
||||
|
||||
comma_separated_headers = [
|
||||
|
@ -179,7 +185,7 @@ class HeaderReader:
|
|||
Interface and default implementation.
|
||||
"""
|
||||
|
||||
def __call__(self, rfile, hdict=None):
|
||||
def __call__(self, rfile, hdict=None): # noqa: C901 # FIXME
|
||||
"""
|
||||
Read headers from the given stream into the given header dict.
|
||||
|
||||
|
@ -248,15 +254,14 @@ class DropUnderscoreHeaderReader(HeaderReader):
|
|||
|
||||
|
||||
class SizeCheckWrapper:
|
||||
"""Wraps a file-like object, raising MaxSizeExceeded if too large."""
|
||||
"""Wraps a file-like object, raising MaxSizeExceeded if too large.
|
||||
|
||||
:param rfile: ``file`` of a limited size
|
||||
:param int maxlen: maximum length of the file being read
|
||||
"""
|
||||
|
||||
def __init__(self, rfile, maxlen):
|
||||
"""Initialize SizeCheckWrapper instance.
|
||||
|
||||
Args:
|
||||
rfile (file): file of a limited size
|
||||
maxlen (int): maximum length of the file being read
|
||||
"""
|
||||
"""Initialize SizeCheckWrapper instance."""
|
||||
self.rfile = rfile
|
||||
self.maxlen = maxlen
|
||||
self.bytes_read = 0
|
||||
|
@ -266,14 +271,12 @@ class SizeCheckWrapper:
|
|||
raise errors.MaxSizeExceeded()
|
||||
|
||||
def read(self, size=None):
|
||||
"""Read a chunk from rfile buffer and return it.
|
||||
"""Read a chunk from ``rfile`` buffer and return it.
|
||||
|
||||
Args:
|
||||
size (int): amount of data to read
|
||||
|
||||
Returns:
|
||||
bytes: Chunk from rfile, limited by size if specified.
|
||||
:param int size: amount of data to read
|
||||
|
||||
:returns: chunk from ``rfile``, limited by size if specified
|
||||
:rtype: bytes
|
||||
"""
|
||||
data = self.rfile.read(size)
|
||||
self.bytes_read += len(data)
|
||||
|
@ -281,14 +284,12 @@ class SizeCheckWrapper:
|
|||
return data
|
||||
|
||||
def readline(self, size=None):
|
||||
"""Read a single line from rfile buffer and return it.
|
||||
"""Read a single line from ``rfile`` buffer and return it.
|
||||
|
||||
Args:
|
||||
size (int): minimum amount of data to read
|
||||
|
||||
Returns:
|
||||
bytes: One line from rfile.
|
||||
:param int size: minimum amount of data to read
|
||||
|
||||
:returns: one line from ``rfile``
|
||||
:rtype: bytes
|
||||
"""
|
||||
if size is not None:
|
||||
data = self.rfile.readline(size)
|
||||
|
@ -309,14 +310,12 @@ class SizeCheckWrapper:
|
|||
return EMPTY.join(res)
|
||||
|
||||
def readlines(self, sizehint=0):
|
||||
"""Read all lines from rfile buffer and return them.
|
||||
"""Read all lines from ``rfile`` buffer and return them.
|
||||
|
||||
Args:
|
||||
sizehint (int): hint of minimum amount of data to read
|
||||
|
||||
Returns:
|
||||
list[bytes]: Lines of bytes read from rfile.
|
||||
:param int sizehint: hint of minimum amount of data to read
|
||||
|
||||
:returns: lines of bytes read from ``rfile``
|
||||
:rtype: list[bytes]
|
||||
"""
|
||||
# Shamelessly stolen from StringIO
|
||||
total = 0
|
||||
|
@ -331,7 +330,7 @@ class SizeCheckWrapper:
|
|||
return lines
|
||||
|
||||
def close(self):
|
||||
"""Release resources allocated for rfile."""
|
||||
"""Release resources allocated for ``rfile``."""
|
||||
self.rfile.close()
|
||||
|
||||
def __iter__(self):
|
||||
|
@ -349,28 +348,24 @@ class SizeCheckWrapper:
|
|||
|
||||
|
||||
class KnownLengthRFile:
|
||||
"""Wraps a file-like object, returning an empty string when exhausted."""
|
||||
"""Wraps a file-like object, returning an empty string when exhausted.
|
||||
|
||||
:param rfile: ``file`` of a known size
|
||||
:param int content_length: length of the file being read
|
||||
"""
|
||||
|
||||
def __init__(self, rfile, content_length):
|
||||
"""Initialize KnownLengthRFile instance.
|
||||
|
||||
Args:
|
||||
rfile (file): file of a known size
|
||||
content_length (int): length of the file being read
|
||||
|
||||
"""
|
||||
"""Initialize KnownLengthRFile instance."""
|
||||
self.rfile = rfile
|
||||
self.remaining = content_length
|
||||
|
||||
def read(self, size=None):
|
||||
"""Read a chunk from rfile buffer and return it.
|
||||
"""Read a chunk from ``rfile`` buffer and return it.
|
||||
|
||||
Args:
|
||||
size (int): amount of data to read
|
||||
|
||||
Returns:
|
||||
bytes: Chunk from rfile, limited by size if specified.
|
||||
:param int size: amount of data to read
|
||||
|
||||
:rtype: bytes
|
||||
:returns: chunk from ``rfile``, limited by size if specified
|
||||
"""
|
||||
if self.remaining == 0:
|
||||
return b''
|
||||
|
@ -384,14 +379,12 @@ class KnownLengthRFile:
|
|||
return data
|
||||
|
||||
def readline(self, size=None):
|
||||
"""Read a single line from rfile buffer and return it.
|
||||
"""Read a single line from ``rfile`` buffer and return it.
|
||||
|
||||
Args:
|
||||
size (int): minimum amount of data to read
|
||||
|
||||
Returns:
|
||||
bytes: One line from rfile.
|
||||
:param int size: minimum amount of data to read
|
||||
|
||||
:returns: one line from ``rfile``
|
||||
:rtype: bytes
|
||||
"""
|
||||
if self.remaining == 0:
|
||||
return b''
|
||||
|
@ -405,14 +398,12 @@ class KnownLengthRFile:
|
|||
return data
|
||||
|
||||
def readlines(self, sizehint=0):
|
||||
"""Read all lines from rfile buffer and return them.
|
||||
"""Read all lines from ``rfile`` buffer and return them.
|
||||
|
||||
Args:
|
||||
sizehint (int): hint of minimum amount of data to read
|
||||
|
||||
Returns:
|
||||
list[bytes]: Lines of bytes read from rfile.
|
||||
:param int sizehint: hint of minimum amount of data to read
|
||||
|
||||
:returns: lines of bytes read from ``rfile``
|
||||
:rtype: list[bytes]
|
||||
"""
|
||||
# Shamelessly stolen from StringIO
|
||||
total = 0
|
||||
|
@ -427,7 +418,7 @@ class KnownLengthRFile:
|
|||
return lines
|
||||
|
||||
def close(self):
|
||||
"""Release resources allocated for rfile."""
|
||||
"""Release resources allocated for ``rfile``."""
|
||||
self.rfile.close()
|
||||
|
||||
def __iter__(self):
|
||||
|
@ -449,16 +440,14 @@ class ChunkedRFile:
|
|||
This class is intended to provide a conforming wsgi.input value for
|
||||
request entities that have been encoded with the 'chunked' transfer
|
||||
encoding.
|
||||
|
||||
:param rfile: file encoded with the 'chunked' transfer encoding
|
||||
:param int maxlen: maximum length of the file being read
|
||||
:param int bufsize: size of the buffer used to read the file
|
||||
"""
|
||||
|
||||
def __init__(self, rfile, maxlen, bufsize=8192):
|
||||
"""Initialize ChunkedRFile instance.
|
||||
|
||||
Args:
|
||||
rfile (file): file encoded with the 'chunked' transfer encoding
|
||||
maxlen (int): maximum length of the file being read
|
||||
bufsize (int): size of the buffer used to read the file
|
||||
"""
|
||||
"""Initialize ChunkedRFile instance."""
|
||||
self.rfile = rfile
|
||||
self.maxlen = maxlen
|
||||
self.bytes_read = 0
|
||||
|
@ -484,7 +473,10 @@ class ChunkedRFile:
|
|||
chunk_size = line.pop(0)
|
||||
chunk_size = int(chunk_size, 16)
|
||||
except ValueError:
|
||||
raise ValueError('Bad chunked transfer size: ' + repr(chunk_size))
|
||||
raise ValueError(
|
||||
'Bad chunked transfer size: {chunk_size!r}'.
|
||||
format(chunk_size=chunk_size),
|
||||
)
|
||||
|
||||
if chunk_size <= 0:
|
||||
self.closed = True
|
||||
|
@ -507,14 +499,12 @@ class ChunkedRFile:
|
|||
)
|
||||
|
||||
def read(self, size=None):
|
||||
"""Read a chunk from rfile buffer and return it.
|
||||
"""Read a chunk from ``rfile`` buffer and return it.
|
||||
|
||||
Args:
|
||||
size (int): amount of data to read
|
||||
|
||||
Returns:
|
||||
bytes: Chunk from rfile, limited by size if specified.
|
||||
:param int size: amount of data to read
|
||||
|
||||
:returns: chunk from ``rfile``, limited by size if specified
|
||||
:rtype: bytes
|
||||
"""
|
||||
data = EMPTY
|
||||
|
||||
|
@ -540,14 +530,12 @@ class ChunkedRFile:
|
|||
self.buffer = EMPTY
|
||||
|
||||
def readline(self, size=None):
|
||||
"""Read a single line from rfile buffer and return it.
|
||||
"""Read a single line from ``rfile`` buffer and return it.
|
||||
|
||||
Args:
|
||||
size (int): minimum amount of data to read
|
||||
|
||||
Returns:
|
||||
bytes: One line from rfile.
|
||||
:param int size: minimum amount of data to read
|
||||
|
||||
:returns: one line from ``rfile``
|
||||
:rtype: bytes
|
||||
"""
|
||||
data = EMPTY
|
||||
|
||||
|
@ -583,14 +571,12 @@ class ChunkedRFile:
|
|||
self.buffer = self.buffer[newline_pos:]
|
||||
|
||||
def readlines(self, sizehint=0):
|
||||
"""Read all lines from rfile buffer and return them.
|
||||
"""Read all lines from ``rfile`` buffer and return them.
|
||||
|
||||
Args:
|
||||
sizehint (int): hint of minimum amount of data to read
|
||||
|
||||
Returns:
|
||||
list[bytes]: Lines of bytes read from rfile.
|
||||
:param int sizehint: hint of minimum amount of data to read
|
||||
|
||||
:returns: lines of bytes read from ``rfile``
|
||||
:rtype: list[bytes]
|
||||
"""
|
||||
# Shamelessly stolen from StringIO
|
||||
total = 0
|
||||
|
@ -635,7 +621,7 @@ class ChunkedRFile:
|
|||
yield line
|
||||
|
||||
def close(self):
|
||||
"""Release resources allocated for rfile."""
|
||||
"""Release resources allocated for ``rfile``."""
|
||||
self.rfile.close()
|
||||
|
||||
|
||||
|
@ -744,7 +730,7 @@ class HTTPRequest:
|
|||
|
||||
self.ready = True
|
||||
|
||||
def read_request_line(self):
|
||||
def read_request_line(self): # noqa: C901 # FIXME
|
||||
"""Read and parse first line of the HTTP request.
|
||||
|
||||
Returns:
|
||||
|
@ -845,7 +831,7 @@ class HTTPRequest:
|
|||
|
||||
# `urlsplit()` above parses "example.com:3128" as path part of URI.
|
||||
# this is a workaround, which makes it detect netloc correctly
|
||||
uri_split = urllib.parse.urlsplit(b'//' + uri)
|
||||
uri_split = urllib.parse.urlsplit(b''.join((b'//', uri)))
|
||||
_scheme, _authority, _path, _qs, _fragment = uri_split
|
||||
_port = EMPTY
|
||||
try:
|
||||
|
@ -975,8 +961,14 @@ class HTTPRequest:
|
|||
|
||||
return True
|
||||
|
||||
def read_request_headers(self):
|
||||
"""Read self.rfile into self.inheaders. Return success."""
|
||||
def read_request_headers(self): # noqa: C901 # FIXME
|
||||
"""Read ``self.rfile`` into ``self.inheaders``.
|
||||
|
||||
Ref: :py:attr:`self.inheaders <HTTPRequest.outheaders>`.
|
||||
|
||||
:returns: success status
|
||||
:rtype: bool
|
||||
"""
|
||||
# then all the http headers
|
||||
try:
|
||||
self.header_reader(self.rfile, self.inheaders)
|
||||
|
@ -1054,8 +1046,10 @@ class HTTPRequest:
|
|||
# Don't use simple_response here, because it emits headers
|
||||
# we don't want. See
|
||||
# https://github.com/cherrypy/cherrypy/issues/951
|
||||
msg = self.server.protocol.encode('ascii')
|
||||
msg += b' 100 Continue\r\n\r\n'
|
||||
msg = b''.join((
|
||||
self.server.protocol.encode('ascii'), SPACE, b'100 Continue',
|
||||
CRLF, CRLF,
|
||||
))
|
||||
try:
|
||||
self.conn.wfile.write(msg)
|
||||
except socket.error as ex:
|
||||
|
@ -1138,10 +1132,11 @@ class HTTPRequest:
|
|||
else:
|
||||
self.conn.wfile.write(chunk)
|
||||
|
||||
def send_headers(self):
|
||||
def send_headers(self): # noqa: C901 # FIXME
|
||||
"""Assert, process, and send the HTTP response message-headers.
|
||||
|
||||
You must set self.status, and self.outheaders before calling this.
|
||||
You must set ``self.status``, and :py:attr:`self.outheaders
|
||||
<HTTPRequest.outheaders>` before calling this.
|
||||
"""
|
||||
hkeys = [key.lower() for key, value in self.outheaders]
|
||||
status = int(self.status[:3])
|
||||
|
@ -1168,6 +1163,12 @@ class HTTPRequest:
|
|||
# Closing the conn is the only way to determine len.
|
||||
self.close_connection = True
|
||||
|
||||
# Override the decision to not close the connection if the connection
|
||||
# manager doesn't have space for it.
|
||||
if not self.close_connection:
|
||||
can_keep = self.server.can_add_keepalive_connection
|
||||
self.close_connection = not can_keep
|
||||
|
||||
if b'connection' not in hkeys:
|
||||
if self.response_protocol == 'HTTP/1.1':
|
||||
# Both server and client are HTTP/1.1 or better
|
||||
|
@ -1178,6 +1179,14 @@ class HTTPRequest:
|
|||
if not self.close_connection:
|
||||
self.outheaders.append((b'Connection', b'Keep-Alive'))
|
||||
|
||||
if (b'Connection', b'Keep-Alive') in self.outheaders:
|
||||
self.outheaders.append((
|
||||
b'Keep-Alive',
|
||||
u'timeout={connection_timeout}'.
|
||||
format(connection_timeout=self.server.timeout).
|
||||
encode('ISO-8859-1'),
|
||||
))
|
||||
|
||||
if (not self.close_connection) and (not self.chunked_read):
|
||||
# Read any remaining request body data on the socket.
|
||||
# "If an origin server receives a request that does not include an
|
||||
|
@ -1228,9 +1237,7 @@ class HTTPConnection:
|
|||
peercreds_resolve_enabled = False
|
||||
|
||||
# Fields set by ConnectionManager.
|
||||
closeable = False
|
||||
last_used = None
|
||||
ready_with_data = False
|
||||
|
||||
def __init__(self, server, sock, makefile=MakeFile):
|
||||
"""Initialize HTTPConnection instance.
|
||||
|
@ -1259,7 +1266,7 @@ class HTTPConnection:
|
|||
lru_cache(maxsize=1)(self.get_peer_creds)
|
||||
)
|
||||
|
||||
def communicate(self):
|
||||
def communicate(self): # noqa: C901 # FIXME
|
||||
"""Read each request and respond appropriately.
|
||||
|
||||
Returns true if the connection should be kept open.
|
||||
|
@ -1351,6 +1358,9 @@ class HTTPConnection:
|
|||
|
||||
if not self.linger:
|
||||
self._close_kernel_socket()
|
||||
# close the socket file descriptor
|
||||
# (will be closed in the OS if there is no
|
||||
# other reference to the underlying socket)
|
||||
self.socket.close()
|
||||
else:
|
||||
# On the other hand, sometimes we want to hang around for a bit
|
||||
|
@ -1426,12 +1436,12 @@ class HTTPConnection:
|
|||
return gid
|
||||
|
||||
def resolve_peer_creds(self): # LRU cached on per-instance basis
|
||||
"""Return the username and group tuple of the peercreds if available.
|
||||
"""Look up the username and group tuple of the ``PEERCREDS``.
|
||||
|
||||
Raises:
|
||||
NotImplementedError: in case of unsupported OS
|
||||
RuntimeError: in case of UID/GID lookup unsupported or disabled
|
||||
:returns: the username and group tuple of the ``PEERCREDS``
|
||||
|
||||
:raises NotImplementedError: if the OS is unsupported
|
||||
:raises RuntimeError: if UID/GID lookup is unsupported or disabled
|
||||
"""
|
||||
if not IS_UID_GID_RESOLVABLE:
|
||||
raise NotImplementedError(
|
||||
|
@ -1462,18 +1472,20 @@ class HTTPConnection:
|
|||
return group
|
||||
|
||||
def _close_kernel_socket(self):
|
||||
"""Close kernel socket in outdated Python versions.
|
||||
"""Terminate the connection at the transport level."""
|
||||
# Honor ``sock_shutdown`` for PyOpenSSL connections.
|
||||
shutdown = getattr(
|
||||
self.socket, 'sock_shutdown',
|
||||
self.socket.shutdown,
|
||||
)
|
||||
|
||||
On old Python versions,
|
||||
Python's socket module does NOT call close on the kernel
|
||||
socket when you call socket.close(). We do so manually here
|
||||
because we want this server to send a FIN TCP segment
|
||||
immediately. Note this must be called *before* calling
|
||||
socket.close(), because the latter drops its reference to
|
||||
the kernel socket.
|
||||
"""
|
||||
if six.PY2 and hasattr(self.socket, '_sock'):
|
||||
self.socket._sock.close()
|
||||
try:
|
||||
shutdown(socket.SHUT_RDWR) # actually send a TCP FIN
|
||||
except errors.acceptable_sock_shutdown_exceptions:
|
||||
pass
|
||||
except socket.error as e:
|
||||
if e.errno not in errors.acceptable_sock_shutdown_error_codes:
|
||||
raise
|
||||
|
||||
|
||||
class HTTPServer:
|
||||
|
@ -1515,7 +1527,12 @@ class HTTPServer:
|
|||
timeout = 10
|
||||
"""The timeout in seconds for accepted connections (default 10)."""
|
||||
|
||||
version = 'Cheroot/' + __version__
|
||||
expiration_interval = 0.5
|
||||
"""The interval, in seconds, at which the server checks for
|
||||
expired connections (default 0.5).
|
||||
"""
|
||||
|
||||
version = 'Cheroot/{version!s}'.format(version=__version__)
|
||||
"""A version string for the HTTPServer."""
|
||||
|
||||
software = None
|
||||
|
@ -1540,16 +1557,23 @@ class HTTPServer:
|
|||
"""The class to use for handling HTTP connections."""
|
||||
|
||||
ssl_adapter = None
|
||||
"""An instance of ssl.Adapter (or a subclass).
|
||||
"""An instance of ``ssl.Adapter`` (or a subclass).
|
||||
|
||||
You must have the corresponding SSL driver library installed.
|
||||
Ref: :py:class:`ssl.Adapter <cheroot.ssl.Adapter>`.
|
||||
|
||||
You must have the corresponding TLS driver library installed.
|
||||
"""
|
||||
|
||||
peercreds_enabled = False
|
||||
"""If True, peer cred lookup can be performed via UNIX domain socket."""
|
||||
"""
|
||||
If :py:data:`True`, peer creds will be looked up via UNIX domain socket.
|
||||
"""
|
||||
|
||||
peercreds_resolve_enabled = False
|
||||
"""If True, username/group will be looked up in the OS from peercreds."""
|
||||
"""
|
||||
If :py:data:`True`, username/group will be looked up in the OS from
|
||||
``PEERCREDS``-provided IDs.
|
||||
"""
|
||||
|
||||
keep_alive_conn_limit = 10
|
||||
"""The maximum number of waiting keep-alive connections that will be kept open.
|
||||
|
@ -1577,7 +1601,6 @@ class HTTPServer:
|
|||
self.requests = threadpool.ThreadPool(
|
||||
self, min=minthreads or 1, max=maxthreads,
|
||||
)
|
||||
self.connections = connections.ConnectionManager(self)
|
||||
|
||||
if not server_name:
|
||||
server_name = self.version
|
||||
|
@ -1603,25 +1626,29 @@ class HTTPServer:
|
|||
'Threads Idle': lambda s: getattr(self.requests, 'idle', None),
|
||||
'Socket Errors': 0,
|
||||
'Requests': lambda s: (not s['Enabled']) and -1 or sum(
|
||||
[w['Requests'](w) for w in s['Worker Threads'].values()], 0,
|
||||
(w['Requests'](w) for w in s['Worker Threads'].values()), 0,
|
||||
),
|
||||
'Bytes Read': lambda s: (not s['Enabled']) and -1 or sum(
|
||||
[w['Bytes Read'](w) for w in s['Worker Threads'].values()], 0,
|
||||
(w['Bytes Read'](w) for w in s['Worker Threads'].values()), 0,
|
||||
),
|
||||
'Bytes Written': lambda s: (not s['Enabled']) and -1 or sum(
|
||||
[w['Bytes Written'](w) for w in s['Worker Threads'].values()],
|
||||
(w['Bytes Written'](w) for w in s['Worker Threads'].values()),
|
||||
0,
|
||||
),
|
||||
'Work Time': lambda s: (not s['Enabled']) and -1 or sum(
|
||||
[w['Work Time'](w) for w in s['Worker Threads'].values()], 0,
|
||||
(w['Work Time'](w) for w in s['Worker Threads'].values()), 0,
|
||||
),
|
||||
'Read Throughput': lambda s: (not s['Enabled']) and -1 or sum(
|
||||
[w['Bytes Read'](w) / (w['Work Time'](w) or 1e-6)
|
||||
for w in s['Worker Threads'].values()], 0,
|
||||
(
|
||||
w['Bytes Read'](w) / (w['Work Time'](w) or 1e-6)
|
||||
for w in s['Worker Threads'].values()
|
||||
), 0,
|
||||
),
|
||||
'Write Throughput': lambda s: (not s['Enabled']) and -1 or sum(
|
||||
[w['Bytes Written'](w) / (w['Work Time'](w) or 1e-6)
|
||||
for w in s['Worker Threads'].values()], 0,
|
||||
(
|
||||
w['Bytes Written'](w) / (w['Work Time'](w) or 1e-6)
|
||||
for w in s['Worker Threads'].values()
|
||||
), 0,
|
||||
),
|
||||
'Worker Threads': {},
|
||||
}
|
||||
|
@ -1645,17 +1672,27 @@ class HTTPServer:
|
|||
def bind_addr(self):
|
||||
"""Return the interface on which to listen for connections.
|
||||
|
||||
For TCP sockets, a (host, port) tuple. Host values may be any IPv4
|
||||
or IPv6 address, or any valid hostname. The string 'localhost' is a
|
||||
synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
|
||||
The string '0.0.0.0' is a special IPv4 entry meaning "any active
|
||||
interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
|
||||
IPv6. The empty string or None are not allowed.
|
||||
For TCP sockets, a (host, port) tuple. Host values may be any
|
||||
:term:`IPv4` or :term:`IPv6` address, or any valid hostname.
|
||||
The string 'localhost' is a synonym for '127.0.0.1' (or '::1',
|
||||
if your hosts file prefers :term:`IPv6`).
|
||||
The string '0.0.0.0' is a special :term:`IPv4` entry meaning
|
||||
"any active interface" (INADDR_ANY), and '::' is the similar
|
||||
IN6ADDR_ANY for :term:`IPv6`.
|
||||
The empty string or :py:data:`None` are not allowed.
|
||||
|
||||
For UNIX sockets, supply the filename as a string.
|
||||
For UNIX sockets, supply the file name as a string.
|
||||
|
||||
Systemd socket activation is automatic and doesn't require tempering
|
||||
with this variable.
|
||||
|
||||
.. glossary::
|
||||
|
||||
:abbr:`IPv4 (Internet Protocol version 4)`
|
||||
Internet Protocol version 4
|
||||
|
||||
:abbr:`IPv6 (Internet Protocol version 6)`
|
||||
Internet Protocol version 6
|
||||
"""
|
||||
return self._bind_addr
|
||||
|
||||
|
@ -1695,7 +1732,7 @@ class HTTPServer:
|
|||
self.stop()
|
||||
raise
|
||||
|
||||
def prepare(self):
|
||||
def prepare(self): # noqa: C901 # FIXME
|
||||
"""Prepare server to serving requests.
|
||||
|
||||
It binds a socket's port, setups the socket to ``listen()`` and does
|
||||
|
@ -1757,6 +1794,9 @@ class HTTPServer:
|
|||
self.socket.settimeout(1)
|
||||
self.socket.listen(self.request_queue_size)
|
||||
|
||||
# must not be accessed once stop() has been called
|
||||
self._connections = connections.ConnectionManager(self)
|
||||
|
||||
# Create worker threads
|
||||
self.requests.start()
|
||||
|
||||
|
@ -1765,23 +1805,24 @@ class HTTPServer:
|
|||
|
||||
def serve(self):
|
||||
"""Serve requests, after invoking :func:`prepare()`."""
|
||||
while self.ready:
|
||||
while self.ready and not self.interrupt:
|
||||
try:
|
||||
self.tick()
|
||||
self._connections.run(self.expiration_interval)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception:
|
||||
self.error_log(
|
||||
'Error in HTTPServer.tick', level=logging.ERROR,
|
||||
'Error in HTTPServer.serve', level=logging.ERROR,
|
||||
traceback=True,
|
||||
)
|
||||
|
||||
# raise exceptions reported by any worker threads,
|
||||
# such that the exception is raised from the serve() thread.
|
||||
if self.interrupt:
|
||||
while self._stopping_for_interrupt:
|
||||
time.sleep(0.1)
|
||||
if self.interrupt:
|
||||
while self.interrupt is True:
|
||||
# Wait for self.stop() to complete. See _set_interrupt.
|
||||
time.sleep(0.1)
|
||||
if self.interrupt:
|
||||
raise self.interrupt
|
||||
raise self.interrupt
|
||||
|
||||
def start(self):
|
||||
"""Run the server forever.
|
||||
|
@ -1795,6 +1836,31 @@ class HTTPServer:
|
|||
self.prepare()
|
||||
self.serve()
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _run_in_thread(self):
|
||||
"""Context manager for running this server in a thread."""
|
||||
self.prepare()
|
||||
thread = threading.Thread(target=self.serve)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
try:
|
||||
yield thread
|
||||
finally:
|
||||
self.stop()
|
||||
|
||||
@property
|
||||
def can_add_keepalive_connection(self):
|
||||
"""Flag whether it is allowed to add a new keep-alive connection."""
|
||||
return self.ready and self._connections.can_add_keepalive_connection
|
||||
|
||||
def put_conn(self, conn):
|
||||
"""Put an idle connection back into the ConnectionManager."""
|
||||
if self.ready:
|
||||
self._connections.put(conn)
|
||||
else:
|
||||
# server is shutting down, just close it
|
||||
conn.close()
|
||||
|
||||
def error_log(self, msg='', level=20, traceback=False):
|
||||
"""Write error message to log.
|
||||
|
||||
|
@ -1804,7 +1870,7 @@ class HTTPServer:
|
|||
traceback (bool): add traceback to output or not
|
||||
"""
|
||||
# Override this in subclasses as desired
|
||||
sys.stderr.write(msg + '\n')
|
||||
sys.stderr.write('{msg!s}\n'.format(msg=msg))
|
||||
sys.stderr.flush()
|
||||
if traceback:
|
||||
tblines = traceback_.format_exc()
|
||||
|
@ -1822,7 +1888,7 @@ class HTTPServer:
|
|||
self.bind_addr = self.resolve_real_bind_addr(sock)
|
||||
return sock
|
||||
|
||||
def bind_unix_socket(self, bind_addr):
|
||||
def bind_unix_socket(self, bind_addr): # noqa: C901 # FIXME
|
||||
"""Create (or recreate) a UNIX socket object."""
|
||||
if IS_WINDOWS:
|
||||
"""
|
||||
|
@ -1965,7 +2031,7 @@ class HTTPServer:
|
|||
|
||||
@staticmethod
|
||||
def resolve_real_bind_addr(socket_):
|
||||
"""Retrieve actual bind addr from bound socket."""
|
||||
"""Retrieve actual bind address from bound socket."""
|
||||
# FIXME: keep requested bind_addr separate real bound_addr (port
|
||||
# is different in case of ephemeral port 0)
|
||||
bind_addr = socket_.getsockname()
|
||||
|
@ -1985,40 +2051,49 @@ class HTTPServer:
|
|||
|
||||
return bind_addr
|
||||
|
||||
def tick(self):
|
||||
"""Accept a new connection and put it on the Queue."""
|
||||
if not self.ready:
|
||||
return
|
||||
|
||||
conn = self.connections.get_conn(self.socket)
|
||||
if conn:
|
||||
try:
|
||||
self.requests.put(conn)
|
||||
except queue.Full:
|
||||
# Just drop the conn. TODO: write 503 back?
|
||||
conn.close()
|
||||
|
||||
self.connections.expire()
|
||||
def process_conn(self, conn):
|
||||
"""Process an incoming HTTPConnection."""
|
||||
try:
|
||||
self.requests.put(conn)
|
||||
except queue.Full:
|
||||
# Just drop the conn. TODO: write 503 back?
|
||||
conn.close()
|
||||
|
||||
@property
|
||||
def interrupt(self):
|
||||
"""Flag interrupt of the server."""
|
||||
return self._interrupt
|
||||
|
||||
@property
|
||||
def _stopping_for_interrupt(self):
|
||||
"""Return whether the server is responding to an interrupt."""
|
||||
return self._interrupt is _STOPPING_FOR_INTERRUPT
|
||||
|
||||
@interrupt.setter
|
||||
def interrupt(self, interrupt):
|
||||
"""Perform the shutdown of this server and save the exception."""
|
||||
self._interrupt = True
|
||||
"""Perform the shutdown of this server and save the exception.
|
||||
|
||||
Typically invoked by a worker thread in
|
||||
:py:mod:`~cheroot.workers.threadpool`, the exception is raised
|
||||
from the thread running :py:meth:`serve` once :py:meth:`stop`
|
||||
has completed.
|
||||
"""
|
||||
self._interrupt = _STOPPING_FOR_INTERRUPT
|
||||
self.stop()
|
||||
self._interrupt = interrupt
|
||||
|
||||
def stop(self):
|
||||
def stop(self): # noqa: C901 # FIXME
|
||||
"""Gracefully shutdown a server that is serving forever."""
|
||||
if not self.ready:
|
||||
return # already stopped
|
||||
|
||||
self.ready = False
|
||||
if self._start_time is not None:
|
||||
self._run_time += (time.time() - self._start_time)
|
||||
self._start_time = None
|
||||
|
||||
self._connections.stop()
|
||||
|
||||
sock = getattr(self, 'socket', None)
|
||||
if sock:
|
||||
if not isinstance(
|
||||
|
@ -2060,7 +2135,7 @@ class HTTPServer:
|
|||
sock.close()
|
||||
self.socket = None
|
||||
|
||||
self.connections.close()
|
||||
self._connections.close()
|
||||
self.requests.stop(self.shutdown_timeout)
|
||||
|
||||
|
||||
|
@ -2108,7 +2183,9 @@ def get_ssl_adapter_class(name='builtin'):
|
|||
try:
|
||||
adapter = getattr(mod, attr_name)
|
||||
except AttributeError:
|
||||
raise AttributeError("'%s' object has no attribute '%s'"
|
||||
% (mod_path, attr_name))
|
||||
raise AttributeError(
|
||||
"'%s' object has no attribute '%s'"
|
||||
% (mod_path, attr_name),
|
||||
)
|
||||
|
||||
return adapter
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue