Cover of the following article

The problem

You just discovered Go (or Golang) programming language and the first thing they tell you is that is very good to make server-side application.

Once you get started and after some practice you decide that the time has arrived! You are finally ready to create your first golang server. But something is not quite working: You can see the HTML of your page, is there, is handling it, so for what reason in the world is not handling the CSS!? Even the images are not showing! Don’t throw away everything and jump to another language. This my dear friend can be a good occasion to learn a bit more of how servers application works.

Setup a basic golang server

Let’s start with a very simple go server:

package main

import (
    "net/http" // Default library for Web servers in Go
)

func main() {
    /* "/message" - relative path (of the URL) that we want to read from
    * w - the response of the server (what will appear on screen)
    * r - it countain all the requests info (get and post values, path ecc)
    */
    http.HandleFunc("/message", func(w http.ResponseWriter, r *http.Request) {
        // Let's write the body of the page
        w.Write( []byte("Hello World!") )
    })
    // Launch default go server on the selected port
    http.ListenAndServe(":8080", nil)
}

After we build and run the program, we can easily notice how going to the page http://localhost:8080/ we receive a weird looking 404 Error like this one: "404 page not found" .

Do not worry! as soon as we go at http://localhost:8080/message we will finally have our beloved “Hello World!” message. Let’s take a moment to understand what happened:

Thanks to the HandleFunc we are able to handle specific incoming request from our server, in this case when a user will go to […]/message he is going to send a request to our server and it replied as we axpected, he write our message on screen.

Q: What if we want to handle the homepage?

A: Simply instead of setting “/message” as path we can set “/” so now we can have our message appearing on the homepage (http://localhost:8080/). Like this:

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write( []byte("Hello World!") )
    })

    http.ListenAndServe(":8080", nil)
}

Q: What if we want to handle more paths?

A: If we want to create more sections then we need to have multiple HandleFunc once for each section of our site, in this way:

func main() {
    // Homepage
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write( []byte("Welcome!") )
    })
    
    // Contacts page
    http.HandleFunc("/contacts", func(w http.ResponseWriter, r *http.Request) {
        w.Write( []byte("Contanct me on Telegram at https://t.me/DazFather") )
    })
    
    // Info page
    http.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
        w.Write( []byte("This is just a test") )
    })
    
    http.ListenAndServe(":8080", nil)
}

Q: What if I want a different content each time?

A: Thanks to the write function we can embed into the page whatever we like. Let’s say we want a counter of some type, then we can do something like this:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    var visitors int

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        var message string

        visitors += 1
        if visitors == 10 {
            message = fmt.Sprint("Congrats! You are the ", visitors, " visitor you just win an iPhone0 !")
        } else {
            message = fmt.Sprint("Welcome ", visitors, " viewer!")
        }

        w.Write( []byte(message) )
    })

    http.HandleFunc("/reset", func(w http.ResponseWriter, r *http.Request) {
        visitors = 0
        w.Write( []byte("You successfully reset the viewer counters") )
    })

    http.ListenAndServe(":8080", nil)
}

so each time we refresh the homepage we get a different message

Add some HTML

Now that we have a basic understanding we can try to render a html file instead of a simple message.

At this point we have to talk about how to render the file: dynamically or statically.

In this article we are going to explore both of them but one step at time to better understand the meaning by doing it and not just by definition. So let’s create a very simple HTML file in the same folder of the main.go called index.html:

<!DOCTYPE html>
<html lang="en" dir="ltr">
    <head>
        <meta charset="utf-8">
        <title>Home</title>
    </head>
    <body>
        <h1>Hello World!</h1>
        <p>
            <strong>Welcome</strong> to our amazing site!<br>
            <em>Hope you like it!</em>
        </p>
    </body>
</html>

Now let’s read the content of this file and show it on screen:

package main

import (
    "os"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        content, _ := os.ReadFile("index.html")
        w.Write(content)
    })

    http.ListenAndServe(":8080", nil)
}

Ah that’s way better!, Let’s read what we have just written. Every time we receive the request from a user to show the homepage we are now reading the content of index.html saving it into content and then writing that content on the body of the response that the user will receive.

