├── src
├── chp14
│ ├── open-create
│ │ ├── test.txt
│ │ └── main.go
│ ├── tee
│ │ └── main.go
│ ├── copy
│ │ └── main.go
│ ├── pipe
│ │ └── main.go
│ ├── binary
│ │ └── main.go
│ └── seek
│ │ └── main.go
├── chp2
│ ├── encoding
│ │ ├── ascii.txt
│ │ └── main.go
│ ├── regex-match
│ │ └── main.go
│ ├── regex-validation
│ │ └── main.go
│ ├── template-empty-lines
│ │ └── main.go
│ ├── regex-replace
│ │ └── main.go
│ ├── trim
│ │ └── main.go
│ ├── regex-submatch
│ │ └── main.go
│ ├── diacritics
│ │ └── main.go
│ ├── split
│ │ └── main.go
│ ├── stringadd-benchmark
│ │ └── add_test.go
│ ├── join
│ │ └── main.go
│ ├── scanner
│ │ └── main.go
│ ├── template-composition
│ │ └── main.go
│ ├── template-basics
│ │ └── main.go
│ ├── template-composition2
│ │ └── main.go
│ ├── template-scoping
│ │ └── main.go
│ ├── formatted-text
│ │ └── main.go
│ └── template-layout
│ │ └── main.go
├── chp8
│ ├── structured-errors
│ │ ├── valid-config.json
│ │ ├── invalid-config.json
│ │ └── main.go
│ ├── wrapping-errors
│ │ ├── valid-config.json
│ │ ├── invalid-config.json
│ │ └── main.go
│ ├── recover
│ │ └── main.go
│ ├── stack
│ │ └── main.go
│ ├── change-return-value
│ │ └── main.go
│ └── chain
│ │ └── main.go
├── chp13
│ ├── http-rnd-service
│ │ ├── readme.md
│ │ └── main.go
│ ├── tls
│ │ ├── config.ext
│ │ ├── readme.md
│ │ ├── client
│ │ │ └── main.go
│ │ ├── server
│ │ │ └── main.go
│ │ └── proxy
│ │ │ └── main.go
│ ├── http-tls
│ │ ├── config.ext
│ │ ├── readme.md
│ │ ├── server
│ │ │ └── main.go
│ │ └── client
│ │ │ └── main.go
│ ├── echo-client-server
│ │ ├── client
│ │ │ └── main.go
│ │ └── server
│ │ │ └── main.go
│ ├── echo-client-server-lines
│ │ ├── client
│ │ │ └── main.go
│ │ └── server
│ │ │ └── main.go
│ ├── udp-client-server
│ │ ├── client
│ │ │ └── main.go
│ │ └── server
│ │ │ └── main.go
│ ├── http-static-files
│ │ └── main.go
│ ├── http-calls
│ │ └── main.go
│ ├── tcp-file-transfer
│ │ ├── sender
│ │ │ └── main.go
│ │ └── receiver
│ │ │ └── main.go
│ ├── timeout
│ │ └── main.go
│ ├── http-upload
│ │ └── main.go
│ └── html-forms
│ │ └── main.go
├── chp11
│ ├── custom-marshal-keys
│ │ └── main.go
│ ├── dynamic-keys
│ │ └── main.go
│ ├── custom-marshal-hexkey
│ │ └── main.go
│ ├── nonstring-keys
│ │ └── main.go
│ ├── custom-marshaling
│ │ └── main.go
│ ├── missing-fields
│ │ └── main.go
│ ├── polymorphic-unmarshal
│ │ └── main.go
│ ├── stream
│ │ └── main.go
│ └── encode-decode
│ │ └── main.go
├── chp17
│ └── sorting
│ │ ├── main.go
│ │ ├── sort
│ │ ├── sort.go
│ │ └── sort_test.go
│ │ └── service
│ │ ├── service.go
│ │ └── service_test.go
├── chp1
│ └── webform
│ │ ├── pkg
│ │ └── commentdb
│ │ │ └── initdb.go
│ │ ├── web
│ │ └── static
│ │ │ └── form.html
│ │ ├── cmd
│ │ └── webform
│ │ │ └── main.go
│ │ └── internal
│ │ └── routes
│ │ └── routes.go
├── chp12
│ ├── namedpipe
│ │ ├── reader
│ │ │ └── main.go
│ │ └── writer
│ │ │ └── main.go
│ ├── shell
│ │ └── main.go
│ ├── process
│ │ ├── sub
│ │ │ └── main.go
│ │ └── main.go
│ ├── stdin
│ │ └── main.go
│ ├── stdinpipe
│ │ └── main.go
│ └── signal
│ │ └── main.go
├── chp5
│ ├── authenticator
│ │ ├── factory.go
│ │ ├── main.go
│ │ └── basicauth.go
│ ├── enums
│ │ └── main.go
│ └── embedding
│ │ └── main.go
├── chp4
│ ├── stringset
│ │ └── main.go
│ ├── compositekeys
│ │ └── main.go
│ ├── cache
│ │ └── main.go
│ ├── slices
│ │ └── main.go
│ └── blockingcache
│ │ └── main.go
├── chp7
│ ├── channels
│ │ └── main.go
│ └── concurrent-download
│ │ └── main.go
├── chp9
│ ├── timeout
│ │ └── main.go
│ ├── server-timeout
│ │ └── main.go
│ ├── cancelation
│ │ └── main.go
│ └── request-scoped-data
│ │ └── main.go
├── chp3
│ ├── ticker
│ │ └── main.go
│ └── format
│ │ └── main.go
├── chp6
│ ├── set
│ │ └── main.go
│ ├── sum
│ │ └── main.go
│ ├── orderedmap
│ │ └── main.go
│ └── adapters
│ │ └── main.go
├── chp15
│ ├── mysql-connection
│ │ └── main.go
│ ├── postgresql-running-statements
│ │ └── main.go
│ ├── postgresql-transactions
│ │ └── main.go
│ └── postgresql-queryvalues
│ │ └── main.go
├── go.mod
├── chp16
│ ├── stdlogger
│ │ └── main.go
│ └── slog
│ │ └── main.go
├── chp10
│ ├── workerpool-dynamic
│ │ └── main.go
│ ├── streaming
│ │ └── main.go
│ ├── connection-pool
│ │ └── main.go
│ ├── simple-pipeline
│ │ └── main.go
│ ├── workerpool-fixed
│ │ └── main.go
│ ├── parallel-pipeline
│ │ └── main.go
│ ├── fanin
│ │ └── main.go
│ └── sorting-fanin
│ │ └── main.go
└── go.sum
├── ERRATA.md
├── LICENSE
└── README.md
/src/chp14/open-create/test.txt:
--------------------------------------------------------------------------------
1 | Hello world!
2 |
--------------------------------------------------------------------------------
/src/chp2/encoding/ascii.txt:
--------------------------------------------------------------------------------
1 | This is an ASCII text file.
2 |
--------------------------------------------------------------------------------
/src/chp8/structured-errors/valid-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "option": "value"
3 | }
4 |
--------------------------------------------------------------------------------
/src/chp8/wrapping-errors/valid-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "option": "value"
3 | }
4 |
--------------------------------------------------------------------------------
/src/chp8/structured-errors/invalid-config.json:
--------------------------------------------------------------------------------
1 | This is an invalid configuration file
2 |
--------------------------------------------------------------------------------
/src/chp8/wrapping-errors/invalid-config.json:
--------------------------------------------------------------------------------
1 | This is an invalid configuration file
2 |
--------------------------------------------------------------------------------
/src/chp13/http-rnd-service/readme.md:
--------------------------------------------------------------------------------
1 | Build the service:
2 |
3 | ```
4 | go build .
5 | ```
6 |
7 | Run the service:
8 |
9 | ```
10 | ./http-rnd-service
11 | ```
12 |
13 | Test it:
14 |
15 | ```
16 | curl http://localhost:8080/rnd
17 | ```
18 |
--------------------------------------------------------------------------------
/src/chp2/regex-match/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "regexp"
6 | )
7 |
8 | func main() {
9 | re := regexp.MustCompile(`[0-9]+`)
10 | fmt.Println(re.FindAllString("This regular expression find numbers, like 1, 100, 500, etc.", -1))
11 | }
12 |
--------------------------------------------------------------------------------
/src/chp8/recover/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | defer func() {
7 | if r := recover(); r != nil {
8 | fmt.Printf("recovering from panic. Type of r: %T value of r: %v\n", r, r)
9 | }
10 | }()
11 |
12 | panic("testing")
13 | }
14 |
--------------------------------------------------------------------------------
/src/chp2/regex-validation/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "regexp"
6 | )
7 |
8 | var integerRegexp = regexp.MustCompile("^[0-9]+$")
9 |
10 | func main() {
11 | fmt.Println(integerRegexp.MatchString("123")) // true
12 | fmt.Println(integerRegexp.MatchString(" 123 ")) // false
13 | }
14 |
--------------------------------------------------------------------------------
/src/chp11/custom-marshal-keys/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | type Key int64
9 |
10 | func main() {
11 | var m map[Key]int
12 | err := json.Unmarshal([]byte(`{"123":123}`), &m)
13 | if err != nil {
14 | panic(err)
15 | }
16 | fmt.Println(m[123]) // Prints 123
17 | }
18 |
--------------------------------------------------------------------------------
/src/chp13/tls/config.ext:
--------------------------------------------------------------------------------
1 | subjectKeyIdentifier = hash
2 | authorityKeyIdentifier = keyid:always,issuer:always
3 | basicConstraints = CA:TRUE
4 | keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign
5 | subjectAltName = DNS:localhost, IP:127.0.0.1
6 | issuerAltName = issuer:copy
7 |
--------------------------------------------------------------------------------
/src/chp13/http-tls/config.ext:
--------------------------------------------------------------------------------
1 | subjectKeyIdentifier = hash
2 | authorityKeyIdentifier = keyid:always,issuer:always
3 | basicConstraints = CA:TRUE
4 | keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign
5 | subjectAltName = DNS:localhost, IP:127.0.0.1
6 | issuerAltName = issuer:copy
7 |
--------------------------------------------------------------------------------
/src/chp17/sorting/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net/http"
6 |
7 | "github.com/PacktPublishing/Go-Recipes-for-Developers/src/chp17/sorting/service"
8 | )
9 |
10 | func main() {
11 | mux := service.GetServeMux()
12 | server := http.Server{
13 | Addr: ":8088",
14 | Handler: mux,
15 | }
16 | log.Println(server.ListenAndServe())
17 | }
18 |
--------------------------------------------------------------------------------
/src/chp8/stack/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "runtime/debug"
6 | )
7 |
8 | func main() {
9 | defer func() {
10 | if r := recover(); r != nil {
11 | stackTrace := string(debug.Stack())
12 | // Work with stackTrace
13 | fmt.Println(stackTrace)
14 | }
15 | }()
16 | f()
17 | }
18 |
19 | func f() {
20 | var i *int
21 | *i = 0
22 | }
23 |
--------------------------------------------------------------------------------
/src/chp2/template-empty-lines/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "text/template"
6 | )
7 |
8 | func main() {
9 | tmpl, err := template.New("tmpl").Parse(`{{range . -}}
10 | {{ if gt . 1 }}
11 | {{- . }}
12 | {{end -}}
13 | {{end -}}
14 | `)
15 | if err != nil {
16 | panic(err)
17 | }
18 | tmpl.Execute(os.Stdout, []int{-1, 0, 1, 2, 3, 4, 5})
19 | }
20 |
--------------------------------------------------------------------------------
/src/chp1/webform/pkg/commentdb/initdb.go:
--------------------------------------------------------------------------------
1 | package commentdb
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 | )
7 |
8 | const createStmt = `create table if not exists comments (
9 | email TEXT,
10 | comment TEXT)`
11 |
12 | func InitDB(conn *sql.DB) {
13 | _, err := conn.ExecContext(context.Background(), createStmt)
14 | if err != nil {
15 | panic(err)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/chp2/regex-replace/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "regexp"
6 | )
7 |
8 | func main() {
9 | // Find numbers, capture the first digit
10 | re := regexp.MustCompile(`([0-9])[0-9]*`)
11 | fmt.Println(re.ReplaceAllString("This example replaces numbers with 'x': 1, 100, 500.", "x"))
12 | fmt.Println(re.ReplaceAllString("This example replaces all numbers with their first digits: 1, 100, 500.", "${1}"))
13 | }
14 |
--------------------------------------------------------------------------------
/src/chp2/trim/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | func main() {
9 | fmt.Println(strings.TrimRight("Break-------", "-"))
10 | fmt.Println(strings.TrimRight("Break with spaces-- -- --", "- "))
11 | fmt.Println(strings.TrimSuffix("file.txt", ".txt"))
12 | fmt.Println(strings.TrimLeft(" \t Indented text", " \t"))
13 | fmt.Println(strings.TrimSpace(" \t \n Indented text \n\t"))
14 | }
15 |
--------------------------------------------------------------------------------
/src/chp12/namedpipe/reader/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "os"
7 | )
8 |
9 | func main() {
10 | // Open the named pipe for reading
11 | pipe, err := os.Open("../pipe")
12 | if err != nil {
13 | panic(err)
14 | }
15 | defer pipe.Close()
16 |
17 | // Read data until it is closed
18 | scanner := bufio.NewScanner(pipe)
19 | for scanner.Scan() {
20 | fmt.Println(scanner.Text())
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/chp12/namedpipe/writer/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io/fs"
6 | "os"
7 | )
8 |
9 | func main() {
10 | // Open the named pipe
11 | pipe, err := os.OpenFile("../pipe", os.O_WRONLY, fs.ModeNamedPipe|0660)
12 | if err != nil {
13 | panic(err)
14 | }
15 | defer pipe.Close()
16 |
17 | // Generate data and write to pipe
18 | for i := 0; i < 20; i++ {
19 | fmt.Fprintf(pipe, "Data %d\n", i)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/chp12/shell/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os/exec"
6 | "runtime"
7 | )
8 |
9 | func main() {
10 | var cmd *exec.Cmd
11 | if runtime.GOOS == "windows" {
12 | cmd = exec.Command("cmd", "/C", "echo test>test.txt")
13 | } else {
14 | cmd = exec.Command("/bin/sh", "-c", "echo test>test.txt")
15 | }
16 |
17 | out, err := cmd.Output()
18 | if err != nil {
19 | panic(err)
20 | }
21 | fmt.Println(string(out))
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/chp2/encoding/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "golang.org/x/text/encoding/ianaindex"
8 | )
9 |
10 | func main() {
11 | enc, err := ianaindex.MIME.Encoding("US-ASCII")
12 | if err != nil {
13 | panic(err)
14 | }
15 | b, err := os.ReadFile("ascii.txt")
16 | if err != nil {
17 | panic(err)
18 | }
19 | decoder := enc.NewDecoder()
20 | encoded, err := decoder.Bytes(b)
21 | if err != nil {
22 | panic(err)
23 | }
24 | fmt.Println(string(encoded))
25 | }
26 |
--------------------------------------------------------------------------------
/src/chp2/regex-submatch/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "regexp"
6 | )
7 |
8 | func main() {
9 | // This regular expression allows extra spaces before and after the
10 | // property name and value using \s*
11 | re := regexp.MustCompile(`^\s*(\w+)\s*=\s*(\w+)\s*$`)
12 | result := re.FindStringSubmatch(` property = 12 `)
13 | fmt.Printf("Key: %s value: %s\n", result[1], result[2])
14 | result = re.FindStringSubmatch(`x = y `)
15 | fmt.Printf("Key: %s value: %s\n", result[1], result[2])
16 | }
17 |
--------------------------------------------------------------------------------
/src/chp8/change-return-value/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | )
7 |
8 | func process(doPanic bool) (err error) {
9 | defer func() {
10 | r := recover()
11 | if e, ok := r.(error); ok {
12 | err = e
13 | }
14 | }()
15 | if doPanic {
16 | panic(errors.New("panic!"))
17 | }
18 | return nil
19 | }
20 |
21 | func main() {
22 | fmt.Printf("return value of process without panic: %v\n", process(false))
23 | fmt.Printf("return value of process with panic: %v\n", process(true))
24 | }
25 |
--------------------------------------------------------------------------------
/src/chp12/process/sub/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "os"
7 | "strconv"
8 | )
9 |
10 | func main() {
11 | var max uint64
12 | max = math.MaxUint64
13 | if len(os.Args) > 1 {
14 | x, err := strconv.ParseInt(os.Args[1], 10, 64)
15 | if err != nil {
16 | panic(err)
17 | }
18 | max = uint64(x)
19 | }
20 | for i := uint64(0); i < max; i++ {
21 | fmt.Println(i)
22 | // Print an error every 10 items
23 | if i%10 == 0 {
24 | fmt.Fprintln(os.Stderr, "Error:", i)
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/chp17/sorting/sort/sort.go:
--------------------------------------------------------------------------------
1 | package sorting
2 |
3 | import (
4 | "sort"
5 | "time"
6 | )
7 |
8 | // Sort times in ascending or descending order
9 | func SortTimes(input []time.Time, asc bool) []time.Time {
10 | output := make([]time.Time, len(input))
11 | copy(output, input)
12 | if asc {
13 | sort.Slice(output, func(i, j int) bool {
14 | return output[i].Before(output[j])
15 | })
16 | return output
17 | }
18 | sort.Slice(output, func(i, j int) bool {
19 | return output[j].Before(output[i])
20 | })
21 | return output
22 | }
23 |
--------------------------------------------------------------------------------
/src/chp12/stdin/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "os/exec"
6 | )
7 |
8 | func main() {
9 | input, err := os.Open("romeo-and-juliet.txt")
10 | if err != nil {
11 | panic(err)
12 | }
13 | // Search for lines containing weak
14 | cmd := exec.Command("grep", "weak")
15 | // Feed the file to the standard input
16 | cmd.Stdin = input
17 | // Redirect the output to stdout
18 | cmd.Stdout = os.Stdout
19 | if err = cmd.Start(); err != nil {
20 | panic(err)
21 | }
22 | if err = cmd.Wait(); err != nil {
23 | panic(err)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/chp13/echo-client-server/client/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "net"
7 | )
8 |
9 | var address = flag.String("a", ":8008", "Server address")
10 |
11 | func main() {
12 | flag.Parse()
13 | conn, err := net.Dial("tcp", *address)
14 | if err != nil {
15 | panic(err)
16 | }
17 | // Send a line of text
18 | text := []byte("Hello echo server!")
19 | conn.Write(text)
20 | // Read the response
21 | response := make([]byte, len(text))
22 | conn.Read(response)
23 | fmt.Println(string(response))
24 | conn.Close()
25 | }
26 |
--------------------------------------------------------------------------------
/src/chp1/webform/web/static/form.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/chp2/diacritics/main.go:
--------------------------------------------------------------------------------
1 | // Based on the blog post https://go.dev/blog/normalization
2 | package main
3 |
4 | import (
5 | "fmt"
6 | "io"
7 | "strings"
8 | "unicode"
9 |
10 | "golang.org/x/text/transform"
11 | "golang.org/x/text/unicode/norm"
12 | )
13 |
14 | func main() {
15 | isMn := func(r rune) bool {
16 | return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks
17 | }
18 | t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.
19 | NFC)
20 | rd := transform.NewReader(strings.NewReader("Montréal"), t)
21 | str, _ := io.ReadAll(rd)
22 | fmt.Println(string(str))
23 | }
24 |
--------------------------------------------------------------------------------
/src/chp2/split/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | func main() {
9 | fmt.Println(strings.Split("a,b,c,d", ","))
10 | fmt.Println(strings.Split("a, b, c, d", ","))
11 | fmt.Println(strings.Fields("a b c d "))
12 | for i, x := range strings.Split("a b c d ", " ") {
13 | if i > 0 {
14 | fmt.Printf(", ")
15 | }
16 | fmt.Printf(`"%s"`, x)
17 | }
18 | fmt.Println()
19 | for i, x := range strings.Split("a---b---c--d--", "-") {
20 | if i > 0 {
21 | fmt.Printf(", ")
22 | }
23 | fmt.Printf(`"%s"`, x)
24 | }
25 | fmt.Println()
26 | }
27 |
--------------------------------------------------------------------------------
/src/chp5/authenticator/factory.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | type AuthenticatorFactory interface {
4 | NewInstance() Authenticator
5 | }
6 |
7 | var registry = map[string]AuthenticatorFactory{}
8 |
9 | // RegisterAuthenticator registers a new authenticator factory
10 | func RegisterAuthenticator(name string, factory AuthenticatorFactory) {
11 | registry[name] = factory
12 | }
13 |
14 | func NewInstance(authType string) Authenticator {
15 | // Create a new instance using the selected factory.
16 | // If the given authType has not been registered, this will panic
17 | return registry[authType].NewInstance()
18 | }
19 |
--------------------------------------------------------------------------------
/src/chp5/enums/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | // Direction is an integer type
6 | type Direction int
7 |
8 | // Direction constants
9 | const (
10 | DirectionLeft Direction = iota
11 | DirectionRight
12 | )
13 |
14 | // String returns a string representation of a direction
15 | func (dir Direction) String() string {
16 | switch dir {
17 | case DirectionLeft:
18 | return "left"
19 | case DirectionRight:
20 | return "right"
21 | }
22 | return ""
23 | }
24 |
25 | func main() {
26 | fmt.Println(DirectionLeft, int(DirectionLeft))
27 | fmt.Println(DirectionRight, int(DirectionRight))
28 | }
29 |
--------------------------------------------------------------------------------
/ERRATA.md:
--------------------------------------------------------------------------------
1 | # Chapter 1
2 |
3 | ## Creating a module
4 |
5 | ### Page 4
6 |
7 | The sequence of commands on page 4 should be:
8 |
9 | ```
10 | $ cd projects
11 | $ mkdir webform
12 | $ cd webform
13 | $ go mod init github.com/examplecompany/webform
14 | ```
15 | This, of course creates the go.mod file:
16 |
17 | ```
18 | module github.com/examplecompany/webform/
19 | go 1.22.0
20 | ```
21 |
22 | The go.mod printed in the book points to the actual repository for
23 | this chapter.
24 |
25 | # Chapter 2
26 |
27 | ## Combining strings
28 |
29 | ### Page 23
30 |
31 | The text refers to `string.Builder`. That should be `strings.Builder`.
32 |
--------------------------------------------------------------------------------
/src/chp2/stringadd-benchmark/add_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "testing"
7 | )
8 |
9 | func BenchmarkXPlusY(b *testing.B) {
10 | x := "Hello"
11 | y := " World"
12 | for i := 0; i < b.N; i++ {
13 | _ = x + y
14 | }
15 | }
16 |
17 | func BenchmarkSprintf(b *testing.B) {
18 | x := "Hello"
19 | y := " World"
20 | for i := 0; i < b.N; i++ {
21 | _ = fmt.Sprintf("%s%s", x, y)
22 | }
23 | }
24 |
25 | func BenchmarkBuilder(b *testing.B) {
26 | x := "Hello"
27 | y := " World"
28 | for i := 0; i < b.N; i++ {
29 | builder := strings.Builder{}
30 | builder.WriteString(x)
31 | builder.WriteString(y)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/chp1/webform/cmd/webform/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "database/sql"
5 | "net/http"
6 |
7 | "github.com/gorilla/mux"
8 | _ "modernc.org/sqlite"
9 |
10 | "github.com/PacktPublishing/Go-Recipes-for-Developers/src/chp1/webform/internal/routes"
11 | "github.com/PacktPublishing/Go-Recipes-for-Developers/src/chp1/webform/pkg/commentdb"
12 | )
13 |
14 | func main() {
15 | db, err := sql.Open("sqlite", "webform.db")
16 | if err != nil {
17 | panic(err)
18 | }
19 | commentdb.InitDB(db)
20 |
21 | r := mux.NewRouter()
22 | routes.Build(r, db)
23 |
24 | server := http.Server{
25 | Addr: ":8181",
26 | Handler: r,
27 | }
28 | server.ListenAndServe()
29 | }
30 |
--------------------------------------------------------------------------------
/src/chp4/stringset/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | // Remove duplicate inputs from the input, preserving order
9 | func DedupOrdered(input []string) []string {
10 | set := make(map[string]struct{})
11 | output := make([]string, 0, len(input))
12 | for _, in := range input {
13 | if _, exists := set[in]; exists {
14 | continue
15 | }
16 | output = append(output, in)
17 | set[in] = struct{}{}
18 | }
19 | return output
20 | }
21 |
22 | func main() {
23 | str := `this function removes the duplicate words in the input using a map used as a string set`
24 | fmt.Println("Input:", str)
25 | fmt.Println("Output:", DedupOrdered(strings.Split(str, " ")))
26 | }
27 |
--------------------------------------------------------------------------------
/src/chp4/compositekeys/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | type Key struct {
6 | UserID string
7 | SessionID string
8 | }
9 |
10 | type User struct {
11 | ID string
12 | Name string
13 | }
14 |
15 | func main() {
16 | compositeKeyMap := map[Key]User{}
17 | compositeKeyMap[Key{
18 | UserID: "123",
19 | SessionID: "1",
20 | }] = User{
21 | Name: "John Doe",
22 | ID: "123",
23 | }
24 |
25 | jane := User{
26 | Name: "Jane Doe",
27 | ID: "124",
28 | }
29 |
30 | key := Key{
31 | UserID: jane.ID,
32 | SessionID: "2",
33 | }
34 | compositeKeyMap[key] = jane
35 |
36 | for k, v := range compositeKeyMap {
37 | fmt.Printf("Key: %+v, Value: %+v\n", k, v)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/chp13/echo-client-server-lines/client/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "net"
7 | )
8 |
9 | var address = flag.String("a", ":8008", "Server address")
10 |
11 | func main() {
12 | flag.Parse()
13 | conn, err := net.Dial("tcp", *address)
14 | if err != nil {
15 | panic(err)
16 | }
17 | // Send a line of text
18 | text := []byte("Hello echo server!\n")
19 | conn.Write(text)
20 | // Read the response
21 | response := make([]byte, len(text))
22 | conn.Read(response)
23 | fmt.Println(string(response))
24 | // Launch an attack by sending large amount of data
25 | data := make([]byte, 1024*1000)
26 | _, err = conn.Write(data)
27 | fmt.Println(err)
28 |
29 | conn.Close()
30 | }
31 |
--------------------------------------------------------------------------------
/src/chp13/echo-client-server/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "io"
7 | "net"
8 | )
9 |
10 | var address = flag.String("a", ":8008", "Address to listen")
11 |
12 | func main() {
13 | flag.Parse()
14 |
15 | // Create a TCP listener
16 | listener, err := net.Listen("tcp", *address)
17 | if err != nil {
18 | panic(err)
19 | }
20 | fmt.Println("Listening on ", listener.Addr())
21 | defer listener.Close()
22 | // Listen to incoming TCP connections
23 | for {
24 | conn, err := listener.Accept()
25 | if err != nil {
26 | fmt.Println(err)
27 | return
28 | }
29 | go handleConnection(conn)
30 | }
31 | }
32 |
33 | func handleConnection(conn net.Conn) {
34 | io.Copy(conn, conn)
35 | }
36 |
--------------------------------------------------------------------------------
/src/chp11/dynamic-keys/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | type User struct {
9 | Name string `json:"name"`
10 | Type string `json:"type"`
11 | }
12 |
13 | type Users struct {
14 | Users map[string]User `json:"users"`
15 | }
16 |
17 | func main() {
18 | input := `{
19 | "users": {
20 | "abb64dfe-d4a8-47a5-b7b0-7613fe3fd11f": {
21 | "name": "John",
22 | "type": "admin"
23 | },
24 | "b158161c-0588-4c67-8e4b-c07a8978f711": {
25 | "name": "Amy",
26 | "type": "editor"
27 | }
28 | }
29 | }`
30 | var users Users
31 | if err := json.Unmarshal([]byte(input), &users); err != nil {
32 | panic(err)
33 | }
34 | fmt.Printf("%+v\n", users)
35 | }
36 |
--------------------------------------------------------------------------------
/src/chp2/join/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "path"
6 | "path/filepath"
7 | "strings"
8 | )
9 |
10 | func main() {
11 | words := []string{"foo", "bar", "baz"}
12 | fmt.Println(strings.Join(words, " "))
13 | // foo bar baz
14 | fmt.Println(strings.Join(words, ""))
15 | // foobarbaz
16 | fmt.Println(path.Join(words...))
17 | // foo/bar/baz
18 | fmt.Println(filepath.Join(words...))
19 | // foo/bar/baz or foo\bar\baz, depending on the host system
20 | paths := []string{"/foo", "//bar", "baz"}
21 | fmt.Println(strings.Join(paths, " "))
22 | // /foo //bar baz
23 | fmt.Println(path.Join(paths...))
24 | // /foo/bar/baz
25 | fmt.Println(filepath.Join(paths...))
26 | // /foo/bar/baz or \foo\bar\baz depending on the host system}
27 | }
28 |
--------------------------------------------------------------------------------
/src/chp14/open-create/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io"
7 | "os"
8 | )
9 |
10 | func open() {
11 | // Open a file read-only
12 | file, err := os.Open("test.txt")
13 | if err != nil {
14 | panic(err)
15 | }
16 | // Make sure it is closed
17 | defer file.Close()
18 |
19 | // Read from the file
20 | data := make([]byte, 100)
21 | count, err := file.Read(data)
22 | if err != nil {
23 | if !errors.Is(err, io.EOF) {
24 | panic(err)
25 | }
26 | }
27 | fmt.Printf("Read %d bytes: %s\n", count, string(data))
28 | // Attempt to write to a read-only file
29 | _, err = file.Write([]byte("Hello, World!"))
30 | fmt.Printf("Attempt to write returns: %T %v\n", err, err)
31 | }
32 |
33 | func main() {
34 | open()
35 | }
36 |
--------------------------------------------------------------------------------
/src/chp12/stdinpipe/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "os"
6 | "os/exec"
7 | )
8 |
9 | func main() {
10 | input, err := os.Open("romeo-and-juliet.txt")
11 | if err != nil {
12 | panic(err)
13 | }
14 | // Search for lines containing weak
15 | cmd := exec.Command("grep", "weak")
16 | stdin, err := cmd.StdinPipe()
17 | if err != nil {
18 | panic(err)
19 | }
20 | // Send the output to stdout
21 | cmd.Stdout = os.Stdout
22 | // Start the command
23 | if err = cmd.Start(); err != nil {
24 | panic(err)
25 | }
26 | // Send the file to the command
27 | io.Copy(stdin, input)
28 | // Close the stdin so the command knows it's done
29 | stdin.Close()
30 | // Wait until the program ends
31 | if err = cmd.Wait(); err != nil {
32 | panic(err)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/chp12/signal/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "io"
6 | "net/http"
7 | "os"
8 | "os/signal"
9 | "syscall"
10 | "time"
11 | )
12 |
13 | func main() {
14 | // Create a simple HTTP echo service
15 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
16 | io.Copy(w, r.Body)
17 | })
18 | server := &http.Server{Addr: ":8080"}
19 |
20 | // Listen for SIGINT and SIGTERM signals
21 | // Terminate the server with the signal
22 | sigTerm := make(chan os.Signal, 1)
23 | signal.Notify(sigTerm, syscall.SIGINT, syscall.SIGTERM)
24 | go func() {
25 | <-sigTerm
26 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.
27 | Second)
28 | defer cancel()
29 | server.Shutdown(ctx)
30 | }()
31 |
32 | server.ListenAndServe()
33 | }
34 |
--------------------------------------------------------------------------------
/src/chp2/scanner/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "strings"
7 | )
8 |
9 | const input = `This is a string
10 | that has 3
11 | lines.`
12 |
13 | func main() {
14 | lineScanner := bufio.NewScanner(strings.NewReader(input))
15 | line := 0
16 | for lineScanner.Scan() {
17 | text := lineScanner.Text()
18 | line++
19 | fmt.Printf("Line %d: %s\n", line, text)
20 | }
21 | if err := lineScanner.Err(); err != nil {
22 | panic(err)
23 | }
24 | wordScanner := bufio.NewScanner(strings.NewReader(input))
25 | wordScanner.Split(bufio.ScanWords)
26 | word := 0
27 | for wordScanner.Scan() {
28 | text := wordScanner.Text()
29 | word++
30 | fmt.Printf("word %d: %s\n", word, text)
31 | }
32 | if err := wordScanner.Err(); err != nil {
33 | panic(err)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/chp7/channels/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | var data []string = []string{
6 | "this",
7 | "example",
8 | "demonstrates",
9 | "using",
10 | "channels",
11 | "to",
12 | "send",
13 | "and",
14 | "receive",
15 | "data",
16 | }
17 |
18 | // This example demonstrates using channels to send and receive data
19 | func main() {
20 |
21 | ch := make(chan string)
22 | // Generator function sends elements to the channel one by one.
23 | // When it is done, it closes the channel to signal the end of data.
24 | go func() {
25 | for _, str := range data {
26 | ch <- str
27 | }
28 | close(ch)
29 | }()
30 |
31 | // Receive strings from the channel and print them
32 | // For loop terminates when channel is closed
33 | for str := range ch {
34 | fmt.Println(str)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/chp13/tls/readme.md:
--------------------------------------------------------------------------------
1 | This is how you can create a self-signed certificate using openssl.
2 |
3 | 1. Create a private key
4 |
5 | ```
6 | openssl ecparam -name secp521r1 -genkey -noout -out privatekey.pem
7 | ```
8 |
9 | 2. Extract the corresponding public key from the private key
10 |
11 | ```
12 | openssl ec -in privatekey.pem -pubout > publickey.pem
13 | ```
14 |
15 | 3. Create a certificate signing request. Note the subject--it is not
16 | provigin a "common name", which is deprecated
17 |
18 | ```
19 | openssl req -new -sha256 -subj "/C=US" -key privatekey.pem -out my.csr
20 | ```
21 |
22 | 4. Create a certificate by signing it using the private key. The
23 | config.ext provides SANs for localhost
24 |
25 | ```
26 | openssl x509 -signkey privatekey.pem -in my.csr -extfile config.ext -req -days 3650 -out server.crt
27 | ```
28 |
--------------------------------------------------------------------------------
/src/chp13/http-tls/readme.md:
--------------------------------------------------------------------------------
1 | This is how you can create a self-signed certificate using openssl.
2 |
3 | 1. Create a private key
4 |
5 | ```
6 | openssl ecparam -name secp521r1 -genkey -noout -out privatekey.pem
7 | ```
8 |
9 | 2. Extract the corresponding public key from the private key
10 |
11 | ```
12 | openssl ec -in privatekey.pem -pubout > publickey.pem
13 | ```
14 |
15 | 3. Create a certificate signing request. Note the subject--it is not
16 | provigin a "common name", which is deprecated
17 |
18 | ```
19 | openssl req -new -sha256 -subj "/C=US" -key privatekey.pem -out my.csr
20 | ```
21 |
22 | 4. Create a certificate by signing it using the private key. The
23 | config.ext provides SANs for localhost
24 |
25 | ```
26 | openssl x509 -signkey privatekey.pem -in my.csr -extfile config.ext -req -days 3650 -out server.crt
27 | ```
28 |
--------------------------------------------------------------------------------
/src/chp13/udp-client-server/client/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "net"
7 | )
8 |
9 | var serverAddress = flag.String("a", "localhost:8008", "Server address")
10 |
11 | func main() {
12 | flag.Parse()
13 | addr, err := net.ResolveUDPAddr("udp4", *serverAddress)
14 | if err != nil {
15 | panic(err)
16 | }
17 | // Create a UDP connection, local address chosen randomly
18 | conn, err := net.DialUDP("udp4", nil, addr)
19 | if err != nil {
20 | panic(err)
21 | }
22 | fmt.Printf("UDP server %s\n", conn.RemoteAddr())
23 | defer conn.Close()
24 | // Send a line of text
25 | text := []byte("Hello echo server!")
26 | n, err := conn.Write(text)
27 | if err != nil {
28 | panic(err)
29 | }
30 | fmt.Printf("Written %d bytes\n", n)
31 | // Read the response
32 | response := make([]byte, 1024)
33 | conn.ReadFromUDP(response)
34 | fmt.Println(string(response))
35 | conn.Close()
36 | }
37 |
--------------------------------------------------------------------------------
/src/chp9/timeout/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "sync"
7 | "time"
8 |
9 | "golang.org/x/exp/rand"
10 | )
11 |
12 | func longRunningGoroutine(ctx context.Context, wg *sync.WaitGroup) {
13 | defer wg.Done()
14 | for {
15 | // Process some data
16 | // Check context cancelation
17 | select {
18 | case <-ctx.Done():
19 | // Context canceled
20 | fmt.Println("Canceled")
21 | return
22 | default:
23 | }
24 | // Continue computation
25 | fmt.Println("Computing...")
26 | time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
27 | }
28 | }
29 |
30 | func main() {
31 | ctx := context.Background()
32 | timeoutable, cancel := context.WithTimeout(ctx, 5*time.Second)
33 | defer cancel()
34 |
35 | wg := sync.WaitGroup{}
36 | wg.Add(1)
37 | go longRunningGoroutine(timeoutable, &wg)
38 | wg.Add(1)
39 | go longRunningGoroutine(timeoutable, &wg)
40 | wg.Wait()
41 | }
42 |
--------------------------------------------------------------------------------
/src/chp13/http-tls/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/tls"
5 | "flag"
6 | "io"
7 | "net/http"
8 | )
9 |
10 | var (
11 | address = flag.String("a", ":4433", "Address to listen")
12 | certificate = flag.String("c", "../server.crt", "Certificate file")
13 | key = flag.String("k", "../privatekey.pem", "Private key")
14 | )
15 |
16 | type echoHandler struct{}
17 |
18 | func (echoHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
19 | io.Copy(w, req.Body)
20 | }
21 |
22 | func main() {
23 | flag.Parse()
24 | handler := echoHandler{}
25 |
26 | cert, err := tls.LoadX509KeyPair(*certificate, *key)
27 | if err != nil {
28 | panic(err)
29 | }
30 | tlsConfig := &tls.Config{
31 | Certificates: []tls.Certificate{cert},
32 | }
33 | server := http.Server{
34 | Addr: *address,
35 | Handler: handler,
36 | TLSConfig: tlsConfig,
37 | }
38 |
39 | server.ListenAndServeTLS("", "")
40 | }
41 |
--------------------------------------------------------------------------------
/src/chp1/webform/internal/routes/routes.go:
--------------------------------------------------------------------------------
1 | package routes
2 |
3 | import (
4 | "database/sql"
5 | "github.com/gorilla/mux"
6 | "net/http"
7 | )
8 |
9 | func Build(router *mux.Router, conn *sql.DB) {
10 | router.Path("/form").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
11 | http.ServeFile(w, r, "web/static/form.html")
12 | })
13 |
14 | router.Path("/form").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
15 | handlePost(conn, w, r)
16 | })
17 | }
18 |
19 | func handlePost(conn *sql.DB, w http.ResponseWriter, r *http.Request) {
20 | email := r.PostFormValue("email")
21 | comment := r.PostFormValue("comment")
22 | _, err := conn.ExecContext(r.Context(), "insert into comments (email,comment) values (?,?)", email, comment)
23 | if err != nil {
24 | http.Error(w, err.Error(), http.StatusInternalServerError)
25 | return
26 | }
27 | http.Redirect(w, r, "/form", http.StatusFound)
28 | }
29 |
--------------------------------------------------------------------------------
/src/chp13/udp-client-server/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "net"
7 | )
8 |
9 | var address = flag.String("a", ":8008", "Address to listen")
10 |
11 | func main() {
12 | flag.Parse()
13 |
14 | addr, err := net.ResolveUDPAddr("udp4", *address)
15 | if err != nil {
16 | panic(err)
17 | }
18 | // Create a UDP connection
19 | conn, err := net.ListenUDP("udp4", addr)
20 | if err != nil {
21 | panic(err)
22 | }
23 | fmt.Println("Listening on ", conn.LocalAddr())
24 | defer conn.Close()
25 | // Listen to incoming UDP connections
26 | buf := make([]byte, 1024)
27 | for {
28 | n, remoteAddr, err := conn.ReadFromUDP(buf)
29 | if err != nil {
30 | fmt.Println(err)
31 | continue
32 | }
33 | fmt.Printf("Received %d bytes from %s\n", n, remoteAddr)
34 | if n > 0 {
35 | _, err := conn.WriteToUDP(buf[:n], remoteAddr)
36 | if err != nil {
37 | fmt.Println(err)
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/chp11/custom-marshal-hexkey/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "strconv"
7 | )
8 |
9 | // Key is an uint that is encoded as an hex strings for JSON key
10 | type Key uint
11 |
12 | func (k *Key) UnmarshalText(data []byte) error {
13 | v, err := strconv.ParseInt(string(data), 16, 64)
14 | if err != nil {
15 | return err
16 | }
17 | *k = Key(v)
18 | return nil
19 | }
20 | func (k Key) MarshalText() ([]byte, error) {
21 | s := strconv.FormatUint(uint64(k), 16)
22 | return []byte(s), nil
23 | }
24 |
25 | func main() {
26 | input := `{
27 | "13AD": "5037",
28 | "3E22": "15906",
29 | "90A3": "37027"
30 | }`
31 | var data map[Key]string
32 |
33 | if err := json.Unmarshal([]byte(input), &data); err != nil {
34 | panic(err)
35 | }
36 | fmt.Println(data)
37 | d, err := json.Marshal(map[Key]any{
38 | Key(123): "123",
39 | Key(255): "255",
40 | })
41 | if err != nil {
42 | panic(err)
43 | }
44 | fmt.Println(string(d))
45 | }
46 |
--------------------------------------------------------------------------------
/src/chp11/nonstring-keys/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "strconv"
7 | )
8 |
9 | // Key is an uint that is encoded as an hex strings for JSON key
10 | type Key uint
11 |
12 | func (k *Key) UnmarshalText(data []byte) error {
13 | v, err := strconv.ParseInt(string(data), 16, 64)
14 | if err != nil {
15 | return err
16 | }
17 | *k = Key(v)
18 | return nil
19 | }
20 |
21 | func (k Key) MarshalText() ([]byte, error) {
22 | s := strconv.FormatUint(uint64(k), 16)
23 | return []byte(s), nil
24 | }
25 |
26 | func main() {
27 | input := `{
28 | "13AD": "5037",
29 | "3E22": "15906",
30 | "90A3": "37027"
31 | }`
32 |
33 | var data map[Key]string
34 | if err := json.Unmarshal([]byte(input), &data); err != nil {
35 | panic(err)
36 | }
37 | fmt.Println(data)
38 | d, err := json.Marshal(map[Key]any{
39 | Key(123): "123",
40 | Key(255): "255",
41 | })
42 | if err != nil {
43 | panic(err)
44 | }
45 | fmt.Println(string(d))
46 | }
47 |
--------------------------------------------------------------------------------
/src/chp14/tee/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "net/http"
7 | "net/http/httptest"
8 | "os"
9 | )
10 |
11 | func main() {
12 | if len(os.Args) != 2 {
13 | fmt.Println("Enter the name of file to read")
14 | os.Exit(1)
15 | }
16 | // Create an http test server
17 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
18 | // Write data to stdout
19 | io.Copy(os.Stdout, r.Body)
20 | }))
21 | defer ts.Close()
22 | serverURL := ts.URL
23 | dataFile := os.Args[1]
24 |
25 | pipeReader, pipeWriter := io.Pipe()
26 | file, err := os.Open(dataFile)
27 | if err != nil {
28 | // Handle error
29 | panic(err)
30 | }
31 | defer file.Close()
32 | tee := io.TeeReader(file, pipeWriter)
33 | go func() {
34 | // Copy the file to stdout
35 | io.Copy(os.Stdout, pipeReader)
36 | }()
37 | _, err = http.Post(serverURL, "text/plain", tee)
38 | if err != nil {
39 | // Make sure pipe is closed
40 | pipeReader.Close()
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/chp2/template-composition/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "text/template"
6 | )
7 |
8 | const tp = `{{define "line"}}
9 | {{.Title}} {{.Author}} {{.PubYear}}
10 | {{end}}
11 | Book list:
12 | {{range . -}}
13 | {{template "line" .}}
14 | {{end -}}
15 | `
16 |
17 | type Book struct {
18 | Title string
19 | Author string
20 | PubYear int
21 | }
22 |
23 | var books = []Book{
24 | {
25 | Title: "Pride and Prejudice",
26 | Author: "Jane Austen",
27 | PubYear: 1813,
28 | },
29 | {
30 | Title: "To Kill a Mockingbird",
31 | Author: "Harper Lee",
32 | PubYear: 1960,
33 | },
34 | {
35 | Title: "The Great Gatsby",
36 | Author: "F. Scott Fitzgerald",
37 | PubYear: 1925,
38 | },
39 | {
40 | Title: "The Lord of the Rings",
41 | Author: "J.R.R. Tolkien",
42 | PubYear: 1954,
43 | },
44 | }
45 |
46 | func main() {
47 | tmpl, err := template.New("body").Parse(tp)
48 | if err != nil {
49 | panic(err)
50 | }
51 | tmpl.Execute(os.Stdout, books)
52 | }
53 |
--------------------------------------------------------------------------------
/src/chp14/copy/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "io"
7 | "os"
8 | )
9 |
10 | var (
11 | sourceFile = flag.String("src", "", "Source file")
12 | targetFile = flag.String("tgt", "", "Target file")
13 | )
14 |
15 | func help() {
16 | fmt.Println("Run with -src -tgt ")
17 | os.Exit(1)
18 | }
19 |
20 | func main() {
21 | flag.Parse()
22 |
23 | sourceFileName := *sourceFile
24 | if sourceFileName == "" {
25 | help()
26 | }
27 | targetFileName := *targetFile
28 | if targetFileName == "" {
29 | help()
30 | }
31 | if sourceFileName == targetFileName {
32 | panic("Target is the same as source")
33 | }
34 | sourceFile, err := os.Open(sourceFileName)
35 | if err != nil {
36 | panic(err)
37 | }
38 | defer sourceFile.Close()
39 | targetFile, err := os.Create(targetFileName)
40 | if err != nil {
41 | panic(err)
42 | }
43 | defer targetFile.Close()
44 | if _, err := io.Copy(targetFile, sourceFile); err != nil {
45 | panic(err)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/chp3/ticker/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func everySecond(f func(), done chan struct{}) {
9 | // Create a new ticker with a 1 second period
10 | ticker := time.NewTicker(time.Second)
11 | start := time.Now()
12 | // Stop the ticker once we're done
13 | defer ticker.Stop()
14 | for {
15 | select {
16 | case <-done:
17 | return
18 | case <-ticker.C:
19 | // Call the function
20 | fmt.Println(time.Since(start).Milliseconds())
21 | f()
22 | }
23 | }
24 | }
25 |
26 | func main() {
27 | cnt := 0
28 | done := make(chan struct{})
29 | everySecond(func() {
30 | switch cnt {
31 | case 0: // First call lasts 10 msecs
32 | time.Sleep(10 * time.Millisecond)
33 | cnt++
34 | case 1: // Second call lasts 1.5seconds
35 | time.Sleep(1500 * time.Millisecond)
36 | cnt++
37 | default:
38 | // Remaining calls are quick, and stop after 5 calls
39 | cnt++
40 | if cnt == 5 {
41 | close(done)
42 | }
43 | }
44 | }, done)
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/chp13/http-tls/client/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/tls"
5 | "crypto/x509"
6 | "flag"
7 | "io"
8 | "net/http"
9 | "os"
10 | "strings"
11 | )
12 |
13 | var (
14 | address = flag.String("a", "https://localhost:4433", "Server address")
15 | certificate = flag.String("c", "../server.crt", "Certificate file")
16 | )
17 |
18 | func main() {
19 | flag.Parse()
20 | certData, err := os.ReadFile(*certificate)
21 | if err != nil {
22 | panic(err)
23 | }
24 | roots := x509.NewCertPool()
25 | ok := roots.AppendCertsFromPEM(certData)
26 | if !ok {
27 | panic("failed to parse root certificate")
28 | }
29 | config := tls.Config{
30 | RootCAs: roots,
31 | }
32 | transport := &http.Transport{
33 | TLSClientConfig: &config,
34 | }
35 | client := &http.Client{
36 | Transport: transport,
37 | }
38 | resp, err := client.Post(*address, "text/plain", strings.NewReader("ping\n"))
39 | if err != nil {
40 | panic(err)
41 | }
42 | defer resp.Body.Close()
43 | io.Copy(os.Stdout, resp.Body)
44 | }
45 |
--------------------------------------------------------------------------------
/src/chp13/tls/client/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/tls"
5 | "crypto/x509"
6 | "flag"
7 | "fmt"
8 | "os"
9 | )
10 |
11 | var (
12 | addr = flag.String("addr", "", "Server address")
13 | certFile = flag.String("cert", "../server.crt", "TLS certificate file")
14 | )
15 |
16 | func main() {
17 | flag.Parse()
18 |
19 | // Create new certificate pool
20 | roots := x509.NewCertPool()
21 | // Load server certificate
22 | certData, err := os.ReadFile(*certFile)
23 | if err != nil {
24 | panic(err)
25 | }
26 | ok := roots.AppendCertsFromPEM(certData)
27 | if !ok {
28 | panic("failed to parse root certificate")
29 | }
30 |
31 | conn, err := tls.Dial("tcp", *addr, &tls.Config{
32 | RootCAs: roots,
33 | })
34 | if err != nil {
35 | panic(err)
36 | }
37 | // Send a line of text
38 | text := []byte("Hello echo server!")
39 | conn.Write(text)
40 | // Read the response
41 | response := make([]byte, len(text))
42 | conn.Read(response)
43 | fmt.Println(string(response))
44 | conn.Close()
45 | }
46 |
--------------------------------------------------------------------------------
/src/chp8/wrapping-errors/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "os"
8 | )
9 |
10 | type Config struct {
11 | Option string `json:"option"`
12 | }
13 |
14 | func LoadConfig(f string) (*Config, error) {
15 | file, err := os.Open(f)
16 | if err != nil {
17 | return nil, fmt.Errorf("file %s: %w", f, err)
18 | }
19 | defer file.Close()
20 | var cfg Config
21 | err = json.NewDecoder(file).Decode(&cfg)
22 | if err != nil {
23 | return nil, fmt.Errorf("While unmarshaling %s: %w", f, err)
24 | }
25 | return &cfg, nil
26 | }
27 |
28 | func main() {
29 | _, err := LoadConfig("nonexistant-config.json")
30 | fmt.Printf("Error: %v, Is ErrNotExist: %v\n", err, errors.Is(err, os.ErrNotExist))
31 | _, err = LoadConfig("invalid-config.json")
32 | fmt.Printf("Error: %v\n", err)
33 | var syntaxError *json.SyntaxError
34 | if errors.As(err, &syntaxError) {
35 | fmt.Printf("Unwrapped syntax error: %+v\n", syntaxError)
36 | }
37 | config, err := LoadConfig("valid-config.json")
38 | fmt.Println(config, err)
39 | }
40 |
--------------------------------------------------------------------------------
/src/chp9/server-timeout/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net"
7 | "time"
8 | )
9 |
10 | const RequestTimeout = time.Second * 5
11 |
12 | func handleRequest(ctx context.Context, c net.Conn) {
13 | defer c.Close()
14 | for {
15 | if ctx.Err() != nil {
16 | fmt.Println("Canceled")
17 | return
18 | }
19 | fmt.Println("Waiting")
20 | time.Sleep(time.Second)
21 | }
22 | }
23 |
24 | func main() {
25 | ln, err := net.Listen("tcp", ":8080")
26 | if err != nil {
27 | panic(err)
28 | }
29 | fmt.Println("Listening on localhost:8080. Run curl localhost:8080 to establish connection...")
30 | for {
31 | conn, err := ln.Accept()
32 | if err != nil {
33 | panic(err)
34 | }
35 | go func(c net.Conn) {
36 | // Step 1:
37 | // Request times out after duration: RequestTimeout
38 | ctx, cancel := context.WithTimeout(context.Background(), RequestTimeout)
39 |
40 | // Step 2:
41 | // Make sure cancel is called
42 | defer cancel()
43 |
44 | // Step 3:
45 | // Pass the context to handler
46 | handleRequest(ctx, c)
47 | }(conn)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/chp13/http-static-files/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "net/http"
7 | "os"
8 | "path/filepath"
9 | )
10 |
11 | var (
12 | address = flag.String("address", ":8080", "HTTP server address")
13 | dir = flag.String("dir", "/usr/share/doc", "Directory to serve")
14 | ext = flag.String("ext", "", "File extension to serve")
15 | )
16 |
17 | type filterFS struct {
18 | dirFS http.FileSystem
19 | ext string
20 | }
21 |
22 | func (f filterFS) Open(name string) (http.File, error) {
23 | if filepath.Ext(name) != f.ext {
24 | return nil, os.ErrNotExist
25 | }
26 | return f.dirFS.Open(name)
27 | }
28 |
29 | func main() {
30 | flag.Parse()
31 | server := http.Server{
32 | Addr: *address,
33 | }
34 | var fs http.FileSystem
35 | if len(*ext) > 0 {
36 | fs = filterFS{
37 | dirFS: http.Dir(*dir),
38 | ext: *ext,
39 | }
40 | } else {
41 | fs = http.Dir(*dir)
42 | }
43 | server.Handler = http.FileServer(fs)
44 | fmt.Printf("Serving %s on HTTP %s\n", *dir, *address)
45 | err := server.ListenAndServe()
46 | if err != nil {
47 | panic(err)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/chp2/template-basics/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "text/template"
6 | )
7 |
8 | type Book struct {
9 | Title string
10 | Author string
11 | PubYear int
12 | }
13 |
14 | const tp = `The book "{{.Title}}" by {{.Author}} was published in {{.PubYear}}.
15 | `
16 |
17 | const tpIter = `{{range .}}
18 | The book "{{.Title}}" by {{.Author}} was published in {{.PubYear}}.
19 | {{end}}`
20 |
21 | func main() {
22 | book1 := Book{
23 | Title: "Pride and Prejudice",
24 | Author: "Jane Austen",
25 | PubYear: 1813,
26 | }
27 | book2 := Book{
28 | Title: "The Lord of the Rings",
29 | Author: "J.R.R. Tolkien",
30 | PubYear: 1954,
31 | }
32 | tmpl, err := template.New("book").Parse(tp)
33 | if err != nil {
34 | panic(err)
35 | }
36 | tmpl.Execute(os.Stdout, book1)
37 | tmpl.Execute(os.Stdout, book2)
38 |
39 | // Iteration
40 | tmpl, err = template.New("bookIter").Parse(tpIter)
41 | if err != nil {
42 | panic(err)
43 | }
44 | tmpl.Execute(os.Stdout, []Book{book1, book2})
45 |
46 | tmpl.Execute(os.Stdout, map[int]Book{
47 | 1: book1,
48 | 2: book2})
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/chp2/template-composition2/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "text/template"
6 | )
7 |
8 | const lineTemplate = `{{.Title}} {{.Author}} {{.PubYear}}`
9 | const bodyTemplate = `Book list:
10 | {{range . -}}
11 | {{template "line" .}}
12 | {{end -}}
13 | `
14 |
15 | type Book struct {
16 | Title string
17 | Author string
18 | PubYear int
19 | }
20 |
21 | var books = []Book{
22 | {
23 | Title: "Pride and Prejudice",
24 | Author: "Jane Austen",
25 | PubYear: 1813,
26 | },
27 | {
28 | Title: "To Kill a Mockingbird",
29 | Author: "Harper Lee",
30 | PubYear: 1960,
31 | },
32 | {
33 | Title: "The Great Gatsby",
34 | Author: "F. Scott Fitzgerald",
35 | PubYear: 1925,
36 | },
37 | {
38 | Title: "The Lord of the Rings",
39 | Author: "J.R.R. Tolkien",
40 | PubYear: 1954,
41 | },
42 | }
43 |
44 | func main() {
45 | tmpl, err := template.New("body").Parse(bodyTemplate)
46 | if err != nil {
47 | panic(err)
48 | }
49 | _, err = tmpl.New("line").Parse(lineTemplate)
50 | if err != nil {
51 | panic(err)
52 | }
53 | tmpl.Execute(os.Stdout, books)
54 | }
55 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Packt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/chp6/set/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | // A Set of values of type T
9 | type Set[T comparable] map[T]struct{}
10 |
11 | // NewSet creates a new set
12 | func NewSet[T comparable]() Set[T] {
13 | return make(Set[T])
14 | }
15 |
16 | // Has returns if the set has the given value
17 | func (s Set[T]) Has(value T) bool {
18 | _, exists := s[value]
19 | return exists
20 | }
21 |
22 | // Add adds values to s
23 | func (s Set[T]) Add(values ...T) {
24 | for _, v := range values {
25 | s[v] = struct{}{}
26 | }
27 | }
28 |
29 | // Remove removes values from s
30 | func (s Set[T]) Remove(values ...T) {
31 | for _, v := range values {
32 | delete(s, v)
33 | }
34 | }
35 |
36 | func (s Set[T]) String() string {
37 | builder := strings.Builder{}
38 | for v := range s {
39 | if builder.Len() > 0 {
40 | builder.WriteString(", ")
41 | }
42 | fmt.Fprint(&builder, v)
43 | }
44 | return builder.String()
45 | }
46 |
47 | func main() {
48 | s := NewSet[string]()
49 | s.Add("a", "b", "c")
50 | fmt.Println(s)
51 | fmt.Println("s has a?", s.Has("a"))
52 | fmt.Println("s has d?", s.Has("d"))
53 | }
54 |
--------------------------------------------------------------------------------
/src/chp13/http-calls/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "net/http"
6 | "os"
7 | "time"
8 | )
9 |
10 | func usingDefaultClient() error {
11 | response, err := http.Get("http://example.com")
12 | if err != nil {
13 | // Handle error
14 | return err
15 | }
16 | // Always close response body
17 | defer response.Body.Close()
18 | if response.StatusCode/100 == 2 {
19 | // HTTP 2xx, call was successful.
20 | // Work with response.Body
21 | io.Copy(os.Stdout, response.Body)
22 | }
23 | return nil
24 | }
25 |
26 | func usingCustomClient() error {
27 | client := http.Client{
28 | // Set a timeout for all outgoing calls.
29 | // If the call does not complete within 30 seconds, timeout.
30 | Timeout: 30 * time.Second,
31 | }
32 | response, err := client.Get("http://example.com")
33 | if err != nil {
34 | // handle error
35 | return err
36 | }
37 | // Always close response body
38 | defer response.Body.Close()
39 | io.Copy(os.Stdout, response.Body)
40 | return nil
41 | }
42 |
43 | func main() {
44 | if err := usingDefaultClient(); err != nil {
45 | panic(err)
46 | }
47 | if err := usingCustomClient(); err != nil {
48 | panic(err)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/chp13/tls/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/tls"
5 | "flag"
6 | "fmt"
7 | "io"
8 | "net"
9 | )
10 |
11 | var (
12 | address = flag.String("a", ":4433", "Address to listen")
13 | certificate = flag.String("c", "../server.crt", "Certificate file")
14 | key = flag.String("k", "../privatekey.pem", "Private key")
15 | )
16 |
17 | func main() {
18 | flag.Parse()
19 |
20 | // Load the key pair
21 | cer, err := tls.LoadX509KeyPair(*certificate, *key)
22 | if err != nil {
23 | panic(err)
24 | }
25 | // Create TLS configuration for the listener
26 | config := &tls.Config{
27 | Certificates: []tls.Certificate{cer},
28 | }
29 | // Create the listener
30 | listener, err := tls.Listen("tcp", *address, config)
31 | if err != nil {
32 | panic(err)
33 | return
34 | }
35 | defer listener.Close()
36 |
37 | fmt.Println("Listening TLS on ", listener.Addr())
38 | // Listen to incoming TCP connections
39 | for {
40 | conn, err := listener.Accept()
41 | if err != nil {
42 | fmt.Println(err)
43 | return
44 | }
45 | go handleConnection(conn)
46 | }
47 | }
48 |
49 | func handleConnection(conn net.Conn) {
50 | io.Copy(conn, conn)
51 | }
52 |
--------------------------------------------------------------------------------
/src/chp14/pipe/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "io"
7 | "net/http"
8 | "net/http/httptest"
9 | "os"
10 | )
11 |
12 | func main() {
13 | // Create an http test server
14 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
15 | // Write data to stdout
16 | io.Copy(os.Stdout, r.Body)
17 | }))
18 | defer ts.Close()
19 | serverURL := ts.URL
20 | // Create a test payload
21 | payload := map[string]any{
22 | "key": "value",
23 | }
24 |
25 | pipeReader, pipeWriter := io.Pipe()
26 | go func() {
27 | // Close the writer side, so the reader knows when it is done
28 | defer pipeWriter.Close()
29 | encoder := json.NewEncoder(pipeWriter)
30 | if err := encoder.Encode(payload); err != nil {
31 | if errors.Is(err, io.ErrClosedPipe) {
32 | // The reader side terminated with error
33 | panic(err)
34 | } else {
35 | // Handle error
36 | panic(err)
37 | }
38 | }
39 | }()
40 | if _, err := http.Post(serverURL, "application/json", pipeReader); err != nil {
41 | // Close the reader, so the writing goroutine terminates
42 | pipeReader.Close()
43 | // Handle error
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/chp13/tcp-file-transfer/sender/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/binary"
5 | "flag"
6 | "io"
7 | "net"
8 | "os"
9 | )
10 |
11 | var address = flag.String("a", ":8008", "Server address")
12 | var file = flag.String("file", "", "File to send")
13 |
14 | type fileMetadata struct {
15 | Size uint64
16 | Mode uint32
17 | NameLen uint16
18 | }
19 |
20 | func main() {
21 | flag.Parse()
22 |
23 | file, err := os.Open(*file)
24 | if err != nil {
25 | panic(err)
26 | }
27 |
28 | conn, err := net.Dial("tcp", *address)
29 | if err != nil {
30 | panic(err)
31 | }
32 |
33 | // Encode file metadata
34 | fileInfo, err := file.Stat()
35 | if err != nil {
36 | panic(err)
37 | }
38 | md := fileMetadata{
39 | Size: uint64(fileInfo.Size()),
40 | Mode: uint32(fileInfo.Mode()),
41 | NameLen: uint16(len(fileInfo.Name())),
42 | }
43 | if err := binary.Write(conn, binary.LittleEndian, md); err != nil {
44 | panic(err)
45 | }
46 | // The file name
47 | if _, err := conn.Write([]byte(fileInfo.Name())); err != nil {
48 | panic(err)
49 | }
50 | // The file contents
51 | if _, err := io.Copy(conn, file); err != nil {
52 | panic(err)
53 | }
54 | conn.Close()
55 | }
56 |
--------------------------------------------------------------------------------
/src/chp13/http-rnd-service/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/binary"
5 | "io"
6 | "net/http"
7 | "os"
8 | "strconv"
9 | )
10 |
11 | type RandomService struct {
12 | rndSource io.Reader
13 | }
14 |
15 | func (svc RandomService) ServeHTTP(w http.ResponseWriter, req *http.Request) {
16 | // Read 4 bytes from the random number source, convert it to string
17 | data := make([]byte, 4)
18 | _, err := svc.rndSource.Read(data)
19 | if err != nil {
20 | // This will return an HTTP 500 error with the error message
21 | // as the message body
22 | http.Error(w, err.Error(), http.StatusInternalServerError)
23 | return
24 | }
25 | // Decode random data using binary little endian encoding
26 | value := binary.LittleEndian.Uint32(data)
27 | // Write the data to the output
28 | w.Write([]byte(strconv.Itoa(int(value))))
29 | }
30 |
31 | func main() {
32 | file, err := os.Open("/dev/random")
33 | if err != nil {
34 | panic(err)
35 | }
36 | defer file.Close()
37 | svc := RandomService{
38 | rndSource: file,
39 | }
40 | mux := http.NewServeMux()
41 | mux.Handle("GET /rnd", svc)
42 | server := http.Server{
43 | Handler: mux,
44 | Addr: ":8080",
45 | }
46 | server.ListenAndServe()
47 | }
48 |
--------------------------------------------------------------------------------
/src/chp6/sum/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | // Sum1 accepts int and float64 values and returns the sum of those
6 | func Sum1[T int | float64](values ...T) T {
7 | var result T
8 | for _, x := range values {
9 | result += x
10 | }
11 | return result
12 | }
13 |
14 | type number interface {
15 | ~int | ~int64 | ~int32 | ~int16 | ~int8 |
16 | ~uint | ~uint64 | ~uint32 | ~uint16 | ~uint8 |
17 | ~float64 | ~float32
18 | }
19 |
20 | func Sum2[T number](values ...T) T {
21 | var result T
22 | for _, x := range values {
23 | result += x
24 | }
25 | return result
26 | }
27 |
28 | func main() {
29 | // Type of Sum1 can be inferred from the arguments (int)
30 | fmt.Println(Sum1(1, 2, 3, 4, 5))
31 | // Type of Sum1 can be inferred from the arguments (float64)
32 | fmt.Println(Sum1(1.1, 12.5, 10.42))
33 |
34 | // Explicit specification of type. This version of Sum1 works with
35 | // float64 values.
36 | fmt.Println(Sum1[float64](1, 2, 3, 4, 5))
37 |
38 | // Sum2 works with all number types. Here, the result is uint16
39 | fmt.Println(Sum2(uint16(1), uint16(2), uint16(3)))
40 |
41 | // fun is a function that accepts float32 values
42 | fun := Sum2[float32]
43 | fmt.Println(fun(1, 2, 3))
44 | }
45 |
--------------------------------------------------------------------------------
/src/chp17/sorting/service/service.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "encoding/json"
5 | "io"
6 | "net/http"
7 | "time"
8 |
9 | "github.com/PacktPublishing/Go-Recipes-for-Developers/src/chp17/sorting/sort"
10 | )
11 |
12 | func HandleSort(w http.ResponseWriter, req *http.Request, ascending bool) {
13 | var input []time.Time
14 | data, err := io.ReadAll(req.Body)
15 | if err != nil {
16 | http.Error(w, err.Error(), http.StatusBadRequest)
17 | return
18 | }
19 | if err := json.Unmarshal(data, &input); err != nil {
20 | http.Error(w, err.Error(), http.StatusBadRequest)
21 | return
22 | }
23 | output := sorting.SortTimes(input, ascending)
24 | data, err = json.Marshal(output)
25 | if err != nil {
26 | http.Error(w, err.Error(), http.StatusInternalServerError)
27 | return
28 | }
29 | w.Header().Set("Content-Type", "application/json")
30 | w.Write(data)
31 | }
32 |
33 | func GetServeMux() *http.ServeMux {
34 | mux := http.NewServeMux()
35 | mux.HandleFunc("POST /sort/asc", func(w http.ResponseWriter, req *http.Request) {
36 | HandleSort(w, req, true)
37 | })
38 | mux.HandleFunc("POST /sort/desc", func(w http.ResponseWriter, req *http.Request) {
39 | HandleSort(w, req, false)
40 | })
41 | return mux
42 | }
43 |
--------------------------------------------------------------------------------
/src/chp3/format/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func main() {
9 | t := time.Date(2024, 3, 8, 18, 2, 13, 500, time.UTC)
10 |
11 | fmt.Println("Date in yyyy/mm/dd format", t.Format("2006/01/02"))
12 | fmt.Println("Date in yyyy/m/d format", t.Format("2006/1/2"))
13 | fmt.Println("Date in yy/m/d format", t.Format("06/1/2"))
14 |
15 | fmt.Println("Time in hh:mm format (12 hr)", t.Format("03:04"))
16 | fmt.Println("Time in hh:m format (24 hr)", t.Format("15:4"))
17 |
18 | fmt.Println("Date-time with time zone", t.Format("2006-01-02 13:04:05 -07:00"))
19 |
20 | printTime := func(format string) {
21 | fmt.Println(format, t.Format(format))
22 | }
23 | printTime("Mon Jan 2 15:04:05 2006")
24 |
25 | t, err := time.Parse("2006-01-02", "2024-08-12")
26 | if err != nil {
27 | panic(err)
28 | }
29 | fmt.Println(t)
30 |
31 | loc, _ := time.LoadLocation("America/Denver")
32 | const format = "Jan 2, 2006 at 3:04pm"
33 | t, err = time.ParseInLocation(format, "Jul 9, 2012 at 5:02am", loc)
34 | if err != nil {
35 | panic(err)
36 | }
37 | fmt.Println(t)
38 | t, err = time.ParseInLocation(format, "Jan 9, 2012 at 5:02am", loc)
39 | if err != nil {
40 | panic(err)
41 | }
42 | fmt.Println(t)
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/chp2/template-scoping/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "text/template"
6 | )
7 |
8 | type Book struct {
9 | Title string
10 | Author string
11 | Editions []Edition
12 | }
13 |
14 | type Edition struct {
15 | Edition int
16 | PubYear int
17 | }
18 |
19 | const tp = `{{range $bookIndex, $book := .}}
20 | {{$book.Author}}
21 | {{range $book.Editions}}
22 | {{$book.Title}} Edition: {{.Edition}} {{.PubYear}}
23 | {{end}}
24 | {{end}}`
25 |
26 | func main() {
27 | book1 := Book{
28 | Title: "Pride and Prejudice",
29 | Author: "Jane Austen",
30 | Editions: []Edition{
31 | {
32 | Edition: 1,
33 | PubYear: 1813,
34 | },
35 | {
36 | Edition: 2,
37 | PubYear: 1813,
38 | },
39 | {
40 | Edition: 3,
41 | PubYear: 1817,
42 | },
43 | },
44 | }
45 | book2 := Book{
46 | Title: "The Lord of the Rings",
47 | Author: "J.R.R. Tolkien",
48 | Editions: []Edition{
49 | {
50 | Edition: 1,
51 | PubYear: 1954,
52 | },
53 | {
54 | Edition: 2,
55 | PubYear: 1966,
56 | },
57 | {
58 | Edition: 3,
59 | PubYear: 1979,
60 | },
61 | },
62 | }
63 | tmpl, err := template.New("book").Parse(tp)
64 | if err != nil {
65 | panic(err)
66 | }
67 | tmpl.Execute(os.Stdout, []Book{book1, book2})
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/chp5/embedding/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/google/uuid"
8 | )
9 |
10 | type Metadata struct {
11 | ID string
12 | CreatedAt time.Time
13 | ModifiedAt time.Time
14 | }
15 |
16 | // New initializes metadata fields
17 | func (m *Metadata) New() {
18 | m.ID = uuid.New().String()
19 | m.CreatedAt = time.Now()
20 | m.ModifiedAt = m.CreatedAt
21 | }
22 |
23 | // Customer.New() uses the promoted Metadata.New() method.
24 | // Calling Customer.New() will initialize Customer.Metadata, but
25 | // will not modify Customer specific fields.
26 | type Customer struct {
27 | Metadata
28 | Name string
29 | }
30 |
31 | // Product.New(string) shadows `Metadata.New() method. You cannot
32 | // call `Product.New()`, but call `Product.New(string)` or
33 | // `Product.Metadata.New()`
34 | type Product struct {
35 | Metadata
36 | SKU string
37 | }
38 |
39 | func (p *Product) New(sku string) {
40 | // Initialize the metadata part of product
41 | p.Metadata.New()
42 | p.SKU = sku
43 | }
44 |
45 | func main() {
46 | c := Customer{}
47 | c.New() // Initialize customer metadata
48 | fmt.Printf("Customer: %+v\n", c)
49 |
50 | p := Product{}
51 | p.New("sku") // Initialize product metadata and sku
52 | fmt.Printf("Product: %+v\n", p)
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/chp2/formatted-text/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func main() {
8 | fmt.Printf("Print integers using %%d: %d|\n", 10)
9 | fmt.Printf("You can set the width of the printed number, left aligned: %5d|\n", 10)
10 | fmt.Printf("You can make numbers right-aligned with a given width: %-5d|\n", 10)
11 | fmt.Printf("The width can be filled with 0s: %05d|\n", 10)
12 |
13 | fmt.Printf("You can use multiple arguments: %d %s %v\n", 10, "yes", true)
14 | fmt.Printf("You can refer to the same argument multiple times : %d %s %[2]s %v\n", 10, "yes", true)
15 | fmt.Printf("But if you use an index n, the next argument will be selected from n+1 : %d %s %[2]s %[1]v %v\n", 10, "yes", true)
16 | fmt.Printf("Use %%v to use the default format for the type: %v %v %v\n", 10, "yes", true)
17 | fmt.Printf("For floating point, you can specify precision: %5.2f\n", 12.345657)
18 | fmt.Printf("For floating point, you can specify precision: %5.2f\n", 12.0)
19 | type S struct {
20 | IntValue int
21 | StringValue string
22 | }
23 |
24 | s := S{
25 | IntValue: 1,
26 | StringValue: `foo "bar"`,
27 | }
28 |
29 | // Print the field values of a structure, in the order they are declared
30 | fmt.Printf("%v\n", s)
31 | // {1 foo "bar"}
32 |
33 | // Print the field names and values of a structure
34 | fmt.Printf("%+v\n", s)
35 | // {IntValue:1 StringValue:foo "bar"}
36 | }
37 |
--------------------------------------------------------------------------------
/src/chp2/template-layout/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "html/template"
5 | "os"
6 | )
7 |
8 | const layout = `
9 |
10 |
11 | {{template "pageTitle" .}}
12 |
13 |
14 | {{template "pageHeader" .}}
15 | {{template "pageBody" .}}
16 | {{template "pageFooter" .}}
17 |
18 |
19 | {{define "pageTitle"}}{{end}}
20 | {{define "pageHeader"}}{{end}}
21 | {{define "pageBody"}}{{end}}
22 | {{define "pageFooter"}}{{end}}
23 | `
24 |
25 | const mainPage = `{{define "pageTitle"}}Main Page{{end}}
26 |
27 | {{define "pageHeader"}}
28 | Main page
29 | {{end}}
30 |
31 | {{define "pageBody"}}
32 | This is the page body.
33 | {{end}}
34 |
35 | {{define "pageFooter"}}
36 | This is the page footer.
37 | {{end}}
38 | `
39 |
40 | const secondPage = `{{define "pageTitle"}}Second page{{end}}
41 |
42 | {{define "pageHeader"}}
43 | Second page
44 | {{end}}
45 |
46 | {{define "pageBody"}}
47 | This is the page body for the second page.
48 | {{end}}`
49 |
50 | func main() {
51 | mainPageTmpl := template.Must(template.New("body").Parse(layout))
52 | template.Must(mainPageTmpl.Parse(mainPage))
53 |
54 | secondPageTmpl := template.Must(template.New("body").Parse(layout))
55 | template.Must(secondPageTmpl.Parse(secondPage))
56 | mainPageTmpl.Execute(os.Stdout, nil)
57 | secondPageTmpl.Execute(os.Stdout, nil)
58 | }
59 |
--------------------------------------------------------------------------------
/src/chp15/mysql-connection/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 | "fmt"
7 | "time"
8 |
9 | // Import the mysql driver
10 | _ "github.com/go-sql-driver/mysql"
11 | )
12 |
13 | /*
14 | You can run this using mysql docker image.
15 |
16 | 1. Start a mysql container using:
17 |
18 | docker run -p 13306:3306 -e MYSQL_ROOT_PASSWORD=rootpwd -e MYSQL_DATABASE=testdb -d mysql:9.1
19 |
20 |
21 | 2. Install mysql CLI tool for your platform
22 |
23 | 3. Run:
24 |
25 | mysql -h localhost -P 13306 -p -u root --protocol=tcp
26 |
27 | Enter the root password when asked: "rootpwd"
28 |
29 | 4. Create a new user
30 |
31 | create user 'myuser'@'%' identified by 'mypassword';
32 |
33 | 5. Grant privileges to the new user
34 |
35 | grant all on testdb.* to 'myuser'@'%';
36 |
37 |
38 | 6. Run this go program
39 |
40 | */
41 |
42 | func main() {
43 | // Use mysql driver name and driver specific connection string
44 | db, err := sql.Open("mysql", "myuser:mypassword@tcp(localhost:13306)/testdb")
45 | if err != nil {
46 | panic(err.Error())
47 | }
48 | defer db.Close()
49 | // Check if database connection succeeded, with 5 second timeout
50 | ctx, cancel := context.WithTimeout(context.
51 | Background(), 5*time.Second)
52 | defer cancel()
53 | if err := db.PingContext(ctx); err != nil {
54 | panic(err)
55 | }
56 | fmt.Println("Success!")
57 | }
58 |
--------------------------------------------------------------------------------
/src/chp11/custom-marshaling/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "strconv"
8 | "strings"
9 | )
10 |
11 | var ErrInvalidTypeAndID = errors.New("invalid TypeAndID")
12 |
13 | // TypeAndID is encoded to JSON as type:id
14 | type TypeAndID struct {
15 | Type string
16 | ID int
17 | }
18 |
19 | // Implementation of json.Marshaler
20 | func (t TypeAndID) MarshalJSON() (out []byte, err error) {
21 | s := fmt.Sprintf(`"%s:%d"`, t.Type, t.ID)
22 | out = []byte(s)
23 | return
24 | }
25 |
26 | // Implementation of json.Unmarshaler. Note the pointer receiver
27 | func (t *TypeAndID) UnmarshalJSON(in []byte) (err error) {
28 | if in[0] != '"' || in[len(in)-1] != '"' {
29 | err = ErrInvalidTypeAndID
30 | return
31 | }
32 | in = in[1 : len(in)-1]
33 | parts := strings.Split(string(in), ":")
34 | if len(parts) != 2 {
35 | err = ErrInvalidTypeAndID
36 | return
37 | }
38 | // The second part must be a valid integer
39 | t.ID, err = strconv.Atoi(parts[1])
40 | if err != nil {
41 | return
42 | }
43 | t.Type = parts[0]
44 | return
45 | }
46 |
47 | func main() {
48 | data, err := json.Marshal(TypeAndID{
49 | Type: "test1",
50 | ID: 1,
51 | })
52 | if err != nil {
53 | panic(err)
54 | }
55 | fmt.Println(string(data))
56 |
57 | var tid TypeAndID
58 | if err := json.Unmarshal([]byte(`"test2:200"`), &tid); err != nil {
59 | panic(err)
60 | }
61 | fmt.Printf("%+v\n", tid)
62 | }
63 |
--------------------------------------------------------------------------------
/src/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/PacktPublishing/Go-Recipes-for-Developers/src
2 |
3 | go 1.22.1
4 |
5 | require (
6 | github.com/gorilla/mux v1.8.1
7 | golang.org/x/exp v0.0.0-20231108232855-2478ac86f678
8 | modernc.org/sqlite v1.33.1
9 | )
10 |
11 | require (
12 | filippo.io/edwards25519 v1.1.0 // indirect
13 | github.com/dustin/go-humanize v1.0.1 // indirect
14 | github.com/go-sql-driver/mysql v1.8.1 // indirect
15 | github.com/google/uuid v1.6.0 // indirect
16 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
17 | github.com/jackc/pgpassfile v1.0.0 // indirect
18 | github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
19 | github.com/jackc/pgx/v5 v5.7.1 // indirect
20 | github.com/jackc/puddle/v2 v2.2.2 // indirect
21 | github.com/mattn/go-isatty v0.0.20 // indirect
22 | github.com/mattn/go-sqlite3 v1.14.24 // indirect
23 | github.com/ncruces/go-strftime v0.1.9 // indirect
24 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
25 | golang.org/x/crypto v0.27.0 // indirect
26 | golang.org/x/sync v0.9.0 // indirect
27 | golang.org/x/sys v0.25.0 // indirect
28 | golang.org/x/text v0.20.0 // indirect
29 | modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
30 | modernc.org/libc v1.55.3 // indirect
31 | modernc.org/mathutil v1.6.0 // indirect
32 | modernc.org/memory v1.8.0 // indirect
33 | modernc.org/strutil v1.2.0 // indirect
34 | modernc.org/token v1.1.0 // indirect
35 | )
36 |
--------------------------------------------------------------------------------
/src/chp16/stdlogger/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func Logging() {
9 | log.Println("This is a log message similar to fmt.Println")
10 | log.Printf("This is a log message similar to fmt.Printf")
11 | }
12 |
13 | func ConfiguringPrefixes() {
14 | logger := log.New(log.Writer(), "prefix: ", log.LstdFlags)
15 | logger.Println("This is a log message with a prefix")
16 | logger.SetFlags(log.LstdFlags | log.Lshortfile)
17 | logger.Println("This is a log message with a prefix and file name")
18 | logger.SetFlags(log.LstdFlags | log.Llongfile)
19 | logger.Println("This is a log message with a prefix and long file name")
20 | logger.SetFlags(log.LstdFlags | log.Lshortfile | log.Lmsgprefix)
21 | logger.Println("This is a log message with a prefix moved to the beginning of the message")
22 | logger.SetFlags(log.LstdFlags | log.Lshortfile | log.LUTC)
23 | logger.Println("This is a log message with with UTC time")
24 | }
25 |
26 | func SettingOutput() {
27 | output, err := os.Create("log.txt")
28 | if err != nil {
29 | log.Fatal(err)
30 | }
31 | defer output.Close()
32 | logger := log.New(os.Stderr, "", log.LstdFlags)
33 | logger.Println("This is a log message to stderr")
34 | logger.SetOutput(output)
35 | logger.Println("This is a log message to log.txt")
36 | logger.SetOutput(os.Stderr)
37 | logger.Println("Message to log.txt was written")
38 | }
39 |
40 | func main() {
41 | Logging()
42 | ConfiguringPrefixes()
43 | SettingOutput()
44 | }
45 |
--------------------------------------------------------------------------------
/src/chp5/authenticator/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | )
7 |
8 | var ErrUnauthorized = errors.New("unauthorized")
9 | var ErrBadCredentials = errors.New("bad credentials")
10 |
11 | // Authenticator uses implementation-specific credentials to create an
12 | // implementation-specific session
13 | type Authenticator interface {
14 | Login(credentials Credentials) (Session, error)
15 | }
16 |
17 | // Credentials contains the credentials to authenticate a user to the backend
18 | type Credentials interface {
19 | Serialize() []byte
20 | Type() string
21 | }
22 |
23 | // CredentialParse implementation parses backend-specific credentials from []byte input
24 | type CredentialParser interface {
25 | Parse([]byte) (Credentials, error)
26 | }
27 |
28 | // A backend-specific session identifies the user and provides a way to close the session
29 | type Session interface {
30 | UserID() string
31 | Close()
32 | }
33 |
34 | func main() {
35 | // Create a new authenticator
36 | authenticator := NewInstance("basic")
37 |
38 | session, err := authenticator.Login(BasicAuthCredentials{
39 | Name: "john",
40 | Password: "doe",
41 | })
42 | if err != nil {
43 | panic(err)
44 | }
45 | fmt.Printf("Login john/doe, session: %+v\n", session)
46 |
47 | _, err = authenticator.Login(BasicAuthCredentials{
48 | Name: "john",
49 | Password: "password",
50 | })
51 | if err != nil {
52 | fmt.Printf("Login john/password: err: %v\n", err)
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/chp8/structured-errors/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "os"
8 | )
9 |
10 | type Config struct {
11 | Option string `json:"option"`
12 | }
13 |
14 | type ErrFile struct {
15 | Name string
16 | When string
17 | Err error
18 | }
19 |
20 | var cfg Config
21 |
22 | func (err ErrFile) Error() string {
23 | return fmt.Sprintf("%s: file %s, when %s", err.Err, err.Name, err.When)
24 | }
25 |
26 | func (err ErrFile) Is(e error) bool {
27 | _, ok := e.(ErrFile)
28 | return ok
29 | }
30 |
31 | func (err ErrFile) Unwrap() error { return err.Err }
32 |
33 | func ReadConfigFile(name string) error {
34 | f, err := os.Open(name)
35 | if err != nil {
36 | return ErrFile{
37 | Name: name,
38 | Err: err,
39 | When: "opening configuration file",
40 | }
41 | }
42 | defer f.Close()
43 | err = json.NewDecoder(f).Decode(&cfg)
44 | if err != nil {
45 | return fmt.Errorf("While unmarshaling %s: %w", name, err)
46 | }
47 | return nil
48 | }
49 |
50 | func main() {
51 | err := ReadConfigFile("nonexistant-config.json")
52 | fmt.Printf("Error: %v\n", err)
53 | var errFile ErrFile
54 | if errors.As(err, &errFile) {
55 | fmt.Printf("Errfile: %v\n", errFile)
56 | }
57 | err = ReadConfigFile("invalid-config.json")
58 | fmt.Printf("Error: %v\n", err)
59 | var syntaxError *json.SyntaxError
60 | if errors.As(err, &syntaxError) {
61 | fmt.Printf("Unwrapped syntax error: %+v\n", syntaxError)
62 | }
63 | err = ReadConfigFile("valid-config.json")
64 | fmt.Println(cfg, err)
65 | }
66 |
--------------------------------------------------------------------------------
/src/chp9/cancelation/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "sync"
7 | "time"
8 |
9 | "golang.org/x/exp/rand"
10 | )
11 |
12 | func cancelableFunc(ctx context.Context) error {
13 | for {
14 | if err := ctx.Err(); err != nil {
15 | fmt.Println("cancelableFunc canceled")
16 | return err
17 | }
18 | fmt.Println("cancelableFunc sleeping")
19 | time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
20 | }
21 | }
22 |
23 | func cancelableGoroutine1(ctx context.Context) {
24 | for {
25 | if err := ctx.Err(); err != nil {
26 | fmt.Println("cancelableGoroutine1 canceled")
27 | return
28 | }
29 | fmt.Println("cancelableGoroutine1 sleeping")
30 | time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
31 | }
32 | }
33 |
34 | func cancelableGoroutine2(ctx context.Context) {
35 | for {
36 | if err := ctx.Err(); err != nil {
37 | fmt.Println("cancelableGoroutine2 canceled")
38 | return
39 | }
40 | fmt.Println("cancelableGoroutine2 sleeping")
41 | time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
42 | }
43 | }
44 |
45 | func main() {
46 | ctx := context.Background()
47 | cancelable, cancel := context.WithCancel(ctx)
48 | defer cancel()
49 |
50 | go func() {
51 | time.Sleep(2 * time.Second)
52 | fmt.Println("Canceling...")
53 | cancel()
54 | }()
55 |
56 | wg := sync.WaitGroup{}
57 |
58 | wg.Add(1)
59 | go func() {
60 | defer wg.Done()
61 | cancelableGoroutine1(cancelable)
62 | }()
63 |
64 | wg.Add(1)
65 | go func() {
66 | defer wg.Done()
67 | cancelableGoroutine2(cancelable)
68 | }()
69 | fmt.Println(cancelableFunc(cancelable))
70 | wg.Wait()
71 | }
72 |
--------------------------------------------------------------------------------
/src/chp4/cache/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 |
7 | "golang.org/x/exp/rand"
8 | )
9 |
10 | type Object struct {
11 | ID string
12 | Name string
13 | // Other fields
14 | }
15 |
16 | type ObjectCache struct {
17 | mutex sync.RWMutex
18 | values map[string]*Object
19 | }
20 |
21 | // Initialize and return a new instance of the cache
22 | func NewObjectCache() *ObjectCache {
23 | return &ObjectCache{
24 | values: make(map[string]*Object),
25 | }
26 | }
27 |
28 | // Get an object from the cache
29 | func (cache *ObjectCache) Get(key string) (*Object, bool) {
30 | cache.mutex.RLock()
31 | obj, exists := cache.values[key]
32 | cache.mutex.RUnlock()
33 | return obj, exists
34 | }
35 |
36 | // Put an object into the cache with the given key
37 | func (cache *ObjectCache) Put(key string, value *Object) {
38 | cache.mutex.Lock()
39 | cache.values[key] = value
40 | cache.mutex.Unlock()
41 | }
42 |
43 | func main() {
44 | cache := NewObjectCache()
45 | objects := make([]Object, 0)
46 | // Create some objects
47 | for i := 0; i < 1000; i++ {
48 | objects = append(objects, Object{
49 | ID: fmt.Sprint(i),
50 | Name: fmt.Sprintf("Name: %d", i),
51 | })
52 | }
53 |
54 | // Ten goroutines add objects to the cache randomly
55 | wg := sync.WaitGroup{}
56 | for i := 0; i < 10; i++ {
57 | wg.Add(1)
58 | go func() {
59 | defer wg.Done()
60 | for i := 0; i < 1000; i++ {
61 | // Pick a random object and add it to the cache
62 | n := rand.Intn(len(objects))
63 | cache.Put(objects[n].ID, &objects[n])
64 | }
65 | }()
66 | }
67 | wg.Wait()
68 | fmt.Printf("Cache has %d objects\n", len(cache.values))
69 | }
70 |
--------------------------------------------------------------------------------
/src/chp13/timeout/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io"
7 | "net"
8 | "os"
9 | "time"
10 | )
11 |
12 | func main() {
13 | // Create a TCP listener
14 | listener, err := net.Listen("tcp", ":0")
15 | if err != nil {
16 | panic(err)
17 | }
18 | fmt.Println("Listening on ", listener.Addr())
19 | defer listener.Close()
20 |
21 | // Start server
22 | go func() {
23 | // Listen to incoming TCP connections
24 | for {
25 | conn, err := listener.Accept()
26 | if err != nil {
27 | fmt.Println(err)
28 | return
29 | }
30 | go handleConnectionWithTimeout(conn)
31 | }
32 | }()
33 |
34 | // Connect to server
35 | clientConn, err := net.Dial("tcp", listener.Addr().String())
36 | if err != nil {
37 | panic(err)
38 | }
39 |
40 | // Write data
41 | clientConn.Write([]byte("Hello server!"))
42 | data := make([]byte, 1024)
43 | n, err := clientConn.Read(data)
44 | if err != nil {
45 | panic(err)
46 | }
47 | fmt.Printf("Read %d bytes: %s\n", n, string(data[:n]))
48 | // Trigger timeout
49 | time.Sleep(3 * time.Second)
50 |
51 | clientConn.Write([]byte("Hello server after timeout!"))
52 | n, err = clientConn.Read(data)
53 | if err != nil {
54 | panic(err)
55 | }
56 | fmt.Printf("Read %d bytes: %s\n", n, string(data[:n]))
57 |
58 | }
59 |
60 | func handleConnectionWithTimeout(conn net.Conn) {
61 | for {
62 | // Set a 1 second read timeout
63 | conn.SetReadDeadline(time.Now().Add(time.Second))
64 | _, err := io.Copy(conn, conn)
65 | if err != nil {
66 | if errors.Is(err, os.ErrDeadlineExceeded) {
67 | fmt.Println("Read timeout, restarting")
68 | } else {
69 | return
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/chp6/orderedmap/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | // An OrderedMap maps keys of type Key to values of type Value while
9 | // maintaining insertion order
10 | type OrderedMap[Key comparable, Value any] struct {
11 | m map[Key]Value
12 | slice []Key
13 | }
14 |
15 | // NewOrderedMap returns a new ordere map with key type Key and value
16 | // type Value
17 | func NewOrderedMap[Key comparable, Value any]() *OrderedMap[Key, Value] {
18 | return &OrderedMap[Key, Value]{
19 | m: make(map[Key]Value),
20 | slice: make([]Key, 0),
21 | }
22 | }
23 |
24 | // Add key:value to the map
25 | func (m *OrderedMap[Key, Value]) Add(key Key, value Value) {
26 | _, exists := m.m[key]
27 | if exists {
28 | m.m[key] = value
29 | } else {
30 | m.slice = append(m.slice, key)
31 | m.m[key] = value
32 | }
33 | }
34 |
35 | // ValueAt returns the value at the given index
36 | func (m *OrderedMap[Key, Value]) ValueAt(index int) Value {
37 | return m.m[m.slice[index]]
38 | }
39 |
40 | // KeyAt returns the key at the given index
41 | func (m *OrderedMap[Key, Value]) KeyAt(index int) Key {
42 | return m.slice[index]
43 | }
44 |
45 | // Get returns the value corresponding to the key, and whether or not
46 | // key exists
47 | func (m *OrderedMap[Key, Value]) Get(key Key) (Value, bool) {
48 | v, bool := m.m[key]
49 | return v, bool
50 | }
51 |
52 | func (m *OrderedMap[Key, Value]) Len() int { return len(m.slice) }
53 |
54 | func main() {
55 | m := NewOrderedMap[int, string]()
56 | for i, s := range strings.Split("This container preserves the insertion order", " ") {
57 | m.Add(i, s)
58 | }
59 | for i := 0; i < m.Len(); i++ {
60 | fmt.Println(m.ValueAt(i))
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/chp8/chain/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | )
7 |
8 | type ErrSyntax struct {
9 | Line int
10 | Col int
11 | Err error
12 | }
13 |
14 | func (err ErrSyntax) Error() string {
15 | return fmt.Sprintf("syntax error at line %d col %d: %s", err.Line, err.Col, err.Err.Error())
16 | }
17 |
18 | func (err ErrSyntax) Is(e error) bool {
19 | _, ok := e.(ErrSyntax)
20 | return ok
21 | }
22 |
23 | func (err ErrSyntax) As(target any) bool {
24 | if tgt, ok := target.(*ErrSyntax); ok {
25 | *tgt = err
26 | return true
27 | }
28 | return false
29 | }
30 |
31 | // Simulate an error return, unwrapped
32 | func Parse1() error {
33 | return ErrSyntax{
34 | Line: 1,
35 | Col: 2,
36 | Err: errors.New("simulated error"),
37 | }
38 | }
39 |
40 | // Simulate an error return, wrapped once
41 | func Parse2() error {
42 | return fmt.Errorf("Error wrapped once: %w", Parse1())
43 | }
44 |
45 | // Simulate an error return, wrapped twice
46 | func Parse3() error {
47 | return fmt.Errorf("Error wrapped twice: %w", Parse2())
48 | }
49 |
50 | func main() {
51 | // The base error
52 | err := Parse1()
53 | fmt.Printf("Base error is syntax error: %t\n", errors.Is(err, ErrSyntax{}))
54 | var syntax ErrSyntax
55 | if errors.As(err, &syntax) {
56 | fmt.Printf("Syntax error: %+v\n", syntax)
57 | }
58 | err = Parse2()
59 | fmt.Printf("Wrapped once, error type: %T, base error is syntax error: %t\n", err, errors.Is(err, ErrSyntax{}))
60 | if errors.As(err, &syntax) {
61 | fmt.Printf("Syntax error: %+v\n", syntax)
62 | }
63 | err = Parse3()
64 | fmt.Printf("Wrapped twice, error type: %T, base error is syntax error: %t\n", err, errors.Is(err, ErrSyntax{}))
65 | if errors.As(err, &syntax) {
66 | fmt.Printf("Syntax error: %+v\n", syntax)
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/chp11/missing-fields/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io"
7 | "net/http"
8 | "strings"
9 | )
10 |
11 | type APIRequest struct {
12 | // If type is not specified, it will be nil
13 | Type *string `json:"type"`
14 | // There will be a default value for seq
15 | Seq int `json:"seq"`
16 | }
17 |
18 | func handler(w http.ResponseWriter, r *http.Request) {
19 | data, err := io.ReadAll(r.Body)
20 | if err != nil {
21 | http.Error(w, "Bad request", http.StatusBadRequest)
22 | return
23 | }
24 | req := APIRequest{
25 | Seq: 1, // Set the default value
26 | }
27 | if err := json.Unmarshal(data, &req); err != nil {
28 | http.Error(w, "Bad request", http.StatusBadRequest)
29 | return
30 | }
31 | // Check which fields are provided
32 | if req.Type != nil {
33 | fmt.Println("Type specified", *req.Type)
34 | } else {
35 | fmt.Println("No type was specified")
36 | }
37 |
38 | // If seq is provided in the input, req.Seq will be set to that value. Otherwise, it will be 1.
39 | if req.Seq == 1 {
40 | fmt.Println("Sequence", 1)
41 | } else {
42 | fmt.Println("Sequence", req.Seq)
43 | }
44 | }
45 |
46 | func main() {
47 | // Simulate request handling: empty body
48 | req, err := http.NewRequest(http.MethodPost, "localhost", strings.NewReader(`{}`))
49 | if err != nil {
50 | panic(err)
51 | }
52 | handler(nil, req)
53 |
54 | // Body with type specified
55 | req, err = http.NewRequest(http.MethodPost, "localhost", strings.NewReader(`{"type":"test1"}`))
56 | if err != nil {
57 | panic(err)
58 | }
59 | handler(nil, req)
60 |
61 | // Body with type and sequence specified
62 | req, err = http.NewRequest(http.MethodPost, "localhost", strings.NewReader(`{"type":"test2", "seq":100}`))
63 | if err != nil {
64 | panic(err)
65 | }
66 | handler(nil, req)
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/chp13/echo-client-server-lines/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "flag"
6 | "fmt"
7 | "io"
8 | "net"
9 | )
10 |
11 | var address = flag.String("a", ":8008", "Address to listen")
12 |
13 | func main() {
14 | flag.Parse()
15 | // Create a TCP listener
16 | listener, err := net.Listen("tcp", *address)
17 | if err != nil {
18 | panic(err)
19 | }
20 | fmt.Println("Listening on ", listener.Addr())
21 | defer listener.Close()
22 | // Listen to incoming TCP connections
23 | for {
24 | conn, err := listener.Accept()
25 | if err != nil {
26 | fmt.Println(err)
27 | return
28 | }
29 | go func() {
30 | err := handleConnection(conn)
31 | if err != nil {
32 | fmt.Println(err)
33 | }
34 | }()
35 | }
36 | }
37 |
38 | // Limit line length to 1K.
39 | const MaxLineLength = 1024
40 |
41 | func handleConnection(conn net.Conn) error {
42 | defer conn.Close()
43 | // Wrap the connection with a limited reader
44 | // to prevent the client from sending unbounded
45 | // amount of data
46 | limiter := &io.LimitedReader{
47 | R: conn,
48 | N: MaxLineLength + 1, // Read one extra byte to detect long lines
49 | }
50 | reader := bufio.NewReader(limiter)
51 | for {
52 | bytes, err := reader.ReadBytes(byte('\n'))
53 | if err != nil {
54 | if err != io.EOF {
55 | // Some error other than end-of-stream
56 | return err
57 | }
58 | // End of stream. It could be because the line is too long
59 | if limiter.N == 0 {
60 | // Line was too long
61 | return fmt.Errorf("Received a line that is too long")
62 | }
63 | // End of stream
64 | return nil
65 | }
66 | // Reset the limiter, so the next line can be read with
67 | // newlimit
68 | limiter.N = MaxLineLength + 1
69 | // Process the line
70 | if _, err := conn.Write(bytes); err != nil {
71 | return err
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/chp7/concurrent-download/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "log"
6 | "net/http"
7 | "os"
8 | "path"
9 | "sync"
10 | )
11 |
12 | // This example demostrates managing multiple independent
13 | // goroutines. A wait group is used to wait for all goroutines to
14 | // end. Each goroutine downloads a file and stores it in the file
15 | // system. There are no dependencies or communications between
16 | // goroutines.
17 | func main() {
18 | urls := []string{
19 | "https://pkg.go.dev/bufio",
20 | "https://pkg.go.dev/builtin",
21 | "https://pkg.go.dev/bytes",
22 | "https://pkg.go.dev/cmp",
23 | "https://pkg.go.dev/context",
24 | "https://pkg.go.dev/crypto",
25 | "https://pkg.go.dev/embed",
26 | "https://pkg.go.dev/encoding",
27 | "https://pkg.go.dev/errors",
28 | "https://pkg.go.dev/expvar",
29 | "https://pkg.go.dev/flag",
30 | "https://pkg.go.dev/hash",
31 | "https://pkg.go.dev/log",
32 | "https://pkg.go.dev/plugin",
33 | }
34 |
35 | wg := sync.WaitGroup{}
36 |
37 | for _, u := range urls {
38 | wg.Add(1)
39 | go func(downloadURL string) {
40 | defer wg.Done()
41 |
42 | rsp, err := http.Get(downloadURL)
43 | if err != nil {
44 | log.Printf("Cannot download %s: %s", downloadURL, err)
45 | return
46 | }
47 | defer rsp.Body.Close()
48 | if rsp.StatusCode != 200 {
49 | log.Printf("Cannot download %s: %s", downloadURL, rsp.Status)
50 | return
51 | }
52 | fname := path.Base(rsp.Request.URL.Path)
53 | file, err := os.Create(fname)
54 | if err != nil {
55 | log.Printf("Cannot write file %s: %s", fname, err)
56 | return
57 | }
58 | defer file.Close()
59 | if _, err := io.Copy(file, rsp.Body); err != nil {
60 | log.Printf("Cannot read %s: %s", downloadURL, err)
61 | return
62 | }
63 | }(u)
64 | }
65 | // Wait for all goroutines to end
66 | wg.Wait()
67 | }
68 |
--------------------------------------------------------------------------------
/src/chp13/tcp-file-transfer/receiver/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/binary"
5 | "flag"
6 | "fmt"
7 | "io"
8 | "net"
9 | "os"
10 | "path/filepath"
11 | )
12 |
13 | var address = flag.String("a", ":8008", "Address to listen")
14 |
15 | type fileMetadata struct {
16 | Size uint64
17 | Mode uint32
18 | NameLen uint16
19 | }
20 |
21 | func main() {
22 | flag.Parse()
23 | os.Mkdir("downloads", 0770)
24 | // Create a TCP listener
25 | listener, err := net.Listen("tcp", *address)
26 | if err != nil {
27 | panic(err)
28 | }
29 | defer listener.Close()
30 | fmt.Println("Listening on ", listener.Addr())
31 | // Listen to incoming TCP connections
32 | for {
33 | conn, err := listener.Accept()
34 | if err != nil {
35 | fmt.Println(err)
36 | return
37 | }
38 | go handleConnection(conn)
39 | }
40 | }
41 |
42 | func handleConnection(conn net.Conn) {
43 | defer conn.Close()
44 | // Read the file metadata
45 | var meta fileMetadata
46 | err := binary.Read(conn, binary.LittleEndian, &meta)
47 | if err != nil {
48 | fmt.Println(err)
49 | return
50 | }
51 | if meta.NameLen > 255 {
52 | fmt.Println("File name too long")
53 | return
54 | }
55 | // Read the file name
56 | name := make([]byte, meta.NameLen)
57 | _, err = io.ReadFull(conn, name)
58 | if err != nil {
59 | fmt.Println(err)
60 | return
61 | }
62 | path := filepath.Join("downloads", string(name))
63 | // Create the file
64 | file, err := os.OpenFile(
65 | path,
66 | os.O_CREATE|os.O_WRONLY,
67 | os.FileMode(meta.Mode),
68 | )
69 | if err != nil {
70 | fmt.Println(err)
71 | return
72 | }
73 | defer file.Close()
74 | // Copy the file contents
75 | _, err = io.CopyN(file, conn, int64(meta.Size))
76 | if err != nil {
77 | os.Remove(path)
78 | fmt.Println(err)
79 | return
80 | }
81 | fmt.Printf("Received file %s: %d bytes\n", string(name), meta.Size)
82 | }
83 |
--------------------------------------------------------------------------------
/src/chp14/binary/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "fmt"
7 | )
8 |
9 | type Data struct {
10 | IntValue int64
11 | BoolValue bool
12 | ArrayValue [2]int64
13 | }
14 |
15 | func EncodeString(s string) []byte {
16 | // Allocate the output buffer for string length (int16) + len(string)
17 | buffer := make([]byte, len(s)+2)
18 | // Encode the length little endian - 2 bytes
19 | binary.LittleEndian.PutUint16(buffer, uint16(len(s)))
20 | // Copy the string bytes
21 | copy(buffer[2:], []byte(s))
22 | return buffer
23 | }
24 |
25 | func DecodeString(input []byte) (string, error) {
26 | // Read the string length. It must be at least 2 bytes
27 | if len(input) < 2 {
28 | return "", fmt.Errorf("invalid input")
29 | }
30 | n := binary.LittleEndian.Uint16(input)
31 | if int(n)+2 > len(input) {
32 | return "", fmt.Errorf("invalid input")
33 | }
34 | return string(input[2 : n+2]), nil
35 | }
36 |
37 | func main() {
38 | output := bytes.Buffer{}
39 | data := Data{
40 | IntValue: 1,
41 | BoolValue: true,
42 | ArrayValue: [2]int64{1, 2},
43 | }
44 | binary.Write(&output, binary.BigEndian, data)
45 | stream := output.Bytes()
46 | fmt.Printf("Big endian encoded data : %v\n", stream)
47 | var value1 Data
48 | binary.Read(bytes.NewReader(stream), binary.BigEndian, &value1)
49 | fmt.Printf("Decoded data: %v\n", value1)
50 |
51 | output = bytes.Buffer{}
52 | binary.Write(&output, binary.LittleEndian, data)
53 | stream = output.Bytes()
54 | fmt.Printf("Little endian encoded data: %v\n", stream)
55 | var value2 Data
56 | binary.Read(bytes.NewReader(stream), binary.LittleEndian, &value2)
57 | fmt.Printf("Decoded data: %v\n", value2)
58 |
59 | encodedString := EncodeString("Hello world!")
60 | fmt.Printf("Encoded string: %v\n", encodedString)
61 | decodedString, _ := DecodeString(encodedString)
62 | fmt.Printf("Decoded string: %s\n", decodedString)
63 | }
64 |
--------------------------------------------------------------------------------
/src/chp6/adapters/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "regexp"
6 |
7 | "golang.org/x/exp/constraints"
8 | )
9 |
10 | // ToPtr returns a pointer *T pointing to the given value
11 | func ToPtr[T any](value T) *T {
12 | return &value
13 | }
14 |
15 | // ToSlice returns a slice []T{value}
16 | func ToSlice[T any](value T) []T {
17 | return []T{value}
18 | }
19 |
20 | // SliceOf returns []T containing values
21 | func SliceOf[T any](values ...T) []T {
22 | return values
23 | }
24 |
25 | type Number interface {
26 | constraints.Float | constraints.Integer
27 | }
28 |
29 | // ConvertNumberSlice converts a number slice to another type of a
30 | // number slice
31 | func ConvertNumberSlice[S, T Number](source []S) []T {
32 | result := make([]T, len(source))
33 | for i, v := range source {
34 | result[i] = T(v)
35 | }
36 | return result
37 | }
38 |
39 | // Last returns the last element of a slice, and whether or not that
40 | // element exists
41 | func Last[T any](slice []T) (T, bool) {
42 | if len(slice) == 0 {
43 | var zero T
44 | return zero, false
45 | }
46 | return slice[len(slice)-1], true
47 | }
48 |
49 | // Must returns value if there is no error, panics otherwise
50 | func Must[T any](value T, err error) T {
51 | if err != nil {
52 | panic(err)
53 | }
54 | return value
55 | }
56 |
57 | func main() {
58 | var x *int
59 | x = ToPtr(1)
60 | fmt.Println("x = ToPtr(1)", x, *x)
61 | fmt.Println("ToSlice(1)", ToSlice(1))
62 | fmt.Println("ToSlice(str)", ToSlice("str"))
63 | fmt.Println("SliceOf(1,2,3)", SliceOf(1, 2, 3))
64 | fmt.Println("ConvertNumberSlice[float64, int]([]float64{1.1, 0.2, 33.41}))", ConvertNumberSlice[float64, int]([]float64{1.1, 0.2, 33.41}))
65 | v, ok := Last([]int{1, 2, 3, 4, 5})
66 | fmt.Println("Last([]int{1, 2, 3, 4, 5})", v, ok)
67 | v, ok = Last([]int{})
68 | fmt.Println("Last([]int{})", v, ok)
69 | fmt.Println("Must(regexp.Compile(test))", Must(regexp.Compile("test")))
70 | }
71 |
--------------------------------------------------------------------------------
/src/chp4/slices/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "slices"
6 | )
7 |
8 | type Stack[T any] []T
9 |
10 | func (s *Stack[T]) Push(val T) {
11 | *s = append(*s, val)
12 | }
13 |
14 | func (s *Stack[T]) Pop() (val T) {
15 | val = (*s)[len(*s)-1]
16 | *s = (*s)[:len(*s)-1]
17 | return
18 | }
19 |
20 | func main() {
21 | // Create an empty integer slice
22 | islice := make([]int, 0)
23 | // Append values 1, 2, 3 to islice, assign it to newSlice
24 | newSlice := append(islice, 1, 2, 3)
25 | fmt.Println(islice)
26 | fmt.Println(newSlice)
27 |
28 | // Create an empty integer slice
29 | islice = make([]int, 0)
30 | // Another integer slice with 3 elements
31 | otherSlice := []int{1, 2, 3}
32 | // Append 'otherSlice' to 'islice'
33 | newSlice = append(islice, otherSlice...)
34 | // Append 'otherSlice' to 'newSlice
35 | newSlice = append(newSlice, otherSlice...)
36 | fmt.Println(newSlice)
37 | fmt.Println(islice)
38 |
39 | slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
40 | fmt.Println(slice, cap(slice))
41 | suffix := slice[1:]
42 | fmt.Println(suffix)
43 | suffix2 := slice[3:]
44 | fmt.Println(suffix2)
45 | prefix := slice[:5]
46 | fmt.Println(prefix)
47 |
48 | mid := slice[3:6]
49 | fmt.Println(mid)
50 |
51 | edges := slices.Delete(slice, 3, 7)
52 | fmt.Println(edges)
53 | fmt.Println(slice)
54 | inserted := slices.Insert(slice, 3, 3, 4)
55 | fmt.Println(inserted)
56 | fmt.Println(edges)
57 | fmt.Println(slice)
58 |
59 | slice = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
60 | // Keep two indexes, one to read from, one to write to
61 | write := 0
62 | for _, elem := range slice {
63 | if elem%2 == 0 { // Copy onle even numbers
64 | slice[write] = elem
65 | write++
66 | }
67 | }
68 | // Truncate the slice
69 | slice = slice[:write]
70 | fmt.Println(slice)
71 |
72 | intStack := Stack[int]{}
73 | intStack.Push(1)
74 | fmt.Println(intStack)
75 | fmt.Println(intStack.Pop())
76 | fmt.Println(intStack)
77 | }
78 |
--------------------------------------------------------------------------------
/src/chp14/seek/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | )
8 |
9 | func main() {
10 | // Create a new file
11 | file, err := os.Create("test.txt")
12 | if err != nil {
13 | panic(err)
14 | }
15 |
16 | // Write to the file
17 | data := []byte("Hello, World!")
18 | count, err := file.Write(data)
19 | if err != nil {
20 | panic(err)
21 | }
22 | fmt.Printf("Wrote %d bytes\n", count)
23 | fsize, err := file.Seek(0, io.SeekEnd)
24 | if err != nil {
25 | panic(err)
26 | }
27 | fmt.Printf("File size: %d\n", fsize)
28 |
29 | // Set file size to 1000
30 | err = file.Truncate(1000)
31 | if err != nil {
32 | panic(err)
33 | }
34 | fsize, err = file.Seek(0, io.SeekEnd)
35 | if err != nil {
36 | panic(err)
37 | }
38 | fmt.Printf("File size: %d\n", fsize)
39 |
40 | // Move to offset `count`
41 | _, err = file.Seek(int64(count), io.SeekStart)
42 | if err != nil {
43 | panic(err)
44 | }
45 | // Write to the file
46 | _, err = file.Write([]byte("\n"))
47 | if err != nil {
48 | panic(err)
49 | }
50 | // Seek beyond file end
51 | _, err = file.Seek(2000, io.SeekStart)
52 | if err != nil {
53 | panic(err)
54 | }
55 | // Write to the file
56 | _, err = file.Write([]byte{0})
57 | if err != nil {
58 | panic(err)
59 | }
60 | fsize, err = file.Seek(0, io.SeekEnd)
61 | if err != nil {
62 | panic(err)
63 | }
64 | fmt.Printf("File size: %d\n", fsize)
65 |
66 | _, err = file.WriteAt([]byte("Hello, World!"), 10)
67 | if err != nil {
68 | panic(err)
69 | }
70 | offset, err := file.Seek(0, io.SeekCurrent)
71 | if err != nil {
72 | panic(err)
73 | }
74 | fmt.Printf("After WriteAt position is: %d\n", offset)
75 |
76 | buffer := make([]byte, 5)
77 | file.ReadAt(buffer, 10)
78 | fmt.Println("ReadAt 10:", string(buffer))
79 |
80 | file.Close()
81 |
82 | // Truncate open file
83 | file, err = os.OpenFile("test.txt", os.O_RDWR|os.O_TRUNC, 0o644)
84 | if err != nil {
85 | panic(err)
86 | }
87 | file.Close()
88 | }
89 |
--------------------------------------------------------------------------------
/src/chp11/polymorphic-unmarshal/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto"
5 | "crypto/ed25519"
6 | "crypto/rsa"
7 | "encoding/json"
8 | "errors"
9 | "fmt"
10 | )
11 |
12 | type KeyType string
13 |
14 | const (
15 | KeyTypeRSA = "rsa"
16 | KeyTypeED25519 = "ed25519"
17 | )
18 |
19 | type Key struct {
20 | Type KeyType `json:"type"`
21 | Key crypto.PublicKey `json:"key"`
22 | }
23 |
24 | var (
25 | KeyUnmarshalers = map[KeyType]func(json.RawMessage) (crypto.PublicKey, error){}
26 | )
27 |
28 | func RegisterKeyUnmarshaler(keyType KeyType, unmarshaler func(json.RawMessage) (crypto.PublicKey, error)) {
29 | KeyUnmarshalers[keyType] = unmarshaler
30 | }
31 |
32 | var ErrInvalidKeyType = errors.New("Invalid key type")
33 |
34 | func (k *Key) UnmarshalJSON(in []byte) error {
35 | type keyUnmarshal struct {
36 | Type KeyType `json:"type"`
37 | Key json.RawMessage `json:"key"`
38 | }
39 |
40 | var key keyUnmarshal
41 | err := json.Unmarshal(in, &key)
42 | if err != nil {
43 | return err
44 | }
45 | k.Type = key.Type
46 | unmarshaler := KeyUnmarshalers[key.Type]
47 | if unmarshaler == nil {
48 | return ErrInvalidKeyType
49 | }
50 | k.Key, err = unmarshaler(key.Key)
51 | if err != nil {
52 | return err
53 | }
54 | return nil
55 | }
56 |
57 | func main() {
58 | RegisterKeyUnmarshaler(KeyTypeRSA, func(in json.RawMessage) (crypto.PublicKey, error) {
59 | var key rsa.PublicKey
60 | if err := json.Unmarshal(in, &key); err != nil {
61 | return nil, err
62 | }
63 | return &key, nil
64 | })
65 | RegisterKeyUnmarshaler(KeyTypeED25519, func(in json.RawMessage) (crypto.PublicKey, error) {
66 | var key ed25519.PublicKey
67 | if err := json.Unmarshal(in, &key); err != nil {
68 | return nil, err
69 | }
70 | return &key, nil
71 | })
72 |
73 | input := []byte(`[
74 | {
75 | "type": "rsa",
76 | "key": {"N": 123,"E":456}
77 | },
78 | {
79 | "type": "ed25519",
80 | "key": [0,0,0,0,0]
81 | }
82 | ]`)
83 | var keys []Key
84 | if err := json.Unmarshal(input, &keys); err != nil {
85 | panic(err)
86 | }
87 |
88 | fmt.Println(keys)
89 | }
90 |
--------------------------------------------------------------------------------
/src/chp11/stream/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "strings"
9 | )
10 |
11 | type Data struct {
12 | Value int `json:"value"`
13 | }
14 |
15 | func generate(nItems int) <-chan Data {
16 | ch := make(chan Data)
17 | go func() {
18 | for i := 0; i < nItems; i++ {
19 | ch <- Data{Value: i}
20 | }
21 | close(ch)
22 | }()
23 | return ch
24 | }
25 |
26 | func stream(out io.Writer, input <-chan Data) error {
27 | enc := json.NewEncoder(out)
28 | if _, err := out.Write([]byte{'['}); err != nil {
29 | return err
30 | }
31 | first := true
32 | for obj := range input {
33 | if first {
34 | first = false
35 | } else {
36 | if _, err := out.Write([]byte{','}); err != nil {
37 | return err
38 | }
39 | }
40 | if err := enc.Encode(obj); err != nil {
41 | return err
42 | }
43 | }
44 |
45 | if _, err := out.Write([]byte{']'}); err != nil {
46 | return err
47 | }
48 | return nil
49 | }
50 |
51 | func parse(input *json.Decoder) (output []Data, err error) {
52 | // Parse the array beginning delimiter
53 | var tok json.Token
54 | tok, err = input.Token()
55 | if err != nil {
56 | return
57 | }
58 | if tok != json.Delim('[') {
59 | err = fmt.Errorf("Array begin delimiter expected")
60 | return
61 | }
62 | // Parse array elements using Decode
63 | for {
64 | var data Data
65 | err = input.Decode(&data)
66 | if err != nil {
67 | // Decode failed. Either there is an input error, or
68 | // we are at the end of the stream
69 | tok, err = input.Token()
70 | if err != nil {
71 | // Data error
72 | return
73 | }
74 | // Are we at the end?
75 | if tok == json.Delim(']') {
76 | // Yes, there is no error
77 | err = nil
78 | break
79 | }
80 | }
81 | output = append(output, data)
82 | }
83 | return
84 | }
85 |
86 | func main() {
87 | buf := bytes.Buffer{}
88 | ch := generate(100)
89 | stream(&buf, ch)
90 | str := buf.String()
91 | fmt.Println(str)
92 | decoder := json.NewDecoder(strings.NewReader(str))
93 | result, err := parse(decoder)
94 | fmt.Println(result, err)
95 | }
96 |
--------------------------------------------------------------------------------
/src/chp10/workerpool-dynamic/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | "time"
7 | )
8 |
9 | type Input struct {
10 | Value int
11 | }
12 |
13 | type Output struct {
14 | Value string
15 | }
16 |
17 | func doWork(input Input) Output {
18 | // Do some work
19 | time.Sleep(100 * time.Millisecond)
20 | return Output{
21 | Value: fmt.Sprint(input.Value),
22 | }
23 | }
24 |
25 | var inputCnt = 0
26 |
27 | const maxPoolSize = 50
28 |
29 | const maxInput = 5000
30 |
31 | func getNextInput() (Input, bool) {
32 | if inputCnt >= maxInput {
33 | return Input{}, true
34 | }
35 | inputCnt++
36 | return Input{
37 | Value: inputCnt,
38 | }, false
39 | }
40 |
41 | func main() {
42 | inputCnt = 0
43 | // Receive outputs from the pool via outputCh
44 | outputCh := make(chan Output)
45 | // A semaphore to limit the pool size
46 | sem := make(chan struct{}, maxPoolSize)
47 |
48 | // Reader goroutine reads results until outputCh is closed
49 | readerWg := sync.WaitGroup{}
50 | readerWg.Add(1)
51 | go func() {
52 | defer readerWg.Done()
53 | for result := range outputCh {
54 | // process result
55 | fmt.Println(result)
56 | }
57 | }()
58 |
59 | // Create the workers as needed, but the number of active workers
60 | // are limited by the capacity of sem
61 | wg := sync.WaitGroup{}
62 | // This loop sends the inputs to workers, creating them as necessary
63 | for {
64 | nextInput, done := getNextInput()
65 | if done {
66 | break
67 | }
68 | wg.Add(1)
69 | // This will block if there are too many goroutines
70 | sem <- struct{}{}
71 | go func(inp Input) {
72 | defer wg.Done()
73 | defer func() {
74 | <-sem
75 | }()
76 | outputCh <- doWork(inp)
77 | }(nextInput)
78 | }
79 |
80 | // This goroutine waits until all worker pool goroutines are done, then
81 | // closes the output channel
82 | go func() {
83 | // Wait until processing is complete
84 | wg.Wait()
85 | // Close the output channel so the reader goroutine can terminate
86 | close(outputCh)
87 | }()
88 |
89 | // Wait until the output channel is closed
90 | readerWg.Wait()
91 | // If we are here, all goroutines are done
92 | }
93 |
--------------------------------------------------------------------------------
/src/chp13/tls/proxy/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/tls"
5 | "flag"
6 | "fmt"
7 | "io"
8 | "net"
9 | "strings"
10 | )
11 |
12 | var (
13 | tlsAddress = flag.String("a", ":4433", "TLS address to listen")
14 | serverAddresses = flag.String("s", ":8080", "Server addresses, comma separated")
15 | certificate = flag.String("c", "../server.crt", "Certificate file")
16 | key = flag.String("k", "../privatekey.pem", "Private key")
17 | )
18 |
19 | func main() {
20 | flag.Parse()
21 |
22 | // Load the key pair
23 | cer, err := tls.LoadX509KeyPair(*certificate, *key)
24 | if err != nil {
25 | panic(err)
26 | }
27 | // Create TLS configuration for the listener
28 | config := &tls.Config{
29 | Certificates: []tls.Certificate{cer},
30 | }
31 | // Create the tls listener
32 | tlsListener, err := tls.Listen("tcp", *tlsAddress, config)
33 | if err != nil {
34 | panic(err)
35 | }
36 | defer tlsListener.Close()
37 | fmt.Println("Listening TLS on ", tlsListener.Addr())
38 |
39 | // Listen to incoming TLS connections
40 | servers := strings.Split(*serverAddresses, ",")
41 | fmt.Println("Forwarding to servers: ", servers)
42 |
43 | // Listen to incoming TLS connections
44 | nextServer := 0
45 | for {
46 | conn, err := tlsListener.Accept()
47 | if err != nil {
48 | fmt.Println(err)
49 | return
50 | }
51 | retries := 0
52 | for {
53 | // Select the next server
54 | server := servers[nextServer]
55 | nextServer++
56 | if nextServer >= len(servers) {
57 | nextServer = 0
58 | }
59 | // Start a connection to this server
60 | targetConn, err := net.Dial("tcp", server)
61 | if err != nil {
62 | retries++
63 | fmt.Errorf("Cannot connect to %s", server)
64 | if retries > len(servers) {
65 | panic("None of the servers are available")
66 | }
67 | continue
68 | }
69 | // Start the proxy
70 | go handleProxy(conn, targetConn)
71 | }
72 | }
73 | }
74 |
75 | func handleProxy(conn, targetConn net.Conn) {
76 | defer conn.Close()
77 | defer targetConn.Close()
78 | // Copy data from the client to the server
79 | go io.Copy(targetConn, conn)
80 | // Copy data from the server to the client
81 | io.Copy(conn, targetConn)
82 | }
83 |
--------------------------------------------------------------------------------
/src/chp13/http-upload/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io"
7 | "net/http"
8 | "net/url"
9 | "os"
10 | "path/filepath"
11 | )
12 |
13 | const form = `
14 |
15 |
16 |
17 |
18 |
19 |
24 |
25 |
26 | `
27 |
28 | func handleUpload(w http.ResponseWriter, request *http.Request) {
29 | reader, err := request.MultipartReader()
30 | if err != nil {
31 | http.Error(w, "Not a multipart request", http.StatusBadRequest)
32 | return
33 | }
34 | for {
35 | part, err := reader.NextPart()
36 | if errors.Is(err, io.EOF) {
37 | break
38 | }
39 | if err != nil {
40 | http.Error(w, err.Error(), http.StatusBadRequest)
41 | return
42 | }
43 | formValues := make(url.Values)
44 | if fileName := part.FileName(); fileName != "" {
45 | // This part contains a file
46 | output, err := os.Create(filepath.Join("uploads", fileName))
47 | if err != nil {
48 | http.Error(w, err.Error(), http.StatusInternalServerError)
49 | return
50 | }
51 | defer output.Close()
52 | fmt.Println("Uploading file", fileName)
53 | if _, err := io.Copy(output, part); err != nil {
54 | http.Error(w, err.Error(), http.StatusInternalServerError)
55 | return
56 | }
57 | } else if fieldName := part.FormName(); fieldName != "" {
58 | // This part contains form data for an input field
59 | data, err := io.ReadAll(part)
60 | if err != nil {
61 | // Handle error
62 | http.Error(w, err.Error(), http.StatusInternalServerError)
63 | return
64 | }
65 | formValues[fieldName] = append(formValues[fieldName],
66 | string(data))
67 | }
68 | }
69 | http.Redirect(w, request, "/upload.html", http.StatusFound)
70 | }
71 |
72 | func main() {
73 | os.Mkdir("uploads", 0o775)
74 | mux := http.NewServeMux()
75 | mux.HandleFunc("POST /upload", handleUpload)
76 | mux.HandleFunc("GET /upload.html", func(w http.ResponseWriter, _ *http.Request) {
77 | w.Header().Set("Content-Type", "text/html")
78 | w.Write([]byte(form))
79 | })
80 | server := http.Server{
81 | Addr: ":8081",
82 | Handler: mux,
83 | }
84 | fmt.Println("Go to http://localhost:8081/upload.html")
85 | server.ListenAndServe()
86 | }
87 |
--------------------------------------------------------------------------------
/src/chp4/blockingcache/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "sync"
7 |
8 | "golang.org/x/exp/rand"
9 | )
10 |
11 | type cacheItem struct {
12 | sync.Once
13 | object *Object
14 | }
15 |
16 | type Object struct {
17 | ID string
18 | Name string
19 | // Other fields
20 | }
21 |
22 | type ObjectCache struct {
23 | mutex sync.RWMutex
24 | values map[string]*cacheItem
25 | getObjectFunc func(string) (*Object, error)
26 | }
27 |
28 | // Initialize and return a new instance of the cache
29 | func NewObjectCache(getObjectFunc func(string) (*Object, error)) *ObjectCache {
30 | return &ObjectCache{
31 | values: make(map[string]*cacheItem),
32 | getObjectFunc: getObjectFunc,
33 | }
34 | }
35 |
36 | func (item *cacheItem) get(key string, cache *ObjectCache) (err error) {
37 | // Calling item.Once.Do
38 | item.Do(func() {
39 | item.object, err = cache.getObjectFunc(key)
40 | })
41 | return
42 | }
43 |
44 | func (cache *ObjectCache) Get(key string) (*Object, error) {
45 | cache.mutex.RLock()
46 | object, exists := cache.values[key]
47 | cache.mutex.RUnlock()
48 | if exists {
49 | return object.object, nil
50 | }
51 | cache.mutex.Lock()
52 | object, exists = cache.values[key]
53 | if !exists {
54 | object = &cacheItem{}
55 | cache.values[key] = object
56 | }
57 | cache.mutex.Unlock()
58 | err := object.get(key, cache)
59 | return object.object, err
60 | }
61 |
62 | var ErrNotFound = errors.New("not found")
63 |
64 | func main() {
65 |
66 | objects := make([]Object, 0)
67 | // Create some objects
68 | for i := 0; i < 1000; i++ {
69 | objects = append(objects, Object{
70 | ID: fmt.Sprint(i),
71 | Name: fmt.Sprintf("Name: %d", i),
72 | })
73 | }
74 |
75 | calledNTimes := 0
76 | cache := NewObjectCache(func(id string) (*Object, error) {
77 | calledNTimes++
78 | for i := range objects {
79 | if objects[i].ID == id {
80 | return &objects[i], nil
81 | }
82 | }
83 | return nil, ErrNotFound
84 | })
85 |
86 | // Ten goroutines get objects from the cache randomly
87 | wg := sync.WaitGroup{}
88 | for i := 0; i < 10; i++ {
89 | wg.Add(1)
90 | go func() {
91 | defer wg.Done()
92 | for i := 0; i < 1000; i++ {
93 | // Pick a random object and add it to the cache
94 | n := rand.Intn(len(objects))
95 | _, err := cache.Get(objects[n].ID)
96 | if err != nil {
97 | fmt.Println(err)
98 | }
99 | }
100 | }()
101 | }
102 | wg.Wait()
103 | fmt.Printf("GetObject func called %d times\n", calledNTimes)
104 | }
105 |
--------------------------------------------------------------------------------
/src/chp17/sorting/service/service_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "net/http"
7 | "net/http/httptest"
8 | "strings"
9 | "testing"
10 | "time"
11 | )
12 |
13 | func TestService(t *testing.T) {
14 | if testing.Short() {
15 | t.Skip("Service")
16 | }
17 | mux := GetServeMux()
18 | server := httptest.NewServer(mux)
19 | defer server.Close()
20 |
21 | rsp, err := http.Post(server.URL+"/sort/asc", "application/json", strings.NewReader("test"))
22 | if err != nil {
23 | t.Error(err)
24 | return
25 | }
26 | // Must return http error
27 | if rsp.StatusCode/100 == 2 {
28 | t.Errorf("Error was expected")
29 | return
30 | }
31 |
32 | data, err := json.Marshal([]time.Time{
33 | time.Date(2023, 2, 1, 12, 8, 37, 0, time.Local),
34 | time.Date(2021, 5, 6, 9, 48, 11, 0, time.Local),
35 | time.Date(2022, 11, 13, 17, 13, 54, 0, time.Local),
36 | time.Date(2022, 6, 23, 22, 29, 28, 0, time.Local),
37 | time.Date(2023, 3, 17, 4, 5, 9, 0, time.Local),
38 | })
39 | if err != nil {
40 | t.Error(err)
41 | return
42 | }
43 | rsp, err = http.Post(server.URL+"/sort/asc", "application/json", bytes.NewReader(data))
44 | if err != nil {
45 | t.Error(err)
46 | return
47 | }
48 | defer rsp.Body.Close()
49 |
50 | if rsp.StatusCode != 200 {
51 | t.Errorf("Expected status code 200, got %d", rsp.StatusCode)
52 | return
53 | }
54 | var output []time.Time
55 | if err := json.NewDecoder(rsp.Body).Decode(&output); err != nil {
56 | t.Error(err)
57 | return
58 | }
59 | for i := 1; i < len(output); i++ {
60 | if !output[i-1].Before(output[i]) {
61 | t.Errorf("Wrong order")
62 | }
63 | }
64 |
65 | }
66 |
67 | func TestHandler(t *testing.T) {
68 | w := httptest.NewRecorder()
69 | data, err := json.Marshal([]time.Time{
70 | time.Date(2023, 2, 1, 12, 8, 37, 0, time.Local),
71 | time.Date(2021, 5, 6, 9, 48, 11, 0, time.Local),
72 | time.Date(2022, 11, 13, 17, 13, 54, 0, time.Local),
73 | time.Date(2022, 6, 23, 22, 29, 28, 0, time.Local),
74 | time.Date(2023, 3, 17, 4, 5, 9, 0, time.Local),
75 | })
76 | if err != nil {
77 | t.Error(err)
78 | return
79 | }
80 | req, _ := http.NewRequest("POST", "localhost/sort/asc", bytes.NewReader(data))
81 | req.Header.Set("Content-Type", "application/json")
82 | HandleSort(w, req, true)
83 | if w.Result().StatusCode != 200 {
84 | t.Errorf("Expecting HTTP 200, got %d", w.Result().StatusCode)
85 | return
86 | }
87 | var output []time.Time
88 | if err := json.NewDecoder(w.Result().Body).Decode(&output); err != nil {
89 | t.Error(err)
90 | return
91 | }
92 | for i := 1; i < len(output); i++ {
93 | if !output[i-1].Before(output[i]) {
94 | t.Errorf("Wrong order")
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/chp5/authenticator/basicauth.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/base64"
5 | "fmt"
6 | "strings"
7 | )
8 |
9 | // BasicAuthCredentials are HTTP basic auth username:password
10 | // credentials
11 | type BasicAuthCredentials struct {
12 | Name string
13 | Password string
14 | }
15 |
16 | // Serialize returns []byte(base64(username:password))
17 | func (cred BasicAuthCredentials) Serialize() []byte {
18 | str := fmt.Sprintf("%s:%s", cred.Name, cred.Password)
19 | target := make([]byte, base64.RawURLEncoding.EncodedLen(len(str)))
20 | base64.RawURLEncoding.Encode(target, []byte(str))
21 | return target
22 | }
23 |
24 | // Type returns "basicauth"
25 | func (BasicAuthCredentials) Type() string { return "basicauth" }
26 |
27 | type BasicAuthParser struct{}
28 |
29 | // Parse parses a basic auth credential obtained by Serialize method
30 | func (BasicAuthParser) Parse(input []byte) (Credentials, error) {
31 | target := make([]byte, base64.RawURLEncoding.DecodedLen(len(input)))
32 | _, err := base64.RawURLEncoding.Decode(target, input)
33 | if err != nil {
34 | return nil, err
35 | }
36 | parts := strings.Split(string(target), ":")
37 | if len(parts) != 2 {
38 | return nil, ErrBadCredentials
39 | }
40 | return BasicAuthCredentials{
41 | Name: parts[0],
42 | Password: parts[1],
43 | }, nil
44 | }
45 |
46 | // BasicAuthAuthenticator authenticates a user based on a map
47 | type BasicAuthAuthenticator struct {
48 | // Map of known user-passwords
49 | users map[string]string
50 | }
51 |
52 | func (auth BasicAuthAuthenticator) Login(credentials Credentials) (Session, error) {
53 | basicAuthCredentials, ok := credentials.(BasicAuthCredentials)
54 | if !ok {
55 | return nil, ErrUnauthorized
56 | }
57 | pwd, ok := auth.users[basicAuthCredentials.Name]
58 | if !ok {
59 | return nil, ErrUnauthorized
60 | }
61 | if pwd != basicAuthCredentials.Password {
62 | return nil, ErrUnauthorized
63 | }
64 | return &BasicAuthSession{
65 | User: basicAuthCredentials.Name,
66 | open: true,
67 | }, nil
68 | }
69 |
70 | // BasicAuthSession represents an active user session
71 | type BasicAuthSession struct {
72 | User string
73 |
74 | open bool
75 | }
76 |
77 | // UserID returns the user id of the session
78 | func (session BasicAuthSession) UserID() string {
79 | return session.User
80 | }
81 |
82 | // Close closes the session
83 | func (session BasicAuthSession) Close() {
84 | session.open = false
85 | }
86 |
87 | type BasicAuthFactory struct{}
88 |
89 | func (BasicAuthFactory) NewInstance() Authenticator {
90 | return &BasicAuthAuthenticator{
91 | users: map[string]string{
92 | "john": "doe",
93 | "jane": "doe",
94 | "foo": "bar",
95 | },
96 | }
97 | }
98 |
99 | func init() {
100 | RegisterAuthenticator("basic", BasicAuthFactory{})
101 | }
102 |
--------------------------------------------------------------------------------
/src/chp10/streaming/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 | "fmt"
7 |
8 | _ "modernc.org/sqlite"
9 | )
10 |
11 | type Result struct {
12 | Err error
13 | Email string
14 | }
15 |
16 | func buildResult(rows *sql.Rows, result *Result) error {
17 | return rows.Scan(&result.Email)
18 | }
19 |
20 | func StreamResults(
21 | ctx context.Context,
22 | db *sql.DB,
23 | query string,
24 | args ...any,
25 | ) (<-chan Result, error) {
26 | rows, err := db.QueryContext(ctx, query, args...)
27 | if err != nil {
28 | return nil, err
29 | }
30 | output := make(chan Result)
31 | go func() {
32 | defer rows.Close()
33 | defer close(output)
34 | var result Result
35 | for rows.Next() {
36 | // Check context cancellation
37 | if result.Err = ctx.Err(); result.Err != nil {
38 | // Context canceled. return
39 | output <- result
40 | return
41 | }
42 | // Set result fields
43 | result.Err = buildResult(rows, &result)
44 | output <- result
45 | }
46 | // If there was an error, return it
47 | if result.Err = rows.Err(); result.Err != nil {
48 | output <- result
49 | }
50 | }()
51 | return output, nil
52 | }
53 |
54 | const createStmt = `create table if not exists users (
55 | email TEXT,
56 | name TEXT)`
57 |
58 | func initDb(conn *sql.DB) {
59 | _, err := conn.ExecContext(context.Background(), createStmt)
60 | if err != nil {
61 | panic(err)
62 | }
63 |
64 | // If there are fewer than 1000 rows, add some
65 | var n int
66 | conn.QueryRowContext(context.Background(), `select count(*) from users`).Scan(&n)
67 | for i := 0; i < 1000-n; i++ {
68 | name := fmt.Sprintf("User-%d", i)
69 | email := fmt.Sprintf("User%d@example.com", i)
70 | conn.ExecContext(context.Background(), `insert into users (name,email) values (?,?)`, name, email)
71 | }
72 | }
73 |
74 | func ProcessResult(result Result) error {
75 | fmt.Println(result)
76 | return nil
77 | }
78 |
79 | func main() {
80 | db, err := sql.Open("sqlite", "chp10.db")
81 | if err != nil {
82 | panic(err)
83 | }
84 | initDb(db)
85 |
86 | // Setup a cancelable context
87 | cancelableCtx, cancel := context.WithCancel(context.Background())
88 | defer cancel()
89 | // Call the streaming API
90 | results, err := StreamResults(cancelableCtx, db, "SELECT EMAIL FROM USERS")
91 | if err != nil {
92 | return
93 | }
94 | // Collect and process results
95 | for result := range results {
96 | if result.Err != nil {
97 | // Handle error in the result
98 | continue
99 | }
100 | // Process the result
101 | if err := ProcessResult(result); err != nil {
102 | // Processing error. Cancel streaming results
103 | cancel()
104 | // Expect to receive at least one more message from the channel,
105 | // because the streaming gorutine sends the error
106 | for range results {
107 | }
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/chp10/connection-pool/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "log"
7 | "net"
8 | "sync"
9 | )
10 |
11 | func server() {
12 | // Listen on TCP port 2000 on all available unicast and
13 | // anycast IP addresses of the local system.
14 | l, err := net.Listen("tcp", ":2000")
15 | if err != nil {
16 | log.Fatal(err)
17 | }
18 | defer l.Close()
19 | for {
20 | // Wait for a connection.
21 | conn, err := l.Accept()
22 | if err != nil {
23 | log.Fatal(err)
24 | }
25 | // Handle the connection in a new goroutine.
26 | // The loop then returns to accepting, so that
27 | // multiple connections may be served concurrently.
28 | go func(c net.Conn) {
29 | // Echo all incoming data.
30 | io.Copy(c, c)
31 | // Shut down the connection.
32 | c.Close()
33 | }(conn)
34 | }
35 | }
36 |
37 | type ConnectionPool struct {
38 | available chan net.Conn
39 | total chan struct{}
40 | }
41 |
42 | func NewConnectionPool(poolSize int) *ConnectionPool {
43 | return &ConnectionPool{
44 | available: make(chan net.Conn, poolSize),
45 | total: make(chan struct{}, poolSize),
46 | }
47 | }
48 |
49 | func (pool *ConnectionPool) GetConnection() (net.Conn, error) {
50 | select {
51 | // If there are connections available in the pool, return one
52 | case conn := <-pool.available:
53 | fmt.Printf("Returning an idle connection.\n")
54 | return conn, nil
55 |
56 | default:
57 | // No connections are available
58 | select {
59 | case conn := <-pool.available:
60 | fmt.Printf("Returning an idle connection.\n")
61 | return conn, nil
62 |
63 | case pool.total <- struct{}{}: // Wait until pool is not full
64 | fmt.Println("Creating a new connection")
65 | // Create a new connection
66 | conn, err := net.Dial("tcp", "localhost:2000")
67 | if err != nil {
68 | return nil, err
69 | }
70 | return conn, nil
71 | }
72 | }
73 | }
74 |
75 | func (pool *ConnectionPool) Release(conn net.Conn) {
76 | pool.available <- conn
77 | fmt.Printf("Releasing a connection. \n")
78 | }
79 |
80 | func (pool *ConnectionPool) Close(conn net.Conn) {
81 | fmt.Println("Closing connection")
82 | conn.Close()
83 | <-pool.total
84 | }
85 |
86 | const PoolSize = 10
87 |
88 | func main() {
89 | go server()
90 |
91 | pool := NewConnectionPool(PoolSize)
92 |
93 | wg := sync.WaitGroup{}
94 | for i := 0; i < 100; i++ {
95 | wg.Add(1)
96 | go func(i int) {
97 | defer wg.Done()
98 | // Get a new connection from the pool
99 | conn, err := pool.GetConnection()
100 | if err != nil {
101 | log.Fatal(err)
102 | }
103 | // Work with the connection
104 | _, err = conn.Write([]byte("test"))
105 | if err != nil {
106 | pool.Close(conn)
107 | return
108 | }
109 | buf := make([]byte, 4)
110 | _, err = io.ReadFull(conn, buf)
111 | if err != nil {
112 | pool.Close(conn)
113 | return
114 | }
115 | pool.Release(conn)
116 | }(i)
117 | }
118 | wg.Wait()
119 | }
120 |
--------------------------------------------------------------------------------
/src/chp12/process/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "context"
6 | "fmt"
7 | "io"
8 | "os"
9 | "os/exec"
10 | "strings"
11 | "time"
12 | )
13 |
14 | // Run "go build" to build the subprocess in the "sub" directory
15 | func buildProgram() {
16 | cmd := exec.Command("go", "build", "-o", "subprocess", ".")
17 | cmd.Dir = "sub"
18 | output, err := cmd.CombinedOutput()
19 | if err != nil {
20 | panic(err)
21 | }
22 | // The build command will not print anything if successful. So if
23 | // there is any output, it is a failure.
24 | if len(output) > 0 {
25 | panic(string(output))
26 | }
27 | }
28 |
29 | // Run the subprocess built before for 10ms, reading from the output
30 | // and error pipes concurrently
31 | func runSubProcessStreamingOutputs() {
32 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
33 | defer cancel()
34 | cmd := exec.CommandContext(ctx, "sub/subprocess")
35 | fmt.Println("Streaming output running for 10 ms")
36 | stdout, err := cmd.StdoutPipe()
37 | if err != nil {
38 | panic(err)
39 | }
40 | stderr, err := cmd.StderrPipe()
41 | if err != nil {
42 | panic(err)
43 | }
44 | // Read from stderr from a separate goroutine
45 | go func() {
46 | io.Copy(os.Stderr, stderr)
47 | }()
48 | err = cmd.Start()
49 | if err != nil {
50 | panic(err)
51 | }
52 | io.Copy(os.Stdout, stdout)
53 | err = cmd.Wait()
54 | if err != nil {
55 | fmt.Println(err)
56 | }
57 | }
58 |
59 | // Run the build subprocess for 10 ms with combined output
60 | func runSubProcessCombinedOutput() {
61 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
62 | defer cancel()
63 | cmd := exec.CommandContext(ctx, "sub/subprocess")
64 | fmt.Println("Streaming combined output running for 10 ms")
65 | cmd.Stdout = os.Stdout
66 | cmd.Stderr = os.Stdout
67 | err := cmd.Start()
68 | if err != nil {
69 | panic(err)
70 | }
71 | err = cmd.Wait()
72 | if err != nil {
73 | fmt.Println(err)
74 | }
75 | }
76 |
77 | func filterSubprocessOutput() {
78 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
79 | defer cancel()
80 | cmd := exec.CommandContext(ctx, "sub/subprocess")
81 | fmt.Println("Filtering stdout running for 10 ms")
82 | pipe, err := cmd.StdoutPipe()
83 | if err != nil {
84 | panic(err)
85 | }
86 |
87 | // Read from the pipe in a separate goroutine
88 | go func() {
89 | scanner := bufio.NewScanner(pipe)
90 | for scanner.Scan() {
91 | line := scanner.Text()
92 | if strings.Index(line, "0") != -1 {
93 | fmt.Printf("Filtered line: %s\n", line)
94 | }
95 | }
96 | if err := scanner.Err(); err != nil {
97 | fmt.Println("Scanner error: %v", err)
98 | }
99 | }()
100 | err = cmd.Start()
101 | if err != nil {
102 | panic(err)
103 | }
104 | err = cmd.Wait()
105 | if err != nil {
106 | fmt.Println(err)
107 | }
108 | }
109 |
110 | func main() {
111 | buildProgram()
112 | runSubProcessStreamingOutputs()
113 | runSubProcessCombinedOutput()
114 | filterSubprocessOutput()
115 | }
116 |
--------------------------------------------------------------------------------
/src/chp9/request-scoped-data/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http"
7 | "sync"
8 |
9 | "github.com/google/uuid"
10 | )
11 |
12 | type requestIDKeyType int
13 |
14 | var requestIDKey requestIDKeyType
15 |
16 | func WithRequestID(ctx context.Context, requestID string) context.Context {
17 | return context.WithValue(ctx, requestIDKey, requestID)
18 | }
19 |
20 | func GetRequestID(ctx context.Context) string {
21 | id, _ := ctx.Value(requestIDKey).(string)
22 | return id
23 | }
24 |
25 | type Privilege uint16
26 |
27 | const (
28 | PrivilegeRead Privilege = 0x0001
29 | PrivilegeWrite Privilege = 0x0002
30 | )
31 |
32 | func GetPrivileges(userID string) map[string]Privilege {
33 | // Return mock privileges for the user
34 | return map[string]Privilege{
35 | "object": PrivilegeRead | PrivilegeWrite,
36 | }
37 | }
38 |
39 | type authInfoKeyType int
40 |
41 | var authInfoKey authInfoKeyType
42 |
43 | type AuthInfo struct {
44 | sync.Mutex
45 | UserID string
46 | privileges map[string]Privilege
47 | }
48 |
49 | func GetAuthInfo(ctx context.Context) *AuthInfo {
50 | info, _ := ctx.Value(authInfoKey).(*AuthInfo)
51 | return info
52 | }
53 |
54 | func (auth *AuthInfo) GetPrivileges() map[string]Privilege {
55 | // Use mutex to initialize the privileges in a thread-safe way
56 | auth.Lock()
57 | defer auth.Unlock()
58 | if auth.privileges == nil {
59 | auth.privileges = GetPrivileges(auth.UserID)
60 | }
61 | return auth.privileges
62 | }
63 |
64 | // Mock authenticator function
65 | func authenticate(_ *http.Request) (*AuthInfo, error) {
66 | return &AuthInfo{
67 | UserID: uuid.New().String(),
68 | privileges: map[string]Privilege{},
69 | }, nil
70 | }
71 |
72 | // Authentication middleware
73 | func AuthMiddleware() func(http.Handler) http.Handler {
74 | return func(next http.Handler) http.Handler {
75 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
76 | // Authenticate the caller
77 | var authInfo *AuthInfo
78 | var err error
79 | authInfo, err = authenticate(r)
80 | if err != nil {
81 | http.Error(w, err.Error(), http.StatusUnauthorized)
82 | return
83 | }
84 | authInfo.privileges = GetPrivileges(authInfo.UserID)
85 | // Create a new context with the authentication info
86 | newCtx := context.WithValue(r.Context(), authInfoKey, authInfo)
87 | // Pass the new context to the next handler
88 | next.ServeHTTP(w, r.WithContext(newCtx))
89 | })
90 | }
91 | }
92 |
93 | func main() {
94 | // Create a new context with a request id
95 | ctx := WithRequestID(context.Background(), uuid.New().String())
96 |
97 | fmt.Printf("Request id in ontext.Background: %s\n", GetRequestID(context.Background()))
98 | fmt.Printf("Request id in context: %s\n", GetRequestID(ctx))
99 |
100 | // Simulate HTTP call here:
101 |
102 | mw := AuthMiddleware()
103 |
104 | req, err := http.NewRequest(http.MethodGet, "localhost:00", nil)
105 | if err != nil {
106 | panic(err)
107 | }
108 | mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
109 | authInfo := GetAuthInfo(r.Context())
110 | fmt.Printf("AuthInfo in context: %+v\n", authInfo)
111 | })).ServeHTTP(nil, req)
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/src/chp11/encode-decode/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "strings"
7 | "time"
8 | )
9 |
10 | type Config struct {
11 | Version string `json:"ver"`
12 | // Encoded as "Name"
13 | Name string
14 | Type string `json:"type,omitempty"` // Encoded as "type",
15 | // and will be omitted if empty
16 | Style string `json:"-"` // Not encoded
17 | value string // Unexported field, not encoded
18 | kind string `json:"kind"` // Unexported field, not encoded
19 |
20 | IntValue int `json:"intValue,omitempty"`
21 | FloatValue float64 `json:"floatValue,omitempty"`
22 |
23 | When *time.Time `json:"when,omitempty"`
24 | HowLong time.Duration `json:"howLong,omitempty"`
25 | }
26 |
27 | type Enclosing struct {
28 | Field string `json:"field"`
29 | Embedded `json:"embedded"`
30 | }
31 |
32 | type Embedded struct {
33 | Field string `json:"embeddedField"`
34 | }
35 |
36 | func main() {
37 | cfg := Config{
38 | Version: "1.1",
39 | Name: "name",
40 | Type: "example",
41 | Style: "json",
42 | value: "example config value",
43 | kind: "test",
44 | }
45 |
46 | data, err := json.Marshal(cfg)
47 | if err != nil {
48 | panic(err)
49 | }
50 | // Marshaled JSON: {"ver":"1.1","Name":"name","type":"example"}
51 |
52 | fmt.Println("Marshaled JSON:", string(data))
53 | if err := json.Unmarshal([]byte(`{
54 | "Ver": "1.2",
55 | "name": "New name",
56 | "value": "val"}`), &cfg); err != nil {
57 | panic(err)
58 | }
59 | // Unmarshaled {Version:1.2 Name:New name Type:example Style:json value:example config value
60 | // kind:test When:0001-01-01 00:00:00 +0000 UTC HowLong:0s}
61 |
62 | fmt.Printf("Unmarshaled %+v\n", cfg)
63 |
64 | enc := Enclosing{
65 | Field: "enclosing",
66 | Embedded: Embedded{
67 | Field: "embedded",
68 | },
69 | }
70 | data, err = json.Marshal(enc)
71 | if err != nil {
72 | panic(err)
73 | }
74 | fmt.Println("Marshaled JSON", string(data))
75 | // Marshaled JSON: {"field":"enclosing","embedded":{"embeddedField":"embedded"}}
76 |
77 | config := map[string]any{
78 | "ver": "1.0",
79 | "Name": "config",
80 | "type": "example",
81 | }
82 | data, err = json.Marshal(config)
83 | if err != nil {
84 | panic(err)
85 | }
86 | fmt.Println("Marshaled JSON", string(data))
87 | // Marshaled JSON {"Name":"config","type":"example","ver":"1.0"}
88 |
89 | numbersWithNil := []any{1, 2, nil, 3}
90 | data, err = json.Marshal(numbersWithNil)
91 | if err != nil {
92 | panic(err)
93 | }
94 | fmt.Println(string(data))
95 | // [1,2,null,3]
96 |
97 | configurations := map[string]map[string]any{
98 | "cfg1": {
99 | "ver": "1.0",
100 | "Name": "config1",
101 | },
102 | "cfg2": {
103 | "ver": "1.1",
104 | "Name": "config2",
105 | },
106 | }
107 | data, err = json.Marshal(configurations)
108 | if err != nil {
109 | panic(err)
110 | }
111 | // {"cfg1":{"Name":"config1","ver":"1.0"},"cfg2":{"Name":"config2","ver":"1.1"}}
112 | fmt.Println(string(data))
113 |
114 | // Decode using json.Number
115 | decoder := json.NewDecoder(strings.NewReader(`[1.1,2,3,4.4]`))
116 | decoder.UseNumber()
117 | var output interface{}
118 | err = decoder.Decode(&output)
119 | if err != nil {
120 | panic(err)
121 | }
122 | fmt.Println(output)
123 | // [1.1 2 3 4.4]
124 | }
125 |
--------------------------------------------------------------------------------
/src/chp17/sorting/sort/sort_test.go:
--------------------------------------------------------------------------------
1 | package sorting
2 |
3 | import (
4 | "math/rand"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func TestSortTimesAscending(t *testing.T) {
10 | input := []time.Time{
11 | time.Date(2023, 2, 1, 12, 8, 37, 0, time.Local),
12 | time.Date(2021, 5, 6, 9, 48, 11, 0, time.Local),
13 | time.Date(2022, 11, 13, 17, 13, 54, 0, time.Local),
14 | time.Date(2022, 6, 23, 22, 29, 28, 0, time.Local),
15 | time.Date(2023, 3, 17, 4, 5, 9, 0, time.Local),
16 | }
17 | t.Logf("Input: %v", input)
18 | output := SortTimes(input, true)
19 | t.Logf("Output: %v", output)
20 | for i := 1; i < len(output); i++ {
21 | if !output[i-1].Before(output[i]) {
22 | t.Error("Wrong order")
23 | }
24 | }
25 | }
26 |
27 | func TestSortTimesDescending(t *testing.T) {
28 | if testing.Short() {
29 | t.Skip("Skipping descending test")
30 | }
31 | input := []time.Time{
32 | time.Date(2023, 2, 1, 12, 8, 37, 0, time.Local),
33 | time.Date(2021, 5, 6, 9, 48, 11, 0, time.Local),
34 | time.Date(2022, 11, 13, 17, 13, 54, 0, time.Local),
35 | time.Date(2022, 6, 23, 22, 29, 28, 0, time.Local),
36 | time.Date(2023, 3, 17, 4, 5, 9, 0, time.Local),
37 | }
38 | output := SortTimes(input, false)
39 | for i := 1; i < len(output); i++ {
40 | if !output[i-1].After(output[i]) {
41 | t.Error("Wrong order")
42 | }
43 | }
44 | }
45 |
46 | func TestSortParallel(t *testing.T) {
47 | input := []time.Time{
48 | time.Date(2023, 2, 1, 12, 8, 37, 0, time.Local),
49 | time.Date(2021, 5, 6, 9, 48, 11, 0, time.Local),
50 | time.Date(2022, 11, 13, 17, 13, 54, 0, time.Local),
51 | time.Date(2022, 6, 23, 22, 29, 28, 0, time.Local),
52 | time.Date(2023, 3, 17, 4, 5, 9, 0, time.Local),
53 | }
54 | t.Run("SortAscending", func(t *testing.T) {
55 | t.Parallel()
56 | output := SortTimes(input, false)
57 | for i := 1; i < len(output); i++ {
58 | if !output[i-1].After(output[i]) {
59 | t.Error("Wrong order")
60 | }
61 | }
62 | })
63 | t.Run("SortDescending", func(t *testing.T) {
64 | t.Parallel()
65 | output := SortTimes(input, false)
66 | for i := 1; i < len(output); i++ {
67 | if !output[i-1].After(output[i]) {
68 | t.Error("Wrong order")
69 | }
70 | }
71 | })
72 | }
73 |
74 | func BenchmarkSortAscending(b *testing.B) {
75 | input := []time.Time{
76 | time.Date(2023, 2, 1, 12, 8, 37, 0, time.Local),
77 | time.Date(2021, 5, 6, 9, 48, 11, 0, time.Local),
78 | time.Date(2022, 11, 13, 17, 13, 54, 0, time.Local),
79 | time.Date(2022, 6, 23, 22, 29, 28, 0, time.Local),
80 | time.Date(2023, 3, 17, 4, 5, 9, 0, time.Local),
81 | }
82 | for i := 0; i < b.N; i++ {
83 | SortTimes(input, true)
84 | }
85 | }
86 |
87 | func benchmarkSort(b *testing.B, nItems int, asc bool) {
88 | input := make([]time.Time, nItems)
89 | t := time.Now().UnixNano()
90 | for i := 0; i < nItems; i++ {
91 | input[i] = time.Unix(0, t-int64(i))
92 | }
93 | rand.Shuffle(len(input), func(i, j int) { input[i], input[j] = input[j], input[i] })
94 | for i := 0; i < b.N; i++ {
95 | SortTimes(input, asc)
96 | }
97 | }
98 |
99 | func BenchmarkSort1000Ascending(b *testing.B) { benchmarkSort(b, 1000, true) }
100 | func BenchmarkSort100Ascending(b *testing.B) { benchmarkSort(b, 100, true) }
101 | func BenchmarkSort10Ascending(b *testing.B) { benchmarkSort(b, 10, true) }
102 | func BenchmarkSort1000Descending(b *testing.B) { benchmarkSort(b, 1000, false) }
103 | func BenchmarkSort100Descending(b *testing.B) { benchmarkSort(b, 100, false) }
104 | func BenchmarkSort10Descending(b *testing.B) { benchmarkSort(b, 10, false) }
105 |
--------------------------------------------------------------------------------
/src/chp13/html-forms/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "html/template"
7 | "net/http"
8 | )
9 |
10 | const form = `
11 |
12 |
13 |
14 |
15 |
16 | {{if .error}}Error: {{.error}}{{end}}
17 |
22 |
23 |
24 | `
25 |
26 | var loginFormTemplate = template.Must(template.New("login").Parse(form))
27 |
28 | type Authenticator struct{}
29 |
30 | func (Authenticator) Authenticate(name, password string) (*http.Cookie, error) {
31 | if name == "test" && password == "password" {
32 | return &http.Cookie{
33 | Name: "test",
34 | Value: name,
35 | MaxAge: 3600,
36 | Path: "/",
37 | }, nil
38 | }
39 | return nil, errors.New("Unknown user")
40 | }
41 |
42 | type UserHandler struct {
43 | Auth Authenticator
44 | }
45 |
46 | func (h UserHandler) HandleLogin(w http.ResponseWriter, req *http.Request) {
47 | // Parse the submitted form. This fills up req.PostForm
48 | // with the submitted information
49 | if err := req.ParseForm(); err != nil {
50 | http.Error(w, err.Error(), http.StatusBadRequest)
51 | return
52 | }
53 | // Get the submitted fields
54 | userName := req.PostForm.Get("userName")
55 | password := req.PostForm.Get("password")
56 | // Handle the login request, and get a cookie
57 | cookie, err := h.Auth.Authenticate(userName, password)
58 | if err != nil {
59 | // Send the user back to login page, setting an error
60 | // cookie containing an error message
61 | http.SetCookie(w, h.NewErrorCookie("Username or password invalid"))
62 | http.Redirect(w, req, "/login.html", http.StatusFound)
63 | return
64 | }
65 | // Set the cookie representing user session
66 | http.SetCookie(w, cookie)
67 | // Redirect the user to the main page
68 | http.Redirect(w, req, "/dashboard.html", http.StatusFound)
69 | }
70 |
71 | func (h UserHandler) ShowLoginPage(w http.ResponseWriter, req *http.Request) {
72 | loginFormData := map[string]any{}
73 | cookie, err := req.Cookie("error_cookie")
74 | fmt.Println(cookie, err)
75 | if err == nil {
76 | loginFormData["error"] = cookie.Value
77 | // Unset the cookie
78 | http.SetCookie(w, &http.Cookie{
79 | Name: "error_cookie",
80 | MaxAge: 0,
81 | })
82 | }
83 | w.Header().Set("Content-Type", "text/html")
84 | loginFormTemplate.Execute(w, loginFormData)
85 | }
86 |
87 | func (h UserHandler) ShowDashboardPage(w http.ResponseWriter, req *http.Request) {
88 | w.Header().Set("Content-Type", "text/plain")
89 | w.Write([]byte("success"))
90 | }
91 |
92 | func (h UserHandler) NewErrorCookie(msg string) *http.Cookie {
93 | return &http.Cookie{
94 | Name: "error_cookie",
95 | Value: msg,
96 | MaxAge: 60, // Cookie lives for 60 seconds
97 | Path: "/",
98 | }
99 | }
100 |
101 | func main() {
102 | authenticator := Authenticator{}
103 | userHandler := UserHandler{
104 | Auth: authenticator,
105 | }
106 | mux := http.NewServeMux()
107 | mux.HandleFunc("POST /auth/login", userHandler.HandleLogin)
108 | mux.HandleFunc("GET /login.html", userHandler.ShowLoginPage)
109 | mux.HandleFunc("GET /dashboard.html", userHandler.ShowDashboardPage)
110 | server := http.Server{
111 | Addr: ":8080",
112 | Handler: mux,
113 | }
114 | fmt.Println("Go to http://localhost:8080/login.html, and login as user 'test' with password 'password'")
115 | server.ListenAndServe()
116 | }
117 |
--------------------------------------------------------------------------------
/src/chp10/simple-pipeline/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "math/rand"
6 | "time"
7 | )
8 |
9 | type InputPayload struct {
10 | Id int
11 | // Add payload data fields here
12 | }
13 |
14 | type Stage2Payload struct {
15 | Id int
16 | // Stage2 data fields here
17 | }
18 |
19 | type Stage3Payload struct {
20 | Id int
21 | // Stage3 data fields here
22 | }
23 |
24 | type OutputPayload struct {
25 | Id int
26 | }
27 |
28 | func processData(id int) error {
29 | // Process data
30 | time.Sleep(time.Millisecond * time.Duration(rand.Intn(100)))
31 | // There may be an error
32 | if rand.Intn(100) < 10 {
33 | return fmt.Errorf("Processing failure for id: %d", id)
34 | }
35 | return nil
36 | }
37 |
38 | type PipelineError struct {
39 | Stage int
40 | Payload any
41 | Err error
42 | }
43 |
44 | func (p PipelineError) Error() string {
45 | return fmt.Sprintf("Pipeline error at stage: %d. Payload: %v. Cause: %s", p.Stage, p.Payload, p.Err)
46 | }
47 |
48 | func Stage1(input <-chan InputPayload, errCh chan<- error) <-chan Stage2Payload {
49 | output := make(chan Stage2Payload)
50 | go func() {
51 | // Close the output channel when done
52 | defer close(output)
53 | // Process all inputs
54 | for in := range input {
55 | // Process data
56 | err := processData(in.Id)
57 | if err != nil {
58 | errCh <- PipelineError{
59 | Stage: 1,
60 | Payload: in,
61 | Err: err,
62 | }
63 | continue
64 | }
65 | output <- Stage2Payload{
66 | Id: in.Id,
67 | }
68 | }
69 | }()
70 | return output
71 | }
72 |
73 | func Stage2(input <-chan Stage2Payload, errCh chan<- error) <-chan Stage3Payload {
74 | output := make(chan Stage3Payload)
75 | go func() {
76 | // Close the output channel when done
77 | defer close(output)
78 | // Process all inputs
79 | for in := range input {
80 | // Process data
81 | err := processData(in.Id)
82 | if err != nil {
83 | errCh <- PipelineError{
84 | Stage: 2,
85 | Payload: in,
86 | Err: err,
87 | }
88 | continue
89 | }
90 | output <- Stage3Payload{
91 | Id: in.Id,
92 | }
93 | }
94 | }()
95 | return output
96 | }
97 |
98 | func Stage3(input <-chan Stage3Payload, errCh chan<- error) <-chan OutputPayload {
99 | output := make(chan OutputPayload)
100 | go func() {
101 | // Close the output channel when done
102 | defer close(output)
103 | // Process all inputs
104 | for in := range input {
105 | // Process data
106 | err := processData(in.Id)
107 | if err != nil {
108 | errCh <- PipelineError{
109 | Stage: 3,
110 | Payload: in,
111 | Err: err,
112 | }
113 | continue
114 | }
115 | output <- OutputPayload{
116 | Id: in.Id,
117 | }
118 | }
119 | }()
120 | return output
121 | }
122 |
123 | func main() {
124 | errCh := make(chan error)
125 | inputCh := make(chan InputPayload)
126 | // Prepare the pipeline by attaching stages
127 | outputCh := Stage3(Stage2(Stage1(inputCh, errCh), errCh), errCh)
128 |
129 | // Feed input asynchronously
130 | go func() {
131 | defer close(inputCh)
132 | for i := 0; i < 1000; i++ {
133 | inputCh <- InputPayload{
134 | Id: i,
135 | }
136 | }
137 | }()
138 |
139 | // Listen to the error channel asynchronously
140 | go func() {
141 | for err := range errCh {
142 | fmt.Println(err)
143 | }
144 | }()
145 |
146 | // Read outputs
147 | for out := range outputCh {
148 | fmt.Println(out)
149 | }
150 | // Close the error channel
151 | close(errCh)
152 |
153 | }
154 |
--------------------------------------------------------------------------------
/src/chp10/workerpool-fixed/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | "time"
7 | )
8 |
9 | type Input struct {
10 | Value int
11 | }
12 |
13 | type Output struct {
14 | Value string
15 | }
16 |
17 | const poolSize = 50
18 |
19 | func doWork(input Input) Output {
20 | // Do some work
21 | time.Sleep(100 * time.Millisecond)
22 | return Output{
23 | Value: fmt.Sprint(input.Value),
24 | }
25 | }
26 |
27 | var inputCnt = 0
28 |
29 | const maxInput = 5000
30 |
31 | func getNextInput() (Input, bool) {
32 | if inputCnt >= maxInput {
33 | return Input{}, true
34 | }
35 | inputCnt++
36 | return Input{
37 | Value: inputCnt,
38 | }, false
39 | }
40 |
41 | func workerPoolWithConcurrentReader() {
42 | inputCnt = 0
43 | // Send inputs to the pool via inputCh
44 | inputCh := make(chan Input)
45 | // Receive outputs from the pool via outputCh
46 | outputCh := make(chan Output)
47 |
48 | // Create the pool of workers
49 | wg := sync.WaitGroup{}
50 | for i := 0; i < poolSize; i++ {
51 | wg.Add(1)
52 | go func() {
53 | defer wg.Done()
54 | for work := range inputCh {
55 | outputCh <- doWork(work)
56 | }
57 | }()
58 | }
59 | // Reader goroutine reads results until outputCh is closed
60 | readerWg := sync.WaitGroup{}
61 | readerWg.Add(1)
62 | go func() {
63 | defer readerWg.Done()
64 | for result := range outputCh {
65 | // process result
66 | fmt.Println(result)
67 | }
68 | }()
69 |
70 | // This goroutine waits until all worker pool goroutines are done, then
71 | // closes the output channel
72 | go func() {
73 | // Wait until processing is complete
74 | wg.Wait()
75 | // Close the output channel so the reader goroutine can terminate
76 | close(outputCh)
77 | }()
78 |
79 | // This loop sends the inputs to the worker pool
80 | for {
81 | nextInput, done := getNextInput()
82 | if done {
83 | break
84 | }
85 | inputCh <- nextInput
86 | }
87 | // Close the input channel, so worker pool goroutines terminate
88 | close(inputCh)
89 | // Wait until the output channel is closed
90 | readerWg.Wait()
91 | // If we are here, all goroutines are done
92 | }
93 |
94 | func workerPoolWithConcurrentWriter() {
95 | inputCnt = 0
96 | // Send inputs to the pool via inputCh
97 | inputCh := make(chan Input)
98 | // Receive outputs from the pool via outputCh
99 | outputCh := make(chan Output)
100 |
101 | // Writer goroutine submits work to the worker pool
102 | go func() {
103 | for {
104 | nextInput, done := getNextInput()
105 | if done {
106 | break
107 | }
108 | inputCh <- nextInput
109 | }
110 | // Close the input channel, so worker pool goroutines terminate
111 | close(inputCh)
112 | }()
113 |
114 | // Create the pool of workers
115 | wg := sync.WaitGroup{}
116 | for i := 0; i < poolSize; i++ {
117 | wg.Add(1)
118 | go func() {
119 | defer wg.Done()
120 | for work := range inputCh {
121 | outputCh <- doWork(work)
122 | }
123 | }()
124 | }
125 |
126 | // This goroutine waits until all worker pool goroutines are done, then
127 | // closes the output channel
128 | go func() {
129 | // Wait until processing is complete
130 | wg.Wait()
131 | // Close the output channel so the reader goroutine can terminate
132 | close(outputCh)
133 | }()
134 |
135 | // Read results until outputCh is closed
136 | for result := range outputCh {
137 | // process result
138 | fmt.Println(result)
139 | }
140 |
141 | }
142 |
143 | func main() {
144 | workerPoolWithConcurrentReader()
145 | workerPoolWithConcurrentWriter()
146 | }
147 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Go Recipes for Developers
2 |
3 |
4 |
5 | This is the code repository for [Go Recipes for Developers](https://www.packtpub.com/en-us/product/go-recipes-for-developers-9781835464786), published by Packt.
6 |
7 | **Top techniques and practical solutions for real-life Go programming problems**
8 |
9 | ## What is this book about?
10 | This recipe-based guide starts with a basic project structure and advances to explore best practices applicable to common situations and not-so-common problems every engineer faces during their work.
11 |
12 | This book covers the following exciting features:
13 | * Understand how to structure projects
14 | * Find out how to process text with Go tools
15 | * Discover how to work with arrays, slices, and maps
16 | * Implement robust error handling patterns
17 | * Explore concurrent data processing for Go programs
18 | * Get up to speed with how to control processes
19 | * Integrate Go applications with databases
20 | * Understand how to test, benchmark, and profile Go programs
21 |
22 | If you feel this book is for you, get your [copy](https://a.co/d/hxca2hS) today!
23 |
24 | ## Instructions and Navigations
25 |
26 | Some code may appear slightly different between the Chapter and the Repository. While the functionality remains the same, the code is not exactly identical. For example, in Chapter 5: in one instance, it is referred to as 'Package Auth' in the Chapter, whereas it is 'Package Main' in the Repository to ensure the code is executable.
27 |
28 | All of the code is organized into folders. For example, Chp2.
29 |
30 | The code will look like the following:
31 |
32 | ```
33 | ctx:=context.Background()
34 | cancelable, cancel:=context.WithCancel(ctx)
35 | defer cancel()
36 | ```
37 |
38 | **Following is what you need for this book:**
39 | This book is for any developer with a basic understanding of the Go language. If you’re a senior developer, you can use it as a reference for finding useful examples they can apply to different use cases..
40 |
41 | With the following software and hardware list you can run all code files present in the book.
42 |
43 | ## Software and Hardware List
44 | | Software/ Hardware required | OS required/ Other requirements |
45 | | ------------------------------------ | ----------------------------------- |
46 | | Go 1.22 or newer | Windows, macOS, or Linux |
47 |
48 | ## Related products
49 | * System Programming Essentials with Go [[Packt]](https://www.packtpub.com/en-us/product/system-programming-essentials-with-go-9781801813440) [[Amazon]](https://a.co/d/9bBmOqZ)
50 | * Domain-Driven Design with Golang [[Packt]](https://www.packtpub.com/en-us/product/domain-driven-design-with-golang-9781804619261) [[Amazon]](https://a.co/d/32Uzd54)
51 |
52 | ## Get to Know the Author
53 | **Burak Serdar** is a software engineer with over 30 years of experience designing and developing distributed applications. He has used Go to create backend software, data processing platforms, interactive applications, and automation systems. Burak has worked for both startups and large corporations as an engineer and technical lead. He holds B.Sc. and M.Sc. degrees in Electrical and Electronics Engineering, as well as an M.Sc. degree in Computer Science.
54 |
55 | ## Other Books by the Author
56 | * Effective Concurrency in Go [[Packt]](https://www.packtpub.com/en-us/product/effective-concurrency-in-go-9781804619070) [[Amazon]](https://a.co/d/04ScE6a)
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/src/chp15/postgresql-running-statements/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "database/sql"
5 | "errors"
6 | "fmt"
7 | "log"
8 | "time"
9 |
10 | _ "github.com/jackc/pgx/v5/stdlib"
11 | "golang.org/x/exp/rand"
12 | )
13 |
14 | /**
15 | You can run this example with a postgres container:
16 |
17 | docker run -p 15432:5432 -e POSTGRES_PASSWORD=mysecretpassword -d postgres:17
18 |
19 |
20 | The PGX driver uses $n for statement arguments instead of ?, so this
21 | example is adjusted to use $n convention.
22 | */
23 |
24 | type User struct {
25 | Id string
26 | Name string
27 | Email string
28 | LastLogin *time.Time
29 | }
30 |
31 | func main() {
32 | // Open the sqlite database using the given local file ./database.db
33 | db, err := sql.Open("pgx", "postgres://postgres:mysecretpassword@localhost:15432/postgres")
34 | if err != nil {
35 | log.Fatal(err)
36 | }
37 | defer db.Close()
38 | // You don't need to ping an embedded database
39 | db.Exec(`CREATE TABLE users (
40 | user_id varchar(32),
41 | user_name varchar(128),
42 | email varchar(128),
43 | last_login timestamp)`)
44 |
45 | // Add some users to the database
46 | users := make([]User, 0)
47 | for i := 0; i < 100; i++ {
48 | t := time.Now().Add(time.Duration(-rand.Intn(1000)) * time.Minute)
49 | users = append(users, User{
50 | Id: fmt.Sprint(i),
51 | Name: fmt.Sprintf("User-%d", i),
52 | Email: fmt.Sprintf("user%d@example.com", i),
53 | LastLogin: &t,
54 | })
55 | }
56 |
57 | if err := AddUsers(db, users); err != nil {
58 | panic(err)
59 | }
60 | fmt.Println("Inserted 100 users")
61 |
62 | fmt.Println("First 10 users fetched by id")
63 | for i := 0; i < 10; i++ {
64 | user, err := GetUserByID(db, fmt.Sprint(i))
65 | if err != nil {
66 | panic(err)
67 | }
68 | fmt.Printf("User %+v\n", user)
69 | }
70 |
71 | t := time.Now().Add(-time.Hour)
72 | fmt.Printf("Users logged in within the last 10 hours (%v)\n", t)
73 | names, err := GetUserNamesLoggedInAfter(db, t)
74 | if err != nil {
75 | panic(err)
76 | }
77 | fmt.Println(names)
78 | }
79 |
80 | func AddUsers(db *sql.DB, users []User) error {
81 | stmt, err := db.Prepare(`INSERT INTO users (user_id,user_name,email,last_login) VALUES ($1,$2,$3,$4)`)
82 | if err != nil {
83 | return err
84 | }
85 | // Close the prepared statement when done
86 | defer stmt.Close()
87 | for _, user := range users {
88 | // Run the prepared statement with different arguments
89 | _, err := stmt.Exec(user.Id, user.Name, user.Email, user.LastLogin)
90 | if err != nil {
91 | return err
92 | }
93 | }
94 | return nil
95 | }
96 |
97 | func GetUserByID(db *sql.DB, id string) (*User, error) {
98 | var user User
99 | err := db.QueryRow(`SELECT user_id, user_name, last_login FROM
100 | users WHERE user_id=$1`, id).
101 | Scan(&user.Id, &user.Name, &user.LastLogin)
102 | if errors.Is(err, sql.ErrNoRows) {
103 | return nil, nil
104 | }
105 | if err != nil {
106 | return nil, err
107 | }
108 | return &user, nil
109 | }
110 |
111 | func GetUserNamesLoggedInAfter(db *sql.DB, after time.Time) ([]string, error) {
112 | rows, err := db.Query(`SELECT users.user_name FROM users WHERE last_login > $1`, after)
113 | if err != nil {
114 | return nil, err
115 | }
116 | defer rows.Close()
117 | names := make([]string, 0)
118 | for rows.Next() {
119 | var name string
120 | if err := rows.Scan(&name); err != nil {
121 | return nil, err
122 | }
123 | names = append(names, name)
124 | }
125 | // Check if iteration produced any errors
126 | if err := rows.Err(); err != nil {
127 | return nil, err
128 | }
129 | return names, nil
130 | }
131 |
--------------------------------------------------------------------------------
/src/chp16/slog/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "log/slog"
6 | "os"
7 | "time"
8 | )
9 |
10 | func LoggingWithLevels() {
11 |
12 | logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
13 | Level: slog.LevelDebug,
14 | }))
15 |
16 | logger.Debug("This is a debug message")
17 | logger.Info("This is an info message with an integer argument", "arg", 42)
18 | logger.Warn("This is a warning message with a string argument", "arg", "foo")
19 |
20 | // Checking if logging is enabled for a specific level
21 | if logger.Enabled(context.Background(), slog.LevelError) {
22 | logger.Error("This is an error message", slog.String("arg", "foo"))
23 | }
24 | }
25 |
26 | func ChangingLogLevel() {
27 |
28 | level := new(slog.LevelVar)
29 | level.Set(slog.LevelDebug)
30 | logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
31 | Level: level,
32 | }))
33 |
34 | logger.Debug("This is a debug message")
35 | logger.Info("This is an info message with an integer argument", "arg", 42)
36 | logger.Warn("This is a warning message with a string argument", "arg", "foo")
37 |
38 | // Set the log level to info
39 | level.Set(slog.LevelInfo)
40 | logger.Debug("This is a debug message")
41 | logger.Info("This is an info message with an integer argument", "arg", 42)
42 | logger.Warn("This is a warning message with a string argument", "arg", "foo")
43 |
44 | }
45 |
46 | type ContextIDHandler struct {
47 | slog.Handler
48 | }
49 |
50 | func (h ContextIDHandler) Handle(ctx context.Context, r slog.Record) error {
51 | // If the context has a string id, retrieve it and add it to the record
52 | if id, ok := ctx.Value("id").(string); ok {
53 | r.Add(slog.String("id", id))
54 | }
55 | return h.Handler.Handle(ctx, r)
56 | }
57 |
58 | func JSONLogging() {
59 | logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
60 | Level: slog.LevelDebug,
61 | },
62 | ))
63 |
64 | logger.Info("This is a debug message in JSON",
65 | slog.Time("now", time.Now()),
66 | slog.String("stringValue", "bar"),
67 | slog.Duration("durationValue", time.Second))
68 | logger.Info("This is an info message in JSON", slog.Time("now", time.Now()))
69 |
70 | }
71 |
72 | func AddingContextInfo() {
73 | logger := slog.New(&ContextIDHandler{
74 | Handler: slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
75 | Level: slog.LevelDebug,
76 | },
77 | ),
78 | })
79 |
80 | // Create a new context with an id
81 | ctx := context.WithValue(context.Background(), "id", "123")
82 |
83 | logger.Info("This is an info message without a context id")
84 | logger.InfoContext(ctx, "This is an info message without a context id")
85 | }
86 |
87 | func Grouping() {
88 | logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
89 | Level: slog.LevelDebug,
90 | },
91 | ))
92 |
93 | logger.Debug("This is a debug message with no group", slog.String("key1", "value1"))
94 | logger.Debug("This is a debug message with group as argument", slog.Group("group1", slog.String("key1", "value1")))
95 | l1 := logger.WithGroup("group2")
96 | l1.Debug("This is a debug message in group2", slog.String("key2", "value2"))
97 |
98 | }
99 |
100 | func WithAttributes() {
101 | logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
102 | Level: slog.LevelDebug,
103 | },
104 | ))
105 |
106 | logger.Debug("This is a debug message with no additional attributes", slog.String("key", "value"))
107 |
108 | l1 := logger.With(slog.String("handler", "a"), slog.String("reqId", "reqId"))
109 | l1.Debug("This is a debug message with id", slog.String("key", "value"))
110 |
111 | }
112 |
113 | func main() {
114 | LoggingWithLevels()
115 | ChangingLogLevel()
116 | JSONLogging()
117 | AddingContextInfo()
118 | Grouping()
119 | WithAttributes()
120 | }
121 |
--------------------------------------------------------------------------------
/src/chp15/postgresql-transactions/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 | "errors"
7 | "fmt"
8 | "log"
9 | "time"
10 |
11 | _ "github.com/jackc/pgx/v5/stdlib"
12 | "golang.org/x/exp/rand"
13 | )
14 |
15 | /**
16 | You can run this example with a postgres container:
17 |
18 | docker run -p 15432:5432 -e POSTGRES_PASSWORD=mysecretpassword -d postgres:17
19 |
20 |
21 | The PGX driver uses $n for statement arguments instead of ?, so this
22 | example is adjusted to use $n convention.
23 | */
24 |
25 | type User struct {
26 | Id string
27 | Name string
28 | Email string
29 | LastLogin *time.Time
30 | }
31 |
32 | func main() {
33 | // Open the sqlite database using the given local file ./database.db
34 | db, err := sql.Open("pgx", "postgres://postgres:mysecretpassword@localhost:15432/postgres")
35 | if err != nil {
36 | log.Fatal(err)
37 | }
38 | defer db.Close()
39 | // You don't need to ping an embedded database
40 | db.Exec(`CREATE TABLE users (
41 | user_id varchar(32),
42 | user_name varchar(128),
43 | email varchar(128),
44 | last_login timestamp)`)
45 |
46 | // Add some users to the database
47 | users := make([]User, 0)
48 | for i := 0; i < 100; i++ {
49 | t := time.Now().Add(time.Duration(-rand.Intn(1000)) * time.Minute)
50 | users = append(users, User{
51 | Id: fmt.Sprint(i),
52 | Name: fmt.Sprintf("User-%d", i),
53 | Email: fmt.Sprintf("user%d@example.com", i),
54 | LastLogin: &t,
55 | })
56 | }
57 |
58 | // Add users in a single transaction
59 | ctx, cancel := context.WithCancel(context.Background())
60 | defer cancel()
61 | // 1. Start transaction
62 | tx, err := db.BeginTx(ctx, &sql.TxOptions{
63 | Isolation: sql.LevelReadCommitted,
64 | })
65 | if err != nil {
66 | panic(err)
67 | }
68 |
69 | if err := AddUsers(tx, users); err != nil {
70 | tx.Rollback()
71 | panic(err)
72 | }
73 | tx.Commit()
74 |
75 | fmt.Println("Inserted 100 users in a single transaction")
76 |
77 | fmt.Println("First 10 users fetched by id")
78 | for i := 0; i < 10; i++ {
79 | user, err := GetUserByID(db, fmt.Sprint(i))
80 | if err != nil {
81 | panic(err)
82 | }
83 | fmt.Printf("User %+v\n", user)
84 | }
85 |
86 | t := time.Now().Add(-time.Hour)
87 | fmt.Printf("Users logged in within the last 10 hours (%v)\n", t)
88 | names, err := GetUserNamesLoggedInAfter(db, t)
89 | if err != nil {
90 | panic(err)
91 | }
92 | fmt.Println(names)
93 | }
94 |
95 | func AddUsers(tx *sql.Tx, users []User) error {
96 | stmt, err := tx.Prepare(`INSERT INTO users (user_id,user_name,email,last_login) VALUES ($1,$2,$3,$4)`)
97 | if err != nil {
98 | return err
99 | }
100 | // Close the prepared statement when done
101 | defer stmt.Close()
102 | for _, user := range users {
103 | // Run the prepared statement with different arguments
104 | _, err := stmt.Exec(user.Id, user.Name, user.Email, user.LastLogin)
105 | if err != nil {
106 | return err
107 | }
108 | }
109 | return nil
110 | }
111 |
112 | func GetUserByID(db *sql.DB, id string) (*User, error) {
113 | var user User
114 | err := db.QueryRow(`SELECT user_id, user_name, last_login FROM
115 | users WHERE user_id=$1`, id).
116 | Scan(&user.Id, &user.Name, &user.LastLogin)
117 | if errors.Is(err, sql.ErrNoRows) {
118 | return nil, nil
119 | }
120 | if err != nil {
121 | return nil, err
122 | }
123 | return &user, nil
124 | }
125 |
126 | func GetUserNamesLoggedInAfter(db *sql.DB, after time.Time) ([]string, error) {
127 | rows, err := db.Query(`SELECT users.user_name FROM users WHERE last_login > $1`, after)
128 | if err != nil {
129 | return nil, err
130 | }
131 | defer rows.Close()
132 | names := make([]string, 0)
133 | for rows.Next() {
134 | var name string
135 | if err := rows.Scan(&name); err != nil {
136 | return nil, err
137 | }
138 | names = append(names, name)
139 | }
140 | // Check if iteration produced any errors
141 | if err := rows.Err(); err != nil {
142 | return nil, err
143 | }
144 | return names, nil
145 | }
146 |
--------------------------------------------------------------------------------
/src/chp10/parallel-pipeline/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "math/rand"
6 | "sync"
7 | "time"
8 | )
9 |
10 | type InputPayload struct {
11 | Id int
12 | // Add payload data fields here
13 | }
14 |
15 | type Stage2Payload struct {
16 | Id int
17 | // Stage2 data fields here
18 | }
19 |
20 | type Stage3Payload struct {
21 | Id int
22 | // Stage3 data fields here
23 | }
24 |
25 | type OutputPayload struct {
26 | Id int
27 | }
28 |
29 | func processData(id int) error {
30 | // Process data
31 | time.Sleep(time.Millisecond * time.Duration(rand.Intn(100)))
32 | // There may be an error
33 | if rand.Intn(100) < 10 {
34 | return fmt.Errorf("Processing failure for id: %d", id)
35 | }
36 | return nil
37 | }
38 |
39 | type PipelineError struct {
40 | Stage int
41 | Payload any
42 | Err error
43 | }
44 |
45 | func (p PipelineError) Error() string {
46 | return fmt.Sprintf("Pipeline error at stage: %d. Payload: %v. Cause: %s", p.Stage, p.Payload, p.Err)
47 | }
48 |
49 | func Stage1(input <-chan InputPayload, errCh chan<- error, nInstances int) <-chan Stage2Payload {
50 | output := make(chan Stage2Payload)
51 | // Close the output channel when all the processing is done
52 | wg := sync.WaitGroup{}
53 | for i := 0; i < nInstances; i++ {
54 | wg.Add(1)
55 | go func() {
56 | defer wg.Done()
57 | // Process all inputs
58 | for in := range input {
59 | // Process data
60 | err := processData(in.Id)
61 | if err != nil {
62 | errCh <- PipelineError{
63 | Stage: 1,
64 | Payload: in,
65 | Err: err,
66 | }
67 | continue
68 | }
69 | output <- Stage2Payload{
70 | Id: in.Id,
71 | }
72 | }
73 | }()
74 | }
75 | go func() {
76 | wg.Wait()
77 | close(output)
78 | }()
79 | return output
80 | }
81 |
82 | func Stage2(input <-chan Stage2Payload, errCh chan<- error, nInstances int) <-chan Stage3Payload {
83 | output := make(chan Stage3Payload)
84 | // Close the output channel when all the processing is done
85 | wg := sync.WaitGroup{}
86 | for i := 0; i < nInstances; i++ {
87 | wg.Add(1)
88 | go func() {
89 | defer wg.Done()
90 | // Process all inputs
91 | for in := range input {
92 | // Process data
93 | err := processData(in.Id)
94 | if err != nil {
95 | errCh <- PipelineError{
96 | Stage: 2,
97 | Payload: in,
98 | Err: err,
99 | }
100 | continue
101 | }
102 | output <- Stage3Payload{
103 | Id: in.Id,
104 | }
105 | }
106 | }()
107 | }
108 | go func() {
109 | wg.Wait()
110 | close(output)
111 | }()
112 | return output
113 | }
114 |
115 | func Stage3(input <-chan Stage3Payload, errCh chan<- error, nInstances int) <-chan OutputPayload {
116 | output := make(chan OutputPayload)
117 | // Close the output channel when all the processing is done
118 | wg := sync.WaitGroup{}
119 | for i := 0; i < nInstances; i++ {
120 | wg.Add(1)
121 | go func() {
122 | defer wg.Done()
123 | // Process all inputs
124 | for in := range input {
125 | // Process data
126 | err := processData(in.Id)
127 | if err != nil {
128 | errCh <- PipelineError{
129 | Stage: 3,
130 | Payload: in,
131 | Err: err,
132 | }
133 | continue
134 | }
135 | output <- OutputPayload{
136 | Id: in.Id,
137 | }
138 | }
139 | }()
140 | }
141 | go func() {
142 | wg.Wait()
143 | close(output)
144 | }()
145 | return output
146 | }
147 |
148 | func main() {
149 | errCh := make(chan error)
150 | inputCh := make(chan InputPayload)
151 | nInstances := 5
152 | // Prepare the pipeline by attaching stages
153 | outputCh := Stage3(Stage2(Stage1(inputCh, errCh, nInstances), errCh, nInstances), errCh, nInstances)
154 |
155 | // Feed input asynchronously
156 | go func() {
157 | defer close(inputCh)
158 | for i := 0; i < 1000; i++ {
159 | inputCh <- InputPayload{
160 | Id: i,
161 | }
162 | }
163 | }()
164 |
165 | // Listen to the error channel asynchronously
166 | go func() {
167 | for err := range errCh {
168 | fmt.Println(err)
169 | }
170 | }()
171 |
172 | // Read outputs
173 | for out := range outputCh {
174 | fmt.Println(out)
175 | }
176 | // Close the error channel
177 | close(errCh)
178 |
179 | }
180 |
--------------------------------------------------------------------------------
/src/chp10/fanin/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "math/rand"
6 | "sync"
7 | "time"
8 | )
9 |
10 | type InputPayload struct {
11 | Id int
12 | // Add payload data fields here
13 | }
14 |
15 | type Stage2Payload struct {
16 | Id int
17 | // Stage2 data fields here
18 | }
19 |
20 | type Stage3Payload struct {
21 | Id int
22 | // Stage3 data fields here
23 | }
24 |
25 | type OutputPayload struct {
26 | Id int
27 | }
28 |
29 | func processData(id int) error {
30 | // Process data
31 | time.Sleep(time.Millisecond * time.Duration(rand.Intn(100)))
32 | // There may be an error
33 | if rand.Intn(100) < 10 {
34 | return fmt.Errorf("Processing failure for id: %d", id)
35 | }
36 | return nil
37 | }
38 |
39 | type PipelineError struct {
40 | Stage int
41 | Payload any
42 | Err error
43 | }
44 |
45 | func (p PipelineError) Error() string {
46 | return fmt.Sprintf("Pipeline error at stage: %d. Payload: %v. Cause: %s", p.Stage, p.Payload, p.Err)
47 | }
48 |
49 | func Stage1(input <-chan InputPayload, errCh chan<- error) <-chan Stage2Payload {
50 | output := make(chan Stage2Payload)
51 | go func() {
52 | defer close(output)
53 | // Process all inputs
54 | for in := range input {
55 | // Process data
56 | err := processData(in.Id)
57 | if err != nil {
58 | errCh <- PipelineError{
59 | Stage: 1,
60 | Payload: in,
61 | Err: err,
62 | }
63 | continue
64 | }
65 | output <- Stage2Payload{
66 | Id: in.Id,
67 | }
68 | }
69 | }()
70 | return output
71 | }
72 |
73 | func Stage2(input <-chan Stage2Payload, errCh chan<- error) <-chan Stage3Payload {
74 | output := make(chan Stage3Payload)
75 | go func() {
76 | defer close(output)
77 | // Process all inputs
78 | for in := range input {
79 | // Process data
80 | err := processData(in.Id)
81 | if err != nil {
82 | errCh <- PipelineError{
83 | Stage: 2,
84 | Payload: in,
85 | Err: err,
86 | }
87 | continue
88 | }
89 | output <- Stage3Payload{
90 | Id: in.Id,
91 | }
92 | }
93 | }()
94 | return output
95 | }
96 |
97 | func Stage3(input <-chan Stage3Payload, errCh chan<- error) <-chan OutputPayload {
98 | output := make(chan OutputPayload)
99 | go func() {
100 | defer close(output)
101 | // Process all inputs
102 | for in := range input {
103 | // Process data
104 | err := processData(in.Id)
105 | if err != nil {
106 | errCh <- PipelineError{
107 | Stage: 3,
108 | Payload: in,
109 | Err: err,
110 | }
111 | continue
112 | }
113 | output <- OutputPayload{
114 | Id: in.Id,
115 | }
116 | }
117 | }()
118 | return output
119 | }
120 |
121 | func fanIn(inputs []<-chan OutputPayload) <-chan OutputPayload {
122 |
123 | result := make(chan OutputPayload)
124 |
125 | // Listen to input channels in separate goroutines
126 | inputWg := sync.WaitGroup{}
127 | for inputIndex := range inputs {
128 | inputWg.Add(1)
129 | go func(index int) {
130 | defer inputWg.Done()
131 | for data := range inputs[index] {
132 | // Send the data to the output
133 | result <- data
134 | }
135 | }(inputIndex)
136 | }
137 |
138 | // When all input channels are closed, close the fan in ch
139 | go func() {
140 | inputWg.Wait()
141 | close(result)
142 | }()
143 |
144 | return result
145 | }
146 |
147 | func main() {
148 | errCh := make(chan error)
149 | inputCh := make(chan InputPayload)
150 |
151 | poolSize := 5
152 | outputs := make([]<-chan OutputPayload, 0)
153 | // All Stage1 goroutines listen to a single input channel
154 | for i := 0; i < poolSize; i++ {
155 | outputCh1 := Stage1(inputCh, errCh)
156 | outputCh2 := Stage2(outputCh1, errCh)
157 | outputCh3 := Stage3(outputCh2, errCh)
158 | outputs = append(outputs, outputCh3)
159 | }
160 |
161 | outputCh := fanIn(outputs)
162 |
163 | // Feed input asynchronously
164 | go func() {
165 | defer close(inputCh)
166 | for i := 0; i < 1000; i++ {
167 | inputCh <- InputPayload{
168 | Id: i,
169 | }
170 | }
171 | }()
172 |
173 | // Listen to the error channel asynchronously
174 | go func() {
175 | for err := range errCh {
176 | fmt.Println(err)
177 | }
178 | }()
179 |
180 | // Read outputs
181 | for out := range outputCh {
182 | fmt.Println(out)
183 | }
184 | // Close the error channel
185 | close(errCh)
186 |
187 | }
188 |
--------------------------------------------------------------------------------
/src/chp10/sorting-fanin/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "container/heap"
5 | "fmt"
6 | "math/rand"
7 | "sync"
8 | "time"
9 | )
10 |
11 | type InputPayload struct {
12 | Id int
13 | // Add payload data fields here
14 | }
15 |
16 | type Stage2Payload struct {
17 | Id int
18 | Err error
19 | // Stage2 data fields here
20 | }
21 |
22 | type Stage3Payload struct {
23 | Id int
24 | Err error
25 | // Stage3 data fields here
26 | }
27 |
28 | type OutputPayload struct {
29 | Id int
30 | Err error
31 | }
32 |
33 | func processData(id int) error {
34 | // Process data
35 | time.Sleep(time.Millisecond * time.Duration(rand.Intn(100)))
36 | // There may be an error
37 | if rand.Intn(100) < 10 {
38 | return fmt.Errorf("Processing failure for id: %d", id)
39 | }
40 | return nil
41 | }
42 |
43 | type PipelineError struct {
44 | Stage int
45 | Payload any
46 | Err error
47 | }
48 |
49 | func (p PipelineError) Error() string {
50 | return fmt.Sprintf("Pipeline error at stage: %d. Payload: %v. Cause: %s", p.Stage, p.Payload, p.Err)
51 | }
52 |
53 | func Stage1(input <-chan InputPayload) <-chan Stage2Payload {
54 | output := make(chan Stage2Payload)
55 | go func() {
56 | defer close(output)
57 | // Process all inputs
58 | for in := range input {
59 | // Process data
60 | err := processData(in.Id)
61 | if err != nil {
62 | err = PipelineError{
63 | Stage: 1,
64 | Payload: in,
65 | Err: err,
66 | }
67 | }
68 | output <- Stage2Payload{
69 | Id: in.Id,
70 | Err: err,
71 | }
72 | }
73 | }()
74 | return output
75 | }
76 |
77 | func Stage2(input <-chan Stage2Payload) <-chan Stage3Payload {
78 | output := make(chan Stage3Payload)
79 | go func() {
80 | defer close(output)
81 | // Process all inputs
82 | for in := range input {
83 | if in.Err != nil {
84 | output <- Stage3Payload{
85 | Id: in.Id,
86 | Err: in.Err,
87 | }
88 | continue
89 | }
90 | // Process data
91 | err := processData(in.Id)
92 | if err != nil {
93 | err = PipelineError{
94 | Stage: 2,
95 | Payload: in,
96 | Err: err,
97 | }
98 | }
99 | output <- Stage3Payload{
100 | Id: in.Id,
101 | Err: err,
102 | }
103 | }
104 | }()
105 | return output
106 | }
107 |
108 | func Stage3(input <-chan Stage3Payload) <-chan OutputPayload {
109 | output := make(chan OutputPayload)
110 | go func() {
111 | defer close(output)
112 | // Process all inputs
113 | for in := range input {
114 | // Process data
115 | if in.Err != nil {
116 | output <- OutputPayload{
117 | Id: in.Id,
118 | Err: in.Err,
119 | }
120 | continue
121 | }
122 | err := processData(in.Id)
123 | if err != nil {
124 | err = PipelineError{
125 | Stage: 3,
126 | Payload: in,
127 | Err: err,
128 | }
129 | }
130 | output <- OutputPayload{
131 | Id: in.Id,
132 | Err: err,
133 | }
134 | }
135 | }()
136 | return output
137 | }
138 |
139 | type SortQueue []OutputPayload
140 |
141 | func (q SortQueue) Len() int { return len(q) }
142 | func (q SortQueue) Less(i, j int) bool {
143 | return q[i].Id < q[j].Id
144 | }
145 |
146 | func (q SortQueue) Swap(i, j int) {
147 | q[i], q[j] = q[j], q[i]
148 | }
149 |
150 | func (q *SortQueue) Push(x any) {
151 | *q = append(*q, x.(OutputPayload))
152 | }
153 |
154 | func (q *SortQueue) Pop() any {
155 | old := *q
156 | n := len(old)
157 | item := old[n-1]
158 | *q = old[0 : n-1]
159 | return item
160 | }
161 |
162 | func order(input <-chan OutputPayload, bufsize int) <-chan OutputPayload {
163 | result := make(chan OutputPayload)
164 | queue := SortQueue{}
165 | go func() {
166 | defer close(result)
167 | for data := range input {
168 | // Add new item to the queue
169 | heap.Push(&queue, data)
170 | // If the queue grew enough, pop
171 | for len(queue) >= bufsize {
172 | result <- heap.Pop(&queue).(OutputPayload)
173 | }
174 | }
175 | for len(queue) > 0 {
176 | result <- heap.Pop(&queue).(OutputPayload)
177 | }
178 | }()
179 |
180 | return result
181 | }
182 |
183 | func fanIn(inputs []<-chan OutputPayload) <-chan OutputPayload {
184 |
185 | result := make(chan OutputPayload)
186 |
187 | // Listen to input channels in separate goroutines
188 | inputWg := sync.WaitGroup{}
189 | for inputIndex := range inputs {
190 | inputWg.Add(1)
191 | go func(index int) {
192 | defer inputWg.Done()
193 | for data := range inputs[index] {
194 | // Send the data to the output
195 | result <- data
196 | }
197 | }(inputIndex)
198 | }
199 |
200 | // When all input channels are closed, close the fan in ch
201 | go func() {
202 | inputWg.Wait()
203 | close(result)
204 | }()
205 |
206 | return result
207 | }
208 |
209 | func main() {
210 | inputCh := make(chan InputPayload)
211 |
212 | poolSize := 5
213 | outputs := make([]<-chan OutputPayload, 0)
214 | // All Stage1 goroutines listen to a single input channel
215 | for i := 0; i < poolSize; i++ {
216 | outputCh1 := Stage1(inputCh)
217 | outputCh2 := Stage2(outputCh1)
218 | outputCh3 := Stage3(outputCh2)
219 | outputs = append(outputs, outputCh3)
220 | }
221 |
222 | outputCh := order(fanIn(outputs), 15)
223 |
224 | // Feed input asynchronously
225 | go func() {
226 | defer close(inputCh)
227 | for i := 0; i < 1000; i++ {
228 | inputCh <- InputPayload{
229 | Id: i,
230 | }
231 | }
232 | }()
233 |
234 | // Read outputs
235 | for out := range outputCh {
236 | fmt.Println(out)
237 | }
238 |
239 | }
240 |
--------------------------------------------------------------------------------
/src/go.sum:
--------------------------------------------------------------------------------
1 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
2 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
5 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
6 | github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
7 | github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
8 | github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
9 | github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
10 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
11 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
12 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
13 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
14 | github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
15 | github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
16 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
17 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
18 | github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
19 | github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
20 | github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
21 | github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
22 | github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
23 | github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
24 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
25 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
26 | github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
27 | github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
28 | github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
29 | github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
30 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
31 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
32 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
33 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
34 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
35 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
36 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
37 | golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
38 | golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
39 | golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
40 | golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
41 | golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
42 | golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
43 | golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
44 | golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
45 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
46 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
47 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
48 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
49 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
50 | golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
51 | golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
52 | golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
53 | golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
54 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
55 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
56 | modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
57 | modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
58 | modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
59 | modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
60 | modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
61 | modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
62 | modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
63 | modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
64 | modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
65 | modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
66 | modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
67 | modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
68 | modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
69 | modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
70 | modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
71 | modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
72 | modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
73 | modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
74 | modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
75 | modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
76 | modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
77 | modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
78 | modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
79 | modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
80 | modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
81 | modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
82 |
--------------------------------------------------------------------------------
/src/chp15/postgresql-queryvalues/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 | "fmt"
7 | "log"
8 | "strings"
9 | "time"
10 |
11 | _ "github.com/jackc/pgx/v5/stdlib"
12 | "golang.org/x/exp/rand"
13 | )
14 |
15 | /**
16 | You can run this example with a postgres container:
17 |
18 | docker run -p 15432:5432 -e POSTGRES_PASSWORD=mysecretpassword -d postgres:17
19 |
20 |
21 | The PGX driver uses $n for statement arguments instead of ?, so this
22 | example is adjusted to use $n convention.
23 | */
24 |
25 | type User struct {
26 | ID uint64
27 | Name string
28 | LastLogin time.Time
29 | AvatarURL string
30 | }
31 |
32 | type UpdateUserRequest struct {
33 | Name *string
34 | LastLogin *time.Time
35 | AvatarURL *string
36 | }
37 |
38 | type UserSearchRequest struct {
39 | Ids []uint64
40 | Name *string
41 | LoggedInBefore *time.Time
42 | LoggedInAfter *time.Time
43 | AvatarURL *string
44 | }
45 |
46 | func main() {
47 | // Open the sqlite database using the given local file ./database.db
48 | db, err := sql.Open("pgx", "postgres://postgres:mysecretpassword@localhost:15432/postgres")
49 | if err != nil {
50 | log.Fatal(err)
51 | }
52 | defer db.Close()
53 | // You don't need to ping an embedded database
54 | db.Exec(`CREATE TABLE user_info (
55 | user_id int not null,
56 | user_name varchar(32) not null,
57 | last_login timestamp null,
58 | avatar_url varchar(128) null
59 | )`)
60 |
61 | {
62 | // Add some users to the database
63 | users := make([]User, 0)
64 | for i := 0; i < 100; i++ {
65 | t := time.Now().Add(time.Duration(-rand.Intn(1000)) * time.Minute)
66 | users = append(users, User{
67 | ID: uint64(i),
68 | Name: fmt.Sprintf("User-%d", i),
69 | AvatarURL: fmt.Sprintf("http://@example.com/%d_avatar", i),
70 | LastLogin: t,
71 | })
72 | }
73 |
74 | if err := AddUsers(db, users); err != nil {
75 | panic(err)
76 | }
77 | fmt.Println("Inserted 100 users")
78 | }
79 |
80 | after := time.Now().Add(-time.Hour)
81 | rows, err := db.Query(`SELECT user_id, user_name, last_login, avatar_url FROM user_info WHERE last_login > $1`, after)
82 | if err != nil {
83 | panic(err)
84 | }
85 | // Close the rows object when done
86 | defer rows.Close()
87 |
88 | users := make([]User, 0)
89 | for rows.Next() {
90 | // Retrieve data from this row
91 | var user User
92 | // avatar column is nullable, so we pass a *string instead of string
93 | var avatarURL *string
94 | if err := rows.Scan(
95 | &user.ID,
96 | &user.Name,
97 | &user.LastLogin,
98 | &avatarURL); err != nil {
99 | panic(err)
100 | }
101 | // avatar URL can be nil in the db
102 | if avatarURL != nil {
103 | user.AvatarURL = *avatarURL
104 | }
105 | users = append(users, user)
106 | }
107 | // Check if there was an error during iteration
108 | if err := rows.Err(); err != nil {
109 | panic(err)
110 | }
111 | fmt.Println(users)
112 |
113 | // Update one user
114 | now := time.Now()
115 | urlString := "https://example.org/avatar.jpg"
116 | update := UpdateUserRequest{
117 | LastLogin: &now,
118 | AvatarURL: &urlString,
119 | }
120 |
121 | err = UpdateUser(context.Background(), db, 1, &update)
122 | if err != nil {
123 | panic(err)
124 | }
125 |
126 | users, err = SearchUsers(context.Background(), db, &UserSearchRequest{
127 | Ids: []uint64{1, 2, 3, 4, 5},
128 | })
129 | if err != nil {
130 | panic(err)
131 | }
132 | fmt.Println("Users with id=1,2,3,4,5", users)
133 |
134 | str := "User-1"
135 | users, err = SearchUsers(context.Background(), db, &UserSearchRequest{
136 | Name: &str,
137 | })
138 | if err != nil {
139 | panic(err)
140 | }
141 | fmt.Println("Users with name=User-1", users)
142 | }
143 |
144 | func AddUsers(db *sql.DB, users []User) error {
145 | stmt, err := db.Prepare(`INSERT INTO user_info (user_id,user_name,last_login,avatar_url) VALUES ($1,$2,$3,$4)`)
146 | if err != nil {
147 | return err
148 | }
149 | // Close the prepared statement when done
150 | defer stmt.Close()
151 | for _, user := range users {
152 | // Run the prepared statement with different arguments
153 | _, err := stmt.Exec(user.ID, user.Name, user.LastLogin, user.AvatarURL)
154 | if err != nil {
155 | return err
156 | }
157 | }
158 | return nil
159 | }
160 |
161 | func UpdateUser(ctx context.Context, db *sql.DB, userId uint64, req *UpdateUserRequest) error {
162 | query := strings.Builder{}
163 | args := make([]interface{}, 0)
164 | // Start building the query. Be mindful of spaces to separate uery clauses
165 | query.WriteString("UPDATE user_info SET ")
166 | if req.Name != nil {
167 | args = append(args, *req.Name)
168 | fmt.Fprintf(&query, "user_name=$%d", len(args))
169 | }
170 | if req.LastLogin != nil {
171 | if len(args) > 0 {
172 | query.WriteString(",")
173 | }
174 | args = append(args, *req.LastLogin)
175 | fmt.Fprintf(&query, "last_login=$%d", len(args))
176 | }
177 | if req.AvatarURL != nil {
178 | if len(args) > 0 {
179 | query.WriteString(",")
180 | }
181 | args = append(args, *req.AvatarURL)
182 | fmt.Fprintf(&query, "avatar_url=$%d", len(args))
183 | }
184 | args = append(args, userId)
185 | fmt.Fprintf(&query, " WHERE user_id=$%d", len(args))
186 | _, err := db.ExecContext(ctx, query.String(), args...)
187 | return err
188 | }
189 |
190 | func SearchUsers(ctx context.Context, db *sql.DB, req *UserSearchRequest) ([]User, error) {
191 | query := strings.Builder{}
192 | where := strings.Builder{}
193 | args := make([]interface{}, 0)
194 | // Start building the query. Be mindful of spaces to separate query clauses
195 | query.WriteString("SELECT user_id, user_name, last_login,avatar_url FROM user_info ")
196 |
197 | if len(req.Ids) > 0 {
198 | // Add this to the WHERE clause with an AND
199 | if where.Len() > 0 {
200 | where.WriteString(" AND ")
201 | }
202 | // Build an IN clause.
203 | // We have to add one argument for each id
204 | where.WriteString("user_id IN (")
205 | for i, id := range req.Ids {
206 | if i > 0 {
207 | where.WriteString(",")
208 | }
209 | args = append(args, id)
210 | fmt.Fprintf(&where, "$%d", len(args))
211 | }
212 | where.WriteString(")")
213 | }
214 | if req.Name != nil {
215 | if where.Len() > 0 {
216 | where.WriteString(" AND ")
217 | }
218 | args = append(args, *req.Name)
219 | fmt.Fprintf(&where, "user_name=$%d", len(args))
220 | }
221 | if req.LoggedInBefore != nil {
222 | if where.Len() > 0 {
223 | where.WriteString(" AND ")
224 | }
225 | args = append(args, *req.LoggedInBefore)
226 | fmt.Fprintf(&where, "last_login<$%d", len(args))
227 | }
228 | if req.LoggedInAfter != nil {
229 | if where.Len() > 0 {
230 | where.WriteString(" AND ")
231 | }
232 | args = append(args, *req.LoggedInAfter)
233 | fmt.Fprintf(&where, "last_login>$%d", len(args))
234 | }
235 | if req.AvatarURL != nil {
236 | if where.Len() > 0 {
237 | where.WriteString(" AND ")
238 | }
239 | args = append(args, *req.AvatarURL)
240 | fmt.Fprintf(&where, "avatar_url=$%d", len(args))
241 | }
242 | if where.Len() > 0 {
243 | query.WriteString(" WHERE ")
244 | query.WriteString(where.String())
245 | }
246 | rows, err := db.QueryContext(ctx, query.String(), args...)
247 | if err != nil {
248 | return nil, err
249 | }
250 | users := make([]User, 0)
251 | for rows.Next() {
252 | // Retrieve data from this row
253 | var user User
254 | // avatar column is nullable, so we pass a *string instead of string
255 | var avatarURL *string
256 | if err := rows.Scan(
257 | &user.ID,
258 | &user.Name,
259 | &user.LastLogin,
260 | &avatarURL); err != nil {
261 | return nil, err
262 | }
263 | // avatar URL can be nil in the db
264 | if avatarURL != nil {
265 | user.AvatarURL = *avatarURL
266 | }
267 | users = append(users, user)
268 | }
269 | // Check if there was an error during iteration
270 | if err := rows.Err(); err != nil {
271 | return nil, err
272 | }
273 | return users, nil
274 | }
275 |
--------------------------------------------------------------------------------