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-v2.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 GET requests which begin with "/static/" are handled using this, like so:

Route pattern Handler Action
GET / home Display the home page
GET /snippet/view/{id} snippetView Display a specific snippet
GET /snippet/create snippetCreate Display a form for creating a new snippet
POST /snippet/create snippetCreatePost Save a new snippet
GET /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 for a file, it will remove the leading slash from the request 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()

    // 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("GET /static/", http.StripPrefix("/static", fileServer))

    // Register the other application routes as normal..
    mux.HandleFunc("GET /{$}", home)
    mux.HandleFunc("GET /snippet/view/{id}", snippetView)
    mux.HandleFunc("GET /snippet/create", snippetCreate)
    mux.HandleFunc("POST /snippet/create", snippetCreatePost)

    log.Print("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.tmpl file to make use of the static files:

File: ui/html/base.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>
        {{template "nav" .}}
        <main>
            {{template "main" .}}
        </main>
        <footer>Powered by <a href='https://golang.org/'>Go</a></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 restart the server and visit http://localhost:4000. Your home page should now look like this:

02.09-04.png

Additional information

File server features and functions

Go’s http.FileServer handler has a few really nice features that are worth mentioning:

Performance

In this chapter we set up the file server so that it serves files out of the ./ui/static directory on your hard disk.

But it’s important to note that http.FileServer probably won’t be reading these files from disk once the application is up-and-running. 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.