├── 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 |
8 |
9 |
10 | 11 |
12 |
13 |
14 |
15 | 16 |
17 | 18 |
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 |
20 | 21 | 22 | 23 |
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 |
18 | 19 | 20 | 21 |
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 | --------------------------------------------------------------------------------