How to Disable FileServer Directory Listings
A nice feature of Go's http.FileServer
is that it automatically generates navigable directory listings, which look a bit like this:
But for certain applications you might want to prevent this behavior and disable directory listings altogether. In this post I’m going to run through three different options for doing exactly that:
Using index.html files
Before http.FileServer
generates a directory listing it checks for the existence of an index.html
file in the directory root. If an index.html
file exists, then it will respond with the contents of the file instead.
So it follows that a simple way to disable directory listings is to add a blank index.html
file to your root static file directory and all sub-directories, like so:
If you've got a lot of sub-directories an easy way to do that is with a one-line command like this:
Any requests for a directory should now result in an empty 200 OK
response for the user, instead of a directory listing. For example:
Or without the trailing slash, the user should get a 301 Redirect
like so:
This is a good-enough solution if you can't (or don't want to) make any changes to your Go application itself.
But it's not perfect. You'll need to remember to add a blank index.html
file for any new sub-directories in the future, and — if we're being pedantic — sending a 403 Forbidden
or 404 Not Found
status code would be more appropriate than sending the user an empty 200 OK
response.
Using middleware
Both of these imperfections can be addressed if we take a different approach and implement some custom middleware to intercept requests before they reach the http.FileServer
.
Essentially, we want the middleware to check if the request URL ends with a /
character, and if it does, return a 404 Not Found
response instead of passing on the request to the http.FileServer
. Here's a basic implementation:
This approach would result in a user getting responses like these:
To me, this feels like a cleaner and easier-to-maintain way to disable directory listings than using blank index.html
files. But again, it's still not perfect.
Firstly, requests for any directories without the trailing slash will be 301
redirected only to receive a 404 Not Found
response. It's an extra, unnecessary, request for both the client and server to deal with.
Secondly, if one of your directories does contain an index.html
file then it won't ever be used. For example, if you have the directory structure...
... any request to http://localhost:4000/static/css/
will result in a 404 Not Found
response instead of returning the contents of the /static/css/index.html
file.
Using a custom filesystem
The final option we're going to look at is creating a custom filesystem and passing that to your http.FileServer
.
There are a couple of approaches described by Brad Fitzpatrick and George Armhold you might want to consider, but I would personally suggest doing something like this:
In this code we're creating a custom neuteredFileSystem
type which embeds the standard http.FileSystem
. We then implement an Open()
method on it — which gets called each time our http.FileServer
receives a request.
In our Open()
method we Stat()
the requested file path and use the IsDir()
method to check whether it's a directory or not. If it is a directory we then try to Open()
any index.html
file in it. If no index.html
file exists, then this will return a os.ErrNotExist
error (which in turn we return and it will be transformed into a 404 Not Found
response by http.Fileserver
). We also call Close()
on the original file to avoid a file descriptor leak. Otherwise, we just return the file and let http.FileServer
do its thing.
Putting this to use with the directory structure...
...would result in responses like:
This is now working pretty nicely:
- All requests for directories (with no
index.html
file) return a404 Not Found
response, instead of a directory listing or a redirect. This works for requests both with and without a trailing slash. - The default behavior of
http.FileServer
isn't changed any other way, andindex.html
files work as per the standard library documentation.