Learning Golang, Day 14 – Removing Code Duplication
Today, on day 14, I created a custom method to remove code duplication creeping into the weather station codebase. Come read the fun story behind getting that done.
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.
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.
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.
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.
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:
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!
Today, on day 14, I created a custom method to remove code duplication creeping into the weather station codebase. Come read the fun story behind getting that done.
Here we are on day 12. I didn’t solve anything. I’m feeling that these exercises are becoming arbitrary and pointless.
Here we are on day 11, where I solved the Stringers Exercise.
Here we are on day 10. Today I read about and played with Type Assertions, Type Switches, and Stringers in Go.
Please consider buying me a coffee. It really helps me to keep producing new tutorials.
Join the discussion
comments powered by Disqus