Kaynağa Gözat

opgaven uitgewerkt, klaar voor nu

Harry de Boer 9 yıl önce
ebeveyn
işleme
9c01ab7cd6

+ 1 - 1
.gitignore

@@ -6,4 +6,4 @@ ex3/ex3
 ex4/ex4
 ex5/ex5
 ex6/ex6
-ex7/ex7
+ex6/ex7

+ 346 - 0
README.md

@@ -0,0 +1,346 @@
+# 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
+ * staticly 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 <bestandsnaam>.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(&amp;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. `contributers` 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(&amp;Message{})
+db.AutoMigrate(&amp;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/<id>` 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
+

+ 1 - 1
ex1/main.go

@@ -3,5 +3,5 @@ package main
 import "fmt"
 
 func main() {
-	fmt.Println("Hello Quintor");
+	fmt.Println("Hello Quintor")
 }

+ 2 - 2
ex2/main.go

@@ -3,9 +3,9 @@ package main
 import "fmt"
 
 func main() {
-	fmt.Println(hello("Quintor"));
+	fmt.Println(hello("Quintor"))
 }
 
 func hello(someone string) string {
-	return fmt.Sprintf("Hello %s", someone);
+	return fmt.Sprintf("Hello %s", someone)
 }

+ 1 - 1
ex3/main.go

@@ -6,7 +6,7 @@ import (
 )
 
 func main() {
-	t, err := template.New("hello").Parse("Hello {{.}}\n")
+	t, err := template.New("helloTemplate").Parse("Hello {{.}}\n")
 
 	if err != nil {
 		panic(err)

+ 15 - 5
ex5/main.go

@@ -11,14 +11,24 @@ type server struct {
 }
 
 func main() {
-	t := template.Must(template.ParseFiles("templates/main.tpl"))
+	t := template.Must(template.ParseFiles("templates/index.html"))
 	s := server{template: t}
-	http.HandleFunc("/", s.handler)
-	log.Fatal(http.ListenAndServe(":8080", nil))
+	http.HandleFunc("/", s.handleRoot)
+	http.Handle("/resources/", http.FileServer(http.Dir(".")))
+	log.Println("Listening on http://localhost:8080")
+	err := http.ListenAndServe(":8080", nil)
+	log.Fatal(err)
 }
 
-func (s *server) handler(w http.ResponseWriter, r *http.Request) {
-	err := s.template.Execute(w, "Devoxx")
+func (s *server) handleRoot(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "text/html; charset=utf8")
+
+	if r.RequestURI != "/" {
+		http.NotFound(w, r)
+		return
+	}
+
+	err := s.template.Execute(w, "Vrolijk pasen!")
 	if err != nil {
 		log.Println(err)
 	}

+ 0 - 0
ex7/resources/img/eggs-in-grass.svg → ex5/resources/img/eggs-in-grass.svg


+ 0 - 0
ex7/resources/img/rabbit.svg → ex5/resources/img/rabbit.svg


+ 15 - 0
ex5/resources/main.css

@@ -0,0 +1,15 @@
+body {
+    background-color: #92CD00
+}
+
+header img {
+    display: inline;
+    vertical-align: middle;
+    width: 150px;
+}
+
+header h1 {
+    display: inline;
+    vertical-align: middle;
+    color: #2C6700
+}

+ 0 - 1
ex5/templates/hello.tpl

@@ -1 +0,0 @@
-Hello {{.}}

+ 0 - 0
ex6/templates/main.html → ex5/templates/index.html


+ 141 - 13
ex6/main.go

@@ -1,37 +1,165 @@
 package main
 
 import (
-	"html/template"
+	"encoding/json"
+	"fmt"
+	"github.com/jinzhu/gorm"
+	_ "github.com/jinzhu/gorm/dialects/sqlite"
 	"log"
 	"net/http"
+	"strings"
+	"strconv"
 )
 
 type server struct {
-	template *template.Template
+	name string
+	db   *gorm.DB
+}
+
+type Message struct {
+	ID      uint
+	Name    string
+	Content string
 }
 
 func main() {
-	t := template.Must(template.ParseFiles("templates/main.html"))
-	s := server{template: t}
-	http.HandleFunc("/", s.handleRoot)
-	http.Handle("/resources/", http.FileServer(http.Dir(".")))
-	log.Println("Listening on https://localhost:8443")
-	err := http.ListenAndServeTLS(":8443", "certs/localhost.cert", "certs/localhost.key", nil)
+	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}
+	http.Handle("/", http.FileServer(http.Dir("resources")))
+	http.HandleFunc("/api/greet/", s.titleHandler)
+	http.HandleFunc("/api/sum/", s.sumHandler)
+	http.HandleFunc("/api/store/", s.storeHandler)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	log.Println("Listening on http://localhost:8080")
+	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")
+type greet struct {
+	Title    string `json:"title"`
+	Subtitle string `json:"subtitle"`
+}
 
-	if r.RequestURI != "/" {
-		http.NotFound(w, r)
+func (s server) titleHandler(w http.ResponseWriter, r *http.Request) {
+	var err error
+
+	if strings.Contains(r.Header.Get("Accept"), "application/json") {
+		w.Header().Set("Content-Type", "application/json; charset=utf8")
+		e := json.NewEncoder(w)
+		err = e.Encode(greet{Title: s.name, Subtitle: "Hallo Quintor!"})
+	} else {
+		w.Header().Set("Content-Type", "text/plain; charset=utf8")
+		_, err = fmt.Fprint(w, s.name)
+	}
+
+	if err != nil {
+		log.Println(err)
+	}
+}
+
+type sumRequest struct {
+	Start   int   `json:"start"`
+	End     int   `json:"end"`
+	Numbers []int `json:"numbers"`
+}
+type sumResponse struct {
+	Answer int `json:"answer"`
+	Count  int `json:"contributers"`
+}
+
+func (s server) sumHandler(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json; charset=utf8")
+
+	var sreq sumRequest
+	d := json.NewDecoder(r.Body)
+	err := d.Decode(&sreq)
+	if err != nil {
+		log.Println(err)
+		http.Error(w, "Bad request", http.StatusBadRequest)
+		return
+	}
+
+	var sresp sumResponse
+	for _, val := range sreq.Numbers[sreq.Start:sreq.End] {
+		sresp.Answer += val
+		sresp.Count++
+	}
+
+	e := json.NewEncoder(w)
+	err = e.Encode(sresp)
+	if err != nil {
+		log.Println(err)
+	}
+}
+
+type storeMessage struct {
+	Name    string `json:"name"`
+	Content string `json:"message"`
+}
+
+type storeResponse struct {
+	Id uint `json:"id"`
+}
+
+func (s server) storeHandler(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json; charset=utf8")
+	if r.Method == http.MethodPost {
+		storeHandlePost(s.db, w, r)
+	} else {
+		storeHandleGet(s.db, w, r)
+	}
+}
+
+func storeHandlePost(db *gorm.DB, w http.ResponseWriter, r *http.Request) {
+	var sm storeMessage
+	d := json.NewDecoder(r.Body)
+	err := d.Decode(&sm)
+	if err != nil {
+		log.Println(err)
+		http.Error(w, "Bad request", http.StatusBadRequest)
 		return
 	}
 
-	err := s.template.Execute(w, "Vrolijk pasen!")
+	m := Message{Name: sm.Name, Content: sm.Content}
+	db.Create(&m)
+	log.Println("message id", m.ID)
 
+	e := json.NewEncoder(w)
+	err = e.Encode(storeResponse{Id: m.ID})
 	if err != nil {
 		log.Println(err)
 	}
+}
+
+func storeHandleGet(db *gorm.DB, w http.ResponseWriter, r *http.Request) {
+	log.Println(r.URL.Path, strings.TrimPrefix(r.URL.Path, "/api/store/"))
+	id, err := strconv.Atoi(strings.TrimPrefix(r.URL.Path, "/api/store/"))
+	if err != nil {
+		http.NotFound(w, r)
+		return
+	}
+	var m Message
+	if err := db.First(&m, id).Error; err != nil {
+		log.Println(err)
+		http.NotFound(w, r)
+		return
+	}
 
+	e := json.NewEncoder(w)
+	err = e.Encode(storeMessage{m.Name, m.Content})
+	if err != nil {
+		log.Println(err)
+	}
 }

+ 0 - 0
ex7/resources/img/egg-broken.svg → ex6/resources/img/egg-broken.svg


+ 0 - 0
ex7/resources/img/egg1.svg → ex6/resources/img/egg1.svg


+ 0 - 0
ex7/resources/img/egg2.svg → ex6/resources/img/egg2.svg


+ 0 - 0
ex7/resources/img/egg3.svg → ex6/resources/img/egg3.svg


+ 0 - 0
ex7/resources/img/egg4.svg → ex6/resources/img/egg4.svg


+ 0 - 0
ex7/resources/img/egg5.svg → ex6/resources/img/egg5.svg


+ 9 - 5
ex7/resources/index.html → ex6/resources/index.html

@@ -11,8 +11,8 @@
 <header>
     <img src="img/rabbit.svg"/>
     <div>
-        <h1>Golang Hands-on</h1>
-        <h2 id="subtitle">&nbsp;</h2>
+        <h1 id="title">...</h1>
+        <h2 id="subtitle">...</h2>
     </div>
 </header>
 
@@ -22,12 +22,16 @@
     </div>
     <div class="egg" onclick="egg2()">
         <img id="egg2" src="img/eggs-in-grass.svg"/>
-    </div>
+    </div>Vrolijk pasen!
     <div class="egg" onclick="egg3()">
         <img id="egg3" src="img/eggs-in-grass.svg"/>
     </div>
-    <div class="egg" onclick="egg4()">
-        <img id="egg4" src="img/eggs-in-grass.svg"/>
+    <div class="egg">
+        <img id="egg4" onclick="egg4()" src="img/eggs-in-grass.svg"/>
+        <form>
+            <label for="name">Naam: </label><input id="name" type="text"/>
+            <label for="message">Bericht: </label><input id="message" type="text"/>
+        </form>
     </div>
     <div class="egg" onclick="egg5()">
         <img id="egg5" src="img/eggs-in-grass.svg"/>

+ 132 - 0
ex6/resources/js/main.js

@@ -0,0 +1,132 @@
+var broken = "img/egg-broken.svg";
+var maxId;
+
+document.addEventListener("DOMContentLoaded", function() {
+    document.getElementById("egg1").style.marginTop = rnd(5) + "em";
+    document.getElementById("egg2").style.marginTop = rnd(15) + "em";
+    document.getElementById("egg3").style.marginTop = rnd(5) + "em";
+    document.getElementById("egg4").style.marginTop = rnd(15) + "em";
+    document.getElementById("egg5").style.marginTop = rnd(5) + "em";
+});
+
+function rnd(range) {
+    return Math.floor((Math.random() * range));
+}
+
+function egg1() {
+    httpGet("/api/greet/", function(data) {
+        document.getElementById("title").textContent = data;
+        document.getElementById("egg1").src = "img/egg1.svg";
+    }, function () {
+        document.getElementById("egg1").src = broken;
+    });
+}
+
+function egg2() {
+    getJson("GET", "/api/greet/", null, function(data) {
+        if (data.title && data.subtitle) {
+            document.getElementById("title").textContent = data.title;
+            document.getElementById("subtitle").textContent = data.subtitle;
+            document.getElementById("egg2").src = "img/egg2.svg";
+        } else {
+            document.getElementById("egg2").src = broken;
+        }
+    }, function () {
+        document.getElementById("egg2").src = broken;
+    });
+}
+
+function egg3() {
+    var nums = [];
+    var count = 50 + rnd(50);
+    var start = rnd(50);
+    var end = start + rnd(count - start);
+    var answer = 0;
+    for (var i = 0; i < count; i++) {
+        var r = rnd(100);
+        nums.push(r);
+        if (i >= start && i < end) {
+            answer += r
+        }
+    }
+    var requestBody = {start: start, end: end, numbers: nums};
+    getJson("POST", "/api/sum/", requestBody, function(data) {
+        if (data.answer === answer && data.contributers === (end - start)) {
+            document.getElementById("egg3").src = "img/egg3.svg";
+        } else {
+            document.getElementById("egg3").src = broken;
+        }
+    }, function () {
+        document.getElementById("egg3").src = broken;
+    });
+}
+
+function egg4() {
+    var name = document.getElementById("name").value;
+    var message = document.getElementById("message").value;
+    if (!name || !message) {
+        document.getElementById("egg4").src = broken;
+    }
+    var requestBody = {
+        name: name,
+        message: message
+    };
+    getJson("POST", "/api/store/", requestBody, function(data) {
+        if (data.id) {
+            maxId = data.id;
+            document.getElementById("egg4").src = "img/egg4.svg";
+        } else {
+            document.getElementById("egg4").src = broken;
+        }
+    }, function () {
+        document.getElementById("egg4").src = broken;
+    });
+}
+
+function egg5() {
+    if (!maxId) {
+        document.getElementById("egg5").src = broken;
+        return;
+    }
+
+    var id = 1 + rnd(maxId);
+    getJson("GET", "/api/store/"+ id, null, function(data) {
+        if (data.name && data.message) {
+            document.getElementById("subtitle").textContent = data.message + " -- " + data.name;
+            document.getElementById("egg5").src = "img/egg5.svg";
+        } else {
+            document.getElementById("egg5").src = broken;
+        }
+    }, function() {
+        document.getElementById("egg5").src = broken;
+    });
+}
+
+function httpGet(url, callbackOK, callbackError) {
+    var req = new XMLHttpRequest();
+    req.onreadystatechange = function() {
+        if (req.readyState === XMLHttpRequest.DONE && req.status === 200) {
+            callbackOK(req.responseText);
+        } else {
+            callbackError();
+        }
+    };
+    req.open("GET", url, true);
+    req.setRequestHeader("Accept", "text/plain");
+    req.send(null);
+}
+
+function getJson(action, url, data, callbackOK, callbackError) {
+    var req = new XMLHttpRequest();
+    req.onreadystatechange = function() {
+        if (req.readyState === XMLHttpRequest.DONE && req.status === 200
+                && req.getResponseHeader("Content-Type").indexOf("application/json") !== -1) {
+            callbackOK(JSON.parse(req.responseText));
+        } else {
+            callbackError();
+        }
+    };
+    req.open(action, url, true);
+    req.setRequestHeader("Accept", "application/json");
+    req.send(JSON.stringify(data));
+}

+ 44 - 7
ex6/resources/main.css

@@ -1,15 +1,52 @@
 body {
-    background-color: #92CD00
+    background-color: #92CD00;
+    font-family: "Trebuchet MS", Helvetica, sans-serif;
+}
+
+header {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+    color: #2C6700;
 }
 
 header img {
-    display: inline;
-    vertical-align: middle;
-    width: 150px;
+    max-width: 10em;
 }
 
 header h1 {
-    display: inline;
-    vertical-align: middle;
-    color: #2C6700
+    font-size: 6vw;
+    margin-bottom: 0.1em;
+}
+
+header h2 {
+    font-size: 3vw;
+    margin-top: 0.1em;
+}
+
+@media screen and (min-width: 800px) {
+    header h1 {
+        font-size: 3em;
+    }
+    header h2 {
+        font-size: 1.5em;
+    }
+}
+
+section {
+    display: flex;
+    flex-wrap: wrap;
+    margin-top: 2em;
+}
+.egg {
+    margin: 2em;
+    display: block;
+    width: 8em;
+    height: 8em;
+}
+
+.egg img {
+    width: 100%;
+    background: url("img/eggs-in-grass.svg") no-repeat;
+    background-size: contain;
 }

+ 0 - 18
ex7/certs/localhost.cert

@@ -1,18 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIC+zCCAeOgAwIBAgIJAOt7/gag9LjJMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV
-BAMMCWxvY2FsaG9zdDAeFw0xNjExMTAxNDUxMDNaFw0yNjExMDgxNDUxMDNaMBQx
-EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
-ggEBAPzIf9Oy1gB84/xpHTcPT14sRQGIo7REyParvRif5357G8vFs1PaMH5/pQCM
-K00mIMvLw56LZsz7byjYabNpXUFMAVJbmz3pX9Y0Mia0wTxB4uWCy0fJcaT7roQz
-fYKAJHlp2mBe9k/ozq2Trsy4Trz0KzrEYAAWnFbh2v3sikKRMvLIwOOjsWVKDySb
-4JZwDqLIdtYXRLPf66WvKDGRqPVLUKfbgl6511QBinCZDdcAffqNUWeMfk6/hxnJ
-u2CPOUUb1bOvWtnbSXL6MWxqtkysdSrmcK0JJ1/5HV6QhGoGDPyfzzSNnZVjqX6d
-9R1dn0TULD3t3mfuJVtDCTTuKTkCAwEAAaNQME4wHQYDVR0OBBYEFDogNDgBq4q4
-bnT3ekTboylmJqbbMB8GA1UdIwQYMBaAFDogNDgBq4q4bnT3ekTboylmJqbbMAwG
-A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAOxuM8I+1o9uDPngbGpnpXkK
-VOScfLQ6BA0ozdbaIvzhItvc0IYofwrXh9ja0MM3OGaGofOcrpHdfx1BVt5tjawv
-pePyZyrFCASg2mtGOaRiD+SfSi6xd1Z6GnKXRV+y7aSSI/R8W/wgstzwS9As540X
-RleXZ6Lo3JIKj8yzaRNylB3VUcBdtkfcL+ijeApfjubOwBYoRUilnXXHbvgwkmII
-qHPHiHAnUtcD94UnCIAHpIy31EPf0LyEZvocXmXAZ30zFoee//GB/q9uVKi+O7jv
-sy3tZD5O3t0c+XE60B+FJTj9tYGtEwlpraYcCPnwwQH0Vc69aG9F18doy6iyWzQ=
------END CERTIFICATE-----

+ 0 - 27
ex7/certs/localhost.key

@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEpgIBAAKCAQEA/Mh/07LWAHzj/GkdNw9PXixFAYijtETI9qu9GJ/nfnsby8Wz
-U9owfn+lAIwrTSYgy8vDnotmzPtvKNhps2ldQUwBUlubPelf1jQyJrTBPEHi5YLL
-R8lxpPuuhDN9goAkeWnaYF72T+jOrZOuzLhOvPQrOsRgABacVuHa/eyKQpEy8sjA
-46OxZUoPJJvglnAOosh21hdEs9/rpa8oMZGo9UtQp9uCXrnXVAGKcJkN1wB9+o1R
-Z4x+Tr+HGcm7YI85RRvVs69a2dtJcvoxbGq2TKx1KuZwrQknX/kdXpCEagYM/J/P
-NI2dlWOpfp31HV2fRNQsPe3eZ+4lW0MJNO4pOQIDAQABAoIBAQCnMaoyiH+5NOHK
-qm+zYkH5pMhDmm/EpRZS6QaaifEgnCanYe0ZkiAvGxg0w4FEduqMQQrzC3sEK227
-k2BeVVtj+/SH16VXjstfr4hYR4fj5iQ/74GqypeLKxijwjbKoRz+3T+eLFE4S2Cg
-OCUOrP0PR2Rsa0Sf6lSftqUU/GX6XiTTHuP8ii3SYF7egQifpDQImWaNZbbZ8Y1U
-BA/HEq/qCrmbDSyNZcimjfG/trB9AMvFTBu9XCq9ZIfOE7MXkqHg/+QDdMaOPTWF
-FNzk2daEppIihvOIZsMa4T6EWuncdZmWnccYYRPCjxxPfmIFWqtFLjcVHa1WYFb2
-RjxxcDqdAoGBAP+SzZ4RzaS24+yojjLUVAE2/nw2uNjDBRjj8V0v7QuwkoaTnFwa
-GxLLKh8E264IvfdcVrG83IOZwx+6Ye29juZZtETh6Evuz7GtXk9STcnd898azBZM
-4MEdUKOKYOUDduK66BvrrmzEEs2wwHCdknkANhPCtikg7NsJrYz6D55fAoGBAP00
-gQO/HpzBr684o5LoBqnWjtNHcnnzlLUoyjbUDyBEardk9QEO1XSV17ULge/QtYgK
-cDwfCGab7QkhOFNHM+KRuFDnjbly6ouWIrakh40XgNMZXN66pSs+280V8GG60KMG
-FA6xtIg1VnbJi0aelR0FOLVtQfU8uA/absGqri9nAoGBAPpcDSDzv8fDgbOryuDw
-LvqhrNpxoNJ2+N9RLHhlIol5B5DduSZgTHU0oKaSU8FAvxc6VZL3t+0TC5phMt15
-/CqbZYS06KK55qvfcauyrJE75FKCJbsSxHlpBj2uqQyvQB8LiaUQU2YpDWKB64Jm
-Wb4+/rqM23We02gfKuwXaoVxAoGBAMQi/aU3ULFA36QtjZfon/DDwRMsjyKkapcC
-J9Hc5nKqTLgPPjI5mWOcBuG5LkfkNhpRa6tGpq1FL5IY55aq2ygwlMQm+gOjXsgN
-3on4XQAQKrxbMzNdBUDAimcNDY3GtoLx3GudgCjamEWfzYFAqmU2fD0dizA6fqHZ
-JF5kplUVAoGBAOIvnhNRXZxRZeT0xMDX7ZKSp8tEgTu9MMz6pg3KMBx2d0QK7MPL
-Dxt3r4nHo041dlKVWwEE9xImBatPb/6EFjSO+k8OsbLamAb/7e71BawV3BBmKIoP
-EBXENVT0AssHah3lkm840I+7ZeiFmBV+pollgwBPjSFIHRf1VE9TyKXw
------END RSA PRIVATE KEY-----

+ 0 - 25
ex7/main.go

@@ -1,25 +0,0 @@
-package main
-
-import (
-	"net/http"
-	"log"
-	"fmt"
-)
-
-func main() {
-	http.HandleFunc("/api/greeting", greetingHandler)
-	http.Handle("/", http.FileServer(http.Dir("resources")))
-	log.Println("Listening on https://localhost:8443")
-	err := http.ListenAndServeTLS(":8443", "certs/localhost.cert", "certs/localhost.key", nil)
-	log.Fatal(err)
-}
-
-func greetingHandler(w http.ResponseWriter, r *http.Request) {
-	w.Header().Set("Content-Type", "text/plain; charset=utf8")
-
-	_, err := fmt.Fprint(w, "Hallo Quintor!")
-
-	if err != nil {
-		log.Println(err)
-	}
-}

+ 0 - 52
ex7/resources/js/main.js

@@ -1,52 +0,0 @@
-var broken = "img/egg-broken.svg";
-
-document.addEventListener("DOMContentLoaded", function() {
-    document.getElementById("egg1").style.marginTop = rnd(5) + "em";
-    document.getElementById("egg2").style.marginTop = rnd(15) + "em";
-    document.getElementById("egg3").style.marginTop = rnd(5) + "em";
-    document.getElementById("egg4").style.marginTop = rnd(15) + "em";
-    document.getElementById("egg5").style.marginTop = rnd(5) + "em";
-});
-
-function rnd(range) {
-    return Math.floor((Math.random() * range));
-}
-
-function egg1() {
-    httpGet("api/greeting", function(data) {
-        document.getElementById("subtitle").textContent = data;
-        document.getElementById("egg1").src = "img/egg1.svg";
-    }, function () {
-        document.getElementById("egg1").src = broken;
-    });
-}
-
-function egg2() {
-    document.getElementById("egg2").src = broken;
-}
-
-function egg3() {
-    document.getElementById("egg3").src = broken; //"img/egg3.svg";
-}
-
-function egg4() {
-    document.getElementById("egg4").src = broken; //"img/egg4.svg";
-}
-
-function egg5() {
-    document.getElementById("egg5").src = broken; //"img/egg5.svg";
-}
-
-function httpGet(url, callbackOK, callbackError) {
-    var xmlHttp = new XMLHttpRequest();
-    xmlHttp.onreadystatechange = function() {
-        if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
-            callbackOK(xmlHttp.responseText);
-        } else {
-            callbackError();
-        }
-    };
-    xmlHttp.open("GET", url, true);
-    xmlHttp.send(null);
-}
-

+ 0 - 52
ex7/resources/main.css

@@ -1,52 +0,0 @@
-body {
-    background-color: #92CD00;
-    font-family: "Trebuchet MS", Helvetica, sans-serif;
-}
-
-header {
-    display: flex;
-    flex-wrap: wrap;
-    align-items: center;
-    color: #2C6700;
-}
-
-header img {
-    max-width: 10em;
-}
-
-header h1 {
-    font-size: 6vw;
-    margin-bottom: 0.1em;
-}
-
-header h2 {
-    font-size: 3vw;
-    margin-top: 0.1em;
-}
-
-@media screen and (min-width: 800px) {
-    header h1 {
-        font-size: 3em;
-    }
-    header h2 {
-        font-size: 1.5em;
-    }
-}
-
-section {
-    display: flex;
-    flex-wrap: wrap;
-    margin-top: 2em;
-}
-.egg {
-    margin: 2em;
-    display: block;
-    width: 8em;
-    height: 8em;
-}
-
-.egg img {
-    width: 100%;
-    background: url("img/eggs-in-grass.svg") no-repeat;
-    background-size: contain;
-}

BIN
opgave5.zip


BIN
opgave6.zip