Reputation: 16371
I am writing a GUI wrapper around Python’s SimpleHTTPServer. It looks like this:
The GUI uses tkinter
. When I click on the OK
button, it launches the web server.
The web server code is based on the article at https://docs.python.org/3/library/http.server.html. Part of it is:
with socketserver.TCPServer(("", PORT), Handler) as httpd:
print("serving at port", PORT)
httpd.serve_forever()
It all works as expected so far, but when the https.server_forever()
function runs, I get the spinning beachball, and I can’t close the window or quit. I can force quit and try again, which is not convenient. The server does do its job, however.
If I run the same code from the command line (a non-gui version), I can easily interrupt it with ctrl-c
; I can catch that and exit more gracefully.
How can I interrupt the running server more politely from the server?
Upvotes: 0
Views: 1309
Reputation: 386210
You will need to run the server in a thread or a separate process, since both the web server and the UI need separate event loops.
If you want the server to communicate with the tkinter program, you'll need to set up a queue. As a general rule, you should only access tkinter objects from the thread that they were created in. However, I think it's safe to send virtual events from a worker thread to the GUI thread, which you can use to cause the GUI thread to read data from the queue.
For example, here's a simple threaded server. It must be passed the host and port, a reference to the root window, and a reference to the queue where information can be sent to the GUI.
class ExampleServer(threading.Thread):
def __init__(self, host, port, gui, queue):
threading.Thread.__init__(self)
self.host = host
self.port = port
self.gui = gui
self.queue = queue
self.daemon = True
def run(self):
print(f"Listening on http://{self.host}:{self.port}\n")
server = HTTPServer((self.host, self.port), ExampleHandler)
server.serve_forever()
In your request handler, you can push items on the queue and then generate an event on the root window. It might look something like this:
class ExampleHandler(BaseHTTPRequestHandler):
def do_GET(self):
# handle the request
...
# notify the UI
self.queue.put("anything you want")
self.gui.event_generate("<<DataAvailable>>")
Your gui also needs to take the queue as an argument and needs to set a binding to the virtual event. It might look something like this:
class ExampleGUI(tk.Tk):
def __init__(self, queue):
super().__init__()
self.queue = queue
# set up the rest of the GUI
...
# bind to the virtual event
self.bind("<<DataAvailable>>", self.poll_queue)
def poll_queue(self, event):
while not queue.empty():
data = self.queue.get_nowait()
# Do whatever you need to do with the data
...
def start(self):
self.mainloop()
Finally, you tie it all together with something like this:
if __name__ == "__main__":
queue = queue.Queue()
gui = ExampleGUI(queue)
server = ExampleServer("localhost", 8910, gui, queue)
server.start()
gui.start()
Upvotes: 2