I maintain a golang API for a client, and the need arose to serve static content based on certain criteria, depending on who was requesting the content. I found this article and started implemented something similar before realizing that it was madness to do it this way when net/http has a function for serving static content called ServeFile. Here’s an example implementation:

package main

import (
	"fmt"
	"net/http"
	"path"
	"path/filepath"
	"strings"
)

func main() {
	port := ":4300"
	fmt.Println("Listening on", port)
	fmt.Println("Serving static content from the /files/ directory.")
	http.HandleFunc("/files/", HandleFiles)
	err := http.ListenAndServe(port, nil)
	if err != nil {
		fmt.Println(err)
	}
}

// HandleFiles handles static content.
func HandleFiles(w http.ResponseWriter, r *http.Request) {
	// Get only the file name.
	Filename := path.Base(r.URL.String())
	// Whatever arbitrary logic you want. For demo purposes, we will not serve
	// content that contains the string "bad". But you could be checking that
	// the user is the owner of this file, or any additional checks you aren't
	// doing in your middleware.
	if strings.Contains(Filename, "bad") {
		fmt.Println("This file is bad and we won't serve it:", Filename)
		w.WriteHeader(http.StatusForbidden)
		w.Write([]byte("Not authorized!"))
		return
	}
	fmt.Println("Attempting to serve", Filename)
	// Use http.ServeFile to serve the content. We don't have to worry about
	// writing not found HTTP error codes, etc., because ServeFile handles it
	// for us.
	http.ServeFile(w, r, filepath.Join(".", "files", Filename))
}

That’s it. Here’s an example of output from me running this with files named good.txt and bad.txt in my /files directory. I downloaded good.txt, attempted to get bad.txt, and then tried to get notfound.txt:

Listening on :4300
Serving static content from the /files/ directory.
Attempting to serve good.txt
This file is bad and we won't serve it: bad.txt
Attempting to serve notfound.txt

What About HTTP Headers?

When serving a file, we have the headers we expect. For example, here are the headers for good.txt:

HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 9
Content-Type: text/plain; charset=utf-8
Last-Modified: Sun, 02 Jun 2019 19:20:27 GMT
Date: Sun, 02 Jun 2019 19:30:43 GMT

Conclusion

In summary: just use http.ServeFile. I created a repository with this demo app if you want to run it yourself.