GoLang Tutorial - Web App 7 (Function Literals and Closures)
Continued from Web Application Part 6.
In this post, we'll be reducing repeated lines of code, especially, when we try to catch the error condition in each handler which introduces a lot of repeated code.
What if we could wrap each of the handlers in a function that does this validation and error checking?
Go's function literals provide a powerful means of abstracting functionality that can bail us out here. Note that a function literal, or lambda, represents a function without a name. It is declared inline without a name, and it is also called an anonymous function. Anonymous functions are useful when we want to define a function inline without having to name it. Here is the well known sample of the function we used in Closures and Anonymous Functions:
As we can see from the output, it printed 1, 2, and then 3, and so on. This means the intSeq() is keeping track of the values of i even though it's gone out of scope (outside its function body). That's the key point of a closure.
A closure is a function value (nextInt()) that references variables (i) from outside its body (intSeq()). But still remembers the value.
Note that Closures are functions which enclose other functions, often with a function return type. The inner functions are able to reference the variables around the closure function:
Also, note that the two instances (nextInt and newInts) are independent of each other, they are separate instances!
Ok, we digressed a bit.
Let's re-write the function definition of each of the handlers to accept a title string.
from:
func viewHandler(w http.ResponseWriter, r *http.Request) {} func editHandler(w http.ResponseWriter, r *http.Request) {} func saveHandler(w http.ResponseWriter, r *http.Request) {}
to:
func viewHandler(w http.ResponseWriter, r *http.Request, title string) {} func editHandler(w http.ResponseWriter, r *http.Request, title string) {} func saveHandler(w http.ResponseWriter, r *http.Request, title string) {}
Now let's define a wrapper function (makeHandler) that takes a function of the above type, and returns a function of type http.HandlerFunc (suitable to be passed to the function http.HandleFunc):
func makeHandler(fn func (http.ResponseWriter, *http.Request, string)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Here we will extract the page title from the Request, // and call the provided handler 'fn' } }
The returned function is called a closure because it encloses values defined outside of it. In this case, the variable fn (the single argument to makeHandler) is enclosed by the closure. The variable fn will be one of our save, edit, or view handlers.
Now we can take the code from getTitle and use it here (with some minor modifications):
func getTitle(w http.ResponseWriter, r *http.Request) (string, error) { m := validPath.FindStringSubmatch(r.URL.Path) if m == nil { http.NotFound(w, r) return "", errors.New("Invalid Page Title") } return m[2], nil // The title is the second subexpression. }
So, our makeHandler will look like this:
func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { m := validPath.FindStringSubmatch(r.URL.Path) if m == nil { http.NotFound(w, r) return } fn(w, r, m[2]) } }
The closure returned by makeHandler is a function that takes an http.ResponseWriter and http.Request (in other words, an http.HandlerFunc).
The closure extracts the title from the request path, and validates it with the TitleValidator regexp. If the title is invalid, an error will be written to the ResponseWriter using the http.NotFound function. If the title is valid, the enclosed handler function fn will be called with the ResponseWriter, Request, and title as arguments.
Now we can wrap the handler functions with makeHandler in main, before they are registered with the http package:
func main() { http.HandleFunc("/view/", makeHandler(viewHandler)) http.HandleFunc("/edit/", makeHandler(editHandler)) http.HandleFunc("/save/", makeHandler(saveHandler)) log.Fatal(http.ListenAndServe(":8080", nil)) }
Finally we remove the calls to getTitle from the handler functions, making them much simpler.
From:
func viewHandler(w http.ResponseWriter, r *http.Request) { title, err := getTitle(w, r) if err != nil { return } p, err := loadPage(title) if err != nil { http.Redirect(w, r, "/edit/"+title, http.StatusFound) return } renderTemplate(w, "view", p) }
To:
func viewHandler(w http.ResponseWriter, r *http.Request, title string) { p, err := loadPage(title) if err != nil { http.Redirect(w, r, "/edit/"+title, http.StatusFound) return } renderTemplate(w, "view", p) }
Same to other Handlers:
func editHandler(w http.ResponseWriter, r *http.Request, title string) { p, err := loadPage(title) if err != nil { p = &Page{Title: title} } renderTemplate(w, "edit", p) } func saveHandler(w http.ResponseWriter, r *http.Request, title string) { body := r.FormValue("body") p := &Page{Title: title, Body: []byte(body)} err := p.save() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, "/view/"+title, http.StatusFound) }
Again, let's check our app:
So, everything is working as expected.
Here is the code so far:
This file is available: wiki-app-7.go
Go Tutorial
- GoLang Tutorial - HelloWorld
- Calling code in an external package & go.mod / go.sum files
- Workspaces
- Workspaces II
- Visual Studio Code
- Data Types and Variables
- byte and rune
- Packages
- Functions
- Arrays and Slices
- A function taking and returning a slice
- Conditionals
- Loops
- Maps
- Range
- Pointers
- Closures and Anonymous Functions
- Structs and receiver methods
- Value or Pointer Receivers
- Interfaces
- Web Application Part 0 (Introduction)
- Web Application Part 1 (Basic)
- Web Application Part 2 (Using net/http)
- Web Application Part 3 (Adding "edit" capability)
- Web Application Part 4 (Handling non-existent pages and saving pages)
- Web Application Part 5 (Error handling and template caching)
- Web Application Part 6 (Validating the title with a regular expression)
- Web Application Part 7 (Function Literals and Closures)
- Building Docker image and deploying Go application to a Kubernetes cluster (minikube)
- Serverless Framework (Serverless Application Model-SAM)
- Serverless Web API with AWS Lambda
- Arrays vs Slices with an array left rotation sample
- Variadic Functions
- Goroutines
- Channels ("<-")
- Channels ("<-") with Select
- Channels ("<-") with worker pools
- Defer
- GoLang Panic and Recover
- String Formatting
- JSON
- SQLite
- Modules 0: Using External Go Modules from GitHub
- Modules 1 (Creating a new module)
- Modules 2 (Adding Dependencies)
- AWS SDK for Go (S3 listing)
- Linked List
- Binary Search Tree (BST) Part 1 (Tree/Node structs with insert and print functions)
- Go Application Authentication I (BasicAuth, Bearer-Token-Based Authentication)
- Go Application Authentication II (JWT Authentication)
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization