Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Comments on Python http.server how can I programatically refresh the browser page?
Parent
Python http.server how can I programatically refresh the browser page?
I'm currently using a static site generator (SSG) to convert a large collection of Markdown notes to a personal HTML/CSS wiki, but I decided to replace it with a Python script so I can fully customize every aspect of the site generation and my workflow. It's also something of a programming challenge/learning exercise for me.
I've already figured out how to create a local HTTP server and simultaneously watch the input files for changes (the actual site construction isn't implemented yet). However, my SSG also refreshes the browser page whenever the content updates so I don't have to do so manually, a convenience I'd like to include in my script. But I'm at a bit of a loss on how that can be done.
Here's my code so far:
# Import dependencies
import sys, signal
import _thread as thread
from http.server import HTTPServer, SimpleHTTPRequestHandler
from PyQt6.QtCore import QFileSystemWatcher, QTimer
from PyQt6.QtWidgets import QApplication
# Define server address and important directories
host = 'localhost'
port = 8000
contentDirectory = '/path/to/content'
siteDirectory = '/path/to/site'
# Define input paths for the filesystem watcher to periodically check for changes
paths = [
'/path/1',
'/path/2',
'/path/3',
]
# Define some colors with ANSI escape sequences
class color():
red = '\033[91m'
yellow = '\033[93m'
green = '\033[92m'
cyan = '\033[96m'
blue = '\033[94m'
magenta = '\033[95m'
white = '\033[97m'
end = '\033[0m'
# Function to be called when the input changes
def changeDetected(path):
print('Directory changed: ' + path)
# Handler for the SIGINT signal
def sigintHandler(*args):
QApplication.quit()
print(color.red + "\nServer stopped" + color.end)
# Function for closing the server on demand
def serverStopped():
server.server_close()
# Subclass SimpleHTTPRequestHandler so we can serve a specific directory
class Handler(SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=siteDirectory, **kwargs)
# Final setup
if __name__ == "__main__":
# Create the core app and the filesystem watcher
app = QApplication(sys.argv)
filesystemWatcher = QFileSystemWatcher(paths)
filesystemWatcher.directoryChanged.connect(changeDetected) # Run some code whenever the input changes
# Listen for signals - particularly Ctrl+C - in the terminal (needed for quitting the QApplication on demand)
signal.signal(signal.SIGINT, sigintHandler) # Implement custom handling for the SIGINT signal
timer = QTimer()
timer.start(500) # NOTE: Change time as needed
timer.timeout.connect(lambda: None) # Let the interpreter run every 500 milliseconds so signals come through
# Create and start the server
def startServer(): # Setup stuff here...
server = HTTPServer((host, port), Handler) # Create the server object
try: # Run the server until...
server.serve_forever()
except KeyboardInterrupt: # ...someone presses Ctrl+C
serverStopped()
thread.start_new_thread(startServer, ()) # Run it in a separate thread for concurrency with other code (particularly the filesystem watcher)
print(color.blue + f'Server started at http://{host}:{port} (Ctrl+C to stop)' + color.end)
# Run the event loop for the watcher
app.exec()
Post
Two basic approaches to this, both require having your server inject a <script> tag into your page...
-
The old-school "simple" script, which uses
setInterval()to periodically poll your server usingXMLHttpRequest.open()orfetch()to query for changes, either by ETag header or 304 Not Modified status code (generation of which you must build into your HTTP server). If the response from the server is not the same ETag as the last time you requested it, or the server sends back something other than 304, callwindow.location.reload(). -
The newer-style "complex" script, which establishes either a
WebSocketor anEventSourceto your HTTP server, and waits for messages -- the event handler function for that would callwindow.location.reload(). In your HTTP server code, you send a message on that channel whenever you want the client to reload (i.e. when you detect the change to your content).
EventSource is possibly the cleanest approach (see https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events or https://dev.to/philip_zhang_854092d88473/mastering-server-sent-events-sse-with-python-and-go-for-real-time-data-streaming-38bf for ideas), but the old-school approach is probably fine for a local dev instance, where the load can be controlled.
In any case, if you intend to expose your HTTP server to the public, you should make it configurable on the server-side, whether the client script is actually injected. You probably don't want hundreds of pings to your server from the outside world, every time someone browses your pages.

0 comment threads