and for each category there will be a list of the vehicles and the amount in stock.
The url scheme should be /{category} for example /saloon and /compact
We will start with a simple HTML server that will only serve 404 not found for any URL and have it listen on port 8080 package main
package main
import (
"fmt"
"net/http"
)
func HandleUnknown(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound);
fmt.Fprint(w, "Hello\n")
fmt.Fprint(w, "This url does not exist")
}
func main() {
http.HandleFunc("/", HandleUnknown)
http.ListenAndServe("localhost:8080", nil)
}
import (
"fmt"
"net/http"
)
func HandleUnknown(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound);
fmt.Fprint(w, "Hello\n")
fmt.Fprint(w, "This url does not exist")
}
func main() {
http.HandleFunc("/", HandleUnknown)
http.ListenAndServe("localhost:8080", nil)
}
now we run go install and run the exe created. browsing to localhost:8080 will load the page with our message without consideration of the path. This is because Go considers the "/" input for the HandleFunc function as any path.
Lets register a path for the compact cars and have it return the list of compact cars.
package main
import (
"fmt"
"net/http"
)
type Car struct {
Name string
Count int
}
type Category struct {
Title string
Cars []Car
}
var (
compactList = Category{ "compact", []Car { Car { "Hyundai i30", 3 }, Car { "Volkswagen Golf", 1 }, Car { "Chevrolet Cruze", 2 } } }
)
func HandleUnknown(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound);
fmt.Fprint(w, "Hello\n")
fmt.Fprint(w, "This url does not exist")
}
func HandleCompact(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "<html><body><ul>")
for _, car := range compactList.Cars {
fmt.Fprint(w, "<li>", car.Name, " ", car.Count, "</li>");
}
fmt.Fprint(w, "</ul></body></html>")
}
func main() {
http.HandleFunc("/compact", HandleCompact);
http.HandleFunc("/", HandleUnknown)
http.ListenAndServe("localhost:8080", nil)
}
So What do we have here? we created a struct for Car and for the Category which holds the list of cars. we initialized one category for compact cars with the data.import (
"fmt"
"net/http"
)
type Car struct {
Name string
Count int
}
type Category struct {
Title string
Cars []Car
}
var (
compactList = Category{ "compact", []Car { Car { "Hyundai i30", 3 }, Car { "Volkswagen Golf", 1 }, Car { "Chevrolet Cruze", 2 } } }
)
func HandleUnknown(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound);
fmt.Fprint(w, "Hello\n")
fmt.Fprint(w, "This url does not exist")
}
func HandleCompact(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "<html><body><ul>")
for _, car := range compactList.Cars {
fmt.Fprint(w, "<li>", car.Name, " ", car.Count, "</li>");
}
fmt.Fprint(w, "</ul></body></html>")
}
func main() {
http.HandleFunc("/compact", HandleCompact);
http.HandleFunc("/", HandleUnknown)
http.ListenAndServe("localhost:8080", nil)
}
We told the http default handler to handle the path "/compact" with the function HandleCompact.
Then we just print the html to the ResponseWriter. It works... but its ugly.
We would not like to write html as strings... we would like to write it in an html file as a template and have the template rendered just as we would have done with Angular or Knockout and the likes... Fortunately Go has a solution for that and lets see how we do it.
First we will create a html template:
<html>
<head>
<title>{{.Title}}</title>
</head>
<body>
<ul>
{{range .Cars}}
<li>{{.Name}} {{.Count}}</li>
{{end}}
</ul>
</body>
</html>
The Go html/template library allows us to put place holders for data. The scope is the object we will pass later on to the template rendering. Since we are going to pass out Category struct we can reference the .Title and .Cars properties.<head>
<title>{{.Title}}</title>
</head>
<body>
<ul>
{{range .Cars}}
<li>{{.Name}} {{.Count}}</li>
{{end}}
</ul>
</body>
</html>
Over the Cars property we can iterate since its a list. Go allows us to state {{range .Cars}} to perform on each item in the list and then foolowed by an {{end}} to complete the loop.
Next we need to apply the template instead of our handler:
package main
import (
"fmt"
"net/http"
"html/template"
"os"
)
type Car struct {
Name string
Count int
}
type Category struct {
Title string
Cars []Car
}
var (
compactList = Category{ "compact", []Car { Car { "Hyundai i30", 3 }, Car { "Volkswagen Golf", 1 }, Car { "Chevrolet Cruze", 2 } } }
)
func HandleUnknown(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound);
fmt.Fprint(w, "Hello\n")
fmt.Fprint(w, "This url does not exist")
}
func HandleCompact(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("template.html")
if err != nil {
fmt.Println("error - did not load template", err);
os.Exit(-2);
}
tmpl.Execute(w, compactList)
}
func main() {
http.HandleFunc("/compact", HandleCompact);
http.HandleFunc("/", HandleUnknown)
http.ListenAndServe("localhost:8080", nil)
}
Note that the template.html file needs to be found in the folder from which your are running the executable - that is where the os is trying to find it.import (
"fmt"
"net/http"
"html/template"
"os"
)
type Car struct {
Name string
Count int
}
type Category struct {
Title string
Cars []Car
}
var (
compactList = Category{ "compact", []Car { Car { "Hyundai i30", 3 }, Car { "Volkswagen Golf", 1 }, Car { "Chevrolet Cruze", 2 } } }
)
func HandleUnknown(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound);
fmt.Fprint(w, "Hello\n")
fmt.Fprint(w, "This url does not exist")
}
func HandleCompact(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("template.html")
if err != nil {
fmt.Println("error - did not load template", err);
os.Exit(-2);
}
tmpl.Execute(w, compactList)
}
func main() {
http.HandleFunc("/compact", HandleCompact);
http.HandleFunc("/", HandleUnknown)
http.ListenAndServe("localhost:8080", nil)
}
This is nice so far, lets create additional handlers for the other categories.
We could have copy pasted the handleCompact function while altering it a bit to get the required result but that would be not elegant.
instead lets use a function closure. This way we can iterate thru all the categories and the HandleCategory function will return the handler for each category.
The final code:
package main
import (
"fmt"
"net/http"
"html/template"
"os"
)
type Car struct {
Name string
Count int
}
type Category struct {
Title string
Cars []Car
}
func HandleUnknown(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound);
fmt.Fprint(w, "Hello\n")
fmt.Fprint(w, "This url does not exist")
}
func HandleCategory(c Category) func (w http.ResponseWriter, r *http.Request) {
return func (w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("template.html")
if err != nil {
fmt.Println("error - did not load template", err);
os.Exit(-2);
}
tmpl.Execute(w, c)
}
}
func main() {
inventory := []Category {
Category { "compact", []Car { Car { "Hyundai i30", 3 }, Car { "Volkswagen Golf", 1 }, Car { "Chevrolet Cruze", 2 } } },
Category { "saloon", []Car { Car { "Toyota Camry", 3 }, Car { "Volkswagen Passat", 1 }, Car { "Audi A6", 2 } } },
Category { "sports", []Car { Car { "Chverolet Corvette", 3 }, Car { "Ferrari F12", 1 }, Car { "Porsche 911", 2 } } },
}
for _, cat := range inventory {
http.HandleFunc("/" + cat.Title, HandleCategory(cat))
}
http.HandleFunc("/", HandleUnknown)
http.ListenAndServe("localhost:8080", nil)
}
import (
"fmt"
"net/http"
"html/template"
"os"
)
type Car struct {
Name string
Count int
}
type Category struct {
Title string
Cars []Car
}
func HandleUnknown(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound);
fmt.Fprint(w, "Hello\n")
fmt.Fprint(w, "This url does not exist")
}
func HandleCategory(c Category) func (w http.ResponseWriter, r *http.Request) {
return func (w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("template.html")
if err != nil {
fmt.Println("error - did not load template", err);
os.Exit(-2);
}
tmpl.Execute(w, c)
}
}
func main() {
inventory := []Category {
Category { "compact", []Car { Car { "Hyundai i30", 3 }, Car { "Volkswagen Golf", 1 }, Car { "Chevrolet Cruze", 2 } } },
Category { "saloon", []Car { Car { "Toyota Camry", 3 }, Car { "Volkswagen Passat", 1 }, Car { "Audi A6", 2 } } },
Category { "sports", []Car { Car { "Chverolet Corvette", 3 }, Car { "Ferrari F12", 1 }, Car { "Porsche 911", 2 } } },
}
for _, cat := range inventory {
http.HandleFunc("/" + cat.Title, HandleCategory(cat))
}
http.HandleFunc("/", HandleUnknown)
http.ListenAndServe("localhost:8080", nil)
}
In the next post we will (without too much code changes) expose this same site also as a JSON REST API
No comments:
Post a Comment