J'exécute mon HTTPServer
dans un thread séparé (en utilisant le module de thread qui n'a aucun moyen d'arrêter les threads ...) et je veux arrêter de servir les requêtes lorsque le thread principal s'arrête également.
La documentation Python indique que BaseHTTPServer.HTTPServer
est une sous-classe de SocketServer.TCPServer
, qui prend en charge une méthode shutdown
, mais elle est absente dans HTTPServer
.
L'ensemble du module BaseHTTPServer
contient très peu de documentation :(
Je devrais commencer par dire que "je ne ferais probablement pas cela moi-même, mais je l'ai fait par le passé". La méthode serve_forever (from SocketServer.py) ressemble à ceci:
def serve_forever(self):
"""Handle one request at a time until doomsday."""
while 1:
self.handle_request()
Vous pouvez remplacer (dans la sous-classe) while 1
avec while self.should_be_running
, et modifiez cette valeur à partir d'un autre thread. Quelque chose comme:
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()
Edit: j'ai déterré le code que j'utilisais à l'époque:
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()
Dans mon python 2.6 installation, je peux l'appeler sur le TCPServer sous-jacent - il est toujours là dans votre HTTPServer
:
TCPServer.shutdown
>>> import BaseHTTPServer
>>> h=BaseHTTPServer.HTTPServer(('',5555), BaseHTTPServer.BaseHTTPRequestHandler)
>>> h.shutdown
<bound method HTTPServer.shutdown of <BaseHTTPServer.HTTPServer instance at 0x0100D800>>
>>>
Je pense que vous pouvez utiliser [serverName].socket.close()
Une autre façon de le faire, basée sur http://docs.python.org/2/library/basehttpserver.html#more-examples , est: au lieu de serve_forever (), continuez à servir aussi longtemps que une condition est remplie, le serveur vérifiant la condition avant et après chaque demande. Par exemple:
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()
La boucle d'événement se termine sur SIGTERM, Ctrl+C ou lorsque shutdown()
est appelée.
server_close()
doit être appelé après server_forever()
pour fermer la prise d'écoute.
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()
Serveur simple pouvant être arrêté avec l'action de l'utilisateur (SIGTERM, Ctrl+C, ...):
server = StoppableHTTPServer(("127.0.0.1", 8080),
http.server.BaseHTTPRequestHandler)
server.run()
Serveur fonctionnant dans un thread:
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()
Dans python 2.7, l'appel à shutdown () fonctionne mais uniquement si vous servez via serve_forever, car il utilise async select et une boucle d'interrogation. L'exécution de votre propre boucle avec handle_request () exclut ironiquement cette fonctionnalité car cela implique un appel de blocage stupide.
Depuis le 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()
Voici une partie de mon code pour faire un arrêt de blocage à partir d'un autre thread, en utilisant un événement pour attendre la fin:
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")
Cette méthode que j'utilise avec succès (Python 3) pour arrêter le serveur à partir de l'application Web elle-même (une page Web):
import http.server
import os
import re
class PatientHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
stop_server = False
base_directory = "/static/"
# A file to use as an "server stopped user information" page.
stop_command = "/control/stop.html"
def send_head(self):
self.path = os.path.normpath(self.path)
if self.path == PatientHTTPRequestHandler.stop_command and self.address_string() == "127.0.0.1":
# I wanted that only the local machine could stop the server.
PatientHTTPRequestHandler.stop_server = True
# Allow the stop page to be displayed.
return http.server.SimpleHTTPRequestHandler.send_head(self)
if self.path.startswith(PatientHTTPRequestHandler.base_directory):
return http.server.SimpleHTTPRequestHandler.send_head(self)
else:
return self.send_error(404, "Not allowed", "The path you requested is forbidden.")
if __== "__main__":
httpd = http.server.HTTPServer(("127.0.0.1", 8080), PatientHTTPRequestHandler)
# A timeout is needed for server to check periodically for KeyboardInterrupt
httpd.timeout = 1
while not PatientHTTPRequestHandler.stop_server:
httpd.handle_request()
De cette façon, les pages diffusées via l'adresse de base http://localhost:8080/static/
(exemple http://localhost:8080/static/styles/common.css
) sera servi par le gestionnaire par défaut, un accès à http://localhost:8080/control/stop.html
depuis l'ordinateur du serveur affichera stop.html
puis arrêtez le serveur, toute autre option sera interdite.
J'ai essayé toutes les solutions possibles ci-dessus et j'ai fini par avoir un problème "quelquefois" - d'une manière ou d'une autre, cela ne l'a pas vraiment fait - alors j'ai fini par faire une solution sale qui fonctionnait tout le temps pour moi:
Si tout ce qui précède échoue, alors la force brute tue votre fil en utilisant quelque chose comme ceci:
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)