Manngo
Manngo

Reputation: 16371

Running Python’s SimpleHTTPServer in a GUI

I am writing a GUI wrapper around Python’s SimpleHTTPServer. It looks like this:

enter image description here

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

Answers (1)

Bryan Oakley
Bryan Oakley

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

Related Questions