I have two or three web pages that I tend to keep open on my desktop Mac. From time to time I have experimented with turning these into web apps, which makes it possible for me to hide them independently of my other web browser windows, helps them remember the appropriate window size, and helps me to not accidentally change them to a different page.
I was happy to discover that Safari makes it easier than ever to convert a page
into a web app. Just select “Add to Dock” in the File menu (or share sheet). It
puts an icon in the Dock, but you don’t have to leave it there because it also
puts an application in ~/Applications
.
But one of my pages is an HTML dashboard that is just a local file on my computer. I originally made it this way because it syncs via iCloud Drive, so that I can also view it on my phone or iPad if necessary. For some reason, you can’t make a web app to view a local file.
The good thing about the Mac is that you can work around almost anything, building exactly what you want out of smaller pieces. And in this case, all you need is a little program that responds to HTTP requests with the content of the file.
There are many ways to accomplish this. I think MacOS still comes with Apache installed by default. It is running on my machine, at least. But I don’t like configuring Apache, so I didn’t use this method.
Python can make an HTTP server easily, just run
python -m http.server -b localhost -d /path/to/directory 8203
The -m
flag runs the http.server
module as if it were a Python script
(that is, with __name__ == "__main__"
). In this case, it starts up a server
with the given options. The -b
flag tells it to listen on localhost
, so
that other computers on your network can’t access the page. Then you give it a
directory and a port number. After this you can visit
http://localhost:8203/file.html
and view the file.
But Python technically serves the entire directory, not just the file. You
could write a custom Python script that imported http.server
, and probably
build exactly what you wanted. But when I want a tiny program, I always seem to
gravitate towards Go, even though I tend to hate it for larger programs. I
probably like it because you can compile a binary that has no dependencies,
which reminds me of Turbo Pascal.
Here is a simple Go program that serves a single file at the port, and redirects any other requests to the base URL:
package main
import (
"flag"
"log"
"net/http"
"os"
)
func main() {
bind := flag.String("bind", "127.0.0.1:8011", "The address to listen on.")
filename := flag.String("file", "", "The file to serve")
flag.Parse()
if *filename == "" {
flag.Usage()
os.Exit(1)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
http.ServeFile(w, r, *filename)
} else {
http.Redirect(w, r, "/", http.StatusMovedPermanently)
}
})
log.Printf("Starting server at %s", *bind)
log.Fatal(http.ListenAndServe(*bind, nil))
}
I called it servefile.go
, then you just run go build servefile.go
and it
creates a binary named servefile
.
If you go the Apache route, MacOS will already keep the server alive for you. If you go the Python or Go route, you need to tell the system to always run your server.
The easiest way is to buy LaunchControl and create a new agent to run your command, checking “Run at load” and “Keep alive no matter what.”
For more information, check out this 13-year-old article on scheduling with launchd.