Tvorba webových aplikací v Go s využitím projektu Gorilla web toolkit

15. 10. 2019
Doba čtení: 48 minut

Sdílet

 Autor: Go Lang
Jazyk Go se často používá pro tvorbu webových aplikací popř. služeb a mikroslužeb s REST API. Pro tvorbu těchto typů aplikací je určen především balíček net/http, který je ovšem možné rozšířit dalšími balíčky.

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

20. Odkazy na Internetu

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.

Poznámka: problematiku mikroslužeb jsme si ve stručnosti představili v samostatně vycházejícím miniseriálu:
  1. Mikroslužby: moderní aplikace využívající známých konceptů
    https://www.root.cz/clanky/mikrosluzby-moderni-aplikace-vyuzivajici-znamych-konceptu/
  2. 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/
  3. 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/
  4. 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/
  5. 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/
  6. Prechod hostingu na mikroslužby: cesta zlyhaní a úspechov
    https://www.root.cz/clanky/prechod-hostingu-na-mikrosluzby-cesta-zlyhani-a-uspechov/
  7. 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_sim­ple_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)
        }
}
Poznámka: mutex můžete otevřít i v bloku defer, což je pro programy psané v Go idiomatičtější řešení:
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_ser­ver_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
Poznámka: povšimněte si, že se hodnota vrátila pouze při použití metody GET. Snaha o použití jiných metod vede k chybovému stavu HTTP/1.1 405 Method Not Allowed, viz též 4×x Client errors (chyba na straně klienta – poslal špatný požadavek).

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)
}
Poznámka: poslední řádky obou handlerů zajistí, že se klientovi pošle nový obsah databáze.

Ú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"}}
Poznámka: alternativně si můžeme informace o osobách uložit do formátu JSON a použít jinou formu přepínače -d:
$ 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")
Poznámka: při testování těchto metod si musíme dát pozor na to, že nástroj curl v tomto případě použije odlišnou hodnotu v hlavičce, a to konkrétně hodnotu „application/x-www-form-urlencoded“. Hlavičku tedy budeme muset specifikovat explicitně.

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")
Poznámka: povšimněte si, že v podsměrovači při registraci handlerů již nepoužíváme prefix „/person“.

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)
Poznámka: pokud budete potřebovat například zjistit čas zpracování požadavku, je nutné problém řešit komplikovaněji – s využitím kontextů (context), což je téma na samostatný článek.

V praxi budou obě mezivrstvy reagovat na požadavky takto:

bitcoin_skoleni

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:

# Příklad Stručný popis Cesta
1 01_simple_http_server.go jednoduchý HTTP server založený na standardním balíčku net/http https://github.com/tisnik/go-root/blob/master/article38/01_sim­ple_http_server.go
2 02_http_server_with_mux.go HTTP server používající balíček gorilla/mux https://github.com/tisnik/go-root/blob/master/article38/02_http_ser­ver_with_mux.go
3 03_method_specification.go specifikace HTTP metod použitých při volání REST API https://github.com/tisnik/go-root/blob/master/article38/03_met­hod_specification.go
4 04_method_specification.go rozšíření a vylepšení předchozího příkladu https://github.com/tisnik/go-root/blob/master/article38/04_met­hod_specification.go
5 05_resource_handling.go vylepšená správa zdrojů (resources) https://github.com/tisnik/go-root/blob/master/article38/05_re­source_handling.go
6 06_resource_handling.go práce se jménem zdroje https://github.com/tisnik/go-root/blob/master/article38/06_re­source_handling.go
7 07_resource_handling_id.go omezení znaků, které může být použito ve jménu zdroje https://github.com/tisnik/go-root/blob/master/article38/07_re­source_handling_id.go
8 08_headers.go specifikace hlaviček, které musí být poslány společně s požadavkem https://github.com/tisnik/go-root/blob/master/article38/08_he­aders.go
9 09_subroutes.go vytvoření a konfigurace podsměrovačů (subroutes) https://github.com/tisnik/go-root/blob/master/article38/09_su­broutes.go
10 10_simple_middleware.go dvě middleware funkce použité při zpracování dotazů https://github.com/tisnik/go-root/blob/master/article38/10_sim­ple_middleware.go

