DEV Community

CK L
CK L

Posted on

Serving Static Files with Custom Headers using Golang

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>


Enter fullscreen mode Exit fullscreen mode

script.js:



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


Enter fullscreen mode Exit fullscreen mode

style.css:



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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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)
    }
}


Enter fullscreen mode Exit fullscreen mode

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


Example Static Site

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 docume
nt 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

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

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)
    }
}


Enter fullscreen mode Exit fullscreen mode

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

Response With Added Headers

Top comments (0)