This way of doing is called dynamic rendering and it may sound a bit un-optimized (the fact that we read it every time) but it have two major advantages:

  • It’s more secure: we don’t give user direct access to the files and the structure of our site or server but we just extract the content only from what the user it’s supposed to get, nothing less, nothing more.

For instance try to create a folder inside the main.go directory and move the html file there and even rename it, now grab the path and put it inside the os.ReadFile. When you are going to run the server you are going to notice that the user have no idea that that file is there or what names it have or how your site is structured

  • it’s dynamic: try to edit the HTML inside the file, you will notice that as soon as you refresh you get the new content, no need to restart anything

But let’s now bring some colors…

Render the CSS

First we need to create a file that I’m going to name style.css . This should be good enough to see the difference:

html, body {
    margin: 0;
    padding: 0;
    font-size: 16px;
    font-family: sans-serif;
}

body {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    width: 100vw;
    height: 100vh;
    background-color: hsl(0, 0%, 20%);
    color: hsla(0, 0%, 100%, 0.7);
    overflow: hidden;
}

h1, strong {
    color: white;
}

h1 {
    margin: 0;
    font-size: 3em;
}

To add the CSS in the HTML file we can simply add the <link rel="stylesheet" href="style.css"> into the head tag but here is bad news… Is not working!

Remember when I said that via dynamical rendering we give to the user the possibility to see only the content that we want? That’s it! We didn’t specify that the user can have access to this stylesheet that we just created.

If you tried to create another HandleFunc to read the CSS file and return the content on screen you have for sure notice that it will not work too, that’s probably because you did not take in consideration the header of the request but just the body using Write.

Dynamically rendering CSS can be very tedious and 90% of the time there is no need for it. In other words: it’s not worth it. Let’s try another approach, let’s try to render files statically, like this:

package main

import (
	"net/http"
)

func main() {
	var HandlerResources = http.FileServer(http.Dir(""))

	http.Handle("/", HandlerResources)

	http.ListenAndServe(":8080", nil)
}

And now if we have everything in the same folder and go to our homepage we should have our beautiful site finally working and the CSS is renderingBut how?

Serving a directory statically means in a nutshell giving the user the possibility to navigate through all the files wich are inside. Very well optimized for both speed and lines of code, sure, but maybe not the best for security because user can have access to everything and really bad things could happen… pretty unfortunate don’t you think?

Is there a way we can combine the security of the dynamical approach with the manageability of the static one? Yes! Let’s see how

Secure it

First of let’s create a folder (that I’m going to call resources) inside our directory to have something that looks like this:

project three

main.go

index.html

/resources

style.css

At this point we are going to say that only the folder resources is going to be served statically served, so we can put all our images, JS scripts and CSS stylesheets, that we want people to have access to.

The homepage (and all the rest) is going to be serve dynamically

main.go

package main

import (
	"net/http"
	"os"
)

const RESOURCE_DIR = "/resources/"

func main() {

	// Resources - Statically served
	var HandlerResources = http.StripPrefix(RESOURCE_DIR,
		http.FileServer(http.Dir("."+RESOURCE_DIR)),
	)
	http.Handle(RESOURCE_DIR, HandlerResources)

	// Homepage - Dynamically served
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		content, _ := os.ReadFile("index.html")
		w.Write(content)
	})

	http.ListenAndServe(":8080", nil)
}

index.html

<!DOCTYPE html>
<html lang="en" dir="ltr">
   <head>
       <meta charset="utf-8">
       <link rel="stylesheet" href="/resources/style.css">
       <title>Home</title>
   </head>
   <body>
       <h1>Hello World!</h1>
       <p>
           <strong>Welcome</strong> to our amazing site!<br>
           <em>Hope you like it!</em>
       </p>
   </body>
</html>

resources/style.css

html, body {
    margin: 0;
    padding: 0;
    font-size: 16px;
    font-family: sans-serif;
}

body {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    width: 100vw;
    height: 100vh;
    background-color: hsl(0, 0%, 20%);
    color: hsla(0, 0%, 100%, 0.7);
    overflow: hidden;
}

h1, strong {
    color: white;
}

h1 {
    margin: 0;
    font-size: 3em;
}

Hope you found this article useful. If you did consider sharing it with a person who is having similar issue. Feel free to contact me for any errors, suggetions or whatever

- DazFather