Obsah
1. Tvorba webových aplikací v Go s využitím projektu Gorilla web toolkit
2. Gorilla web toolkit není webový framework
3. Instalace balíčku gorilla/mux
4. Jednoduchý HTTP server postavený nad standardním balíčkem net/http
5. Jak číst zprávy vypisované nástrojem curl?
6. HTTP server používající balíček gorilla/mux
7. Specifikace HTTP metody pro jednotlivé handlery
8. Nastavení nové hodnoty čítače pomocí HTTP metody PUT
9. Kostra vylepšené REST API služby – správa (databáze) osob
10. Předávání dat s využitím formátu JSON
11. Realizace jednotlivých částí služby
12. Otestování všech nově implementovaných operací
13. Omezení znaků, které se mohou nacházet v ID osob
14. Specifikace hlaviček dotazů, které budou akceptovány
15. Otestování nové varianty služby
16. Služby s více přístupovými body a použití podsměrovačů (subrouter)
17. Přidání mezivrstev do řetězce zpracování požadavku – middleware
18. Poslední varianta jednoduché REST API služby
19. Repositář s demonstračními příklady
1. Tvorba webových aplikací v Go s využitím projektu Gorilla web toolkit
Již několikrát jsme se v seriálu o programovacím jazyce Go setkali s tvrzením, že se tento jazyk poměrně často používá pro tvorbu webových aplikací (resp. přesněji řečeno především pro jejich backend) a taktéž pro vytváření služeb a mikroslužeb s rozhraním REST API (popř. s dalšími rozhraními – STOMP atd.). Již v základní sadě standardních knihoven nalezneme balíček pojmenovaný net/http, který sám o sobě postačuje pro vytvoření plnohodnotného HTTP či HTTPS serveru. Ovšem možnosti tohoto balíčku nemusí být ve všech případech dostatečné, zejména ve chvíli, kdy je REST API realizované službou rozsáhlejší, popř. když se v URI vyskytuje specifikace většího množství prostředků (resources). A právě v těchto situacích je vhodné využít dalších knihoven, které pro Go vznikly a které na net/http navazují. Jednou z těchto knihoven je i gorilla/mux, která je součástí většího celku nazvaného Gorilla web toolkit.
Některými možnostmi nabízenými výše zmíněnou knihovnou gorilla/mux se budeme zabývat v dnešním článku, v němž si ukážeme i několik demonstračních příkladů založených právě na této knihovně. Bude se jednat o implementace jednoduchých (mikro)služeb.
- Mikroslužby: moderní aplikace využívající známých konceptů
https://www.root.cz/clanky/mikrosluzby-moderni-aplikace-vyuzivajici-znamych-konceptu/ - Způsoby uložení dat v aplikacích založených na mikroslužbách
https://www.root.cz/clanky/zpusoby-ulozeni-dat-v-aplikacich-zalozenych-na-mikrosluzbach/ - Posílání zpráv v aplikacích založených na mikroslužbách
https://www.root.cz/clanky/posilani-zprav-v-aplikacich-zalozenych-na-mikrosluzbach/ - Použití nástroje Apache Kafka v aplikacích založených na mikroslužbách
https://www.root.cz/clanky/pouziti-nastroje-apache-kafka-v-aplikacich-zalozenych-na-mikrosluzbach/ - Nástroje a služby využívané při nasazování mikroslužeb
https://www.root.cz/clanky/nastroje-a-sluzby-vyuzivane-pri-nasazovani-mikrosluzeb/ - Prechod hostingu na mikroslužby: cesta zlyhaní a úspechov
https://www.root.cz/clanky/prechod-hostingu-na-mikrosluzby-cesta-zlyhani-a-uspechov/ - Mikroslužby založené na REST API
https://www.root.cz/clanky/mikrosluzby-zalozene-na-rest-api/
2. Gorilla web toolkit není webový framework
Samotní tvůrci projektu Gorilla web toolkit říkají, že se nejedná o ucelený webový framework, ale spíše o sadu užitečných knihoven, které lze použít společně s dalšími standardními knihovnami (net/http, text/template), ale i s knihovnami, jež je nutné nejdříve nainstalovat. A skutečně – dnes využijeme jen jediný modul z celého projektu Gorilla web toolkit – knihovnu gorilla/mux, a to bez toho, abychom se museli vzdát možností nabízených ostatními knihovnami. Nic nám přitom nebrání, aby zbytek aplikace používal nějaký šablonovací nástroj, jiný nástroj pro logování, řešení pro MVC atd. atd.
3. Instalace balíčku gorilla/mux
Tato kapitola bude velmi stručná, protože samotná instalace balíčku gorilla/mux je stejně snadná a bezproblémová jako instalace jakéhokoli jiného balíčku určeného pro programovací jazyk Go. V případě, že se nepoužije systém modulů, provede se instalace následujícím příkazem:
$ go get github.com/gorilla/mux
Pokud používáte systém modulů (Go 1.11, 1.12 nebo 1.13), je nutné nejdříve moduly pro danou aplikaci správně inicializovat:
$ go mod init jméno_aplikace_či_balíčku
Následně do libovolného zdrojového kódu aplikace přidejte import modulu a použijte některou jeho funkci, například funkci pro vytvoření nového směrovače:
import "github.com/gorilla/mux" mux.NewRouter()
Při prvním překladu aplikace se příslušný modul automaticky stáhne a inicializuje v adresářové struktuře používané moduly:
$ go build jméno_aplikace_či_balíčku
4. Jednoduchý HTTP server postavený nad standardním balíčkem net/http
Nejprve si ukažme (resp. přesněji řečeno připomeňme), jakým způsobem se vytváří webové servery či služby založené na REST API s využitím standardního balíčku net/http. Implementovaný HTTP server, jehož úplný zdrojový kód naleznete na adrese https://github.com/tisnik/go-root/blob/master/article38/01_simple_http_server.go, bude obsluhovat pouze dva koncové body „/“ a „/counter“. V prvním případě se vrátí konstantní odpověď „Hello world!“ následovaná koncem řádku, ve druhém případě se pak vrátí hodnota čítače, který je s každým novým požadavkem zvýšen o jedničku. Samotný čítač je zvýšen uvnitř mutexu:
package main import ( "fmt" "io" "log" "net/http" "os" "sync" ) const ADDRESS = ":8080" var counter int var mutex = &sync.Mutex{} func mainEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "Hello world!\n") } func counterEndpoint(writer http.ResponseWriter, request *http.Request) { mutex.Lock() counter++ fmt.Fprintf(writer, "Counter: %d\n", counter) mutex.Unlock() } func main() { http.HandleFunc("/", mainEndpoint) http.HandleFunc("/counter", counterEndpoint) log.Println("Starting HTTP server at address", ADDRESS) err := http.ListenAndServe(ADDRESS, nil) if err != nil { log.Fatal("Unable to initialize HTTP server", err) os.Exit(2) } }
func counterEndpoint(writer http.ResponseWriter, request *http.Request) { mutex.Lock() defer mutex.Unlock() counter++ fmt.Fprintf(writer, "Counter: %d\n", counter) }
Povšimněte si, jakým způsobem byly koncové body navázány na příslušné handlery, tj. obslužné funkce:
http.HandleFunc("/", mainEndpoint) http.HandleFunc("/counter", counterEndpoint)
Právě deklarace handlerů a jejich navázání na koncové body je v případě použití balíčku gorilla/mux vyřešena odlišným způsobem, jak si to ostatně ukážeme v navazujících kapitolách.
Otestování činnosti této aplikace je jednoduché a postačí nám k tomu univerzální nástroj curl. Nejprve vyzkoušíme, zda server dokáže odpovědět na jednoduchý požadavek / (metoda GET):
$ curl localhost:8080 Hello world!
A následně otestujeme i to, zda a jak korektně se mění hodnota čítače:
$ curl localhost:8080/counter Counter: 1 $ curl localhost:8080/counter Counter: 2
5. Jak číst zprávy vypisované nástrojem curl?
V případě, že nástroj curl spustíme bez přepínače -v, bude vypisovat pouze samotná těla odpovědí serveru, popř. základní chybová hlášení. Většinou ovšem potřebujeme znát podrobnější informace, a to jak o poslaném požadavku (request), tak i případné odpovědi serveru (response). Z tohoto důvodu se používá již výše zmíněný přepínač -v, který zajistí, že nástroj curl začne vypisovat tři typy zpráv, které velmi snadno rozeznáme podle prvního znaku na každém řádku:
- Dotaz posílaný od klienta k serveru začíná znakem „>“
- Odpověď serveru je zobrazena na řádcích, které začínají znakem „<“
- Ostatní informace o činnosti samotného nástroje curl začínají znakem „*“
- Tělo odpovědi serveru je zobrazeno v nezměněné podobě, tj. není před ním zobrazen žádný další znak
Jednotlivé typy zpráv jsou patrné i z následujícího pokusu o přístup na adresu localhost:8080/:
$ curl -v localhost:8080 * Rebuilt URL to: localhost:8080/ * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > GET / HTTP/1.1 > User-Agent: curl/7.35.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 200 OK < Date: Sat, 12 Oct 2019 19:44:14 GMT < Content-Length: 13 < Content-Type: text/plain; charset=utf-8 < Hello world! * Connection #0 to host localhost left intact
Podobné informace získáme i při požadavku na vrácení hodnoty čítače:
$ curl -v localhost:8080/counter * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > GET /counter HTTP/1.1 > User-Agent: curl/7.35.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 200 OK < Date: Sat, 12 Oct 2019 19:44:17 GMT < Content-Length: 11 < Content-Type: text/plain; charset=utf-8 < Counter: 1 * Connection #0 to host localhost left intact
6. HTTP server používající balíček gorilla/mux
Nyní se podívejme na způsob realizace jednoduchého HTTP serveru, který bude používat balíček gorilla/mux. Základní služby poskytované serverem budou stejné, jako v předchozím demonstračním příkladu, což konkrétně znamená, že vrácen bude buď konstantní řetězec, nebo aktuální hodnota čítače. Jediné změny nastanou náhradou následujících dvou řádků s registrací handlerů:
http.HandleFunc("/", mainEndpoint) http.HandleFunc("/counter", counterEndpoint)
V upraveném zdrojovém kódu demonstračního příkladu použijeme takzvaný směrovač neboli router poskytovaný knihovnou gorilla/mux. Jeho konstrukce může vypadat takto:
router := mux.NewRouter()
Popř. můžeme explicitně specifikovat, zda se budou URI typu /cesta a /cesta/ považovat za shodné či nikoli:
router := mux.NewRouter().StrictSlash(true)
Dále zaregistrujeme oba handlery, ovšem nyní použijeme metodu router.HandleFunc a nikoli funkci http.HandleFunc (z balíčku net/http):
router.HandleFunc("/", mainEndpoint) router.HandleFunc("/counter", counterEndpoint)
Nakonec je pochopitelně nutné HTTP server spustit. Povšimněte si, že se nyní využije druhý parametr funkce http.ListenAndServe – již se zde nepředává hodnota nil, ale instance právě nakonfigurovaného směrovače:
err := http.ListenAndServe(ADDRESS, router)
Úplný zdrojový kód upraveného příkladu, který naleznete na adrese https://github.com/tisnik/go-root/blob/master/article38/02_http_server_with_mux.go, vypadá následovně:
package main import ( "fmt" "github.com/gorilla/mux" "io" "log" "net/http" "os" "sync" ) const ADDRESS = ":8080" var counter int var mutex = &sync.Mutex{} func mainEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "Hello world!\n") } func counterEndpoint(writer http.ResponseWriter, request *http.Request) { mutex.Lock() counter++ fmt.Fprintf(writer, "Counter: %d\n", counter) mutex.Unlock() } func main() { router := mux.NewRouter().StrictSlash(true) router.HandleFunc("/", mainEndpoint) router.HandleFunc("/counter", counterEndpoint) log.Println("Starting HTTP server at address", ADDRESS) err := http.ListenAndServe(ADDRESS, router) if err != nil { log.Fatal("Unable to initialize HTTP server", err) os.Exit(2) } }
Funkcionalitu tohoto příkladu snadno otestujeme, a to opět s využitím nástroje curl:
$ curl -v localhost:8080 * Rebuilt URL to: localhost:8080/ * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > GET / HTTP/1.1 > User-Agent: curl/7.35.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 200 OK < Date: Sun, 13 Oct 2019 16:25:33 GMT < Content-Length: 13 < Content-Type: text/plain; charset=utf-8 < Hello world! * Connection #0 to host localhost left intact
Otestování funkce čítače:
$ curl -v localhost:8080/counter * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > GET /counter HTTP/1.1 > User-Agent: curl/7.35.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 200 OK < Date: Sun, 13 Oct 2019 16:25:48 GMT < Content-Length: 11 < Content-Type: text/plain; charset=utf-8 < Counter: 1 * Connection #0 to host localhost left intact
7. Specifikace HTTP metody pro jednotlivé handlery
V případě, že u předchozího demonstračního příkladu použijeme jinou HTTP metodu než GET (což je pro nástroj curl výchozí metoda, pokud ovšem nebudeme na server posílat data), bude například čítač stále přístupný. O tom se ostatně můžeme velmi snadno přesvědčit, pokud budeme explicitně specifikovat metodu POST, PUT či dokonce DELETE:
$ curl -v -X POST localhost:8080/counter * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > POST /counter HTTP/1.1 > User-Agent: curl/7.35.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 200 OK < Date: Sun, 13 Oct 2019 16:31:50 GMT < Content-Length: 11 < Content-Type: text/plain; charset=utf-8 < Counter: 2 * Connection #0 to host localhost left intact $ curl -v -X DELETE localhost:8080/counter * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > DELETE /counter HTTP/1.1 > User-Agent: curl/7.35.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 200 OK < Date: Sun, 13 Oct 2019 16:31:56 GMT < Content-Length: 11 < Content-Type: text/plain; charset=utf-8 < Counter: 3 * Connection #0 to host localhost left intact
Takové chování ovšem většinou u služeb postavených nad REST API není ideální, protože s prostředky, které jsou přes API obsluhovány, se provádí různé operace typu CRUD. Samozřejmě je možné i při použití základního balíčku net/http získat jméno použité metody, ovšem nejedná se o ideální řešení. To nám nabízí až balíček gorilla/mux, v němž můžeme omezit volání handleru pouze pro danou metodu. V našem demonstračním příkladu prozatím pouze čteme hodnoty (prostředků) a neměníme je, takže nám postačuje použít metodu GET omezit použití ostatních metod:
router := mux.NewRouter().StrictSlash(true) router.HandleFunc("/", mainEndpoint).Methods("GET") router.HandleFunc("/counter", counterEndpoint).Methods("GET")
Upravený zdrojový kód demonstračního příkladu bude vypadat následovně:
package main import ( "fmt" "github.com/gorilla/mux" "io" "log" "net/http" "os" "sync" ) const ADDRESS = ":8080" var counter int var mutex = &sync.Mutex{} func mainEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "Hello world!\n") } func counterEndpoint(writer http.ResponseWriter, request *http.Request) { mutex.Lock() counter++ fmt.Fprintf(writer, "Counter: %d\n", counter) mutex.Unlock() } func main() { router := mux.NewRouter().StrictSlash(true) router.HandleFunc("/", mainEndpoint).Methods("GET") router.HandleFunc("/counter", counterEndpoint).Methods("GET") log.Println("Starting HTTP server at address", ADDRESS) err := http.ListenAndServe(ADDRESS, router) if err != nil { log.Fatal("Unable to initialize HTTP server", err) os.Exit(2) } }
Můžeme si ihned otestovat, jak se bude nová služba chovat při použití různých HTTP metod.
Výchozí metoda GET:
$ curl -v localhost:8080/ * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > GET / HTTP/1.1 > User-Agent: curl/7.35.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 200 OK < Date: Sun, 13 Oct 2019 18:45:33 GMT < Content-Length: 13 < Content-Type: text/plain; charset=utf-8 < Hello world!
Metoda PUT:
$ curl -v -X PUT localhost:8080/ * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > PUT / HTTP/1.1 > User-Agent: curl/7.35.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 405 Method Not Allowed < Date: Sun, 13 Oct 2019 18:45:37 GMT < Content-Length: 0 < * Connection #0 to host localhost left intact
Metoda POST:
$ curl -v -X POST localhost:8080/ * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > POST / HTTP/1.1 > User-Agent: curl/7.35.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 405 Method Not Allowed < Date: Sun, 13 Oct 2019 18:45:42 GMT < Content-Length: 0 < * Connection #0 to host localhost left intact
Metoda DELETE:
$ curl -v -X DELETE localhost:8080/ * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > DELETE / HTTP/1.1 > User-Agent: curl/7.35.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 405 Method Not Allowed < Date: Sun, 13 Oct 2019 18:45:45 GMT < Content-Length: 0 < * Connection #0 to host localhost left intact
8. Nastavení nové hodnoty čítače pomocí HTTP metody PUT
Naši prozatím velmi primitivní REST API službu upravíme takovým způsobem, že čítač bude moci být změněn posláním požadavku s HTTP metodou PUT. Nová hodnota čítače by přitom měla být umístěna v těle požadavku, odkud bude přečtena a zpracována. Samotný směrovač nyní bude muset rozlišit mezi přístupem k čítači metodou GET (čtení) a metodou PUT (zápis):
router.HandleFunc("/counter", getCounterEndpoint).Methods("GET") router.HandleFunc("/counter", setCounterEndpoint).Methods("PUT")
Podívejme se na prozatím značně zjednodušené načtení nové hodnoty čítače z těla požadavku. Pouze pokud tělo požadavku obsahuje řetězec s celým číslem, bude čítač skutečně změněn:
body, err := ioutil.ReadAll(request.Body) if err == nil { number, err := strconv.ParseInt(string(body), 10, 0) if err == nil { setCounter(int(number)) fmt.Fprintf(writer, "New counter value: %d\n", counter) } }
Úplný zdrojový kód tohoto příkladu vypadá následovně:
package main import ( "fmt" "github.com/gorilla/mux" "io" "io/ioutil" "log" "net/http" "os" "strconv" "sync" ) const ADDRESS = ":8080" var counter int var mutex = &sync.Mutex{} func mainEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "Hello world!\n") } func getCounterEndpoint(writer http.ResponseWriter, request *http.Request) { mutex.Lock() counter++ fmt.Fprintf(writer, "Counter: %d\n", counter) mutex.Unlock() } func setCounter(new_value int) { mutex.Lock() counter = new_value mutex.Unlock() } func setCounterEndpoint(writer http.ResponseWriter, request *http.Request) { body, err := ioutil.ReadAll(request.Body) if err == nil { number, err := strconv.ParseInt(string(body), 10, 0) if err == nil { setCounter(int(number)) fmt.Fprintf(writer, "New counter value: %d\n", counter) } else { log.Printf("conversion failed for input string '%s'", string(body)) } } else { log.Printf("request body is empty") } } func main() { router := mux.NewRouter().StrictSlash(true) router.HandleFunc("/", mainEndpoint).Methods("GET") router.HandleFunc("/counter", getCounterEndpoint).Methods("GET") router.HandleFunc("/counter", setCounterEndpoint).Methods("PUT") log.Println("Starting HTTP server at address", ADDRESS) err := http.ListenAndServe(ADDRESS, router) if err != nil { log.Fatal("Unable to initialize HTTP server", err) os.Exit(2) } }
Otestování nové funkcionality, opět s využitím nástroje curl:
$ curl localhost:8080/counter Counter: 1 $ curl localhost:8080/counter Counter: 2 $ curl -X PUT localhost:8080/counter -d "100" New counter value: 100 $ curl localhost:8080/counter Counter: 101
9. Kostra vylepšené REST API služby – správa (databáze) osob
V dalších kapitolách budeme postupně rozšiřovat a vylepšovat REST API službu, která bude zajišťovat velmi jednoduchou správu osob. K dispozici budou tyto operace:
# | Operace | Volání | Metoda |
---|---|---|---|
1 | výpis celé databáze | /person | GET |
2 | informace o zvolené osobě | /person/ID_OSOBY | GET |
3 | přidání nové osoby do databáze | /person/ID_OSOBY | POST |
4 | změna údajů v databázi | /person/ID_OSOBY | PUT |
5 | vymazání osoby | /person/ID_OSOBY | DELETE |
Při specifikaci handlerů využijeme toho, že (proměnné) jméno prostředku lze uzavřít do složených závorek:
router.HandleFunc("/person", listAllPersonsEndpoint).Methods("GET") router.HandleFunc("/person/{id}", getPersonEndpoint).Methods("GET") router.HandleFunc("/person/{id}", createPersonEndpoint).Methods("POST") router.HandleFunc("/person/{id}", updatePersonEndpoint).Methods("PUT") router.HandleFunc("/person/{id}", deletePersonEndpoint).Methods("DELETE")
Kostra této služby, prozatím bez implementace jednotlivých operací v handlerech, může vypadat následovně:
package main import ( "github.com/gorilla/mux" "io" "log" "net/http" "os" ) const ADDRESS = ":8080" func mainEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "Hello world!\n") } func listAllPersonsEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "LIST ALL PERSONS\n") } func getPersonEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "GET PERSON\n") } func createPersonEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "CREATE PERSON\n") } func updatePersonEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "UPDATE PERSON\n") } func deletePersonEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "DELETE PERSON\n") } func main() { router := mux.NewRouter().StrictSlash(true) router.HandleFunc("/", mainEndpoint).Methods("GET") router.HandleFunc("/person", listAllPersonsEndpoint).Methods("GET") router.HandleFunc("/person/{id}", getPersonEndpoint).Methods("GET") router.HandleFunc("/person/{id}", createPersonEndpoint).Methods("POST") router.HandleFunc("/person/{id}", updatePersonEndpoint).Methods("PUT") router.HandleFunc("/person/{id}", deletePersonEndpoint).Methods("DELETE") log.Println("Starting HTTP server at address", ADDRESS) err := http.ListenAndServe(ADDRESS, router) if err != nil { log.Fatal("Unable to initialize HTTP server", err) os.Exit(2) } }
10. Předávání dat s využitím formátu JSON
U mnoha služeb postavených na REST API se data předávají ve formátu JSON. Práci s tímto formátem jsme si již ukázali v předchozích částech tohoto seriálu, takže můžeme relativně snadno naši službu rozšířit takovým způsobem, aby dokázala data o osobách jak posílat, tak i načítat, a to právě ve formátu JSON. Nejdříve je nutné specifikovat, jak se jednotlivé atributy osob převedou na klíče ve formátu JSON. V Go musíme používat velká písmena u všech exportovaných/importovaných atributů, zatímco v JSONu se typicky používají písmena malá. Vyřešení převodu je v tomto případě snadné:
type Person struct { Firstname string `json:"firstname"` Surname string `json:"lastname"` }
Posílání dat v těch handlerech, které vrací seznam osob či informace o vybrané osobě, zajistí tento úryvek kódu:
json.NewEncoder(writer).Encode(persons)
Pro jednu osobu pak:
person, found := persons[id] json.NewEncoder(writer).Encode(person)
Poněkud komplikovanější je získání dat poslaných klientem serveru. Zde je nutné použít JSON dekodér, kterému se předá celé tělo požadavku a následně otestovat, zda se načtení a parsing JSONu podařil či nikoli. V nejjednodušší variantě lze tuto operaci provést následujícím způsobem:
var person Person err := json.NewDecoder(request.Body).Decode(&person) if err == nil { log.Println("JSON decoded") persons[id] = person } else { log.Println(err) }
11. Realizace jednotlivých částí služby
V demonstrační aplikaci použijeme velmi jednoduchou formu „databáze“, která bude pro jednoduchost tvořena mapou s klíči typu řetězec (ID osoby) a hodnotami typu Person:
type Person struct { Firstname string `json:"firstname"` Surname string `json:"lastname"` }
Při inicializaci služby mapu naplníme dvěma záznamy:
func init() { persons = make(map[string]Person) persons["LT"] = Person{"Linus", "Torvalds"} persons["RP"] = Person{"Rob", "Pike"} }
Následují handlery jednotlivých operací, nejdříve pro přečtení a vrácení osoby pro zadané ID. Povšimněte si, jak se přistupuje k parametrům požadavku zadaným v URL:
func getPersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := mux.Vars(request)["id"] person, found := persons[id] if found { json.NewEncoder(writer).Encode(person) } else { json.NewEncoder(writer).Encode(nil) } }
Pro přidání nové osoby do databáze je nejdříve nutné získat hodnoty předané klientem v JSON formátu, ovšem pouze v případě, že osoba s daným ID v databázi ještě neexistuje:
func createPersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := mux.Vars(request)["id"] _, found := persons[id] if !found { processPersonFromPayload(id, request) } json.NewEncoder(writer).Encode(persons) }
Vymazání osoby z databáze je snadnější:
func deletePersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := mux.Vars(request)["id"] _, found := persons[id] if found { delete(persons, id) } json.NewEncoder(writer).Encode(persons) }
Úplný zdrojový kód příkladu, do něhož byl přidán i handler pro změnu údajů o osobě, vypadá takto:
package main import ( "encoding/json" "github.com/gorilla/mux" "io" "log" "net/http" "os" ) const ADDRESS = ":8080" type Person struct { Firstname string `json:"firstname"` Surname string `json:"lastname"` } var persons map[string]Person func init() { persons = make(map[string]Person) persons["LT"] = Person{"Linus", "Torvalds"} persons["RP"] = Person{"Rob", "Pike"} } func mainEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "Hello world!\n") } func listAllPersonsEndpoint(writer http.ResponseWriter, request *http.Request) { json.NewEncoder(writer).Encode(persons) } func getPersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := mux.Vars(request)["id"] person, found := persons[id] if found { json.NewEncoder(writer).Encode(person) } else { json.NewEncoder(writer).Encode(nil) } } func processPersonFromPayload(id string, request *http.Request) { var person Person err := json.NewDecoder(request.Body).Decode(&person) if err == nil { log.Println("JSON decoded") persons[id] = person } else { log.Println(err) } } func createPersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := mux.Vars(request)["id"] _, found := persons[id] if !found { processPersonFromPayload(id, request) } json.NewEncoder(writer).Encode(persons) } func updatePersonEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "UPDATE PERSON\n") id := mux.Vars(request)["id"] _, found := persons[id] if found { processPersonFromPayload(id, request) } json.NewEncoder(writer).Encode(persons) } func deletePersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := mux.Vars(request)["id"] _, found := persons[id] if found { delete(persons, id) } json.NewEncoder(writer).Encode(persons) } func main() { router := mux.NewRouter().StrictSlash(true) router.HandleFunc("/", mainEndpoint).Methods("GET") router.HandleFunc("/person", listAllPersonsEndpoint).Methods("GET") router.HandleFunc("/person/{id}", getPersonEndpoint).Methods("GET") router.HandleFunc("/person/{id}", createPersonEndpoint).Methods("POST") router.HandleFunc("/person/{id}", updatePersonEndpoint).Methods("PUT") router.HandleFunc("/person/{id}", deletePersonEndpoint).Methods("DELETE") log.Println("Starting HTTP server at address", ADDRESS) err := http.ListenAndServe(ADDRESS, router) if err != nil { log.Fatal("Unable to initialize HTTP server", err) os.Exit(2) } }
12. Otestování všech nově implementovaných operací
Nové operace popsané v předchozích kapitolách si otestujeme, opět pomocí nástroje curl. Postupně budou ukázány všechny operace CRUD (create, read, update a delete):
Create:
$ curl -X POST localhost:8080/person/KM -d '{"firstname":"Ken","lastname":"Thompson"}' {"KM":{"firstname":"Ken","lastname":"Thompson"},"LT":{"firstname":"Linus","lastname":"Torvalds"},"RP":{"firstname":"Rob","lastname":"Pike"}}
Read:
$ curl localhost:8080/person {"KM":{"firstname":"Ken","lastname":"Thompson"},"LT":{"firstname":"Linus","lastname":"Torvalds"},"RP":{"firstname":"Rob","lastname":"Pike"}}
Update:
$ curl -X PUT localhost:8080/person/RP -d '{"firstname":"Robert","lastname":"Pike"}' {"KM":{"firstname":"Ken","lastname":"Thompson"},"LT":{"firstname":"Linus","lastname":"Torvalds"},"RP":{"firstname":"Robert","lastname":"Pike"}}
Delete:
$ curl -X DELETE localhost:8080/person/LT {"KM":{"firstname":"Ken","lastname":"Thompson"},"RP":{"firstname":"Robert","lastname":"Pike"}}
$ curl -X POST localhost:8080/person/KM -d @ken.json {"KM":{"firstname":"Ken","lastname":"Thompson"},"LT":{"firstname":"Linus","lastname":"Torvalds"},"RP":{"firstname":"Rob","lastname":"Pike"}} $ curl -X PUT localhost:8080/person/RP -d @rp.json {"KM":{"firstname":"Ken","lastname":"Thompson"},"LT":{"firstname":"Linus","lastname":"Torvalds"},"RP":{"firstname":"Robert","lastname":"Pike"}}
13. Omezení znaků, které se mohou nacházet v ID osob
Prozatím jsme v ID osoby mohli používat prakticky libovolné znaky, ovšem v praxi tomu tak být nemusí. Pokud například službu upravíme takovým způsobem, že ID osob budou reprezentovány celými čísly (což je obvyklé), změní se nepatrně vlastní „databáze“:
var persons map[int]Person persons = make(map[int]Person) persons[0] = Person{"Linus", "Torvalds"} persons[1] = Person{"Rob", "Pike"}
Ovšem budeme muset změnit i jednotlivé handlery, aby se akceptovaly jen skutečná ID a nikoli libovolné znaky. Základní omezení znaků ve jméně prostředků můžeme provést již na úrovni knihovny gorilla/mux, a to zcela jednoduše – zapsáním regulárního výrazu v deklaraci URI. Regulární výraz je od jména prostředku oddělen dvojtečkou:
router.HandleFunc("/person/{id:[0-9]+}", getPersonEndpoint).Methods("GET") router.HandleFunc("/person/{id:[0-9]+}", createPersonEndpoint).Methods("POST") router.HandleFunc("/person/{id:[0-9]+}", updatePersonEndpoint).Methods("PUT") router.HandleFunc("/person/{id:[0-9]+}", deletePersonEndpoint).Methods("DELETE")
Dále vyčleníme funkci pro získání ID z dotazu (prozatím bez složitějších kontrol na rozsah hodnot ID):
func retrieveIdRequestParameter(request *http.Request) int { id_var := mux.Vars(request)["id"] id, _ := strconv.ParseInt(id_var, 10, 0) return int(id) }
V realizaci služby použijeme výše uvedenou funkci retrieveIdRequestParameter pro získání ID ve všech handlerech, kde se s ID osob pracuje:
package main import ( "encoding/json" "github.com/gorilla/mux" "io" "log" "net/http" "os" "strconv" ) const ADDRESS = ":8080" type Person struct { Firstname string `json:"firstname"` Surname string `json:"lastname"` } var persons map[int]Person func init() { persons = make(map[int]Person) persons[0] = Person{"Linus", "Torvalds"} persons[1] = Person{"Rob", "Pike"} } func mainEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "Hello world!\n") } func listAllPersonsEndpoint(writer http.ResponseWriter, request *http.Request) { json.NewEncoder(writer).Encode(persons) } func retrieveIdRequestParameter(request *http.Request) int { id_var := mux.Vars(request)["id"] id, _ := strconv.ParseInt(id_var, 10, 0) return int(id) } func getPersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := retrieveIdRequestParameter(request) person, found := persons[id] if found { json.NewEncoder(writer).Encode(person) } else { json.NewEncoder(writer).Encode(nil) } } func processPersonFromPayload(id int, request *http.Request) { var person Person err := json.NewDecoder(request.Body).Decode(&person) if err == nil { log.Println("JSON decoded") persons[id] = person } else { log.Println(err) } } func createPersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := retrieveIdRequestParameter(request) _, found := persons[id] if !found { processPersonFromPayload(id, request) } json.NewEncoder(writer).Encode(persons) } func updatePersonEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "UPDATE PERSON\n") id := retrieveIdRequestParameter(request) _, found := persons[id] if found { processPersonFromPayload(id, request) } json.NewEncoder(writer).Encode(persons) } func deletePersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := retrieveIdRequestParameter(request) _, found := persons[id] if found { delete(persons, id) } json.NewEncoder(writer).Encode(persons) } func main() { router := mux.NewRouter().StrictSlash(true) router.HandleFunc("/", mainEndpoint).Methods("GET") router.HandleFunc("/person", listAllPersonsEndpoint).Methods("GET") router.HandleFunc("/person/{id:[0-9]+}", getPersonEndpoint).Methods("GET") router.HandleFunc("/person/{id:[0-9]+}", createPersonEndpoint).Methods("POST") router.HandleFunc("/person/{id:[0-9]+}", updatePersonEndpoint).Methods("PUT") router.HandleFunc("/person/{id:[0-9]+}", deletePersonEndpoint).Methods("DELETE") log.Println("Starting HTTP server at address", ADDRESS) err := http.ListenAndServe(ADDRESS, router) if err != nil { log.Fatal("Unable to initialize HTTP server", err) os.Exit(2) } }
14. Specifikace hlaviček dotazů, které budou akceptovány
Balíček gorilla/mux umožňuje přesně specifikovat hlavičky HTTP dotazů posílaných klientem, které jsou službou vyžadovány. Můžeme si to ukázat na obvyklé hlavičce „Content-type“, u níž budeme striktně vyžadovat hodnotu „application-json“, pochopitelně ovšem jen u těch metod, v nichž se předávají data (od klienta k serveru). V našem konkrétním případě se jedná o metody POST a PUT určené pro přidání nové osoby, resp. pro úpravu údajů o již existující osobě:
router.HandleFunc("/person/{id:[0-9]+}", createPersonEndpoint).Methods("POST").Headers("Content-Type", "application/json") router.HandleFunc("/person/{id:[0-9]+}", updatePersonEndpoint).Methods("PUT").Headers("Content-Type", "application/json")
Nová varianta naší REST API služby se změní jen nepatrně:
package main import ( "encoding/json" "github.com/gorilla/mux" "io" "log" "net/http" "os" "strconv" ) const ADDRESS = ":8080" type Person struct { Firstname string `json:"firstname"` Surname string `json:"lastname"` } var persons map[int]Person func init() { persons = make(map[int]Person) persons[0] = Person{"Linus", "Torvalds"} persons[1] = Person{"Rob", "Pike"} } func mainEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "Hello world!\n") } func listAllPersonsEndpoint(writer http.ResponseWriter, request *http.Request) { json.NewEncoder(writer).Encode(persons) } func retrieveIdRequestParameter(request *http.Request) int { id_var := mux.Vars(request)["id"] id, _ := strconv.ParseInt(id_var, 10, 0) return int(id) } func getPersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := retrieveIdRequestParameter(request) person, found := persons[id] if found { json.NewEncoder(writer).Encode(person) } else { json.NewEncoder(writer).Encode(nil) } } func processPersonFromPayload(id int, request *http.Request) { var person Person err := json.NewDecoder(request.Body).Decode(&person) if err == nil { log.Println("JSON decoded") persons[id] = person } else { log.Println(err) } } func createPersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := retrieveIdRequestParameter(request) _, found := persons[id] if !found { processPersonFromPayload(id, request) } json.NewEncoder(writer).Encode(persons) } func updatePersonEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "UPDATE PERSON\n") id := retrieveIdRequestParameter(request) _, found := persons[id] if found { processPersonFromPayload(id, request) } json.NewEncoder(writer).Encode(persons) } func deletePersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := retrieveIdRequestParameter(request) _, found := persons[id] if found { delete(persons, id) } json.NewEncoder(writer).Encode(persons) } func main() { router := mux.NewRouter().StrictSlash(true) router.HandleFunc("/", mainEndpoint).Methods("GET") router.HandleFunc("/person", listAllPersonsEndpoint).Methods("GET") router.HandleFunc("/person/{id:[0-9]+}", getPersonEndpoint).Methods("GET") router.HandleFunc("/person/{id:[0-9]+}", createPersonEndpoint).Methods("POST").Headers("Content-Type", "application/json") router.HandleFunc("/person/{id:[0-9]+}", updatePersonEndpoint).Methods("PUT").Headers("Content-Type", "application/json") router.HandleFunc("/person/{id:[0-9]+}", deletePersonEndpoint).Methods("DELETE") log.Println("Starting HTTP server at address", ADDRESS) err := http.ListenAndServe(ADDRESS, router) if err != nil { log.Fatal("Unable to initialize HTTP server", err) os.Exit(2) } }
15. Otestování nové varianty služby
Opět si můžeme službu otestovat, tentokrát ovšem použijeme „ukecaný“ režim nástroje curl zapnutý přepínačem -v. Nejprve se pokusíme vytvořit novou osobu, ovšem neuvedeme hlavičku Content-Type. V tomto případě curl použije hodnotu „application/x-www-form-urlencoded“, která není akceptována:
$ curl -v -X POST localhost:8080/person/5 -d @ken.json * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > POST /person/5 HTTP/1.1 > User-Agent: curl/7.35.0 > Host: localhost:8080 > Accept: */* > Content-Length: 51 > Content-Type: application/x-www-form-urlencoded > * upload completely sent off: 51 out of 51 bytes < HTTP/1.1 405 Method Not Allowed < Date: Mon, 14 Oct 2019 16:57:05 GMT < Content-Length: 0 < * Connection #0 to host localhost left intact
Ve druhém příkladu je již vše v pořádku, protože hlavičku explicitně nastavíme při volání nástroje curl:
$ curl -v -X POST -H "Content-Type: application/json" localhost:8080/person/4 -d @ken.json * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > POST /person/4 HTTP/1.1 > User-Agent: curl/7.35.0 > Host: localhost:8080 > Accept: */* > Content-Type: application/json > Content-Length: 51 > * upload completely sent off: 51 out of 51 bytes < HTTP/1.1 200 OK < Date: Mon, 14 Oct 2019 16:56:58 GMT < Content-Length: 138 < Content-Type: text/plain; charset=utf-8 < {"0":{"firstname":"Linus","lastname":"Torvalds"},"1":{"firstname":"Rob","lastname":"Pike"},"4":{"firstname":"Ken","lastname":"Thompson"}} * Connection #0 to host localhost left intact
16. Služby s více přístupovými body a použití podsměrovačů (subrouter)
Ve chvíli, kdy nějaká služba poskytuje klientům velké množství prostředků (a tím pádem i koncových bodů), může být jejich správa na jednom místě zbytečně komplikovaná. Knihovna gorilla/mux nám ovšem i v tomto případě nabízí řešení ve formě takzvaných podsměrovačů (subrouter). Práce s podsměrovači je ve skutečnosti poměrně jednoduchá, protože každému podsměrovači přísluší nějaký prefix z URI, například /person, /book atd. Tyto prefixy URI jsou společné pro všechny handlery spravované jedním podsměrovačem. Naši službu lze upravit takto – hlavní vstupní bod bude spravován hlavním směrovačem a zdroje (resource) typu person jsou spravovány nově vytvořeným podsměrovačem:
router.HandleFunc("/", mainEndpoint).Methods("GET") s := router.PathPrefix("/person").Subrouter() s.HandleFunc("", listAllPersonsEndpoint).Methods("GET") s.HandleFunc("/{id:[0-9]+}", getPersonEndpoint).Methods("GET") s.HandleFunc("/{id:[0-9]+}", createPersonEndpoint).Methods("POST").Headers("Content-Type", "application/json")
Zbytek aplikace může zůstat naprosto shodný s verzí předchozí:
package main import ( "encoding/json" "github.com/gorilla/mux" "io" "log" "net/http" "os" "strconv" ) const ADDRESS = ":8080" type Person struct { Firstname string `json:"firstname"` Surname string `json:"lastname"` } var persons map[int]Person func init() { persons = make(map[int]Person) persons[0] = Person{"Linus", "Torvalds"} persons[1] = Person{"Rob", "Pike"} } func mainEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "Hello world!\n") } func listAllPersonsEndpoint(writer http.ResponseWriter, request *http.Request) { json.NewEncoder(writer).Encode(persons) } func retrieveIdRequestParameter(request *http.Request) int { id_var := mux.Vars(request)["id"] id, _ := strconv.ParseInt(id_var, 10, 0) return int(id) } func getPersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := retrieveIdRequestParameter(request) person, found := persons[id] if found { json.NewEncoder(writer).Encode(person) } else { json.NewEncoder(writer).Encode(nil) } } func processPersonFromPayload(id int, request *http.Request) { var person Person err := json.NewDecoder(request.Body).Decode(&person) if err == nil { log.Println("JSON decoded") persons[id] = person } else { log.Println(err) } } func createPersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := retrieveIdRequestParameter(request) _, found := persons[id] if !found { processPersonFromPayload(id, request) } json.NewEncoder(writer).Encode(persons) } func updatePersonEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "UPDATE PERSON\n") id := retrieveIdRequestParameter(request) _, found := persons[id] if found { processPersonFromPayload(id, request) } json.NewEncoder(writer).Encode(persons) } func deletePersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := retrieveIdRequestParameter(request) _, found := persons[id] if found { delete(persons, id) } json.NewEncoder(writer).Encode(persons) } func main() { router := mux.NewRouter().StrictSlash(true) router.HandleFunc("/", mainEndpoint).Methods("GET") s := router.PathPrefix("/person").Subrouter() s.HandleFunc("", listAllPersonsEndpoint).Methods("GET") s.HandleFunc("/{id:[0-9]+}", getPersonEndpoint).Methods("GET") s.HandleFunc("/{id:[0-9]+}", createPersonEndpoint).Methods("POST").Headers("Content-Type", "application/json") s.HandleFunc("/{id:[0-9]+}", updatePersonEndpoint).Methods("PUT").Headers("Content-Type", "application/json") s.HandleFunc("/{id:[0-9]+}", deletePersonEndpoint).Methods("DELETE").Headers("Content-Type", "application/json") log.Println("Starting HTTP server at address", ADDRESS) err := http.ListenAndServe(ADDRESS, router) if err != nil { log.Fatal("Unable to initialize HTTP server", err) os.Exit(2) } }
17. Přidání mezivrstev do řetězce zpracování požadavku – middleware
Mnohdy nastane při realizaci služeb a mikroslužeb situace, kdy je nutné nějakou operaci provádět se všemi požadavky. Může se jednat o kontrolu určitých hlaviček (tokeny atd.), logování apod. V takových případech je možné využít takzvané middleware, které si můžeme představit jako mezivrstvy vložené mezi směrovač a jednotlivé handlery. Příkladem mezivrstvy může být logování požadavků. Povšimněte si, že funkce realizující mezivrstvu musí explicitně zavolat další článek v celém řetězci zpracování (protože se počet mezivrstev může zvyšovat):
func logRequestHandler(writer http.ResponseWriter, request *http.Request, nextHandler http.Handler) { log.Println("Request URI: " + request.RequestURI) log.Println("Request method: " + request.Method) nextHandler.ServeHTTP(writer, request) }
Tuto funkci ovšem nevoláme přímo, ale musíme ji zaregistrovat:
func logRequest(nextHandler http.Handler) http.Handler { return http.HandlerFunc( func(writer http.ResponseWriter, request *http.Request) { logRequestHandler(writer, request, nextHandler) }) } router.Use(logRequest)
Pomocná funkce logRequestHandler ve skutečnosti není zapotřebí. Druhá mezivrstva vypisuje časy příchodu jednotlivých požadavků a je realizována jedinou funkcí (vracející jinou funkci, tj. jedná se o funkci vyššího řádu):
func logTimestamp(nextHandler http.Handler) http.Handler { return http.HandlerFunc( func(writer http.ResponseWriter, request *http.Request) { t := time.Now() log.Println("Timestamp: " + t.Format(time.UnixDate)) }) } router.Use(logTimestamp)
V praxi budou obě mezivrstvy reagovat na požadavky takto:
2019/10/14 19:36:34 Starting HTTP server at address :8080 2019/10/14 19:36:39 Request URI: /person/ 2019/10/14 19:36:39 Request method: GET 2019/10/14 19:36:39 Timestamp: Mon Oct 14 19:36:39 CEST 2019 2019/10/14 19:37:30 Request URI: /person/4 2019/10/14 19:37:30 Request method: POST 2019/10/14 19:37:30 Timestamp: Mon Oct 14 19:37:30 CEST 2019
18. Poslední varianta jednoduché REST API služby
Poslední varianta naší jednoduché REST API služby může vypadat následovně:
package main import ( "encoding/json" "github.com/gorilla/mux" "io" "log" "net/http" "os" "strconv" "time" ) const ADDRESS = ":8080" type Person struct { Firstname string `json:"firstname"` Surname string `json:"lastname"` } var persons map[int]Person func init() { persons = make(map[int]Person) persons[0] = Person{"Linus", "Torvalds"} persons[1] = Person{"Rob", "Pike"} } func mainEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "Hello world!\n") } func listAllPersonsEndpoint(writer http.ResponseWriter, request *http.Request) { json.NewEncoder(writer).Encode(persons) } func retrieveIdRequestParameter(request *http.Request) int { id_var := mux.Vars(request)["id"] id, _ := strconv.ParseInt(id_var, 10, 0) return int(id) } func getPersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := retrieveIdRequestParameter(request) person, found := persons[id] if found { json.NewEncoder(writer).Encode(person) } else { json.NewEncoder(writer).Encode(nil) } } func processPersonFromPayload(id int, request *http.Request) { var person Person err := json.NewDecoder(request.Body).Decode(&person) if err == nil { log.Println("JSON decoded") persons[id] = person } else { log.Println(err) } } func createPersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := retrieveIdRequestParameter(request) _, found := persons[id] if !found { processPersonFromPayload(id, request) } json.NewEncoder(writer).Encode(persons) } func updatePersonEndpoint(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "UPDATE PERSON\n") id := retrieveIdRequestParameter(request) _, found := persons[id] if found { processPersonFromPayload(id, request) } json.NewEncoder(writer).Encode(persons) } func deletePersonEndpoint(writer http.ResponseWriter, request *http.Request) { id := retrieveIdRequestParameter(request) _, found := persons[id] if found { delete(persons, id) } json.NewEncoder(writer).Encode(persons) } func logRequestHandler(writer http.ResponseWriter, request *http.Request, nextHandler http.Handler) { log.Println("Request URI: " + request.RequestURI) log.Println("Request method: " + request.Method) nextHandler.ServeHTTP(writer, request) } func logRequest(nextHandler http.Handler) http.Handler { return http.HandlerFunc( func(writer http.ResponseWriter, request *http.Request) { logRequestHandler(writer, request, nextHandler) }) } func logTimestamp(nextHandler http.Handler) http.Handler { return http.HandlerFunc( func(writer http.ResponseWriter, request *http.Request) { t := time.Now() log.Println("Timestamp: " + t.Format(time.UnixDate)) }) } func main() { router := mux.NewRouter().StrictSlash(true) router.HandleFunc("/", mainEndpoint).Methods("GET") s := router.PathPrefix("/person").Subrouter() s.HandleFunc("", listAllPersonsEndpoint).Methods("GET") s.HandleFunc("/{id:[0-9]+}", getPersonEndpoint).Methods("GET") s.HandleFunc("/{id:[0-9]+}", createPersonEndpoint).Methods("POST").Headers("Content-Type", "application/json") s.HandleFunc("/{id:[0-9]+}", updatePersonEndpoint).Methods("PUT").Headers("Content-Type", "application/json") s.HandleFunc("/{id:[0-9]+}", deletePersonEndpoint).Methods("DELETE").Headers("Content-Type", "application/json") router.Use(logRequest) router.Use(logTimestamp) log.Println("Starting HTTP server at address", ADDRESS) err := http.ListenAndServe(ADDRESS, router) if err != nil { log.Fatal("Unable to initialize HTTP server", err) os.Exit(2) } }
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně čtyři megabajty), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- Dokumentace k balíčku gorilla/mux
https://godoc.org/github.com/gorilla/mux - Gorilla web toolkitk
http://www.gorillatoolkit.org/ - Metric types
https://prometheus.io/docs/concepts/metric_types/ - Histograms with Prometheus: A Tale of Woe
http://linuxczar.net/blog/2017/06/15/prometheus-histogram-2/ - Why are Prometheus histograms cumulative?
https://www.robustperception.io/why-are-prometheus-histograms-cumulative - Histograms and summaries
https://prometheus.io/docs/practices/histograms/ - Instrumenting Golang server in 5 min
https://medium.com/@gsisimogang/instrumenting-golang-server-in-5-min-c1c32489add3 - Semantic Import Versioning in Go
https://www.aaronzhuo.com/semantic-import-versioning-in-go/ - Sémantické verzování
https://semver.org/ - Getting started with Go modules
https://medium.com/@fonseka.live/getting-started-with-go-modules-b3dac652066d - Create projects independent of $GOPATH using Go Modules
https://medium.com/mindorks/create-projects-independent-of-gopath-using-go-modules-802260cdfb51o - Anatomy of Modules in Go
https://medium.com/rungo/anatomy-of-modules-in-go-c8274d215c16 - Modules
https://github.com/golang/go/wiki/Modules - Go Modules Tutorial
https://tutorialedge.net/golang/go-modules-tutorial/ - Module support
https://golang.org/cmd/go/#hdr-Module_support - Go Lang: Memory Management and Garbage Collection
https://vikash1976.wordpress.com/2017/03/26/go-lang-memory-management-and-garbage-collection/ - Golang Internals, Part 4: Object Files and Function Metadata
https://blog.altoros.com/golang-part-4-object-files-and-function-metadata.html - What is REPL?
https://pythonprogramminglanguage.com/repl/ - What is a REPL?
https://codewith.mu/en/tutorials/1.0/repl - Programming at the REPL: Introduction
https://clojure.org/guides/repl/introduction - What is REPL? (Quora)
https://www.quora.com/What-is-REPL - Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/ - Read-eval-print loop (Wikipedia)
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - Vim as a Go (Golang) IDE using LSP and vim-go
https://octetz.com/posts/vim-as-go-ide - gopls
https://github.com/golang/go/wiki/gopls - IDE Integration Guide
https://github.com/stamblerre/gocode/blob/master/docs/IDE_integration.md - How to instrument Go code with custom expvar metrics
https://sysdig.com/blog/golang-expvar-custom-metrics/ - Golang expvar metricset (Metricbeat Reference)
https://www.elastic.co/guide/en/beats/metricbeat/7.x/metricbeat-metricset-golang-expvar.html - Package expvar
https://golang.org/pkg/expvar/#NewInt - Java Platform Debugger Architecture: Overview
https://docs.oracle.com/en/java/javase/11/docs/specs/jpda/jpda.html - The JVM Tool Interface (JVM TI): How VM Agents Work
https://www.oracle.com/technetwork/articles/javase/index-140680.html - JVM Tool Interface Version 11.0
https://docs.oracle.com/en/java/javase/11/docs/specs/jvmti.html - Creating a Debugging and Profiling Agent with JVMTI
http://www.oracle.com/technetwork/articles/javase/jvmti-136367.html - JVM TI (Wikipedia)
http://en.wikipedia.org/wiki/JVM_TI - IBM JVMTI extensions
http://publib.boulder.ibm.com/infocenter/realtime/v2r0/index.jsp?topic=%2Fcom.ibm.softrt.doc%2Fdiag%2Ftools%2Fjvmti_extensions.html - Go & cgo: integrating existing C code with Go
http://akrennmair.github.io/golang-cgo-slides/#1 - Using cgo to call C code from within Go code
https://wenzr.wordpress.com/2018/06/07/using-cgo-to-call-c-code-from-within-go-code/ - Package trace
https://golang.org/pkg/runtime/trace/ - Introducing HTTP Tracing
https://blog.golang.org/http-tracing - Command trace
https://golang.org/cmd/trace/ - A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
https://github.com/wesovilabs/koazee - Funkce vyššího řádu v knihovně Underscore
https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/ - Delve: a debugger for the Go programming language.
https://github.com/go-delve/delve - Příkazy debuggeru Delve
https://github.com/go-delve/delve/tree/master/Documentation/cli - Debuggery a jejich nadstavby v Linuxu
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/ - Debuggery a jejich nadstavby v Linuxu (2. část)
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/ - Debuggery a jejich nadstavby v Linuxu (3): Nemiver
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/ - Debuggery a jejich nadstavby v Linuxu (4): KDbg
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/ - Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/ - Debugging Go Code with GDB
https://golang.org/doc/gdb - Debugging Go (golang) programs with gdb
https://thornydev.blogspot.com/2014/01/debugging-go-golang-programs-with-gdb.html - GDB – Dokumentace
http://sourceware.org/gdb/current/onlinedocs/gdb/ - GDB – Supported Languages
http://sourceware.org/gdb/current/onlinedocs/gdb/Supported-Languages.html#Supported-Languages - GNU Debugger (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Debugger - The LLDB Debugger
http://lldb.llvm.org/ - Debugger (Wikipedia)
https://en.wikipedia.org/wiki/Debugger - 13 Linux Debuggers for C++ Reviewed
http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817 - Go is on a Trajectory to Become the Next Enterprise Programming Language
https://hackernoon.com/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language-3b75d70544e - Go Proverbs: Simple, Poetic, Pithy
https://go-proverbs.github.io/ - Handling Sparse Files on Linux
https://www.systutorials.com/136652/handling-sparse-files-on-linux/ - Gzip (Wikipedia)
https://en.wikipedia.org/wiki/Gzip - Deflate
https://en.wikipedia.org/wiki/DEFLATE - 10 tools written in Go that every developer needs to know
https://gustavohenrique.net/en/2019/01/10-tools-written-in-go-that-every-dev-needs-to-know/ - Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
https://www.root.cz/clanky/hexadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/ - Hex dump
https://en.wikipedia.org/wiki/Hex_dump - Rozhraní io.ByteReader
https://golang.org/pkg/io/#ByteReader - Rozhraní io.RuneReader
https://golang.org/pkg/io/#RuneReader - Rozhraní io.ByteScanner
https://golang.org/pkg/io/#ByteScanner - Rozhraní io.RuneScanner
https://golang.org/pkg/io/#RuneScanner - Rozhraní io.Closer
https://golang.org/pkg/io/#Closer - Rozhraní io.Reader
https://golang.org/pkg/io/#Reader - Rozhraní io.Writer
https://golang.org/pkg/io/#Writer - Typ Strings.Reader
https://golang.org/pkg/strings/#Reader - VACUUM (SQL)
https://www.sqlite.org/lang_vacuum.html - VACUUM (Postgres)
https://www.postgresql.org/docs/8.4/sql-vacuum.html - go-cron
https://github.com/rk/go-cron - gocron
https://github.com/jasonlvhit/gocron - clockwork
https://github.com/whiteShtef/clockwork - clockwerk
https://github.com/onatm/clockwerk - JobRunner
https://github.com/bamzi/jobrunner - Rethinking Cron
https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ - In the Beginning was the Command Line
https://web.archive.org/web/20180218045352/http://www.cryptonomicon.com/beginning.html - repl.it (REPL pro různé jazyky)
https://repl.it/languages - GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
https://github.com/jroimartin/gocui - Read–eval–print loop
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - go-prompt
https://github.com/c-bata/go-prompt - readline
https://github.com/chzyer/readline - A pure golang implementation for GNU-Readline kind library
https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/ - go-readline
https://github.com/fiorix/go-readline - 4 Python libraries for building great command-line user interfaces
https://opensource.com/article/17/5/4-practical-python-libraries - prompt_toolkit 2.0.3 na PyPi
https://pypi.org/project/prompt_toolkit/ - python-prompt-toolkit na GitHubu
https://github.com/jonathanslenders/python-prompt-toolkit - The GNU Readline Library
https://tiswww.case.edu/php/chet/readline/rltop.html - GNU Readline (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Readline - readline — GNU readline interface (Python 3.x)
https://docs.python.org/3/library/readline.html - readline — GNU readline interface (Python 2.x)
https://docs.python.org/2/library/readline.html - GNU Readline Library – command line editing
https://tiswww.cwru.edu/php/chet/readline/readline.html - gnureadline 6.3.8 na PyPi
https://pypi.org/project/gnureadline/ - Editline Library (libedit)
http://thrysoee.dk/editline/ - Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/ - libedit or editline
http://www.cs.utah.edu/~bigler/code/libedit.html - WinEditLine
http://mingweditline.sourceforge.net/ - rlcompleter — Completion function for GNU readline
https://docs.python.org/3/library/rlcompleter.html - rlwrap na GitHubu
https://github.com/hanslub42/rlwrap - rlwrap(1) – Linux man page
https://linux.die.net/man/1/rlwrap - readline(3) – Linux man page
https://linux.die.net/man/3/readline - history(3) – Linux man page
https://linux.die.net/man/3/history - Dokumentace k balíčku oglematchers
https://godoc.org/github.com/jacobsa/oglematchers - Balíček oglematchers
https://github.com/jacobsa/oglematchers - Dokumentace k balíčku ogletest
https://godoc.org/github.com/jacobsa/ogletest - Balíček ogletest
https://github.com/jacobsa/ogletest - Dokumentace k balíčku assert
https://godoc.org/github.com/stretchr/testify/assert - Testify – Thou Shalt Write Tests
https://github.com/stretchr/testify/ - package testing
https://golang.org/pkg/testing/ - Golang basics – writing unit tests
https://blog.alexellis.io/golang-writing-unit-tests/ - An Introduction to Programming in Go / Testing
https://www.golang-book.com/books/intro/12 - An Introduction to Testing in Go
https://tutorialedge.net/golang/intro-testing-in-go/ - Advanced Go Testing Tutorial
https://tutorialedge.net/golang/advanced-go-testing-tutorial/ - GoConvey
http://goconvey.co/ - Testing Techniques
https://talks.golang.org/2014/testing.slide - 5 simple tips and tricks for writing unit tests in #golang
https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742 - Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - How the Go runtime implements maps efficiently (without generics)
https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Goroutine IDs
https://blog.sgmansfield.com/2015/12/goroutine-ids/ - Different ways to pass channels as arguments in function in go (golang)
https://stackoverflow.com/questions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang - justforfunc #22: using the Go execution tracer
https://www.youtube.com/watch?v=ySy3sR1LFCQ - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation - The zero value of a slice is not nil
https://stackoverflow.com/questions/30806931/the-zero-value-of-a-slice-is-not-nil - Go-tcha: When nil != nil
https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic - Nils in Go
https://www.doxsey.net/blog/nils-in-go