cherrypy update

fixes #3348
This commit is contained in:
AdeHub
2024-12-07 19:56:21 +13:00
parent 94d62430a0
commit a09e91ff8a
28 changed files with 512 additions and 108 deletions
+19 -3
View File
@@ -38,6 +38,7 @@ class SimplePlugin(object):
"""
def __init__(self, bus):
"""Initialize a simple plugin."""
self.bus = bus
def subscribe(self):
@@ -93,6 +94,7 @@ class SignalHandler(object):
del k, v
def __init__(self, bus):
"""Initialize a signal handler plugin."""
self.bus = bus
# Set default handlers
self.handlers = {'SIGTERM': self.bus.exit,
@@ -117,8 +119,7 @@ class SignalHandler(object):
self.bus.exit()
def _is_daemonized(self):
"""Return boolean indicating if the current process is
running as a daemon.
"""Check if current process is running as a daemon.
The criteria to determine the `daemon` condition is to verify
if the current pid is not the same as the one that got used on
@@ -223,6 +224,7 @@ class DropPrivileges(SimplePlugin):
"""
def __init__(self, bus, umask=None, uid=None, gid=None):
"""Initialize the privilege dropping plugin."""
SimplePlugin.__init__(self, bus)
self.finalized = False
self.uid = uid
@@ -288,6 +290,7 @@ class DropPrivileges(SimplePlugin):
self._umask = val
def start(self):
"""Drop the process privileges."""
# uid/gid
def current_ids():
"""Return the current (uid, gid) if available."""
@@ -353,6 +356,7 @@ class Daemonizer(SimplePlugin):
def __init__(self, bus, stdin='/dev/null', stdout='/dev/null',
stderr='/dev/null'):
"""Initialize the daemonizer plugin."""
SimplePlugin.__init__(self, bus)
self.stdin = stdin
self.stdout = stdout
@@ -360,6 +364,7 @@ class Daemonizer(SimplePlugin):
self.finalized = False
def start(self):
"""Attempt to daemonize the process."""
if self.finalized:
self.bus.log('Already deamonized.')
@@ -382,6 +387,7 @@ class Daemonizer(SimplePlugin):
def daemonize(
stdin='/dev/null', stdout='/dev/null', stderr='/dev/null',
logger=lambda msg: None):
"""Daemonize the process."""
# See http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
# (or http://www.faqs.org/faqs/unix-faq/programmer/faq/ section 1.7)
# and http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012
@@ -428,11 +434,13 @@ class PIDFile(SimplePlugin):
"""Maintain a PID file via a WSPBus."""
def __init__(self, bus, pidfile):
"""Initialize the PID file plugin."""
SimplePlugin.__init__(self, bus)
self.pidfile = pidfile
self.finalized = False
def start(self):
"""Write a PID file to disk."""
pid = os.getpid()
if self.finalized:
self.bus.log('PID %r already written to %r.' % (pid, self.pidfile))
@@ -444,6 +452,7 @@ class PIDFile(SimplePlugin):
start.priority = 70
def exit(self):
"""Delete the PID file from disk."""
try:
os.remove(self.pidfile)
self.bus.log('PID file removed: %r.' % self.pidfile)
@@ -462,11 +471,12 @@ class PerpetualTimer(threading.Timer):
"""
def __init__(self, *args, **kwargs):
"Override parent constructor to allow 'bus' to be provided."
"""Override parent constructor to allow 'bus' to be provided."""
self.bus = kwargs.pop('bus', None)
super(PerpetualTimer, self).__init__(*args, **kwargs)
def run(self):
"""Run an infinitely repeated callable."""
while True:
self.finished.wait(self.interval)
if self.finished.isSet():
@@ -494,6 +504,7 @@ class BackgroundTask(threading.Thread):
"""
def __init__(self, interval, function, args=[], kwargs={}, bus=None):
"""Initialize a background task parameters."""
super(BackgroundTask, self).__init__()
self.interval = interval
self.function = function
@@ -506,9 +517,11 @@ class BackgroundTask(threading.Thread):
self.daemon = True
def cancel(self):
"""Set a task cancellation flag."""
self.running = False
def run(self):
"""Start running the repeated background task in the loop."""
self.running = True
while self.running:
time.sleep(self.interval)
@@ -539,6 +552,7 @@ class Monitor(SimplePlugin):
"""
def __init__(self, bus, callback, frequency=60, name=None):
"""Initialize the monitor plugin."""
SimplePlugin.__init__(self, bus)
self.callback = callback
self.frequency = frequency
@@ -611,6 +625,7 @@ class Autoreloader(Monitor):
"""A regular expression by which to match filenames."""
def __init__(self, bus, frequency=1, match='.*'):
"""Initialize the auto-reloader monitor plugin."""
self.mtimes = {}
self.files = set()
self.match = match
@@ -717,6 +732,7 @@ class ThreadManager(SimplePlugin):
"""A map of {thread ident: index number} pairs."""
def __init__(self, bus):
"""Initialize the thread manager plugin."""
self.threads = {}
SimplePlugin.__init__(self, bus)
self.bus.listeners.setdefault('acquire_thread', set())
+19 -5
View File
@@ -1,4 +1,6 @@
r"""
Server interfaces.
Starting in CherryPy 3.1, cherrypy.server is implemented as an
:ref:`Engine Plugin<plugins>`. It's an instance of
:class:`cherrypy._cpserver.Server`, which is a subclass of
@@ -127,6 +129,8 @@ import portend
class Timeouts:
"""Timeout constants."""
occupied = 5
free = 1
@@ -146,6 +150,7 @@ class ServerAdapter(object):
"""
def __init__(self, bus, httpserver=None, bind_addr=None):
"""Initialize the HTTP server plugin."""
self.bus = bus
self.httpserver = httpserver
self.bind_addr = bind_addr
@@ -153,10 +158,12 @@ class ServerAdapter(object):
self.running = False
def subscribe(self):
"""Subscribe control methods to the bus lifecycle events."""
self.bus.subscribe('start', self.start)
self.bus.subscribe('stop', self.stop)
def unsubscribe(self):
"""Unsubcribe control methods to the bus lifecycle events."""
self.bus.unsubscribe('start', self.start)
self.bus.unsubscribe('stop', self.stop)
@@ -212,7 +219,9 @@ class ServerAdapter(object):
return '%s://%s' % (scheme, host)
def _start_http_thread(self):
"""HTTP servers MUST be running in new threads, so that the
"""Start the HTTP server thread.
HTTP servers MUST be running in new threads, so that the
main thread persists to receive KeyboardInterrupt's. If an
exception is raised in the httpserver's thread then it's
trapped here, and the bus (and therefore our httpserver)
@@ -258,9 +267,10 @@ class ServerAdapter(object):
@property
def bound_addr(self):
"""
The bind address, or if it's an ephemeral port and the
socket has been bound, return the actual port bound.
"""The bind address.
If it's an ephemeral port and the socket has been bound,
return the actual port bound.
"""
host, port = self.bind_addr
if port == 0 and self.httpserver.socket:
@@ -292,6 +302,7 @@ class FlupCGIServer(object):
"""Adapter for a flup.server.cgi.WSGIServer."""
def __init__(self, *args, **kwargs):
"""Initialize the flup CGI Server plugin."""
self.args = args
self.kwargs = kwargs
self.ready = False
@@ -315,6 +326,7 @@ class FlupFCGIServer(object):
"""Adapter for a flup.server.fcgi.WSGIServer."""
def __init__(self, *args, **kwargs):
"""Initialize the FCGI server parameters."""
if kwargs.get('bindAddress', None) is None:
import socket
if not hasattr(socket, 'fromfd'):
@@ -360,6 +372,7 @@ class FlupSCGIServer(object):
"""Adapter for a flup.server.scgi.WSGIServer."""
def __init__(self, *args, **kwargs):
"""Initialize the SCGI server parameters."""
self.args = args
self.kwargs = kwargs
self.ready = False
@@ -395,7 +408,8 @@ class FlupSCGIServer(object):
@contextlib.contextmanager
def _safe_wait(host, port):
"""
"""Warn when bind interface is ambiguous.
On systems where a loopback interface is not available and the
server is bound to all interfaces, it's difficult to determine
whether the server is in fact occupying the port. In this case,
+10
View File
@@ -17,10 +17,12 @@ class ConsoleCtrlHandler(plugins.SimplePlugin):
"""A WSPBus plugin for handling Win32 console events (like Ctrl-C)."""
def __init__(self, bus):
"""Initialize the console control handler."""
self.is_set = False
plugins.SimplePlugin.__init__(self, bus)
def start(self):
"""Register handling of the console control events."""
if self.is_set:
self.bus.log('Handler for console events already set.', level=20)
return
@@ -34,6 +36,7 @@ class ConsoleCtrlHandler(plugins.SimplePlugin):
self.is_set = True
def stop(self):
"""Unregister the console control handlers."""
if not self.is_set:
self.bus.log('Handler for console events already off.', level=20)
return
@@ -78,6 +81,7 @@ class Win32Bus(wspbus.Bus):
"""
def __init__(self):
"""Initialize a Win32 bus implementation."""
self.events = {}
wspbus.Bus.__init__(self)
@@ -94,10 +98,12 @@ class Win32Bus(wspbus.Bus):
@property
def state(self):
"""The bus state."""
return self._state
@state.setter
def state(self, value):
"""Set the bus state."""
self._state = value
event = self._get_state_event(value)
win32event.PulseEvent(event)
@@ -144,6 +150,7 @@ control_codes = _ControlCodes({'graceful': 138})
def signal_child(service, command):
"""Send a control command to a service."""
if command == 'stop':
win32serviceutil.StopService(service)
elif command == 'restart':
@@ -165,16 +172,19 @@ class PyWebService(win32serviceutil.ServiceFramework):
_svc_description_ = 'Python Web Service'
def SvcDoRun(self):
"""Start the service."""
from cherrypy import process
process.bus.start()
process.bus.block()
def SvcStop(self):
"""Stop the service."""
from cherrypy import process
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
process.bus.exit()
def SvcOther(self, control):
"""Send a command to the service."""
from cherrypy import process
process.bus.publish(control_codes.key_for(control))