Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

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?

+3
−0

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()
History

0 comment threads

Post
+2
−0

Two basic approaches to this, both require having your server inject a <script> tag into your page...

  1. The old-school "simple" script, which uses setInterval() to periodically poll your server using XMLHttpRequest.open() or fetch() 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, call window.location.reload().

  2. The newer-style "complex" script, which establishes either a WebSocket or an EventSource to your HTTP server, and waits for messages -- the event handler function for that would call window.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.

History

1 comment thread

Just to make sure I'm understanding this correctly, if I go the EventSource route, the Python script ... (1 comment)
Just to make sure I'm understanding this correctly, if I go the EventSource route, the Python script ...
pycoder‭ wrote 9 months ago

Just to make sure I'm understanding this correctly, if I go the EventSource route, the Python script should inject JavaScript into each HTML page it generates in the form of an EventSource, which listens for messages from the HTTP server and calls window.location.reload() when it gets one.

The other half of this would be sending messages to the client from the HTTP server using some Python code, though I'm having trouble figuring out how to implement this part in pure Python (every guide I've found uses Flask or FastAPI, and I don't want to add any more dependencies if at all possible).

The server's just for local dev work and shouldn't be exposed at all. It's just there to serve the site and refresh the page when I update the contents.