# Go Hands-on 6 april 2017 Harry de Boer, Quintor ## Wat is go Volgens https://golang.org: _Go is an open source programming language that makes it easy to build simple, reliable, and efficient software._ Eigenschappen: * type safe * compiled * garbage collected * statically linked binaries ## Tips * zoek online op 'golang' ipv 'go' ## Opgaven In deze hands-on sessie beginnen we met een simpel programma. Deze breiden we steeds verder uit naar volledig werkende backend voor een webapplicatie. Opdrachten 1 t/m 5 zijn bedoeld als eerste kennismakinng. Voor opdrachten 6 t/m 10 is een simpele website aangeleverd waarvoor gedurende de opdrachten de backend geschreven wordt. ### Opgave 1: een eerste programma In deze opgave maken we ons eerste werkende go programma. Een go programma begint altijd met een package declaratie. Voor een executable - wat we hier gaan maken - is altijd dit altijd ~~~~ package main ~~~~ Na de package declaratie komen de import statements: ~~~~ import ( "fmt" ) ~~~~ Functies uit een geimporteerd package kan je aanroepen met `packagenaam.Functie(...)`. Alleen funties/methoden/typen/velden die beginnen met een hoofdletter zijn _exported_ (public) en zichtbaar voor andere packges. Binnen een package is alles zichtbaar. Een package zit altijd in een directory van dezelfde naam, met uitzondering van `main`. Het entrypoint van een executable is de `main` functie. ~~~~ func main() { statement } ~~~~ Het getoonde statement heeft geen `;` aan het einde, deze zijn optioneel in go. Schrijf nu een programma dat je favoriete paar woorden afdrukt. Importeer hier voor [package fmt](https://golang.org/pkg/fmt) en gebruik de `Println` functie. De naam van het bestand maakt niet uit, maar deze moet eindigen op `.go` Compileer het programma met ~~~~ go build .go ~~~~ De resulterende executable zal dezelfde naam krijgen als de directory waar deze in staat. Waar wacht je op? Voer hem uit! ### Opgave 2: functies Functies in go beginnen met het `func` keyword gevolgd door een lijst van parameters tussen haakjes ~~~~ func bla(naam string) string { return naam + "bla" } ~~~~ In tegenstelling tot veel andere talen komen de types van de parameters na de naam. Voeg nu je eerste functie toe aan je programma. Geef als parameter een string mee, verwerk deze in een andere string met `fmt.Sprintf`, return het resultaat en druk dit af. ### Opgave 3: template parsing `Sprintf` is prima voor kleine strings, maar voor grotere teksten is een template parser handig. Importeer het `text/template` package en maak een nieuw template: ~~~~ template.New("helloTemplate").Parse("Hello {{.}}\n") ~~~~ In de documentatie van `template.New` kan je zien dat deze twee return values heeft. De eerste is het template, noem deze `t`. De tweede is een error die je bijvoorbeeld kan printen, of je kan je programma laten crashen met ~~~~ if err != nil { panic(err) } ~~~~ Het template verwerken gaat met de `Execute` methode die op `t` aangeroepen kan worden ~~~~ t.Execute(os.Stdout, "Quintor") ~~~~ De eerste parameter is een interface, `io.Writer`, die afdwingt dat het meegegeven argument een `Write([]byte)` methode heeft. In dit geval wordt naar de standaard uitvoer geschreven. De tweede parameter is een object waarmee de placeholders in het template vervangen worden. In dit voorbeeld is dit een simpele string Verwerk nu het bovenstaande in je programma. ### Opgave 4: template parsing #2 Templates kunnen ook uit een file geladen worden met ~~~~ template.ParseFiles("mijntemplate.tpl") ~~~~ Pas je programma nu zo aan dat het `ParseFiles` gebruikt ipv `New`/`Parse`. ### Opgave 5: een webserver Het `io.Writer` interface kan veelzijdig gebruikt worden. Zoals in het voorbeeld hieronder te zien is voldoet `http.ResponseWriter` ook aan dit interface. Analyseer het onderstaande programma en bouw een webserver in je eigen programma in. Gebruik evt. de templates/resources in `opgave5.zip` Een paar hints: * go kent geen classes * datatypen worden gedefineerd met `type`, hieronder wordt het type `server` gedefineerd als een `struct` met een veld `template` * `main()` is een funcie * `handleRoot()` is een methode op het type `server` * `http.ListenAndServe` start een http server op * `http.FileServer` serveert static files in een directory * wees niet bang om de documentatie van een package er bij te pakken ~~~~ package main import ( "log" "net/http" "text/template" ) type server struct { template *template.Template } func main() { t := template.Must(template.ParseFiles("templates/index.html")) s := server{template: t} http.HandleFunc("/", s.handleRoot) http.Handle("/resources/", http.FileServer(http.Dir("."))) err := http.ListenAndServe(":8080", nil) log.Fatal(err) } func (s *server) handleRoot(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf8") err := s.template.Execute(w, "Vrolijk pasen!") if err != nil { log.Println(err) } } ~~~~ ### Opgave 6: paaseieren zoeken `opgave6.zip` bevat een html/javascript webapplicatie. Serveer de bestanden in `/resources` met `http.FileServer` en zoek de eieren door in het scherm te klikken. Je zult merken dat je geen mooie paaseieren krijgt, hiervoor moet de backend geimplementeerd. Maak een server type aan met een veld naam. ~~~~ type server struct { name string } ~~~~ Initialiseer een variabele van dit type: ~~~~ s := server{name: "Golang Hands-on"} ~~~~ Maak een endpoint onder `/api/greet` ~~~~ http.HandleFunc("/api/greet/", s.titleHandler) ~~~~ En de bijbehorende handler ~~~~ func (s server) titleHandler(w http.ResponseWriter, r *http.Request) { var err error w.Header().Set("Content-Type", "text/plain; charset=utf8") _, err = fmt.Fprint(w, s.name) if err != nil { log.Println(err) } } ~~~~ Als het goed is kan je nu een paasei vinden! ### Opgave 7: marshalling json Om het tweede ei te vinden moet het endpoint om kunnen gaan met de `Accept: application/json` http header. Voor het eerste ei werd om `text/plain` gevraagd, voor het tweede ei wordt json gevraagd _op hetzelfde endpoint_. Er wordt een json object in het volgende formaat verwacht: ~~~~ {title: "title", subtitle: "subtitle"} ~~~~ Je kan de header ophalen met ~~~~ r.Header.Get("Accept") ~~~~ Je kan het `strings` package gebruiken om strings te manipuleren. Om json te encoden heb je een geschikt type nodig om te encoden, bijvoorbeeld ~~~~ type greet struct { Title string `json:"title"` Subtitle string `json:"subtitle"` } ~~~~ Deze kan je encoden met het `json` package. `json.NewEncoder` verwacht wederom een `io.Writer`. ~~~~ e := json.NewEncoder(w) err = e.Encode(greet{Title: s.name, Subtitle: "Hallo Quintor!"}) ~~~~ Stuur ook het juiste Content-Type mee: ~~~~ w.Header().Set("Content-Type", "application/json; charset=utf8") ~~~~ Als je dit goed doet heb je een ei erbij! ### Opgave 8: unmarshalling json Maak een nieuwe Handler voor het endpoint `/api/sum/`. In het request wordt een json string meegestuurd. Om deze te kunnen decoden hebben we een struct nodig met de juiste structuur: ~~~~ type sumRequest struct { Start int `json:"start"` End int `json:"end"` Numbers []int `json:"numbers"` } ~~~~ En decode met: ~~~~ var sreq sumRequest d := json.NewDecoder(r.Body) err := d.Decode(&sreq) ~~~~ Als response verwacht de client een json object van de vorm ~~~~ {answer: 13, contributers: 3} ~~~~ Het veld `answer` moet de som van de elementen tussen `start` (inclusief) en `end` (exclusief) in de numbers slice bevatten. `contributors` moet gelijk zijn aan `end - start`. Maak hier zelf een type voor aan. ### Opgave 9: data opslaan in een database Maak een nieuw endpoint `/api/store/` waar een json request in de vorm ~~~~ {name: "name", message: "message"} ~~~~ verwacht en een response van de vorm terug stuurt. ~~~~ {id: 0} ~~~~ Om data op te slaan gaan we gebruik maken maken van [gorm](github.com/jinzhu/gorm). Voeg de volgende imports toe: ~~~~ "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/sqlite" ~~~~ Pas de server struct aan en voeg een type `Message` toe, dit wordt onze database entiteit. ~~~~ type server struct { name string db *gorm.DB } type Message struct { ID uint Name string Content string } ~~~~ Voeg de volgende regels toe bovenaan je main() ~~~~ db, err := gorm.Open("sqlite3", "sqlite.db") if err != nil { log.Fatal("Could not open database", err) } defer db.Close() db.DropTableIfExists(&Message{}) db.AutoMigrate(&Message{}) s := server{name: "Golang Hands-on", db: db} ~~~~ In je handler kan je nu een message als volgt opslaan. Zorg ervoor dat je de message vult met de waarden uit het json request. ~~~~ m := Message{Name: "naam", Content: "inhoud"} db.Create(&m) log.Println("message id", m.ID) ~~~~ Na de create kan je het ID opvragen waarmee het message is opgeslagen. Deze stuur je terug naar de client. ### Opgave 10: data ophalen uit database Voor het laatste paasei gaan we data weer ophalen uit de database aan de hand van een id. De client zal een request doen naar `/api/store/` en verwacht json object in hen volgende formaat als response: ~~~~ {name: "name", message: "message"} ~~~~ Gebruik voor het ophalen `First`, zie http://jinzhu.me/gorm/crud.html#query ## Bonusopgaven * Implementeer https mbv https://golang.org/pkg/net/http/#ListenAndServeTLS * Implementeer Server Push https://blog.golang.org/h2push * Gebruik een locale MySQL of Postgres database ipv SQLite * Volg de Go Tour: https://tour.golang.org/ ## Resources * [golang.org/doc](https://golang.org/doc/) - alle go documentatie * [golang.org/pkg](https://golang.org/pkg/) - documentatie standard library * [godoc.org](https://godoc.org/) - documentatie externe packages * [jinzhu.me/gorm](http://jinzhu.me/gorm/) - gorm database mapper * [github.com/julienschmidt/httprouter](https://github.com/julienschmidt/httprouter) - meer flexibele url router