Serving Static Files with Custom Headers using Golang

MeCode4Food
3 min readJun 11, 2021
Clickjacking (pic from dzone)

Basic Golang Static File Server

One of the common uses for Golang is to create servers to serve content, be it an API, or serve some files. Develop APIs long enough and soon you’ll find yourself needing to serve static content such as HTML, JS, CSS.

Let’s use the following as an example:

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Basic HTML Document</title>
</head>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x"
crossorigin="anonymous"
/>
<link rel="stylesheet" href="style.css">
<body>
<h1><TITLE></TITLE></h1>
<script src="script.js"></script>
<div class="blue">This is Blue</div>
<button class="btn btn-primary m-5">Bootstrap Button</button>
</body>
</html>

script.js:

document.write('this comes from a script')

style.css:

.blue {
background-color: blue;
color: white;
}

The first step is to put the following files inside a folder we conveniently named static. Next we create a main.go at the root directory, and the folder structure of your project will look like this:

.
├── main.go
└── static
├── index.html
├── script.js
└── style.css

A simple version of a file serving server written in Go takes advantage of the http.FileServer method.

main.go:

package main

import (
"fmt"
"log"
"net/http"
)

func main() {
fs := http.FileServer(http.Dir("./static"))
http.Handle("/", fs)

fmt.Printf("Server started at port 3000")
err := http.ListenAndServe(":3000", nil)
if err == nil {
log.Fatal(err)
}
}

Running the above with go run main.go and browsing http://localhost:3000 will show you the following page:

Example Static Site

Now if that is all you need, you would do well to stop here and be on your merry way. However, often times in production you may want to add certain headers to the document request response for security purposes.

X-Frame-Options Header Ramblings

An example of such a header is the X-Frame-Options header. A part of Clickjacking attacks is that the attacker may embed the main site in an <iframe> tag, thus allowing the attacker's site to snoop on the user's input while impersonating a different site.

Clickjacking Example

What the X-Frame-Options header does is that it tells the browser that it only allows the domain of say legitsite.com to put the response inside an <iframe> tag, so the illegitimate leg1tsite.com would not be able to put the content of legitsite.com inside an <iframe> because the browser would not agree to render it.

Clickjacking Prevention

Of course this is not the only security header required to secure a website, but for more information on common web vulnerabilities do check out owasp.org

Implementing headers using http.FileServer

So the idea of implementing headers is by creating a wrapper over the fs variable, serving as a middleware. Of course, while we are applying the X-Frame-Options header in the wrapper, one can do all sorts of preprocessing in the wrapper, allowing you to solve more problems than just adding headers. main.go then is changed to the following:

main.go:

package main

import (
"fmt"
"log"
"net/http"
)

func main() {
fs := http.FileServer(http.Dir("./static"))
http.Handle("/", addHeaders(fs))

fmt.Printf("Server started at port 3000")
err := http.ListenAndServe(":3000", nil)
if err == nil {
log.Fatal(err)
}
}

func addHeaders(fs http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-Frame-Options", "DENY")
fs.ServeHTTP(w, r)
}
}

With this change, you are done! All document requests to localhost will have the X-Frame-Options header, as well as any headers you wish to add.

Response With Added Headers

--

--