Developing Web Apps
in Go Language
Juan E. Vargas
https://bit.ly/4cUMA1n
Outline
Covering in detail the content in these slides may take N lectures of a standard CS course about web development, because the topics that must be covered include:
Go Language
Go has become by far my favorite computer programming language.
See
https://bit.ly/4fmuE1i
Web Apps, Microservices and Go
One of the main reasons for developing Web apps and microservices in Go is the growing number of libraries and tools available at the server and the client side.
Another reason is Go’s native concurrency features.
Some people consider that server-side HTML generation is not the best way to build web apps. The most current trend is to do as much of the rendering as possible on the client side using JavaScript.
Apps whose UI is fully JS-driven are sometimes called Single Page Applications (SPA), and in the opinion of some who know more that me, SPAs are a better architecture and the way to go (no pun intended) especially for microservices.
Web Apps, SPA and Go
Under the SPA model, the server only serves data, typically as JSON, eliminating the need for construction of HTML content there.
The complexities involved in using a scripting language on the server are, in the view of many, is not worth the trouble, especially when considering that Python or Ruby bring so little when all of the output is done via JSON.
Go native support of concurrency eliminates the need of using a global interpreter lock mechanism or GIL. That mechanism is used in Python, Ruby, etc to synchronize thread execution, which is how Apache server and other frameworks handle multiple concurrent requests.
Web Dev and Go
Using Go, there is no need to add language-specific libraries on the server to handle concurrency, because Go runs concurrency natively.
This significantly reduces the complexity of web development, because there is no need to constantly update runtime API versions and/or concurrency tools; Go already provides concurrency at a binary level.
Go can run concurrent tasks in the background, thus no need for tools like Resque.
Go can run web apps as a single process, making caching trivial. As a consequence, APIs like Memcached or Redis are not necessary either.
Go can handle an unlimited number of parallel connections, eliminating the need for a front-end guard like Nginx.
Web Dev and Go
With Go, the tall stack of Python, Ruby, Bundler, Virtualenv, Unicorn, WSGI, Resque, Memcached, Redis, etc is reduced to just one binary.
The only other component that might be needed is a decent database (PostgreSQL, MySQL,...).
It’s important to note that all of these tools could be used, but with Go, there is the option of working without them.
Additionally, a Go program will easily outperform any Python/Ruby app by at least one order of magnitude. Also, Go requires less memory and fewer lines of code.
Background
We need to explore/review/understand a few key ideas such as
Objects in Go
An object in Go is a value or a variable that has methods; a method is a function associated to a type.
type Circle struct {
x, y, r float64
}
func (o *Circle) area() float64 {
return math.Pi * o.r*o.r
}
func main () {
c := Circle{0,0,5}
ac := c.area()
}
Note the different signatures in main and in area
// area is a method associated to objects of type Circle
func (o type) area ( args ) { body }
//main is a standard func
func main ( args ) { body }
An interface is a named collection of method signatures.
type Circle struct {� x, y, r float64�}
func (c *Circle) area() float64 {� return math.Pi * c.r*c.r�}
Suppose we have the structs Circle and a Rectangle as defined below.
We could write methods called area() for both types that return a float64 value:
type Rectangle struct {� x1, y1, x2, y2 float64�}
func (r *Rectangle) area() float64 {� l := distance(r.x1, r.y1, r.x1, r.y2)� w := distance(r.x1, r.y1, r.x2, r.y1)� return l * w�}
c := Circle{0,0,5}
ac := c.area()
r := Rectangle{0,0,5,5}
ar := r.area()
in o.method()
o is called receiver
type Shape interface {� area() float64�}
Suppose we add an interface called Shape as
func totalArea (shapes … Shape) float64 {� var area float64� for _, s := range shapes {� area += s.area()� }� return area�}
c := Circle{0,0,5}
r := Rectangle{0,0,5,5}
fmt.Println( totalArea(&c, &r) )
totalArea accepts as arguments an arbitrary number of Shape objects and returns the sum of their areas, as calculated by the corresponding area() methods defined for circle and rectangle.
totalArea is a variadic function
The entire app is just 69 lines of code !
Challenge: Write and run in ~15 minutes an app that contains the code from slides 12, 13.
Using Go Objects and Interfaces for HTTP Development
In essence, HTTP development consists of
Browser
Server
HTTP REQUEST
HTTP RESPONSE
Using Go Objects and Interfaces for HTTP Dev
HTTP REQUEST
HTTP RESPONSE
Request Line
HTTP Headers
Status Line
HTTP Headers
content
The building block that defines http package operations in GoLang is the Http.Handler interface
type Handler interface {� ServeHTTP(ResponseWriter, *Request)�}
The Http.Handler interface defines the operations of the Http Package
Recall that an interface is a typed collection of method signatures.
type Handler interface {� ServeHTTP(ResponseWriter, *Request)�}
HTTP REQUEST
HTTP RESPONSE
package main�import (� "fmt"� "log"� "net/http"�)��type helloHandler struct{}��func (h helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {� fmt.Fprintf(w, "hello, you are in %s\n", r.URL.Path)�}�
func main() {� err := http.ListenAndServe(":1234", helloHandler{})� log.Fatal(err)�}
ServeHTTP is a method associated to the helloHandler interface
curl localhost:1234/foo/bar
func main() { } is a standard func
The simplest e-Commerce site
The code below is from the book “The Go Programming Language” by Donovan and Kernighan. It prints the name and price of items in a “db” store, created as a map
type dollars float32
type database map[string]dollars
func (d dollars) String() string {
return fmt.Sprintf("$%.2f", d)
}
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
for item, price := range db { fmt.Fprintf(w, "%s: %s\n", item, price) }
}
func main() {
db := database{"shoes": 50, "socks": 5}
log.Fatal(http.ListenAndServe("localhost:8000", db))
}
The simplest e-Commerce site
Adding a “/list” and “/price” url
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch req.URL.Path {
case "/list":
for item, price := range db {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
case "/price":
item := req.URL.Query().Get("item")
price, ok := db[item]
if !ok {
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such item: w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such page: %s\n", req.URL)
}
}
}
We could keep on adding cases within ServeHTTP, or, we could use a method dispatching mechanism to serve each request
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello, you are in %s\n", r.URL.Path)
})
err := http.ListenAndServe(":1234", h)
log.Fatal(err)
}
A HandlerFunc type is an adapter that allows calling ordinary functions as HTTP handlers.
If f is a function with the appropriate signature, HandlerFunc(f) is a Handler that calls f.
curl localhost:1234/foo/bar
func main() {
h := http.NewServeMux()
h.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, you are in foo!")
})
h.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, you are in bar!")
})
h.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404)
fmt.Fprintln(w, "You are done. Go home")
})
err := http.ListenAndServe(":1234", h)
log.Fatal(err)
}
Using HandlerFunc and ServeMux we can have different methods serving different behaviors for different entry points.
This is much better than having to code behavior as if or switch statements
curl localhost:1234/foo
curl localhost:1234/bar
curl localhost:1234
The Power of method Dispatching
Note the signature of h.Handle:
( “/” , pageNumber( i ) )
each call to h.Handle finds ServeHTTP and passes
the value in i to the responseWriter w
ServeHTTP is a method which in this case dispatches http requests from objects of type pageNumber to the http server
1
2
3
We can even trace the execution with VSC debugger
Web Dev and Go
The writing of this part of the doc is a summary from the materials available at the URL below, that contains an article about using Go APIs to develop the server side and the client side of a simple yet fully functional wiki as a SPA:
https://golang.org/doc/articles/wiki/
The code in that article shines simplicity and elegance. It presents the Go APIs by following a stepwise evolution from the simplest possible wiki to a fully functional wiki (including the client and the server side), which is quite remarkable.
wiki.go
func main() {
http.HandleFunc("/view/", makeHandler(viewHandler))
http.HandleFunc("/edit/", makeHandler(editHandler))
http.HandleFunc("/save/", makeHandler(saveHandler))
http.ListenAndServe(":1234", nil)
}
A typical wiki provides three operations:
edit an existing page or a new page,
display (view) a page,
save a page.
In Golang, execution starts at main. The main function below launches these operations:
http://localhost:1234/view/new
If the page been requested does not exist for view, the app redirects the request to the edit handler to create one.
How does that happen?
The magic is part of the routing provided by the interfaces and objects in GoLang http package
wiki.go
The magic is in the writing of the function handlers.
Before going there we need to cover a few preliminaries.
First we need to import the APIs that we will use.
Then we need to define the structure of a wiki page, and with that, how to save a page into a file and how to read (load) a page from a file.
wiki.go
package main
import (
"html/template"
"io/ioutil"
"net/http"
"regexp"
)
type Page struct {
Title string
Body []byte
}
func (p *Page) save() error {
filename := p.Title + ".txt"
return ioutil.WriteFile(filename, p.Body, 0600)
}
func readPage(title string) (*Page, error) {
filename := title + ".txt"
body, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return &Page{Title: title, Body: body}, nil
}
wiki.go
A Page is just a struct with two items, a Title and a Body.
The method save gets a pointer to a Page as a parameter. The contents of the page are written into a file whose name is title.txt. The method returns an empty error (nil) if successful. If the operation fails, save returns a non-nil error object.
The function readPage gets a string parameter (title) which is expected to be the name of the file. If the file read is successful the function returns a pointer to a page and a nil error. If the read operation fails, the function returns a page pointer object with no content and a non-nil error.
wiki.go
func editHandler(w http.ResponseWriter, r *http.Request, title string) {
p, err := readPage(title)
if err != nil {
p = &Page{Title: title}
}
renderTemplate(w, "edit", p)
}
editHandler is the simplest of the three. The parameters are (w,r,title). The first two are objs from the http package. Note that r is really not used. It is there only for consistency with the other two handlers, which are created via a “make handler” function.
The code gets a page from a file. If the page is there, error is nil and edit template is rendered with the page content. If page is not there then error is non-nil and an empty page is rendered
Templates
Good news: Go has a template package !
Go Templates are similar to jinja2 templates from Python. There are a few sites where the two are compared.
The Go template package (html/template) implements data-driven templates for generating HTML output safe against code injection. It provides the same interface as package text/template and should be used instead of text/template whenever the output is HTML.
The package is mostly used in web applications to display data in a structured way at the client’s browser.
A benefit not found in other implementations (Python, Java, etc) is that Go templates provide automatic escaping of data. What does this mean? There is no need to worry about XSS attacks because Go parses the HTML template and escapes all inputs before displaying it to the browser.
Templates are HTML “macro expansions” that use the notation {{ action item … }}
The template engine reads .html files and expands sections denoted by {{ action }} from Go data structures.
The “macro expansions” produce text.
Below is a list of some of the actions (from the Go Documentation)
{{/* a comment */}}
{{pipeline}}
The default textual representation (the same as would be printed by fmt.Print)
of the value of the pipeline is copied to the output.
{{if pipeline}} T1 {{end}}
If the value of the pipeline is empty, no output is generated; otherwise, T1
is executed. The empty values are false, 0, any nil pointer or interface value,
and any array, slice, map, or string of length zero. Dot is unaffected.
{{if pipeline}} T1 {{else}} T0 {{end}}
If the value of the pipeline is empty, T0 is executed; otherwise, T1 is executed.
Dot is unaffected.
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
To simplify the appearance of if-else chains, the else action
of an if may include another if directly; the effect is exactly
the same as writing {{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
Actions, Part 1
{{range pipeline}} T1 {{end}}
The value of the pipeline must be an array, slice, map, or channel.
If the value of the pipeline has length zero, no output is rendered; otherwise,
dot is set to the successive elements of the items in T1. If the value is a map
and the keys are of a type with a defined order ("comparable"), the elements will
be visited in sorted key order.
{{range pipeline}} T1 {{else}} T0 {{end}}
The value of the pipeline must be an array, slice, map, or channel.
If the value of the pipeline has length zero, dot is unaffected and
T0 is executed; otherwise, dot is set to the successive elements
of the array, slice, or map and T1 is executed.
{{template "name"}}
Actions, Part 2
Templates
<h1>{{.PageTitle}}</h1>
<ul>
{{range .Todos}}
{{if .Done}}
<li class="done">{{.Title}}</li>
{{else}}
<li>{{.Title}}</li>
{{end}}
{{end}}
</ul>
Using templates in Go is simple.
The code shows a ToDo list, written as a HTML
unordered list <ul> tag </ul>.
The code in blue is HTML. The code in white is processed by the template library.
The data passed from/to go/html can be any kind of Go’s data structures, including simple strings or numbers, or even nested data structures as shown the code will show.
To access the data in a template, the top most variable is access by {{.}}. The dot inside the curly braces is the root element of the data (typically a list) and is called the pipeline.
Templates Control Structures
To get a detailed list of all possible structures visit: https://pkg.go.dev/text/template#hdr-Actions
Define a comment | {{/* a comment */}} |
Render the root element | {{.}} |
Example: render the “Title”-field in a nested element | {{.Title}} |
If Statement | {{if .Done}}<blabla>{{else}}<blabla>{{end}} |
Loops over the elements of a list and renders each using {{.}} | {{range .Todos}} {{.}} {{end}} |
Defines a block named “content” | {{block "content" .}} {{end}} |
Go’s template language contains a rich set of control structures to render HTML. The list below summarizes the most commonly used.
Templates Sample1
func sample01() {
// Define a template.
const letter = `
Dear {{.Name}},
{{if .Attended}}
It was a pleasure to see you at the wedding.
{{- else}}
It is a shame you couldn't make it to the wedding.
{{- end}}
{{with .Gift -}}
Thank you for the lovely {{.}}.
{{end}}
Best wishes,
Josie
`
// Prepare some data to insert into the template.
type Recipient struct {
Name, Gift string
Attended bool
}
var recipients = []Recipient{
{"Aunt Mildred", "bone china tea set", true},
{"Uncle John", "moleskin pants", false},
{"Cousin Rodney", "", false},
}
// Create a new template and parse the letter into it.
t := template.Must(template.New("letter").Parse(letter))
// Execute the template for each recipient.
for _, r := range recipients {
err := t.Execute(os.Stdout, r)
if err != nil {
log.Println("executing template:", err)
}
}
}
Templates Sample2
func sample02() {
// print one name on each line
const (
master = `Names:{{block "list" .}}{{"\n"}}{{range .}}{{println "-" .}}{{end}}{{end}}`
overlay = `{{define "list"}} {{join . ", "}}{{end}} `
)
var (
funcs = template.FuncMap{"join": strings.Join}
guardians = []string{"Gamora", "Groot", "Nebula", "Rocket", "Star-Lord"}
)
masterTmpl, err := template.New("master").Funcs(funcs).Parse(master)
if err != nil {
log.Fatal(err)
}
if err := masterTmpl.Execute(os.Stdout, guardians); err != nil {
log.Fatal(err)
}
// print all names in a single line
overlayTmpl, err := template.Must(masterTmpl.Clone()).Parse(overlay)
if err != nil {
log.Fatal(err)
}
if err := overlayTmpl.Execute(os.Stdout, guardians); err != nil {
log.Fatal(err)
}
}
println "-" list[i]
println "-" list[i]
alias for fmt.Println(args) https://pkg.go.dev/text/template#hdr-Examples
Templates Sample3
func sample03() {
tmpl := template.Must(template.ParseFiles("layout.html"))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
data := TodoPageData{
PageTitle: "My TODO list",
Todos: []Todo{
{Title: "Task 1", Done: false},
{Title: "Task 2", Done: true},
{Title: "Task 3", Done: true},
},
}
tmpl.Execute(w, data)
})
http.ListenAndServe(":1234", nil)
}
<h1>{{.PageTitle}}</h1>
<ul>
{{range .Todos}}
{{if .Done}}
<li class="done">{{.Title}}</li>
{{else}}
<li>{{.Title}}</li>
{{end}}
{{end}}
</ul>
type Todo struct {
Title string
Done bool
}
type TodoPageData struct {
PageTitle string
Todos []Todo
}
4
1
2
3
5
Templates
func sample03() {
tmpl := template.Must(template.ParseFiles("layout.html"))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
data := TodoPageData{
PageTitle: "My TODO list",
Todos: []Todo{
{Title: "Task 1", Done: false},
{Title: "Task 2", Done: true},
{Title: "Task 3", Done: true},
},
}
tmpl.Execute(w, data)
})
http.ListenAndServe(":1234", nil)
}
html file
HandleFunc
Learn Templates Demo
render header
scope of range
render from fields
render from fields
render from fields
render from fields
reference by field
reference by field
when btn clicked go to
q/vote_nvc/XVC
render header
scope of range
scope of range
title field
content field
PVC
NVC
btn
btn
Edit Template
View Template
<h1>Editing {{.Title}}</h1>
<form action="/save/{{.Title}}" method="POST">
<div><textarea name="body" rows="20" cols="80">
{{printf "%s" .Body}}</textarea>
</div>
<div>
<input type="submit" value="Save">
</div>
</form>
<h1>{{.Title}}</h1>
<p>[<a href="/edit/{{.Title}}">edit</a>]</p>
<div>{{printf "%s" .Body}}</div>
Code injection occurs when the compiler sees the pattern
{{ goCode }}
within an html file
wiki.go
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)
}
The viewHandler gets params (w, r, title) to load a page or to start the editing of a new one.
If page is not there, p is empty and error is non-nil. This causes the handler to redirect request to editHandler.
Either way, the viewHandler uses the view template for rendering the page
wiki.go
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)
}
The saveHandler gets params (w, r, title). The body is extracted from the response’s page and the title is used as file name. Note how the func save is called: save gets as argument a page pointer, which in fact is the “target” of the function when it is called as p.save().
wiki.go
var templates = template.Must(template.ParseFiles("edit.html", "view.html"))
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
err := templates.ExecuteTemplate(w, tmpl+".html", p)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
templates is a global variable used to save the output of parsing the view and edit templates. In this way the parsing is done once and keeping the parsed output improves efficiency.
renderTemplate gets params (w, tmpl, p). This function is used to render into the view or the edit template.
wiki.go
var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")
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])
}
}
makeHandler is a very interesting function. It gets param (fn) to return an obj of type http.HandlerFunc. Yes, that is correct, the return is a function!
wiki.go
func main() {
http.HandleFunc("/view/", makeHandler(viewHandler))
http.HandleFunc("/edit/", makeHandler(editHandler))
http.HandleFunc("/save/", makeHandler(saveHandler))
http.ListenAndServe(":1234", nil)
}
The entire app is just 130 lines of GoLang code !
The entire app is just 130 lines of GoLang code !
Web Client
Web Server
mkcog
GoWa: MakinaCognika: ML in Pure Go
Go as a System Platform
GoWaMain
GoWa Main is the "V2" of a lib I wrote to demo writing web apps in entirely Go.
The version is Go 1.22.4, which must be declared in go.mod
## UPDATE June 19, 2024
The code in
The go.mod file was created as follows:
cd /drv3/hm3/code/go/src/gowamain
go mod init
go mod tidy
GoWaMain
/*
GoWa is a WebApp written n Go. The purpose is to create a fully functional Web App that includes a web server,
a client, and simple code for data science, with access/interface to a fully functional DB, most likely under MySql.
*/
package main
import (
"fmt"
gowa "gowa/gowa/lib"
"log"
"net/http"
"os"
"os/exec"
"runtime"
)
func openBrowser(url string) bool {
var args []string
switch runtime.GOOS {
case "darwin":
args = []string{"open"}
case "windows":
args = []string{"cmd", "/c", "start"}
default:
args = []string{"xdg-open"}
}
cmd := exec.Command(args[0], append(args[1:], url)...)
return cmd.Start() == nil
}
func main() {
var url = "http://localhost:1233"
openBrowser(url)
fmt.Printf("Process ID %d ", os.Getpid())
// code below is needed to add .css styles into the html files
http.Handle(gowa.GoWaDirCss, http.StripPrefix(gowa.GoWaDirCss,
http.FileServer(http.Dir(gowa.GoWaDirCss))))
http.HandleFunc("/", gowa.Gowa) // set router
http.HandleFunc("/pgPython", gowa.PgPython) // set router
http.HandleFunc("/hello", gowa.HelloName) // set router
http.HandleFunc("/login", gowa.Login) // set router
http.HandleFunc("/sqlquery", gowa.SqlQuery) // set router
http.HandleFunc("/server2", gowa.Server2) // set router
http.HandleFunc("/server3", gowa.Server3) // set router
http.HandleFunc("/server4", gowa.Server4) // set router
http.HandleFunc("/server5", gowa.Server5) // set router
http.HandleFunc("/XssLogin", gowa.XssLogin) // set router
http.HandleFunc("/vrb", gowa.Vrb) // set router
http.HandleFunc("/vdd", gowa.Vdd) // set router
http.HandleFunc("/vcb", gowa.Vcb) // set router
// use these two once mkcog is added to this app
http.HandleFunc("/slrp", gowa.SLRP) // SLRP = Simple LR Page
http.HandleFunc("/mlrp", gowa.MLRP) // MLRP = Multi-variable LR Page
http.HandleFunc("/upload", gowa.UploadFile) // set router
err := http.ListenAndServe(":1233", nil) // listen at port 1233
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
fmt.Println("DONE")
}
ref to gowa
GoWa
GoWa
The ../lib subdirectory contains the files.go where the handlers and functions are defined
The ../lib subdirectory contains the files.go where the handlers and functions are defined
DEMOS
WebAppCSS_2024_0708
GoWa
Moderator
Mod App in Go Language
Juan E. Vargas
https://bit.ly/4cUMQNT
what’s next?
We only scratched the surface. The http package contains tons of constants, types and methods that deserve attention
Computing is about insights, not just numbers