You're reading a sample of this book. Get the full version here.
Let's Go Foundations › Serving Static Files
Previous · Contents · Next
Chapter 2.9.

Serving Static Files

Now let’s improve the look and feel of the home page by adding some static CSS and image files to our project, along with a tiny bit of JavaScript to highlight the active navigation item.

If you’re following along, you can grab the necessary files and extract them into the ui/static folder that we made earlier with the following commands:

$ cd $HOME/code/snippetbox
$ curl https://www.alexedwards.net/static/sb.v130.tar.gz | tar -xvz -C ./ui/static/

The contents of your ui/static directory should now look like this:

02.09-01.png

The http.FileServer Handler

Go’s net/http package ships with a built-in http.FileServer handler which you can use to serve files over HTTP from a specific directory. Let’s add a new route to our application so that all requests which begin with "/static/" are handled using this, like so:

Method Pattern Handler Action
ANY / home Display the home page
ANY /snippet?id=1 showSnippet Display a specific snippet
POST /snippet/create createSnippet Create a new snippet
ANY /static/ http.FileServer Serve a specific static file

To create a new http.FileServer handler, we need to use the http.FileServer() function like this:

fileServer := http.FileServer(http.Dir("./ui/static/"))

When this handler receives a request, it will remove the leading slash from the URL path and then search the ./ui/static directory for the corresponding file to send to the user.

So, for this to work correctly, we must strip the leading "/static" from the URL path before passing it to http.FileServer. Otherwise it will be looking for a file which doesn’t exist and the user will receive a 404 page not found response. Fortunately Go includes a http.StripPrefix() helper specifically for this task.

Open your main.go file and add the following code, so that the file ends up looking like this:

File: cmd/web/main.go
package main

import (
    "log"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", home)
    mux.HandleFunc("/snippet", showSnippet)
    mux.HandleFunc("/snippet/create", createSnippet)

    // Create a file server which serves files out of the "./ui/static" directory.
    // Note that the path given to the http.Dir function is relative to the project
    // directory root.
    fileServer := http.FileServer(http.Dir("./ui/static/"))

    // Use the mux.Handle() function to register the file server as the handler for
    // all URL paths that start with "/static/". For matching paths, we strip the
    // "/static" prefix before the request reaches the file server.
    mux.Handle("/static/", http.StripPrefix("/static", fileServer))

    log.Println("Starting server on :4000")
    err := http.ListenAndServe(":4000", mux)
    log.Fatal(err)
}

Once that’s complete, restart the application and open http://localhost:4000/static/ in your browser. You should see a navigable directory listing of the ui/static folder which looks like this:

02.09-02.png

Feel free to have a play around and browse through the directory listing to view individual files. For example, if you navigate to http://localhost:4000/static/css/main.css you should see the CSS file appear in your browser like so:

02.09-03.png

Using the Static Files

With the file server working properly, we can now update the ui/html/base.layout.tmpl file to make use of the static files:

File: ui/html/base.layout.tmpl
{{define "base"}}
<!doctype html>
<html lang='en'>
    <head>
        <meta charset='utf-8'>
        <title>{{template "title" .}} - Snippetbox</title>
        <!-- Link to the CSS stylesheet and favicon -->
        <link rel='stylesheet' href='/static/css/main.css'>
        <link rel='shortcut icon' href='/static/img/favicon.ico' type='image/x-icon'>
        <!-- Also link to some fonts hosted by Google -->
        <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700'>
    </head>
    <body>
        <header>
            <h1><a href='/'>Snippetbox</a></h1>
        </header>
        <nav>
            <a href='/'>Home</a>
        </nav>
        <main>
            {{template "main" .}}
        </main>
        {{template "footer" .}}
        <!-- And include the JavaScript file -->
        <script src="/static/js/main.js" type="text/javascript"></script>
    </body>
</html>
{{end}}

Make sure you save the changes, then visit http://localhost:4000. Your home page should now look like this:

02.09-04.png

Additional Information

Features and Functions

Go’s file server has a few really nice features that are worth mentioning:

Performance

In the code above we’ve set up our file server so that it serves files out of the ./ui/static directory on your hard disk.

But it’s important to note that, once the application is up-and-running, http.FileServer probably won’t be reading these files from disk. Both Windows and Unix-based operating systems cache recently-used files in RAM, so (for frequently-served files at least) it’s likely that http.FileServer will be serving them from RAM rather than making the relatively slow round-trip to your hard disk.

Serving Single Files

Sometimes you might want to serve a single file from within a handler. For this there’s the http.ServeFile() function, which you can use like so:

func downloadHandler(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w, r, "./ui/static/file.zip")
}

Disabling Directory Listings

If you want to disable directory listings there are a few different approaches you can take. The simplest way? Add a blank index.html file to the specific directory that you want to disable listings for. This will then be served instead of the directory listing, and the user will get a 200 OK response with no body. If you want to do this for all directories under ./ui/static you can use the command:

$ find ./ui/static -type d -exec touch {}/index.html \;

A more complicated (but arguably better) solution is to create a custom implementation of http.FileSystem, and have it return an os.ErrNotExist error for any directories. A full explanation and sample code can be found in this blog post.

Embedding Files

The Go 1.16 release introduced the new embed package which makes it possible to embed static files into your Go program itself. We’ll explain how to use this new functionality later in Appendix 15.01: Embedding Files.