mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Refactor server opts into a central location
This commit is contained in:
parent
3ffc0f5e8c
commit
c00057b9fc
@ -262,7 +262,7 @@ class HTTPPair(object):
|
|||||||
def __init__(self, conn, handle_request):
|
def __init__(self, conn, handle_request):
|
||||||
self.conn = conn
|
self.conn = conn
|
||||||
self.server_loop = conn.server_loop
|
self.server_loop = conn.server_loop
|
||||||
self.max_header_line_size = self.server_loop.max_header_line_size
|
self.max_header_line_size = self.server_loop.opts.max_header_line_size
|
||||||
self.scheme = 'http' if self.server_loop.ssl_context is None else 'https'
|
self.scheme = 'http' if self.server_loop.ssl_context is None else 'https'
|
||||||
self.inheaders = MultiDict()
|
self.inheaders = MultiDict()
|
||||||
self.outheaders = MultiDict()
|
self.outheaders = MultiDict()
|
||||||
@ -393,11 +393,11 @@ class HTTPPair(object):
|
|||||||
self.simple_response(httplib.BAD_REQUEST, as_unicode(e))
|
self.simple_response(httplib.BAD_REQUEST, as_unicode(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.request_content_length > self.server_loop.max_request_body_size:
|
if self.request_content_length > self.server_loop.opts.max_request_body_size:
|
||||||
self.simple_response(
|
self.simple_response(
|
||||||
httplib.REQUEST_ENTITY_TOO_LARGE,
|
httplib.REQUEST_ENTITY_TOO_LARGE,
|
||||||
"The entity sent with the request exceeds the maximum "
|
"The entity sent with the request exceeds the maximum "
|
||||||
"allowed bytes (%d)." % self.server_loop.max_request_body_size)
|
"allowed bytes (%d)." % self.server_loop.opts.max_request_body_size)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Persistent connection support
|
# Persistent connection support
|
||||||
@ -470,7 +470,7 @@ class HTTPPair(object):
|
|||||||
|
|
||||||
def response(self):
|
def response(self):
|
||||||
if self.chunked_read:
|
if self.chunked_read:
|
||||||
self.input_reader = ChunkedReader(self.conn.socket_file, self.server_loop.max_request_body_size)
|
self.input_reader = ChunkedReader(self.conn.socket_file, self.server_loop.opts.max_request_body_size)
|
||||||
else:
|
else:
|
||||||
self.input_reader = FixedSizeReader(self.conn.socket_file, self.request_content_length)
|
self.input_reader = FixedSizeReader(self.conn.socket_file, self.request_content_length)
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ from io import DEFAULT_BUFFER_SIZE, BytesIO
|
|||||||
|
|
||||||
from calibre.srv.errors import NonHTTPConnRequest, MaxSizeExceeded
|
from calibre.srv.errors import NonHTTPConnRequest, MaxSizeExceeded
|
||||||
from calibre.srv.http import http_communicate
|
from calibre.srv.http import http_communicate
|
||||||
|
from calibre.srv.opts import defaults
|
||||||
from calibre.utils.socket_inheritance import set_socket_inherit
|
from calibre.utils.socket_inheritance import set_socket_inherit
|
||||||
from calibre.utils.logging import ThreadSafeLog
|
from calibre.utils.logging import ThreadSafeLog
|
||||||
|
|
||||||
@ -517,50 +518,17 @@ class ThreadPool(object):
|
|||||||
|
|
||||||
class ServerLoop(object):
|
class ServerLoop(object):
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(
|
||||||
bind_address=('localhost', 8080),
|
self,
|
||||||
|
bind_address=('localhost', 8080),
|
||||||
http_handler=None,
|
http_handler=None,
|
||||||
nonhttp_handler=None,
|
nonhttp_handler=None,
|
||||||
|
opts=None,
|
||||||
ssl_certfile=None,
|
# A calibre logging object. If None a default log that logs to
|
||||||
ssl_keyfile=None,
|
# stdout is used
|
||||||
|
log=None
|
||||||
# Max. queued connections for socket.accept()
|
|
||||||
request_queue_size=5,
|
|
||||||
|
|
||||||
# Timeout in seconds for accepted connections
|
|
||||||
timeout=10,
|
|
||||||
|
|
||||||
# Total time in seconds to wait for worker threads to cleanly
|
|
||||||
# exit
|
|
||||||
shutdown_timeout=5,
|
|
||||||
|
|
||||||
# Minimum number of connection handling threads
|
|
||||||
min_threads=10,
|
|
||||||
# Maximum number of connection handling threads (beyond this
|
|
||||||
# number of connections will be dropped)
|
|
||||||
max_threads=500,
|
|
||||||
|
|
||||||
# Allow socket pre-allocation, for example, with systemd
|
|
||||||
# socket activation
|
|
||||||
allow_socket_preallocation=True,
|
|
||||||
|
|
||||||
# Max. size of single header
|
|
||||||
max_header_line_size=8192, # 8 KB
|
|
||||||
|
|
||||||
# Max. size of a request
|
|
||||||
max_request_body_size=500 * 1024 * 1024,
|
|
||||||
|
|
||||||
# no_delay turns on TCP_NODELAY which decreases latency at the cost of
|
|
||||||
# worse overall performance when sending multiple small packets. It
|
|
||||||
# prevents the TCP stack from aggregating multiple small TCP packets.
|
|
||||||
no_delay=True,
|
|
||||||
|
|
||||||
# A calibre logging object. If None a default log that logs to
|
|
||||||
# stdout is used
|
|
||||||
log=None
|
|
||||||
):
|
):
|
||||||
|
self.opts = opts or defaults
|
||||||
if http_handler is None:
|
if http_handler is None:
|
||||||
def aborth(*args, **kwargs):
|
def aborth(*args, **kwargs):
|
||||||
raise NonHTTPConnRequest()
|
raise NonHTTPConnRequest()
|
||||||
@ -571,13 +539,6 @@ class ServerLoop(object):
|
|||||||
raise ValueError('You must specify at least one protocol handler')
|
raise ValueError('You must specify at least one protocol handler')
|
||||||
self.log = log or ThreadSafeLog(level=ThreadSafeLog.DEBUG)
|
self.log = log or ThreadSafeLog(level=ThreadSafeLog.DEBUG)
|
||||||
self.gso_cache, self.gso_lock = {}, Lock()
|
self.gso_cache, self.gso_lock = {}, Lock()
|
||||||
self.allow_socket_preallocation = allow_socket_preallocation
|
|
||||||
self.no_delay = no_delay
|
|
||||||
self.request_queue_size = request_queue_size
|
|
||||||
self.timeout = timeout
|
|
||||||
self.max_header_line_size = max_header_line_size
|
|
||||||
self.max_request_body_size = max_request_body_size
|
|
||||||
self.shutdown_timeout = shutdown_timeout
|
|
||||||
ba = bind_address
|
ba = bind_address
|
||||||
if not isinstance(ba, basestring):
|
if not isinstance(ba, basestring):
|
||||||
ba = tuple(ba)
|
ba = tuple(ba)
|
||||||
@ -586,12 +547,12 @@ class ServerLoop(object):
|
|||||||
ba = ('0.0.0.0', ba[1])
|
ba = ('0.0.0.0', ba[1])
|
||||||
self.bind_address = ba
|
self.bind_address = ba
|
||||||
self.ssl_context = None
|
self.ssl_context = None
|
||||||
if ssl_certfile is not None and ssl_keyfile is not None:
|
if self.opts.ssl_certfile is not None and self.opts.ssl_keyfile is not None:
|
||||||
self.ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
self.ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||||
self.ssl_context.load_cert_chain(certfile=ssl_certfile, keyfile=ssl_keyfile)
|
self.ssl_context.load_cert_chain(certfile=self.opts.ssl_certfile, keyfile=self.opts.ssl_keyfile)
|
||||||
|
|
||||||
self.pre_activated_socket = None
|
self.pre_activated_socket = None
|
||||||
if self.allow_socket_preallocation:
|
if self.opts.allow_socket_preallocation:
|
||||||
from calibre.srv.pre_activated import pre_activated_socket
|
from calibre.srv.pre_activated import pre_activated_socket
|
||||||
self.pre_activated_socket = pre_activated_socket()
|
self.pre_activated_socket = pre_activated_socket()
|
||||||
if self.pre_activated_socket is not None:
|
if self.pre_activated_socket is not None:
|
||||||
@ -599,7 +560,7 @@ class ServerLoop(object):
|
|||||||
self.bind_address = self.pre_activated_socket.getsockname()
|
self.bind_address = self.pre_activated_socket.getsockname()
|
||||||
|
|
||||||
self.ready = False
|
self.ready = False
|
||||||
self.requests = ThreadPool(self, min_threads=min_threads, max_threads=max_threads)
|
self.requests = ThreadPool(self, min_threads=self.opts.min_threads, max_threads=self.opts.max_threads)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s(%r)" % (self.__class__.__name__, self.bind_address)
|
return "%s(%r)" % (self.__class__.__name__, self.bind_address)
|
||||||
@ -666,7 +627,7 @@ class ServerLoop(object):
|
|||||||
|
|
||||||
# Timeout so KeyboardInterrupt can be caught on Win32
|
# Timeout so KeyboardInterrupt can be caught on Win32
|
||||||
self.socket.settimeout(1)
|
self.socket.settimeout(1)
|
||||||
self.socket.listen(self.request_queue_size)
|
self.socket.listen(self.opts.request_queue_size)
|
||||||
ba = self.bind_address
|
ba = self.bind_address
|
||||||
if isinstance(ba, tuple):
|
if isinstance(ba, tuple):
|
||||||
ba = ':'.join(map(type(''), ba))
|
ba = ':'.join(map(type(''), ba))
|
||||||
@ -686,7 +647,7 @@ class ServerLoop(object):
|
|||||||
|
|
||||||
def setup_socket(self):
|
def setup_socket(self):
|
||||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
if self.no_delay and not isinstance(self.bind_address, basestring):
|
if self.opts.no_delay and not isinstance(self.bind_address, basestring):
|
||||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
|
|
||||||
# If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
|
# If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
|
||||||
@ -717,7 +678,7 @@ class ServerLoop(object):
|
|||||||
|
|
||||||
set_socket_inherit(s, False)
|
set_socket_inherit(s, False)
|
||||||
if hasattr(s, 'settimeout'):
|
if hasattr(s, 'settimeout'):
|
||||||
s.settimeout(self.timeout)
|
s.settimeout(self.opts.timeout)
|
||||||
|
|
||||||
if self.ssl_context is not None:
|
if self.ssl_context is not None:
|
||||||
try:
|
try:
|
||||||
@ -741,7 +702,7 @@ class ServerLoop(object):
|
|||||||
return # Drop connection
|
return # Drop connection
|
||||||
raise
|
raise
|
||||||
if hasattr(s, 'settimeout'):
|
if hasattr(s, 'settimeout'):
|
||||||
s.settimeout(self.timeout)
|
s.settimeout(self.opts.timeout)
|
||||||
|
|
||||||
conn = Connection(self, s)
|
conn = Connection(self, s)
|
||||||
|
|
||||||
@ -809,7 +770,7 @@ class ServerLoop(object):
|
|||||||
sock.close()
|
sock.close()
|
||||||
self.socket = None
|
self.socket = None
|
||||||
|
|
||||||
self.requests.stop(self.shutdown_timeout)
|
self.requests.stop(self.opts.shutdown_timeout)
|
||||||
|
|
||||||
def echo_handler(conn, data):
|
def echo_handler(conn, data):
|
||||||
keep_going = True
|
keep_going = True
|
||||||
|
66
src/calibre/srv/opts.py
Normal file
66
src/calibre/srv/opts.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
Option = namedtuple('Option', 'name default doc choices')
|
||||||
|
|
||||||
|
raw_options = (
|
||||||
|
|
||||||
|
'Path to the SSL certificate file',
|
||||||
|
'ssl_certfile', None,
|
||||||
|
|
||||||
|
'Path to the SSL private key file',
|
||||||
|
'ssl_keyfile', None,
|
||||||
|
|
||||||
|
' Max. queued connections while waiting to accept',
|
||||||
|
'request_queue_size', 5,
|
||||||
|
|
||||||
|
'Timeout in seconds for accepted connections',
|
||||||
|
'timeout', 10.0,
|
||||||
|
|
||||||
|
'Total time in seconds to wait for worker threads to cleanly exit',
|
||||||
|
'shutdown_timeout', 5.0,
|
||||||
|
|
||||||
|
'Minimum number of connection handling threads',
|
||||||
|
'min_threads', 10,
|
||||||
|
|
||||||
|
'Maximum number of simultaneous connections (beyond this number of connections will be dropped)',
|
||||||
|
'max_threads', 500,
|
||||||
|
|
||||||
|
'Allow socket pre-allocation, for example, with systemd socket activation',
|
||||||
|
'allow_socket_preallocation', True,
|
||||||
|
|
||||||
|
'Max. size of single header (in KB)',
|
||||||
|
'max_header_line_size', 8,
|
||||||
|
|
||||||
|
'Max. size of a request (in MB)',
|
||||||
|
'max_request_body_size', 500,
|
||||||
|
|
||||||
|
'no_delay turns on TCP_NODELAY which decreases latency at the cost of'
|
||||||
|
' worse overall performance when sending multiple small packets. It'
|
||||||
|
' prevents the TCP stack from aggregating multiple small TCP packets.',
|
||||||
|
'no_delay', True,
|
||||||
|
)
|
||||||
|
|
||||||
|
options = []
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
while i + 2 < len(raw_options):
|
||||||
|
doc, name, default = raw_options[i:i+3]
|
||||||
|
i += 3
|
||||||
|
choices = None
|
||||||
|
if isinstance(default, set):
|
||||||
|
default = list(default)[0]
|
||||||
|
choices = raw_options[i]
|
||||||
|
i += 1
|
||||||
|
options.append(Option(name, default, doc, choices))
|
||||||
|
options = tuple(options)
|
||||||
|
del raw_options
|
||||||
|
|
||||||
|
defaults = namedtuple('Defaults', ' '.join(o.name for o in options))(*tuple(o.default for o in options))
|
Loading…
x
Reference in New Issue
Block a user