Welcome to the ninth article in our series on learning GO; All the previous articles in this series can be found on the links below:
- It is time to “Go” and learn how to code with Go
- It is Time to Go: our journey into grown-up code part 2
- It is Time to Go: our journey into grown-up code part 3
- It is Time to Go: our journey into grown-up code Part 4
- It is Time to Go: our journey into grown-up code part 5
- It is Time to Go: our journey into grown-up code Part 6
- It is Time to Go: our journey into grown-up code part 7
- It is Time to Go: our journey into grown-up code part 8
Our journey to date has covered a lot of ground, and we have slowly built up an understanding of several core features and concepts. As a result, we are genuinely building firm foundations.
- The core Variables types
- Variadic variables
- Redeclaration
- Constants
- inputs
- Structs
- Pointers
- Interfaces
- Function basics
- Conditional statements, if statements and switch statements.
- Loops, breaks, deferment, continue, goto, return and recover
- Range
- Importation of built-in functions (“fmt”, “sync”, “reflect”, “strings”, “strconv”, “math”, “net/html”, “html/template”)
- Arrays and Slices
At the end of our last article, we extended our understanding by looking at the slice. How it was both similar and different to the array, we investigated the various methods of creating and manipulating them. We also created an array of 98 numbers, and split it into two slices, one that contained all prime numbers between 2 and 99 and the other that contained all the composite numbers. Finally we introduced three new built-in functions, and re-engineered our prime and composite function to take advantage of the “math” function and display the output of the package in a basic HTML page.
What are we looking at today?
OK, so now we know that we can build a simple website. Let’s investigate that a bit further. Today, I am going to look at getting a little interactive with our code. So, we will create a very simple static site, with a couple of pages, but we will look at creating a form for us to input something and the have that something display in a table. From an architectural perspective, our website will look something like the image below.
By now we should be able to recognise a lot of the code.
package main import ( "fmt" "html/template" "log" "net/http" "math" ) type Entry struct { Name string Address string } var entries []Entry func main() { http.HandleFunc("/", rootHandler) http.HandleFunc("/primes", primesHandler) http.HandleFunc("/form", formHandler) http.HandleFunc("/table", tableHandler) log.Fatal(http.ListenAndServe(":8080", nil)) } func rootHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "This is the root page. It is pretty boring, but it is expected to look like this.") } func primesHandler(w http.ResponseWriter, r *http.Request){ var primes []int var composites []int for i := 2; i <=99; i++ { if isPrime(i){ primes = append(primes, i) }else{ composites = append(composites, i) } } tmpl := template.Must(template.ParseFiles("prime.html")) data := struct { Primes []int Composites []int }{ primes, composites, } err := tmpl.Execute(w, data) if err != nil { fmt.Println(err) } } func isPrime(n int) bool{ if n <=1{ return false } for i := 2; i <=int(math.Sqrt(float64(n))); i++{ if n%i == 0 { return false } } return true } func formHandler(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { name := r.FormValue("name") address := r.FormValue("address") entries = append(entries, Entry{name,address}) http.Redirect(w,r,"/table",http.StatusSeeOther) return } tmpl := template.Must(template.ParseFiles("form.html")) tmpl.Execute(w,nil) } func tableHandler(w http.ResponseWriter,r *http.Request) { tmpl := template.Must(template.ParseFiles("table.html")) tmpl.Execute(w,map[string]interface{}{ "Entries": entries, }) }
For example, “func primeHandler” is an exact copy of the code we used in our first web site in our last post, now we have simply changed he function to refer to a different html template “prime.html”, which is actually simply just a rename of the original “index.html” file of the previous post. To be fair I forget to rename the index file in the code which resulted in some pretty nasty errors rolling down my terminal screen, I have captured a small snippet of the output for your delight and delectation.
Fortunately after forcing an exit from the program, a quick return to the original command entry pointed me in the right direction.
That is a pretty large hammer of an attention getter. So a simple change to the following line replacing the word “index” with “prime” and rerun to test and all was well.
tmpl := template.Must(template.ParseFiles("prime.html"))
If you want to refresh your memory on what the two functions “func primesHandler” and “func isPrime” actually do, have a quick re-read the previous article found here
OK let’s look at the rest of the code
The first block of code of not is shown below:
type Entry struct { Name string Address string } var entries []Entry
Here we have defined a new data type called “Entry” using the struct keyword. Our struct has has two fields: Name and Address, both of which have been declared as being of the type string. We have then declared variable called entries, which is a slice (a dynamically-sized array) created of the Entry values that are passed to the Entry Struct. Now you may be wondering why this has been declared outside of a function, this is because the values will be available to all functions declared in this package. Next we move on to the “func main” function.
func main() { http.HandleFunc("/", rootHandler) http.HandleFunc("/primes", primesHandler) http.HandleFunc("/form", formHandler) http.HandleFunc("/table", tableHandler) log.Fatal(http.ListenAndServe(":8080", nil)) }
As we already know the main() function is common to ever package, here we have created a http server, that listens and serves on “port 8008”, whilst also creating a log if there is an error.
log.Fatal(http.ListenAndServe(":8080", nil))
The Core of the functions is handled by the four “http.HandleFunc” functions which are used to register functions to handle specific HTTP requests. In our case, we have declared four handlers registering the functions “rootHandler”, “primesHandler”, “formHandler”, and “tableHandler”. Each of these handlers are called when the server receives requests for the “/” page’s, the “/primes” page’s, the “/form” page’s, and finally the “/table” page’s URLs, respectively.
Our “rootHandler” function is very simple, in fact it is basically just a print-out of some defined text
func rootHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "This is the root page. It is pretty boring, but it is expected to look like this.") }
In our case a simple “This is the Root page, IT is pretty boring, but it is expected to look like this” boring I know.
We have already discussed the “primeHandler” function so we will look at “formHandler” next.
func formHandler(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { name := r.FormValue("name") address := r.FormValue("address") entries = append(entries, Entry{name,address}) http.Redirect(w,r,"/table",http.StatusSeeOther) return } tmpl := template.Must(template.ParseFiles("form.html")) tmpl.Execute(w,nil) }
Our “formHandler” function gathers the information that is going to be passed to our Entry slice. It is declared with has two arguments: “w” and “r”. “w” which is of the type “http.ResponseWriter”, this is an interface that represents the server’s response to an HTTP write request and “r” is of type “*http.Request”, which is a pointer to a struct that represents the client’s HTTP read request.
As we move into the function proper we hit a good old “If” statement, what this is saying is that if “r.Method” is “POST”, this position is decided by the clicking of the “submit” button on the html page (we will discuss this later), once the Post method has been activated, the function retrieves the values of the “name” and “address” form fields and appends them to our slice “entries” that is created out of our “Entry” structs. The function then redirects the client to the “/table” URL using the `http.StatusSeeOther` status code. Our last two lines use the “template” package from the standard library to parse and execute our HTML template file named “form.html”.
tmpl := template.Must(template.ParseFiles("form.html")) tmpl.Execute(w,nil)
The “template.Must” function is used to wrap a call to “template.ParseFiles”, which returns a “*template.Template” and an error. If the error is non-nil, “template.Must” will panic. See the section on primeHander for an explanation. If it is nil, it returns the “*template.Template”. The Execute method is then called on the “*template.Template” to write the output of executing the template to the “http.ResponseWriter”.
Our Template html file “form.html” looks like below, it is a simple file that basically creats two input fields “name” and “address” each are of the type “text” and finally there is a “submit” button created that will effect the “POST” situation needed for the “formHandler” function.
<!DOCTYPE html> <html> <head> <title>Form</title> </head> <body> <h1>Form</h1> <form action="/form" method="POST"> <label for="name">Name:</label><br> <input type="text" id="name" name="name"><br> <label for="address">Address:</label><br> <input type="text" id="address" name="address"><br><br> <input type="submit" value="Submit"> </form> </body> </html>
Our final function is “tableHandler” at is base level this function handles the request for a table.
func tableHandler(w http.ResponseWriter,r *http.Request) { tmpl := template.Must(template.ParseFiles("table.html")) tmpl.Execute(w,map[string]interface{}{ "Entries": entries, }) }
You will see a lot of repetition in this function, we have the same two arguments for the function declaration and the same “tmpl” variable created, this time pointing to the “table.html” file. The last line of code calls the “Execute” method on a *template.Template using the variable “tmpl”. The first argument to Execute is an “io.Writer”, in this case, the ”http.ResponseWriter” named “w”. The second argument is the data to be passed to the template for execution. In this case, it is a map with a single key “Entries” and value entries. The map is of type “map[string]interface{}”, which means that the keys are strings and the values can be of any type. However, where do these values come from? Remember this line in the “formHandler” function.
http.Redirect(w,r,"/table",http.StatusSeeOther)
Yes, that is correct; we are redirecting the output of the “formHandler” function to the input of “/table”. So our input gets added to the struct and is displayed
This template html file is shown below.
<!DOCTYPE html> <html> <head> <title>Table</title> </head> <body> <h1>Table</h1> <table> <tr> <th>Name</th> <th>Address</th> </tr> {{range .Entries}} <tr> <td>{{.Name}}</td> <td>{{.Address}}</td> </tr> {{end}} </table> </body> </html>
This html template displays the inputted name and address in a simple table.
Lets GO and make the magic happen.
Previously we have used the “go run main.go” command to run the program; this complies the code to run on the pc, but at runtime. Very similar to how you would run a terraform script, you could say that your code is interpreted at run time. However, this is not how proper programs run, these have been converted into a binary program. Today I am going to introduce you to the “build” command. This is command takes your code and builds an executable from it.
Running that is not spectacular. Infact it just appear to pause at the terminal and then return to prompt. However, if you now do a ”dir” or “ls” of your working directory you will notice a file called “main.exe”.
Now this appears to be a rather large file considering the size of the original source code we have written. This is understandable as this file is a distributable executable file. It contains all the necessary files, resources etc to run the program, like the imported functions and support code. Now it is important to not that it will not contain any ancillary code like the html files in our case, these will need to be packaged up with the binary and due to the way we wrote our code placed in the same folder as our binary file. We can prove this by moving the main.exe file to its own folder and running the executable “.\main.exe”.
Do you recognise that return? Yes, we have the panic again caused by the programs inability to locate the form.html file. Returning the main.exe to its original location allows the code to run properly. So now that the file is back in the correct folder, rerun “.\main.exe”.
Open your favourite browser and point it to “http://localhost:8080”. If everything has correctly complied, you should receive the following webpage.
Move to “localhost:8080/primes” and you will receive the list of prime and composite numbers we had configured in the previous post.
Next navigate to “localhost:8080/forms”
Enter your name and address and click submit. You will be redirected the “/table” page, which will be displaying your entered values.
Now for bells and whistles, repoint your browser at “/forms” and enter in another name and address and click “submit”.
Now that is impressive. Currently these values are only contained in the struct, and as such they will be purged from memory on exiting the program, but you must admit. It is starting to look like we are doing proper programming.
Summary
Once again, we have covered and extended our knowledge base. Delving into the html functions a little deeper. We now have the capability to capture input, store it is a struct and pass that to another function for action. I am starting to see how we can start to put together wrapper pages to feed variable directly other solutions. If there is anything that you wish to have explained, please drop a line in the comments.