The problem I ran into with the answers based on using handle_request() instead of serve_forever() is that the server won't time out until it receives the next request. I need a solution for when the server has stopped getting requests but is still hanging around for some reason, so that won't work for me.
Thanks to Gregoire Cattan's answer though, I realized that I can get the behavior I want by subclassing HTTPServer and overriding the service_actions() method. It's called within the serve_forever() loop, so putting the timeout logic there means it will be triggered even in the absence of incoming requests.
#!/usr/bin/env python
"""Run a HTTP server until it times out."""
from http.server import HTTPServer, SimpleHTTPRequestHandler
from sys import stderr
from time import time
PORT = 1313
TIMEOUT = 5
class Server(HTTPServer):
"""Expiring HTTP Server."""
timeout = TIMEOUT
def __init__(self, *args, **kwargs):
"""Initialize the server with a start time."""
self.started = time()
super().__init__(*args, **kwargs)
@property
def age(self):
"""Return the number of seconds the server has been alive."""
return time() - self.started
def service_actions(self, *args, **kwargs):
"""Time out if the server has expired."""
print(self.age, file=stderr)
if self.age > self.timeout:
self.stopped = time()
raise TimeoutError()
super().service_actions(*args, **kwargs)
def run():
"""Run the web server."""
server = Server(("", PORT), SimpleHTTPRequestHandler)
print(f"Starting server at {server.started} with a {TIMEOUT} second timeout.\n", file=stderr)
try:
server.serve_forever()
except TimeoutError:
...
finally:
print(f"\nShutting down server at {server.stopped}.", file=stderr)
server.server_close()
if __name__ == "__main__":
run()
Here's the output:
Starting server at 1715792021.774305 with a 5 second timeout.
0.5272078514099121
1.0283708572387695
1.5295538902282715
2.0307466983795166
2.5319831371307373
3.032681941986084
3.5338847637176514
4.0351338386535645
4.536334037780762
5.037556886672974
Shutting down server at 1715792026.811923.