python - servidor - Cómo detener BaseHTTPServer.serve_forever() en una subclase BaseHTTPRequestHandler?
python simplehttpserver 8080 (7)
Estoy ejecutando mi HTTPServer
en un subproceso separado (utilizando el módulo de subprocesamiento que no tiene forma de detener subprocesos ...) y quiero dejar de atender solicitudes cuando el hilo principal también se apaga.
La documentación de Python indica que BaseHTTPServer.HTTPServer
es una subclase de SocketServer.TCPServer
, que admite un método de shutdown
, pero falta en HTTPServer
.
Todo el módulo BaseHTTPServer
tiene muy poca documentación :(
Debería comenzar diciendo que "probablemente yo no haría esto yo mismo, pero lo hice en el pasado". El método serve_forever (desde SocketServer.py) tiene el siguiente aspecto:
def serve_forever(self):
"""Handle one request at a time until doomsday."""
while 1:
self.handle_request()
Podría reemplazar (en la subclase) while 1
con while self.should_be_running
, y modificar ese valor desde un hilo diferente. Algo como:
def stop_serving_forever(self):
"""Stop handling requests"""
self.should_be_running = 0
# Make a fake request to the server, to really force it to stop.
# Otherwise it will just stop on the next request.
# (Exercise for the reader.)
self.make_a_fake_request_to_myself()
Editar: desenterré el código real que usaba en ese momento:
class StoppableRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
stopped = False
allow_reuse_address = True
def __init__(self, *args, **kw):
SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, *args, **kw)
self.register_function(lambda: ''OK'', ''ping'')
def serve_forever(self):
while not self.stopped:
self.handle_request()
def force_stop(self):
self.server_close()
self.stopped = True
self.create_dummy_request()
def create_dummy_request(self):
server = xmlrpclib.Server(''http://%s:%s'' % self.server_address)
server.ping()
En mi instalación de Python 2.6, puedo invocarlo en el TCPServer subyacente; aún está allí dentro de su HTTPServer
:
TCPServer.shutdown
>>> import BaseHTTPServer
>>> h=BaseHTTPServer.HTTPServer(('''',5555), BaseHTTPServer.BaseHTTPRequestHandler)
>>> h.shutdown
<bound method HTTPServer.shutdown of <BaseHTTPServer.HTTPServer instance at 0x0100D800>>
>>>
Creo que puedes usar [serverName].socket.close()
Otra forma de hacerlo, basada en http://docs.python.org/2/library/basehttpserver.html#more-examples , es: en lugar de serve_forever (), seguir sirviendo mientras se cumpla una condición, con el servidor que verifica la condición antes y después de cada solicitud. Por ejemplo:
import CGIHTTPServer
import BaseHTTPServer
KEEP_RUNNING = True
def keep_running():
return KEEP_RUNNING
class Handler(CGIHTTPServer.CGIHTTPRequestHandler):
cgi_directories = ["/cgi-bin"]
httpd = BaseHTTPServer.HTTPServer(("", 8000), Handler)
while keep_running():
httpd.handle_request()
En python 2.7, la llamada shutdown () funciona pero solo si está sirviendo a través de serve_forever, porque usa la selección async y un bucle de sondeo. Ejecutar su propio bucle con handle_request () irónicamente excluye esta funcionalidad porque implica una llamada de bloqueo tonta.
Desde BaseServer de SocketServer.py:
def serve_forever(self, poll_interval=0.5):
"""Handle one request at a time until shutdown.
Polls for shutdown every poll_interval seconds. Ignores
self.timeout. If you need to do periodic tasks, do them in
another thread.
"""
self.__is_shut_down.clear()
try:
while not self.__shutdown_request:
# XXX: Consider using another file descriptor or
# connecting to the socket to wake this up instead of
# polling. Polling reduces our responsiveness to a
# shutdown request and wastes cpu at all other times.
r, w, e = select.select([self], [], [], poll_interval)
if self in r:
self._handle_request_noblock()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()
Heres parte de mi código para hacer un bloqueo de cierre de otro hilo, usando un evento para esperar la finalización:
class MockWebServerFixture(object):
def start_webserver(self):
"""
start the web server on a new thread
"""
self._webserver_died = threading.Event()
self._webserver_thread = threading.Thread(
target=self._run_webserver_thread)
self._webserver_thread.start()
def _run_webserver_thread(self):
self.webserver.serve_forever()
self._webserver_died.set()
def _kill_webserver(self):
if not self._webserver_thread:
return
self.webserver.shutdown()
# wait for thread to die for a bit, then give up raising an exception.
if not self._webserver_died.wait(5):
raise ValueError("couldn''t kill webserver")
Probé todas las posibles soluciones anteriores y terminé teniendo un problema de "alguna vez", de alguna manera realmente no lo hice, así que terminé creando una solución sucia que funcionó todo el tiempo para mí:
Si todo lo anterior falla, entonces la fuerza bruta mata tu hilo usando algo como esto:
import subprocess
cmdkill = "kill $(ps aux|grep ''<name of your thread> true''|grep -v ''grep''|awk ''{print $2}'') 2> /dev/null"
subprocess.Popen(cmdkill, stdout=subprocess.PIPE, shell=True)
Los bucles de eventos finalizan en SIGTERM, Ctrl + C o cuando se llama a shutdown()
.
server_close()
debe llamarse después de server_forever()
para cerrar el socket de escucha.
import http.server
class StoppableHTTPServer(http.server.HTTPServer):
def run(self):
try:
self.serve_forever()
except KeyboardInterrupt:
pass
finally:
# Clean-up server (close socket, etc.)
self.server_close()
Servidor simple que se puede detener con la acción del usuario (SIGTERM, Ctrl + C , ...):
server = StoppableHTTPServer(("127.0.0.1", 8080),
http.server.BaseHTTPRequestHandler)
server.run()
Servidor que se ejecuta en un hilo:
import threading
server = StoppableHTTPServer(("127.0.0.1", 8080),
http.server.BaseHTTPRequestHandler)
# Start processing requests
thread = threading.Thread(None, server.run)
thread.start()
# ... do things ...
# Shutdown server
server.shutdown()
thread.join()