20. Odkazy na Internetu

  1. Dokumentace k balíčku gorilla/mux
    https://godoc.org/github.com/go­rilla/mux
  2. Gorilla web toolkitk
    http://www.gorillatoolkit.org/
  3. Metric types
    https://prometheus.io/doc­s/concepts/metric_types/
  4. Histograms with Prometheus: A Tale of Woe
    http://linuxczar.net/blog/2017/06/15/pro­metheus-histogram-2/
  5. Why are Prometheus histograms cumulative?
    https://www.robustperception.io/why-are-prometheus-histograms-cumulative
  6. Histograms and summaries
    https://prometheus.io/doc­s/practices/histograms/
  7. Instrumenting Golang server in 5 min
    https://medium.com/@gsisi­mogang/instrumenting-golang-server-in-5-min-c1c32489add3
  8. Semantic Import Versioning in Go
    https://www.aaronzhuo.com/semantic-import-versioning-in-go/
  9. Sémantické verzování
    https://semver.org/
  10. Getting started with Go modules
    https://medium.com/@fonse­ka.live/getting-started-with-go-modules-b3dac652066d
  11. Create projects independent of $GOPATH using Go Modules
    https://medium.com/mindorks/create-projects-independent-of-gopath-using-go-modules-802260cdfb51o
  12. Anatomy of Modules in Go
    https://medium.com/rungo/anatomy-of-modules-in-go-c8274d215c16
  13. Modules
    https://github.com/golang/go/wi­ki/Modules
  14. Go Modules Tutorial
    https://tutorialedge.net/golang/go-modules-tutorial/
  15. Module support
    https://golang.org/cmd/go/#hdr-Module_support
  16. Go Lang: Memory Management and Garbage Collection
    https://vikash1976.wordpres­s.com/2017/03/26/go-lang-memory-management-and-garbage-collection/
  17. Golang Internals, Part 4: Object Files and Function Metadata
    https://blog.altoros.com/golang-part-4-object-files-and-function-metadata.html
  18. What is REPL?
    https://pythonprogramminglan­guage.com/repl/
  19. What is a REPL?
    https://codewith.mu/en/tu­torials/1.0/repl
  20. Programming at the REPL: Introduction
    https://clojure.org/guides/re­pl/introduction
  21. What is REPL? (Quora)
    https://www.quora.com/What-is-REPL
  22. Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
    https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/
  23. Read-eval-print loop (Wikipedia)
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  24. Vim as a Go (Golang) IDE using LSP and vim-go
    https://octetz.com/posts/vim-as-go-ide
  25. gopls
    https://github.com/golang/go/wi­ki/gopls
  26. IDE Integration Guide
    https://github.com/stamble­rre/gocode/blob/master/doc­s/IDE_integration.md
  27. How to instrument Go code with custom expvar metrics
    https://sysdig.com/blog/golang-expvar-custom-metrics/
  28. Golang expvar metricset (Metricbeat Reference)
    https://www.elastic.co/gu­ide/en/beats/metricbeat/7­.x/metricbeat-metricset-golang-expvar.html
  29. Package expvar
    https://golang.org/pkg/expvar/#NewInt
  30. Java Platform Debugger Architecture: Overview
    https://docs.oracle.com/en/ja­va/javase/11/docs/specs/jpda/jpda­.html
  31. The JVM Tool Interface (JVM TI): How VM Agents Work
    https://www.oracle.com/technet­work/articles/javase/index-140680.html
  32. JVM Tool Interface Version 11.0
    https://docs.oracle.com/en/ja­va/javase/11/docs/specs/jvmti­.html
  33. Creating a Debugging and Profiling Agent with JVMTI
    http://www.oracle.com/technet­work/articles/javase/jvmti-136367.html
  34. JVM TI (Wikipedia)
    http://en.wikipedia.org/wiki/JVM_TI
  35. IBM JVMTI extensions
    http://publib.boulder.ibm­.com/infocenter/realtime/v2r0/in­dex.jsp?topic=%2Fcom.ibm.sof­trt.doc%2Fdiag%2Ftools%2Fjvmti_ex­tensions.html
  36. Go & cgo: integrating existing C code with Go
    http://akrennmair.github.io/golang-cgo-slides/#1
  37. Using cgo to call C code from within Go code
    https://wenzr.wordpress.com/2018/06/07/u­sing-cgo-to-call-c-code-from-within-go-code/
  38. Package trace
    https://golang.org/pkg/runtime/trace/
  39. Introducing HTTP Tracing
    https://blog.golang.org/http-tracing
  40. Command trace
    https://golang.org/cmd/trace/
  41. A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
    https://github.com/wesovilabs/koazee
  42. Funkce vyššího řádu v knihovně Underscore
    https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/
  43. Delve: a debugger for the Go programming language.
    https://github.com/go-delve/delve
  44. Příkazy debuggeru Delve
    https://github.com/go-delve/delve/tree/master/Do­cumentation/cli
  45. Debuggery a jejich nadstavby v Linuxu
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/
  46. Debuggery a jejich nadstavby v Linuxu (2. část)
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/
  47. Debuggery a jejich nadstavby v Linuxu (3): Nemiver
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/
  48. Debuggery a jejich nadstavby v Linuxu (4): KDbg
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/
  49. 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/
  50. Debugging Go Code with GDB
    https://golang.org/doc/gdb
  51. Debugging Go (golang) programs with gdb
    https://thornydev.blogspot­.com/2014/01/debugging-go-golang-programs-with-gdb.html
  52. GDB – Dokumentace
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/
  53. GDB – Supported Languages
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/Suppor­ted-Languages.html#Supported-Languages
  54. GNU Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Debugger
  55. The LLDB Debugger
    http://lldb.llvm.org/
  56. Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/Debugger
  57. 13 Linux Debuggers for C++ Reviewed
    http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817
  58. 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
  59. Go Proverbs: Simple, Poetic, Pithy
    https://go-proverbs.github.io/
  60. Handling Sparse Files on Linux
    https://www.systutorials.com/136652/han­dling-sparse-files-on-linux/
  61. Gzip (Wikipedia)
    https://en.wikipedia.org/wiki/Gzip
  62. Deflate
    https://en.wikipedia.org/wiki/DEFLATE
  63. 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/
  64. Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
    https://www.root.cz/clanky/he­xadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/
  65. Hex dump
    https://en.wikipedia.org/wi­ki/Hex_dump
  66. Rozhraní io.ByteReader
    https://golang.org/pkg/io/#ByteReader
  67. Rozhraní io.RuneReader
    https://golang.org/pkg/io/#RuneReader
  68. Rozhraní io.ByteScanner
    https://golang.org/pkg/io/#By­teScanner
  69. Rozhraní io.RuneScanner
    https://golang.org/pkg/io/#Ru­neScanner
  70. Rozhraní io.Closer
    https://golang.org/pkg/io/#Closer
  71. Rozhraní io.Reader
    https://golang.org/pkg/io/#Reader
  72. Rozhraní io.Writer
    https://golang.org/pkg/io/#Writer
  73. Typ Strings.Reader
    https://golang.org/pkg/strin­gs/#Reader
  74. VACUUM (SQL)
    https://www.sqlite.org/lan­g_vacuum.html
  75. VACUUM (Postgres)
    https://www.postgresql.or­g/docs/8.4/sql-vacuum.html
  76. go-cron
    https://github.com/rk/go-cron
  77. gocron
    https://github.com/jasonlvhit/gocron
  78. clockwork
    https://github.com/whiteShtef/cloc­kwork
  79. clockwerk
    https://github.com/onatm/clockwerk
  80. JobRunner
    https://github.com/bamzi/jobrunner
  81. Rethinking Cron
    https://adam.herokuapp.com/pas­t/2010/4/13/rethinking_cron/
  82. In the Beginning was the Command Line
    https://web.archive.org/web/20180218045352/htt­p://www.cryptonomicon.com/be­ginning.html
  83. repl.it (REPL pro různé jazyky)
    https://repl.it/languages
  84. GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
    https://github.com/jroimartin/gocui
  85. Read–eval–print loop
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  86. go-prompt
    https://github.com/c-bata/go-prompt
  87. readline
    https://github.com/chzyer/readline
  88. A pure golang implementation for GNU-Readline kind library
    https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/
  89. go-readline
    https://github.com/fiorix/go-readline
  90. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  91. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  92. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  93. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  94. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  95. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  96. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  97. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  98. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  99. Editline Library (libedit)
    http://thrysoee.dk/editline/
  100. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  101. libedit or editline
    http://www.cs.utah.edu/~bi­gler/code/libedit.html
  102. WinEditLine
    http://mingweditline.sourceforge.net/
  103. rlcompleter — Completion function for GNU readline
    https://docs.python.org/3/li­brary/rlcompleter.html
  104. rlwrap na GitHubu
    https://github.com/hanslub42/rlwrap
  105. rlwrap(1) – Linux man page
    https://linux.die.net/man/1/rlwrap
  106. readline(3) – Linux man page
    https://linux.die.net/man/3/readline
  107. history(3) – Linux man page
    https://linux.die.net/man/3/history
  108. Dokumentace k balíčku oglematchers
    https://godoc.org/github.com/ja­cobsa/oglematchers
  109. Balíček oglematchers
    https://github.com/jacobsa/o­glematchers
  110. Dokumentace k balíčku ogletest
    https://godoc.org/github.com/ja­cobsa/ogletest
  111. Balíček ogletest
    https://github.com/jacobsa/ogletest
  112. Dokumentace k balíčku assert
    https://godoc.org/github.com/stret­chr/testify/assert
  113. Testify – Thou Shalt Write Tests
    https://github.com/stretchr/testify/
  114. package testing
    https://golang.org/pkg/testing/
  115. Golang basics – writing unit tests
    https://blog.alexellis.io/golang-writing-unit-tests/
  116. An Introduction to Programming in Go / Testing
    https://www.golang-book.com/books/intro/12
  117. An Introduction to Testing in Go
    https://tutorialedge.net/golang/intro-testing-in-go/
  118. Advanced Go Testing Tutorial
    https://tutorialedge.net/go­lang/advanced-go-testing-tutorial/
  119. GoConvey
    http://goconvey.co/
  120. Testing Techniques
    https://talks.golang.org/2014/tes­ting.slide
  121. 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
  122. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  123. package gg
    https://godoc.org/github.com/fo­gleman/gg
  124. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  125. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  126. The Go image package
    https://blog.golang.org/go-image-package
  127. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  128. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  129. YAML
    https://yaml.org/
  130. edn
    https://github.com/edn-format/edn
  131. Smile
    https://github.com/FasterXML/smile-format-specification
  132. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  133. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  134. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  135. Introducing JSON
    http://json.org/
  136. Package json
    https://golang.org/pkg/encoding/json/
  137. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  138. Go by Example: JSON
    https://gobyexample.com/json
  139. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  140. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  141. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  142. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  143. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  144. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  145. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  146. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  147. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  148. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  149. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  150. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  151. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  152. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  153. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  154. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  155. Algorithms to Go
    https://yourbasic.org/
  156. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  157. 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/
  158. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  159. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  160. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  161. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  162. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  163. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  164. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  165. The Go Programming Language (home page)
    https://golang.org/
  166. GoDoc
    https://godoc.org/
  167. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  168. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  169. The Go Programming Language Specification
    https://golang.org/ref/spec
  170. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  171. Package builtin
    https://golang.org/pkg/builtin/
  172. Package fmt
    https://golang.org/pkg/fmt/
  173. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  174. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  175. Learning Go
    https://www.miek.nl/go/
  176. Go Bootcamp
    http://www.golangbootcamp.com/
  177. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  178. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  179. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  180. The Go Blog
    https://blog.golang.org/
  181. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  182. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  183. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  184. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  185. 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
  186. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  187. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  188. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  189. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  190. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  191. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  192. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  193. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  194. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  195. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  196. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  197. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  198. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  199. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  200. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  201. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  202. 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/
  203. 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
  204. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  205. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  206. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  207. Go vs. Python
    https://www.peterbe.com/plog/govspy
  208. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  209. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  210. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  211. Go by Example: Slices
    https://gobyexample.com/slices
  212. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  213. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  214. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  215. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  216. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  217. nils In Go
    https://go101.org/article/nil.html
  218. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  219. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  220. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  221. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  222. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  223. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  224. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  225. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  226. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  227. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  228. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  229. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  230. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  231. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  232. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  233. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  234. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  235. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  236. 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
  237. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  238. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  239. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  240. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  241. Selectors
    https://golang.org/ref/spec#Selectors
  242. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  243. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  244. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  245. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  246. Part 21: Goroutines
    https://golangbot.com/goroutines/
  247. Part 22: Channels
    https://golangbot.com/channels/
  248. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  249. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  250. 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/
  251. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  252. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  253. Control Structures
    https://www.golang-book.com/books/intro/5
  254. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  255. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  256. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  257. Goroutine IDs
    https://blog.sgmansfield.com/2015/12/go­routine-ids/
  258. Different ways to pass channels as arguments in function in go (golang)
    https://stackoverflow.com/qu­estions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang
  259. justforfunc #22: using the Go execution tracer
    https://www.youtube.com/wat­ch?v=ySy3sR1LFCQ
  260. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  261. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  262. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
  263. Effective Go
    https://golang.org/doc/ef­fective_go.html
  264. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  265. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation
  266. The zero value of a slice is not nil
    https://stackoverflow.com/qu­estions/30806931/the-zero-value-of-a-slice-is-not-nil
  267. Go-tcha: When nil != nil
    https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic
  268. Nils in Go
    https://www.doxsey.net/blog/nils-in-go

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.