DEV Community

CK L
CK L

Posted on

3 1

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

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

👋 Kindness is contagious

Immerse yourself in a wealth of knowledge with this piece, supported by the inclusive DEV Community—every developer, no matter where they are in their journey, is invited to contribute to our collective wisdom.

A simple “thank you” goes a long way—express your gratitude below in the comments!

Gathering insights enriches our journey on DEV and fortifies our community ties. Did you find this article valuable? Taking a moment to thank the author can have a significant impact.

Okay