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.v120.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>
        <section>
            {{template "body" .}}
        </section>
        {{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.