Learning Golang, Day 13 – Regular Expressions and the Gorilla Mux Router

Learning Golang, Day 13 – Regular Expressions and the Gorilla Mux Router

Here we are on day 13. Today, I continued learning Golang by working on the Golang version of my PHP/Python weather station, adding a function to render static pages. Let me share my learnings with you.


:idseparator: - :idprefix: :experimental: :source-highlighter: rouge :rouge-style: pastie :imagesdir: /images

After an unexpected hiatus, I’m back learning Golang and continuing to grow my knowledge in this wonderful, fun, and light language. If you’ve been following link:/tags/golang/[my journey so far], I’ve been working through https://go.dev/tour/welcome/1[the Go Tour] as a way of having a proper sense of structure to my learning efforts.

However, toward the end of the time, before the unexpected break, I felt that it was becoming quite arbitrary to follow that approach. This is because the things I was learning weren’t tied to a practical project which held any genuine sense of meaning for me.

Given that, during my time off, I had a good long think about how I’d approached learning Golang so far and decided to take a different approach, one that was more akin to how I learn best. I decided to learn in a hands-on way by continuing to work on porting https://github.com/settermjd/php-python-weather-station[my weather station project] (written in a combination of PHP and Python).

This approach has several benefits.

  • It’s a project that means something to me.
  • It’s a project that I can use.
  • It’s a project that I could work on with others.
  • The development decisions will be more akin to ones I would make if I were working in a team with other developers, and not just having fun, in an off the cuff, freewheeling kind of way.
  • I can have fun on a project that I want to spend time on and build over time.

This is not to be demeaning nor discourteous toward the Go Tour. It is an excellent resource, one that is extremely beneficial, and highly recommended. However, I feel that stepping through it from one thing to the next isn’t the best way for me from here on in.

So I looked around for some good alternative options and felt that the best thing to do, the one that would make most sense, would be to continue porting my weather station to Go. I looked at the current state of the Go version of https://github.com/settermjd/go-weather-station[the weather station] and compared it to the PHP/Python version, and picked the easiest thing that I could: implementing static pages functionality. Sure, I could have implemented other functionality, but I wanted to ease back in after my time off.

At first, I wasn’t quite sure how to implement the functionality. After a bit of thought, I figured I’d start by taking most of the existing functionality for rendering templates in DefaultRouteHandler. Then, I’d determine the route template to render based on retrieved path information in the requested URL.

For example, if the route were /disclaimer, the route’s template would be templates/routes/disclaimer.html. If it were privacy-policy, the route’s template would be templates/routes/privacy-policy.html. That didn’t seem too hard to achieve.

Here’s my first version.

[source,go]

func (ctx *HandlerContext) HandleStaticRoute(w http.ResponseWriter, r *http.Request) { path := strings.TrimPrefix(r.URL.Path, “/”) routeTemplate := filepath.Join(“templates”, “routes”, path+".html") footerTemplate := filepath.Join(“templates”, “footer.html”) layoutTemplate := filepath.Join(“templates”, “layout.html”)

tmpl := template.Must(template.ParseFiles(routeTemplate, footerTemplate, layoutTemplate))
data := DisclaimerPageData{PageTitle: "Disclaimer"}
err := tmpl.ExecuteTemplate(w, "layout", data)
if err != nil {
	log.Println(err.Error())
	http.Error(w, http.StatusText(404), 404)

	return
}

}

In the code above, the requested URL’s path is retrieved, and any leading forward slash, if present, is stripped. The retrieved path is then concatenated with .html, building the template to render.

After that, an attempt is made to render the template. If an error occurs, an https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404[HTTP 404] is returned, as it made the most sense to me in that context.

To use the handler function, I added the following line in the main() function.

[source,go]

http.HandleFunc("/disclaimer", ctx.HandleStaticRoute)

With these changes made, /disclaimer could now be requested within the application.

However, to support the rest of the required static routes, I’d have to add a call to HandleFunc(), similar to the one above, for each static page required (about, cookie-policy, datenschutzerklaerung, disclaimer, disclaimer, impressum, privacy-policy), as it doesn’t support regular expressions.

There’s nothing wrong with that, and it is very clear as to what’s being requested. But, to me, regular expressions would mean that I only had to call HandleFunc once. After a bit of googling, I decided to use https://github.com/gorilla/mux[Gorilla Mux], one of the most well-known third-part Go routers, refactoring the HandleStaticRoute method as follows.

[source,go]

func (ctx *HandlerContext) HandleStaticRoute(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) path := params[“path”] routeTemplate := filepath.Join(“templates”, “routes”, path+".html") footerTemplate := filepath.Join(“templates”, “footer.html”) layoutTemplate := filepath.Join(“templates”, “layout.html”)

tmpl := template.Must(template.ParseFiles(routeTemplate, footerTemplate, layoutTemplate))
data := DisclaimerPageData{PageTitle: "Disclaimer"}
err := tmpl.ExecuteTemplate(w, "layout", data)
if err != nil {
	log.Println(err.Error())
	http.Error(w, http.StatusText(404), 404)

	return
}

}

Here, the request variables are retrieved by passing the request object to mux.Vars(). From the request variables, path is extracted and used to build the desired template via string concatenation.

Then, I refactored the main() function to use the revised version of HandleStaticRoute, as follows:

[source,go]

func main() { // … preceding code

r := mux.NewRouter()

fs := http.FileServer(http.Dir("assets/"))
r.Handle("/assets/", http.StripPrefix("/assets/", fs))

r.HandleFunc("/", ctx.DefaultRouteHandler)
r.HandleFunc(
    "/{path:about|disclaimer|cookie-policy|datenschutzerklaerung|disclaimer|impressum|privacy-policy}",
    ctx.HandleStaticRoute,
)

// Boot the application
err = http.ListenAndServe(":8001", r)
if err != nil {
	log.Println(err.Error())
	return
}

}

There isn’t a lot of difference. The primary one being that the Gorilla Mux router is being used in place of https://pkg.go.dev/net/http[the default router] in Go standard library’s. Then, after the default route, a call to HandleFunc using a regular expression for the path is made. The key thing to note is that if the route matches, the path will be stored as the route parameter named path, making it easier to retrieve and use.

While I feel that I should refactor the duplicate template setup logic into a separate function, I’m happy with the change, and with what I’ve learned today.

My key takeaway is that I’ve grown a deeper appreciation for https://go.dev/tour/methods/9[Go Interfaces], and because of them, how trivial it can be to substitute one package for another, in this case the http router in Go’s standard library with Gorilla Mux instead.

Anyway, that’s me for today. See you, next time!


You might also be interested in these tutorials too...


Want more tutorials like this?

If so, enter your email address in the field below and click subscribe.

You can unsubscribe at any time by clicking the link in the footer of the emails you'll receive. Here's my privacy policy, if you'd like to know more. I use Mailchimp to send emails. You can learn more about their privacy practices here.

Join the discussion

comments powered by Disqus