Building a Webserver in Go


Introduction

Welcome back, dear readers! Your favorite truthbringer is back in action, fresh out of a battle with the common cold, and ready to dive back into the digital trenches! Today, we’re ditching the cat jokes (sorry, feline fans!) and dialing down the sass. So, buckle up, and let’s get our code on, yours truly!

Today`s Goal

We’re on a mission to spin up a working webserver that not only greets visitors but also assigns them a unique user number. If they come back, boom—you’ll see their number again! Thanks to the magic of the sessions package, we’re turning this dream into code.

What is a Webserver

A webserver isn’t just a software or a hardware; it’s your website’s best friend. It serves up your website’s files to visitors, applies rules, runs functions, and generally makes sure your site acts like it should. In simple terms, it’s the brains behind the web’s brawn.

Why Go?

Why Go? Because it’s straightforward, robust, and its error handling is a thing of beauty. Go makes the hard stuff easier, and who doesn’t want that?


Programming

Let’s jump right in. This guide is all about hands-on results and crystal-clear explanations.

Prerequisite

  • Go installed on your machine
  • A computer (Well, obviously!)
  • A sprinkle of motivation (Strongly recommended but optional)

Create a Project

First things first: Let`s set up a Go project. Fire up your terminal and make a new folder just for your project.

go mod init your_project_name

Got it? Excellent! Your project now has its own little digital plot.

Your Source File

Now, let’s create the main hub for our code, aptly named “main.go”.

touch main.go && vim main.go

The Basics

Here’s a quick “Hello World” to get us rolling:

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello World!")
}

What’s happening here?

  1. Package Declaration: We declare package main, signaling that this file is not a package and should compile to a standalone executable.
  2. Imports: We’re pulling in fmt, a library for formatting text, which we need to print our greeting.
  3. Main Function: This is where the magic starts. It’s the first thing that runs in our program.

Create the Server

Let`s build the backbone of our webserver, capable of listening on port 8080.

package main

import (
    "fmt"
    "net/http"
)

func main() {
    router()
}

func router() {
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        fmt.Println("Error starting server:", err)
        return
    }
}

Pop open your browser and hit up http://localhost:8080. Nothing fancy yet, right? That’s because we need to add some functionality.

http.ListenAndServe(":8080", nil)

This function is straightforward but powerful:

  1. Port: Specifies the server should listen on port 8080.
  2. Handler: Uses a default handler if none is specified, which is what nil means here.

Your First Webpage

Let’s keep it simple for our first page. No flashy UI here—just the essentials.

package main

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

func main() {
    router()
}

func homepage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "You have reached the homepage")
}

func router() {
    http.HandleFunc("/", homepage)
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Println("Error starting server:", err)
    }
}

We’ve just set up a route. That means when someone visits the root URL, they’ll get a greeting from our server.

http.HandleFunc("/", homepage)

Here`s what each part does:

  1. Route Path: The root URL, denoted by “/”.
  2. Handler: The function homepage that handles requests to the root URL.

Our simple handler looks like this:

func homepage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "You have reached the homepage")
}

It uses the response writer to send a message back to the visitor.

Homepage

That’s your basic web server up and running! But let’s not stop here; we’ve got middleware to cover next!

Middleware Magic

Middleware is your code’s gatekeeper. It checks requests before they reach their intended destination—perfect for logging visits or adding security checks.

package main

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

func main() {
    router()
}

func homepage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "You have reached the homepage")
}

func router() {
    http.HandleFunc("/", homepage)
    http.Handle("/middleware", middleware(homepage))

    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Println("Error starting server:", err)
    }
}

func middleware(next http.HandlerFunc) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("Function called")
        next.ServeHTTP(w, r)
    })
}

Here’s how our middleware works:

  • It logs “Function called” every time the /middleware route is accessed.
  • It then proceeds to run whatever function was supposed to handle that route, ensuring seamless operation.

Middleware is crucial in asynchronous development as it organizes tasks such as logging and authentication into manageable segments. It also allows for additional condition checking before the initial function is called, enhancing control flow without cluttering the core logic. This approach keeps the code clean and modular, significantly boosting the scalability and maintainability of asynchronous applications. Visit http://localhost:8080/middleware and you’ll see it in action, with a log entry to prove it.

Middleware

Session Managment

We’ll also set some settings on how these “cookies” should be handled. Now we introduce the gorilla/sessions package, which is a powerful library for handling sessions in Go. This allows us to track and persists user data across multiple requests. We use that in oder to assign users user ID’s. But let’s first create a few variables first. We’ll use these for keeping track of the user count and assign them to sessions.

var (
	userCount = 0
	store     = sessions.NewCookieStore([]byte("test"))
)
store.Options = &sessions.Options{
    Path:     "/", 
    MaxAge:   3600 * 8, // 8 hours
    HttpOnly: true,     // Mitigate the risk of client side script accessing the protected cookie
    Secure:   true,     // Ensure cookies are sent over HTTPS
}

Now onto the middleware. Just a few lines more and we’ll have reached our goal.

func middleware(next http.HandlerFunc) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		session, err := store.Get(r, "session") // Create local session instance to retrieve/send cookies
		if err != nil {
			log.Println("Error retrieving session: ", err)
		}

		if session.Values["UserCount"] == nil { // Ask if cookie already holds a value for "UserCount"
			session.Values["UserCount"] = userCount // If not assign value
			userCount++ // Increment for next client
			err = session.Save(r, w) // Save session and write to client
			if err != nil {
				log.Println("Could not save session: ", err)
				return
			}
		}
		log.Printf("Function called by %d's User", session.Values["UserCount"]) // Log client's "UserNumber"
		next.ServeHTTP(w, r)
	})
}

And there you have it. The complete code can be found here.


Wrapping Up

Congratulations! You’ve just built a fully functional web server in Go that not only welcomes visitors but also assigns them a unique identifier, remembered across sessions. Throughout this tutorial, we’ve explored the fundamentals of HTTP servers, delved into middleware, and implemented basic session management—a solid foundation for any aspiring Go developer.

Key Takeaways

  • Web Server Basics: We started by setting up a simple server that could handle HTTP requests and display a basic greeting.
  • Middleware Integration: Next, we added middleware to our server, enhancing its functionality with session tracking and logging capabilities.
  • Session Management: Using the gorilla/sessions package, we demonstrated how to manage user sessions, ensuring each visitor receives a unique user number that persists across their visits.

Trust me, darlings, it was a Herculean task to keep the sass at bay and deliver just the facts. But fear not, the sass queen is just on a little break, not gone for good!

Here’s a joke I found on the internet: (I couldn’t resist)

What is a cat’s way of keeping law and order?

Claw Enforcement.